Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support target for plan/apply #229

Merged
merged 7 commits into from
Jun 16, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
# Unpublished changes

* [Issue #193](https://github.com/manheim/terraform-pipeline/issues/193) Support passing branch plans to Github PRs
* [Issue #102](https://github.com/manheim/terraform-pipeline/issues/102) Support target when running terraform plan and apply

# v5.7

Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ The example above gives you a bare-bones pipeline, and there may be Jenkinsfile
* [TerraformDirectoryPlugin](./docs/TerraformDirectoryPlugin.md): Change the default directory containing your terraform code.
* [TerraformLandscapePlugin](./docs/TerraformLandscapePlugin.md): Enable terraform-landscape plan output.
* [TerraformPlanResultsPR](./docs/TerraformPlanResultsPR.md): Use this to post Terraform plan results in the comments of a PR.
* [TargetPlugin](./docs/TargetPlugin.md): set `-target` parameter for terraform plan and apply.

## Write your own Plugin

Expand Down
27 changes: 27 additions & 0 deletions docs/TargetPlugin.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
## [TargetPlugin](../src/TargetPlugin.groovy)

Enable this plugin to run plan/apply on selective resource targets

```
// Jenkinsfile
@Library(['terraform-pipeline']) _

Jenkinsfile.init(this, env)
TargetPlugin.init() // Optionally limit plan/apply to specific targets


kmanning marked this conversation as resolved.
Show resolved Hide resolved
def validate = new TerraformValidateStage()

def deployQa = new TerraformEnvironmentStage('qa')
def deployUat = new TerraformEnvironmentStage('uat')
def deployProd = new TerraformEnvironmentStage('prod')


// Pipeline can now be built with "Build with Parameters"
// New 'target' option in parameter list can limit plan/apply to specific targets
// By default, builds should skip the 'target' option, and build as-normal
validate.then(deployQa)
.then(deployUat)
.then(deployProd)
.build()
```
43 changes: 43 additions & 0 deletions src/TargetPlugin.groovy
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import static TerraformEnvironmentStage.ALL

class TargetPlugin implements TerraformPlanCommandPlugin, TerraformApplyCommandPlugin, TerraformEnvironmentStagePlugin {
public static void init() {
TargetPlugin plugin = new TargetPlugin()

TerraformPlanCommand.addPlugin(plugin)
TerraformApplyCommand.addPlugin(plugin)
TerraformEnvironmentStage.addPlugin(plugin)
}

@Override
public void apply(TerraformPlanCommand command) {
def targets = Jenkinsfile.instance.getEnv().RESOURCE_TARGETS ?: ''
targets.split(',').each { item -> command.withArgument("-target ${item}") }
}

@Override
public void apply(TerraformApplyCommand command) {
def targets = Jenkinsfile.instance.getEnv().RESOURCE_TARGETS ?: ''
targets.split(',').each { item -> command.withArgument("-target ${item}") }
}

@Override
public void apply(TerraformEnvironmentStage stage) {
stage.decorate(ALL, addBuildParams())
}

public static Closure addBuildParams() {
return { closure ->
def params = [
string(name: 'RESOURCE_TARGETS', defaultValue: '', description: 'comma-separated list of resource addresses to pass to plan and apply "-target=" parameters'),
]
def props = [
parameters(params)
]
properties(props)

closure()
}
}

}
109 changes: 109 additions & 0 deletions test/TargetPluginTest.groovy
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
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.eq;
import static org.mockito.Mockito.times;
import static TerraformEnvironmentStage.ALL;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.mock;

import org.junit.Test
import org.junit.Before
import org.junit.After
import org.junit.runner.RunWith
import de.bechte.junit.runners.context.HierarchicalContextRunner

@RunWith(HierarchicalContextRunner.class)
class TargetPluginTest {
@Before
void resetJenkinsEnv() {
Jenkinsfile.instance = mock(Jenkinsfile.class)
when(Jenkinsfile.instance.getEnv()).thenReturn([:])
}

private configureJenkins(Map config = [:]) {
Jenkinsfile.instance = mock(Jenkinsfile.class)
when(Jenkinsfile.instance.getEnv()).thenReturn(config.env ?: [:])
}

public class Init {
@After
void resetPlugins() {
TerraformPlanCommand.resetPlugins()
TerraformApplyCommand.resetPlugins()
TerraformEnvironmentStage.resetPlugins()
}

@Test
void modifiesTerraformPlanCommand() {
TargetPlugin.init()

Collection actualPlugins = TerraformPlanCommand.getPlugins()
assertThat(actualPlugins, hasItem(instanceOf(TargetPlugin.class)))
}

@Test
void modifiesTerraformApplyCommand() {
TargetPlugin.init()

Collection actualPlugins = TerraformApplyCommand.getPlugins()
assertThat(actualPlugins, hasItem(instanceOf(TargetPlugin.class)))
}

@Test
void modifiesTerraformEnvironmentStageCommand() {
TargetPlugin.init()

Collection actualPlugins = TerraformEnvironmentStage.getPlugins()
assertThat(actualPlugins, hasItem(instanceOf(TargetPlugin.class)))
}
}

public class Apply {

@Test
void addsTargetArgumentToTerraformPlan() {
TargetPlugin plugin = new TargetPlugin()
TerraformPlanCommand command = new TerraformPlanCommand()
configureJenkins(env: [
'RESOURCE_TARGETS': 'aws_dynamodb_table.test-table-2,aws_dynamodb_table.test-table-3'
])

plugin.apply(command)

String result = command.toString()
assertThat(result, containsString(" -target aws_dynamodb_table.test-table-2 -target aws_dynamodb_table.test-table-3"))
}

@Test
void addsTargetArgumentToTerraformApply() {
TargetPlugin plugin = new TargetPlugin()
TerraformApplyCommand command = new TerraformApplyCommand()
configureJenkins(env: [
'RESOURCE_TARGETS': 'aws_dynamodb_table.test-table-2,aws_dynamodb_table.test-table-3'
])

plugin.apply(command)

String result = command.toString()
assertThat(result, containsString(" -target aws_dynamodb_table.test-table-2 -target aws_dynamodb_table.test-table-3"))
}

@Test
void decoratesTheTerraformEnvironmentStage() {
TargetPlugin plugin = new TargetPlugin()
def environment = spy(new TerraformEnvironmentStage())
configureJenkins(env: [
'RESOURCE_TARGETS': 'aws_dynamodb_table.test-table-2,aws_dynamodb_table.test-table-3'
])

plugin.apply(environment)

verify(environment, times(1)).decorate(eq(TerraformEnvironmentStage.ALL), any(Closure.class))
}
}
}