From 9b6d6b16643e4f29fd02bbeaa76dabc0a4027261 Mon Sep 17 00:00:00 2001 From: Keith Manning Date: Thu, 30 Jul 2020 00:24:35 -0400 Subject: [PATCH 01/11] Rewrite to exclude Strategies --- src/AnsiColorPlugin.groovy | 2 - src/ConditionalApplyPlugin.groovy | 2 - src/CrqPlugin.groovy | 1 - src/DefaultStrategy.groovy | 53 ---------------- src/DestroyPlugin.groovy | 29 +++++++-- src/DestroyStrategy.groovy | 64 ------------------- src/TerraformEnvironmentStage.groovy | 47 ++++++++++++-- test/DestroyPluginTest.groovy | 94 ++++++++++++++++++++++++---- 8 files changed, 148 insertions(+), 144 deletions(-) delete mode 100644 src/DefaultStrategy.groovy delete mode 100644 src/DestroyStrategy.groovy diff --git a/src/AnsiColorPlugin.groovy b/src/AnsiColorPlugin.groovy index 06a1f002..3f6210ce 100644 --- a/src/AnsiColorPlugin.groovy +++ b/src/AnsiColorPlugin.groovy @@ -1,6 +1,5 @@ import static TerraformEnvironmentStage.PLAN import static TerraformEnvironmentStage.APPLY -import static TerraformEnvironmentStage.DESTROY class AnsiColorPlugin implements TerraformEnvironmentStagePlugin { @@ -12,7 +11,6 @@ class AnsiColorPlugin implements TerraformEnvironmentStagePlugin { public void apply(TerraformEnvironmentStage stage) { stage.decorate(PLAN, addColor()) stage.decorate(APPLY, addColor()) - stage.decorate(DESTROY, addColor()) } public static Closure addColor() { diff --git a/src/ConditionalApplyPlugin.groovy b/src/ConditionalApplyPlugin.groovy index f3e6a898..f24acbf9 100644 --- a/src/ConditionalApplyPlugin.groovy +++ b/src/ConditionalApplyPlugin.groovy @@ -1,6 +1,5 @@ import static TerraformEnvironmentStage.CONFIRM import static TerraformEnvironmentStage.APPLY -import static TerraformEnvironmentStage.DESTROY public class ConditionalApplyPlugin implements TerraformEnvironmentStagePlugin { @@ -14,7 +13,6 @@ public class ConditionalApplyPlugin implements TerraformEnvironmentStagePlugin { public void apply(TerraformEnvironmentStage stage) { stage.decorateAround(CONFIRM, onlyOnExpectedBranch()) stage.decorateAround(APPLY, onlyOnExpectedBranch()) - stage.decorateAround(DESTROY, onlyOnExpectedBranch()) } public Closure onlyOnExpectedBranch() { diff --git a/src/CrqPlugin.groovy b/src/CrqPlugin.groovy index ca398394..0227a527 100644 --- a/src/CrqPlugin.groovy +++ b/src/CrqPlugin.groovy @@ -13,7 +13,6 @@ class CrqPlugin implements TerraformEnvironmentStagePlugin { def environment = stage.getEnvironment() stage.decorate(TerraformEnvironmentStage.APPLY, addCrq(environment)) - stage.decorate(TerraformEnvironmentStage.DESTROY, addCrq(environment)) } public String getCrqEnvironment(String environment) { diff --git a/src/DefaultStrategy.groovy b/src/DefaultStrategy.groovy deleted file mode 100644 index 6e43a21b..00000000 --- a/src/DefaultStrategy.groovy +++ /dev/null @@ -1,53 +0,0 @@ -import static TerraformEnvironmentStage.ALL -import static TerraformEnvironmentStage.PLAN -import static TerraformEnvironmentStage.CONFIRM -import static TerraformEnvironmentStage.APPLY - -class DefaultStrategy { - - private TerraformInitCommand initCommand - private TerraformPlanCommand planCommand - private TerraformApplyCommand applyCommand - private Jenkinsfile jenkinsfile - - public Closure createPipelineClosure(String environment, StageDecorations decorations) { - initCommand = TerraformInitCommand.instanceFor(environment) - planCommand = TerraformPlanCommand.instanceFor(environment) - applyCommand = TerraformApplyCommand.instanceFor(environment) - - jenkinsfile = Jenkinsfile.instance - - return { -> - node(jenkinsfile.getNodeName()) { - deleteDir() - checkout(scm) - - decorations.apply(ALL) { - stage("${PLAN}-${environment}") { - decorations.apply(PLAN) { - sh initCommand.toString() - sh planCommand.toString() - } - } - - decorations.apply("Around-${CONFIRM}") { - stage("${CONFIRM}-${environment}") { - decorations.apply(CONFIRM) { - echo "Approved" - } - } - } - - decorations.apply("Around-${APPLY}") { - stage("${APPLY}-${environment}") { - decorations.apply(APPLY) { - sh initCommand.toString() - sh applyCommand.toString() - } - } - } - } - } - } - } -} diff --git a/src/DestroyPlugin.groovy b/src/DestroyPlugin.groovy index db3d5e11..fbfcaa2e 100644 --- a/src/DestroyPlugin.groovy +++ b/src/DestroyPlugin.groovy @@ -1,4 +1,6 @@ -class DestroyPlugin implements TerraformEnvironmentStagePlugin { +class DestroyPlugin implements TerraformEnvironmentStagePlugin, + TerraformPlanCommandPlugin, + TerraformApplyCommandPlugin { private static arguments = [] @@ -9,6 +11,25 @@ class DestroyPlugin implements TerraformEnvironmentStagePlugin { ConfirmApplyPlugin.withOkMessage("Run terraform DESTROY now") TerraformEnvironmentStage.addPlugin(plugin) + TerraformPlanCommand.addPlugin(plugin) + TerraformApplyCommand.addPlugin(plugin) + } + + @Override + public void apply(TerraformEnvironmentStage stage) { + // Change stage name to append the word 'destroy' so it's clear that it's altered + // the Stage + } + + public void apply(TerraformPlanCommand command) { + command.withArgument('-destroy') + } + + public void apply(TerraformApplyCommand command) { + command.withCommand('destroy') + for (arg in arguments) { + command.withArgument(arg) + } } public static withArgument(String arg) { @@ -16,9 +37,7 @@ class DestroyPlugin implements TerraformEnvironmentStagePlugin { return this } - @Override - public void apply(TerraformEnvironmentStage stage) { - stage.withStrategy(new DestroyStrategy(arguments)) + public static reset() { + arguments = [] } - } diff --git a/src/DestroyStrategy.groovy b/src/DestroyStrategy.groovy deleted file mode 100644 index b104d232..00000000 --- a/src/DestroyStrategy.groovy +++ /dev/null @@ -1,64 +0,0 @@ -import static TerraformEnvironmentStage.ALL -import static TerraformEnvironmentStage.PLAN -import static TerraformEnvironmentStage.CONFIRM -import static TerraformEnvironmentStage.DESTROY - -class DestroyStrategy { - - private TerraformInitCommand initCommand - private TerraformPlanCommand planCommand - private TerraformApplyCommand destroyCommand - private Jenkinsfile jenkinsfile - private List extraArguments - - DestroyStrategy(List args) { - this.extraArguments = args - } - - public Closure createPipelineClosure(String environment, StageDecorations decorations) { - initCommand = TerraformInitCommand.instanceFor(environment) - - planCommand = TerraformPlanCommand.instanceFor(environment) - planCommand = planCommand.withArgument("-destroy") - - destroyCommand = TerraformApplyCommand.instanceFor(environment) - destroyCommand = destroyCommand.withCommand("destroy") - for (arg in extraArguments) { - destroyCommand = destroyCommand.withArgument(arg) - } - - jenkinsfile = Jenkinsfile.instance - - return { -> - node(jenkinsfile.getNodeName()) { - deleteDir() - checkout(scm) - - decorations.apply(ALL) { - stage("${PLAN}-${DESTROY}-${environment}") { - decorations.apply(PLAN) { - sh initCommand.toString() - sh planCommand.toString() - } - } - - decorations.apply("Around-${CONFIRM}") { - stage("${CONFIRM}-${DESTROY}-${environment}") { - decorations.apply(CONFIRM) { - echo "Approved" - } - } - } - - decorations.apply("Around-${DESTROY}") { - stage("${DESTROY}-${environment}") { - decorations.apply(DESTROY) { - sh destroyCommand.toString() - } - } - } - } - } - } - } -} diff --git a/src/TerraformEnvironmentStage.groovy b/src/TerraformEnvironmentStage.groovy index 1b02c4a2..beae7ba6 100644 --- a/src/TerraformEnvironmentStage.groovy +++ b/src/TerraformEnvironmentStage.groovy @@ -3,7 +3,6 @@ class TerraformEnvironmentStage implements Stage { private String environment private StageDecorations decorations private localPlugins - private static strategy = new DefaultStrategy() private static final DEFAULT_PLUGINS = [ new ConditionalApplyPlugin(), new ConfirmApplyPlugin(), new DefaultEnvironmentPlugin() ] private static globalPlugins = DEFAULT_PLUGINS.clone() @@ -45,13 +44,49 @@ class TerraformEnvironmentStage implements Stage { Jenkinsfile.build(pipelineConfiguration()) } - public void withStrategy(newStrategy) { - this.strategy = newStrategy - } - private Closure pipelineConfiguration() { + def initCommand = TerraformInitCommand.instanceFor(environment) + def planCommand = TerraformPlanCommand.instanceFor(environment) + def applyCommand = TerraformApplyCommand.instanceFor(environment) + applyPlugins() - return strategy.createPipelineClosure(environment, decorations) + + def String environment = this.environment + return { -> + node(jenkinsfile.getNodeName()) { + deleteDir() + checkout(scm) + + decorations.apply(ALL) { + // The stage name need to be editable + stage("${PLAN}-${environment}") { + decorations.apply(PLAN) { + sh initCommand.toString() + sh planCommand.toString() + } + } + + decorations.apply("Around-${CONFIRM}") { + // The stage name needs to be editable + stage("${CONFIRM}-${environment}") { + decorations.apply(CONFIRM) { + echo "Approved" + } + } + } + + decorations.apply("Around-${APPLY}") { + // The stage name needs to be editable + stage("${APPLY}-${environment}") { + decorations.apply(APPLY) { + sh initCommand.toString() + sh applyCommand.toString() + } + } + } + } + } + } } public void decorate(Closure decoration) { diff --git a/test/DestroyPluginTest.groovy b/test/DestroyPluginTest.groovy index 134ab733..67649351 100644 --- a/test/DestroyPluginTest.groovy +++ b/test/DestroyPluginTest.groovy @@ -1,10 +1,7 @@ +import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.hasItem; import static org.hamcrest.Matchers.instanceOf; import static org.junit.Assert.assertThat; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.any; -import static org.mockito.Mockito.times; import static org.mockito.Mockito.when; import static org.mockito.Mockito.mock; import static org.junit.Assert.assertEquals; @@ -25,10 +22,11 @@ class DestroyPluginTest { } public class Init { - @After void resetPlugins() { TerraformEnvironmentStage.resetPlugins() + TerraformPlanCommand.resetPlugins() + TerraformApplyCommand.resetPlugins() } @Test @@ -46,22 +44,96 @@ class DestroyPluginTest { String confirmMessage = ConfirmApplyPlugin.confirmMessage String okMessage = ConfirmApplyPlugin.okMessage + // Lets turn these into constants assertEquals(confirmMessage, 'WARNING! Are you absolutely sure the plan above is correct? Your environment will be IMMEDIATELY DESTROYED via "terraform destroy"') assertEquals(okMessage, "Run terraform DESTROY now") } + @Test + void modifiesTerraformPlanCommand() { + DestroyPlugin.init() + + Collection actualPlugins = TerraformPlanCommand.getPlugins() + assertThat(actualPlugins, hasItem(instanceOf(DestroyPlugin.class))) + } + + @Test + void modifiesTerraformApplyCommand() { + DestroyPlugin.init() + + Collection actualPlugins = TerraformApplyCommand.getPlugins() + assertThat(actualPlugins, hasItem(instanceOf(DestroyPlugin.class))) + } } - public class Apply { + class ApplyTerraformPlanCommand { + @Test + void modifiesPlanToIncludeDestroyArgument() { + def command = new TerraformPlanCommand() + def plugin = new DestroyPlugin() + + plugin.apply(command) + def result = command.toString() + + assertThat(result, containsString('-destroy')) + } + } + class ApplyTerraformApplyCommand { @Test - void setStrategyForTerraformEnvironmentStage() { - DestroyPlugin plugin = new DestroyPlugin() - def environment = spy(new TerraformEnvironmentStage()) + void modifiesCommandFromApplyToDestroy() { + def command = new TerraformApplyCommand() + def plugin = new DestroyPlugin() + + plugin.apply(command) + def result = command.toString() + + assertThat(result, containsString('terraform destroy')) + } + + class WithArguments { + @After + void resetPlugins() { + DestroyPlugin.reset() + } + + @Test + void includesTheGivenArgument() { + def expectedArgument = '-refresh=false' + def command = new TerraformApplyCommand() + def plugin = new DestroyPlugin() + + plugin.withArgument(expectedArgument) + plugin.apply(command) + def result = command.toString() - plugin.apply(environment) + assertThat(result, containsString(expectedArgument)) + } + + @Test + void includesMultipleArguments() { + def arg1 = '-arg1' + def arg2 = '-arg2' + def command = new TerraformApplyCommand() + def plugin = new DestroyPlugin() + + plugin.withArgument(arg1) + plugin.withArgument(arg2) + plugin.apply(command) + def result = command.toString() + + assertThat(result, containsString(arg1)) + assertThat(result, containsString(arg2)) + } + } + } + + class WithArgument { + @Test + void isFluent() { + def result = DestroyPlugin.withArgument('-arg1') - verify(environment, times(1)).withStrategy(any(DestroyStrategy.class)) + assertEquals(DestroyPlugin.class, result) } } } From ba03c76a2814f2200953c108233ff18e5e5bf8b1 Mon Sep 17 00:00:00 2001 From: Keith Manning Date: Thu, 30 Jul 2020 00:31:38 -0400 Subject: [PATCH 02/11] Start testing pipelineConfiguration --- src/TerraformEnvironmentStage.groovy | 2 +- test/TerraformEnvironmentStageTest.groovy | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/TerraformEnvironmentStage.groovy b/src/TerraformEnvironmentStage.groovy index beae7ba6..ff1ce0ff 100644 --- a/src/TerraformEnvironmentStage.groovy +++ b/src/TerraformEnvironmentStage.groovy @@ -44,7 +44,7 @@ class TerraformEnvironmentStage implements Stage { Jenkinsfile.build(pipelineConfiguration()) } - private Closure pipelineConfiguration() { + public Closure pipelineConfiguration() { def initCommand = TerraformInitCommand.instanceFor(environment) def planCommand = TerraformPlanCommand.instanceFor(environment) def applyCommand = TerraformApplyCommand.instanceFor(environment) diff --git a/test/TerraformEnvironmentStageTest.groovy b/test/TerraformEnvironmentStageTest.groovy index 17571760..e9f2cb3a 100644 --- a/test/TerraformEnvironmentStageTest.groovy +++ b/test/TerraformEnvironmentStageTest.groovy @@ -115,4 +115,15 @@ class TerraformEnvironmentStageTest { assertTrue(result == TerraformEnvironmentStage.class) } } + + class PipelineConfigurations { + @Test + void returnsAClosure() { + def stage = new TerraformEnvironmentStage('foo') + + def result = stage.pipelineConfiguration() + + assertThat(result, isA(Closure.class)) + } + } } From 2f4e56ad69f2dcfd383db4fd8b5079042179359c Mon Sep 17 00:00:00 2001 From: Keith Manning Date: Thu, 30 Jul 2020 01:02:24 -0400 Subject: [PATCH 03/11] Increase test coverage for TerraformEnvironmentStage --- src/Jenkinsfile.groovy | 1 + test/DummyJenkinsfile.groovy | 24 ++++++++++++++++++++ test/TerraformEnvironmentStageTest.groovy | 27 ++++++++++++++++++++++- 3 files changed, 51 insertions(+), 1 deletion(-) diff --git a/src/Jenkinsfile.groovy b/src/Jenkinsfile.groovy index 058ef585..062001c8 100644 --- a/src/Jenkinsfile.groovy +++ b/src/Jenkinsfile.groovy @@ -119,5 +119,6 @@ class Jenkinsfile { public static reset() { instance = new Jenkinsfile() original = null + defaultNodeName = null } } diff --git a/test/DummyJenkinsfile.groovy b/test/DummyJenkinsfile.groovy index 29e20b28..d831f659 100644 --- a/test/DummyJenkinsfile.groovy +++ b/test/DummyJenkinsfile.groovy @@ -1,5 +1,7 @@ class DummyJenkinsfile { public docker + public scm + public env public DummyJenkinsfile() { docker = this @@ -20,8 +22,30 @@ class DummyJenkinsfile { public writeFile(Map options) { println "DummyJenkinsfile.writeFile: ${options.toString()}" } public readFile(String filename) { return "Dummyjenkinsfile.readFile(${filename}): some content" } public fileExists(String filename) { return false } + public sh(String command) { println "DummyJenkinsfile.sh: ${command.toString()}"; return 'DummyJenkinsfile.sh output' } public sh(Map options) { println "DummyJenkinsfile.sh: ${options.toString()}"; return 'DummyJenkinsfile.sh output' } public string(Map options) { println "DummyJenkinsfile.string: ${options.toString()}" } public parameters(params) { println "DummyJenkinsfile.parameters: ${params.toString()}" } public properties(props) { println "DummyJenkinsfile.properties: ${props.toString()}" } + public node(String nodeName, Closure closure) { + println "DummyJenkinsfile.node(${nodeName})" + closure() + } + public deleteDir() { println "DummyJenkinsfile.deleteDir" } + public checkout(scm) { println "DummyJenkinsfile.checkout(${scm})" } + public withEnv(Collection env, Closure closure) { + println "DummyJenkinsfile.withEnv(${env})" + closure() + } + public stage(String stageName, Closure closure) { + println "DummyJenkinsfile.stage(${stageName})" + closure() + } + public timeout(options, Closure closure) { + println "DummyJenkinsfile.timeout(${options})" + closure() + } + public input(options) { + println "DummyJenkinsfile.input(${options})" + } } diff --git a/test/TerraformEnvironmentStageTest.groovy b/test/TerraformEnvironmentStageTest.groovy index e9f2cb3a..30d834f7 100644 --- a/test/TerraformEnvironmentStageTest.groovy +++ b/test/TerraformEnvironmentStageTest.groovy @@ -1,4 +1,6 @@ -import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.doReturn +import static org.mockito.Mockito.mock +import static org.mockito.Mockito.spy import static org.mockito.Mockito.verify import static org.hamcrest.Matchers.hasItem import static org.hamcrest.Matchers.is @@ -8,6 +10,7 @@ import static org.junit.Assert.assertThat import static org.junit.Assert.assertTrue import org.junit.After +import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import de.bechte.junit.runners.context.HierarchicalContextRunner @@ -117,6 +120,16 @@ class TerraformEnvironmentStageTest { } class PipelineConfigurations { + @Before + void resetBefore() { + Jenkinsfile.reset() + } + + @After + void resetAfter() { + Jenkinsfile.reset() + } + @Test void returnsAClosure() { def stage = new TerraformEnvironmentStage('foo') @@ -125,5 +138,17 @@ class TerraformEnvironmentStageTest { assertThat(result, isA(Closure.class)) } + + @Test + void doesNotBlowUpWhenRunningClosure() { + Jenkinsfile.instance = spy(new Jenkinsfile()) + doReturn([:]).when(Jenkinsfile.instance).getEnv() + Jenkinsfile.defaultNodeName = 'foo' + def stage = new TerraformEnvironmentStage('foo') + + def closure = stage.pipelineConfiguration() + closure.delegate = new DummyJenkinsfile() + closure() + } } } From c36e001f10185098ec6d8b8c91d1cc106843e77d Mon Sep 17 00:00:00 2001 From: Keith Manning Date: Thu, 30 Jul 2020 01:08:19 -0400 Subject: [PATCH 04/11] WIP: make the stage name editable --- test/TerraformEnvironmentStageTest.groovy | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/test/TerraformEnvironmentStageTest.groovy b/test/TerraformEnvironmentStageTest.groovy index 30d834f7..e4503b4d 100644 --- a/test/TerraformEnvironmentStageTest.groovy +++ b/test/TerraformEnvironmentStageTest.groovy @@ -151,4 +151,14 @@ class TerraformEnvironmentStageTest { closure() } } + + /* + class WithStageNamePattern { + @Test + void constructsTheDefaultStageNameWhenBlank() { } + + @Test + void constructTheStageNameUsingTheGivenPattern() { } + } + */ } From 1427181dc9f55bf939bb0a91d15873898236fd6d Mon Sep 17 00:00:00 2001 From: Keith Manning Date: Thu, 30 Jul 2020 14:17:15 -0400 Subject: [PATCH 05/11] Test existing functionality --- src/TerraformEnvironmentStage.groovy | 4 ++++ test/TerraformEnvironmentStageTest.groovy | 13 ++++++++++--- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/TerraformEnvironmentStage.groovy b/src/TerraformEnvironmentStage.groovy index ff1ce0ff..afecd7e5 100644 --- a/src/TerraformEnvironmentStage.groovy +++ b/src/TerraformEnvironmentStage.groovy @@ -89,6 +89,10 @@ class TerraformEnvironmentStage implements Stage { } } + public String getStageNameFor(String command) { + return "${command}-${environment}" + } + public void decorate(Closure decoration) { decorations.add(ALL, decoration) } diff --git a/test/TerraformEnvironmentStageTest.groovy b/test/TerraformEnvironmentStageTest.groovy index e4503b4d..9fdc6eed 100644 --- a/test/TerraformEnvironmentStageTest.groovy +++ b/test/TerraformEnvironmentStageTest.groovy @@ -8,6 +8,7 @@ import static org.hamcrest.Matchers.isA import static org.junit.Assert.assertEquals import static org.junit.Assert.assertThat import static org.junit.Assert.assertTrue +import static TerraformEnvironmentStage.PLAN import org.junit.After import org.junit.Before @@ -152,13 +153,19 @@ class TerraformEnvironmentStageTest { } } - /* class WithStageNamePattern { @Test - void constructsTheDefaultStageNameWhenBlank() { } + void constructsTheDefaultStageNameWhenBlank() { + def stage = new TerraformEnvironmentStage('myenv') + def name = stage.getStageNameFor(PLAN) + + assertEquals('plan-myenv', name) + } + + /* @Test void constructTheStageNameUsingTheGivenPattern() { } + */ } - */ } From defec557f5f6af8f07d451c15523666fb672de66 Mon Sep 17 00:00:00 2001 From: Keith Manning Date: Thu, 30 Jul 2020 14:25:57 -0400 Subject: [PATCH 06/11] Allow stage names to be overwritten --- src/TerraformEnvironmentStage.groovy | 15 ++++++++++++++- test/TerraformEnvironmentStageTest.groovy | 21 ++++++++++++++++----- 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/src/TerraformEnvironmentStage.groovy b/src/TerraformEnvironmentStage.groovy index afecd7e5..25e28309 100644 --- a/src/TerraformEnvironmentStage.groovy +++ b/src/TerraformEnvironmentStage.groovy @@ -6,6 +6,7 @@ class TerraformEnvironmentStage implements Stage { private static final DEFAULT_PLUGINS = [ new ConditionalApplyPlugin(), new ConfirmApplyPlugin(), new DefaultEnvironmentPlugin() ] private static globalPlugins = DEFAULT_PLUGINS.clone() + private static Closure stageNamePattern public static final String ALL = 'all' public static final String PLAN = 'plan' @@ -90,7 +91,10 @@ class TerraformEnvironmentStage implements Stage { } public String getStageNameFor(String command) { - return "${command}-${environment}" + def pattern = stageNamePattern ?: { options -> "${options['command']}-${options['environment']}" } + def options = [ command: command, environment: environment ] + + return pattern.call(options) } public void decorate(Closure decoration) { @@ -176,8 +180,17 @@ class TerraformEnvironmentStage implements Stage { return globalPlugins } + public static withStageNamePattern(Closure stageNamePattern) { + this.stageNamePattern = stageNamePattern + } + public static void resetPlugins() { this.globalPlugins = DEFAULT_PLUGINS.clone() // This totally jacks with localPlugins } + + // Replace resetPlugins with this method + public static void reset() { + this.stageNamePattern = null + } } diff --git a/test/TerraformEnvironmentStageTest.groovy b/test/TerraformEnvironmentStageTest.groovy index 9fdc6eed..42fcf0cf 100644 --- a/test/TerraformEnvironmentStageTest.groovy +++ b/test/TerraformEnvironmentStageTest.groovy @@ -154,18 +154,29 @@ class TerraformEnvironmentStageTest { } class WithStageNamePattern { + @Before + @After + void reset() { + TerraformEnvironmentStage.reset() + } + @Test void constructsTheDefaultStageNameWhenBlank() { def stage = new TerraformEnvironmentStage('myenv') - def name = stage.getStageNameFor(PLAN) + def actualName = stage.getStageNameFor(PLAN) - assertEquals('plan-myenv', name) + assertEquals('plan-myenv', actualName) } - /* @Test - void constructTheStageNameUsingTheGivenPattern() { } - */ + void constructTheStageNameUsingTheGivenPattern() { + TerraformEnvironmentStage.withStageNamePattern { options -> "${options['command']}-override-${options['environment']}" } + def stage = new TerraformEnvironmentStage('myenv') + + def actualName = stage.getStageNameFor(PLAN) + + assertEquals('plan-override-myenv', actualName) + } } } From 1acc78ef40707d1e6400a34ffe21c706230c99d4 Mon Sep 17 00:00:00 2001 From: Keith Manning Date: Thu, 30 Jul 2020 14:31:42 -0400 Subject: [PATCH 07/11] Refactor: clean up static property resets --- src/TerraformEnvironmentStage.groovy | 8 ++------ test/AgentNodePluginTest.groovy | 2 +- test/AnsiColorPluginTest.groovy | 2 +- test/CredentialsPluginTest.groovy | 2 +- test/CrqPluginTest.groovy | 2 +- test/DestroyPluginTest.groovy | 2 +- test/FileParametersPluginTest.groovy | 2 +- test/GithubPRPlanPlugin.groovy | 2 +- test/ParameterStoreBuildWrapperPluginTest.groovy | 2 +- test/ParameterStoreExecPluginTest.groovy | 2 +- test/RegressionStageTest.groovy | 4 ++-- test/TargetPluginTest.groovy | 2 +- test/TerraformEnvironmentStageTest.groovy | 2 +- test/WithAwsPluginTest.groovy | 2 +- 14 files changed, 16 insertions(+), 20 deletions(-) diff --git a/src/TerraformEnvironmentStage.groovy b/src/TerraformEnvironmentStage.groovy index 25e28309..b2c803aa 100644 --- a/src/TerraformEnvironmentStage.groovy +++ b/src/TerraformEnvironmentStage.groovy @@ -184,13 +184,9 @@ class TerraformEnvironmentStage implements Stage { this.stageNamePattern = stageNamePattern } - public static void resetPlugins() { - this.globalPlugins = DEFAULT_PLUGINS.clone() - // This totally jacks with localPlugins - } - - // Replace resetPlugins with this method public static void reset() { + // This totally jacks with localPlugins + this.globalPlugins = DEFAULT_PLUGINS.clone() this.stageNamePattern = null } } diff --git a/test/AgentNodePluginTest.groovy b/test/AgentNodePluginTest.groovy index 46206c57..f78068ce 100644 --- a/test/AgentNodePluginTest.groovy +++ b/test/AgentNodePluginTest.groovy @@ -29,7 +29,7 @@ class AgentNodePluginTest { @After void resetPlugins() { TerraformValidateStage.resetPlugins() - TerraformEnvironmentStage.resetPlugins() + TerraformEnvironmentStage.reset() } @Test diff --git a/test/AnsiColorPluginTest.groovy b/test/AnsiColorPluginTest.groovy index d7b38484..240b5ca0 100644 --- a/test/AnsiColorPluginTest.groovy +++ b/test/AnsiColorPluginTest.groovy @@ -12,7 +12,7 @@ class AnsiColorPluginTest { public class Init { @After void resetPlugins() { - TerraformEnvironmentStage.resetPlugins() + TerraformEnvironmentStage.reset() } @Test diff --git a/test/CredentialsPluginTest.groovy b/test/CredentialsPluginTest.groovy index 0848f86e..32335acd 100644 --- a/test/CredentialsPluginTest.groovy +++ b/test/CredentialsPluginTest.groovy @@ -22,7 +22,7 @@ class CredentialsPluginTest { void resetPlugins() { BuildStage.resetPlugins() RegressionStage.resetPlugins() - TerraformEnvironmentStage.resetPlugins() + TerraformEnvironmentStage.reset() TerraformValidateStage.resetPlugins() CredentialsPlugin.reset() } diff --git a/test/CrqPluginTest.groovy b/test/CrqPluginTest.groovy index 130ef716..adf1ec03 100644 --- a/test/CrqPluginTest.groovy +++ b/test/CrqPluginTest.groovy @@ -17,7 +17,7 @@ class CrqPluginTest { public class Init { @After void resetPlugins() { - TerraformEnvironmentStage.resetPlugins() + TerraformEnvironmentStage.reset() } @Test diff --git a/test/DestroyPluginTest.groovy b/test/DestroyPluginTest.groovy index 67649351..5d7df052 100644 --- a/test/DestroyPluginTest.groovy +++ b/test/DestroyPluginTest.groovy @@ -24,7 +24,7 @@ class DestroyPluginTest { public class Init { @After void resetPlugins() { - TerraformEnvironmentStage.resetPlugins() + TerraformEnvironmentStage.reset() TerraformPlanCommand.resetPlugins() TerraformApplyCommand.resetPlugins() } diff --git a/test/FileParametersPluginTest.groovy b/test/FileParametersPluginTest.groovy index 903cc0f7..9c5d7fd7 100644 --- a/test/FileParametersPluginTest.groovy +++ b/test/FileParametersPluginTest.groovy @@ -15,7 +15,7 @@ class FileParametersPluginTest { public class Init { @After void resetPlugins() { - TerraformEnvironmentStage.resetPlugins() + TerraformEnvironmentStage.reset() } @Test diff --git a/test/GithubPRPlanPlugin.groovy b/test/GithubPRPlanPlugin.groovy index 27b1b1a9..57d04372 100644 --- a/test/GithubPRPlanPlugin.groovy +++ b/test/GithubPRPlanPlugin.groovy @@ -42,7 +42,7 @@ class GithubPRPlanPluginTest { @After void resetPlugins() { TerraformPlanCommand.resetPlugins() - TerraformEnvironmentStage.resetPlugins() + TerraformEnvironmentStage.reset() } @Test diff --git a/test/ParameterStoreBuildWrapperPluginTest.groovy b/test/ParameterStoreBuildWrapperPluginTest.groovy index 1dc2f021..dbe9a1df 100644 --- a/test/ParameterStoreBuildWrapperPluginTest.groovy +++ b/test/ParameterStoreBuildWrapperPluginTest.groovy @@ -15,7 +15,7 @@ class ParameterStoreBuildWrapperPluginTest { public class Init { @After void resetPlugins() { - TerraformEnvironmentStage.resetPlugins() + TerraformEnvironmentStage.reset() } @Test diff --git a/test/ParameterStoreExecPluginTest.groovy b/test/ParameterStoreExecPluginTest.groovy index e0476dab..f2fd55ba 100644 --- a/test/ParameterStoreExecPluginTest.groovy +++ b/test/ParameterStoreExecPluginTest.groovy @@ -16,7 +16,7 @@ class ParameterStoreExecPluginTest { @After public void reset() { Jenkinsfile.instance = null - TerraformEnvironmentStage.resetPlugins() + TerraformEnvironmentStage.reset() TerraformPlanCommand.resetPlugins() TerraformApplyCommand.resetPlugins() } diff --git a/test/RegressionStageTest.groovy b/test/RegressionStageTest.groovy index 0c9cd282..7ca48f4c 100644 --- a/test/RegressionStageTest.groovy +++ b/test/RegressionStageTest.groovy @@ -13,7 +13,7 @@ class RegressionStageTest { public class AutomationRepo { @After void reset() { - TerraformEnvironmentStage.resetPlugins() + TerraformEnvironmentStage.reset() Jenkinsfile.instance = mock(Jenkinsfile.class) Jenkinsfile.original = null } @@ -81,7 +81,7 @@ class RegressionStageTest { public class AddedPlugins { @After void resetPlugins() { - TerraformEnvironmentStage.resetPlugins() + TerraformEnvironmentStage.reset() } @Test diff --git a/test/TargetPluginTest.groovy b/test/TargetPluginTest.groovy index ee1e2871..4e371b63 100644 --- a/test/TargetPluginTest.groovy +++ b/test/TargetPluginTest.groovy @@ -36,7 +36,7 @@ class TargetPluginTest { void resetPlugins() { TerraformPlanCommand.resetPlugins() TerraformApplyCommand.resetPlugins() - TerraformEnvironmentStage.resetPlugins() + TerraformEnvironmentStage.reset() } @Test diff --git a/test/TerraformEnvironmentStageTest.groovy b/test/TerraformEnvironmentStageTest.groovy index 42fcf0cf..7122b67d 100644 --- a/test/TerraformEnvironmentStageTest.groovy +++ b/test/TerraformEnvironmentStageTest.groovy @@ -20,7 +20,7 @@ import de.bechte.junit.runners.context.HierarchicalContextRunner class TerraformEnvironmentStageTest { @After void resetPlugins() { - TerraformEnvironmentStage.resetPlugins() + TerraformEnvironmentStage.reset() } public class Then { diff --git a/test/WithAwsPluginTest.groovy b/test/WithAwsPluginTest.groovy index cb4b8f72..e59e3680 100644 --- a/test/WithAwsPluginTest.groovy +++ b/test/WithAwsPluginTest.groovy @@ -29,7 +29,7 @@ class WithAwsPluginTest { public class Init { @After void resetPlugins() { - TerraformEnvironmentStage.resetPlugins() + TerraformEnvironmentStage.reset() } @Test From 00ba40e95d877b831214e520d4e4c2a1458c1b31 Mon Sep 17 00:00:00 2001 From: Keith Manning Date: Thu, 30 Jul 2020 14:41:36 -0400 Subject: [PATCH 08/11] Modify stage names with DestoryPlugin --- src/DestroyPlugin.groovy | 11 ++--------- src/TerraformEnvironmentStage.groovy | 7 +++---- test/DestroyPluginTest.groovy | 6 ++++-- 3 files changed, 9 insertions(+), 15 deletions(-) diff --git a/src/DestroyPlugin.groovy b/src/DestroyPlugin.groovy index fbfcaa2e..82ec126e 100644 --- a/src/DestroyPlugin.groovy +++ b/src/DestroyPlugin.groovy @@ -1,5 +1,4 @@ -class DestroyPlugin implements TerraformEnvironmentStagePlugin, - TerraformPlanCommandPlugin, +class DestroyPlugin implements TerraformPlanCommandPlugin, TerraformApplyCommandPlugin { private static arguments = [] @@ -9,18 +8,12 @@ class DestroyPlugin implements TerraformEnvironmentStagePlugin, ConfirmApplyPlugin.withConfirmMessage('WARNING! Are you absolutely sure the plan above is correct? Your environment will be IMMEDIATELY DESTROYED via "terraform destroy"') ConfirmApplyPlugin.withOkMessage("Run terraform DESTROY now") + TerraformEnvironmentStage.withStageNamePattern { options -> "${options['command']}-DESTROY-${options['environment']}" } - TerraformEnvironmentStage.addPlugin(plugin) TerraformPlanCommand.addPlugin(plugin) TerraformApplyCommand.addPlugin(plugin) } - @Override - public void apply(TerraformEnvironmentStage stage) { - // Change stage name to append the word 'destroy' so it's clear that it's altered - // the Stage - } - public void apply(TerraformPlanCommand command) { command.withArgument('-destroy') } diff --git a/src/TerraformEnvironmentStage.groovy b/src/TerraformEnvironmentStage.groovy index b2c803aa..7caa1811 100644 --- a/src/TerraformEnvironmentStage.groovy +++ b/src/TerraformEnvironmentStage.groovy @@ -59,8 +59,7 @@ class TerraformEnvironmentStage implements Stage { checkout(scm) decorations.apply(ALL) { - // The stage name need to be editable - stage("${PLAN}-${environment}") { + stage(getStageNameFor(PLAN)) { decorations.apply(PLAN) { sh initCommand.toString() sh planCommand.toString() @@ -69,7 +68,7 @@ class TerraformEnvironmentStage implements Stage { decorations.apply("Around-${CONFIRM}") { // The stage name needs to be editable - stage("${CONFIRM}-${environment}") { + stage(getStageNameFor(CONFIRM)) { decorations.apply(CONFIRM) { echo "Approved" } @@ -78,7 +77,7 @@ class TerraformEnvironmentStage implements Stage { decorations.apply("Around-${APPLY}") { // The stage name needs to be editable - stage("${APPLY}-${environment}") { + stage(getStageNameFor(APPLY)) { decorations.apply(APPLY) { sh initCommand.toString() sh applyCommand.toString() diff --git a/test/DestroyPluginTest.groovy b/test/DestroyPluginTest.groovy index 5d7df052..23df2f3a 100644 --- a/test/DestroyPluginTest.groovy +++ b/test/DestroyPluginTest.groovy @@ -32,9 +32,11 @@ class DestroyPluginTest { @Test void modifiesTerraformEnvironmentStageCommand() { DestroyPlugin.init() + def stage = new TerraformEnvironmentStage('foo') - Collection actualPlugins = TerraformEnvironmentStage.getPlugins() - assertThat(actualPlugins, hasItem(instanceOf(DestroyPlugin.class))) + def actualStageName = stage.getStageNameFor(TerraformEnvironmentStage.PLAN) + + assertThat(actualStageName, containsString('DESTROY')) } @Test From 1d734d3dc000b54f9876218a9be1bfb2c4536350 Mon Sep 17 00:00:00 2001 From: Keith Manning Date: Thu, 30 Jul 2020 15:11:17 -0400 Subject: [PATCH 09/11] Add coverage to TerraformEnvironmentStage tests --- test/TerraformEnvironmentStageTest.groovy | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/test/TerraformEnvironmentStageTest.groovy b/test/TerraformEnvironmentStageTest.groovy index 7122b67d..5db7e849 100644 --- a/test/TerraformEnvironmentStageTest.groovy +++ b/test/TerraformEnvironmentStageTest.groovy @@ -23,6 +23,18 @@ class TerraformEnvironmentStageTest { TerraformEnvironmentStage.reset() } + public class ToString { + @Test + void returnsEnvironmentName() { + def expectedEnvironment = 'foo' + def stage = new TerraformEnvironmentStage(expectedEnvironment) + + def result = stage.toString() + + assertEquals(expectedEnvironment, result) + } + } + public class Then { @Test From 1eab22052afd74a155d4a77a59d53c85e2e9eb4f Mon Sep 17 00:00:00 2001 From: Keith Manning Date: Thu, 30 Jul 2020 15:11:29 -0400 Subject: [PATCH 10/11] Add coverage to TerraformEnvironmentStage tests --- test/TerraformEnvironmentStageTest.groovy | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/test/TerraformEnvironmentStageTest.groovy b/test/TerraformEnvironmentStageTest.groovy index 5db7e849..19649510 100644 --- a/test/TerraformEnvironmentStageTest.groovy +++ b/test/TerraformEnvironmentStageTest.groovy @@ -91,6 +91,19 @@ class TerraformEnvironmentStageTest { assertThat(plugins[plugin1Index + 2], is(plugin3)) } + @Test + void preservesMultipleEnvironmentPlugins() { + def stage = new TerraformEnvironmentStage('foo') + + stage.withEnv('key1', 'value1') + .withEnv('key2', 'value2') + + def plugins = stage.getAllPlugins() + .findAll { plugin -> plugin instanceof EnvironmentVariablePlugin } + + assertEquals(2, plugins.size()) + } + @Test void doesNotAddPluginToOtherInstances() { def modifiedStage = new TerraformEnvironmentStage('modified') From 024b9f556de3e784a1a23ff91e07c57cf80a362f Mon Sep 17 00:00:00 2001 From: Keith Manning Date: Thu, 30 Jul 2020 17:01:58 -0400 Subject: [PATCH 11/11] Use constants for destroy messages --- src/DestroyPlugin.groovy | 6 ++++-- test/DestroyPluginTest.groovy | 5 ++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/DestroyPlugin.groovy b/src/DestroyPlugin.groovy index 82ec126e..e0717ea2 100644 --- a/src/DestroyPlugin.groovy +++ b/src/DestroyPlugin.groovy @@ -2,12 +2,14 @@ class DestroyPlugin implements TerraformPlanCommandPlugin, TerraformApplyCommandPlugin { private static arguments = [] + public static DESTROY_CONFIRM_MESSAGE = 'WARNING! Are you absolutely sure the plan above is correct? Your environment will be IMMEDIATELY DESTROYED via "terraform destroy"' + public static DESTROY_OK_MESSAGE = "Run terraform DESTROY now" public static void init() { DestroyPlugin plugin = new DestroyPlugin() - ConfirmApplyPlugin.withConfirmMessage('WARNING! Are you absolutely sure the plan above is correct? Your environment will be IMMEDIATELY DESTROYED via "terraform destroy"') - ConfirmApplyPlugin.withOkMessage("Run terraform DESTROY now") + ConfirmApplyPlugin.withConfirmMessage(DESTROY_CONFIRM_MESSAGE) + ConfirmApplyPlugin.withOkMessage(DESTROY_OK_MESSAGE) TerraformEnvironmentStage.withStageNamePattern { options -> "${options['command']}-DESTROY-${options['environment']}" } TerraformPlanCommand.addPlugin(plugin) diff --git a/test/DestroyPluginTest.groovy b/test/DestroyPluginTest.groovy index 23df2f3a..6ab7a9ca 100644 --- a/test/DestroyPluginTest.groovy +++ b/test/DestroyPluginTest.groovy @@ -46,9 +46,8 @@ class DestroyPluginTest { String confirmMessage = ConfirmApplyPlugin.confirmMessage String okMessage = ConfirmApplyPlugin.okMessage - // Lets turn these into constants - assertEquals(confirmMessage, 'WARNING! Are you absolutely sure the plan above is correct? Your environment will be IMMEDIATELY DESTROYED via "terraform destroy"') - assertEquals(okMessage, "Run terraform DESTROY now") + assertEquals(DestroyPlugin.DESTROY_CONFIRM_MESSAGE, confirmMessage) + assertEquals(DestroyPlugin.DESTROY_OK_MESSAGE, okMessage) } @Test