Skip to content

Commit

Permalink
Merge pull request #137 from Thales-Netherlands/feature/fine-grained-…
Browse files Browse the repository at this point in the history
…control-retain-plugin-executions-on-master

Fine-grained control to retaining plugin executions on master
  • Loading branch information
bvarner authored Apr 19, 2023
2 parents f8414ec + df46c91 commit db0864b
Show file tree
Hide file tree
Showing 3 changed files with 97 additions and 30 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,22 @@
import org.apache.maven.MavenExecutionException;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.model.Plugin;
import org.apache.maven.model.PluginExecution;
import org.apache.maven.plugin.prefix.NoPluginFoundForPrefixException;
import org.apache.maven.project.MavenProject;
import org.codehaus.plexus.component.annotations.Component;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
* Maven extension which removes (skips) undesired plugins from the build reactor when running on a master branch.
Expand All @@ -26,7 +29,7 @@
@Component(role = AbstractMavenLifecycleParticipant.class, hint = "promote-master")
public class MasterPromoteExtension extends AbstractBranchDetectingExtension {

private static final Set<String> PLUGIN_WHITELIST = Collections.unmodifiableSet(
private static final Set<String> DEFAULT_PLUGIN_WHITELIST = Collections.unmodifiableSet(
new HashSet<>(
Arrays.asList(
"org.apache.maven.plugins:maven-deploy-plugin",
Expand All @@ -39,45 +42,54 @@ public class MasterPromoteExtension extends AbstractBranchDetectingExtension {
public void afterProjectsRead(final MavenSession session) throws MavenExecutionException {
super.afterProjectsRead(session);

// Any plugin which is part of the project goals needs to be retained.
List<Plugin> pluginsToRetain = new ArrayList<>(session.getGoals().size());
// Build a whitelist of plugin (executions) that should remain while running on master.
// The key of the map is the plugin key, the value is a collection of specific executions of that plugin
// to retain (where an empty collection denotes that all executions should be retained).
final Map<String, Collection<String>> pluginWhitelist = new HashMap<>();

// First load the default whitelist
DEFAULT_PLUGIN_WHITELIST.forEach(plugin -> pluginWhitelist.put(plugin, Collections.emptyList()));

// Then determine which plugin(s) are activated through commandline supplied goals
List<String> goals = session.getGoals();
for (String goal : goals) {
int delimiter = goal.indexOf(":");
if (delimiter != -1) {
String prefix = goal.substring(0, delimiter);
try {
pluginsToRetain.add(descriptorCreator.findPluginForPrefix(prefix, session));
pluginWhitelist.put(descriptorCreator.findPluginForPrefix(prefix, session).getKey(), Collections.emptyList());
} catch (NoPluginFoundForPrefixException ex) {
logger.warn("gitflow-helper-maven-plugin: Unable to resolve project plugin for prefix: " + prefix + " for goal: " + goal);
}
}
}

// Build up a map of plugins to remove from projects, if we're on the master branch.
Map<MavenProject, List<Plugin>> pluginsToDrop = new HashMap<>();

final List<String> configuredPluginsToRetain;
// Finally parse the configured plugin (executions) to retain
if (this.retainPlugins != null) {
configuredPluginsToRetain = this.retainPlugins;
} else {
configuredPluginsToRetain = Collections.emptyList();
for (String retainPlugin : retainPlugins) {
String[] elements = retainPlugin.split(":");
if (elements.length != 2 && elements.length != 3) {
throw new MavenExecutionException(
"Expected syntax for retainPlugin: groupId:artifactId[:execution-id] but found " + retainPlugin,
session.getRequest().getPom()
);
}
final String pluginKey = Plugin.constructKey(elements[0], elements[1]);
if (elements.length == 2) {
pluginWhitelist.put(pluginKey, Collections.emptyList());
} else {
final Collection<String> executionsToRetain;
if (pluginWhitelist.containsKey(pluginKey)) {
executionsToRetain = pluginWhitelist.get(pluginKey);
} else {
executionsToRetain = new HashSet<>();
pluginWhitelist.put(pluginKey, executionsToRetain);
}
executionsToRetain.add(elements[2]);
}
}
}

for (MavenProject project : session.getProjects()) {

// Create a list of all plugins that are not in the whitelist, not explicitly invoked from the commandline,
// and not configured to be allowed on master/support.
List<Plugin> dropPlugins = project.getModel().getBuild().getPlugins()
.stream()
.filter(plugin -> !PLUGIN_WHITELIST.contains(plugin.getKey()))
.filter(plugin -> !pluginsToRetain.contains(plugin))
.filter(plugin -> !configuredPluginsToRetain.contains(plugin.getKey()))
.collect(Collectors.toList());

pluginsToDrop.put(project, dropPlugins);
}

if (pluginFound) {
boolean pruneBuild = false;
Expand All @@ -95,12 +107,25 @@ public void afterProjectsRead(final MavenSession session) throws MavenExecutionE
}

if (pruneBuild) {
logger.info("The following plugin (execution) whitelist will be applied: " + pluginWhitelist);
for (MavenProject project : session.getProjects()) {
// Drop all the plugins from the build except for the gitflow-helper-maven-plugin, or plugins we
// invoked goals for which could be mapped back to plugins in our project build.
// Goals invoked from the commandline which cannot be mapped back to our project, will get warnings, but should still execute.
// If someone is on 'master' and starts executing goals, we need to allow them to do that.
project.getModel().getBuild().getPlugins().removeAll(pluginsToDrop.get(project));
// Using the pluginWhiteList, determine which plugin (executions) are allowed to stay.
final Iterator<Plugin> iterator = project.getModel().getBuild().getPlugins().iterator();
while (iterator.hasNext()) {
Plugin plugin = iterator.next();
if (pluginWhitelist.containsKey(plugin.getKey())) {
// If the plugin key is present in the whitelist, either all executions must be retained
// (in case of an empty collection), or only those mentioned in the collection.
final Collection<String> executionToRetain = pluginWhitelist.get(plugin.getKey());
if (!executionToRetain.isEmpty()) {
plugin.getExecutions()
.removeIf(pluginExecution -> !executionToRetain.contains(pluginExecution.getId()));
}
} else {
// If the plugin's key is not present in the whitelist, it can be dropped
iterator.remove();
}
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
@RunWith(BlockJUnit4ClassRunner.class)
public class MasterSupportBranchIT extends AbstractIntegrationTest {
private static final String PROMOTION_FAILED_MESSAGE = "Promotion Deploy from origin/master allowed something to Compile.";
public static final String FILTERED_EXECUTION = "Filtered execution should not be executing";

@Test
public void releaseVersionSuccess() throws Exception {
Expand Down Expand Up @@ -163,6 +164,15 @@ public void dontPruneExplicitlyConfiguredPlugins() throws Exception {
}

verifier.verifyTextInLog("Generating flattened POM of project"); // This should still be there.
verifier.verifyTextInLog("This execution must be retained"); // This should also still be there
try {
verifier.verifyTextInLog("This execution must be filtered"); // This should not be here
throw new VerificationException(FILTERED_EXECUTION);
} catch (VerificationException ve) {
if (ve.getMessage().equals(FILTERED_EXECUTION)) {
throw ve;
}
}
verifier.verifyTextInLog(
"gitflow-helper-maven-plugin: Enabling MasterPromoteExtension. GIT_BRANCH: [origin/master] matches masterBranchPattern");
verifier.verifyTextInLog("[INFO] Setting release artifact repository to: [releases]");
Expand Down
32 changes: 32 additions & 0 deletions src/test/resources/project-stub/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,37 @@
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<version>1.8</version>
<executions>
<execution>
<id>retain</id>
<goals>
<goal>run</goal>
</goals>
<phase>validate</phase>
<configuration>
<target>
<echo>This execution must be retained</echo>
</target>
</configuration>
</execution>
<execution>
<id>filter</id>
<goals>
<goal>run</goal>
</goals>
<phase>validate</phase>
<configuration>
<target>
<echo>This execution must be filtered</echo>
</target>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>

Expand All @@ -147,6 +178,7 @@
<configuration>
<retainPlugins>
<retainPlugin>org.codehaus.mojo:flatten-maven-plugin</retainPlugin>
<retainPlugin>org.apache.maven.plugins:maven-antrun-plugin:retain</retainPlugin>
</retainPlugins>
</configuration>
</plugin>
Expand Down

0 comments on commit db0864b

Please sign in to comment.