Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add abillity to run docker task only for changed images #416

Merged
9 changes: 7 additions & 2 deletions .github/workflows/scan-docker-images.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,13 @@ jobs:
java-version: '17'
distribution: 'graalvm'
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: "🔎 Check docker images"
- name: "Install required tools"
run: |
curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sudo sh -s -- -b /usr/local/bin
sudo apt-get install jq
./gradlew checkAllowedDockerImages
- name: "🔎 Check changed docker images"
if: github.event_name == 'pull_request'
run: ./gradlew checkAllowedDockerImages --baseCommit=${{ github.event.pull_request.base.sha }} --newCommit=${{ github.event.pull_request.head.sha }}
- name: "🔎 Check all docker images"
if: github.event_name != 'pull_request'
run: ./gradlew checkAllowedDockerImages
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ Provider<Task> generateMatrixDiffCoordinates = tasks.register("generateMatrixDif
}
}

Provider<Task> getListOfDockerImages = tasks.register("checkAllowedDockerImages", GrypeTask.class) { task ->
tasks.register("checkAllowedDockerImages", GrypeTask.class) { task ->
task.setDescription("Returns list of allowed docker images")
task.setGroup(METADATA_GROUP)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
import java.util.List;
import java.util.Set;

import static org.graalvm.internal.tck.DockerUtils.getAllowedImages;
import static org.graalvm.internal.tck.DockerUtils.getAllAllowedImages;

public abstract class DockerTask extends DefaultTask {

Expand Down Expand Up @@ -47,7 +47,7 @@ void run() throws IOException, IllegalStateException, URISyntaxException {
return;
}

Set<String> allowedImages = getAllowedImages();
Set<String> allowedImages = getAllAllowedImages();
List<String> requiredImages = Files.readAllLines(dockerList.toPath());
for (String image : requiredImages) {
if (allowedImages.contains(image)) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,43 +1,81 @@
package org.graalvm.internal.tck;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.net.URL;
import java.nio.file.*;
import java.util.*;
import java.util.stream.Collectors;

public class DockerUtils {
private static final String ALLOWED_DOCKER_IMAGES = "/allowed-docker-images";

public static Set<String> getAllowedImages() throws IOException, URISyntaxException {
String dockerfileDirectory = Paths.get("./tests/tck-build-logic/src/main/resources/allowed-docker-images").toString();
File[] dockerFiles = new File(dockerfileDirectory).listFiles();
if (dockerFiles == null) {
throw new RuntimeException("Cannot find allowed-docker-images directory content");
private static URL getDockerfileDirectory() {
URL url = DockerUtils.class.getResource(ALLOWED_DOCKER_IMAGES);
if (url == null) {
throw new RuntimeException("Cannot find allowed-docker-images directory");
}

return url;
}

public static URL getDockerFile(String name) {
try {
return new URL(getDockerfileDirectory().toString().concat("/" + name));
} catch (MalformedURLException e) {
throw new RuntimeException(e);
}
}

private static String imageNameFromFile(URL dockerFile) throws IOException, URISyntaxException {
final String FROM = "FROM";
Set<String> allowedImages = new HashSet<>();
for (File dockerFile : dockerFiles) {
List<String> images = Files.readAllLines(dockerFile.toPath())
.stream()
.filter(line -> line.startsWith(FROM))
.map(line -> line.substring(FROM.length()).trim())
.toList();
if (images.size() != 1) {
throw new RuntimeException("Dockerfile: " + dockerFile.getName() + " must contain only one FROM line, got '" + images.size() + "' (" + images + "). Please read our documentation: "
+ new URI("https://github.com/oracle/graalvm-reachability-metadata/blob/master/CONTRIBUTING.md#providing-the-tests-that-use-docker"));
}
List<String> images = new BufferedReader(new InputStreamReader(dockerFile.openStream()))
.lines()
.filter(line -> line.startsWith(FROM))
.map(line -> line.substring(FROM.length()).trim())
.toList();
if (images.size() != 1) {
throw new RuntimeException("Dockerfile: " + fileNameFromJar(dockerFile) + " must contain only one FROM line, got '" + images.size() + "' (" + images + "). Please read our documentation: "
+ new URI("https://github.com/oracle/graalvm-reachability-metadata/blob/master/CONTRIBUTING.md#providing-the-tests-that-use-docker"));
}

allowedImages.add(images.get(0));
return images.get(0);
}

private static String fileNameFromJar(URL jarFile) {
return jarFile.toString().split("!")[1];
}

public static Set<String> extractImagesNames(List<URL> dockerFileNames) throws IOException, URISyntaxException {
Set<String> allowedImages = new HashSet<>();
for (URL dockerFile : dockerFileNames) {
allowedImages.add(imageNameFromFile(dockerFile));
}

return allowedImages;
}

public static Set<String> getAllAllowedImages() {
URL url = getDockerfileDirectory();
try (FileSystem fs = FileSystems.newFileSystem(url.toURI(), Collections.emptyMap())) {
List<URL> result = Files.walk(fs.getPath(ALLOWED_DOCKER_IMAGES))
.filter(Files::isRegularFile)
.map(Path::toString)
.map(path -> path.substring(path.lastIndexOf("/") + 1))
.map(DockerUtils::getDockerFile)
.toList();

return extractImagesNames(result);
} catch (IOException e) {
throw new RuntimeException("Cannot find files in allowed-docker-images directory");
} catch (URISyntaxException e) {
throw new RuntimeException(e);
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,74 @@

import org.gradle.api.DefaultTask;
import org.gradle.api.tasks.TaskAction;
import org.gradle.internal.impldep.com.esotericsoftware.kryo.io.ByteBufferOutputStream;
import org.gradle.api.tasks.options.Option;
import org.gradle.process.ExecOperations;
import org.gradle.process.ExecResult;

import javax.inject.Inject;
import java.io.*;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.Paths;
import java.util.*;
import java.util.function.Predicate;

import static org.graalvm.internal.tck.DockerUtils.getAllowedImages;
import static org.graalvm.internal.tck.DockerUtils.extractImagesNames;
import static org.graalvm.internal.tck.DockerUtils.getAllAllowedImages;

public abstract class GrypeTask extends DefaultTask {

@Inject
protected abstract ExecOperations getExecOperations();

@Option(option = "baseCommit", description = "Last commit from master")
void setBaseCommit(String baseCommit) {
this.baseCommit = baseCommit;
}

@Option(option = "newCommit", description = "HEAD commit of the pull request")
void setNewCommit(String newCommit) {
this.newCommit = newCommit;
}

private String newCommit;
private String baseCommit;

private final String jqMatcher = " | jq -c '.matches | .[] | .vulnerability | select(.severity | (contains(\"High\") or contains(\"Critical\")))'";

private List<URL> getChangedImages(String base, String head){
ByteArrayOutputStream baos = new ByteArrayOutputStream();
getExecOperations().exec(spec -> {
spec.setStandardOutput(baos);
spec.commandLine("git", "diff", "--name-only", "--diff-filter=ACMRT", base, head);
fniephaus marked this conversation as resolved.
Show resolved Hide resolved
});

String output = baos.toString(StandardCharsets.UTF_8);
String dockerfileDirectory = "allowed-docker-images";
List<URL> diffFiles = Arrays.stream(output.split("\\r?\\n"))
.filter(path -> path.contains(dockerfileDirectory))
.map(path -> path.substring(path.lastIndexOf("/") + 1))
.map(DockerUtils::getDockerFile)
.toList();

if (diffFiles.isEmpty()) {
throw new RuntimeException("There are no changed or new docker image founded. " +
"This task should be executed only if there are changes in allowed-docker-images directory.");
}

return diffFiles;
}

@TaskAction
void run() throws IllegalStateException, IOException, URISyntaxException {
List<String> vulnerableImages = new ArrayList<>();
Set<String> allowedImages = getAllowedImages();
boolean shouldFail = false;
Set<String> allowedImages;
if (baseCommit == null && newCommit == null) {
allowedImages = getAllAllowedImages();
} else {
allowedImages = extractImagesNames(getChangedImages(baseCommit, newCommit));
}

boolean shouldFail = false;
for (String image : allowedImages) {
System.out.println("Checking image: " + image);
String[] command = { "-c", "grype -o json " + image + jqMatcher };
Expand Down
Loading