diff --git a/src/com/sap/piper/ConfigurationHelper.groovy b/src/com/sap/piper/ConfigurationHelper.groovy index b8e61d2d0..88ee6af1d 100644 --- a/src/com/sap/piper/ConfigurationHelper.groovy +++ b/src/com/sap/piper/ConfigurationHelper.groovy @@ -1,5 +1,7 @@ package com.sap.piper +import com.cloudbees.groovy.cps.NonCPS + class ConfigurationHelper implements Serializable { static ConfigurationHelper loadStepDefaults(Script step){ return new ConfigurationHelper(step) @@ -96,13 +98,19 @@ class ConfigurationHelper implements Serializable { return this } - Map use(){ return config } + @NonCPS // required because we have a closure in the + // method body that cannot be CPS transformed + Map use(){ + MapUtils.traverse(config, { v -> (v instanceof GString) ? v.toString() : v }) + return config + } ConfigurationHelper(Map config = [:]){ this.config = config } def getConfigProperty(key) { + use() return getConfigPropertyNested(config, key) } diff --git a/src/com/sap/piper/MapUtils.groovy b/src/com/sap/piper/MapUtils.groovy index de4ad2096..784a281e7 100644 --- a/src/com/sap/piper/MapUtils.groovy +++ b/src/com/sap/piper/MapUtils.groovy @@ -38,4 +38,28 @@ class MapUtils implements Serializable { return result } + + /** + * @param m The map to which the changed denoted by closure strategy + * should be applied. + * The strategy is also applied to all sub-maps contained as values + * in m in a recursive manner. + * @param strategy Strategy applied to all non-map entries + */ + @NonCPS + static void traverse(Map m, Closure strategy) { + + def updates = [:] + for(def e : m.entrySet()) { + if(isMap(e.value)) { + traverse(e.getValue(), strategy) + } + else { + // do not update the map while it is traversed. Depending + // on the map implementation the behavior is undefined. + updates.put(e.getKey(), strategy(e.getValue())) + } + } + m.putAll(updates) + } } diff --git a/test/groovy/CloudFoundryDeployTest.groovy b/test/groovy/CloudFoundryDeployTest.groovy index afabbd3af..f4e5d7326 100644 --- a/test/groovy/CloudFoundryDeployTest.groovy +++ b/test/groovy/CloudFoundryDeployTest.groovy @@ -6,6 +6,7 @@ import org.junit.rules.ExpectedException import org.junit.rules.RuleChain import org.yaml.snakeyaml.Yaml import util.BasePiperTest +import util.JenkinsCredentialsRule import util.JenkinsEnvironmentRule import util.JenkinsDockerExecuteRule import util.JenkinsLoggingRule @@ -42,28 +43,9 @@ class CloudFoundryDeployTest extends BasePiperTest { .around(jwfr) .around(jedr) .around(jer) + .around(new JenkinsCredentialsRule(this).withCredentials('test_cfCredentialsId', 'test_cf', '********')) .around(jsr) // needs to be activated after jedr, otherwise executeDocker is not mocked - @Before - void init() throws Throwable { - helper.registerAllowedMethod('usernamePassword', [Map], { m -> return m }) - helper.registerAllowedMethod('withCredentials', [List, Closure], { l, c -> - if(l[0].credentialsId == 'test_cfCredentialsId') { - binding.setProperty('username', 'test_cf') - binding.setProperty('password', '********') - } else if(l[0].credentialsId == 'test_camCredentialsId') { - binding.setProperty('username', 'test_cam') - binding.setProperty('password', '********') - } - try { - c() - } finally { - binding.setProperty('username', null) - binding.setProperty('password', null) - } - }) - } - @Test void testNoTool() throws Exception { nullScript.commonPipelineEnvironment.configuration = [ diff --git a/test/groovy/NeoDeployTest.groovy b/test/groovy/NeoDeployTest.groovy index a39f2a771..3e6841981 100644 --- a/test/groovy/NeoDeployTest.groovy +++ b/test/groovy/NeoDeployTest.groovy @@ -5,8 +5,10 @@ import org.junit.rules.TemporaryFolder import org.junit.BeforeClass import org.junit.ClassRule import org.junit.Ignore + import org.hamcrest.BaseMatcher import org.hamcrest.Description +import org.jenkinsci.plugins.credentialsbinding.impl.CredentialNotFoundException import org.junit.Assert import org.junit.Before import org.junit.Rule @@ -15,6 +17,7 @@ import org.junit.rules.ExpectedException import org.junit.rules.RuleChain import util.BasePiperTest +import util.JenkinsCredentialsRule import util.JenkinsLoggingRule import util.JenkinsReadYamlRule import util.JenkinsShellCallRule @@ -40,6 +43,9 @@ class NeoDeployTest extends BasePiperTest { .around(thrown) .around(jlr) .around(jscr) + .around(new JenkinsCredentialsRule(this) + .withCredentials('myCredentialsId', 'anonymous', '********') + .withCredentials('CI_CREDENTIALS_ID', 'defaultUser', '********')) .around(jsr) private static workspacePath @@ -66,24 +72,6 @@ class NeoDeployTest extends BasePiperTest { helper.registerAllowedMethod('dockerExecute', [Map, Closure], null) helper.registerAllowedMethod('fileExists', [String], { s -> return new File(workspacePath, s).exists() }) - helper.registerAllowedMethod('usernamePassword', [Map], { m -> return m }) - helper.registerAllowedMethod('withCredentials', [List, Closure], { l, c -> - if(l[0].credentialsId == 'myCredentialsId') { - binding.setProperty('username', 'anonymous') - binding.setProperty('password', '********') - } else if(l[0].credentialsId == 'CI_CREDENTIALS_ID') { - binding.setProperty('username', 'defaultUser') - binding.setProperty('password', '********') - } - try { - c() - } finally { - binding.setProperty('username', null) - binding.setProperty('password', null) - } - - }) - helper.registerAllowedMethod('sh', [Map], { Map m -> getVersionWithEnvVars(m) }) nullScript.commonPipelineEnvironment.configuration = [steps:[neoDeploy: [host: 'test.deploy.host.com', account: 'trialuser123']]] @@ -179,8 +167,7 @@ class NeoDeployTest extends BasePiperTest { @Test void badCredentialsIdTest() { - thrown.expect(MissingPropertyException) - thrown.expectMessage('No such property: username') + thrown.expect(CredentialNotFoundException) jsr.step.call(script: nullScript, archivePath: archiveName, diff --git a/test/groovy/PrepareDefaultValuesTest.groovy b/test/groovy/PrepareDefaultValuesTest.groovy index 3be4d5333..6d9195f48 100644 --- a/test/groovy/PrepareDefaultValuesTest.groovy +++ b/test/groovy/PrepareDefaultValuesTest.groovy @@ -30,16 +30,14 @@ public class PrepareDefaultValuesTest extends BasePiperTest { @Before public void setup() { - helper.registerAllowedMethod("libraryResource", [String], { fileName-> return fileName }) - helper.registerAllowedMethod("readYaml", [Map], { m -> - switch(m.text) { - case 'default_pipeline_environment.yml': return [default: 'config'] - case 'custom.yml': return [custom: 'myConfig'] + helper.registerAllowedMethod("libraryResource", [String], { fileName -> + switch(fileName) { + case 'default_pipeline_environment.yml': return "default: 'config'" + case 'custom.yml': return "custom: 'myConfig'" case 'not_found': throw new hudson.AbortException('No such library resource not_found could be found') - default: return [the:'end'] + default: return "the:'end'" } }) - } @Test @@ -54,11 +52,16 @@ public class PrepareDefaultValuesTest extends BasePiperTest { @Test public void testReInitializeOnCustomConfig() { - DefaultValueCache.createInstance([key:'value']) + def instance = DefaultValueCache.createInstance([key:'value']) // existing instance is dropped in case a custom config is provided. jsr.step.call(script: nullScript, customDefaults: 'custom.yml') + // this check is for checking we have another instance + assert ! instance.is(DefaultValueCache.getInstance()) + + // some additional checks that the configuration represented by the new + // config is fine assert DefaultValueCache.getInstance().getDefaultValues().size() == 2 assert DefaultValueCache.getInstance().getDefaultValues().default == 'config' assert DefaultValueCache.getInstance().getDefaultValues().custom == 'myConfig' @@ -67,10 +70,11 @@ public class PrepareDefaultValuesTest extends BasePiperTest { @Test public void testNoReInitializeWithoutCustomConfig() { - DefaultValueCache.createInstance([key:'value']) + def instance = DefaultValueCache.createInstance([key:'value']) jsr.step.call(script: nullScript) + assert instance.is(DefaultValueCache.getInstance()) assert DefaultValueCache.getInstance().getDefaultValues().size() == 1 assert DefaultValueCache.getInstance().getDefaultValues().key == 'value' } diff --git a/test/groovy/com/sap/piper/ConfigurationHelperTest.groovy b/test/groovy/com/sap/piper/ConfigurationHelperTest.groovy index 4d82d55b0..634979743 100644 --- a/test/groovy/com/sap/piper/ConfigurationHelperTest.groovy +++ b/test/groovy/com/sap/piper/ConfigurationHelperTest.groovy @@ -279,4 +279,32 @@ class ConfigurationHelperTest { Assert.assertThat(configuration, hasEntry('collectTelemetryData', false)) } + + @Test + public void testGStringsAreReplacedByJavaLangStrings() { + // + // needed in order to ensure we have real GStrings. + // a GString not containing variables might be optimized to + // a java.lang.String from the very beginning. + def dummy = 'Dummy', + aGString = "a${dummy}", + bGString = "b${dummy}", + cGString = "c${dummy}" + + assert aGString instanceof GString + assert bGString instanceof GString + assert cGString instanceof GString + + def config = new ConfigurationHelper([a: aGString, + nextLevel: [b: bGString]]) + .mixin([c : cGString]) + .use() + + assert config == [a: 'aDummy', + c: 'cDummy', + nextLevel: [b: 'bDummy']] + assert config.a instanceof java.lang.String + assert config.c instanceof java.lang.String + assert config.nextLevel.b instanceof java.lang.String + } } diff --git a/test/groovy/com/sap/piper/MapUtilsTest.groovy b/test/groovy/com/sap/piper/MapUtilsTest.groovy index 2d38e9f0f..61a06aaf8 100644 --- a/test/groovy/com/sap/piper/MapUtilsTest.groovy +++ b/test/groovy/com/sap/piper/MapUtilsTest.groovy @@ -43,4 +43,11 @@ class MapUtilsTest { c: [ d: 'abc', e: '']] } + + @Test + void testTraverse() { + Map m = [a: 'x1', m:[b: 'x2', c: 'otherString']] + MapUtils.traverse(m, { s -> (s.startsWith('x')) ? "replaced" : s}) + assert m == [a: 'replaced', m: [b: 'replaced', c: 'otherString']] + } } diff --git a/test/groovy/util/JenkinsCredentialsRule.groovy b/test/groovy/util/JenkinsCredentialsRule.groovy index 63133c372..5f6e7dd8c 100644 --- a/test/groovy/util/JenkinsCredentialsRule.groovy +++ b/test/groovy/util/JenkinsCredentialsRule.groovy @@ -14,6 +14,7 @@ import static org.hamcrest.Matchers.nullValue import static org.junit.Assert.assertThat; import org.hamcrest.Matchers +import org.jenkinsci.plugins.credentialsbinding.impl.CredentialNotFoundException /** * By default a user "anonymous" with password "********" @@ -47,16 +48,19 @@ class JenkinsCredentialsRule implements TestRule { @Override void evaluate() throws Throwable { - testInstance.helper.registerAllowedMethod('usernamePassword', [Map.class], {m -> return m}) + testInstance.helper.registerAllowedMethod('usernamePassword', [Map.class], + { m -> if (credentials.keySet().contains(m.credentialsId)) return m; + // this is what really happens in case of an unknown credentials id, + // checked with reality using credentials plugin 2.1.18. + throw new CredentialNotFoundException( + "Could not find credentials entry with ID '${m.credentialsId}'") + }) testInstance.helper.registerAllowedMethod('withCredentials', [List, Closure], { l, c -> def credsId = l[0].credentialsId def creds = credentials.get(credsId) - assertThat("Unexpected credentialsId received: '${credsId}'.", - creds, is(not(nullValue()))) - binding.setProperty('username', creds?.user) binding.setProperty('password', creds?.passwd) try {