package jenkins.plugins.nodejs;
import java.io.IOException;
import java.util.Collection;
import javax.annotation.CheckForNull;
import org.jenkinsci.Symbol;
import org.jenkinsci.lib.configprovider.model.Config;
import org.jenkinsci.plugins.configfiles.GlobalConfigFiles;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.QueryParameter;
import hudson.AbortException;
import hudson.EnvVars;
import hudson.Extension;
import hudson.FilePath;
import hudson.Launcher;
import hudson.Util;
import hudson.model.AbstractBuild;
import hudson.model.AbstractProject;
import hudson.model.Environment;
import hudson.model.Node;
import hudson.model.TaskListener;
import hudson.tasks.BuildStepDescriptor;
import hudson.tasks.Builder;
import hudson.tasks.CommandInterpreter;
import hudson.util.ArgumentListBuilder;
import hudson.util.FormValidation;
import jenkins.plugins.nodejs.configfiles.NPMConfig;
import jenkins.plugins.nodejs.configfiles.NPMConfig.NPMConfigProvider;
import jenkins.plugins.nodejs.configfiles.VerifyConfigProviderException;
import jenkins.plugins.nodejs.tools.NodeJSInstallation;
import jenkins.plugins.nodejs.tools.Platform;
/**
* This class executes a JavaScript file using node. The file should contain
* NodeJS script specified in the job configuration.
*
* @author cliffano
* @author Nikolas Falco
*/
public class NodeJSCommandInterpreter extends CommandInterpreter {
private final String nodeJSInstallationName;
private final String configId;
private transient String nodeExec; // NOSONAR
/**
* Constructs a {@link NodeJSCommandInterpreter} with specified command.
*
* @param command
* 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, final String configId) {
super(command);
this.nodeJSInstallationName = Util.fixEmpty(nodeJSInstallationName);
this.configId = Util.fixEmpty(configId);
}
/**
* Gets the NodeJS to invoke, or null to invoke the default one.
*
* @return a NodeJS installation setup for this job, {@code null} otherwise.
*/
public NodeJSInstallation getNodeJS() {
return NodeJSUtils.getNodeJS(nodeJSInstallationName);
}
/*
* (non-Javadoc)
* @see hudson.tasks.CommandInterpreter#perform(hudson.model.AbstractBuild, hudson.Launcher, hudson.model.TaskListener)
*/
@Override
public boolean perform(AbstractBuild<?, ?> build, Launcher launcher, TaskListener listener) throws InterruptedException {
try {
EnvVars env = build.getEnvironment(listener);
EnvVars newEnv = new EnvVars();
// get specific installation for the node
NodeJSInstallation ni = getNodeJS();
if (ni == null) {
if (nodeJSInstallationName != null) {
throw new AbortException(Messages.NodeJSBuilders_noInstallationFound(nodeJSInstallationName));
}
// use system NodeJS if any, in case let fails later
nodeExec = (launcher.isUnix() ? Platform.LINUX : Platform.WINDOWS).nodeFileName;
} else {
Node node = build.getBuiltOn();
if (node == null) {
throw new AbortException(Messages.NodeJSBuilders_nodeOffline());
}
ni = ni.forNode(node, listener);
ni = ni.forEnvironment(env);
ni.buildEnvVars(newEnv);
// enhance env with installation environment because is passed to supplyConfig
env.overrideAll(newEnv);
nodeExec = ni.getExecutable(launcher);
if (nodeExec == null) {
throw new AbortException(Messages.NodeJSBuilders_noExecutableFound(ni.getHome()));
}
}
// add npmrc config
FilePath configFile = NodeJSUtils.supplyConfig(configId, build, build.getWorkspace(), listener, env);
if (configFile != null) {
newEnv.put(NodeJSConstants.NPM_USERCONFIG, configFile.getRemote());
}
// add an Environment so when the in super class is called build.getEnviroment() gets the enhanced env
build.getEnvironments().add(Environment.create(newEnv));
} catch (AbortException e) {
listener.fatalError(e.getMessage()); // NOSONAR
return false;
} catch (IOException e) {
Util.displayIOException(e, listener);
e.printStackTrace(listener.fatalError(hudson.tasks.Messages.CommandInterpreter_CommandFailed()));
}
return internalPerform(build, launcher, listener);
}
protected boolean internalPerform(AbstractBuild<?, ?> build, Launcher launcher, TaskListener listener) throws InterruptedException {
return super.perform(build, launcher, listener);
}
@Override
public String[] buildCommandLine(FilePath script) {
if (nodeExec == null) {
throw new IllegalStateException("Node executable not initialised");
}
ArgumentListBuilder args = new ArgumentListBuilder(nodeExec, script.getRemote());
return args.toCommandArray();
}
@Override
protected String getContents() {
return getCommand();
}
@Override
protected String getFileExtension() {
return NodeJSConstants.JAVASCRIPT_EXT;
}
public String getNodeJSInstallationName() {
return nodeJSInstallationName;
}
public String getConfigId() {
return configId;
}
/**
* Provides builder details for the job configuration page.
*
* @author cliffano
* @author Nikolas Falco
*/
@Symbol("nodejsci")
@Extension
public static final class NodeJsDescriptor extends BuildStepDescriptor<Builder> {
/**
* Customise the name of this job step.
*
* @return the builder name
*/
@Override
public String getDisplayName() {
return Messages.NodeJSCommandInterpreter_displayName();
}
/**
* Return the help file.
*
* @return the help file URL path
*/
@Override
public String getHelpFile() {
return "/plugin/nodejs/help.html";
}
public NodeJSInstallation[] getInstallations() {
return NodeJSUtils.getInstallations();
}
/**
* Gather all defined npmrc config files.
*
* @return a collection of user npmrc files or {@code empty} if no one
* defined.
*/
public Collection<Config> getConfigs() {
return GlobalConfigFiles.get().getConfigs(NPMConfigProvider.class);
}
public FormValidation doCheckConfigId(@CheckForNull @QueryParameter final String configId) {
NPMConfig config = (NPMConfig) GlobalConfigFiles.get().getById(configId);
if (config != null) {
try {
config.doVerify();
} catch (VerifyConfigProviderException e) {
return FormValidation.error(e.getMessage());
}
}
return FormValidation.ok();
}
@Override
public boolean isApplicable(@SuppressWarnings("rawtypes") Class<? extends AbstractProject> jobType) {
return true;
}
}
}