Skip to content

Commit fd3a2d2

Browse files
committed
Initial work on stage repo-based purging.
* The repository definition for stage will need to be: ** Discrete from other repositories. ** Discrete from any nexus 'mirror' id set in a mirror definition. Maven's 'enhanced' local repo managers (aether, if you really want to get pendantic) like to cache source repositories by repo (server) "Id", not by URI base path. This has the consequence of: If you use an authenticated mirror for your 'central' proxy (to say, public/groups in nexus 2 parlance), and you give that mirror an 'id'... For argument sake, let's say that's 'corporate-nexus-proxy', and then define your credentials for 'coroporate-nexus-proxy'. Running maven and having dependencies resolve from that mirror, will get them recorded in the local repo as having come from 'corporate-nexus-proxy'. If you use that same 'id' as part of your stage repository definition: `<stageDeploymentRepository>corporate-nexus-proxy::foo::bar::false</stageDeploymentRepository>` Then any non-snapshot dependency will look like it came from the 'stage' repository, and the 'update-stage-dependencies' will purge all your local non-snapshot dependencies and re-resolve them. Yick. If you use a discrete id for the `stageDeploymentRepository` (a good idea, from a security standpoint, in fact), this won't happen, and only things which were actually resolved from the stage repo's id into your local will become elligable for purging. This is doubly awesome, in that once a thing locally resolved from stage gets purged, and then reresolved from 'release' (due it having been you know, promoted) your local repo will stop purging them, since the remote ID wouldn't match the stage repo Id anymore. It's a bit convoluted, but I'll update the docs and readmes in due-course.. and I may move this behavior to the extension (if I can reliably inject the necessary bits) to purge earlier in the build cycle.
1 parent 3fab816 commit fd3a2d2

File tree

2 files changed

+149
-64
lines changed

2 files changed

+149
-64
lines changed

src/main/java/com/e_gineering/maven/gitflowhelper/AbstractGitflowBasedRepositoryMojo.java

Lines changed: 57 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,14 @@
99
import org.apache.maven.plugins.annotations.Parameter;
1010
import org.apache.maven.project.MavenProjectHelper;
1111
import org.codehaus.plexus.util.FileUtils;
12+
import org.eclipse.aether.DefaultRepositorySystemSession;
1213
import org.eclipse.aether.RepositorySystemSession;
1314
import org.eclipse.aether.artifact.DefaultArtifact;
1415
import org.eclipse.aether.impl.ArtifactResolver;
16+
import org.eclipse.aether.internal.impl.EnhancedLocalRepositoryManagerFactory;
1517
import org.eclipse.aether.repository.LocalRepository;
1618
import org.eclipse.aether.repository.RemoteRepository;
19+
import org.eclipse.aether.repository.RepositoryPolicy;
1720
import org.eclipse.aether.resolution.ArtifactRequest;
1821
import org.eclipse.aether.resolution.ArtifactResolutionException;
1922
import org.eclipse.aether.resolution.ArtifactResult;
@@ -27,7 +30,6 @@
2730
import java.io.InputStreamReader;
2831
import java.io.OutputStreamWriter;
2932
import java.io.PrintWriter;
30-
import java.lang.reflect.Field;
3133
import java.nio.charset.Charset;
3234
import java.nio.file.Files;
3335
import java.util.ArrayList;
@@ -54,7 +56,10 @@ public abstract class AbstractGitflowBasedRepositoryMojo extends AbstractGitflow
5456
protected String snapshotDeploymentRepository;
5557

5658
@Parameter(defaultValue = "${repositorySystemSession}", required = true)
57-
private RepositorySystemSession session;
59+
protected RepositorySystemSession session;
60+
61+
@Component
62+
protected EnhancedLocalRepositoryManagerFactory localRepositoryManagerFactory;
5863

5964
@Parameter(defaultValue = "${project.build.directory}", required = true)
6065
protected File buildDirectory;
@@ -109,7 +114,7 @@ protected ArtifactRepository getDeploymentRepository(final String altRepository)
109114
* @throws MojoExecutionException
110115
* @throws MojoFailureException
111116
*/
112-
private RemoteRepository getRepository(final String altRepository) throws MojoExecutionException, MojoFailureException {
117+
protected RemoteRepository getRepository(final String altRepository) throws MojoExecutionException, MojoFailureException {
113118
if (getLog().isDebugEnabled()) {
114119
getLog().debug("Creating remote Aether repository (to resolve remote artifacts) for: " + altRepository);
115120
}
@@ -137,15 +142,14 @@ private RemoteRepository getRepository(final String altRepository) throws MojoEx
137142
}
138143

139144
private String getCoordinates(ArtifactResult result) {
140-
StringBuilder buffer = new StringBuilder( 128 );
141-
buffer.append( result.getArtifact().getGroupId() );
142-
buffer.append( ':' ).append( result.getArtifact().getArtifactId() );
143-
buffer.append( ':' ).append( result.getArtifact().getExtension() );
144-
if ( result.getArtifact().getClassifier().length() > 0 )
145-
{
146-
buffer.append( ':' ).append( result.getArtifact().getClassifier() );
145+
StringBuilder buffer = new StringBuilder(128);
146+
buffer.append(result.getArtifact().getGroupId());
147+
buffer.append(':').append(result.getArtifact().getArtifactId());
148+
buffer.append(':').append(result.getArtifact().getExtension());
149+
if (result.getArtifact().getClassifier().length() > 0) {
150+
buffer.append(':').append(result.getArtifact().getClassifier());
147151
}
148-
buffer.append( ':' ).append( result.getArtifact().getBaseVersion() );
152+
buffer.append(':').append(result.getArtifact().getBaseVersion());
149153
return buffer.toString();
150154
}
151155

@@ -183,47 +187,46 @@ private String getCoordinates(org.apache.maven.artifact.Artifact artifact) {
183187
* group:artifact:type:classifier:version
184188
*/
185189
protected void attachArtifactCatalog() throws MojoExecutionException {
186-
getLog().info("Cataloging Artifacts for promotion & reattachment: " + project.getBuild().getDirectory());
190+
getLog().info("Cataloging Artifacts for promotion & reattachment: " + project.getBuild().getDirectory());
187191

188-
File catalog = new File(buildDirectory, project.getArtifact().getArtifactId() + ".txt");
192+
File catalog = new File(buildDirectory, project.getArtifact().getArtifactId() + ".txt");
189193

190-
PrintWriter writer = null;
194+
PrintWriter writer = null;
191195

192-
try {
193-
catalog.delete();
194-
buildDirectory.mkdirs();
195-
writer = new PrintWriter(new OutputStreamWriter(new FileOutputStream(catalog), Charset.forName("UTF-8")));
196-
197-
if (project.getArtifact() != null && project.getArtifact().getFile() != null &&
198-
project.getArtifact().getFile().exists() && !project.getArtifact().getFile().isDirectory())
199-
{
200-
String coords = getCoordinates(project.getArtifact());
201-
if (!coords.isEmpty()){
202-
getLog().info("Cataloging: " + coords);
203-
writer.println(coords);
204-
}
205-
} else {
206-
getLog().info("No primary artifact to catalog, cataloging attached artifacts instead.");
196+
try {
197+
catalog.delete();
198+
buildDirectory.mkdirs();
199+
writer = new PrintWriter(new OutputStreamWriter(new FileOutputStream(catalog), Charset.forName("UTF-8")));
200+
201+
if (project.getArtifact() != null && project.getArtifact().getFile() != null &&
202+
project.getArtifact().getFile().exists() && !project.getArtifact().getFile().isDirectory()) {
203+
String coords = getCoordinates(project.getArtifact());
204+
if (!coords.isEmpty()) {
205+
getLog().info("Cataloging: " + coords);
206+
writer.println(coords);
207207
}
208+
} else {
209+
getLog().info("No primary artifact to catalog, cataloging attached artifacts instead.");
210+
}
208211

209-
// Iterate the attached artifacts.
210-
for (org.apache.maven.artifact.Artifact artifact : project.getAttachedArtifacts()) {
211-
String coords = getCoordinates(artifact);
212-
if (!coords.isEmpty()) {
213-
getLog().info("Cataloging: " + coords);
214-
writer.println(coords);
215-
}
212+
// Iterate the attached artifacts.
213+
for (org.apache.maven.artifact.Artifact artifact : project.getAttachedArtifacts()) {
214+
String coords = getCoordinates(artifact);
215+
if (!coords.isEmpty()) {
216+
getLog().info("Cataloging: " + coords);
217+
writer.println(coords);
216218
}
219+
}
217220

218-
getLog().info("Attaching catalog artifact: " + catalog);
219-
projectHelper.attachArtifact(project, "txt", "catalog", catalog);
220-
} catch (IOException ioe) {
221-
throw new MojoExecutionException("Failed to create catalog of artifacts", ioe);
222-
} finally {
223-
if (writer != null) {
224-
writer.close();
225-
}
221+
getLog().info("Attaching catalog artifact: " + catalog);
222+
projectHelper.attachArtifact(project, "txt", "catalog", catalog);
223+
} catch (IOException ioe) {
224+
throw new MojoExecutionException("Failed to create catalog of artifacts", ioe);
225+
} finally {
226+
if (writer != null) {
227+
writer.close();
226228
}
229+
}
227230
}
228231

229232
/**
@@ -250,35 +253,30 @@ protected void attachExistingArtifacts(final String sourceRepository, final bool
250253
// A place to store our resolved files...
251254
List<ArtifactResult> resolvedArtifacts = new ArrayList<ArtifactResult>();
252255

253-
// Keep track of the original base directory.
254-
Field localBaseDir = null;
255-
File originalBaseDir = session.getLocalRepositoryManager().getRepository().getBasedir();
256256

257-
// Disable the local repository - using a bit of reflection that I wish we didn't need to use.
257+
// Use a custom repository session, setup to force a few behaviors we like.
258+
DefaultRepositorySystemSession tempSession = new DefaultRepositorySystemSession(session);
259+
tempSession.setUpdatePolicy(RepositoryPolicy.UPDATE_POLICY_ALWAYS);
260+
258261
File tempRepo = null;
259262
if (disableLocal) {
260-
getLog().info("Disabling local repository @ " + session.getLocalRepository().getBasedir());
263+
getLog().info("Disabling local repository @ " + tempSession.getLocalRepository().getBasedir());
261264
try {
262-
localBaseDir = LocalRepository.class.getDeclaredField("basedir");
263-
localBaseDir.setAccessible(true);
264-
265-
// Generate a new temp directory.
266265
tempRepo = Files.createTempDirectory("gitflow-helper-maven-plugin-repo").toFile();
267266

268267
getLog().info("Using temporary local repository @ " + tempRepo.getAbsolutePath());
269-
localBaseDir.set(session.getLocalRepositoryManager().getRepository(), tempRepo);
268+
tempSession.setLocalRepositoryManager(localRepositoryManagerFactory.newInstance(tempSession, new LocalRepository(tempRepo)));
270269
} catch (Exception ex) {
271270
getLog().warn("Failed to disable local repository path.", ex);
272271
}
273272
}
274273

275-
276274
List<ArtifactRequest> requiredArtifacts = new ArrayList<ArtifactRequest>();
277275

278276
// Locate our text catalog classifier file. :-)
279277
BufferedReader reader = null;
280278
try {
281-
ArtifactResult catalogResult = artifactResolver.resolveArtifact(session, new ArtifactRequest(new DefaultArtifact(project.getGroupId(), project.getArtifactId(), "catalog", "txt", project.getVersion()), remoteRepositories, null));
279+
ArtifactResult catalogResult = artifactResolver.resolveArtifact(tempSession, new ArtifactRequest(new DefaultArtifact(project.getGroupId(), project.getArtifactId(), "catalog", "txt", project.getVersion()), remoteRepositories, null));
282280
resolvedArtifacts.add(catalogResult);
283281

284282
if (catalogResult.isResolved()) {
@@ -301,14 +299,15 @@ protected void attachExistingArtifacts(final String sourceRepository, final bool
301299
if (reader != null) {
302300
try {
303301
reader.close();
304-
} catch (IOException ioe) {}
302+
} catch (IOException ioe) {
303+
}
305304
}
306305
}
307306

308307

309308
// Resolve the artifacts from the catalog (if there are any)
310309
try {
311-
resolvedArtifacts.addAll(artifactResolver.resolveArtifacts(session, requiredArtifacts));
310+
resolvedArtifacts.addAll(artifactResolver.resolveArtifacts(tempSession, requiredArtifacts));
312311
} catch (ArtifactResolutionException are) {
313312
throw new MojoExecutionException("Failed to resolve the required project files from: " + sourceRepository, are);
314313
}
@@ -339,12 +338,6 @@ protected void attachExistingArtifacts(final String sourceRepository, final bool
339338

340339
// Restore the local repository, again using reflection.
341340
if (disableLocal) {
342-
try {
343-
localBaseDir.set(session.getLocalRepositoryManager().getRepository(), originalBaseDir);
344-
localBaseDir.setAccessible(false);
345-
} catch (Exception ex) {
346-
getLog().warn("Failed to restore original local repository path.", ex);
347-
}
348341
if (tempRepo != null) {
349342
try {
350343
FileUtils.deleteDirectory(tempRepo);
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
package com.e_gineering.maven.gitflowhelper;
2+
3+
import org.apache.commons.io.FileUtils;
4+
import org.apache.maven.plugin.MojoExecutionException;
5+
import org.apache.maven.plugin.MojoFailureException;
6+
import org.apache.maven.plugins.annotations.Component;
7+
import org.apache.maven.plugins.annotations.LifecyclePhase;
8+
import org.apache.maven.plugins.annotations.Mojo;
9+
import org.apache.maven.project.DefaultDependencyResolutionRequest;
10+
import org.apache.maven.project.DependencyResolutionException;
11+
import org.apache.maven.project.DependencyResolutionResult;
12+
import org.apache.maven.project.ProjectDependenciesResolver;
13+
import org.eclipse.aether.DefaultRepositoryCache;
14+
import org.eclipse.aether.DefaultRepositorySystemSession;
15+
import org.eclipse.aether.graph.Dependency;
16+
import org.eclipse.aether.repository.LocalArtifactRequest;
17+
import org.eclipse.aether.repository.LocalArtifactResult;
18+
import org.eclipse.aether.repository.LocalRepositoryManager;
19+
import org.eclipse.aether.repository.RemoteRepository;
20+
21+
import java.io.File;
22+
import java.io.IOException;
23+
import java.util.Arrays;
24+
import java.util.List;
25+
26+
/**
27+
* Forces a re-resolution of all dependency artifacts which were resolved from the 'stage' remote repository.
28+
*/
29+
@Mojo(name = "update-stage-dependencies", defaultPhase = LifecyclePhase.INITIALIZE)
30+
public class UpdateStageDependenciesMojo extends AbstractGitflowBasedRepositoryMojo {
31+
32+
@Component
33+
ProjectDependenciesResolver dependenciesResolver;
34+
35+
@Override
36+
protected void execute(GitBranchType type, String gitBranch, String branchPattern) throws MojoExecutionException, MojoFailureException {
37+
getLog().debug("update-stage-dependencies setting up Repository session...");
38+
39+
DefaultRepositorySystemSession reresolveSession = new DefaultRepositorySystemSession(session);
40+
reresolveSession.setUpdatePolicy(org.eclipse.aether.repository.RepositoryPolicy.UPDATE_POLICY_ALWAYS);
41+
reresolveSession.setCache(new DefaultRepositoryCache());
42+
43+
LocalRepositoryManager localRepositoryManager = reresolveSession.getLocalRepositoryManager();
44+
45+
getLog().debug("configuring stage as the remote repository for artifact resolution requests...");
46+
List<RemoteRepository> stageRepo = Arrays.asList(getRepository(stageDeploymentRepository));
47+
48+
boolean itemsPurged = false;
49+
50+
try {
51+
DependencyResolutionResult depencencyResult = dependenciesResolver.resolve(
52+
new DefaultDependencyResolutionRequest(project, reresolveSession));
53+
54+
for (Dependency dependency : depencencyResult.getResolvedDependencies()) {
55+
if (!dependency.getArtifact().isSnapshot()) {
56+
// Find the artifact in the local repo, and if it came from the 'stageRepo', populate that info
57+
// as the 'repository' on the artifact.
58+
LocalArtifactResult localResult = localRepositoryManager.find(reresolveSession,
59+
new LocalArtifactRequest(dependency.getArtifact(), stageRepo, null));
60+
61+
// If the result has a file... and the getRepository() matched the stage repo id...
62+
if (localResult.getFile() != null && localResult.getRepository() != null) {
63+
getLog().info("Dependency: " + dependency + " from remote repository: " + localResult.getRepository() + " will be purged and re-resolved.");
64+
File deleteTarget = new File(localRepositoryManager.getRepository().getBasedir(), localRepositoryManager.getPathForLocalArtifact(dependency.getArtifact()));
65+
66+
if (deleteTarget.isDirectory()) {
67+
try {
68+
FileUtils.deleteDirectory(deleteTarget);
69+
} catch (IOException ioe) {
70+
getLog().warn("Failed to purge stage artifact from local repository: " + deleteTarget, ioe);
71+
}
72+
} else if (!deleteTarget.delete()) {
73+
getLog().warn("Failed to purge stage artifact from local repository: " + deleteTarget);
74+
}
75+
itemsPurged = true;
76+
}
77+
}
78+
}
79+
} catch (DependencyResolutionException dre) {
80+
throw new MojoExecutionException("Initial dependency resolution to resolve dependencies which may have been provided by the 'stage' repository failed.", dre);
81+
}
82+
83+
84+
if (itemsPurged) {
85+
try {
86+
dependenciesResolver.resolve(new DefaultDependencyResolutionRequest(project, reresolveSession));
87+
} catch (DependencyResolutionException e) {
88+
throw new MojoExecutionException("Post-purge dependency resolution failed!", e);
89+
}
90+
}
91+
}
92+
}

0 commit comments

Comments
 (0)