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

Camelcade / Perl5-IDEA / #525521576

05 Jun 2025 06:17AM UTC coverage: 82.298% (-0.02%) from 82.318%
#525521576

push

github

hurricup
Localized strings and improved annotations

26 of 41 new or added lines in 19 files covered. (63.41%)

22 existing lines in 6 files now uncovered.

30837 of 37470 relevant lines covered (82.3%)

0.82 hits per line

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

75.23
/plugin/docker/src/main/java/com/perl5/lang/perl/idea/sdk/host/docker/PerlDockerAdapter.java
1
/*
2
 * Copyright 2015-2025 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 CONTAINER = "container";
65
  private static final String REMOVE = "rm";
66
  private static final String CREATE = "create";
67
  private static final String WITH_CONTAINER_NAME = "--name";
68
  private static final String COPY = "cp";
69
  private static final String AS_ARCHIVE = "--archive";
70
  private static final String FOLLOWING_LINKS = "--follow-link";
71
  private static final String IMAGE = "image";
72
  private static final String LIST_IMAGE = "ls";
73
  private static final String IN_FORMAT = "--format";
74
  private static final String INTERACTIVELY = "-i";
75
  private static final String WITH_ATTACHED = "-a";
76
  private static final String STDOUT = "stdout";
77
  private static final String STDERR = "stderr";
78
  private static final String STDIN = "stdin";
79
  private static final String WITH_TTY = "-t";
80
  private static final String WITH_VOLUME = "-v";
81
  private static final String EXPOSE_PORT = "--expose";
82
  private static final String PUBLISH_PORT = "-p";
83
  private static final String WORKING_DIRECTORY = "-w";
84
  static final String DOCKER_EXECUTABLE = SystemInfo.isWindows ? "docker.exe" : "docker";
1✔
85

86
  private final @NotNull PerlDockerData myData;
87

88
  public PerlDockerAdapter(@NotNull PerlDockerData data) {
1✔
89
    myData = data;
1✔
90
  }
1✔
91

92
  /**
93
   * @return new container name, generated from {@code containerNameSeed}
94
   */
95
  public @NotNull String createContainer(@NotNull String containerNameSeed) throws ExecutionException {
96
    String containerName = createContainerName(containerNameSeed);
×
97
    runCommand(CONTAINER, CREATE, WITH_CONTAINER_NAME, containerName, myData.getImageName());
×
98
    return containerName;
×
99
  }
100

101
  /**
102
   * @return new container name, generated from {@code containerNameSeed}
103
   */
104
  public @NotNull String createRunningContainer(@NotNull String containerNameSeed) throws ExecutionException {
105
    String containerName = createContainerName(containerNameSeed);
1✔
106
    runCommand(RUN, AS_DAEMON, WITH_AUTOREMOVE,
1✔
107
               WITH_ENTRYPOINT, "",
108
               WITH_CONTAINER_NAME, containerName,
109
               myData.getImageName(), "bash", "-c", "while true;do sleep 1000000;done");
1✔
110
    return containerName;
1✔
111
  }
112

113
  public void copyRemote(@NotNull String containerName, @NotNull String remotePath, @NotNull String localPath) throws ExecutionException {
114
    try {
115
      File localPathFile = new File(localPath);
1✔
116
      FileUtil.createDirectory(localPathFile);
1✔
117
      runCommand(COPY, AS_ARCHIVE, FOLLOWING_LINKS, containerName + ':' + remotePath, localPathFile.getParent());
1✔
118
    }
119
    catch (PerlExecutionException e) {
1✔
120
      ProcessOutput processOutput = e.getProcessOutput();
1✔
121
      String stderr = processOutput.getStderr();
1✔
122
      if (!stderr.contains("no such file or directory") &&
1✔
123
          !stderr.contains("Could not find the file") &&
1✔
124
          !stderr.contains("No such container:path")) {
×
125
        throw e;
×
126
      }
127
    }
1✔
128
  }
1✔
129

130
  public void killContainer(@NotNull String... containers) throws ExecutionException {
131
    runCommand(ArrayUtil.mergeArrays(new String[]{KILL}, containers));
1✔
132
  }
1✔
133

134
  private void dropContainer(@NotNull String containerName) throws ExecutionException {
135
    runCommand(CONTAINER, REMOVE, containerName);
×
136
  }
×
137

138
  private @NotNull ProcessOutput checkOutput(@NotNull ProcessOutput output) throws ExecutionException {
139
    if (output.getExitCode() != 0) {
1✔
140
      throw new PerlExecutionException(output);
1✔
141
    }
142
    return output;
1✔
143
  }
144

145
  public @NotNull PerlDockerData getData() {
146
    return myData;
×
147
  }
148

149
  /**
150
   * @return contents of {@code path} in the container.
151
   */
152
  public @NotNull List<PerlFileDescriptor> listFiles(@NotNull String containerName, @NotNull String path) {
153
    try {
154
      if (ApplicationManager.getApplication().isDispatchThread()) {
1✔
155
        return ProgressManager.getInstance().runProcessWithProgressSynchronously(
×
156
          () -> doListFiles(containerName, path),
×
157
          PerlDockerBundle.message("docker.adapter.listing.files.in", path),
×
158
          true,
159
          null
160
        );
161
      }
162
      else {
163
        return doListFiles(containerName, path);
1✔
164
      }
165
    }
166
    catch (ExecutionException e) {
×
167
      LOG.error(e);
×
168
      return Collections.emptyList();
×
169
    }
170
  }
171

172
  private @NotNull List<PerlFileDescriptor> doListFiles(@NotNull String containerName, @NotNull String path) throws ExecutionException {
173
    try {
174
      ProcessOutput output = runCommand(EXEC, containerName, "ls", "-LAs", "--classify", path);
1✔
175
      return output.getStdoutLines().stream()
1✔
176
        .map(it -> PerlFileDescriptor.create(path, it))
1✔
177
        .filter(Objects::nonNull)
1✔
178
        .collect(Collectors.toList());
1✔
179
    }
180
    catch (ExecutionException e) {
×
181
      if (StringUtil.contains(e.getMessage(), "cannot access")) {
×
182
        LOG.warn("Could not access " + path + " " + e.getMessage());
×
183
        return Collections.emptyList();
×
184
      }
185
      throw e;
×
186
    }
187
  }
188

189
  private static String createContainerName(@NotNull String seed) {
190
    return CONTAINER_NAME_PREFIX + seed + "_" + System.currentTimeMillis();
1✔
191
  }
192

193
  private @NotNull ProcessOutput runCommand(@NotNull String... params) throws ExecutionException {
194
    return checkOutput(PerlHostData.execAndGetOutput(baseCommandLine().withParameters(params)));
1✔
195
  }
196

197
  /**
198
   * Wrapping a {@code commandLine} to a script and runs it using {@code docker} command, returning it's process
199
   */
200
  public Process createProcess(@NotNull PerlCommandLine commandLine) throws ExecutionException {
201
    PerlCommandLine dockerCommandLine = buildBaseProcessCommandLine(commandLine);
1✔
202

203
    // mounting helpers
204
    dockerCommandLine.withParameters(WITH_VOLUME, PerlPluginUtil.getPluginHelpersRoot() + ':' + myData.getHelpersRootPath());
1✔
205

206
    if (!commandLine.isUserCommandLine()) {
1✔
207
      dockerCommandLine.withParameters(WITH_ENTRYPOINT, "");
1✔
208
    }
209

210
    Project project = commandLine.getEffectiveProject();
1✔
211
    if (project != null) {
1✔
212
      // mounting modules roots
213
      Set<VirtualFile> roots = new HashSet<>();
1✔
214
      for (Module module : ModuleManager.getInstance(project).getModules()) {
1✔
215
        roots.addAll(Arrays.asList(ModuleRootManager.getInstance(module).getContentRoots()));
1✔
216
      }
217
      roots.addAll(PerlProjectManager.getInstance(project).getExternalLibraryRoots());
1✔
218
      for (VirtualFile rootToMount : VfsUtil.getCommonAncestors(roots.toArray(VirtualFile.EMPTY_ARRAY))) {
1✔
219
        String localPath = rootToMount.getPath();
1✔
220
        String remotePath = myData.getRemotePath(localPath);
1✔
221
        dockerCommandLine.withParameters(WITH_VOLUME, localPath + ':' + remotePath);
1✔
222
      }
223

224
      // adding project settings if possible
225
      dockerCommandLine
1✔
226
        .withParameters(StringUtil.split(PerlDockerProjectSettings.getInstance(project).getAdditionalDockerParameters(), " "));
1✔
227
    }
228

229
    // working directory
230
    File remoteWorkingDirectory = myData.getRemotePath(commandLine.getWorkDirectory());
1✔
231
    if (remoteWorkingDirectory != null) {
1✔
232
      dockerCommandLine.withParameters(WORKING_DIRECTORY + "=" + StringUtil.escapeChar(
1✔
233
        FileUtil.toSystemIndependentName(remoteWorkingDirectory.toString()), ' '));
1✔
234
    }
235

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

240
    // we sure that command script is under system dir
241
    File script = createCommandScript(commandLine);
1✔
242
    String dockerScriptPath = myData.getRemotePath(script.getPath());
1✔
243
    if (StringUtil.isEmpty(dockerScriptPath)) {
1✔
NEW
244
      throw new ExecutionException(PerlDockerBundle.message("dialog.message.unable.to.map.path.for.in", script.getPath(), myData));
×
245
    }
246

247
    return dockerCommandLine.withParameters(myData.getImageName(), "sh", dockerScriptPath).createProcess();
1✔
248
  }
249

250
  /**
251
   * @return list of images in {@code name[:tag]} format
252
   */
253
  List<String> listImages() throws ExecutionException {
254
    ProcessOutput output = runCommand(IMAGE, LIST_IMAGE, IN_FORMAT, "{{.Repository}}:{{.Tag}}");
×
255
    return output.getStdoutLines().stream()
×
256
      .map(it -> StringUtil.replace(it, ":<none>", ""))
×
257
      .filter(it -> !StringUtil.equals(it, "<none>"))
×
258
      .sorted().collect(Collectors.toList());
×
259
  }
260

261
  private static PerlCommandLine baseCommandLine() {
262
    return new PerlCommandLine(DOCKER_EXECUTABLE).withHostData(PerlHostHandler.getDefaultHandler().createData());
1✔
263
  }
264

265
  static @NotNull PerlCommandLine buildBaseProcessCommandLine(@NotNull PerlCommandLine commandLine) {
266
    PerlCommandLine dockerCommandLine = baseCommandLine()
1✔
267
      .withParameters(RUN, WITH_AUTOREMOVE, INTERACTIVELY)
1✔
268
      .withParameters(WITH_ATTACHED, STDOUT, WITH_ATTACHED, STDERR, WITH_ATTACHED, STDIN)
1✔
269
      .withCharset(commandLine.getCharset());
1✔
270

271
    if (commandLine.isUsePty()) {
1✔
272
      dockerCommandLine.withParameters(WITH_TTY);
1✔
273
      dockerCommandLine.withPty(true);
1✔
274
    }
275

276
    // mapping ports
277
    commandLine.getPortMappings().forEach(
1✔
278
      it -> dockerCommandLine.withParameters(EXPOSE_PORT,
1✔
279
                                             String.valueOf(it.getRemote()),
1✔
280
                                             PUBLISH_PORT, it.getLocal() + ":" + it.getRemote()));
1✔
281
    return dockerCommandLine;
1✔
282
  }
283

284
  private @NotNull File createCommandScript(@NotNull PerlCommandLine commandLine) throws ExecutionException {
285
    StringBuilder sb = new StringBuilder();
1✔
286
    commandLine.getEnvironment().forEach((key, val) -> sb.append("export ").append(key).append('=')
1✔
287
      .append(CommandLineUtil.posixQuote(val)).append("\n"));
1✔
288
    sb.append(String.join(" ", ContainerUtil.map(commandLine.getCommandLineList(null), CommandLineUtil::posixQuote)));
1✔
289

290
    try {
291
      String command = sb.toString();
1✔
292
      LOG.debug("Executing in ", myData.getImageName());
1✔
293
      StringUtil.split(command, "\n").forEach(it -> LOG.debug("    ", it));
1✔
294
      var dockerWrapper = ExecUtil.createTempExecutableScript("dockerWrapper", "", command);
1✔
295
      LOG.debug("Created docker wrapper: ", dockerWrapper);
1✔
296
      return dockerWrapper;
1✔
297
    }
298
    catch (IOException e) {
×
299
      throw new ExecutionException(e);
×
300
    }
301
  }
302
}
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