Skip to content

Commit

Permalink
[JENKINS-47823] Make withMaven compatible with docker-pipeline:1.14 (#…
Browse files Browse the repository at this point in the history
…108)

[JENKINS-47823] Add warning for docker-pipeline incompatibility + export MAVEN_CONFIG + export MVN_CMD
  • Loading branch information
Cyrille Le Clerc committed Nov 6, 2017
1 parent 0cc6d3f commit c7d85c5
Show file tree
Hide file tree
Showing 2 changed files with 86 additions and 63 deletions.
Expand Up @@ -75,7 +75,6 @@
import hudson.slaves.WorkspaceList;
import hudson.tasks.Maven;
import hudson.tasks.Maven.MavenInstallation;
import hudson.util.ArgumentListBuilder;
import jenkins.model.Jenkins;
import jenkins.mvn.DefaultGlobalSettingsProvider;
import jenkins.mvn.DefaultSettingsProvider;
Expand Down Expand Up @@ -107,9 +106,10 @@
class WithMavenStepExecution extends StepExecution {

private static final long serialVersionUID = 1L;
private static final String MAVEN_HOME = "MAVEN_HOME";
private static final String M2_HOME = "M2_HOME";
private static final String MAVEN_HOME = "MAVEN_HOME";
private static final String MAVEN_OPTS = "MAVEN_OPTS";
private static final String MVN_CMD = "MVN_CMD";

private static final Logger LOGGER = Logger.getLogger(WithMavenStepExecution.class.getName());

Expand All @@ -130,7 +130,7 @@ class WithMavenStepExecution extends StepExecution {
private transient BodyExecution body;

/**
* Inidicates if running on docker with <tt>docker.image()</tt>
* Indicates if running on docker with <tt>docker.image()</tt>
*/
private boolean withContainer;

Expand Down Expand Up @@ -173,14 +173,25 @@ public boolean start() throws Exception {

withContainer = detectWithContainer();

if (withContainer) {
listener.getLogger().print("[withMaven] WARNING: \"withMaven(){...}\" step running within \"docker.image('image').inside {...}\"." +
" Since the Docker Pipeline Plugin version 1.14, you MUST prepend the 'MVN_CMD' environment variable" +
" to the 'PATH' environment variable in every 'sh' step that invokes 'mvn'. ");
listener.getLogger().print("See ");
listener.hyperlink("https://wiki.jenkins.io/display/JENKINS/Pipeline+Maven+Plugin#PipelineMavenPlugin-HowtousethePipelineMavenPluginwithDocker", "Pipeline Maven Plugin FAQ");
listener.getLogger().println(".");
listener.getLogger().println("[withMaven] Sample:");
listener.getLogger().println("[withMaven] sh \"export PATH=$MVN_CMD:$PATH && mvn ...\"");
}

setupJDK();

// list of credentials injected by withMaven. They will be tracked and masked in the logs
Collection<Credentials> credentials = new ArrayList<>();
setupMaven(credentials);

if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.log(Level.FINE, this.build + " - Track usage and mask password of credentials " + Joiner.on(", ").join(Collections2.transform(credentials, new CredentialsToPrettyString())));
LOGGER.log(Level.FINE, this.build + " - Track usage and mask password of credentials " + Joiner.on(", ").join(Collections2.transform(credentials, new CredentialsToPrettyString())));
}
CredentialsProvider.trackAll(build, new ArrayList<>(credentials));

Expand Down Expand Up @@ -243,8 +254,8 @@ private void setupJDK() throws AbortException, IOException, InterruptedException
// see #detectWithContainer()
LOGGER.log(Level.FINE, "Ignoring JDK installation parameter: {0}", jdkInstallationName);
console.println("WARNING: \"withMaven(){...}\" step running within \"docker.image().inside{...}\"," +
" tool installations are not available see https://issues.jenkins-ci.org/browse/JENKINS-36159. " +
"You have specified a JDK installation \"" + jdkInstallationName + "\", which will be ignored.");
" tool installations are not available see https://issues.jenkins-ci.org/browse/JENKINS-36159. " +
"You have specified a JDK installation \"" + jdkInstallationName + "\", which will be ignored.");
return;
}

Expand All @@ -264,31 +275,28 @@ private void setupJDK() throws AbortException, IOException, InterruptedException
}

/**
*
* @param credentials list of credentials injected by withMaven. They will be tracked and masked in the logs.
* @throws AbortException
* @throws IOException
* @throws InterruptedException
*/
private void setupMaven(@Nonnull Collection<Credentials> credentials) throws AbortException, IOException, InterruptedException {
String mvnExecPath = obtainMavenExec();

// Temp dir with the wrapper that will be prepended to the path
private void setupMaven(@Nonnull Collection<Credentials> credentials) throws IOException, InterruptedException {
// Temp dir with the wrapper that will be prepended to the path and the temporary files used by withMazven (settings files...)
tempBinDir = tempDir(ws).child("withMaven" + Util.getDigestOf(UUID.randomUUID().toString()).substring(0, 8));
tempBinDir.mkdirs();
// set the path to our script
envOverride.put("PATH+MAVEN", tempBinDir.getRemote());

LOGGER.log(Level.FINE, "Using temp dir: {0}", tempBinDir.getRemote());
// SETTINGS FILES
String settingsFilePath = setupSettingFile(credentials);
String globalSettingsFilePath = setupGlobalSettingFile(credentials);

if (mvnExecPath == null) {
throw new AbortException("Couldn\u2019t find any maven executable");
}
// LOCAL REPOSITORY
String mavenLocalRepo = setupMavenLocalRepo();

FilePath mvnExec = new FilePath(ws.getChannel(), mvnExecPath);
// MAVEN EVENT SPY
FilePath mavenSpyJarPath = setupMavenSpy();

// JAVA_TOOL_OPTIONS: https://docs.oracle.com/javase/8/docs/technotes/guides/troubleshoot/envvars002.html
//
// JAVA_TOOL_OPTIONS
// https://docs.oracle.com/javase/8/docs/technotes/guides/troubleshoot/envvars002.html
String javaToolsOptions = env.get("JAVA_TOOL_OPTIONS", "");
if (StringUtils.isNotEmpty(javaToolsOptions)) {
javaToolsOptions += " ";
Expand All @@ -297,11 +305,25 @@ private void setupMaven(@Nonnull Collection<Credentials> credentials) throws Abo
"-Dorg.jenkinsci.plugins.pipeline.maven.reportsFolder=\"" + this.tempBinDir.getRemote() + "\" ";
envOverride.put("JAVA_TOOL_OPTIONS", javaToolsOptions);

String content = mavenWrapperContent(mvnExec, setupSettingFile(credentials), setupGlobalSettingFile(credentials), setupMavenLocalRepo());
//
// MAVEN_CONFIG
StringBuilder mavenConfig = new StringBuilder();
mavenConfig.append("--batch-mode ");
mavenConfig.append("--show-version ");
if (StringUtils.isNotEmpty(settingsFilePath)) {
mavenConfig.append("--settings " + settingsFilePath + " ");
}
if (StringUtils.isNotEmpty(globalSettingsFilePath)) {
mavenConfig.append("--global-settings " + globalSettingsFilePath + " ");
}
if (StringUtils.isNotEmpty(mavenLocalRepo)) {
mavenConfig.append("-Dmaven.repo.local=" + mavenLocalRepo + " ");
}

createWrapperScript(tempBinDir, mvnExec.getName(), content);
envOverride.put("MAVEN_CONFIG", mavenConfig.toString());

// Maven Ops
//
// MAVEN_OPTS
if (StringUtils.isNotEmpty(step.getMavenOpts())) {
String mavenOpts = envOverride.expand(env.expand(step.getMavenOpts()));

Expand All @@ -311,6 +333,25 @@ private void setupMaven(@Nonnull Collection<Credentials> credentials) throws Abo
}
envOverride.put(MAVEN_OPTS, mavenOpts.replaceAll("[\t\r\n]+", " "));
}

// MAVEN SCRIPT WRAPPER
String mvnExecPath = obtainMavenExec();

LOGGER.log(Level.FINE, "Using temp dir: {0}", tempBinDir.getRemote());

if (mvnExecPath == null) {
throw new AbortException("Couldn\u2019t find any maven executable");
}

FilePath mvnExec = new FilePath(ws.getChannel(), mvnExecPath);
String content = generateMavenWrapperScriptContent(mvnExec);

// ADD MAVEN WRAPPER SCRIPT PARENT DIRECTORY TO PATH
// WARNING MUST BE INVOKED AFTER obtainMavenExec(), THERE SEEM TO BE A BUG IN ENVIRONMENT VARIABLE HANDLING IN obtainMavenExec()
envOverride.put("PATH+MAVEN", tempBinDir.getRemote());

createWrapperScript(tempBinDir, mvnExec.getName(), content);

}

private FilePath setupMavenSpy() throws IOException, InterruptedException {
Expand Down Expand Up @@ -348,7 +389,7 @@ private FilePath setupMavenSpy() throws IOException, InterruptedException {
return mavenSpyJarFilePath;
}

private String obtainMavenExec() throws AbortException, IOException, InterruptedException {
private String obtainMavenExec() throws IOException, InterruptedException {
String mavenInstallationName = step.getMaven();
LOGGER.log(Level.FINE, "Setting up maven: {0}", mavenInstallationName);

Expand All @@ -371,7 +412,7 @@ private String obtainMavenExec() throws AbortException, IOException, Interrupted
if (mavenInstallationName.equals(i.getName())) {
mavenInstallation = i;
consoleMessage.append(" use Maven installation '" + mavenInstallation.getName() + "'");
LOGGER.log(Level.FINE, "Found maven installation {0} with installation home {1}", new Object[]{mavenInstallation.getName(), mavenInstallation.getHome()});
LOGGER.log(Level.FINE, "Found maven installation {0} with installation home {1}", new Object[]{mavenInstallation.getName(), mavenInstallation.getHome()});
break;
}
}
Expand Down Expand Up @@ -491,58 +532,39 @@ private String readFromProcess(String... args) throws InterruptedException {
}

/**
* Generates the content of the maven wrapper script that works
* Generates the content of the maven wrapper script
*
* @param mvnExec maven executable location
* @param settingsFile settings file
* @param globalSettingsFile global settings file
* @param mavenLocalRepo maven local repo location
* @param mvnExec maven executable location
* @return wrapper script content
* @throws AbortException when problems creating content
*/
private String mavenWrapperContent(FilePath mvnExec, String settingsFile, String globalSettingsFile, String mavenLocalRepo) throws AbortException {

ArgumentListBuilder argList = new ArgumentListBuilder(mvnExec.getRemote());
private String generateMavenWrapperScriptContent(FilePath mvnExec) throws AbortException {

boolean isUnix = Boolean.TRUE.equals(getComputer().isUnix());

String lineSep = isUnix ? "\n" : "\r\n";

if (StringUtils.isNotEmpty(settingsFile)) {
argList.add("--settings", settingsFile);
}
StringBuilder script = new StringBuilder();

if (StringUtils.isNotEmpty(globalSettingsFile)) {
argList.add("--global-settings", globalSettingsFile);
}
if (isUnix) { // Linux, Unix, MacOSX
String lineSep = "\n";
script.append("#!/bin/sh -e").append(lineSep);
script.append("echo ----- withMaven Wrapper script -----").append(lineSep);

This comment has been minimized.

Copy link
@stephenc

stephenc Oct 8, 2018

Member

Just absolutely wonderful. Now anyone trying to parse Maven output cannot because you trample all over them. At least check for the presence of the -q or --quiet command line option and suppress this in those cases

This comment has been minimized.

Copy link
@stephenc

stephenc Oct 8, 2018

Member

Bonus, if JENKINS_MAVEN_AGENT_DISABLED=true then also do not show this

script.append(mvnExec.getRemote() + " $MAVEN_CONFIG \"$@\"").append(lineSep);

if (StringUtils.isNotEmpty(mavenLocalRepo)) {
argList.addKeyValuePair(null, "maven.repo.local", mavenLocalRepo, false);
}

argList.add("--batch-mode");
argList.add("--show-version");

StringBuilder c = new StringBuilder();

if (isUnix) {
c.append("#!/bin/sh -e").append(lineSep);
} else { // Windows
c.append("@echo off").append(lineSep);
String lineSep = "\r\n";
script.append("@echo off").append(lineSep);
script.append("echo ----- withMaven Wrapper script -----").append(lineSep);
script.append(mvnExec.getRemote() + " %MAVEN_CONFIG% %*").append(lineSep);
}

c.append("echo ----- withMaven Wrapper script -----").append(lineSep);
c.append(argList.toString()).append(isUnix ? " \"$@\"" : " %*").append(lineSep);

String content = c.toString();
LOGGER.log(Level.FINER, "Generated wrapper: {0}", content);
return content;
LOGGER.log(Level.FINER, "Generated Maven wrapper script: \n{0}", script);
return script.toString();
}

/**
* Creates the actual wrapper script file and sets the permissions.
*
* @param tempBinDir dir to create the script file on
* @param tempBinDir dir to create the script file
* @param name the script file name
* @param content contents of the file
* @return
Expand All @@ -551,6 +573,7 @@ private String mavenWrapperContent(FilePath mvnExec, String settingsFile, String
*/
private FilePath createWrapperScript(FilePath tempBinDir, String name, String content) throws IOException, InterruptedException {
FilePath scriptFile = tempBinDir.child(name);
envOverride.put(MVN_CMD, scriptFile.getRemote());

scriptFile.write(content, getComputer().getDefaultCharset().name());
scriptFile.chmod(0755);
Expand Down Expand Up @@ -729,7 +752,7 @@ private String setupGlobalSettingFile(@Nonnull Collection<Credentials> credentia
* folder to use it with the maven wrapper script
*
* @param mavenSettingsConfigId config file id from Config File Provider
* @param mavenSettingsFile path to write te content to
* @param mavenSettingsFile path to write te content to
* @param credentials
* @return the {@link FilePath} to the settings file
* @throws AbortException in case of error
Expand Down Expand Up @@ -784,7 +807,7 @@ private void settingsFromConfig(String mavenSettingsConfigId, FilePath mavenSett
* folder to use it with the maven wrapper script
*
* @param mavenGlobalSettingsConfigId global config file id from Config File Provider
* @param mavenGlobalSettingsFile path to write te content to
* @param mavenGlobalSettingsFile path to write te content to
* @param credentials
* @return the {@link FilePath} to the settings file
* @throws AbortException in case of error
Expand Down
Expand Up @@ -599,7 +599,7 @@ public void maven_settings_path_defined_through_pipeline_attribute() throws Exce
"</project>'''\n" +
"\n" +
" withMaven(maven: 'apache-maven-3.5.0', mavenSettingsFilePath: 'maven-settings.xml') {\n" +
" sh 'mvn help:effective-settings'\n" +
" sh 'env && mvn help:effective-settings'\n" +
" }\n" +
"}\n";

Expand Down

0 comments on commit c7d85c5

Please sign in to comment.