package com.anchore.jenkins.plugins.anchore; import hudson.model.Action; import hudson.model.Job; import hudson.model.Run; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Set; import jenkins.model.Jenkins; import jenkins.model.lazy.LazyBuildMixIn; import jenkins.tasks.SimpleBuildStep; import net.sf.json.JSONObject; /** * Anchore plugin results for a given build are stored and subsequently retrieved from an instance of this class. Rendering/display of * the results is defined in the appropriate index and summary jelly files. This Jenkins Action is associated with a build (and not the * project which is one level up) */ public class AnchoreAction implements SimpleBuildStep.LastBuildAction { private Run<?, ?> build; private String gateStatus; private String gateOutputUrl; private Map<String, String> queryOutputUrls; private String gateSummary; private String cveListingUrl; private int stopActionCount; private int warnActionCount; private int goActionCount; // For backwards compatibility @Deprecated private String gateReportUrl; @Deprecated private Map<String, String> queries; public AnchoreAction(Run<?, ?> build, String gateStatus, final String jenkinsOutputDirName, String gateReport, Map<String, String> queryReports, String gateSummary, String cveListingFileName, int stopActionCount, int warnActionCount, int goActionCount) { this.build = build; this.gateStatus = gateStatus; this.stopActionCount = stopActionCount; this.warnActionCount = warnActionCount; this.goActionCount = goActionCount; this.gateOutputUrl = "../artifact/" + jenkinsOutputDirName + "/" + gateReport; this.queryOutputUrls = new HashMap<String, String>(); for (Map.Entry<String, String> entry : queryReports.entrySet()) { String k = entry.getKey(); String v = entry.getValue(); String newv = "../artifact/" + jenkinsOutputDirName + "/" + v; this.queryOutputUrls.put(k, newv); } // original maps conversion method /* this.queryOutputUrls = Maps.transformValues(queryReports, new Function<String, String>() { @Override public String apply(@Nullable String queryOutput) { return "../artifact/" + jenkinsOutputDirName + "/" + queryOutput; } }); */ this.gateSummary = gateSummary; if (null != cveListingFileName && cveListingFileName.trim().length() > 0) { this.cveListingUrl = "../artifact/" + jenkinsOutputDirName + "/" + cveListingFileName; } } @Override public String getIconFileName() { return Jenkins.RESOURCE_PATH + "/plugin/anchore-container-scanner/images/anchore.png"; } @Override public String getDisplayName() { return "Anchore Report (" + gateStatus + ")"; } @Override public String getUrlName() { return "anchore-results"; } public Run<?, ?> getBuild() { return this.build; } public String getGateStatus() { return gateStatus; } public String getGateOutputUrl() { return this.gateOutputUrl; } public Map<String, String> getQueryOutputUrls() { // queryOutputUrls was a guava TransformedEntriesMap object in plugin version < 1.0.13 and is loaded as such. Plugin versions >= // 1.0.13 changed the type definition and lose the transformer function required for reading the map contents. This results in // a failure to load the member. Transfer the contents from the underlying guava map to a native java map using the keys and // some hacky guess work /* Find bugs does not like the instanceof check, falling back to try-catch approach if (!(this.queryOutputUrls instanceof HashMap)) { String base_path = this.gateOutputUrl.substring(0, this.gateOutputUrl.lastIndexOf('/')); int query_num = 0; Map<String, String> fixedQueryOutputUrls = new HashMap<>(); for (String key : this.queryOutputUrls.keySet()) { fixedQueryOutputUrls.put(key, base_path + "/anchore_query_" + String.valueOf(++query_num) + ".json"); } return fixedQueryOutputUrls; }*/ Map<String, String> fixedQueryOutputUrls = new HashMap<>(); try { // Fetch values in the map to verify the underlying map is functional if (null != this.queryOutputUrls) { fixedQueryOutputUrls.putAll(this.queryOutputUrls); } } catch (Exception e) { String base_path = this.gateOutputUrl.substring(0, this.gateOutputUrl.lastIndexOf('/')); int query_num = 0; for (String key : this.queryOutputUrls.keySet()) { fixedQueryOutputUrls.put(key, base_path + "/anchore_query_" + String.valueOf(++query_num) + ".json"); } } return fixedQueryOutputUrls; } public JSONObject getGateSummary() { // gateSummary was a JSON object in plugin version <= 1.0.12. Jenkins does not handle this type change correctly post upgrade. // Summary data from the previous versions is lost during deserialization due to the type change and plugin versions > 1.0.12 // won't be able to render the summary table only for builds that were executed using older versions of the plugin. This check // is necessary to ensure plugin doesn't exception out in the process if (null != this.gateSummary && this.gateSummary.trim().length() > 0) { return JSONObject.fromObject(this.gateSummary); } else { return null; } } public String getCveListingUrl() { return cveListingUrl; } public String getGateReportUrl() { return this.gateReportUrl; } public Map<String, String> getQueries() { return this.queries; } public int getGoActionCount(){ return this.goActionCount; } public int getStopActionCount(){ return this.stopActionCount; } public int getWarnActionCount(){ return this.warnActionCount; } @Override public Collection<? extends Action> getProjectActions() { Job<?,?> job = this.build.getParent(); return Collections.singleton(new AnchoreProjectAction(job)); } /** * Gets the Anchore result of the previous build, if it's recorded, or null. * @return the previous AnchoreAction */ public AnchoreAction getPreviousResult() { Run<?,?> b = this.build; while(true) { b = b.getPreviousBuild(); if (b == null) { return null; } AnchoreAction r = b.getAction(AnchoreAction.class); if (r != null) { if (r == this) { throw new IllegalStateException(this + " was attached to both " + b + " and " + this.build); } if (r.build.number != b.number) { throw new IllegalStateException(r + " was attached to both " + b + " and " + r.build); } return r; } } } }