Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@ public final class GradleProvider extends BaseJavaProvider {

private static final Logger log = LoggersFactory.getLogger(GradleProvider.class.getName());

private static final long TIMEOUT =
Long.parseLong(System.getProperty("trustify.gradle.timeout.seconds", "120"));

private final String gradleExecutable = Operations.getExecutable("gradle", "--version");

public GradleProvider(Path manifest) {
Expand All @@ -73,20 +76,24 @@ public String readLicenseFromManifest() {
@Override
public Content provideStack() throws IOException {
Path tempFile = getDependencies(manifest);
if (debugLoggingIsNeeded()) {
String stackAnalysisDependencyTree = Files.readString(tempFile);
log.info(
String.format(
"Package Manager Gradle Stack Analysis Dependency Tree Output: %s %s",
System.lineSeparator(), stackAnalysisDependencyTree));
}
Map<String, String> propertiesMap = extractProperties(manifest);
try {
if (debugLoggingIsNeeded()) {
String stackAnalysisDependencyTree = Files.readString(tempFile);
log.info(
String.format(
"Package Manager Gradle Stack Analysis Dependency Tree Output: %s %s",
System.lineSeparator(), stackAnalysisDependencyTree));
}
Map<String, String> propertiesMap = extractProperties(manifest);

var sbom = buildSbomFromTextFormat(tempFile, propertiesMap, AnalysisType.STACK);
var ignored = getIgnoredDeps(manifest);
var sbom = buildSbomFromTextFormat(tempFile, propertiesMap, AnalysisType.STACK);
var ignored = getIgnoredDeps(manifest);

return new Content(
sbom.filterIgnoredDeps(ignored).getAsJsonString().getBytes(), Api.CYCLONEDX_MEDIA_TYPE);
return new Content(
sbom.filterIgnoredDeps(ignored).getAsJsonString().getBytes(), Api.CYCLONEDX_MEDIA_TYPE);
} finally {
Files.deleteIfExists(tempFile);
}
}

private List<String> getIgnoredDeps(Path manifestPath) throws IOException {
Expand Down Expand Up @@ -228,28 +235,42 @@ private String extractPackageName(String line) {
}

private Path getDependencies(Path manifestPath) throws IOException {
// create a temp file for storing the dependency tree in
var tempFile = Files.createTempFile("TRUSTIFY_DA_graph_", null);
// the command will create the dependency tree in the temp file
String gradleCommand = gradleExecutable + " dependencies";

String[] cmdList = gradleCommand.split("\\s+");
String gradleOutput =
Operations.runProcessGetOutput(Path.of(manifestPath.getParent().toString()), cmdList);
Files.writeString(tempFile, gradleOutput);

var result =
Operations.runProcessGetFullOutput(
Path.of(manifestPath.getParent().toString()), cmdList, null, TIMEOUT);

if (result.getExitCode() != 0) {
throw new RuntimeException(
String.format(
"gradle dependencies command failed with exit code %d for manifest '%s': %s",
result.getExitCode(), manifestPath, result.getError()));
}

var tempFile = Files.createTempFile("TRUSTIFY_DA_graph_", null);
Files.writeString(tempFile, result.getOutput());
return tempFile;
}

private Path getProperties(Path manifestPath) throws IOException {
Path propsTempFile = Files.createTempFile("propsfile", ".txt");
String propCmd = gradleExecutable + " properties";
String[] propCmdList = propCmd.split("\\s+");
String properties =
Operations.runProcessGetOutput(Path.of(manifestPath.getParent().toString()), propCmdList);
// Create a temporary file
Files.writeString(propsTempFile, properties);

var result =
Operations.runProcessGetFullOutput(
Path.of(manifestPath.getParent().toString()), propCmdList, null, TIMEOUT);

if (result.getExitCode() != 0) {
throw new RuntimeException(
String.format(
"gradle properties command failed with exit code %d for manifest '%s': %s",
result.getExitCode(), manifestPath, result.getError()));
}

Path propsTempFile = Files.createTempFile("propsfile", ".txt");
Files.writeString(propsTempFile, result.getOutput());
return propsTempFile;
}

Expand Down Expand Up @@ -509,9 +530,12 @@ private boolean containsVersion(String line) {

private String getRoot(Path textFormatFile, Map<String, String> propertiesMap)
throws IOException {
String group = propertiesMap.get("group");
String version = propertiesMap.get("version");
String group = propertiesMap.getOrDefault("group", "unknown");
String version = propertiesMap.getOrDefault("version", "0.0.0");
String rootName = extractRootProjectValue(textFormatFile);
if (rootName == null || rootName.isEmpty()) {
rootName = "unknown";
}
return group + ':' + rootName + ':' + "jar" + ':' + version;
}

Expand All @@ -531,24 +555,28 @@ private String extractRootProjectValue(Path inputFilePath) throws IOException {

private Map<String, String> extractProperties(Path manifestPath) throws IOException {
Path propsTempFile = getProperties(manifestPath);
String content = Files.readString(propsTempFile);
// Define the regular expression pattern for key-value pairs
Pattern pattern = Pattern.compile("([^:]+):\\s+(.+)");
Matcher matcher = pattern.matcher(content);
// Create a Map to store key-value pairs
Map<String, String> keyValueMap = new HashMap<>();

// Iterate through matches and add them to the map
while (matcher.find()) {
String key = matcher.group(1).trim();
String value = matcher.group(2).trim();
keyValueMap.put(key, value);
}
// Check if any key-value pairs were found
if (!keyValueMap.isEmpty()) {
return keyValueMap;
} else {
return Collections.emptyMap();
try {
String content = Files.readString(propsTempFile);
// Define the regular expression pattern for key-value pairs
Pattern pattern = Pattern.compile("([^:]+):\\s+(.+)");
Matcher matcher = pattern.matcher(content);
// Create a Map to store key-value pairs
Map<String, String> keyValueMap = new HashMap<>();

// Iterate through matches and add them to the map
while (matcher.find()) {
String key = matcher.group(1).trim();
String value = matcher.group(2).trim();
keyValueMap.put(key, value);
}
// Check if any key-value pairs were found
if (!keyValueMap.isEmpty()) {
return keyValueMap;
} else {
return Collections.emptyMap();
}
} finally {
Files.deleteIfExists(propsTempFile);
}
}

Expand Down Expand Up @@ -577,14 +605,17 @@ private List<String> extractLines(Path inputFilePath, String startMarker) throws

@Override
public Content provideComponent() throws IOException {

Path tempFile = getDependencies(manifest);
Map<String, String> propertiesMap = extractProperties(manifest);
try {
Map<String, String> propertiesMap = extractProperties(manifest);

Sbom sbom = buildSbomFromTextFormat(tempFile, propertiesMap, AnalysisType.COMPONENT);
var ignored = getIgnoredDeps(manifest);
Sbom sbom = buildSbomFromTextFormat(tempFile, propertiesMap, AnalysisType.COMPONENT);
var ignored = getIgnoredDeps(manifest);

return new Content(
sbom.filterIgnoredDeps(ignored).getAsJsonString().getBytes(), Api.CYCLONEDX_MEDIA_TYPE);
return new Content(
sbom.filterIgnoredDeps(ignored).getAsJsonString().getBytes(), Api.CYCLONEDX_MEDIA_TYPE);
} finally {
Files.deleteIfExists(tempFile);
}
}
}
61 changes: 41 additions & 20 deletions src/main/java/io/github/guacsec/trustifyda/tools/Operations.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;
import java.util.stream.Collectors;
Expand Down Expand Up @@ -192,8 +194,15 @@ public static String runProcessGetOutput(Path dir, final String[] cmdList, Strin
}
}

private static final long DEFAULT_PROCESS_TIMEOUT_SECONDS = 120L;

public static ProcessExecOutput runProcessGetFullOutput(
Path dir, final String[] cmdList, String[] envList) {
return runProcessGetFullOutput(dir, cmdList, envList, DEFAULT_PROCESS_TIMEOUT_SECONDS);
}

public static ProcessExecOutput runProcessGetFullOutput(
Path dir, final String[] cmdList, String[] envList, long timeoutSeconds) {
try {
Process process;
if (dir == null) {
Expand All @@ -210,34 +219,46 @@ public static ProcessExecOutput runProcessGetFullOutput(
}
}

BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
StringBuilder output = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
output.append(line);
if (!line.endsWith(System.lineSeparator())) {
output.append("\n");
}
}
CompletableFuture<String> stdoutFuture =
CompletableFuture.supplyAsync(() -> drainStream(process.getInputStream()));
CompletableFuture<String> stderrFuture =
CompletableFuture.supplyAsync(() -> drainStream(process.getErrorStream()));

reader = new BufferedReader(new InputStreamReader(process.getErrorStream()));
StringBuilder error = new StringBuilder();
while ((line = reader.readLine()) != null) {
error.append(line);
if (!line.endsWith(System.lineSeparator())) {
error.append("\n");
}
boolean finished = process.waitFor(timeoutSeconds, TimeUnit.SECONDS);
if (!finished) {
process.destroyForcibly();
throw new RuntimeException(
String.format(
"Command '%s' timed out after %d seconds", join(" ", cmdList), timeoutSeconds));
}

process.waitFor(30L, TimeUnit.SECONDS);

return new ProcessExecOutput(output.toString(), error.toString(), process.exitValue());
} catch (IOException | InterruptedException e) {
return new ProcessExecOutput(stdoutFuture.get(), stderrFuture.get(), process.exitValue());
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException(
String.format("Command '%s' was interrupted", join(" ", cmdList)), e);
} catch (ExecutionException e) {
throw new RuntimeException(
String.format("Failed reading output of command '%s'", join(" ", cmdList)), e);
} catch (IOException e) {
throw new RuntimeException(
String.format("Failed to execute command '%s' ", join(" ", cmdList)), e);
}
}

private static String drainStream(java.io.InputStream inputStream) {
try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) {
StringBuilder sb = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
sb.append(line).append("\n");
}
return sb.toString();
} catch (IOException e) {
throw new RuntimeException("Failed to read process stream", e);
}
}

public static class ProcessExecOutput {
private final String output;
private final String error;
Expand Down
Loading
Loading