Skip to content

Commit

Permalink
Merge pull request #421 from jleopold28/issue_420
Browse files Browse the repository at this point in the history
Support withDirectory for PassPlanFilePlugin
  • Loading branch information
jleopold28 committed Feb 3, 2022
2 parents d06a9ba + 48e7ce0 commit 3072cd0
Show file tree
Hide file tree
Showing 6 changed files with 141 additions and 17 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
- [Issue #422](https://github.com/manheim/terraform-pipeline/issues/422) TerraformPlugin and TerraformPluginVersion should implement TerraformInitCommandPlugin
- [Issue #379](https://github.com/manheim/terraform-pipeline/issues/379) Support Terraform 0.15. Added `-chdir` argument.
- [Issue #395](https://github.com/manheim/terraform-pipeline/issues/395) Support Terraform 1.0. Added `-chdir` argument.

- [Issue #420](https://github.com/manheim/terraform-pipeline/issues/420) Add `withDirectory` argument to PassPlanFilePlugin

# v5.18

Expand Down
29 changes: 29 additions & 0 deletions docs/PassPlanFilePlugin.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ Enable this plugin to pass the plan file output to `terraform apply`.
This plugin stashes the plan file during the `plan` step.
When `apply` is called, the plan file is unstashed and passed as an argument.

For stash and unstash commands, you can either specify a directory when initializing the plugin, or it will default to the `./` directory.

```
// Jenkinsfile
Expand All @@ -15,6 +16,34 @@ Jenkinsfile.init(this, env)
// Pass the plan file to 'terraform apply'
PassPlanFilePlugin.init()
def validate = new TerraformValidateStage()
def destroyQa = new TerraformEnvironmentStage('qa')
def destroyUat = new TerraformEnvironmentStage('uat')
def destroyProd = new TerraformEnvironmentStage('prod')
validate.then(destroyQa)
.then(destroyUat)
.then(destroyProd)
.build()
```

### TerraformDirectoryPlugin Changes

If you are using the TerraformDirectoryPlugin, you must specify the same directory to support stash and unstash.

```
// Jenkinsfile
@Library(['terraform-pipeline@v3.10']) _
Jenkinsfile.init(this, env)
// When using TerraformDirectoryPlugin,
// Pass the plan file to 'terraform apply' using withDirectory
PassPlanFilePlugin.withDirectory('./tf/').init()
TerraformDirectoryPlugin.withDirectory('./tf/').init()
def validate = new TerraformValidateStage()
def destroyQa = new TerraformEnvironmentStage('qa')
Expand Down
23 changes: 19 additions & 4 deletions src/PassPlanFilePlugin.groovy
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import static TerraformEnvironmentStage.PLAN_COMMAND
import static TerraformEnvironmentStage.APPLY_COMMAND

class PassPlanFilePlugin implements TerraformPlanCommandPlugin, TerraformApplyCommandPlugin, TerraformEnvironmentStagePlugin {
class PassPlanFilePlugin implements TerraformPlanCommandPlugin, TerraformApplyCommandPlugin, TerraformEnvironmentStagePlugin, Resettable {

private static String directory = "./"

public static void init() {
PassPlanFilePlugin plugin = new PassPlanFilePlugin()
Expand All @@ -11,6 +13,11 @@ class PassPlanFilePlugin implements TerraformPlanCommandPlugin, TerraformApplyCo
TerraformApplyCommand.addPlugin(plugin)
}

public static withDirectory(String directory) {
PassPlanFilePlugin.directory = directory
return this
}

@Override
public void apply(TerraformEnvironmentStage stage) {
stage.decorate(PLAN_COMMAND, stashPlan(stage.getEnvironment()))
Expand All @@ -26,25 +33,33 @@ class PassPlanFilePlugin implements TerraformPlanCommandPlugin, TerraformApplyCo
@Override
public void apply(TerraformApplyCommand command) {
String env = command.getEnvironment()
command.withDirectory("tfplan-" + env)
command.withPlanFile("tfplan-" + env)
}

public Closure stashPlan(String env) {
return { closure ->
closure()
String planFile = "tfplan-" + env
echo "Stashing ${planFile} file"
stash name: planFile, includes: planFile
dir(directory) {
stash name: planFile, includes: planFile
}
}
}

public Closure unstashPlan(String env) {
return { closure ->
String planFile = "tfplan-" + env
echo "Unstashing ${planFile} file"
unstash planFile
dir(directory) {
unstash planFile
}
closure()
}
}

public static reset() {
directory = "./"
}

}
12 changes: 11 additions & 1 deletion src/TerraformApplyCommand.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ class TerraformApplyCommand implements TerraformCommand, Resettable {
private suffixes = []
private args = []
private String directory
private String planFile
private boolean chdir_flag = false
private Closure variablePattern
private Closure mapPattern
Expand Down Expand Up @@ -76,6 +77,11 @@ class TerraformApplyCommand implements TerraformCommand, Resettable {
return this
}

public TerraformApplyCommand withPlanFile(String planFile) {
this.planFile = planFile
return this
}

public TerraformApplyCommand withChangeDirectoryFlag() {
this.chdir_flag = true
return this
Expand All @@ -94,10 +100,14 @@ class TerraformApplyCommand implements TerraformCommand, Resettable {
pieces << "-input=false"
}
pieces += args
if (directory && !chdir_flag) {
if (directory && !chdir_flag && !planFile) {
pieces << directory
}

if (planFile) {
pieces << planFile
}

pieces += suffixes

return pieces.join(' ')
Expand Down
72 changes: 61 additions & 11 deletions test/PassPlanFilePluginTest.groovy
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import static org.hamcrest.Matchers.containsString
import static org.hamcrest.Matchers.equalTo
import static org.hamcrest.Matchers.hasItem
import static org.hamcrest.Matchers.instanceOf
import static org.hamcrest.MatcherAssert.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;
Expand Down Expand Up @@ -83,15 +83,40 @@ class PassPlanFilePluginTest {

@Test
void runsStashPlan() {
def wasCalled = false
def passedClosure = { -> wasCalled = true }
def plugin = new PassPlanFilePlugin()
def workflowScript = spy(new MockWorkflowScript())

def stashClosure = plugin.stashPlan('dev')
stashClosure.delegate = new MockWorkflowScript()
stashClosure.call(passedClosure)
stashClosure.delegate = workflowScript
stashClosure.call { } // we don't care about the inner closure, so we're passing an empty one

assertThat(wasCalled, equalTo(true))
verify(workflowScript, times(1)).stash(eq(name: 'tfplan-dev', includes: 'tfplan-dev'))
}

@Test
void usesCurrentDirectoryByDefault() {
def plugin = new PassPlanFilePlugin()
def workflowScript = mock(MockWorkflowScript.class)

def stashClosure = plugin.stashPlan('dev')
stashClosure.delegate = workflowScript
stashClosure.call { } // we don't care about the inner closure, so we're passing an empty one

verify(workflowScript, times(1)).dir(eq('./'), any(Closure))
}

@Test
void usesDirectoryIfGiven() {
def plugin = new PassPlanFilePlugin()
def workflowScript = mock(MockWorkflowScript.class)
def expectedDirectory = 'myDir'
plugin.withDirectory(expectedDirectory)

def stashClosure = plugin.stashPlan('dev')
stashClosure.delegate = workflowScript
stashClosure.call { } // we don't care about the inner closure, so we're passing an empty one

verify(workflowScript, times(1)).dir(eq(expectedDirectory), any(Closure))
}

}
Expand All @@ -101,15 +126,40 @@ class PassPlanFilePluginTest {

@Test
void runsUnstashPlan() {
def wasCalled = false
def passedClosure = { -> wasCalled = true }
def plugin = new PassPlanFilePlugin()
def workflowScript = spy(new MockWorkflowScript())

def unstashClosure = plugin.unstashPlan('dev')
unstashClosure.delegate = workflowScript
unstashClosure.call { } // we don't care about the inner closure, so we're passing an empty one

verify(workflowScript, times(1)).unstash(eq('tfplan-dev'))
}

@Test
void usesCurrentDirectoryByDefault() {
def plugin = new PassPlanFilePlugin()
def workflowScript = mock(MockWorkflowScript.class)

def unstashClosure = plugin.unstashPlan('dev')
unstashClosure.delegate = workflowScript
unstashClosure.call { } // we don't care about the inner closure, so we're passing an empty one

verify(workflowScript, times(1)).dir(eq('./'), any(Closure))
}

@Test
void usesDirectoryIfGiven() {
def plugin = new PassPlanFilePlugin()
def workflowScript = mock(MockWorkflowScript.class)
def expectedDirectory = 'myDir'
plugin.withDirectory(expectedDirectory)

def unstashClosure = plugin.unstashPlan('dev')
unstashClosure.delegate = new MockWorkflowScript()
unstashClosure.call(passedClosure)
unstashClosure.delegate = workflowScript
unstashClosure.call { } // we don't care about the inner closure, so we're passing an empty one

assertThat(wasCalled, equalTo(true))
verify(workflowScript, times(1)).dir(eq(expectedDirectory), any(Closure))
}

}
Expand Down
20 changes: 20 additions & 0 deletions test/TerraformApplyCommandTest.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,26 @@ class TerraformApplyCommandTest {
}
}

@Nested
public class WithPlanFile {
@Test
void addsPlanFileArgument() {
def command = new TerraformApplyCommand().withPlanFile("foobar")

def actualCommand = command.toString()
assertThat(actualCommand, endsWith(" foobar"))
}

@Test
void addsPlanFileArgumentInPlaceOfDirectory() {
def command = new TerraformApplyCommand().withDirectory("dir")
.withPlanFile("foobar")

def actualCommand = command.toString()
assertThat(actualCommand, endsWith(" foobar"))
}
}

@Nested
public class WithPrefix {
@Test
Expand Down

0 comments on commit 3072cd0

Please sign in to comment.