Skip to content

Commit

Permalink
[JENKINS-40364] Implement the config-files extension point to enhance…
Browse files Browse the repository at this point in the history
… the npmrc user config file to handle NPM registries, scope and credentials. Builder generate the configuration file and delete it when finish.
  • Loading branch information
nfalco79 committed Jan 13, 2017
1 parent ad01b1a commit 20b82f1
Show file tree
Hide file tree
Showing 35 changed files with 1,185 additions and 23 deletions.
5 changes: 5 additions & 0 deletions pom.xml
Expand Up @@ -45,6 +45,11 @@
</properties>

<dependencies>
<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>config-file-provider</artifactId>
<version>2.13</version>
</dependency>
</dependencies>

<repositories>
Expand Down
32 changes: 30 additions & 2 deletions src/main/java/jenkins/plugins/nodejs/NodeJSBuildWrapper.java
@@ -1,9 +1,13 @@
package jenkins.plugins.nodejs;

import java.io.IOException;
import java.util.Collection;
import java.util.Collections;

import javax.annotation.Nonnull;

import org.jenkinsci.lib.configprovider.ConfigProvider;
import org.jenkinsci.lib.configprovider.model.Config;
import org.kohsuke.stapler.DataBoundConstructor;

import hudson.AbortException;
Expand All @@ -12,11 +16,14 @@
import hudson.FilePath;
import hudson.Launcher;
import hudson.Util;
import hudson.model.AbstractBuild;
import hudson.model.AbstractProject;
import hudson.model.Computer;
import hudson.model.Node;
import hudson.model.Run;
import hudson.model.TaskListener;
import hudson.tasks.BuildWrapperDescriptor;
import jenkins.plugins.nodejs.configfiles.NPMConfig;
import jenkins.plugins.nodejs.tools.NodeJSInstallation;
import jenkins.tasks.SimpleBuildWrapper;

Expand Down Expand Up @@ -46,10 +53,12 @@ public String put(String key, String value) {
}

private final String nodeJSInstallationName;
private final String configId;

@DataBoundConstructor
public NodeJSBuildWrapper(String nodeJSInstallationName){
public NodeJSBuildWrapper(String nodeJSInstallationName, String configId) {
this.nodeJSInstallationName = Util.fixEmpty(nodeJSInstallationName);
this.configId = Util.fixEmpty(configId);
}

/**
Expand All @@ -65,6 +74,10 @@ public String getNodeJSInstallationName() {
return nodeJSInstallationName;
}

public String getConfigId() {
return configId;
}

/*
* (non-Javadoc)
* @see jenkins.tasks.SimpleBuildWrapper#setUp(jenkins.tasks.SimpleBuildWrapper.Context, hudson.model.Run, hudson.FilePath, hudson.Launcher, hudson.model.TaskListener, hudson.EnvVars)
Expand All @@ -76,13 +89,19 @@ public void setUp(final Context context, Run<?, ?> build, FilePath workspace, La
if (ni == null) {
throw new IOException(Messages.NodeJSCommandInterpreter_noInstallationFound(nodeJSInstallationName));
}
Node node = workspace.toComputer().getNode();
Computer computer = workspace.toComputer();
if (computer == null) {
throw new AbortException(Messages.NodeJSCommandInterpreter_nodeOffline());
}
Node node = computer.getNode();
if (node == null) {
throw new AbortException(Messages.NodeJSCommandInterpreter_nodeOffline());
}
ni = ni.forNode(node, listener);
ni = ni.forEnvironment(initialEnvironment);
ni.buildEnvVars(new EnvVarsAdapter(context));

NodeJSUtils.supplyConfig(configId, (AbstractBuild<?, ?>) build, listener);
}


Expand All @@ -103,6 +122,15 @@ public NodeJSInstallation[] getInstallations() {
return NodeJSUtils.getInstallations();
}

public Collection<Config> getConfigs() {
ConfigProvider provider = ConfigProvider.getByIdOrNull(NPMConfig.class.getName());
if (provider != null) {
return provider.getAllConfigs();
}

return Collections.emptyList();
}

}

}
37 changes: 33 additions & 4 deletions src/main/java/jenkins/plugins/nodejs/NodeJSCommandInterpreter.java
@@ -1,7 +1,11 @@
package jenkins.plugins.nodejs;

import java.io.IOException;
import java.util.Collection;
import java.util.Collections;

import org.jenkinsci.lib.configprovider.ConfigProvider;
import org.jenkinsci.lib.configprovider.model.Config;
import org.kohsuke.stapler.DataBoundConstructor;

import hudson.AbortException;
Expand All @@ -17,6 +21,7 @@
import hudson.tasks.Builder;
import hudson.tasks.CommandInterpreter;
import hudson.util.ArgumentListBuilder;
import jenkins.plugins.nodejs.configfiles.NPMConfig;
import jenkins.plugins.nodejs.tools.NodeJSInstallation;
import jenkins.plugins.nodejs.tools.Platform;

Expand All @@ -28,9 +33,8 @@
* @author Nikolas Falco
*/
public class NodeJSCommandInterpreter extends CommandInterpreter {
private static final String JAVASCRIPT_EXT = ".js";

private final String nodeJSInstallationName;
private final String configId;
private transient String nodeExec; // NOSONAR

/**
Expand All @@ -40,11 +44,14 @@ public class NodeJSCommandInterpreter extends CommandInterpreter {
* the NodeJS script
* @param nodeJSInstallationName
* the NodeJS label configured in Jenkins
* @param configId
* the provided Config id
*/
@DataBoundConstructor
public NodeJSCommandInterpreter(final String command, final String nodeJSInstallationName) {
public NodeJSCommandInterpreter(final String command, final String nodeJSInstallationName, final String configId) {
super(command);
this.nodeJSInstallationName = Util.fixEmpty(nodeJSInstallationName);
this.configId = Util.fixEmpty(configId);
}

/**
Expand Down Expand Up @@ -82,6 +89,9 @@ public boolean perform(AbstractBuild<?, ?> build, Launcher launcher, BuildListen
throw new AbortException(Messages.NodeJSCommandInterpreter_noExecutableFound(ni.getHome()));
}
}

// add npmrc config
NodeJSUtils.supplyConfig(configId, build, listener);
} catch (AbortException e) {
listener.fatalError(e.getMessage()); // NOSONAR
return false;
Expand Down Expand Up @@ -110,13 +120,17 @@ protected String getContents() {

@Override
protected String getFileExtension() {
return JAVASCRIPT_EXT;
return NodeJSConstants.JAVASRIPT_EXT;
}

public String getNodeJSInstallationName() {
return nodeJSInstallationName;
}

public String getConfigId() {
return configId;
}

/**
* Provides builder details for the job configuration page.
*
Expand Down Expand Up @@ -149,6 +163,21 @@ public NodeJSInstallation[] getInstallations() {
return NodeJSUtils.getInstallations();
}

/**
* Gather all defined npmrc config files.
*
* @return a collection of user npmrc files or {@empty} if no one
* defined.
*/
public Collection<Config> getConfigs() {
ConfigProvider provider = ConfigProvider.getByIdOrNull(NPMConfig.class.getName());
if (provider != null) {
return provider.getAllConfigs();
}

return Collections.emptyList();
}

}

}
18 changes: 18 additions & 0 deletions src/main/java/jenkins/plugins/nodejs/NodeJSConstants.java
@@ -0,0 +1,18 @@
package jenkins.plugins.nodejs;

public final class NodeJSConstants {

/**
* Default extension for javascript file.
*/
public static final String JAVASRIPT_EXT = ".js";

/**
* The location of user-level configuration settings.
*/
public static final String NPM_USERCONFIG = "npm_config_userconfig";

private NodeJSConstants() {
// constructor
}
}
85 changes: 84 additions & 1 deletion src/main/java/jenkins/plugins/nodejs/NodeJSUtils.java
@@ -1,12 +1,32 @@
package jenkins.plugins.nodejs;

import hudson.AbortException;
import hudson.FilePath;
import hudson.Util;
import hudson.model.Environment;
import hudson.model.TaskListener;
import hudson.model.AbstractBuild;

import java.util.List;
import java.util.Map;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;

import jenkins.model.Jenkins;
import jenkins.plugins.nodejs.configfiles.NPMConfig;
import jenkins.plugins.nodejs.configfiles.NPMRegistry;
import jenkins.plugins.nodejs.configfiles.RegistryHelper;
import jenkins.plugins.nodejs.tools.NodeJSInstallation;
import jenkins.plugins.nodejs.tools.NodeJSInstallation.DescriptorImpl;

import org.apache.commons.lang.StringUtils;
import org.jenkinsci.lib.configprovider.model.Config;
import org.jenkinsci.plugins.configfiles.buildwrapper.ManagedFileUtil;
import org.jenkinsci.plugins.configfiles.common.CleanTempFilesAction;

import com.cloudbees.plugins.credentials.common.StandardUsernameCredentials;

/*package*/final class NodeJSUtils {

private NodeJSUtils() {
Expand All @@ -25,7 +45,7 @@ private NodeJSUtils() {
public static NodeJSInstallation getNodeJS(@Nullable String name) {
if (name != null) {
for (NodeJSInstallation installation : getInstallations()) {
if (name != null && name.equals(installation.getName()))
if (name.equals(installation.getName()))
return installation;
}
}
Expand All @@ -46,4 +66,67 @@ public static NodeJSInstallation[] getInstallations() {
return descriptor.getInstallations();
}

/**
* Create a copy of the given configuration in a no accessible folder for
* the user.
* <p>
* This file will be deleted at the end of job also in case of user
* interruption.
* </p>
*
* @param configId the configuration identification
* @param build a build being run
* @param listener a way to report progress
* @throws AbortException in case the provided configId is not valid
*/
public static FilePath supplyConfig(String configId, AbstractBuild<?, ?> build, TaskListener listener) throws AbortException {
if (StringUtils.isNotBlank(configId)) {
Config c = Config.getByIdOrNull(configId);

if (c == null) {
throw new AbortException("this NodeJS build is setup to use a config with id " + configId + " but can not be find");
} else {
NPMConfig config;
if (c instanceof NPMConfig) {
config = (NPMConfig) c;
} else {
config = new NPMConfig(c.id, c.name, c.comment, c.content, c.getProviderId(), null);
}

listener.getLogger().println("using user config with name " + config.name);
String fileContent = config.content;

listener.getLogger().println("Adding all registry entries");
List<NPMRegistry> registries = config.getRegistries();
RegistryHelper helper = new RegistryHelper(registries);
if (!registries.isEmpty()) {
Map<String, StandardUsernameCredentials> registry2Credentials = helper.resolveCredentials(build);
fileContent = helper.fillRegistry(fileContent, registry2Credentials);
}

try {
if (StringUtils.isNotBlank(fileContent)) { // NOSONAR
FilePath workDir = ManagedFileUtil.tempDir(build.getWorkspace());
final FilePath f = workDir.createTextTempFile(".npmrc", "", Util.replaceMacro(fileContent, build.getEnvironment(listener)), true);
listener.getLogger().printf("Create %s", f);

build.getEnvironments().add(new Environment() {
@Override
public void buildEnvVars(Map<String, String> env) {
env.put(NodeJSConstants.NPM_USERCONFIG, f.getRemote());
}
});

build.addAction(new CleanTempFilesAction(f.getRemote()));
return f;
}
} catch (Exception e) {
throw new IllegalStateException("the npmrc user config could not be supplied for the current build: " + e.getMessage(), e);
}
}
}

return null;
}

}

0 comments on commit 20b82f1

Please sign in to comment.