diff --git a/orca-bakery/src/main/groovy/com/netflix/spinnaker/orca/bakery/tasks/CreateBakeTask.groovy b/orca-bakery/src/main/groovy/com/netflix/spinnaker/orca/bakery/tasks/CreateBakeTask.groovy index 418ca676ab..5717e86d78 100644 --- a/orca-bakery/src/main/groovy/com/netflix/spinnaker/orca/bakery/tasks/CreateBakeTask.groovy +++ b/orca-bakery/src/main/groovy/com/netflix/spinnaker/orca/bakery/tasks/CreateBakeTask.groovy @@ -17,6 +17,7 @@ package com.netflix.spinnaker.orca.bakery.tasks import com.fasterxml.jackson.databind.ObjectMapper +import com.netflix.spinnaker.kork.artifacts.model.Artifact import com.netflix.spinnaker.orca.ExecutionStatus import com.netflix.spinnaker.orca.RetryableTask import com.netflix.spinnaker.orca.TaskResult @@ -131,7 +132,10 @@ class CreateBakeTask implements RetryableTask { packageType = new OperatingSystem(stage.context.baseOs as String).getPackageType() } + List artifacts = artifactResolver.getAllArtifacts(stage.getExecution()) + PackageInfo packageInfo = new PackageInfo(stage, + artifacts, packageType.packageType, packageType.versionDelimiter, extractBuildDetails, @@ -140,6 +144,8 @@ class CreateBakeTask implements RetryableTask { Map requestMap = packageInfo.findTargetPackage(allowMissingPackageInstallation) + // if the field "packageArtifactIds" is present in the context, because it was set in the UI, + // this will resolve those ids into real artifacts and then put them in List packageArtifacts requestMap.packageArtifacts = stage.context.packageArtifactIds.collect { String artifactId -> artifactResolver.getBoundArtifactForId(stage, artifactId) } diff --git a/orca-bakery/src/test/groovy/com/netflix/spinnaker/orca/bakery/tasks/CreateBakeTaskSpec.groovy b/orca-bakery/src/test/groovy/com/netflix/spinnaker/orca/bakery/tasks/CreateBakeTaskSpec.groovy index d9e95f33e0..7a62e4e16a 100644 --- a/orca-bakery/src/test/groovy/com/netflix/spinnaker/orca/bakery/tasks/CreateBakeTaskSpec.groovy +++ b/orca-bakery/src/test/groovy/com/netflix/spinnaker/orca/bakery/tasks/CreateBakeTaskSpec.groovy @@ -49,6 +49,10 @@ class CreateBakeTaskSpec extends Specification { Stage bakeStage def mapper = OrcaObjectMapper.newInstance() + ArtifactResolver artifactResolver = Stub() { + getAllArtifacts(_) >> [] + } + @Shared def runningStatus = new BakeStatus(id: randomUUID(), state: RUNNING) @@ -205,6 +209,7 @@ class CreateBakeTaskSpec extends Specification { def setup() { task.mapper = mapper + task.artifactResolver = artifactResolver bakeStage = pipeline.stages.first() } @@ -349,7 +354,7 @@ class CreateBakeTaskSpec extends Specification { then: IllegalStateException ise = thrown(IllegalStateException) - ise.message.startsWith("Found build artifact in Jenkins") + ise.message.startsWith("Found build artifact in both Jenkins") } def "outputs the status of the bake"() { @@ -820,6 +825,7 @@ class CreateBakeTaskSpec extends Specification { then: 2 * task.artifactResolver.getBoundArtifactForId(stage, _) >> new Artifact() + 1 * task.artifactResolver.getAllArtifacts(_) >> [] bakeResult.getPackageArtifacts().size() == 2 } @@ -837,6 +843,7 @@ class CreateBakeTaskSpec extends Specification { then: 0 * task.artifactResolver.getBoundArtifactForId(*_) >> new Artifact() + 1 * task.artifactResolver.getAllArtifacts(_) >> [] bakeResult.getPackageArtifacts().size() == 0 } @@ -855,6 +862,7 @@ class CreateBakeTaskSpec extends Specification { then: noExceptionThrown() 2 * task.artifactResolver.getBoundArtifactForId(stage, _) >> new Artifact() + 1 * task.artifactResolver.getAllArtifacts(_) >> [] bakeResult.getPackageArtifacts().size() == 2 } } diff --git a/orca-core/src/main/java/com/netflix/spinnaker/orca/pipeline/util/PackageInfo.java b/orca-core/src/main/java/com/netflix/spinnaker/orca/pipeline/util/PackageInfo.java index db973f5807..1de1f545b6 100644 --- a/orca-core/src/main/java/com/netflix/spinnaker/orca/pipeline/util/PackageInfo.java +++ b/orca-core/src/main/java/com/netflix/spinnaker/orca/pipeline/util/PackageInfo.java @@ -20,6 +20,7 @@ import java.util.regex.Pattern; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.annotations.VisibleForTesting; +import com.netflix.spinnaker.kork.artifacts.model.Artifact; import com.netflix.spinnaker.orca.pipeline.model.Stage; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -30,12 +31,22 @@ import static java.util.stream.Collectors.joining; import static java.util.stream.Collectors.toList; +/** + * This class inspects the context of a stage, preceding stages, the trigger, and possibly the parent pipeline + * in order to see if an artifact matching the name(s) specified in the bake stage was produced. + * If so, that version will be used in the bake request. + * If nothing is found after all this searching it is up to the bakery to pull the latest package version. + * + * Artifact information comes from Jenkins on the pipeline trigger in the field `buildInfo.artifacts`. + * If your trigger contains the Artifacts field, this class will also look for version information in there. + */ public class PackageInfo { private final Logger log = LoggerFactory.getLogger(getClass()); private final ObjectMapper mapper; private final Stage stage; + private final List artifacts; private final String versionDelimiter; private final String packageType; private final boolean extractBuildDetails; @@ -43,8 +54,15 @@ public class PackageInfo { private final BuildDetailExtractor buildDetailExtractor; private final List packageFilePatterns = new ArrayList<>(); - public PackageInfo(Stage stage, String packageType, String versionDelimiter, boolean extractBuildDetails, boolean extractVersion, ObjectMapper mapper) { + public PackageInfo(Stage stage, + List artifacts, + String packageType, + String versionDelimiter, + boolean extractBuildDetails, + boolean extractVersion, + ObjectMapper mapper) { this.stage = stage; + this.artifacts = artifacts; this.packageType = packageType; this.versionDelimiter = versionDelimiter; this.extractBuildDetails = extractBuildDetails; @@ -69,82 +87,78 @@ private boolean isUrl(String potentialUrl) { } public Map findTargetPackage(boolean allowMissingPackageInstallation) { - Map requestMap = new HashMap<>(); + Map stageContext = new HashMap<>(); // copy the context since we may modify it in createAugmentedRequest - requestMap.putAll(stage.getContext()); + stageContext.putAll(stage.getContext()); if (stage.getExecution().getType() == PIPELINE) { Map trigger = mapper.convertValue(stage.getExecution().getTrigger(), Map.class); - Map buildInfo = null; - if (requestMap.get("buildInfo") != null) { // package was built as part of the pipeline - buildInfo = mapper.convertValue(requestMap.get("buildInfo"), Map.class); + Map buildInfoCurrentExecution = null; + if (stageContext.get("buildInfo") != null) { // package was built as part of the pipeline + buildInfoCurrentExecution = mapper.convertValue(stageContext.get("buildInfo"), Map.class); } - if (buildInfo == null || (buildInfo.get("artifacts") != null && !((Collection) buildInfo.get("artifacts")).isEmpty())) { + if (buildInfoCurrentExecution == null || (buildInfoCurrentExecution.get("artifacts") != null && !((Collection) buildInfoCurrentExecution.get("artifacts")).isEmpty())) { Map upstreamBuildInfo = findBuildInfoInUpstreamStage(stage, packageFilePatterns); if (!upstreamBuildInfo.isEmpty()) { - buildInfo = upstreamBuildInfo; + buildInfoCurrentExecution = upstreamBuildInfo; } } - if (buildInfo == null) { - buildInfo = emptyMap(); + if (buildInfoCurrentExecution == null) { + buildInfoCurrentExecution = emptyMap(); } - return createAugmentedRequest(trigger, buildInfo, requestMap, allowMissingPackageInstallation); + return createAugmentedRequest(trigger, buildInfoCurrentExecution, stageContext, allowMissingPackageInstallation); } - return requestMap; + + // A package could only have been produced as part of a pipeline, + // so if this is not a pipeline return the unchanged context. + return stageContext; } /** - * Try to find a package from the pipeline trigger and/or a step in the pipeline. + * Try to find a package from the artifacts. + * If not present, fall back to the pipeline trigger and/or a step in the pipeline. * Optionally put the build details into the request object. This does not alter the stage context, * so assign it back if that's the desired behavior. * - * @param trigger - * @param buildInfo - * @param request + * @param trigger the trigger of the pipeline + * @param buildInfoCurrentExecution the buildInfo block that comes from either the current execution, not the trigger + * @param stageContext the stage context * @return */ @VisibleForTesting - private Map createAugmentedRequest(Map trigger, Map buildInfo, Map request, boolean allowMissingPackageInstallation) { - Map artifactSourceBuildInfo = getArtifactSourceBuildInfo(trigger); - List> triggerArtifacts = Optional.ofNullable((List>) artifactSourceBuildInfo.get("artifacts")).orElse(emptyList()); - List> buildArtifacts = Optional.ofNullable((List>) buildInfo.get("artifacts")).orElse(emptyList()); - - if (request.get("package") == null || request.get("package").equals("") || isUrl(request.get("package").toString())) { - return request; + private Map createAugmentedRequest(Map trigger, + Map buildInfoCurrentExecution, + Map stageContext, + boolean allowMissingPackageInstallation) { + Map triggerBuildInfo = getBuildInfoFromTriggerOrParentTrigger(trigger); + List> triggerArtifacts = Optional.ofNullable((List>) triggerBuildInfo.get("artifacts")).orElse(emptyList()); + List> buildArtifacts = Optional.ofNullable((List>) buildInfoCurrentExecution.get("artifacts")).orElse(emptyList()); + + if (stageContext.get("package") == null || stageContext.get("package").equals("") || isUrl(stageContext.get("package").toString())) { + return stageContext; } - if (buildInfo.isEmpty() || buildArtifacts.isEmpty()) { + if (buildInfoCurrentExecution.isEmpty() || buildArtifacts.isEmpty()) { Optional> parentBuildInfo = Optional .ofNullable((Map) trigger.get("parentExecution")) .map(it -> (Map) it.get("trigger")) .map(it -> (Map) it.get("buildInfo")); - if (triggerArtifacts.isEmpty() && (trigger.get("buildInfo") != null || parentBuildInfo.isPresent())) { + if (triggerArtifacts.isEmpty() && (trigger.get("buildInfo") != null || parentBuildInfo.isPresent()) && artifacts.isEmpty()) { throw new IllegalStateException("Jenkins job detected but no artifacts found, please archive the packages in your job and try again."); } } - if (buildArtifacts.isEmpty() && triggerArtifacts.isEmpty()) { - return request; + if (buildArtifacts.isEmpty() && triggerArtifacts.isEmpty() && artifacts.isEmpty()) { + return stageContext; } List missingPrefixes = new ArrayList<>(); String fileExtension = format(".%s", packageType); - // There might not be a request.package so we look for the package name from either the buildInfo or trigger - // - String reqPkg = Optional - .ofNullable(request.get("package").toString()) - .orElseGet(() -> - buildArtifacts - .stream() - .findFirst() - .map(it -> it.get("fileName").toString().split(versionDelimiter)[0]) - .orElseGet(() -> triggerArtifacts.stream().findFirst().map(it -> it.get("fileName").toString().split(versionDelimiter)[0]).orElse(null)) - ); - + String reqPkg = stageContext.get("package").toString(); List requestPackages = Arrays.asList(reqPkg.split(" ")); for (int index = 0; index < requestPackages.size(); index++) { @@ -152,49 +166,72 @@ private Map createAugmentedRequest(Map trigger, String prefix = requestPackage + versionDelimiter; + Artifact matchedArtifact = filterKorkArtifacts(artifacts, requestPackage, packageType); Map triggerArtifact = filterArtifacts(triggerArtifacts, prefix, fileExtension); Map buildArtifact = filterArtifacts(buildArtifacts, prefix, fileExtension); // only one unique package per pipeline is allowed if (!triggerArtifact.isEmpty() && !buildArtifact.isEmpty() && !triggerArtifact.get("fileName").equals(buildArtifact.get("fileName"))) { - throw new IllegalStateException("Found build artifact in Jenkins stage and Pipeline Trigger"); + throw new IllegalStateException("Found build artifact in both Jenkins stage (" + + buildArtifact.get("fileName") + + ") and Pipeline Trigger (" + + triggerArtifact.get("filename") + + ")"); } - String packageName = null; + if (!triggerArtifact.isEmpty() && matchedArtifact != null && !extractPackageVersion(triggerArtifact, prefix, fileExtension).equals(matchedArtifact.getVersion())) { + throw new IllegalStateException("Found build artifact in both Pipeline Trigger (" + + triggerArtifact.get("filename") + + ") and produced artifacts (" + + matchedArtifact.getVersion() + versionDelimiter + matchedArtifact.getVersion() + + ")"); + } + + if (!buildArtifact.isEmpty() && matchedArtifact != null && !extractPackageVersion(buildArtifact, prefix, fileExtension).equals(matchedArtifact.getVersion())) { + throw new IllegalStateException("Found build artifact in both Jenkins stage (" + + matchedArtifact.getVersion() + versionDelimiter + matchedArtifact.getVersion() + + ") and produced artifacts (" + + buildArtifact.get("fileName") + + ")"); + } + + String packageIdentifier = null; //package-name + delimiter + version, like "test-package_1.0.0" String packageVersion = null; - if (!triggerArtifact.isEmpty()) { - packageName = extractPackageName(triggerArtifact, fileExtension); + if (matchedArtifact != null) { + packageIdentifier = matchedArtifact.getName() + versionDelimiter + matchedArtifact.getVersion(); if (extractVersion) { - packageVersion = extractPackageVersion(triggerArtifact, prefix, fileExtension); + packageVersion = matchedArtifact.getVersion(); } - } - - if (!buildArtifact.isEmpty()) { - packageName = extractPackageName(buildArtifact, fileExtension); + } else if (!buildArtifact.isEmpty()) { + packageIdentifier = extractPackageIdentifier(buildArtifact, fileExtension); if (extractVersion) { packageVersion = extractPackageVersion(buildArtifact, prefix, fileExtension); } + } else if (!triggerArtifact.isEmpty()) { + packageIdentifier = extractPackageIdentifier(triggerArtifact, fileExtension); + if (extractVersion) { + packageVersion = extractPackageVersion(triggerArtifact, prefix, fileExtension); + } } if (packageVersion != null) { - request.put("packageVersion", packageVersion); + stageContext.put("packageVersion", packageVersion); } - if (triggerArtifact.isEmpty() && buildArtifact.isEmpty()) { + if (triggerArtifact.isEmpty() && buildArtifact.isEmpty() && matchedArtifact == null) { missingPrefixes.add(prefix); } - // When a package match one of the packages coming from the trigger or from the previous stage its name + // When a package matches one of the packages coming from the trigger or from the previous stage its name // get replaced with the actual package name. Otherwise its just passed down to the bakery, // letting the bakery to resolve it. - requestPackages.set(index, packageName != null ? packageName : requestPackage); - - if (packageName != null) { + requestPackages.set(index, packageIdentifier != null ? packageIdentifier : requestPackage); + if (packageIdentifier != null) { if (extractBuildDetails) { - Map buildInfoForDetails = !buildArtifact.isEmpty() ? buildInfo : artifactSourceBuildInfo; - buildDetailExtractor.tryToExtractBuildDetails(buildInfoForDetails, request); + Map buildInfoForDetails = !buildArtifact.isEmpty() ? buildInfoCurrentExecution : triggerBuildInfo; + buildDetailExtractor.tryToExtractBuildDetails(buildInfoForDetails, stageContext); } } } @@ -210,23 +247,35 @@ private Map createAugmentedRequest(Map trigger, )); } - request.put("package", requestPackages.stream().collect(joining(" "))); - return request; + stageContext.put("package", requestPackages.stream().collect(joining(" "))); + return stageContext; } - Map getArtifactSourceBuildInfo(Map trigger) { - Map buildInfo = Optional.ofNullable((Map) trigger.get("buildInfo")).orElse(emptyMap()); + /** + * @param trigger + * @return the buildInfo block from the pipeline trigger if it exists, + * or the buildInfo block from the parent pipeline trigger if it exists, + * or an empty map. + */ + Map getBuildInfoFromTriggerOrParentTrigger(Map trigger) { + Map triggerBuildInfo = Optional.ofNullable((Map) trigger.get("buildInfo")).orElse(emptyMap()); Map parentExecution = Optional.ofNullable((Map) trigger.get("parentExecution")).orElse(emptyMap()); - if (buildInfo.get("artifacts") != null) { - return buildInfo; + if (triggerBuildInfo.get("artifacts") != null) { + return triggerBuildInfo; } if (parentExecution.get("trigger") != null) { - return getArtifactSourceBuildInfo((Map) parentExecution.get("trigger")); + return getBuildInfoFromTriggerOrParentTrigger((Map) parentExecution.get("trigger")); } return emptyMap(); } - private String extractPackageName(Map artifact, String fileExtension) { + /** + * packageIdentifier is the package name plus version information. + * When filename = orca_1.1767.0-h1997.29115f6_all.deb + * then packageIdentifier = orca_1.1767.0-h1997.29115f6_all + * @return the name of the package plus all version information, including architecture. + */ + private String extractPackageIdentifier(Map artifact, String fileExtension) { String fileName = artifact.get("fileName").toString(); return fileName.substring(0, fileName.lastIndexOf(fileExtension)); } @@ -245,8 +294,8 @@ private Map filterArtifacts(List> artifacts, if (packageType.equals("rpm")) { return filterRPMArtifacts(artifacts, prefix); } else { - return artifacts. - stream() + return artifacts + .stream() .filter(it -> it.get("fileName") != null && it.get("fileName").toString().startsWith(prefix) && it.get("fileName").toString().endsWith(fileExtension) ) @@ -255,6 +304,15 @@ private Map filterArtifacts(List> artifacts, } } + private Artifact filterKorkArtifacts(List artifacts, String requestPackage, String packageType) { + return artifacts + .stream() + .filter( it -> + it.getName() != null && it.getName().equals(requestPackage) && it.getType().equalsIgnoreCase(packageType) + ).findFirst() + .orElse(null); + } + private Map filterRPMArtifacts(List> artifacts, String prefix) { return artifacts .stream() diff --git a/orca-core/src/test/groovy/com/netflix/spinnaker/orca/pipeline/util/PackageInfoSpec.groovy b/orca-core/src/test/groovy/com/netflix/spinnaker/orca/pipeline/util/PackageInfoSpec.groovy index eacca44bbc..dd39408594 100644 --- a/orca-core/src/test/groovy/com/netflix/spinnaker/orca/pipeline/util/PackageInfoSpec.groovy +++ b/orca-core/src/test/groovy/com/netflix/spinnaker/orca/pipeline/util/PackageInfoSpec.groovy @@ -15,6 +15,8 @@ */ package com.netflix.spinnaker.orca.pipeline.util +import com.netflix.spinnaker.kork.artifacts.model.Artifact + import java.util.regex.Pattern import com.fasterxml.jackson.databind.ObjectMapper import com.netflix.spinnaker.orca.pipeline.model.JenkinsTrigger @@ -72,7 +74,7 @@ class PackageInfoSpec extends Specification { def packageType = DEB def packageInfo = - new PackageInfo(quipStage, packageType.packageType, packageType.versionDelimiter, true, false, new ObjectMapper()) + new PackageInfo(quipStage, [], packageType.packageType, packageType.versionDelimiter, true, false, new ObjectMapper()) when: packageInfo.findTargetPackage(false) @@ -112,7 +114,7 @@ class PackageInfoSpec extends Specification { def quipStage = pipeline.stages.last() PackageInfo packageInfo = - new PackageInfo(quipStage, DEB.packageType, DEB.versionDelimiter, true, false, new ObjectMapper()) + new PackageInfo(quipStage, [], DEB.packageType, DEB.versionDelimiter, true, false, new ObjectMapper()) when: def requestMap = packageInfo.findTargetPackage(true) @@ -140,6 +142,7 @@ class PackageInfoSpec extends Specification { PackageType packageType = DEB boolean extractBuildDetails = false PackageInfo packageInfo = new PackageInfo(bakeStage, + [], packageType.packageType, packageType.versionDelimiter, extractBuildDetails, @@ -184,6 +187,7 @@ class PackageInfoSpec extends Specification { when: PackageInfo packageInfo = new PackageInfo(bakeStage, + [], packageType.packageType, packageType.versionDelimiter, extractBuildDetails, @@ -236,7 +240,7 @@ class PackageInfoSpec extends Specification { PackageType packageType = DEB ObjectMapper objectMapper = new ObjectMapper() - PackageInfo packageInfo = new PackageInfo(bakeStage, packageType.packageType, packageType.versionDelimiter, true, true, objectMapper) + PackageInfo packageInfo = new PackageInfo(bakeStage, [], packageType.packageType, packageType.versionDelimiter, true, true, objectMapper) when: Map targetPkg = packageInfo.findTargetPackage(false) @@ -258,7 +262,7 @@ class PackageInfoSpec extends Specification { PackageType packageType = DEB ObjectMapper objectMapper = new ObjectMapper() - PackageInfo packageInfo = new PackageInfo(bakeStage, packageType.packageType, packageType.versionDelimiter, true, true, objectMapper) + PackageInfo packageInfo = new PackageInfo(bakeStage, [], packageType.packageType, packageType.versionDelimiter, true, true, objectMapper) when: Map targetPkg = packageInfo.findTargetPackage(false) @@ -305,7 +309,7 @@ class PackageInfoSpec extends Specification { def bakeStage = pipeline.stageByRef("3") PackageType packageType = DEB ObjectMapper objectMapper = new ObjectMapper() - PackageInfo packageInfo = new PackageInfo(bakeStage, packageType.packageType, packageType.versionDelimiter, true, true, objectMapper) + PackageInfo packageInfo = new PackageInfo(bakeStage, [], packageType.packageType, packageType.versionDelimiter, true, true, objectMapper) def pattern = Pattern.compile("api.*") when: @@ -341,7 +345,7 @@ class PackageInfoSpec extends Specification { PackageType packageType = DEB ObjectMapper objectMapper = new ObjectMapper() - PackageInfo packageInfo = new PackageInfo(quipStage, packageType.packageType, packageType.versionDelimiter, true, true, objectMapper) + PackageInfo packageInfo = new PackageInfo(quipStage, [], packageType.packageType, packageType.versionDelimiter, true, true, objectMapper) when: Map targetPkg = packageInfo.findTargetPackage(false) @@ -376,7 +380,7 @@ class PackageInfoSpec extends Specification { PackageType packageType = DEB ObjectMapper objectMapper = new ObjectMapper() - PackageInfo packageInfo = new PackageInfo(quipStage, packageType.packageType, packageType.versionDelimiter, true, true, objectMapper) + PackageInfo packageInfo = new PackageInfo(quipStage, [], packageType.packageType, packageType.versionDelimiter, true, true, objectMapper) when: Map targetPkg = packageInfo.findTargetPackage(allowMissingPackageInstallation) @@ -405,7 +409,7 @@ class PackageInfoSpec extends Specification { PackageType packageType = DEB ObjectMapper objectMapper = new ObjectMapper() - PackageInfo packageInfo = new PackageInfo(quipStage, packageType.packageType, packageType.versionDelimiter, true, true, objectMapper) + PackageInfo packageInfo = new PackageInfo(quipStage, [], packageType.packageType, packageType.versionDelimiter, true, true, objectMapper) expect: Map targetPkg @@ -440,7 +444,7 @@ class PackageInfoSpec extends Specification { PackageType packageType = DEB ObjectMapper objectMapper = new ObjectMapper() - PackageInfo packageInfo = new PackageInfo(quipStage, packageType.packageType, packageType.versionDelimiter, true, true, objectMapper) + PackageInfo packageInfo = new PackageInfo(quipStage, [], packageType.packageType, packageType.versionDelimiter, true, true, objectMapper) when: Map targetPkg = packageInfo.findTargetPackage(false) @@ -455,6 +459,7 @@ class PackageInfoSpec extends Specification { PackageType packageType = DEB boolean extractBuildDetails = false PackageInfo packageInfo = new PackageInfo(bakeStage, + [], packageType.packageType, packageType.versionDelimiter, extractBuildDetails, @@ -478,10 +483,10 @@ class PackageInfoSpec extends Specification { def "getArtifactSourceBuildInfo: get buildInfo from nearest trigger with artifact"() { given: Stage stage = new Stage(context: [package: "package"]) - PackageInfo packageInfo = new PackageInfo(stage, null, null, true, true, null) + PackageInfo packageInfo = new PackageInfo(stage, [], null, null, true, true, null) expect: - packageInfo.getArtifactSourceBuildInfo(trigger) == buildInfo + packageInfo.getBuildInfoFromTriggerOrParentTrigger(trigger) == buildInfo where: trigger || buildInfo @@ -504,7 +509,7 @@ class PackageInfoSpec extends Specification { PackageType packageType = DEB ObjectMapper objectMapper = new ObjectMapper() - PackageInfo packageInfo = new PackageInfo(quipStage, packageType.packageType, packageType.versionDelimiter, true, true, objectMapper) + PackageInfo packageInfo = new PackageInfo(quipStage, [], packageType.packageType, packageType.versionDelimiter, true, true, objectMapper) when: Map targetPkg = packageInfo.findTargetPackage(true) @@ -559,9 +564,185 @@ class PackageInfoSpec extends Specification { } and: - def packageInfo = new PackageInfo(pipeline.stageById("3"), "deb", "_", false, false, new ObjectMapper()) + def packageInfo = new PackageInfo(pipeline.stageById("3"), [], "deb", "_", false, false, new ObjectMapper()) expect: packageInfo.findTargetPackage(false).package == "spinnakerdeps_0.1.0-114_all spinnaker_0.2.0-114_all" } + + @Unroll("#requestPackage -> #result") + def "should consume kork artifact format when only artifacts are present"() { + given: + Stage bakeStage = new Stage() + PackageType packageType = DEB + boolean extractBuildDetails = false + + Artifact artifact1 = new Artifact.ArtifactBuilder() + .type("DEB") + .name("deb-sample-app-server") + .version("0.0.1~rc.52-h53.96b4f22") + .reference("debian-local:pool/d/deb-sample-app-server/deb-sample-app-server_0.0.1~rc.52-h53.96b4f22.deb") + .provenance("https://jenkins/deb-sample-app-build-master") + .build() + Artifact artifact2 = new Artifact.ArtifactBuilder() + .type("DEB") + .name("my-package") + .version("0.0.1") + .reference("debian-local:pool/d/my-package/my-package_0.0.1_all.deb") + .provenance("https://jenkins/my-package-build-master") + .build() + List artifacts = new ArrayList<>() + artifacts.add(artifact1) + artifacts.add(artifact2) + + PackageInfo packageInfo = new PackageInfo(bakeStage, + artifacts, + packageType.packageType, + packageType.versionDelimiter, + extractBuildDetails, + false, + mapper) + def allowMissingPackageInstallation = true + + Map trigger = ["artifacts": artifacts] + Map buildInfo = [:] + Map stageContext = ["package": requestPackage] + + when: + Map returnedStageContext = packageInfo.createAugmentedRequest(trigger, buildInfo, stageContext, allowMissingPackageInstallation) + + then: + returnedStageContext.package == result + + where: + requestPackage | result + "deb-sample-app-server" | "deb-sample-app-server_0.0.1~rc.52-h53.96b4f22" + "deb-sample-app" | "deb-sample-app" + "deb-sample-app-server deb-sample-app" | "deb-sample-app-server_0.0.1~rc.52-h53.96b4f22 deb-sample-app" + } + + @Unroll("#requestPackage -> #result") + def "should consume kork artifact format there are other build and trigger artifacts"() { + given: + Stage bakeStage = new Stage() + PackageType packageType = DEB + boolean extractBuildDetails = false + + Artifact artifact1 = new Artifact.ArtifactBuilder() + .type("DEB") + .name("deb-sample-app-server") + .version("0.0.1~rc.52-h53.96b4f22") + .reference("debian-local:pool/d/deb-sample-app-server/deb-sample-app-server_0.0.1~rc.52-h53.96b4f22.deb") + .provenance("https://jenkins/deb-sample-app-build-master") + .build() + List artifacts = new ArrayList<>() + artifacts.add(artifact1) + + PackageInfo packageInfo = new PackageInfo(bakeStage, + artifacts, + packageType.packageType, + packageType.versionDelimiter, + extractBuildDetails, + false, + mapper) + def allowMissingPackageInstallation = true + + Map trigger = ["buildInfo": ["artifacts": filename], "artifacts": artifacts] + Map buildInfo = ["artifacts": [["fileName": "blabla.txt"]]] + Map stageContext = ["package": requestPackage] + + when: + Map returnedStageContext = packageInfo.createAugmentedRequest(trigger, buildInfo, stageContext, allowMissingPackageInstallation) + + then: + returnedStageContext.package == result + + where: + filename | requestPackage | result + [["fileName": "testEmpty.txt"]] | "deb-sample-app-server" | "deb-sample-app-server_0.0.1~rc.52-h53.96b4f22" + [["fileName": "testEmpty.txt"]] | "deb-sample-app" | "deb-sample-app" + [["fileName": "test-package_1.0.0.deb"]] | "deb-sample-app-server deb-sample-app" | "deb-sample-app-server_0.0.1~rc.52-h53.96b4f22 deb-sample-app" + } + + def "should fail if artifact is present with different versions in artifact and either trigger or build info"() { + given: + Stage bakeStage = new Stage() + PackageType packageType = DEB + boolean extractBuildDetails = false + + Artifact artifact1 = new Artifact.ArtifactBuilder() + .type("DEB") + .name("test-package") + .version("1.0.0") + .reference("debian-local:pool/d/test-package/test-package_1.0.0.deb") + .provenance("https://jenkins/test-package-build-master") + .build() + List artifacts = new ArrayList<>() + artifacts.add(artifact1) + + PackageInfo packageInfo = new PackageInfo(bakeStage, + artifacts, + packageType.packageType, + packageType.versionDelimiter, + extractBuildDetails, + false, + mapper) + def allowMissingPackageInstallation = true + + Map trigger = ["buildInfo": ["artifacts": triggerFilename], "artifacts": artifacts] + Map buildInfo = ["artifacts": buildFilename] + Map stageContext = ["package": requestPackage] + + when: + packageInfo.createAugmentedRequest(trigger, buildInfo, stageContext, allowMissingPackageInstallation) + + then: + def exception = thrown(IllegalStateException) + exception.message.contains("build artifact in both") + + where: + triggerFilename | buildFilename | requestPackage + [["fileName": "test-package_2.0.0.deb"]] | [["fileName": "bla_1.0.0.deb"]] | "test-package" + [["fileName": "bla_1.0.0.deb"]] | [["fileName": "test-package_2.0.0.deb"]] | "test-package" + } + + def "should work if the same artifact is present in different places"() { + given: + Stage bakeStage = new Stage() + PackageType packageType = DEB + boolean extractBuildDetails = false + + Artifact artifact1 = new Artifact.ArtifactBuilder() + .type("DEB") + .name("test-package") + .version("1.0.0") + .reference("debian-local:pool/d/test-package/test-package_1.0.0.deb") + .provenance("https://jenkins/test-package-build-master") + .build() + List artifacts = new ArrayList<>() + artifacts.add(artifact1) + + PackageInfo packageInfo = new PackageInfo(bakeStage, + artifacts, + packageType.packageType, + packageType.versionDelimiter, + extractBuildDetails, + false, + mapper) + def allowMissingPackageInstallation = true + + Map trigger = ["buildInfo": ["artifacts": triggerFilename], "artifacts": artifacts] + Map buildInfo = ["artifacts": buildFilename] + Map stageContext = ["package": requestPackage] + + when: + Map returnedStageContext = packageInfo.createAugmentedRequest(trigger, buildInfo, stageContext, allowMissingPackageInstallation) + + then: + returnedStageContext.package == result + + where: + triggerFilename | buildFilename | requestPackage | result + [["fileName": "test-package_1.0.0.deb"]] | [["fileName": "test-package_1.0.0.deb"]] | "test-package" | "test-package_1.0.0" + } }