package org.jenkinsci.plugins.aquadockerscannerbuildstep; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import hudson.AbortException; import hudson.Launcher; import hudson.Extension; import hudson.util.FormValidation; import hudson.model.AbstractProject; import hudson.tasks.Builder; import hudson.tasks.ArtifactArchiver; import hudson.tasks.BuildStepDescriptor; import net.sf.json.JSONObject; import org.kohsuke.stapler.DataBoundConstructor; import org.kohsuke.stapler.StaplerRequest; import org.kohsuke.stapler.QueryParameter; import javax.servlet.ServletException; import java.io.IOException; import hudson.FilePath; import hudson.model.Run; import hudson.model.TaskListener; import jenkins.tasks.SimpleBuildStep; import org.jenkinsci.Symbol; import hudson.util.Secret; /** * This is the builder class. * <p> * When a build is performed, the {@link #perform} method will be invoked. * * @author Oran Moshai */ public class AquaMicroScannerBuilder extends Builder implements SimpleBuildStep{ public static final int OK_CODE = 0; public static final int DISALLOWED_CODE = 4; private final String imageName; private final String onDisallowed; private final String notCompliesCmd; private final String outputFormat; private static int count; private static int buildId = 0; public synchronized static void setCount(int count) { AquaMicroScannerBuilder.count = count; } public synchronized static void setBuildId(int buildId) { AquaMicroScannerBuilder.buildId = buildId; } // Fields in config.jelly must match the parameter names in the // "DataBoundConstructor" @DataBoundConstructor public AquaMicroScannerBuilder(String imageName, String onDisallowed, String notCompliesCmd, String outputFormat) { this.imageName = imageName; this.onDisallowed = onDisallowed; this.notCompliesCmd = notCompliesCmd; if ("json".equalsIgnoreCase(outputFormat)) { this.outputFormat = outputFormat; } else { this.outputFormat = "html"; } } /** * Public access required by config.jelly to display current values in * configuration screen. */ public String getImageName() { return imageName; } public String getOnDisallowed() { return onDisallowed; } public String getNotCompliesCmd() { return notCompliesCmd; } public String getOutputFormat() { return outputFormat; } // Returns the 'checked' state of the radio button for the step GUI public String isOnDisallowed(String state) { if (this.onDisallowed == null) { // default for new step GUI return "ignore".equals(state) ? "true" : "false"; } else { return this.onDisallowed.equals(state) ? "true" : "false"; } } // Returns the 'checked' state of the radio button for the output public String isOnOutputFormat(String state) { if (this.outputFormat == null) { // default for new step GUI return "html".equals(state) ? "true" : "false"; } else { return this.outputFormat.equals(state) ? "true" : "false"; } } @Override public void perform(Run<?, ?> build, FilePath workspace, Launcher launcher, TaskListener listener) throws AbortException, java.lang.InterruptedException { // This is where you 'build' the project. Secret microScannerToken = getDescriptor().getMicroScannerToken(); boolean caCertificates = getDescriptor().getCaCertificates(); if (microScannerToken == null || Secret.toString(microScannerToken).trim().equals("")) { throw new AbortException("Missing configuration. Please set the global configuration parameters in The \"Aqua Security\" section under \"Manage Jenkins/Configure System\", before continuing.\n"); } // Support unique names for artifacts when there are multiple steps in the same // build String artifactSuffix, artifactName; if (build.hashCode() != buildId) { // New build setBuildId(build.hashCode()); setCount(1); artifactSuffix = null; // When there is only one step, there should be no suffix at all artifactName = "scanout." + outputFormat; } else { setCount(count + 1); artifactSuffix = Integer.toString(count); artifactName = "scanout-" + artifactSuffix + "." + outputFormat; } int exitCode = ScannerExecuter.execute(build, workspace, launcher, listener, artifactName, microScannerToken, imageName, notCompliesCmd, outputFormat == null ? "html" : outputFormat, onDisallowed == null || !onDisallowed.equals("fail"),caCertificates); build.addAction(new AquaScannerAction(build, artifactSuffix, artifactName, imageName)); archiveArtifacts(build, workspace, launcher, listener); System.out.println("exitCode: " + exitCode); String failedMessage = "Scanning failed."; switch (exitCode) { case OK_CODE: System.out.println("Scanning success."); break; case DISALLOWED_CODE: throw new AbortException(failedMessage); default: // This exception causes the message to appear in the Jenkins console throw new AbortException(failedMessage); } } // Archive scanout artifact @SuppressFBWarnings("NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE") // No idea why this is needed private void archiveArtifacts(Run<?, ?> build, FilePath workspace, Launcher launcher, TaskListener listener) throws java.lang.InterruptedException { ArtifactArchiver artifactArchiver = new ArtifactArchiver("scanout*"); artifactArchiver.perform(build, workspace, launcher, listener); ArtifactArchiver styleArtifactArchiver = new ArtifactArchiver("styles.css"); styleArtifactArchiver.perform(build, workspace, launcher, listener); } // Overridden for better type safety. // If your plugin doesn't really define any property on Descriptor, // you don't have to do this. @Override public DescriptorImpl getDescriptor() { return (DescriptorImpl) super.getDescriptor(); } /** * Descriptor for {@link AquaMicroScannerBuilder}. Used as a singleton. The * class is marked as public so that it can be accessed from views. */ @Symbol("aquaMicroscanner") @Extension // This indicates to Jenkins that this is an implementation of an extension // point. public static final class DescriptorImpl extends BuildStepDescriptor<Builder> { /** * To persist global configuration information, simply store it in a field and * call save(). */ private Secret microScannerToken; private boolean caCertificates; /** * In order to load the persisted global configuration, you have to call load() * in the constructor. */ public DescriptorImpl() { load(); } /** * Performs on-the-fly validation of the form field 'name'. * * @param value * This parameter receives the value that the user has typed. * @return Indicates the outcome of the validation. This is sent to the browser. */ public FormValidation doCheckTimeout(@QueryParameter String value) throws IOException, ServletException { try { Integer.parseInt(value); return FormValidation.ok(); } catch (NumberFormatException e) { return FormValidation.error("Must be a number"); } } public boolean isApplicable(Class<? extends AbstractProject> aClass) { // Indicates that this builder can be used with all kinds of project types return true; } /** * This human readable name is used in the configuration screen. */ public String getDisplayName() { return "Aqua MicroScanner"; } @Override public boolean configure(StaplerRequest req, JSONObject formData) throws FormException { // To persist global configuration information, // set that to properties and call save(). microScannerToken = Secret.fromString(formData.getString("microScannerToken")); caCertificates = formData.getBoolean("caCertificates"); save(); return super.configure(req, formData); } public Secret getMicroScannerToken() { return microScannerToken; } public boolean getCaCertificates() { return caCertificates; } } }