Skip to content

Commit 89ffefd

Browse files
authored
BI-551 - Log the executed "nuget" and "dotnet" commands - Fix (#423)
1 parent 6cb7099 commit 89ffefd

File tree

12 files changed

+158
-72
lines changed

12 files changed

+158
-72
lines changed

build-info-extractor-go/src/main/java/org/jfrog/build/extractor/go/GoDriver.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ public CommandResults runCmd(String args, boolean prompt) throws IOException {
4444
public CommandResults runCmd(List<String> args, boolean prompt) throws IOException {
4545
CommandResults goCmdResult;
4646
try {
47-
goCmdResult = commandExecutor.exeCommand(workingDirectory, args, logger);
47+
goCmdResult = commandExecutor.exeCommand(workingDirectory, args, null, logger);
4848
} catch (IOException | InterruptedException e) {
4949
throw new IOException("Go execution failed", e);
5050
}

build-info-extractor-go/src/test/java/org/jfrog/build/extractor/go/extractor/GoExtractorTest.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ private void cleanGoModCacheAndEnv() throws IOException, InterruptedException {
127127
List<String> goCleanArgs = new ArrayList<>();
128128
goCleanArgs.add("clean");
129129
goCleanArgs.add("-modcache");
130-
goCommandExecutor.exeCommand(PROJECTS_ROOT.toFile(), goCleanArgs, log);
130+
goCommandExecutor.exeCommand(PROJECTS_ROOT.toFile(), goCleanArgs, null, log);
131131
env.clear();
132132
// Since we are handling dummy projects, we want to avoid package validation against Go's checksum DB.
133133
env.put("GONOSUMDB", "github.com/jfrog");

build-info-extractor-npm/src/main/java/org/jfrog/build/extractor/npm/NpmDriver.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ public JsonNode list(File workingDirectory, List<String> extraArgs) throws IOExc
7070
args.add("--long");
7171
args.addAll(extraArgs);
7272
try {
73-
CommandResults npmCommandRes = commandExecutor.exeCommand(workingDirectory, args, null);
73+
CommandResults npmCommandRes = commandExecutor.exeCommand(workingDirectory, args, null, null);
7474
String res = StringUtils.isBlank(npmCommandRes.getRes()) ? "{}" : npmCommandRes.getRes();
7575
JsonNode npmLsResults = jsonReader.readTree(res);
7676
if (!npmCommandRes.isOk() && !npmLsResults.has("problems")) {
@@ -100,7 +100,7 @@ private String runCommand(File workingDirectory, String[] args, List<String> ext
100100

101101
private String runCommand(File workingDirectory, String[] args, List<String> extraArgs, Log logger) throws IOException, InterruptedException {
102102
List<String> finalArgs = Stream.concat(Arrays.stream(args), extraArgs.stream()).collect(Collectors.toList());
103-
CommandResults npmCommandRes = commandExecutor.exeCommand(workingDirectory, finalArgs, logger);
103+
CommandResults npmCommandRes = commandExecutor.exeCommand(workingDirectory, finalArgs, null, logger);
104104
if (!npmCommandRes.isOk()) {
105105
throw new IOException(npmCommandRes.getErr() + npmCommandRes.getRes());
106106
}

build-info-extractor-nuget/src/main/java/org/jfrog/build/extractor/nuget/drivers/DotnetDriver.java

+4-4
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,9 @@ public DotnetDriver(Map<String, String> env, Path workingDirectory, Log logger)
2323
public String addSource(String configPath, ArtifactoryDependenciesClient client, String repo, String sourceName, String username, String password) throws IOException {
2424
try {
2525
String sourceUrl = buildNugetSourceUrl(client, repo);
26-
List<String> extraArgs = new ArrayList<>();
27-
extraArgs.addAll(Arrays.asList(FLAG_PREFIX + CONFIG_FILE_FLAG, configPath, FLAG_PREFIX + NAME_FLAG, sourceName, FLAG_PREFIX + USERNAME_FLAG, username, FLAG_PREFIX + PASSWORD_FLAG, password, CLEAR_TEXT_PASSWORD_FLAG));
28-
return runCommand(new String[]{"nuget", "add", "source", sourceUrl}, extraArgs, logger);
26+
List<String> extraArgs = Arrays.asList(getFlagSyntax(CONFIG_FILE_FLAG), configPath, getFlagSyntax(NAME_FLAG), sourceName);
27+
List<String> credentials = Arrays.asList(getFlagSyntax(USERNAME_FLAG), username, getFlagSyntax(PASSWORD_FLAG), password, CLEAR_TEXT_PASSWORD_FLAG);
28+
return runCommand(new String[]{"nuget", "add", "source", sourceUrl}, extraArgs, credentials, logger);
2929
} catch (Exception e) {
3030
throw new IOException("dotnet nuget add source failed: " + e.getMessage() + ". Please make sure .NET Core 3.1.200 SDK or above is installed.", e);
3131
}
@@ -36,7 +36,7 @@ public String globalPackagesCache() throws IOException, InterruptedException {
3636
List<String> args = new ArrayList<>();
3737
args.add(GLOBAL_PACKAGES_ARG);
3838
args.add(getFlagSyntax(LIST_FLAG));
39-
String output = runCommand(new String[]{"nuget", LOCALS_ARG,}, args, logger);
39+
String output = runCommand(new String[]{"nuget", LOCALS_ARG}, args, null, logger);
4040
return output.replaceFirst(GLOBAL_PACKAGES_REGEX, "").trim();
4141
}
4242

build-info-extractor-nuget/src/main/java/org/jfrog/build/extractor/nuget/drivers/NugetDriver.java

+8-5
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@
66

77
import java.io.IOException;
88
import java.nio.file.Path;
9-
import java.util.*;
9+
import java.util.ArrayList;
10+
import java.util.Arrays;
11+
import java.util.List;
12+
import java.util.Map;
1013

1114
public class NugetDriver extends ToolchainDriverBase {
1215
private static final String FLAG_PREFIX = "-";
@@ -19,9 +22,9 @@ public NugetDriver(Map<String, String> env, Path workingDirectory, Log logger) {
1922
public String addSource(String configPath, ArtifactoryDependenciesClient client, String repo, String sourceName, String username, String password) throws IOException {
2023
try {
2124
String sourceUrl = buildNugetSourceUrl(client, repo);
22-
List<String> extraArgs = new ArrayList<>();
23-
extraArgs.addAll(Arrays.asList(FLAG_PREFIX + CONFIG_FILE_FLAG, configPath, FLAG_PREFIX + SOURCE_FLAG, sourceUrl, FLAG_PREFIX + NAME_FLAG, sourceName, FLAG_PREFIX + USERNAME_FLAG, username, FLAG_PREFIX + PASSWORD_FLAG, password));
24-
return runCommand(new String[]{"sources", "add"}, extraArgs, logger);
25+
List<String> extraArgs = Arrays.asList(getFlagSyntax(CONFIG_FILE_FLAG), configPath, getFlagSyntax(SOURCE_FLAG), sourceUrl, getFlagSyntax(NAME_FLAG), sourceName);
26+
List<String> credentials = Arrays.asList(getFlagSyntax(USERNAME_FLAG), username, getFlagSyntax(PASSWORD_FLAG), password);
27+
return runCommand(new String[]{"sources", "add"}, extraArgs, credentials, logger);
2528
} catch (Exception e) {
2629
throw new IOException("nuget sources add failed: " + e.getMessage(), e);
2730
}
@@ -32,7 +35,7 @@ public String globalPackagesCache() throws IOException, InterruptedException {
3235
List<String> args = new ArrayList<>();
3336
args.add(GLOBAL_PACKAGES_ARG);
3437
args.add(getFlagSyntax(LIST_FLAG));
35-
String output = runCommand(new String[]{LOCALS_ARG, }, args, logger);
38+
String output = runCommand(new String[]{LOCALS_ARG}, args, null, logger);
3639
return output.replaceFirst(GLOBAL_PACKAGES_REGEX, "").trim();
3740
}
3841

build-info-extractor-nuget/src/main/java/org/jfrog/build/extractor/nuget/drivers/ToolchainDriverBase.java

+16-5
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ public boolean isInstalled() {
6060
abstract public String getFlagSyntax(String flagName);
6161

6262
public String help() throws IOException, InterruptedException {
63-
return runCommand(new String[]{"help"}, Collections.emptyList(), logger);
63+
return runCommand(new String[]{"help"}, Collections.emptyList(), null, logger);
6464
}
6565

6666
protected String buildNugetSourceUrl(ArtifactoryBaseClient client, String repo) throws Exception {
@@ -73,17 +73,28 @@ protected String buildNugetSourceUrl(ArtifactoryBaseClient client, String repo)
7373
return sourceUrlBuilder.build().toURL().toString();
7474
}
7575

76-
public void runCmd(String args, List<String> extraArgs, boolean prompt) throws IOException, InterruptedException {
76+
public void runCmd(String args, List<String> extraArgs, List<String> credentials, boolean prompt) throws IOException, InterruptedException {
7777
Log logger = prompt ? this.logger : null;
78-
String cmdOutput = runCommand(args.split(" "), extraArgs, logger);
78+
String cmdOutput = runCommand(args.split(" "), extraArgs, credentials, logger);
7979
if (prompt) {
8080
logger.info(cmdOutput);
8181
}
8282
}
8383

84-
protected String runCommand(String[] args, List<String> extraArgs, Log logger) throws IOException, InterruptedException {
84+
/**
85+
* Run .NET/Nuget command.
86+
*
87+
* @param args - Base args, such as "nuget add source source-url"
88+
* @param extraArgs - Extra args, such as "--config=config-path --name=source-name"
89+
* @param credentials - Credentials, such as "--username=username --password=password"
90+
* @param logger - The logger of the command
91+
* @return the output.
92+
* @throws IOException if the command execution returned an error.
93+
* @throws InterruptedException if the command execution interrupted.
94+
*/
95+
protected String runCommand(String[] args, List<String> extraArgs, List<String> credentials, Log logger) throws IOException, InterruptedException {
8596
List<String> finalArgs = Stream.concat(Arrays.stream(args), extraArgs.stream()).collect(Collectors.toList());
86-
CommandResults nugetCommandRes = commandExecutor.exeCommand(workingDirectory, finalArgs, logger);
97+
CommandResults nugetCommandRes = commandExecutor.exeCommand(workingDirectory, finalArgs, credentials, logger);
8798
if (!nugetCommandRes.isOk()) {
8899
throw new IOException(nugetCommandRes.getErr() + nugetCommandRes.getRes());
89100
}

build-info-extractor-nuget/src/main/java/org/jfrog/build/extractor/nuget/extractor/NugetRun.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ private void prepareAndRunCmd() throws Exception {
180180
String configPath = configFile.getAbsolutePath();
181181
extraArgs = StringUtils.isBlank(configPath) ? null : Arrays.asList(toolchainDriver.getFlagSyntax(ToolchainDriverBase.CONFIG_FILE_FLAG), configPath);
182182
}
183-
toolchainDriver.runCmd(nugetCmdArgs, extraArgs, true);
183+
toolchainDriver.runCmd(nugetCmdArgs, extraArgs, null, true);
184184
}
185185
}
186186

build-info-extractor-pip/src/main/java/org/jfrog/build/extractor/pip/PipDriver.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ public String freeze(File workingDirectory, Log logger) throws IOException {
4848
}
4949

5050
public String runCommand(File workingDirectory, List<String> args, Log logger) throws IOException, InterruptedException {
51-
CommandResults pipCommandRes = commandExecutor.exeCommand(workingDirectory, args, logger);
51+
CommandResults pipCommandRes = commandExecutor.exeCommand(workingDirectory, args, null, logger);
5252
if (!pipCommandRes.isOk()) {
5353
throw new IOException(pipCommandRes.getErr() + pipCommandRes.getRes());
5454
}
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,31 @@
11
package org.jfrog.build.extractor.executor;
22

3-
import org.apache.commons.io.IOUtils;
3+
import org.apache.commons.lang.SystemUtils;
44
import org.jfrog.build.api.util.Log;
55
import org.jfrog.build.extractor.clientConfiguration.util.UrlUtils;
66

77
import java.io.File;
88
import java.io.IOException;
9+
import java.io.InputStream;
910
import java.io.Serializable;
10-
import java.util.HashMap;
11-
import java.util.List;
12-
import java.util.Map;
11+
import java.util.*;
1312
import java.util.concurrent.ExecutorService;
1413
import java.util.concurrent.Executors;
1514
import java.util.concurrent.TimeUnit;
1615

16+
import static java.lang.String.format;
17+
import static java.lang.String.join;
18+
1719
/**
1820
* @author Yahav Itzhak
1921
*/
2022
public class CommandExecutor implements Serializable {
2123
private static final long serialVersionUID = 1L;
24+
private static final int TIMEOUT_EXIT_VALUE = 124;
25+
private static final int TIMEOUT_SECONDS = 10;
2226

23-
private String[] env;
24-
private String executablePath;
27+
private final String[] env;
28+
private final String executablePath;
2529

2630
/**
2731
* @param executablePath - Executable path.
@@ -49,7 +53,7 @@ private void fixPathEnv(Map<String, String> env) {
4953
if (path == null) {
5054
return;
5155
}
52-
if (isWindows()) {
56+
if (SystemUtils.IS_OS_WINDOWS) {
5357
path = getFixedWindowsPath(path);
5458
} else {
5559
path = path.replaceAll(";", File.pathSeparator) + ":/usr/local/bin";
@@ -82,67 +86,100 @@ static String getFixedWindowsPath(String path) {
8286
String newPart = startPart + endPart.replaceAll(":", ";");
8387
newPathParts[index] = newPart;
8488
}
85-
return String.join(";", newPathParts);
89+
return join(";", newPathParts);
90+
}
91+
92+
/**
93+
* Replace credentials in the string command by '***'.
94+
*
95+
* @param command - The command
96+
* @param credentials - The credentials list
97+
* @return masked command.
98+
*/
99+
static String maskCredentials(String command, List<String> credentials) {
100+
if (credentials == null || credentials.isEmpty()) {
101+
return command;
102+
}
103+
// The mask pattern is a regex, which is used to mask all credentials
104+
String maskPattern = join("|", credentials);
105+
return command.replaceAll(maskPattern, "***");
86106
}
87107

88108
/**
89109
* Execute a command in external process.
90110
*
91-
* @param execDir - The execution dir (Usually path to project). Null means current directory.
92-
* @param args - Command arguments.
93-
* @return CommandResults
111+
* @param execDir - The execution dir (Usually path to project). Null means current directory.
112+
* @param args - Command arguments.
113+
* @param credentials - If specified, the credentials will be concatenated to the other commands.
114+
* The credentials will be makes in the log output.
115+
* @param logger - The logger which will log the running command.
116+
* @return CommandResults object
94117
*/
95-
public CommandResults exeCommand(File execDir, List<String> args, Log logger) throws InterruptedException, IOException {
118+
public CommandResults exeCommand(File execDir, List<String> args, List<String> credentials, Log logger) throws InterruptedException, IOException {
96119
args.add(0, executablePath);
97-
Process process = null;
98120
ExecutorService service = Executors.newFixedThreadPool(2);
99121
try {
100-
CommandResults commandRes = new CommandResults();
101-
process = runProcess(execDir, args, env, logger);
102-
StreamReader inputStreamReader = new StreamReader(process.getInputStream());
103-
StreamReader errorStreamReader = new StreamReader(process.getErrorStream());
104-
service.submit(inputStreamReader);
105-
service.submit(errorStreamReader);
106-
process.waitFor();
107-
service.shutdown();
108-
service.awaitTermination(10, TimeUnit.SECONDS);
109-
commandRes.setRes(inputStreamReader.getOutput());
110-
commandRes.setErr(errorStreamReader.getOutput());
111-
commandRes.setExitValue(process.exitValue());
112-
return commandRes;
122+
Process process = runProcess(execDir, args, credentials, env, logger);
123+
// The output stream is not necessary in non-interactive scenarios, therefore we can close it now.
124+
process.getOutputStream().close();
125+
try (InputStream inputStream = process.getInputStream();
126+
InputStream errorStream = process.getErrorStream()) {
127+
StreamReader inputStreamReader = new StreamReader(inputStream);
128+
StreamReader errorStreamReader = new StreamReader(errorStream);
129+
service.submit(inputStreamReader);
130+
service.submit(errorStreamReader);
131+
process.waitFor();
132+
service.shutdown();
133+
boolean terminatedProperly = service.awaitTermination(TIMEOUT_SECONDS, TimeUnit.SECONDS);
134+
return getCommandResults(terminatedProperly, args, inputStreamReader.getOutput(), errorStreamReader.getOutput(), process.exitValue());
135+
}
113136
} finally {
114-
closeStreams(process);
115137
service.shutdownNow();
116138
}
117139
}
118140

119-
private static void closeStreams(Process process) {
120-
if (process != null) {
121-
IOUtils.closeQuietly(process.getInputStream());
122-
IOUtils.closeQuietly(process.getOutputStream());
123-
IOUtils.closeQuietly(process.getErrorStream());
141+
private CommandResults getCommandResults(boolean terminatedProperly, List<String> args, String output, String error, int exitValue) {
142+
CommandResults commandRes = new CommandResults();
143+
if (!terminatedProperly) {
144+
error += System.lineSeparator() + format("Process '%s' had been terminated forcibly after timeout of %d seconds.",
145+
join(" ", args), TIMEOUT_SECONDS);
146+
exitValue = TIMEOUT_EXIT_VALUE;
124147
}
148+
commandRes.setRes(output);
149+
commandRes.setErr(error);
150+
commandRes.setExitValue(exitValue);
151+
return commandRes;
125152
}
126153

127-
private static boolean isWindows() {
128-
return System.getProperty("os.name").toLowerCase().contains("win");
129-
}
130-
131-
private static boolean isMac() {
132-
return System.getProperty("os.name").toLowerCase().contains("mac");
133-
}
134-
135-
private static Process runProcess(File execDir, List<String> args, String[] env, Log logger) throws IOException {
136-
if (isWindows()) {
137-
args.add(0, "cmd");
138-
args.add(1, "/c");
139-
} else if (isMac()) {
140-
args.add(0, "/bin/sh");
141-
args.add(1, "-c");
154+
private static Process runProcess(File execDir, List<String> args, List<String> credentials, String[] env, Log logger) throws IOException {
155+
if (credentials != null) {
156+
args.addAll(credentials);
142157
}
143-
if (logger != null) {
144-
logger.info("Executing command: " + UrlUtils.maskCredentialsInUrl(String.join(" ", args)));
158+
if (SystemUtils.IS_OS_WINDOWS) {
159+
args.addAll(0, Arrays.asList("cmd", "/c"));
160+
} else if (SystemUtils.IS_OS_MAC) {
161+
// In MacOS, the arguments for '/bin/sh -c' must be a single string. For example "/bin/sh","-c", "npm i".
162+
String strArgs = join(" ", args);
163+
args = new ArrayList<String>() {{
164+
add("/bin/sh");
165+
add("-c");
166+
add(strArgs);
167+
}};
145168
}
169+
logCommand(logger, args, credentials);
146170
return Runtime.getRuntime().exec(args.toArray(new String[0]), env, execDir);
147171
}
172+
173+
private static void logCommand(Log logger, List<String> args, List<String> credentials) {
174+
if (logger == null) {
175+
return;
176+
}
177+
// Mask credentials in URL
178+
String output = UrlUtils.maskCredentialsInUrl(join(" ", args));
179+
180+
// Mask credentials arguments
181+
output = maskCredentials(output, credentials);
182+
183+
logger.info("Executing command: " + output);
184+
}
148185
}

build-info-extractor/src/main/java/org/jfrog/build/extractor/issuesCollection/IssuesCollector.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ private String getGitLog(File execDir, Log logger, String previousVcsRevision) t
139139
args.add(previousVcsRevision + "..");
140140
}
141141
CommandExecutor commandExecutor = new CommandExecutor("git", null);
142-
CommandResults res = commandExecutor.exeCommand(execDir, args, logger);
142+
CommandResults res = commandExecutor.exeCommand(execDir, args, null, logger);
143143
if (!res.isOk()) {
144144
throw new IOException(ISSUES_COLLECTION_ERROR_PREFIX + "Git log command failed: " + res.getErr());
145145
}

build-info-extractor/src/test/java/org/jfrog/build/extractor/clientConfiguration/util/GitUtilsTest.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ private void testReadGitConfig() throws IOException, InterruptedException {
3333

3434
private String getGitFieldWithExecutor(File execDir, Log log, List<String> args) throws IOException, InterruptedException {
3535
CommandExecutor executor = new CommandExecutor("git", null);
36-
CommandResults res = executor.exeCommand(execDir, args, log);
36+
CommandResults res = executor.exeCommand(execDir, args, null, log);
3737
Assert.assertTrue(res.isOk());
3838
return res.getRes().trim();
3939
}

0 commit comments

Comments
 (0)