diff --git a/CHANGELOG.md b/CHANGELOG.md index 9d1b99ab..268686ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Unreleased +# v5.6 + +* [Issue #203](https://github.com/manheim/terraform-pipeline/issues/201) ParameterStoreBuildWrapperPlugin - allow path to be customized +* [Issue #192](https://github.com/manheim/terraform-pipeline/issues/192) Apply CredentialsPlugins to all Stages, not just BuildStage + # v5.5 * [Issue #151](https://github.com/manheim/terraform-pipeline/issues/151) Fix CPS mismatch errors, reduce meta magic in Jenkinsfile.groovy and Stage decorations diff --git a/README.md b/README.md index 5e4f2004..5c44a537 100644 --- a/README.md +++ b/README.md @@ -98,7 +98,7 @@ The example above gives you a bare-bones pipeline, and there may be Jenkinsfile * [ConditionalApplyPlugin](./docs/ConditionalApplyPlugin.md): only allow apply on master branch. * [DefaultEnvironmentPlugin](./docs/DefaultEnvironmentPlugin.md): automatically set `TF_VAR_environment` variable. ### Credentials and Configuration Management -* [CredentialsPlugin](./docs/CredentialsPlugin.md): Inject Jenkins credentials into your BuildStage. +* [CredentialsPlugin](./docs/CredentialsPlugin.md): Inject Jenkins credentials into your stages. * [FileParametersPlugin](./docs/FileParametersPlugin.md): Use properties files to inject environment-specific variables. * [ParameterStoreBuildWrapperPlugin](./docs/ParameterStoreBuildWrapperPlugin.md): Inject environment-specific variables using `withAwsParameterStore`. * [ParameterStoreExecPlugin](./docs/ParameterStoreExecPlugin.md): Inject environment-specific variables using parameter-store-exec. diff --git a/docs/CredentialsPlugin.md b/docs/CredentialsPlugin.md index 0be81199..e75c87ac 100644 --- a/docs/CredentialsPlugin.md +++ b/docs/CredentialsPlugin.md @@ -1,12 +1,12 @@ ## [CredentialsPlugin](../src/CredentialsPlugin.groovy) -Enable this plugin to inject credentials into your BuildStage using the [Jenkins Credentials Plugin](https://wiki.jenkins.io/display/JENKINS/Credentials+Plugin). +Enable this plugin to inject credentials into your stages using the [Jenkins Credentials Plugin](https://wiki.jenkins.io/display/JENKINS/Credentials+Plugin). One-time setup: * Install the [Jenkins Credentials Plugin](https://wiki.jenkins.io/display/JENKINS/Credentials+Plugin) on your Jenkins master. * Define a credential that you want to inject. Currently, only usernamePassword credentials are supported. -Specify the credential that you want to inject during the BuildStage. Optionally provide custom username/password environment variables that will contain the credential values for your use. +Specify the credential that you want to inject in your stages. Optionally provide custom username/password environment variables that will contain the credential values for your use. ``` // Jenkinsfile @@ -14,17 +14,19 @@ Specify the credential that you want to inject during the BuildStage. Optionall Jenkinsfile.init(this) +// MY_CREDENTIALS_USERNAME and MY_CREDENTIALS_PASSWORD will contain the respective username/password values of the 'my-credentials' credential. CredentialsPlugin.withBuildCredentials('my-credentials').init() def validate = new TerraformValidateStage() -// MY_CREDENTIALS_USERNAME and MY_CREDENTIALS_PASSWORD will contain the respective username/password values of the 'my-credentials' credential. def build = new BuildStage() -def deployQA = new TerraformEnvironmentStage('qa') +def deployQa = new TerraformEnvironmentStage('qa') +def testQa = new RegressionStage() def deployUat = new TerraformEnvironmentStage('uat') def deployProd = new TerraformEnvironmentStage('prod') validate.then(build) - .then(deployQA) + .then(deployQa) + .then(testQa) .then(deployUat) .then(deployProd) .build() diff --git a/docs/ParameterStoreBuildWrapperPlugin.md b/docs/ParameterStoreBuildWrapperPlugin.md index 6abb9cff..3d63abb4 100644 --- a/docs/ParameterStoreBuildWrapperPlugin.md +++ b/docs/ParameterStoreBuildWrapperPlugin.md @@ -32,3 +32,32 @@ validate.then(deployQA) .then(deployProd) .build() ``` + +Optionally, you can override the default ParameterStore path with your own custom pattern. + +``` +// Jenkinsfile +@Library(['terraform-pipeline@v5.0']) _ + +Jenkinsfile.init(this) + +// Enable ParameterStoreBuildWrapperPlugin with a custom path pattern +ParameterStoreBuildWrapperPlugin.withPathPattern { options -> "/${options['organization']}/${options['environment']}/${options['repoName']}" } + .init() + +def validate = new TerraformValidateStage() + +// Inject all parameters in //qa/ +def deployQA = new TerraformEnvironmentStage('qa') + +// Inject all parameters in //uat/ +def deployUat = new TerraformEnvironmentStage('uat') + +// Inject all parameters in //prod/ +def deployProd = new TerraformEnvironmentStage('prod') + +validate.then(deployQA) + .then(deployUat) + .then(deployProd) + .build() +``` diff --git a/src/CredentialsPlugin.groovy b/src/CredentialsPlugin.groovy index ee939c50..3ec555aa 100644 --- a/src/CredentialsPlugin.groovy +++ b/src/CredentialsPlugin.groovy @@ -1,4 +1,4 @@ -class CredentialsPlugin implements BuildStagePlugin { +class CredentialsPlugin implements BuildStagePlugin, RegressionStagePlugin, TerraformEnvironmentStagePlugin, TerraformValidateStagePlugin { private static globalBuildCredentials = [] private buildCredentials = [] @@ -6,6 +6,9 @@ class CredentialsPlugin implements BuildStagePlugin { def plugin = new CredentialsPlugin() BuildStage.addPlugin(plugin) + RegressionStage.addPlugin(plugin) + TerraformEnvironmentStage.addPlugin(plugin) + TerraformValidateStage.addPlugin(plugin) } public CredentialsPlugin() { @@ -24,6 +27,21 @@ class CredentialsPlugin implements BuildStagePlugin { buildStage.decorate(addBuildCredentials()) } + @Override + public void apply(RegressionStage regressionStage) { + regressionStage.decorate(addBuildCredentials()) + } + + @Override + public void apply(TerraformEnvironmentStage environmentStage) { + environmentStage.decorate(addBuildCredentials()) + } + + @Override + public void apply(TerraformValidateStage validateStage) { + validateStage.decorate(addBuildCredentials()) + } + private addBuildCredentials() { def credentials = this.buildCredentials return { closure -> diff --git a/src/ParameterStoreBuildWrapperPlugin.groovy b/src/ParameterStoreBuildWrapperPlugin.groovy index 9f80ee9c..84123de2 100644 --- a/src/ParameterStoreBuildWrapperPlugin.groovy +++ b/src/ParameterStoreBuildWrapperPlugin.groovy @@ -2,10 +2,18 @@ import static TerraformEnvironmentStage.PLAN import static TerraformEnvironmentStage.APPLY class ParameterStoreBuildWrapperPlugin implements TerraformEnvironmentStagePlugin { + private static globalPathPattern + private static defaultPathPattern = { options -> "/${options['organization']}/${options['repoName']}/${options['environment']}/" } + public static void init() { TerraformEnvironmentStage.addPlugin(new ParameterStoreBuildWrapperPlugin()) } + public static withPathPattern(Closure newPathPattern) { + globalPathPattern = newPathPattern + return this + } + @Override public void apply(TerraformEnvironmentStage stage) { def environment = stage.getEnvironment() @@ -23,8 +31,13 @@ class ParameterStoreBuildWrapperPlugin implements TerraformEnvironmentStagePlugi String pathForEnvironment(String environment) { String organization = Jenkinsfile.instance.getOrganization() String repoName = Jenkinsfile.instance.getRepoName() + def patternOptions = [ environment: environment, + repoName: repoName, + organization: organization ] - return "/${organization}/${repoName}/${environment}/" + def pathPattern = globalPathPattern ?: defaultPathPattern + + return pathPattern(patternOptions) } public static Closure addParameterStoreBuildWrapper(Map options = []) { @@ -40,4 +53,8 @@ class ParameterStoreBuildWrapperPlugin implements TerraformEnvironmentStagePlugi } } } + + public static reset() { + globalPathPattern = null + } } diff --git a/src/TerraformEnvironmentStage.groovy b/src/TerraformEnvironmentStage.groovy index 76c317bc..015c7dd0 100644 --- a/src/TerraformEnvironmentStage.groovy +++ b/src/TerraformEnvironmentStage.groovy @@ -88,6 +88,10 @@ class TerraformEnvironmentStage implements Stage { } } + public void decorate(Closure decoration) { + decorations.add(ALL, decoration) + } + public decorate(String stageName, Closure decoration) { decorations.add(stageName, decoration) } diff --git a/src/TerraformValidateStage.groovy b/src/TerraformValidateStage.groovy index 39ab32ec..f69c8c31 100644 --- a/src/TerraformValidateStage.groovy +++ b/src/TerraformValidateStage.groovy @@ -41,6 +41,10 @@ class TerraformValidateStage implements Stage { } } + public decorate(Closure decoration) { + decorations.add(ALL, decoration) + } + public decorate(String stageName, Closure decoration) { decorations.add(stageName, decoration) } diff --git a/test/CredentialsPluginTest.groovy b/test/CredentialsPluginTest.groovy index 70075823..0848f86e 100644 --- a/test/CredentialsPluginTest.groovy +++ b/test/CredentialsPluginTest.groovy @@ -5,6 +5,10 @@ import static org.hamcrest.Matchers.instanceOf import static org.hamcrest.Matchers.is import static org.hamcrest.Matchers.notNullValue import static org.junit.Assert.assertThat +import static org.mockito.Mockito.mock +import static org.mockito.Mockito.spy +import static org.mockito.Mockito.verify +import static org.mockito.Mockito.any import org.junit.After import org.junit.Test @@ -17,6 +21,9 @@ class CredentialsPluginTest { @After void resetPlugins() { BuildStage.resetPlugins() + RegressionStage.resetPlugins() + TerraformEnvironmentStage.resetPlugins() + TerraformValidateStage.resetPlugins() CredentialsPlugin.reset() } @@ -28,6 +35,24 @@ class CredentialsPluginTest { assertThat(actualPlugins, hasItem(instanceOf(CredentialsPlugin.class))) } + + @Test + void modifiesRegressionStage() { + CredentialsPlugin.init() + + Collection actualPlugins = RegressionStage.getPlugins() + + assertThat(actualPlugins, hasItem(instanceOf(CredentialsPlugin.class))) + } + + @Test + void modifiesTerraformEnvironmentStage() { + CredentialsPlugin.init() + + Collection actualPlugins = TerraformEnvironmentStage.getPlugins() + + assertThat(actualPlugins, hasItem(instanceOf(CredentialsPlugin.class))) + } } public class WithBuildCredentials { @@ -139,5 +164,47 @@ class CredentialsPluginTest { assertThat(results['passwordVariable'], is(equalTo(customPasswordVariable))) } } + + class Apply { + @Test + void decoratesTheBuildStage() { + def buildStage = mock(BuildStage.class) + def plugin = spy(new CredentialsPlugin()) + + plugin.apply(buildStage) + + verify(buildStage).decorate(any(Closure.class)) + } + + @Test + void decoratesTheRegressionStage() { + def testStage = mock(RegressionStage.class) + def plugin = spy(new CredentialsPlugin()) + + plugin.apply(testStage) + + verify(testStage).decorate(any(Closure.class)) + } + + @Test + void decoratesTheTerraformEnvironmentStage() { + def environment = mock(TerraformEnvironmentStage.class) + def plugin = spy(new CredentialsPlugin()) + + plugin.apply(environment) + + verify(environment).decorate(any(Closure.class)) + } + + @Test + void decoratesTheTerraformValidateStage() { + def environment = mock(TerraformValidateStage.class) + def plugin = spy(new CredentialsPlugin()) + + plugin.apply(environment) + + verify(environment).decorate(any(Closure.class)) + } + } } diff --git a/test/ParameterStoreBuildWrapperPluginTest.groovy b/test/ParameterStoreBuildWrapperPluginTest.groovy index e75db74f..1dc2f021 100644 --- a/test/ParameterStoreBuildWrapperPluginTest.groovy +++ b/test/ParameterStoreBuildWrapperPluginTest.groovy @@ -31,6 +31,7 @@ class ParameterStoreBuildWrapperPluginTest { @After public void reset() { Jenkinsfile.instance = null + ParameterStoreBuildWrapperPlugin.reset() } private configureJenkins(Map config = [:]) { @@ -51,7 +52,36 @@ class ParameterStoreBuildWrapperPluginTest { ParameterStoreBuildWrapperPlugin plugin = new ParameterStoreBuildWrapperPlugin() String actual = plugin.pathForEnvironment(environment) - assertEquals(actual, "/${organization}/${repoName}/${environment}/".toString()) + assertEquals("/${organization}/${repoName}/${environment}/".toString(), actual) + } + + @Test + void usesCustomPatternWhenProvided() { + String organization = "MyOrg" + String repoName = "MyRepo" + String environment = "qa" + Closure customPattern = { options -> "/foo/${options['organization']}/${options['environment']}/${options['repoName']}" } + + configureJenkins(repoName: repoName, organization: organization) + ParameterStoreBuildWrapperPlugin.withPathPattern(customPattern) + ParameterStoreBuildWrapperPlugin plugin = new ParameterStoreBuildWrapperPlugin() + + String actual = plugin.pathForEnvironment(environment) + assertEquals("/foo/${organization}/${environment}/${repoName}".toString(), actual) + } + } + + class WithPathPattern { + @After + public void reset() { + ParameterStoreBuildWrapperPlugin.reset() + } + + @Test + void isFluent() { + def result = ParameterStoreBuildWrapperPlugin.withPathPattern { options -> 'somePattern' } + + assertEquals(ParameterStoreBuildWrapperPlugin.class, result) } } }