diff --git a/atlas-jenkins-pipeline-test/src/main/groovy/net/wooga/jenkins/pipeline/test/specifications/DeclarativeJenkinsSpec.groovy b/atlas-jenkins-pipeline-test/src/main/groovy/net/wooga/jenkins/pipeline/test/specifications/DeclarativeJenkinsSpec.groovy index aac7dbf5..174efd36 100644 --- a/atlas-jenkins-pipeline-test/src/main/groovy/net/wooga/jenkins/pipeline/test/specifications/DeclarativeJenkinsSpec.groovy +++ b/atlas-jenkins-pipeline-test/src/main/groovy/net/wooga/jenkins/pipeline/test/specifications/DeclarativeJenkinsSpec.groovy @@ -144,6 +144,7 @@ abstract class DeclarativeJenkinsSpec extends Specification { jenkinsTest.paramInterceptor : //string() from parameters clause WithCredentials.string(params) //string() from withCredentials() context } + registerAllowedMethod("file", [Map]) { WithCredentials.file(it) } registerAllowedMethod("withCredentials", [List.class, Closure.class], WithCredentials.&bindCredentials.curry(credentials, environment)) //needed as utils scripts are dependent on jenkins sandbox diff --git a/atlas-jenkins-pipeline-test/src/main/groovy/net/wooga/jenkins/pipeline/test/specifications/fakes/FakeCredentialStorage.groovy b/atlas-jenkins-pipeline-test/src/main/groovy/net/wooga/jenkins/pipeline/test/specifications/fakes/FakeCredentialStorage.groovy index f5eb3c38..8cf68ac7 100644 --- a/atlas-jenkins-pipeline-test/src/main/groovy/net/wooga/jenkins/pipeline/test/specifications/fakes/FakeCredentialStorage.groovy +++ b/atlas-jenkins-pipeline-test/src/main/groovy/net/wooga/jenkins/pipeline/test/specifications/fakes/FakeCredentialStorage.groovy @@ -1,5 +1,7 @@ package net.wooga.jenkins.pipeline.test.specifications.fakes +import java.nio.file.Files + class FakeCredentialStorage { private final Map keychain @@ -8,6 +10,16 @@ class FakeCredentialStorage { keychain = new HashMap<>() } + def addFile(String key, File secretFile) { + keychain[key] = secretFile.absolutePath + return secretFile + } + def addFile(String key, String content) { + def secretFile = Files.createTempFile(key, ".secret").toFile() + secretFile << content + return addFile(key, secretFile) + } + def addString(String key, String value) { keychain[key] = value } @@ -16,6 +28,11 @@ class FakeCredentialStorage { keychain[key] = [username, password] } + File getFile(String key) { + def filePath = getSecretValueAsString(key) + return filePath? new File(filePath) : null + } + String[] getUsernamePassword(String name) { if(keychain[name] instanceof List) { return keychain[name] as String[] diff --git a/atlas-jenkins-pipeline-test/src/main/groovy/net/wooga/jenkins/pipeline/test/specifications/fakes/FakeEnvironment.groovy b/atlas-jenkins-pipeline-test/src/main/groovy/net/wooga/jenkins/pipeline/test/specifications/fakes/FakeEnvironment.groovy index cc78e5c6..357ddbdf 100644 --- a/atlas-jenkins-pipeline-test/src/main/groovy/net/wooga/jenkins/pipeline/test/specifications/fakes/FakeEnvironment.groovy +++ b/atlas-jenkins-pipeline-test/src/main/groovy/net/wooga/jenkins/pipeline/test/specifications/fakes/FakeEnvironment.groovy @@ -21,7 +21,7 @@ class FakeEnvironment { void runWithEnv(List envStrs, Closure cls) { def envMap = envStrs. collect{it.toString()}. - collectEntries{String envStr -> [(envStr.split("=")[0]): envStr.split("=")[1]]} + collectEntries{String envStr -> [(envStr.split("=")[0].trim()): envStr.split("=")[1].trim()]} runWithEnv(envMap, cls) } void runWithEnv(Map env, Closure cls) { diff --git a/atlas-jenkins-pipeline-test/src/main/groovy/net/wooga/jenkins/pipeline/test/specifications/fakes/WithCredentials.groovy b/atlas-jenkins-pipeline-test/src/main/groovy/net/wooga/jenkins/pipeline/test/specifications/fakes/WithCredentials.groovy index 3d57099b..2a518ebd 100644 --- a/atlas-jenkins-pipeline-test/src/main/groovy/net/wooga/jenkins/pipeline/test/specifications/fakes/WithCredentials.groovy +++ b/atlas-jenkins-pipeline-test/src/main/groovy/net/wooga/jenkins/pipeline/test/specifications/fakes/WithCredentials.groovy @@ -15,6 +15,14 @@ class WithCredentials { } + static FileCredentials file(Map contents) { + return new FileCredentials().with { + key = contents.credentialsId + filePathVariable = contents.variable + return it + } + } + static StringCredentials string(Map contents) { return new StringCredentials().with { key = contents.credentialsId as String @@ -38,21 +46,26 @@ class WithCredentials { def bindCredentials(List creds, Closure operation) { - Map clsDelegate = [:] + Map credsEnv = [:] creds.each { + if(it instanceof FileCredentials) { + def fileCreds = it as FileCredentials + def secret = credStorage.getFile(fileCreds.key) + credsEnv[fileCreds.filePathVariable] = secret.absolutePath + } if(it instanceof StringCredentials) { def strCreds = it as StringCredentials - clsDelegate[strCreds.variable] = credStorage.getSecretValueAsString(strCreds.key) + credsEnv[strCreds.variable] = credStorage.getSecretValueAsString(strCreds.key) } if(it instanceof UsernamePasswordCredentials) { def upCreds = it as UsernamePasswordCredentials def secrets = credStorage.getUsernamePassword(upCreds.key) - clsDelegate[upCreds.usernameVariable] = secrets[0] - clsDelegate[upCreds.passwordVariable] = secrets[1] + credsEnv[upCreds.usernameVariable] = secrets[0] + credsEnv[upCreds.passwordVariable] = secrets[1] } } - operation.setDelegate(clsDelegate) - return environment.runWithEnv(clsDelegate, operation) + operation.setDelegate(credsEnv) + return environment.runWithEnv(credsEnv, operation) } static class StringCredentials { @@ -66,5 +79,10 @@ class WithCredentials { String passwordVariable } + static class FileCredentials { + String key + String filePathVariable + } + } diff --git a/test/groovy/scripts/BuildUnityWdkV2Spec.groovy b/test/groovy/scripts/BuildUnityWdkV2Spec.groovy new file mode 100644 index 00000000..61935918 --- /dev/null +++ b/test/groovy/scripts/BuildUnityWdkV2Spec.groovy @@ -0,0 +1,183 @@ +package scripts + + +import spock.lang.Unroll +import tools.DeclarativeJenkinsSpec + +class BuildUnityWdkV2Spec extends DeclarativeJenkinsSpec { + private static final String SCRIPT_PATH = "vars/buildUnityWdkV2.groovy" + + @Unroll("publishes UPM/Paket WDK #releaseType-#releaseScope release") + def "publishes UPM/Paket WDK with #release release type"() { + given: "credentials holder with publish keys" + credentials.addUsernamePassword("artifactory_publish", "usr", "pwd") + credentials.addUsernamePassword('github_access', "usr", "pwd") + and: "build plugin with publish parameters" + def buildWDK = loadSandboxedScript(SCRIPT_PATH) { + params.RELEASE_STAGE = releaseType + params.RELEASE_SCOPE = releaseScope + env.GRGIT_USR = "usr" + env.GRGIT_PSW = "pwd" + } + + when: "running buildWDKAutoSwitch pipeline" + inSandbox { buildWDK(unityVersions: ["2019"], logLevel: "level") } + + then: "runs gradle with parameters" + assertShCallsWith("gradlew", + "publish", + "-Ppaket.publish.repository='${releaseType}'", + "-Ppublish.repository='${releaseType}'", + "-PversionBuilder.stage=${releaseType}", + "-PversionBuilder.scope=${releaseScope}" + ) + and: "has set up environment" + def env = usedEnvironments[usedEnvironments.size()-2] + hasBaseEnvironment(env, "level") + env.GRGIT == this.credentials['github_access'] + env.GRGIT_USER == "usr" //"${GRGIT_USR}" + env.GRGIT_PASS == "pwd" //"${GRGIT_PSW}" + env.GITHUB_LOGIN == "usr" //"${GRGIT_USR}" + env.GITHUB_PASSWORD == "pwd" //"${GRGIT_PSW}" + env.UNITY_PACKAGE_MANAGER == "upm" + env.UNITY_LOG_CATEGORY == "build" + env.UPM_USERNAME == "usr" //artifactory_publish user + env.UPM_PASSWORD == "pwd" //artifactory_publish password + env.NUGET_KEY == this.credentials['artifactory_publish'] + + where: + releaseType | releaseScope + "snapshot" | "patch" + "preflight" | "minor" + "rc" | "minor" + "final" | "major" + } + + + @Unroll("assemblies WDK for #releaseType-#releaseScope release ") + def "assemblies UPM WDK with given release data"() { + given: "needed credentials" + def upmCredsFile = credentials.addFile("atlas-upm-credentials", "creds") + credentials.addUsernamePassword("artifactory_read", "key", "pwd") + and: "build plugin with publish parameters" + def buildWDK = loadSandboxedScript(SCRIPT_PATH) { + params.RELEASE_STAGE = releaseType + params.RELEASE_SCOPE = releaseScope + } + + when: "running buildWDKAutoSwitch pipeline" + inSandbox { buildWDK(unityVersions: ["2018"], logLevel: "level") } + then: "runs gradle with parameters" + assertShCallsWith("gradlew", + "-Prelease.stage=${releaseType}", + "-Prelease.scope=${releaseScope}", + "assemble") + + and: "has set up environment" + def env = usedEnvironments.first() + hasBaseEnvironment(env, "level") + env.UNITY_LOG_CATEGORY == "build" + env.UNITY_PACKAGE_MANAGER == "upm" + env.UPM_USER_CONFIG_FILE == upmCredsFile.absolutePath + + and: "stashes gradle cache and build outputs" + stash['wdk_output']["includes"] ==".gradle/**, **/build/outputs/**/*" + + where: + releaseType | releaseScope + "snapshot" | "patch" + "preflight" | "minor" + "rc" | "minor" + "final" | "major" + } + + @Unroll("sets up WDK (UPM and Paket) for #releaseType-#releaseScope release") + def "sets up WDK (UPM and Paket) with given release data"() { + given: "needed credentials" + credentials.addUsernamePassword("artifactory_read", "key", "pwd") + and: "build plugin with publish parameters" + def buildWDK = loadSandboxedScript(SCRIPT_PATH) { + params.RELEASE_STAGE = releaseType + params.RELEASE_SCOPE = releaseScope + } + + when: "running buildWDKAutoSwitch pipeline" + inSandbox { + buildWDK(unityVersions: ["2018"], logLevel: "level", forceRefreshDependencies: forceRefreshDependencies) + } + + then: "runs gradle with parameters" + assertShCallsWith(2,"gradlew", //2 calls 1 for upm, 1 for paket + "-Prelease.stage=${releaseType}", + "-Prelease.scope=${releaseScope}", + "setup") + + and: "stashes upm setup" + stash['upm_setup_w'].with { + assert useDefaultExcludes == true + assert includes == "**/Packages/packages-lock.json, **/PackageCache/**, **/build/**" + } + and: "stashes paket setup" + stash['paket_setup_w'].with { + useDefaultExcludes == true + includes == "paket.lock, .gradle/**, **/build/**, .paket/**, packages/**, paket-files/**, **/Paket.Unity3D/**, **/Wooga/Plugins/**" + } + //TODO: env asserts need a better way to associate environment with stage/step + + where: + releaseType | releaseScope | forceRefreshDependencies + "snapshot" | "patch" | false + "preflight" | "patch" | false + "rc" | "minor" | false + "final" | "major" | false + "snapshot" | "patch" | true + "preflight" | "patch" | true + "rc" | "minor" | true + "final" | "major" | true + } + + @Unroll("#description workspace steps when clearWs is #clearWs") + def "clears steps if clearWs is set"() { + given: "loaded check in a running jenkins build" + def buildWDK = loadSandboxedScript(SCRIPT_PATH) { + params.RELEASE_STAGE = "any" + params.RELEASE_SCOPE = "any" + } + + when: "running pipeline" + inSandbox { + buildWDK(unityVersions: ["2019"], clearWs: clearWs) + } + + then: "all workspaces are clean" + calls["cleanWs"].length == (clearWs? 4 : 0) //setup, build, hash, and publish steps + + where: + clearWs << [true, false] + description = clearWs? "clears" : "doesn't clear" + } + + def hasBaseEnvironment(Map env, String logLevel) { + env.with { + UVM_AUTO_SWITCH_UNITY_EDITOR == "YES" && + UVM_AUTO_INSTALL_UNITY_EDITOR == "YES" && + LOG_LEVEL == logLevel && + ATLAS_READ == this.credentials["artifactory_read"] + } + } + + def assertShCallsWith(int count = 1, String... elements) { + def callArgs = calls["sh"].collect { it.args[0]["script"] as String } + def found = callArgs.findAll { args -> + elements.every { args.contains(it) } + } + assert found.size() == count, + "${found.size()} sh call with arguments [${elements.join(", ")}] found in:\n [${pprintList(callArgs)}]" + return found + } + + def pprintList(List list) { + return list.collect { it.toString() }.join("\n") + } + +} diff --git a/test/groovy/tools/DeclarativeJenkinsSpec.groovy b/test/groovy/tools/DeclarativeJenkinsSpec.groovy index 759d36ff..b840a1c3 100644 --- a/test/groovy/tools/DeclarativeJenkinsSpec.groovy +++ b/test/groovy/tools/DeclarativeJenkinsSpec.groovy @@ -18,7 +18,6 @@ class DeclarativeJenkinsSpec extends SandboxedDeclarativeJenkinsSpec { registerAllowedMethod("junit", [LinkedHashMap]) {} registerAllowedMethod("nunit", [LinkedHashMap]) {} registerAllowedMethod("catchError", [LinkedHashMap, Closure]) {} - } } diff --git a/vars/buildUnityWdkV2.groovy b/vars/buildUnityWdkV2.groovy index f7da0a67..5915aeff 100644 --- a/vars/buildUnityWdkV2.groovy +++ b/vars/buildUnityWdkV2.groovy @@ -1,4 +1,5 @@ #!/usr/bin/env groovy +import net.wooga.jenkins.pipeline.check.steps.Step import net.wooga.jenkins.pipeline.config.PipelineConventions import net.wooga.jenkins.pipeline.config.Platform import net.wooga.jenkins.pipeline.config.WDKConfig @@ -13,7 +14,8 @@ def call(Map configMap = [unityVersions: []]) { configMap.logLevel = configMap.get("logLevel", params.LOG_LEVEL ?: env.LOG_LEVEL as String) configMap.showStackTrace = configMap.get("showStackTrace", params.STACK_TRACE as Boolean) configMap.refreshDependencies = configMap.get("refreshDependencies", params.REFRESH_DEPENDENCIES as Boolean) - configMap.testWrapper = { Closure testOperation, Platform plat -> + configMap.clearWs = configMap.get("clearWs", params.CLEAR_WS as boolean) + configMap.testWrapper = { Step testOperation, Platform plat -> if(env."UNITY_PACKAGE_MANAGER" == "upm") { withCredentials([file(credentialsId: 'atlas-upm-credentials', variable: "UPM_USER_CONFIG_FILE")]) { testOperation(plat) @@ -24,7 +26,7 @@ def call(Map configMap = [unityVersions: []]) { } - def config = WDKConfig.fromConfigMap("macos", configMap, this) + def config = WDKConfig.fromConfigMap(configMap, this) def packageManagerEnvVar = "UNITY_PACKAGE_MANAGER" // We can only configure static pipelines atm. @@ -50,6 +52,7 @@ def call(Map configMap = [unityVersions: []]) { choice(name: 'UPM_RESOLUTION_STRATEGY', choices: ["", "lowest", "highestPatch", "highestMinor", "highest"], description: 'Override the resolution strategy for indirect dependencies') booleanParam(name: 'STACK_TRACE', defaultValue: false, description: 'Whether to log truncated stacktraces') booleanParam(name: 'REFRESH_DEPENDENCIES', defaultValue: false, description: 'Whether to refresh dependencies') + booleanParam(name: 'CLEAR_WS', defaultValue: false, description: 'Whether to clear workspaces after build') } stages { @@ -58,7 +61,7 @@ def call(Map configMap = [unityVersions: []]) { parallel { stage("setup paket") { agent { - label "atlas && $config.buildLabel" + label "atlas && macos" } environment { UNITY_PACKAGE_MANAGER = 'paket' @@ -79,14 +82,18 @@ def call(Map configMap = [unityVersions: []]) { } cleanup { - cleanWs() + script { + if(config.mainPlatform.clearWs) { + cleanWs() + } + } } } } stage("setup upm") { agent { - label "atlas && $config.buildLabel" + label "atlas && macos" } environment { UPM_USER_CONFIG_FILE = credentials('atlas-upm-credentials') @@ -110,7 +117,11 @@ def call(Map configMap = [unityVersions: []]) { archiveArtifacts artifacts: '**/build/logs/*.log', allowEmptyArchive: true } cleanup { - cleanWs() + script { + if(config.mainPlatform.clearWs) { + cleanWs() + } + } } } } @@ -119,7 +130,7 @@ def call(Map configMap = [unityVersions: []]) { stage("Validate package resolution") { agent { - label "atlas && $config.buildLabel" + label "atlas && macos" } steps { unstash 'upm_setup_w' @@ -148,7 +159,7 @@ def call(Map configMap = [unityVersions: []]) { stage('assemble package') { agent { - label "atlas && $config.buildLabel" + label "atlas && macos" } environment { UNITY_PACKAGE_MANAGER = 'upm' @@ -171,7 +182,11 @@ def call(Map configMap = [unityVersions: []]) { archiveArtifacts artifacts: '**/build/logs/*.log', allowEmptyArchive: true } cleanup { - cleanWs() + script { + if(config.mainPlatform.clearWs) { + cleanWs() + } + } } } } @@ -186,12 +201,12 @@ def call(Map configMap = [unityVersions: []]) { } steps { script { - parallel paket:{ + parallel paket: { withEnv(["UNITY_PACKAGE_MANAGER=paket"]) { parallel checkSteps(config, "paket check unity ", "paket_setup_w") } }, - upm:{ + upm: { withEnv(["UNITY_PACKAGE_MANAGER=upm"]) { parallel checkSteps(config, "upm check unity ", "upm_setup_w") } @@ -205,11 +220,10 @@ def call(Map configMap = [unityVersions: []]) { stage('publish') { agent { - label "atlas && $config.buildLabel" + label "atlas && macos" } environment { - UNITY_PACKAGE_MANAGER = 'upm' GRGIT = credentials('github_access') UPM_USER_CONFIG_FILE = credentials('atlas-upm-credentials') GRGIT_USER = "${GRGIT_USR}" @@ -232,7 +246,11 @@ def call(Map configMap = [unityVersions: []]) { archiveArtifacts artifacts: '**/build/logs/*.log', allowEmptyArchive: true } cleanup { - cleanWs() + script { + if(config.mainPlatform.clearWs) { + cleanWs() + } + } } } }