Skip to content

Commit

Permalink
Add configuration filter to narrow deployment
Browse files Browse the repository at this point in the history
Adds a -df -deployFilterfile configuration that is a file path to load
for telling the MavenRepositoryDeployer which GAVs to deploy (i.e.
filter in). This file contains a groupId:artifactId:version:ext
(patterns supported) per line, and is loaded into memory as a Set<Gav>.
It implements the Object.equals method on Gav and matcher support, so
that we can compare GAVs in order to know what to deploy. Only artifact
references that exist in the file will be deployed. If the filter file
is not specified, or doesnt exist, we do not filter at all. The
matching logic is made to be multi-threaded, in case the filter file is
large.

References: #81

Signed-off-by: Samuel Dacanay <sam.dacanay@chainguard.dev>
  • Loading branch information
dakaneye committed Oct 29, 2024
1 parent f73bd7c commit 8c64664
Show file tree
Hide file tree
Showing 12 changed files with 529 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
*/
package com.simpligility.maven;

import java.util.Objects;

public final class Gav {
private final String groupId;

Expand Down Expand Up @@ -60,6 +62,26 @@ public String getRepositoryURLPath() {
return groupId.replace(".", "/") + "/" + artifactId + "/" + version + "/";
}

@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
Gav gav = (Gav) obj;
return groupId.equals(gav.getGroupId())
&& artifactId.equals(gav.getArtifactId())
&& version.equals(gav.getVersion())
&& packaging.equals(gav.getPackaging());
}

@Override
public int hashCode() {
return Objects.hash(groupId, artifactId, version, packaging);
}

@Override
public String toString() {
return groupId + ":" + artifactId + ":" + version;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.simpligility.maven;

import java.util.concurrent.Callable;
import java.util.function.Supplier;

public class GavMatcher implements Callable<Boolean>, Supplier<Boolean> {
private final Gav gav;
private final GavPattern pattern;

public GavMatcher(Gav gav, GavPattern pattern) {
this.gav = gav;
this.pattern = pattern;
}

@Override
public Boolean call() {
return pattern.matches(gav);
}

@Override
public Boolean get() {
return call();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.simpligility.maven;

import java.util.regex.Pattern;

public class GavPattern {
private final Pattern pattern;

public GavPattern(Pattern pattern) {
this.pattern = pattern;
}

public boolean matches(Gav gav) {
if (gav == null) {
return false;
}
String gavString =
gav.getGroupId() + ":" + gav.getArtifactId() + ":" + gav.getVersion() + ":" + gav.getPackaging();
return pattern.matcher(gavString).matches();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,13 @@ public class Configuration {
arity = 1)
private Boolean verifyOnly = false;

@Parameter(
names = {"-df", "deployFilterFile"},
description = "File containing a list of GAVs to deploy, one per line in format of groupId:artifactId:"
+ "version:extension",
arity = 1)
private String deployFilterFile;

public void setSourceUrl(String sourceUrl) {
this.sourceUrl = sourceUrl;
}
Expand Down Expand Up @@ -165,6 +172,10 @@ public void setVerifyOnly(Boolean verifyOnly) {
this.verifyOnly = verifyOnly;
}

public void setDeployFilterFile(String deployFilterFile) {
this.deployFilterFile = deployFilterFile;
}

public boolean getHelp() {
return help;
}
Expand Down Expand Up @@ -241,6 +252,10 @@ public Boolean getVerifyOnly() {
return verifyOnly;
}

public String getDeployFilterFile() {
return deployFilterFile;
}

public List<String> getArtifactCoordinates() {
List<String> coords = Arrays.asList(artifactCoordinate.split("\\|"));
return coords;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.simpligility.maven.provisioner;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import com.simpligility.maven.Gav;
import com.simpligility.maven.GavMatcher;
import com.simpligility.maven.GavPattern;

public class GavMatcherExecutor implements AutoCloseable {
private final ExecutorService executorService;

public GavMatcherExecutor(int threadPoolSize) {
this.executorService = Executors.newFixedThreadPool(threadPoolSize);
}

public List<CompletableFuture<Boolean>> evaluateGav(Gav gav, Set<GavPattern> patterns) {
List<CompletableFuture<Boolean>> futures = new ArrayList<>();
for (GavPattern pattern : patterns) {
CompletableFuture<Boolean> future =
CompletableFuture.supplyAsync(new GavMatcher(gav, pattern), executorService);
futures.add(future);
}
return futures;
}

@Override
public void close() throws Exception {
executorService.shutdown();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,16 @@
import java.net.HttpURLConnection;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Future;
import java.util.regex.Pattern;

import com.simpligility.maven.Gav;
import com.simpligility.maven.GavPattern;
import com.simpligility.maven.GavUtil;
import com.simpligility.maven.MavenConstants;
import org.apache.commons.io.FileUtils;
Expand Down Expand Up @@ -59,9 +66,19 @@ public class MavenRepositoryDeployer {

private final TreeSet<String> potentialDeploys = new TreeSet<String>();

public MavenRepositoryDeployer(File repositoryPath) {
private final Set<GavPattern> gavPatterns;

public MavenRepositoryDeployer(File repositoryPath, String deployFilterFile) {
this.repositoryPath = repositoryPath;
initialize();
gavPatterns = loadGavPatternsFromFilterFile(deployFilterFile);
}

/**
* Default constructor to make unit testing easier
*/
public MavenRepositoryDeployer() {
gavPatterns = new HashSet<>();
}

private void initialize() {
Expand Down Expand Up @@ -123,6 +140,12 @@ public void deployToRemote(

Gav gav = GavUtil.getGavFromRepositoryPath(leafRepoPath);

if (!canDeployGav(gav, 10)) {
logger.info("Skipping deployment of " + gav + " as it is not in the deploy filter file.");
skippedDeploys.add(gav.toString());
continue;
}

boolean pomInTarget = false;
if (checkTarget) {
pomInTarget = checkIfPomInTarget(targetUrl, username, password, gav);
Expand Down Expand Up @@ -209,6 +232,14 @@ public void deployToRemote(
}
}
}
summarize();
}

public void summarize() {
logger.info("Deployed {} artifacts.", successfulDeploys.size());
logger.info("Failed to deploy {} artifacts.", failedDeploys.size());
logger.info("Skipped {} artifacts.", skippedDeploys.size());
logger.info("Potentially deploy {} artifacts.", potentialDeploys.size());
}

/**
Expand Down Expand Up @@ -322,4 +353,53 @@ public boolean hasFailure() {
public String getFailureMessage() {
return "Failed to deploy some artifacts.";
}

public Set<GavPattern> loadGavPatternsFromFilterFile(String deployFilterFile) {
Set<GavPattern> gavPatterns = new HashSet<>();
if (deployFilterFile != null) {
try {
File file = new File(deployFilterFile);
if (file.exists()) {
BufferedReader reader = new BufferedReader(new FileReader(file));
String line;
while ((line = reader.readLine()) != null) {
String[] parts = line.split(":");
if (parts.length == 4) {
String groupIdPattern = parts[0].replace("*", ".*");
String artifactIdPattern = parts[1].replace("*", ".*");
String versionPattern = parts[2].replace("*", ".*");
String packagingPattern = parts[3].replace("*", ".*");

Pattern pattern = Pattern.compile(groupIdPattern + ":" + artifactIdPattern + ":"
+ versionPattern + ":" + packagingPattern);

gavPatterns.add(new GavPattern(pattern));
}
}
}
} catch (IOException e) {
logger.error("Failed to load GAVs from filter file", e);
}
}
return gavPatterns;
}

public boolean canDeployGav(Gav gav, int threadPoolSize) {

if (gavPatterns == null || gavPatterns.isEmpty()) {
return true;
}

try (GavMatcherExecutor gavMatcherExecutor = new GavMatcherExecutor(threadPoolSize)) {
List<CompletableFuture<Boolean>> futures = gavMatcherExecutor.evaluateGav(gav, gavPatterns);
for (Future<Boolean> future : futures) {
if (future.get()) {
return true;
}
}
} catch (Exception e) {
logger.error("Error evaluating GAV {} against patterns", gav, e);
}
return false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ private static ArtifactRetriever retrieveArtifacts() {

private static MavenRepositoryDeployer deployArtifacts() {
logger.info("Artifact deployment starting.");
MavenRepositoryDeployer helper = new MavenRepositoryDeployer(cacheDirectory);
MavenRepositoryDeployer helper = new MavenRepositoryDeployer(cacheDirectory, config.getDeployFilterFile());
helper.deployToRemote(
config.getTargetUrl(),
config.getUsername(),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package com.simpligility.maven;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.regex.Pattern;

import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

import org.junit.Test;

public class GavMatcherTest {

@Test
public void testCall_MatchingPattern() throws ExecutionException, InterruptedException {
GavPattern pattern = new GavPattern(Pattern.compile("org\\.apache\\.maven\\.resolver:.*:.*:.*"));
Gav gav = new Gav("org.apache.maven.resolver", "artifactId", "version", "jar");
GavMatcher matcher = new GavMatcher(gav, pattern);

ExecutorService executor = Executors.newSingleThreadExecutor();
Future<Boolean> result = executor.submit(matcher);
assertTrue(result.get());
executor.shutdown();
}

@Test
public void testCall_NonMatchingPattern() throws ExecutionException, InterruptedException {
GavPattern pattern = new GavPattern(Pattern.compile("com\\.simpligility:.*:.*:.*"));
Gav gav = new Gav("org.apache.maven.resolver", "artifactId", "version", "jar");
GavMatcher matcher = new GavMatcher(gav, pattern);

ExecutorService executor = Executors.newSingleThreadExecutor();
Future<Boolean> result = executor.submit(matcher);
assertFalse(result.get());
executor.shutdown();
}

@Test
public void testCall_EmptyPattern() throws ExecutionException, InterruptedException {
GavPattern pattern = new GavPattern(Pattern.compile(""));
Gav gav = new Gav("org.apache.maven.resolver", "artifactId", "version", "jar");
GavMatcher matcher = new GavMatcher(gav, pattern);

ExecutorService executor = Executors.newSingleThreadExecutor();
Future<Boolean> result = executor.submit(matcher);
assertFalse(result.get());
executor.shutdown();
}

@Test
public void testCall_NullGav() throws ExecutionException, InterruptedException {
GavPattern pattern = new GavPattern(Pattern.compile("org\\.apache\\.maven\\.resolver:.*:.*:.*"));
GavMatcher matcher = new GavMatcher(null, pattern);

ExecutorService executor = Executors.newSingleThreadExecutor();
Future<Boolean> result = executor.submit(matcher);
assertFalse(result.get());
executor.shutdown();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.simpligility.maven;

import java.util.regex.Pattern;

import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

import org.junit.Test;

public class GavPatternTest {

@Test
public void testMatches_ValidPattern() {
GavPattern pattern = new GavPattern(Pattern.compile("org\\.apache\\.maven\\.resolver:.*:.*:.*"));
Gav gav = new Gav("org.apache.maven.resolver", "artifactId", "version", "jar");
assertTrue(pattern.matches(gav));
}

@Test
public void testMatches_InvalidPattern() {
GavPattern pattern = new GavPattern(Pattern.compile("com\\.simpligility:.*:.*:.*"));
Gav gav = new Gav("org.apache.maven.resolver", "artifactId", "version", "jar");
assertFalse(pattern.matches(gav));
}

@Test
public void testMatches_EmptyPattern() {
GavPattern pattern = new GavPattern(Pattern.compile(""));
Gav gav = new Gav("org.apache.maven.resolver", "artifactId", "version", "jar");
assertFalse(pattern.matches(gav));
}

@Test
public void testMatches_NullGav() {
GavPattern pattern = new GavPattern(Pattern.compile("org\\.apache\\.maven\\.resolver:.*:.*:.*"));
assertFalse(pattern.matches(null));
}
}
Loading

0 comments on commit 8c64664

Please sign in to comment.