• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Camelcade / Perl5-IDEA / #525521824

24 Apr 2026 06:38PM UTC coverage: 76.187% (+0.2%) from 75.952%
#525521824

push

github

hurricup
Pass a disposable to Registry.get to revert it in the end

14757 of 22542 branches covered (65.46%)

Branch coverage included in aggregate %.

31096 of 37643 relevant lines covered (82.61%)

0.83 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

71.81
/plugin/docker/src/main/java/com/perl5/lang/perl/idea/sdk/host/docker/PerlDockerAdapter.java
1
/*
2
 * Copyright 2015-2026 Alexandr Evstigneev
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License");
5
 * you may not use this file except in compliance with the License.
6
 * You may obtain a copy of the License at
7
 *
8
 * http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 * Unless required by applicable law or agreed to in writing, software
11
 * distributed under the License is distributed on an "AS IS" BASIS,
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 * See the License for the specific language governing permissions and
14
 * limitations under the License.
15
 */
16

17
package com.perl5.lang.perl.idea.sdk.host.docker;
18

19
import com.intellij.execution.CommandLineUtil;
20
import com.intellij.execution.ExecutionException;
21
import com.intellij.execution.process.ProcessOutput;
22
import com.intellij.execution.util.ExecUtil;
23
import com.intellij.openapi.application.ApplicationManager;
24
import com.intellij.openapi.application.PathManager;
25
import com.intellij.openapi.diagnostic.Logger;
26
import com.intellij.openapi.module.Module;
27
import com.intellij.openapi.module.ModuleManager;
28
import com.intellij.openapi.progress.ProgressManager;
29
import com.intellij.openapi.project.Project;
30
import com.intellij.openapi.roots.ModuleRootManager;
31
import com.intellij.openapi.util.SystemInfo;
32
import com.intellij.openapi.util.io.FileUtil;
33
import com.intellij.openapi.util.text.StringUtil;
34
import com.intellij.openapi.vfs.VfsUtil;
35
import com.intellij.openapi.vfs.VirtualFile;
36
import com.intellij.util.ArrayUtil;
37
import com.intellij.util.containers.ContainerUtil;
38
import com.perl5.lang.perl.idea.execution.PerlCommandLine;
39
import com.perl5.lang.perl.idea.project.PerlProjectManager;
40
import com.perl5.lang.perl.idea.sdk.host.PerlExecutionException;
41
import com.perl5.lang.perl.idea.sdk.host.PerlFileDescriptor;
42
import com.perl5.lang.perl.idea.sdk.host.PerlHostData;
43
import com.perl5.lang.perl.idea.sdk.host.PerlHostHandler;
44
import com.perl5.lang.perl.util.PerlPluginUtil;
45
import org.jetbrains.annotations.NotNull;
46

47
import java.io.File;
48
import java.io.IOException;
49
import java.util.*;
50
import java.util.stream.Collectors;
51

52
/**
53
 * Adapter running command using docker cli
54
 */
55
class PerlDockerAdapter {
56
  private static final Logger LOG = Logger.getInstance(PerlDockerAdapter.class);
1✔
57
  private static final String CONTAINER_NAME_PREFIX = "intellijPerl_";
58
  private static final String KILL = "kill";
59
  private static final String RUN = "run";
60
  private static final String EXEC = "exec";
61
  private static final String WITH_AUTOREMOVE = "--rm";
62
  private static final String WITH_ENTRYPOINT = "--entrypoint";
63
  private static final String AS_DAEMON = "-d";
64
  private static final String WITH_CONTAINER_NAME = "--name";
65
  private static final String COPY = "cp";
66
  private static final String AS_ARCHIVE = "--archive";
67
  private static final String FOLLOWING_LINKS = "--follow-link";
68
  private static final String IMAGE = "image";
69
  private static final String LIST_IMAGE = "ls";
70
  private static final String IN_FORMAT = "--format";
71
  private static final String INTERACTIVELY = "-i";
72
  private static final String WITH_ATTACHED = "-a";
73
  private static final String STDOUT = "stdout";
74
  private static final String STDERR = "stderr";
75
  private static final String STDIN = "stdin";
76
  private static final String WITH_TTY = "-t";
77
  private static final String WITH_VOLUME = "-v";
78
  private static final String EXPOSE_PORT = "--expose";
79
  private static final String PUBLISH_PORT = "-p";
80
  private static final String WORKING_DIRECTORY = "-w";
81
  static final String DOCKER_EXECUTABLE = SystemInfo.isWindows ? "docker.exe" : "docker";
1✔
82

83
  private final @NotNull PerlDockerData myData;
84

85
  public PerlDockerAdapter(@NotNull PerlDockerData data) {
1✔
86
    myData = data;
1✔
87
  }
1✔
88

89
  /**
90
   * @return new container name, generated from {@code containerNameSeed}
91
   */
92
  public @NotNull String createRunningContainer(@NotNull String containerNameSeed) throws ExecutionException {
93
    String containerName = createContainerName(containerNameSeed);
1✔
94
    runCommand(RUN, AS_DAEMON, WITH_AUTOREMOVE,
1✔
95
               WITH_ENTRYPOINT, "",
96
               WITH_CONTAINER_NAME, containerName,
97
               myData.getImageName(), "bash", "-c", "while true;do sleep 1000000;done");
1✔
98
    return containerName;
1!
99
  }
100

101
  public void copyRemote(@NotNull String containerName, @NotNull String remotePath, @NotNull String localPath) throws ExecutionException {
102
    try {
103
      File localPathFile = new File(localPath);
1✔
104
      FileUtil.createDirectory(localPathFile);
1✔
105
      runCommand(COPY, AS_ARCHIVE, FOLLOWING_LINKS, containerName + ':' + remotePath, localPathFile.getParent());
1✔
106
    }
107
    catch (PerlExecutionException e) {
1✔
108
      ProcessOutput processOutput = e.getProcessOutput();
1✔
109
      String stderr = processOutput.getStderr();
1✔
110
      if (!stderr.contains("no such file or directory") &&
1!
111
          !stderr.contains("Could not find the file") &&
1!
112
          !stderr.contains("No such container:path")) {
×
113
        throw e;
×
114
      }
115
    }
1✔
116
  }
1✔
117

118
  public void killContainer(@NotNull String... containers) throws ExecutionException {
119
    runCommand(ArrayUtil.mergeArrays(new String[]{KILL}, containers));
1✔
120
  }
1✔
121

122
  private @NotNull ProcessOutput checkOutput(@NotNull ProcessOutput output) throws ExecutionException {
123
    if (output.getExitCode() != 0) {
1✔
124
      throw new PerlExecutionException(output);
1✔
125
    }
126
    return output;
1!
127
  }
128

129
  /**
130
   * @return contents of {@code path} in the container.
131
   */
132
  public @NotNull List<PerlFileDescriptor> listFiles(@NotNull String containerName, @NotNull String path) {
133
    try {
134
      if (ApplicationManager.getApplication().isDispatchThread()) {
1!
135
        return ProgressManager.getInstance().runProcessWithProgressSynchronously(
×
136
          () -> doListFiles(containerName, path),
×
137
          PerlDockerBundle.message("docker.adapter.listing.files.in", path),
×
138
          true,
139
          null
140
        );
141
      }
142
      else {
143
        return doListFiles(containerName, path);
1✔
144
      }
145
    }
146
    catch (ExecutionException e) {
×
147
      LOG.error(e);
×
148
      return Collections.emptyList();
×
149
    }
150
  }
151

152
  private @NotNull List<PerlFileDescriptor> doListFiles(@NotNull String containerName, @NotNull String path) throws ExecutionException {
153
    try {
154
      ProcessOutput output = runCommand(EXEC, containerName, "ls", "-LAs", "--classify", path);
1✔
155
      return output.getStdoutLines().stream()
1!
156
        .map(it -> PerlFileDescriptor.create(path, it))
1✔
157
        .filter(Objects::nonNull)
1✔
158
        .collect(Collectors.toList());
1✔
159
    }
160
    catch (ExecutionException e) {
×
161
      if (StringUtil.contains(e.getMessage(), "cannot access")) {
×
162
        LOG.warn("Could not access " + path + " " + e.getMessage());
×
163
        return Collections.emptyList();
×
164
      }
165
      throw e;
×
166
    }
167
  }
168

169
  private static String createContainerName(@NotNull String seed) {
170
    return CONTAINER_NAME_PREFIX + seed + "_" + System.currentTimeMillis();
1✔
171
  }
172

173
  private @NotNull ProcessOutput runCommand(@NotNull String... params) throws ExecutionException {
174
    return checkOutput(PerlHostData.execAndGetOutput(baseCommandLine().withParameters(params)));
1✔
175
  }
176

177
  /**
178
   * Wrapping a {@code commandLine} to a script and runs it using {@code docker} command, returning it's process
179
   */
180
  public Process createProcess(@NotNull PerlCommandLine commandLine) throws ExecutionException {
181
    PerlCommandLine dockerCommandLine = buildBaseProcessCommandLine(commandLine);
1✔
182

183
    // mounting helpers
184
    dockerCommandLine.withParameters(WITH_VOLUME, PerlPluginUtil.getPluginHelpersRoot() + ':' + myData.getHelpersRootPath());
1✔
185

186
    if (!commandLine.isUserCommandLine()) {
1✔
187
      dockerCommandLine.withParameters(WITH_ENTRYPOINT, "");
1✔
188
    }
189

190
    Project project = commandLine.getEffectiveProject();
1✔
191
    if (project != null) {
1✔
192
      // mounting modules roots
193
      Set<VirtualFile> roots = new HashSet<>();
1✔
194
      for (Module module : ModuleManager.getInstance(project).getModules()) {
1✔
195
        roots.addAll(Arrays.asList(ModuleRootManager.getInstance(module).getContentRoots()));
1✔
196
      }
197
      roots.addAll(PerlProjectManager.getInstance(project).getExternalLibraryRoots());
1✔
198
      for (VirtualFile rootToMount : VfsUtil.getCommonAncestors(roots.toArray(VirtualFile.EMPTY_ARRAY))) {
1✔
199
        String localPath = rootToMount.getPath();
1✔
200
        String remotePath = myData.getRemotePath(localPath);
1✔
201
        dockerCommandLine.withParameters(WITH_VOLUME, localPath + ':' + remotePath);
1✔
202
      }
203

204
      // adding project settings if possible
205
      dockerCommandLine
1✔
206
        .withParameters(StringUtil.split(PerlDockerProjectSettings.getInstance(project).getAdditionalDockerParameters(), " "));
1✔
207
    }
208

209
    // working directory
210
    File remoteWorkingDirectory = myData.getRemotePath(commandLine.getWorkDirectory());
1✔
211
    if (remoteWorkingDirectory != null) {
1✔
212
      dockerCommandLine.withParameters(WORKING_DIRECTORY + "=" + StringUtil.escapeChar(
1✔
213
        FileUtil.toSystemIndependentName(remoteWorkingDirectory.toString()), ' '));
1✔
214
    }
215

216
    // required by coverage, probably we should have a getter for this; Also contains a temp path
217
    String localSystemPath = PathManager.getSystemPath();
1✔
218
    dockerCommandLine.withParameters(WITH_VOLUME, localSystemPath + ':' + myData.getRemotePath(localSystemPath));
1✔
219

220
    // we sure that command script is under system dir
221
    File script = createCommandScript(commandLine);
1✔
222
    String dockerScriptPath = myData.getRemotePath(script.getPath());
1✔
223
    if (StringUtil.isEmpty(dockerScriptPath)) {
1!
224
      throw new ExecutionException(PerlDockerBundle.message("dialog.message.unable.to.map.path.for.in", script.getPath(), myData));
×
225
    }
226

227
    return dockerCommandLine.withParameters(myData.getImageName(), "sh", dockerScriptPath).createProcess();
1✔
228
  }
229

230
  /**
231
   * @return list of images in {@code name[:tag]} format
232
   */
233
  List<String> listImages() throws ExecutionException {
234
    ProcessOutput output = runCommand(IMAGE, LIST_IMAGE, IN_FORMAT, "{{.Repository}}:{{.Tag}}");
×
235
    return output.getStdoutLines().stream()
×
236
      .map(it -> StringUtil.replace(it, ":<none>", ""))
×
237
      .filter(it -> !StringUtil.equals(it, "<none>"))
×
238
      .sorted().collect(Collectors.toList());
×
239
  }
240

241
  private static PerlCommandLine baseCommandLine() {
242
    return new PerlCommandLine(DOCKER_EXECUTABLE).withHostData(PerlHostHandler.getDefaultHandler().createData());
1✔
243
  }
244

245
  static @NotNull PerlCommandLine buildBaseProcessCommandLine(@NotNull PerlCommandLine commandLine) {
246
    PerlCommandLine dockerCommandLine = baseCommandLine()
1✔
247
      .withParameters(RUN, WITH_AUTOREMOVE, INTERACTIVELY)
1✔
248
      .withParameters(WITH_ATTACHED, STDOUT, WITH_ATTACHED, STDERR, WITH_ATTACHED, STDIN)
1✔
249
      .withCharset(commandLine.getCharset());
1✔
250

251
    if (commandLine.isUsePty()) {
1✔
252
      dockerCommandLine.withParameters(WITH_TTY);
1✔
253
      dockerCommandLine.withPty(true);
1✔
254
    }
255

256
    // mapping ports
257
    commandLine.getPortMappings().forEach(
1✔
258
      it -> dockerCommandLine.withParameters(EXPOSE_PORT,
1✔
259
                                             String.valueOf(it.getRemote()),
1✔
260
                                             PUBLISH_PORT, it.getLocal() + ":" + it.getRemote()));
1✔
261
    return dockerCommandLine;
1!
262
  }
263

264
  private @NotNull File createCommandScript(@NotNull PerlCommandLine commandLine) throws ExecutionException {
265
    StringBuilder sb = new StringBuilder();
1✔
266
    commandLine.getEnvironment().forEach((key, val) -> sb.append("export ").append(key).append('=')
1✔
267
      .append(CommandLineUtil.posixQuote(val)).append("\n"));
1✔
268
    sb.append(String.join(" ", ContainerUtil.map(commandLine.getCommandLineList(null), CommandLineUtil::posixQuote)));
1✔
269

270
    try {
271
      String command = sb.toString();
1✔
272
      LOG.debug("Executing in ", myData.getImageName());
1✔
273
      StringUtil.split(command, "\n").forEach(it -> LOG.debug("    ", it));
1✔
274
      var dockerWrapper = ExecUtil.createTempExecutableScript("dockerWrapper", "", command);
1✔
275
      LOG.debug("Created docker wrapper: ", dockerWrapper);
1✔
276
      return dockerWrapper;
1!
277
    }
278
    catch (IOException e) {
×
279
      throw new ExecutionException(e);
×
280
    }
281
  }
282
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc