From de31cde9b82e80334a9789ce6722827d2fe29893 Mon Sep 17 00:00:00 2001 From: Oliver Nocon <33484802+OliverNocon@users.noreply.github.com> Date: Wed, 6 Nov 2019 11:28:10 +0100 Subject: [PATCH] Add PiperGoUtils for downloading piper binary (#928) * Add PiperGoUtils for downloading piper binary PiperGoUtils provide the link between a Jenkins library step and the library step execution running in a go binary. It makes sure that an adequate binary is available. * fix CodeClimate finding * Remove Delimiter and add download resilience. --- .gitignore | 4 +- go.sum | 1 + src/com/sap/piper/JenkinsUtils.groovy | 19 +++ src/com/sap/piper/PiperGoUtils.groovy | 63 +++++++++ .../com/sap/piper/JenkinsUtilsTest.groovy | 18 +++ .../com/sap/piper/PiperGoUtilsTest.groovy | 129 ++++++++++++++++++ 6 files changed, 232 insertions(+), 2 deletions(-) create mode 100644 src/com/sap/piper/PiperGoUtils.groovy create mode 100644 test/groovy/com/sap/piper/PiperGoUtilsTest.groovy diff --git a/.gitignore b/.gitignore index 8225066ba..98be8170b 100644 --- a/.gitignore +++ b/.gitignore @@ -19,5 +19,5 @@ documentation/docs-gen consumer-test/**/workspace *.code-workspace -piper -piper.exe +/piper +/piper.exe diff --git a/go.sum b/go.sum index 138315660..23b2c9ede 100644 --- a/go.sum +++ b/go.sum @@ -12,6 +12,7 @@ github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeME github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= diff --git a/src/com/sap/piper/JenkinsUtils.groovy b/src/com/sap/piper/JenkinsUtils.groovy index 58296f7a9..6b43ffa77 100644 --- a/src/com/sap/piper/JenkinsUtils.groovy +++ b/src/com/sap/piper/JenkinsUtils.groovy @@ -7,6 +7,7 @@ import hudson.tasks.junit.TestResultAction import jenkins.model.Jenkins import org.apache.commons.io.IOUtils +import org.jenkinsci.plugins.workflow.libs.LibrariesAction import org.jenkinsci.plugins.workflow.steps.MissingContextVariableException @API @@ -108,3 +109,21 @@ String getIssueCommentTriggerAction() { def getJobStartedByUserId() { return getRawBuild().getCause(hudson.model.Cause.UserIdCause.class)?.getUserId() } + +@NonCPS +def getLibrariesInfo() { + def libraries = [] + def build = getRawBuild() + def libs = build.getAction(LibrariesAction.class).getLibraries() + + for (def i = 0; i < libs.size(); i++) { + Map lib = [:] + + lib['name'] = libs[i].name + lib['version'] = libs[i].version + lib['trusted'] = libs[i].trusted + libraries.add(lib) + } + + return libraries +} diff --git a/src/com/sap/piper/PiperGoUtils.groovy b/src/com/sap/piper/PiperGoUtils.groovy new file mode 100644 index 000000000..c575433cb --- /dev/null +++ b/src/com/sap/piper/PiperGoUtils.groovy @@ -0,0 +1,63 @@ +package com.sap.piper + +class PiperGoUtils implements Serializable { + + private static Script steps + private static Utils utils + + PiperGoUtils(Script steps) { + this.steps = steps + this.utils = new Utils() + } + + PiperGoUtils(Script steps, Utils utils) { + this.steps = steps + this.utils = utils + } + + void unstashPiperBin() { + + if (utils.unstash('piper-bin').size() > 0) return + + def libraries = getLibrariesInfo() + String version + libraries.each {lib -> + if (lib.name == 'piper-lib-os') { + version = lib.version + } + } + + def fallbackUrl = 'https://github.com/SAP/jenkins-library/releases/latest/download/piper_master' + def piperBinUrl = (version == 'master') ? fallbackUrl : "https://github.com/SAP/jenkins-library/releases/tag/${version}" + + boolean downloaded = downloadGoBinary(piperBinUrl) + if (!downloaded) { + //Inform that no Piper binary is available for used library branch + steps.echo ("Not able to download go binary of Piper for version ${version}") + //Fallback to master version & throw error in case this fails + steps.retry(5) { + if (!downloadGoBinary(fallbackUrl)) { + steps.sleep(2) + steps.error("Download of Piper go binary failed.") + } + } + + } + utils.stashWithMessage('piper-bin', 'failed to stash piper binary', 'piper') + } + + List getLibrariesInfo() { + return new JenkinsUtils().getLibrariesInfo() + } + + private boolean downloadGoBinary(url) { + + def httpStatus = steps.sh(returnStdout: true, script: "curl --insecure --silent --location --write-out '%{http_code}' --output ./piper '${url}'") + + if (httpStatus == '200') { + steps.sh(script: 'chmod +x ./piper') + return true + } + return false + } +} diff --git a/test/groovy/com/sap/piper/JenkinsUtilsTest.groovy b/test/groovy/com/sap/piper/JenkinsUtilsTest.groovy index b915bc82e..b07fe41b8 100644 --- a/test/groovy/com/sap/piper/JenkinsUtilsTest.groovy +++ b/test/groovy/com/sap/piper/JenkinsUtilsTest.groovy @@ -71,6 +71,16 @@ class JenkinsUtilsTest extends BasePiperTest { return triggerCause } } + def getAction(type) { + return new Object() { + def getLibraries() { + return [ + [name: 'lib1', version: '1', trusted: true], + [name: 'lib2', version: '2', trusted: false], + ] + } + } + } } LibraryLoadingTestExecutionListener.prepareObjectInterceptors(rawBuildMock) @@ -130,4 +140,12 @@ class JenkinsUtilsTest extends BasePiperTest { userId = null assertThat(jenkinsUtils.getJobStartedByUserId(), isEmptyOrNullString()) } + + @Test + void testGetLibrariesInfo() { + def libs + libs = jenkinsUtils.getLibrariesInfo() + assertThat(libs[0], is([name: 'lib1', version: '1', trusted: true])) + assertThat(libs[1], is([name: 'lib2', version: '2', trusted: false])) + } } diff --git a/test/groovy/com/sap/piper/PiperGoUtilsTest.groovy b/test/groovy/com/sap/piper/PiperGoUtilsTest.groovy new file mode 100644 index 000000000..92f64b370 --- /dev/null +++ b/test/groovy/com/sap/piper/PiperGoUtilsTest.groovy @@ -0,0 +1,129 @@ +package com.sap.piper + +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.rules.ExpectedException +import org.junit.rules.RuleChain +import util.BasePiperTest +import util.JenkinsLoggingRule +import util.JenkinsShellCallRule +import util.Rules + +import static org.hamcrest.Matchers.containsString +import static org.hamcrest.Matchers.is +import static org.junit.Assert.assertThat + +class PiperGoUtilsTest extends BasePiperTest { + + public ExpectedException exception = ExpectedException.none() + public JenkinsShellCallRule shellCallRule = new JenkinsShellCallRule(this) + public JenkinsLoggingRule loggingRule = new JenkinsLoggingRule(this) + + @Rule + public RuleChain ruleChain = Rules.getCommonRules(this) + .around(shellCallRule) + .around(exception) + .around(loggingRule) + + @Before + void init() { + helper.registerAllowedMethod("retry", [Integer, Closure], null) + } + + @Test + void testUnstashPiperBinAvailable() { + + def piperBinStash = 'piper-bin' + + // this mocks utils.unstash + helper.registerAllowedMethod("unstash", [String.class], { stashFileName -> + if (stashFileName != piperBinStash) { + return [] + } + return [piperBinStash] + }) + + def piperGoUtils = new PiperGoUtils(nullScript, utils) + + piperGoUtils.unstashPiperBin() + } + + + @Test + void testUnstashPiperBinMaster() { + + def piperGoUtils = new PiperGoUtils(nullScript, utils) + piperGoUtils.metaClass.getLibrariesInfo = {-> return [[name: 'piper-lib-os', version: 'master']]} + + // this mocks utils.unstash - mimic stash not existing + helper.registerAllowedMethod("unstash", [String.class], { stashFileName -> + return [] + }) + + shellCallRule.setReturnValue('curl --insecure --silent --location --write-out \'%{http_code}\' --output ./piper \'https://github.com/SAP/jenkins-library/releases/latest/download/piper_master\'', '200') + + piperGoUtils.unstashPiperBin() + assertThat(shellCallRule.shell.size(), is(2)) + assertThat(shellCallRule.shell[0].toString(), is('curl --insecure --silent --location --write-out \'%{http_code}\' --output ./piper \'https://github.com/SAP/jenkins-library/releases/latest/download/piper_master\'')) + assertThat(shellCallRule.shell[1].toString(), is('chmod +x ./piper')) + } + + @Test + void testUnstashPiperBinNonMaster() { + + def piperGoUtils = new PiperGoUtils(nullScript, utils) + piperGoUtils.metaClass.getLibrariesInfo = {-> return [[name: 'piper-lib-os', version: 'testTag']]} + + // this mocks utils.unstash - mimic stash not existing + helper.registerAllowedMethod("unstash", [String.class], { stashFileName -> + return [] + }) + + shellCallRule.setReturnValue('curl --insecure --silent --location --write-out \'%{http_code}\' --output ./piper \'https://github.com/SAP/jenkins-library/releases/tag/testTag\'', '200') + + piperGoUtils.unstashPiperBin() + assertThat(shellCallRule.shell.size(), is(2)) + assertThat(shellCallRule.shell[0].toString(), is('curl --insecure --silent --location --write-out \'%{http_code}\' --output ./piper \'https://github.com/SAP/jenkins-library/releases/tag/testTag\'')) + assertThat(shellCallRule.shell[1].toString(), is('chmod +x ./piper')) + } + + @Test + void testUnstashPiperBinFallback() { + + def piperGoUtils = new PiperGoUtils(nullScript, utils) + piperGoUtils.metaClass.getLibrariesInfo = {-> return [[name: 'piper-lib-os', version: 'notAvailable']]} + + shellCallRule.setReturnValue('curl --insecure --silent --location --write-out \'%{http_code}\' --output ./piper \'https://github.com/SAP/jenkins-library/releases/tag/notAvailable\'', '404') + shellCallRule.setReturnValue('curl --insecure --silent --location --write-out \'%{http_code}\' --output ./piper \'https://github.com/SAP/jenkins-library/releases/latest/download/piper_master\'', '200') + + // this mocks utils.unstash - mimic stash not existing + helper.registerAllowedMethod("unstash", [String.class], { stashFileName -> + return [] + }) + + piperGoUtils.unstashPiperBin() + assertThat(shellCallRule.shell.size(), is(3)) + assertThat(shellCallRule.shell[0].toString(), is('curl --insecure --silent --location --write-out \'%{http_code}\' --output ./piper \'https://github.com/SAP/jenkins-library/releases/tag/notAvailable\'')) + assertThat(shellCallRule.shell[1].toString(), is('curl --insecure --silent --location --write-out \'%{http_code}\' --output ./piper \'https://github.com/SAP/jenkins-library/releases/latest/download/piper_master\'')) + assertThat(shellCallRule.shell[2].toString(), is('chmod +x ./piper')) + } + + @Test + void testDownloadFailed() { + def piperGoUtils = new PiperGoUtils(nullScript, utils) + piperGoUtils.metaClass.getLibrariesInfo = {-> return [[name: 'piper-lib-os', version: 'notAvailable']]} + + shellCallRule.setReturnValue('curl --insecure --silent --location --write-out \'%{http_code}\' --output ./piper \'https://github.com/SAP/jenkins-library/releases/tag/notAvailable\'', '404') + shellCallRule.setReturnValue('curl --insecure --silent --location --write-out \'%{http_code}\' --output ./piper \'https://github.com/SAP/jenkins-library/releases/latest/download/piper_master\'', '500') + + helper.registerAllowedMethod("unstash", [String.class], { stashFileName -> + return [] + }) + + exception.expectMessage(containsString('Download of Piper go binary failed')) + piperGoUtils.unstashPiperBin() + } +} + +