├── .github
├── pull_request_template.md
└── workflows
│ └── build.yml
├── .gitignore
├── CHANGELOG.md
├── Jenkinsfile.terraform-pipeline
├── LICENSE
├── README.md
├── build.gradle
├── codecov.yml
├── config
└── codenarc
│ └── codenarc.xml
├── docs
├── AgentNodePlugin.md
├── AnsiColorPlugin.md
├── AwssumePlugin.md
├── BuildStage.md
├── BuildWithParametersPlugin.md
├── ConditionalApplyPlugin.md
├── ConfirmApplyPlugin.md
├── ConsulBackendPlugin.md
├── CredentialsPlugin.md
├── DefaultEnvironmentPlugin.md
├── DestroyPlugin.md
├── FileParametersPlugin.md
├── FlywayMigrationPlugin.md
├── GithubPRPlanPlugin.md
├── ParameterStoreBuildWrapperPlugin.md
├── ParameterStoreExecPlugin.md
├── PassPlanFilePlugin.md
├── PlanOnlyPlugin.md
├── RegressionStage.md
├── S3BackendPlugin.md
├── TagPlugin.md
├── TargetPlugin.md
├── TerraformDirectoryPlugin.md
├── TerraformEnvironmentStage.md
├── TerraformEnvironmentStageShellHookPlugin.md
├── TerraformImportPlugin.md
├── TerraformLandscapePlugin.md
├── TerraformOutputOnlyPlugin.md
├── TerraformPlugin.md
├── TerraformStartDirectoryPlugin.md
├── TerraformTaintPlugin.md
├── TfvarsFilesPlugin.md
├── ValidateFormatPlugin.md
└── WithAwsPlugin.md
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── images
├── MultibranchPipeline.png
├── NewItem.png
├── configure-project.png
├── custom-declarative-pipeline.png
├── declarative-pipeline.png
├── default-pipeline-success.png
├── destroy-pipeline.png
├── import-terraform-pipeline.png
├── restart-from-stage-numbers.png
└── restart-from-stage.png
├── settings.gradle
├── src
├── AgentNodePlugin.groovy
├── AnsiColorPlugin.groovy
├── AwssumePlugin.groovy
├── BuildGraph.groovy
├── BuildStage.groovy
├── BuildStagePlugin.groovy
├── BuildWithParametersPlugin.groovy
├── ConditionalApplyPlugin.groovy
├── ConfirmApplyPlugin.groovy
├── ConsulBackendPlugin.groovy
├── CredentialsPlugin.groovy
├── DecoratableStage.groovy
├── DefaultEnvironmentPlugin.groovy
├── DestroyPlugin.groovy
├── EnvironmentVariablePlugin.groovy
├── FileParametersPlugin.groovy
├── FlywayCommand.groovy
├── FlywayMigrationPlugin.groovy
├── GithubPRPlanPlugin.groovy
├── HookPoint.groovy
├── Jenkinsfile.groovy
├── ParameterStoreBuildWrapperPlugin.groovy
├── ParameterStoreExecPlugin.groovy
├── PassPlanFilePlugin.groovy
├── PlanOnlyPlugin.groovy
├── RegressionStage.groovy
├── RegressionStagePlugin.groovy
├── Resettable.groovy
├── S3BackendPlugin.groovy
├── SemanticVersion.groovy
├── Stage.groovy
├── StageDecorations.groovy
├── TagPlugin.groovy
├── TargetPlugin.groovy
├── TerraformApplyCommand.groovy
├── TerraformApplyCommandPlugin.groovy
├── TerraformCommand.groovy
├── TerraformDirectoryPlugin.groovy
├── TerraformEnvironmentStage.groovy
├── TerraformEnvironmentStagePlugin.groovy
├── TerraformEnvironmentStageShellHookPlugin.groovy
├── TerraformFormatCommand.groovy
├── TerraformFormatCommandPlugin.groovy
├── TerraformImportCommand.groovy
├── TerraformImportCommandPlugin.groovy
├── TerraformImportPlugin.groovy
├── TerraformInitCommand.groovy
├── TerraformInitCommandPlugin.groovy
├── TerraformLandscapePlugin.groovy
├── TerraformOutputCommand.groovy
├── TerraformOutputCommandPlugin.groovy
├── TerraformOutputOnlyPlugin.groovy
├── TerraformPlanCommand.groovy
├── TerraformPlanCommandPlugin.groovy
├── TerraformPlugin.groovy
├── TerraformPluginVersion.groovy
├── TerraformPluginVersion11.groovy
├── TerraformPluginVersion12.groovy
├── TerraformPluginVersion15.groovy
├── TerraformStartDirectoryPlugin.groovy
├── TerraformTaintCommand.groovy
├── TerraformTaintCommandPlugin.groovy
├── TerraformTaintPlugin.groovy
├── TerraformUntaintCommand.groovy
├── TerraformUntaintCommandPlugin.groovy
├── TerraformValidateCommand.groovy
├── TerraformValidateCommandPlugin.groovy
├── TerraformValidateStage.groovy
├── TerraformValidateStagePlugin.groovy
├── TfvarsFilesPlugin.groovy
├── ValidateFormatPlugin.groovy
├── WhenToRun.groovy
└── WithAwsPlugin.groovy
├── test
├── AgentNodePluginTest.groovy
├── AnsiColorPluginTest.groovy
├── AwssumePluginTest.groovy
├── BuildGraphTest.groovy
├── BuildStageTest.groovy
├── BuildWithParametersPluginTest.groovy
├── ConditionalApplyPluginTest.groovy
├── ConfirmApplyPluginTest.groovy
├── ConsulBackendPluginTest.groovy
├── CredentialsPluginTest.groovy
├── DefaultEnvironmentPluginTest.groovy
├── DestroyPluginTest.groovy
├── FileParametersPluginTest.groovy
├── FlywayCommandTest.groovy
├── FlywayMigrationPluginTest.groovy
├── GithubPRPlanPluginTest.groovy
├── HookPointTest.groovy
├── JenkinsfileTest.groovy
├── MockJenkinsfile.groovy
├── MockWorkflowScript.groovy
├── ParameterStoreBuildWrapperPluginTest.groovy
├── ParameterStoreExecPluginTest.groovy
├── PassPlanFilePluginTest.groovy
├── PlanOnlyPluginTest.groovy
├── RegressionStageTest.groovy
├── ResetStaticStateExtension.groovy
├── S3BackendPluginTest.groovy
├── SemanticVersionTest.groovy
├── StageDecorationsTest.groovy
├── TagPluginTest.groovy
├── TargetPluginTest.groovy
├── TerraformApplyCommandTest.groovy
├── TerraformDirectoryPluginTest.groovy
├── TerraformEnvironmentStageShellHookPluginTest.groovy
├── TerraformEnvironmentStageTest.groovy
├── TerraformFormatCommandTest.groovy
├── TerraformImportCommandTest.groovy
├── TerraformImportPluginTest.groovy
├── TerraformInitCommandTest.groovy
├── TerraformLandscapePluginTest.groovy
├── TerraformOutputCommandTest.groovy
├── TerraformOutputOnlyPluginTest.groovy
├── TerraformPlanCommandTest.groovy
├── TerraformPluginTest.groovy
├── TerraformPluginVersion11Test.groovy
├── TerraformPluginVersion12Test.groovy
├── TerraformPluginVersion15Test.groovy
├── TerraformStartDirectoryPluginTest.groovy
├── TerraformTaintCommandTest.groovy
├── TerraformTaintPluginTest.groovy
├── TerraformUntaintCommandTest.groovy
├── TerraformValidateCommandTest.groovy
├── TerraformValidateStageTest.groovy
├── TfvarsFilesPluginTest.groovy
├── ValidateFormatPluginTest.groovy
└── WithAwsPluginTest.groovy
└── vars
├── ApplyJenkinsfileClosure.groovy
├── Pipeline2Stage.groovy
├── Pipeline3Stage.groovy
├── Pipeline4Stage.groovy
├── Pipeline5Stage.groovy
├── Pipeline6Stage.groovy
└── Pipeline7Stage.groovy
/.github/pull_request_template.md:
--------------------------------------------------------------------------------
1 | ## Merge Checklist
2 |
3 | * [ ] Have you linked this Pull Request to an [Issue](https://github.com/manheim/terraform-pipeline/issues)?
4 | * [ ] Have you updated any relevant README files, to explain how this new feature works (with examples)?
5 | * [ ] Have you added relevant test cases for your change?
6 | * [ ] Do all tests and code style checks pass with `./gradlew check --info`?
7 | * [ ] Have you successfully run your change in a pipeline in a Jenkins instance?
8 | * [ ] Have you updated the [CHANGELOG](https://github.com/manheim/terraform-pipeline/blob/master/CHANGELOG.md)?
9 |
--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | # Run unit tests
2 | name: CI
3 |
4 | # Triggers the workflow on all push or pull request events
5 | on: [push, pull_request]
6 |
7 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel
8 | jobs:
9 | # This workflow contains a single job called "build"
10 | build:
11 | # The type of runner that the job will run on
12 | runs-on: ubuntu-latest
13 |
14 | # Steps represent a sequence of tasks that will be executed as part of the job
15 | steps:
16 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
17 | - uses: actions/checkout@v2
18 |
19 | - name: Set up JDK 11
20 | uses: actions/setup-java@v2
21 | with:
22 | java-version: '11'
23 | distribution: 'adopt'
24 |
25 | - name: Validate Gradle wrapper
26 | uses: gradle/wrapper-validation-action@e6e38bacfdf1a337459f332974bb2327a31aaf4b
27 |
28 | # Run unit tests
29 | - name: Run tests with gradle
30 | run: ./gradlew check --info
31 |
32 | # Upload code coverage data to CodeCov
33 | - name: Upload CodeCov results
34 | uses: codecov/codecov-action@v1
35 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.swp
2 | .gradle
3 |
4 | build
5 | .idea
6 | *.iml
7 | /out
8 | .history
9 | bin/
10 | tags
11 | .vscode
12 | shell.nix
13 | nix/
14 |
--------------------------------------------------------------------------------
/Jenkinsfile.terraform-pipeline:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/manheim/terraform-pipeline/0b618f10644fb16025e6cc72599b2e16e74c2646/Jenkinsfile.terraform-pipeline
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'groovy'
2 | apply plugin: 'jacoco'
3 | apply plugin: 'codenarc'
4 |
5 | repositories {
6 | mavenCentral()
7 | }
8 |
9 | dependencies {
10 | compile 'org.codehaus.groovy:groovy-all:2.4.12'
11 |
12 | testCompile 'com.lesfurets:jenkins-pipeline-unit:1.1'
13 | testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: '5.7.0'
14 | testRuntimeOnly group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: '5.7.0'
15 | testCompile group: 'com.cyrusinnovation', name: 'mockito-groovy-support', version: '1.3'
16 | testImplementation 'org.hamcrest:hamcrest:2.2'
17 | testImplementation 'org.reflections:reflections:0.9.12'
18 | testImplementation 'org.slf4j:slf4j-api:1.7.30'
19 | }
20 |
21 | test {
22 | useJUnitPlatform()
23 | }
24 |
25 | sourceSets {
26 | test {
27 | groovy {
28 | srcDirs = ['test']
29 | }
30 | }
31 |
32 | main {
33 | groovy {
34 | srcDirs = ['src']
35 | }
36 | }
37 |
38 | }
39 |
40 | jacocoTestReport {
41 | reports {
42 | xml.enabled true
43 | html.enabled true
44 | }
45 | }
46 |
47 | check.dependsOn jacocoTestReport
48 |
--------------------------------------------------------------------------------
/codecov.yml:
--------------------------------------------------------------------------------
1 | codecov:
2 | require_ci_to_pass: no
3 |
--------------------------------------------------------------------------------
/config/codenarc/codenarc.xml:
--------------------------------------------------------------------------------
1 |
16 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/docs/AgentNodePlugin.md:
--------------------------------------------------------------------------------
1 | ## [AgentNodePlugin](../src/AgentNodePlugin.groovy)
2 |
3 | This plugin allows you to run the terraform stages in a docker container. This **DOES NOT WORK WITH AWSSUME** you should be using iam_block with aws provider with terraform.
4 |
5 | ### Using pre-built docker image
6 | ```
7 | // Jenkinsfile
8 | @Library(['terraform-pipeline']) _
9 |
10 | Jenkinsfile.init(this)
11 |
12 | AgentNodePlugin.withAgentDockerImage("hashicorp/terraform:latest")
13 | .withAgentDockerImageOptions("--entrypoint=''")
14 | .init()
15 |
16 | def validate = new TerraformValidateStage()
17 | def deployQA = new TerraformEnvironmentStage('qa')
18 | def deployUat = new TerraformEnvironmentStage('uat')
19 | def deployProd = new TerraformEnvironmentStage('prod')
20 |
21 | validate.then(deployQA)
22 | .then(deployUat)
23 | .then(deployProd)
24 | .build()
25 | ```
26 |
27 | ### Using Dockerfile in repo
28 | ```
29 | // Jenkinsfile
30 | @Library(['terraform-pipeline']) _
31 |
32 | Jenkinsfile.init(this)
33 |
34 | AgentNodePlugin.withAgentDockerImage('custom/terraform-ruby:latest')
35 | .withAgentDockerImageOptions("--entrypoint=''")
36 | .withAgentDockerfile()
37 | .init()
38 |
39 | def validate = new TerraformValidateStage()
40 | def deployQA = new TerraformEnvironmentStage('qa')
41 | def deployUat = new TerraformEnvironmentStage('uat')
42 | def deployProd = new TerraformEnvironmentStage('prod')
43 |
44 | validate.then(deployQA)
45 | .then(deployUat)
46 | .then(deployProd)
47 | .build()
48 | ```
49 |
50 | ### Optionally use a Dockerfile with a different filename
51 | ```
52 | // Jenkinsfile
53 | @Library(['terraform-pipeline']) _
54 |
55 | Jenkinsfile.init(this)
56 |
57 | AgentNodePlugin.withAgentDockerImage('custom/terraform-ruby:latest')
58 | .withAgentDockerImageOptions("--entrypoint=''")
59 | .withAgentDockerfile('MyDockerfile')
60 | .init()
61 |
62 | def validate = new TerraformValidateStage()
63 | def deployQA = new TerraformEnvironmentStage('qa')
64 | def deployUat = new TerraformEnvironmentStage('uat')
65 | def deployProd = new TerraformEnvironmentStage('prod')
66 |
67 | validate.then(deployQA)
68 | .then(deployUat)
69 | .then(deployProd)
70 | .build()
71 | ```
72 |
73 |
--------------------------------------------------------------------------------
/docs/AnsiColorPlugin.md:
--------------------------------------------------------------------------------
1 | ## [AnsiColorPlugin](../src/AnsiColorPlugin.groovy)
2 |
3 | Enable this plugin to color the output for terraform plan and apply.
4 |
5 | One-time setup:
6 | * Install the [AnsiColorPlugin](https://wiki.jenkins.io/display/JENKINS/AnsiColor+Plugin) on your Jenkins server.
7 |
8 | ```
9 | // Jenkinsfile
10 | @Library(['terraform-pipeline@v3.10']) _
11 |
12 | Jenkinsfile.init(this, env)
13 |
14 | AnsiColorPlugin.init() // Decorate your TerraformEnvironmentStages with the AnsiColor plugin
15 |
16 | def validate = new TerraformValidateStage()
17 |
18 | def deployQa = new TerraformEnvironmentStage('qa')
19 | def deployUat = new TerraformEnvironmentStage('uat')
20 | def deployProd = new TerraformEnvironmentStage('prod')
21 |
22 | validate.then(deployQa)
23 | .then(deployUat)
24 | .then(deployProd)
25 | .build()
26 | ```
27 |
--------------------------------------------------------------------------------
/docs/AwssumePlugin.md:
--------------------------------------------------------------------------------
1 | ## [AwssumePlugin](../src/AwssumePlugin.groovy)
2 |
3 | Enable this plugin to wrap your terraform commands with the [awssume](https://github.com/manheim/awssume) gem, allowing you to assume roles across accounts.
4 |
5 | One-time setup:
6 | * Install the awssume gem on your Jenkins slaves.
7 | * Optional: Define global variables that match your environment name, to a role across all pipelines with that environment:
8 | * QA_AWS_ROLE_ARN (all 'qa' environments will assume the role specified by this variable)
9 | * UAT_AWS_ROLE_ARN (all 'uat' environments will assume the role specified by this variable)
10 | * PROD_AWS_ROLE_ARN (all 'prod' environments will assume the role specified by this variable)
11 |
12 | Awssume will assume the role for any environment where a `AWS_ROLE_ARN` is defined, or for any environment that matches a global `_AWS_ROLE_ARN`. If neither variables are specified, the use of Awssume will be skipped.
13 |
14 | ```
15 | // Jenkinsfile
16 | @Library(['terraform-pipeline@v3.10']) _
17 |
18 | Jenkinsfile.init(this, env)
19 |
20 | AwssumePlugin.init() // Decorate your TerraformEnvironmentStages with the Awssume plugin
21 |
22 | def validate = new TerraformValidateStage()
23 |
24 | // Run terraform apply and plan using the AWS Role defined by either AWS_ROLE_ARN or QA_AWS_ROLE_ARN
25 | def deployQA = new TerraformEnvironmentStage('qa')
26 |
27 | // Run terraform apply and plan using the AWS Role defined by either AWS_ROLE_ARN or UAT_AWS_ROLE_ARN
28 | def deployUat = new TerraformEnvironmentStage('uat')
29 |
30 | // Run terraform apply and plan using the AWS Role defined by either AWS_ROLE_ARN or PROD_AWS_ROLE_ARN
31 | def deployProd = new TerraformEnvironmentStage('prod')
32 |
33 | validate.then(deployQa)
34 | .then(deployUat)
35 | .then(deployProd)
36 | .build()
37 | ```
38 |
--------------------------------------------------------------------------------
/docs/BuildStage.md:
--------------------------------------------------------------------------------
1 | # BuildStage
2 |
3 | Some pipelines dealing with application code may to build deployment artifacts. The BuildStage can be used to accommodate this. Create a BuildStage then add it to your other linked stages in the appropriate position. Below is an example for building a deployment artifact which is later used to deploy to QA.
4 |
5 | Let terraform-pipline know which build artifacts to save and make available using the `saveArtifact` method. Artifacts that match the pattern passed to `saveArtifact` will automatically be stashed after BuildStage, and unstashed in each subsequent TerraformEnvironmentStage.
6 |
7 | ```
8 | // Jenkinsfile
9 | ...
10 | def build = new BuildStage().saveArtifact('*/target/MyApp.war')
11 |
12 | validate.then(build)
13 | .then(deployQa) // MyApp.war will be available in the working directory
14 | ...
15 | ```
16 |
--------------------------------------------------------------------------------
/docs/BuildWithParametersPlugin.md:
--------------------------------------------------------------------------------
1 | ## [BuildWithParametersPlugin](../src/BuildWithParametersPlugin.groovy)
2 |
3 | This plugin is enabled by default.
4 |
5 | Pipelines can prompt the user for parameters at build-time, with the Jenkinsfile "Build With Parameters" feature. This plugin lets you enable this feature, and prompt the user for parameters. If no parameters are configured, this plugin does nothing.
6 |
7 | Eg:
8 |
9 | ```
10 | @Library(['terraform-pipeline@v5.1']) _
11 |
12 | Jenkinsfile.init(this)
13 | BuildWithParametersPlugin.withBooleanParameter([
14 | name: 'PIPELINE_PREFERENCE',
15 | description: 'Do you like pipelines?',
16 | defaultValue: true
17 | ])
18 | BuildWithParametersPlugin.withStringParameter([
19 | name: 'PIPELINE_THOUGHTS',
20 | description: 'What do you think about pipelines?',
21 | defaultValue: 'They make deployments so easy'
22 | ])
23 |
24 | def validate = new TerraformValidateStage()
25 | def deployQa = new TerraformEnvironmentStage('qa')
26 | def deployUat = new TerraformEnvironmentStage('uat')
27 | def deployProd = new TerraformEnvironmentStage('prod')
28 |
29 | // At the start of the pipeline, the user with a checkbox and a string input
30 | // The user's responses are available in the environment variables
31 | // PIPELINE_PREFERENCE and PIPELINE_THOUGHTS
32 | validate.then(deployQa)
33 | .then(deployUat)
34 | .then(deployProd)
35 | .build()
36 | ```
37 |
--------------------------------------------------------------------------------
/docs/ConditionalApplyPlugin.md:
--------------------------------------------------------------------------------
1 | ## [ConditionalApplyPlugin](../src/ConditionalApplyPlugin.groovy)
2 |
3 | This plugin is enabled by default.
4 |
5 | By default, changes are applied through one and only one branch - main. The
6 | ConditionalApplyPlugin enforces this by making the "Confirm" and "Apply" steps
7 | of a TerraformEnvironmentStage visible only on the main or master branch. You can
8 | continue to use branches and PullRequests, however, branches and PullRequests
9 | will only run the Plan step for each environment, and skip over the
10 | Confirm/Apply steps.
11 |
12 | This behavior can be changed by using `ConditionalApplyPlugin.withApplyOnBranch()`. This method accepts one or more branches. "Confirm" and "Apply" steps of TerraformEnvironmentStage will then be visible for each of the specified branches. Any branch or PullRequest not in that list will only run the Plan step for each environment, and skip over the Confirm/Apply steps.
13 |
14 | Example:
15 |
16 | ```
17 | @Library(['terraform-pipeline']) _
18 |
19 | Jenkinsfile.init(this)
20 | ConditionalApplyPlugin.withApplyOnBranch('myMainReplacement')
21 |
22 | def validate = new TerraformValidateStage()
23 | // 'qa' stage will run Plan/Confirm/Apply on the 'myMainReplacement' branch.
24 | // 'qa' stage will only run Plan for all other branches and PullRequests.
25 | def deployQa = new TerraformEnvironmentStage('qa')
26 | // 'uat' stage will run Plan/Confirm/Apply on 'myMainReplacement' branch.
27 | // 'uat' stage will only run Plan for all other branches and PullRequests.
28 | def deployUat = new TerraformEnvironmentStage('uat')
29 | // 'prod' stage will run Plan/Confirm/Apply on 'myMainReplacement' branch.
30 | // 'prod' stage will only run Plan for all other branches and PullRequests.
31 | def deployProd = new TerraformEnvironmentStage('prod')
32 |
33 | validate.then(deployQa)
34 | .then(deployUat)
35 | .then(deployProd)
36 | .build()
37 | ...
38 | ```
39 |
40 | Alternatively, enable "Confirm" and "Apply" for specific environments with `ConditionalApplyPlugin.withApplyOnEnvironment()`. This method accepts one or more environment names. "Confirm" and "Apply" steps of TerraformEnvironmentStage will then be visible for each of the specified environments, regardless of the branch or PullRequest.
41 |
42 | Example:
43 |
44 | ```
45 | @Library(['terraform-pipeline']) _
46 |
47 | Jenkinsfile.init(this)
48 | ConditionalApplyPlugin.withApplyOnEnvironment('qa')
49 |
50 | def validate = new TerraformValidateStage()
51 | // 'qa' stage will run Plan/Confirm/Apply on all branches and PullRequests.
52 | def deployQa = new TerraformEnvironmentStage('qa')
53 | // 'uat' stage will run Plan/Confirm/Apply only on main, and will only run Plan on all other branches and PullRequests.
54 | def deployUat = new TerraformEnvironmentStage('uat')
55 | // 'prod' stage will run Plan/Confirm/Apply only on main, and will only run Plan on all other branches and PullRequests.
56 | def deployProd = new TerraformEnvironmentStage('prod')
57 |
58 | validate.then(deployQa)
59 | .then(deployUat)
60 | .then(deployProd)
61 | .build()
62 | ...
63 | ```
64 |
65 | Disable this plugin, if you want to allow "Confirm" and "Apply" on any branch or PullRequest.
66 |
67 | ```
68 | @Library(['terraform-pipeline']) _
69 |
70 | Jenkinsfile.init(this)
71 | ConditionalApplyPlugin.disable()
72 | ...
73 | ```
74 |
--------------------------------------------------------------------------------
/docs/ConfirmApplyPlugin.md:
--------------------------------------------------------------------------------
1 | ## [ConfirmApplyPlugin](../src/ConfirmApplyPlugin.groovy)
2 |
3 | This plugin is enabled by default.
4 |
5 | It's a good practice to review the terraform plan before applying changes to any environment, to confirm that the changes that are being applied are the same changes that are expected. The ConfirmApplyPlugin will pause your pipeline after the Plan step, allowing a human to review the changes. A human must then manually Confirm the changes by clicking on the pipeline, before the changes are applied.
6 |
7 | The pipeline will pause for a limited amount of time - 15 minutes. Once that timeout is exceeded, that particular pipeline run will be canceled.
8 |
9 | This functionality of this plugin can be disabled with the following configuration:
10 |
11 | ```
12 | ConfirmApplyPlugin.disable()
13 |
--------------------------------------------------------------------------------
/docs/ConsulBackendPlugin.md:
--------------------------------------------------------------------------------
1 | ## [ConsulBackendPlugin](../src/ConsulBackendPlugin.groovy)
2 |
3 | Enable this plugin to store state in Consul.
4 |
5 | Terraform state can be stored in consul with the following configuration:
6 |
7 | ```
8 | # main.tf
9 |
10 | terraform {
11 | backend "consul" { }
12 | }
13 | ```
14 |
15 | See: https://www.terraform.io/docs/backends/types/consul.html
16 |
17 | The configuration above still requires you to tell Consul the path where environment states should be managed, but hardcoding that value into your terraform template prevents you from reusing the same template across all your environments. Ideally, you would provide a variable for the path for each environment, but terraform treats backend configuration as special, and you can't use normal variables. Instead terraform provides a separate `-backend-config` flag for `terraform init` to configure different backends (See: https://www.terraform.io/docs/backends/config.html#partial-configuration).
18 |
19 | By enabling this plugin, each environment state will automatically be given a unique consul path to store state, in the form `-backend-config=path=terraform///`.
20 |
21 | ```
22 | // Jenkinsfile
23 | @Library(['terraform-pipeline@v5.0']) _
24 |
25 | Jenkinsfile.init(this)
26 |
27 | ConsulBackendPlugin.init()
28 |
29 | def validate = new TerraformValidateStage()
30 |
31 | // terraform init -backend-config=path=///qa
32 | def deployQa = new TerraformEnvironmentStage('qa')
33 |
34 | // terraform init -backend-config=path=///uat
35 | def deployUat = new TerraformEnvironmentStage('uat')
36 |
37 | // terraform init -backend-config=path=///prod
38 | def deployProd = new TerraformEnvironmentStage('prod')
39 |
40 | validate.then(deployQA)
41 | .then(deployUat)
42 | .then(deployProd)
43 | .build()
44 | ```
45 |
--------------------------------------------------------------------------------
/docs/CredentialsPlugin.md:
--------------------------------------------------------------------------------
1 | ## [CredentialsPlugin](../src/CredentialsPlugin.groovy)
2 |
3 | Enable this plugin to inject credentials into your stages using the [Jenkins Credentials Plugin](https://wiki.jenkins.io/display/JENKINS/Credentials+Plugin).
4 |
5 | One-time setup:
6 | * Install the [Jenkins Credentials Binding Plugin](https://www.jenkins.io/doc/pipeline/steps/credentials-binding/) on your Jenkins server.
7 | * Define a credential that you want to inject.
8 |
9 | Add any number of credentials bindings that you want to wrap your stages, with `withBinding`. Each call to this method will cumulatively add more credentials. See the [Credentials Binding Plugin homepage](https://www.jenkins.io/doc/pipeline/steps/credentials-binding/) for the list of supported bindings.
10 |
11 | ```
12 | // Jenkinsfile
13 | @Library(['terraform-pipeline@v5.0']) _
14 |
15 | Jenkinsfile.init(this)
16 |
17 | // Add credentials to all Stages from usernamePassword, usernameColonPassword, and string credentials
18 | CredentialsPlugin.withBinding { usernamePassword(credentialsId: 'my-user-pass', usernameVariable: 'USERNAME', passwordVariable: 'PASSWORD') }
19 | .withBinding { usernameColonPassword(credentialsId: 'my-user-colon-pass', variable: 'USERPASS') }
20 | .withBinding { string(credentialsId: 'my-string-token', variable: 'TOKEN') }
21 | .init()
22 |
23 | def validate = new TerraformValidateStage()
24 | def build = new BuildStage()
25 | def deployQa = new TerraformEnvironmentStage('qa')
26 | def testQa = new RegressionStage()
27 | def deployUat = new TerraformEnvironmentStage('uat')
28 | def deployProd = new TerraformEnvironmentStage('prod')
29 |
30 | validate.then(build)
31 | .then(deployQa)
32 | .then(testQa)
33 | .then(deployUat)
34 | .then(deployProd)
35 | .build()
36 | ```
37 |
--------------------------------------------------------------------------------
/docs/DefaultEnvironmentPlugin.md:
--------------------------------------------------------------------------------
1 | ## [DefaultEnvironmentPlugin](../src/DefaultEnvironmentPlugin.groovy)
2 |
3 | This plugin is enabled by default.
4 |
5 | Provides a terraform variable `environment` to all TerraformEnvironmentStages by default, by setting an environment variable in the form `TF_VAR_environment`. The value of `environment` is the same as the environment value that you passed when creating an instance of the TerraformEnvironmentStage.
6 |
7 |
--------------------------------------------------------------------------------
/docs/DestroyPlugin.md:
--------------------------------------------------------------------------------
1 | ## [DestroyPlugin](../src/DestroyPlugin.groovy)
2 |
3 | Enable this plugin to use `terraform destroy` to destroy your environment(s).
4 |
5 | When this plugin is enabled, the pipeline will follow these steps:
6 | 1. Run a `terraform plan -destroy` to display which resources will get destroyed.
7 | 2. Ask for human confirmation to proceed with the destroy.
8 | 3. Run the `terraform destroy` command.
9 |
10 |
11 | ```
12 | // Jenkinsfile
13 | @Library(['terraform-pipeline@v3.10']) _
14 |
15 | Jenkinsfile.init(this, env)
16 |
17 | // This enables the destroy functionality
18 | DestroyPlugin.init()
19 |
20 | def validate = new TerraformValidateStage()
21 |
22 | def destroyQa = new TerraformEnvironmentStage('qa')
23 | def destroyUat = new TerraformEnvironmentStage('uat')
24 | def destroyProd = new TerraformEnvironmentStage('prod')
25 |
26 | validate.then(destroyQa)
27 | .then(destroyUat)
28 | .then(destroyProd)
29 | .build()
30 | ```
31 |
32 | When using this plugin, your pipeline will look something like this:
33 |
34 | 
35 |
36 | ## Adding arguments to the destroy command
37 |
38 | You can use `withArgument("-some-arg")` to add arguments to the `terraform destroy` command.
39 | ```
40 | // Jenkinsfile
41 | @Library(['terraform-pipeline@v3.10']) _
42 |
43 | Jenkinsfile.init(this, env)
44 |
45 | // This enables the destroy functionality
46 | // Set refresh to false for destroy command
47 | DestroyPlugin.withArgument("-refresh=false").init()
48 |
49 | ...
50 | ```
51 |
--------------------------------------------------------------------------------
/docs/FileParametersPlugin.md:
--------------------------------------------------------------------------------
1 | ## [FileParametersPlugin](../src/FileParametersPlugin.groovy)
2 |
3 | Enable this plugin to inject variables from a local properties file.
4 |
5 | The properties file should have variables in the form `KEY=VALUE`, with each variable on its own line. Values can reference other existing environment variables defined elsewhere, using the [Jenkinsfile `env` variable](https://jenkins.io/doc/book/pipeline/jenkinsfile/#using-environment-variables), and [Groovy string interpolation](http://docs.groovy-lang.org/latest/html/documentation/#_string_interpolation). Eg: `DATABASE_URL=${env.QA_DATABASE_URL}`.
6 |
7 | You should not store sensitive data in these properties files. Instead, sensitive data should be stored with the [CredentialsPlugin](./src/CredentialsPlugin.groovy), [ParameterStoreBuildWrapperPlugin](#parameterstorebuildwrapperplugin) with encryption, or any other tool that supports safe storage.
8 |
9 | ```
10 | // Jenkinsfile
11 | @Library(['terraform-pipeline@v5.0']) _
12 |
13 | Jenkinsfile.init(this)
14 |
15 | FileParametersPlugin.init() // Enable FileParametersPlugin
16 |
17 | def validate = new TerraformValidateStage()
18 |
19 | // Inject all parameters in qa.properties
20 | def deployQA = new TerraformEnvironmentStage('qa')
21 |
22 | // Inject all parameters in uat.properties
23 | def deployUat = new TerraformEnvironmentStage('uat')
24 |
25 | // Inject all parameters in prod.properties
26 | def deployProd = new TerraformEnvironmentStage('prod')
27 |
28 | validate.then(deployQA)
29 | .then(deployUat)
30 | .then(deployProd)
31 | .build()
32 | ```
33 |
--------------------------------------------------------------------------------
/docs/GithubPRPlanPlugin.md:
--------------------------------------------------------------------------------
1 | ## [GithubPRPlanPlugin](../src/GithubPRPlanPlugin.groovy)
2 |
3 | Use this to post Terraform plan results in the comments of a PR.
4 |
5 | One-Time Setup:
6 | * Your pipeline should be configured as a Multibranch Pipeline with Github Repository HTTPS URL.
7 | * Create a Github Personal Access Token that has permission to comment on your repo. By default, the a Github Personal Access Token is expected to be available in an environment variable `GITHUB_TOKEN`. A number of [credential and configuration management plugins](https://github.com/manheim/terraform-pipeline#credentials-and-configuration-management) are available to do this. Optionally, your Github Authentication token can be assigned to a different environment variable using `.withGithubTokenEnvVar()`
8 |
9 | ```
10 | @Library(['terraform-pipeline']) _
11 |
12 | Jenkinsfile.init(this)
13 |
14 | // A GITHUB_TOKEN environment variable should contain your Github PAT
15 | GithubPRPlanPlugin.init()
16 |
17 | // After creating a PullRequest, the plan results for each
18 | // environment are posted as a comment to the PullRequest.
19 | def validate = new TerraformValidateStage()
20 | def deployQa = new TerraformEnvironmentStage('qa')
21 | def deployUat = new TerraformEnvironmentStage('uat')
22 | def deployProd = new TerraformEnvironmentStage('prod')
23 |
24 | validate.then(deployQa)
25 | .then(deployUat)
26 | .then(deployProd)
27 | .build()
28 | ```
29 |
30 | ## Changing Repo Host
31 |
32 | By default, this plugin will examime the SCM URL configured for a given pipeline to determine how to post comments.
33 | If you wish to change the repo host url, use the `withRepoHost()` option.
34 |
35 | Example:
36 | ```
37 | ...
38 |
39 | // Change the repo host
40 | GithubPRPlanPlugin.withRepoHost("https://mygithubenterprise.company.com")
41 | .init()
42 | ...
43 | ~``
--------------------------------------------------------------------------------
/docs/ParameterStoreBuildWrapperPlugin.md:
--------------------------------------------------------------------------------
1 | ## [ParameterStoreBuildWrapperPlugin](../src/ParameterStoreBuildWrapperPlugin.groovy)
2 |
3 | Enable this plugin to inject variables using the [AWS Parameter Store Build Wrapper Plugin](https://plugins.jenkins.io/aws-parameter-store)
4 |
5 | One-time step:
6 | * Install the AWS Parameter Store Build Wrapper Plugin on your Jenkins server
7 | * For cross-account deployments, create an AWS Credential with the id '<ENVIRONMENT>_PARAMETER_STORE_ACCESS' that provides access to ParameterStore for that account.
8 |
9 | By default, parameters will be retrieved from the ParameterStore path constructed from your project's Git Organization, Git Repository name, and environment. Eg: If my terraform project were at https://github.com/Manheim/fake-terraform-project, then my 'qa' environment would receive parameters from the ParameterStore path '/Manheim/fake-terraform-project/qa'.
10 |
11 | ```
12 | // Jenkinsfile
13 | @Library(['terraform-pipeline@v5.0']) _
14 |
15 | Jenkinsfile.init(this)
16 |
17 | ParameterStoreBuildWrapperPlugin.init() // Enable ParameterStoreBuildWrapperPlugin
18 |
19 | def validate = new TerraformValidateStage()
20 |
21 | // Inject all parameters in ///qa
22 | def deployQA = new TerraformEnvironmentStage('qa')
23 |
24 | // Inject all parameters in ///uat
25 | def deployUat = new TerraformEnvironmentStage('uat')
26 |
27 | // Inject all parameters in ///prod
28 | def deployProd = new TerraformEnvironmentStage('prod')
29 |
30 | validate.then(deployQA)
31 | .then(deployUat)
32 | .then(deployProd)
33 | .build()
34 | ```
35 |
36 | Optionally, you can override the default ParameterStore path with your own custom pattern.
37 |
38 | ```
39 | // Jenkinsfile
40 | @Library(['terraform-pipeline@v5.0']) _
41 |
42 | Jenkinsfile.init(this)
43 |
44 | // Enable ParameterStoreBuildWrapperPlugin with a custom path pattern
45 | ParameterStoreBuildWrapperPlugin.withPathPattern { options -> "/${options['organization']}/${options['environment']}/${options['repoName']}" }
46 | .init()
47 |
48 | def validate = new TerraformValidateStage()
49 |
50 | // Inject all parameters in //qa/
51 | def deployQA = new TerraformEnvironmentStage('qa')
52 |
53 | // Inject all parameters in //uat/
54 | def deployUat = new TerraformEnvironmentStage('uat')
55 |
56 | // Inject all parameters in //prod/
57 | def deployProd = new TerraformEnvironmentStage('prod')
58 |
59 | validate.then(deployQA)
60 | .then(deployUat)
61 | .then(deployProd)
62 | .build()
63 | ```
64 |
65 |
66 | You can also optionally support Global Parameters applied to the following stages `VALIDATE`, `PLAN`, `APPLY`
67 |
68 |
69 | ```
70 | // Jenkinsfile
71 | @Library(['terraform-pipeline@v5.0']) _
72 |
73 | Jenkinsfile.init(this)
74 |
75 | ParameterStoreBuildWrapperPlugin.withGlobalParameter('/somePath/') // get all keys under `/somePath/`
76 | .withGlobalParameter('/someOtherPath/', [naming: 'relative', recursive: true]) // get all keys recursively under `/someOtherPath/`
77 | .init() // Enable ParameterStoreBuildWrapperPlugin
78 |
79 | def validate = new TerraformValidateStage()
80 |
81 | // Inject all parameters in ///qa
82 | def deployQA = new TerraformEnvironmentStage('qa')
83 |
84 | // Inject all parameters in ///uat
85 | def deployUat = new TerraformEnvironmentStage('uat')
86 |
87 | // Inject all parameters in ///prod
88 | def deployProd = new TerraformEnvironmentStage('prod')
89 |
90 | validate.then(deployQA)
91 | .then(deployUat)
92 | .then(deployProd)
93 | .build()
94 | ```
--------------------------------------------------------------------------------
/docs/ParameterStoreExecPlugin.md:
--------------------------------------------------------------------------------
1 | ## [ParameterStoreExecPlugin](../src/ParameterStoreExecPlugin.groovy)
2 |
3 | Enable this plugin to inject variables from AWS ParameterStore using [parameter-store-exec](https://github.com/cultureamp/parameter-store-exec).
4 |
5 | Prefer using the [ParameterStoreBuildWrapperPlugin](#ParameterStoreBuildWrapperPlugin) above if possible.
6 |
7 | One-time setup:
8 | * Install parameter-store-exec on your Jenkins slaves.
9 |
10 | By default, parameters will be retrieved from the ParameterStore path constructed from your project's Git Organization, Git Repository name, and environment. Eg: If my terraform project were at https://github.com/Manheim/fake-terraform-project, then my 'qa' environment would receive parameters from the ParameterStore path '/Manheim/fake-terraform-project/qa'.
11 |
12 | ```
13 | // Jenkinsfile
14 | @Library(['terraform-pipeline@v']) _
15 |
16 | Jenkinsfile.init(this)
17 |
18 | ParameterStoreExecPlugin.init() // Enable ParameterStoreExecPlugin
19 |
20 | def validate = new TerraformValidateStage()
21 |
22 | // Inject all parameters in ///qa with parameter-store-exec
23 | def deployQA = new TerraformEnvironmentStage('qa')
24 |
25 | // Inject all parameters in ///uat with parameter-store-exec
26 | def deployUat = new TerraformEnvironmentStage('uat')
27 |
28 | // Inject all parameters in ///prod with parameter-store-exec
29 | def deployProd = new TerraformEnvironmentStage('prod')
30 |
31 | validate.then(deployQA)
32 | .then(deployUat)
33 | .then(deployProd)
34 | .build()
35 | ```
36 |
--------------------------------------------------------------------------------
/docs/PassPlanFilePlugin.md:
--------------------------------------------------------------------------------
1 | ## [PassPlanFilePlugin](../src/PassPlanFilePlugin.groovy)
2 |
3 | Enable this plugin to pass the plan file output to `terraform apply`.
4 |
5 | This plugin stashes the plan file during the `plan` step.
6 | When `apply` is called, the plan file is unstashed and passed as an argument.
7 |
8 | For stash and unstash commands, you can either specify a directory when initializing the plugin, or it will default to the `./` directory.
9 |
10 | ```
11 | // Jenkinsfile
12 | @Library(['terraform-pipeline@v3.10']) _
13 |
14 | Jenkinsfile.init(this, env)
15 |
16 | // Pass the plan file to 'terraform apply'
17 | PassPlanFilePlugin.init()
18 |
19 | def validate = new TerraformValidateStage()
20 |
21 | def destroyQa = new TerraformEnvironmentStage('qa')
22 | def destroyUat = new TerraformEnvironmentStage('uat')
23 | def destroyProd = new TerraformEnvironmentStage('prod')
24 |
25 | validate.then(destroyQa)
26 | .then(destroyUat)
27 | .then(destroyProd)
28 | .build()
29 | ```
30 |
31 | ### TerraformDirectoryPlugin Changes
32 |
33 | If you are using the TerraformDirectoryPlugin, you must specify the same directory to support stash and unstash.
34 |
35 | ```
36 | // Jenkinsfile
37 | @Library(['terraform-pipeline@v3.10']) _
38 |
39 | Jenkinsfile.init(this, env)
40 |
41 | // When using TerraformDirectoryPlugin,
42 | // Pass the plan file to 'terraform apply' using withDirectory
43 | PassPlanFilePlugin.withDirectory('./tf/').init()
44 | TerraformDirectoryPlugin.withDirectory('./tf/').init()
45 |
46 |
47 | def validate = new TerraformValidateStage()
48 |
49 | def destroyQa = new TerraformEnvironmentStage('qa')
50 | def destroyUat = new TerraformEnvironmentStage('uat')
51 | def destroyProd = new TerraformEnvironmentStage('prod')
52 |
53 | validate.then(destroyQa)
54 | .then(destroyUat)
55 | .then(destroyProd)
56 | .build()
57 | ```
58 |
--------------------------------------------------------------------------------
/docs/PlanOnlyPlugin.md:
--------------------------------------------------------------------------------
1 | ## [PlanOnlyPlugin](../src/PlanOnlyPlugin.groovy)
2 |
3 | Enable this plugin to add a parameter to the build which will restrict pipeline functionality to `terraform plan` only.
4 |
5 | ```
6 | // Jenkinsfile
7 | @Library(['terraform-pipeline@v3.10']) _
8 |
9 | Jenkinsfile.init(this, env)
10 |
11 | // This enables the "plan only" functionality
12 | PlanOnlyPlugin.init()
13 |
14 | def validate = new TerraformValidateStage()
15 |
16 | def destroyQa = new TerraformEnvironmentStage('qa')
17 | def destroyUat = new TerraformEnvironmentStage('uat')
18 | def destroyProd = new TerraformEnvironmentStage('prod')
19 |
20 | validate.then(destroyQa)
21 | .then(destroyUat)
22 | .then(destroyProd)
23 | .build()
24 | ```
25 |
--------------------------------------------------------------------------------
/docs/RegressionStage.md:
--------------------------------------------------------------------------------
1 | # RegressionStage
2 |
3 | Some pipelines dealing with application code may need to run automation tests. The RegressionStage can be used to accommodate this. Create a RegressionStage then add it to your other linked stages in the appropriate position. Below is an example for running tests after a QA deployment:
4 | ```
5 | // Jenkinsfile
6 | ...
7 | def testQa = new RegressionStage()
8 |
9 | validate.then(deployQa)
10 | .then(testQa)
11 | ...
12 | ```
13 |
14 | By default, the RegressionStage will execute a `./bin/test.sh` script. If you wish to use a different path or script name, you can do so by passing it into the RegressionStage constructor:
15 | ```
16 | // Jenkinsfile
17 | ...
18 | def testQa = new RegressionStage('./some/other/location/some_other_test.sh')
19 |
20 | validate.then(deployQa)
21 | .then(testQa)
22 | ...
23 | ```
24 |
25 | If your test suite lives outside of the code, you can call `withScm()` and pass it the git URL of your automation test:
26 | ```
27 | // Jenkinsfile
28 | ...
29 | def testQa = new RegressionStage().withScm('git@SomeHost:SomeTeam/SomeAutomationRepository.git')
30 | validate.then(deployQa)
31 | .then(testQa)
32 | ...
33 |
34 | ```
35 |
36 | There could be instances where multiple repositories are required to execute automation tests (such as needing to clone application code as well as automation code). When calling `withScm()` more than once, the repositories become cumulative and it will check out all repositories into their respective subdirectories (named by the project name referenced by the git url). Below is an example of a scenario where the application repository and automation repository are both required. The test script to execute is also located in a subdirectory which we can use `changeDirectory()` so that we are in the appropriate directory.
37 |
38 | ```
39 | // Jenkinsfile
40 | ...
41 | def testQa = new RegressionStage().withScm('git@SomeHost:SomeTeam/SomeAutomationRepository.git')
42 | .withScm('git@SomeHost:SomeTeam/SomeApplicationRepository.git')
43 | .changeDirectory('SomeAutomationRespository')
44 | validate.then(deployQa)
45 | .then(testQa)
46 | ...
47 | ```
48 | The above example will check out `git@SomeHost:SomeTeam/SomeAutomationRepository.git` into a subdirectory named `SomeAutomationRepository` and `git@SomeHost:SomeTeam/SomeApplicationRepository.git` into a subdirectory named `SomeApplicationRepository`.
49 |
50 | Note that if withScm is only called once, there will not be a subdirectory and it will check out the repository at the root.
51 |
52 |
--------------------------------------------------------------------------------
/docs/TargetPlugin.md:
--------------------------------------------------------------------------------
1 | ## [TargetPlugin](../src/TargetPlugin.groovy)
2 |
3 | Enable this plugin to run plan/apply on selective resource targets
4 |
5 | ```
6 | // Jenkinsfile
7 | @Library(['terraform-pipeline']) _
8 |
9 | Jenkinsfile.init(this, env)
10 | TargetPlugin.init() // Optionally limit plan/apply to specific targets
11 |
12 |
13 | def validate = new TerraformValidateStage()
14 |
15 | def deployQa = new TerraformEnvironmentStage('qa')
16 | def deployUat = new TerraformEnvironmentStage('uat')
17 | def deployProd = new TerraformEnvironmentStage('prod')
18 |
19 |
20 | // Pipeline can now be built with "Build with Parameters"
21 | // New 'target' option in parameter list can limit plan/apply to specific targets
22 | // By default, builds should skip the 'target' option, and build as-normal
23 | validate.then(deployQa)
24 | .then(deployUat)
25 | .then(deployProd)
26 | .build()
27 | ```
28 |
--------------------------------------------------------------------------------
/docs/TerraformDirectoryPlugin.md:
--------------------------------------------------------------------------------
1 | ## [TerraformDirectoryPlugin](../src/TerraformDirectoryPlugin.groovy)
2 |
3 | This plugin allows Terraform to run in a specific directory so that the number of files at the root of any given project can be limited.
4 |
5 | It works by appending `-chdir=` before any Terraform sub-command (`init`, `plan`, etc.) run by terraform-pipeline. You can either specify a directory when initializing the plugin, or it will default to the `./terraform/` directory.
6 |
7 | ### Terraform Version Notes
8 | Previous versions of this plugin would append the directory to the end of the Terraform commands. This has been removed as of Terraform 0.15 and replaced with the `-chdir` argument.
9 |
10 | ```
11 | // Jenkinsfile
12 | @Library(['terraform-pipeline@v5.0']) _
13 |
14 | Jenkinsfile.init(this)
15 |
16 | // Using withDirectory to initialize here would cause all terraform
17 | // commands to run in the ./xyz/ directory
18 | TerraformDirectoryPlugin.withDirectory('./xyz/').init()
19 |
20 | def validate = new TerraformValidateStage()
21 |
22 | def deployQA = new TerraformEnvironmentStage('qa')
23 | def deployUat = new TerraformEnvironmentStage('uat')
24 | def deployProd = new TerraformEnvironmentStage('prod')
25 |
26 | validate.then(deployQA)
27 | .then(deployUat)
28 | .then(deployProd)
29 | .build()
30 | ```
31 |
--------------------------------------------------------------------------------
/docs/TerraformEnvironmentStage.md:
--------------------------------------------------------------------------------
1 | ## [TerraformEnvironmentStage](../src/TerraformEnvironmentStage.groovy)
2 |
3 | ### Pass in Global Environment variable
4 |
5 | You can pass in global environment variables too all stages in your pipeline using `withGlobalEnv`
6 |
7 | ```
8 | // Jenkinsfile
9 | @Library(['terraform-pipeline']) _
10 |
11 | Jenkinsfile.init(this)
12 |
13 | TerraformEnvironmentStage.withGlobalEnv('KEY_01', 'VALUE_01')
14 | .withGlobalEnv('KEY_01', 'VALUE_02')
15 |
16 | def validate = new TerraformValidateStage()
17 | def deployQA = new TerraformEnvironmentStage('qa')
18 | def deployProd = new TerraformEnvironmentStage('prod')
19 |
20 | validate.then(deployQA)
21 | .then(deployProd)
22 | .build()
23 | ```
24 |
25 | ### Pass in Stage Specific Environment variable
26 |
27 | You can pass in stage specific environment variables each stage in your pipeline using `withEnv`
28 |
29 | ```
30 | // Jenkinsfile
31 | @Library(['terraform-pipeline']) _
32 |
33 | Jenkinsfile.init(this)
34 |
35 | def validate = new TerraformValidateStage()
36 | def deployQA = new TerraformEnvironmentStage('qa').withEnv('KEY_01', 'VALUE_01')
37 | .withEnv('KEY_02', 'VALUE_02')
38 | def deployProd = new TerraformEnvironmentStage('prod').withEnv('KEY_03', 'VALUE_03')
39 | .withEnv('KEY_04', 'VALUE_04')
40 |
41 | validate.then(deployQA)
42 | .then(deployProd)
43 | .build()
44 | ```
45 |
46 | ### For Plugin Developers
47 |
48 | #### Decorate Stages/Commands or Hook before/after command execution
49 |
50 | TerraformEnvironmentStage has support for numerous decorators, to allow wrapping the various stages, steps, and individual commands. Please see the [source code](../src/TerraformEnvironmentStage.groovy) for details.
51 |
--------------------------------------------------------------------------------
/docs/TerraformEnvironmentStageShellHookPlugin.md:
--------------------------------------------------------------------------------
1 | ## [TerraformEnvironmentStageShellHookPlugin](../src/TerraformEnvironmentStageShellHookPlugin.groovy)
2 |
3 | This plugin allows inserting shell script hooks at various points in the TerraformEnvironmentStage execution. Please see the [TerraformEnvironmentStage source code](../src/TerraformEnvironmentStage.groovy) for a list of the supported hook points. At this time, they are:
4 |
5 | * ``TerraformEnvironmentStage.ALL``
6 | * ``TerraformEnvironmentStage.INIT_COMMAND``
7 | * ``TerraformEnvironmentStage.PLAN``
8 | * ``TerraformEnvironmentStage.PLAN_COMMAND``
9 | * ``TerraformEnvironmentStage.APPLY``
10 | * ``TerraformEnvironmentStage.APPLY_COMMAND``
11 |
12 | Each hook point "wraps" various parts of the Stage, and supports a total of four hooks:
13 |
14 | * Before the wrapped code runs (``WhenToRun.BEFORE``)
15 | * **The default,** after the wrapped code runs successfully (``WhenToRun.ON_SUCCESS``)
16 | * After the wrapped code runs and fails (``WhenToRun.ON_FAILURE``)
17 | * After the wrapped code runs, regardless of success or failure (``WhenToRun.AFTER``)
18 |
19 | ```
20 | // Jenkinsfile
21 | @Library(['terraform-pipeline']) _
22 |
23 | Jenkinsfile.init(this)
24 |
25 | TerraformEnvironmentStageShellHookPlugin.withHook(TerraformEnvironmentStage.APPLY_COMMAND, './bin/after_successful_apply.sh')
26 | .withHook(TerraformEnvironmentStage.INIT_COMMAND, './bin/download_deps.sh', WhenToRun.BEFORE)
27 | .withHook(TerraformEnvironmentStage.ALL, './bin/cleanup.sh', WhenToRun.AFTER)
28 | .init()
29 |
30 | def validate = new TerraformValidateStage()
31 |
32 | def deployQA = new TerraformEnvironmentStage('qa')
33 | def deployUat = new TerraformEnvironmentStage('uat')
34 | def deployProd = new TerraformEnvironmentStage('prod')
35 |
36 | validate.then(deployQA)
37 | .then(deployUat)
38 | .then(deployProd)
39 | .build()
40 | ```
41 |
42 | **Reminder:** Please remember that plugin definition order matters. You most likely want this plugin to be defined in your Jenkinsfile _before_ any plugins that affect the environment.
43 |
--------------------------------------------------------------------------------
/docs/TerraformImportPlugin.md:
--------------------------------------------------------------------------------
1 | ## [TerraformImportPlugin](../src/TerraformImportPlugin.groovy)
2 |
3 | Enable this plugin to change pipeline functionality. This plugin will import a
4 | resource into state, and adds two new job parameters.
5 |
6 | * `IMPORT_RESOURCE`: This is the resource identifier to import into state.
7 | * `IMPORT_TARGET_PATH`: This is the Terraform state path into which the
8 | resource should be imported.
9 | * `IMPORT_ENVIRONMENT`: This should be set to the terraform-pipeline
10 | environment stage that should perform the import.
11 |
12 | ```
13 | // Jenkinsfile
14 | @Library(['terraform-pipeline@v3.10']) _
15 |
16 | Jenkinsfile.init(this, env)
17 |
18 | // This enables the "import" functionality
19 | TerraformImportPlugin.init()
20 |
21 | def validate = new TerraformValidateStage()
22 |
23 | def qa = new TerraformEnvironmentStage('qa')
24 | def uat = new TerraformEnvironmentStage('uat')
25 | def prod = new TerraformEnvironmentStage('prod')
26 |
27 | validate.then(qa)
28 | .then(uat)
29 | .then(prod)
30 | .build()
31 | ```
32 |
--------------------------------------------------------------------------------
/docs/TerraformLandscapePlugin.md:
--------------------------------------------------------------------------------
1 | ## [TerraformLandscapePlugin](../src/TerraformLandscapePlugin.groovy)
2 |
3 | Enable this plugin to improve Terraform's plan output commands with the [terraform-landscape](https://github.com/coinbase/terraform-landscape) gem.
4 |
5 | One-time setup:
6 | * Install the terraform-landscape gem on your Jenkins slaves.
7 |
8 | Requirements:
9 | * Enable AnsiColorPlugin for colors in Jenkins
10 |
11 | ```
12 | // Jenkinsfile
13 | @Library(['terraform-pipeline@v3.10']) _
14 |
15 | Jenkinsfile.init(this, env)
16 |
17 | AnsiColorPlugin.init() // REQUIRED: Decorate your TerraformEnvironmentStages with the AnsiColor plugin
18 | TerraformLandscapePlugin.init() // Use the Terraform Landscape gem to format plan output
19 |
20 | def validate = new TerraformValidateStage()
21 |
22 | def deployQa = new TerraformEnvironmentStage('qa')
23 | def deployUat = new TerraformEnvironmentStage('uat')
24 | def deployProd = new TerraformEnvironmentStage('prod')
25 |
26 | validate.then(deployQa)
27 | .then(deployUat)
28 | .then(deployProd)
29 | .build()
30 | ```
31 |
--------------------------------------------------------------------------------
/docs/TerraformOutputOnlyPlugin.md:
--------------------------------------------------------------------------------
1 | ## [TerraformOutputOnlyPlugin](../src/TerraformOutputOnlyPlugin.groovy)
2 |
3 | Enable this plugin to change pipeline functionality. This plugin will skip the plan and apply stages and add three new job parameters.
4 |
5 | * `SHOW_OUTPUTS_ONLY`: This configures the job to skip execution of the plan and apply terraform commands. The job will perform the INIT stage and immediately perform `terraform output`. Unless this option is checked, the following options will have no effect.
6 | * `JSON_FORMAT_OUTPUTS`: This will instruct the plugin to display the output in JSON format.
7 | * `REDIRECT_OUTPUTS_TO_FILE`: Text entered into this option will be used to redirect the result of `terraform output` to a file in the current workspace. The filename should be relative to the workspace, and directories will NOT be created so they should exist beforehand.
8 |
9 | ```
10 | // Jenkinsfile
11 | @Library(['terraform-pipeline@v3.10']) _
12 |
13 | Jenkinsfile.init(this, env)
14 |
15 | // This enables the "output only" functionality
16 | TerraformOutputOnlyPlugin.init()
17 |
18 | def validate = new TerraformValidateStage()
19 |
20 | def destroyQa = new TerraformEnvironmentStage('qa')
21 | def destroyUat = new TerraformEnvironmentStage('uat')
22 | def destroyProd = new TerraformEnvironmentStage('prod')
23 |
24 | validate.then(destroyQa)
25 | .then(destroyUat)
26 | .then(destroyProd)
27 | .build()
28 | ```
29 |
--------------------------------------------------------------------------------
/docs/TerraformPlugin.md:
--------------------------------------------------------------------------------
1 | ## [TerraformPlugin](../src/TerraformPlugin.groovy)
2 |
3 | This plugin provides a code point for the library to apply different arguments
4 | and behaviors based on your expected terraform version.
5 |
6 | ### Usage
7 |
8 | This is a default plugin that is automatically added to all required commands
9 | and stages. As such, there is no `init()` method to call on the class.
10 |
11 | Instead, place a `.terraform-version` file in the root of your repository
12 | containing the version of terraform you require.
13 |
14 | If this is not possible, you may declare the version in your Jenkinsfile as
15 | follows:
16 |
17 | ```
18 | // Jenkinsfile
19 |
20 | Jenkinsfile.init(this)
21 |
22 | TerraformPlugin.withVersion('0.12.17')
23 | ```
24 |
25 | ### Development
26 |
27 | When a version change occurs that breaks with previous behavior, you will need
28 | to update this plugin to account for that. The process is fairly
29 | straightforward. Please see documentation in the
30 | [source code](../src/TerraformPlugin.groovy) for the plugin for further
31 | information.
32 |
--------------------------------------------------------------------------------
/docs/TerraformStartDirectoryPlugin.md:
--------------------------------------------------------------------------------
1 | ## [TerraformStartDirectoryPlugin](../src/TerraformStartDirectoryPlugin.groovy)
2 |
3 | This plugin changes the starting execution workspace directory. Commands will use this directory as current and any relative path will use it as base path.
4 |
5 | ```
6 | // Jenkinsfile
7 | @Library(['terraform-pipeline']) _
8 |
9 | Jenkinsfile.init(this)
10 |
11 | TerraformStartDirectoryPlugin.withDirectory('./xyz/').init()
12 |
13 | def validate = new TerraformValidateStage()
14 |
15 | def deployQA = new TerraformEnvironmentStage('qa')
16 | def deployUat = new TerraformEnvironmentStage('uat')
17 | def deployProd = new TerraformEnvironmentStage('prod')
18 |
19 | validate.then(deployQA)
20 | .then(deployUat)
21 | .then(deployProd)
22 | .build()
23 | ```
24 |
--------------------------------------------------------------------------------
/docs/TerraformTaintPlugin.md:
--------------------------------------------------------------------------------
1 | ## [TerraformTaintPlugin](../src/TerraformTaintPlugin.groovy)
2 |
3 | Enable this plugin to add `TAINT_RESOURCE` and `UNTAINT_RESOURCE` parameters
4 | to the build. If a resource path is provided via one of those parameters, then
5 | the `terraform plan` command will be preceded by the corresponding Terraform
6 | command to taint or untaint the appropriate resources.
7 |
8 | Note that the untaint command will take precedence, so if for some reason the
9 | same resource is placed in both parameters, it will be tainted and immediately
10 | untainted, resulting in no change.
11 |
12 | There are several ways to customize where and when the taint/untaint can run:
13 |
14 | * `onBranch()`: This takes in a branch name as a parameter. This adds the
15 | branch to the list of approved branches.
16 |
17 | ```
18 | // Jenkinsfile
19 | @Library(['terraform-pipeline@v3.10']) _
20 |
21 | Jenkinsfile.init(this, env)
22 |
23 | // This enables the "taint" and "untaint" functionality
24 | // It will only apply to the main branch (default behavior)
25 | TerraformTaintPlugin.init()
26 |
27 | def validate = new TerraformValidateStage()
28 |
29 | def destroyQa = new TerraformEnvironmentStage('qa')
30 | def destroyUat = new TerraformEnvironmentStage('uat')
31 | def destroyProd = new TerraformEnvironmentStage('prod')
32 |
33 | validate.then(destroyQa)
34 | .then(destroyUat)
35 | .then(destroyProd)
36 | .build()
37 | ```
38 |
--------------------------------------------------------------------------------
/docs/TfvarsFilesPlugin.md:
--------------------------------------------------------------------------------
1 | ## [TfvarsFilesPlugin](../src/TfvarsFilesPlugin.groovy)
2 |
3 | This plugin allows you to add `-var-file=${environment}.tfvars` to your plan
4 | and apply commands. It supports being configured for a directory relative to
5 | to the git root and global files being applied to every plan and apply command.
6 |
7 | For the following repository:
8 |
9 | ```
10 | .
11 | ├── Jenkinsfile
12 | ├── main.tf
13 | ├── variables.tf
14 | └── tfvars
15 | ├── qa.tfvars
16 | ├── uat.tfvars
17 | ├── prod.tfvars
18 | └── global.tfvars
19 | ```
20 | This Jenkinsfile setup will include a `-var-file` argument for both
21 | `qa.tfvars` and `global.tfvars` on the QA plan and apply commands. Similarly,
22 | the UAT commands will include `uat.tfvars` and `global.tfvars`.
23 |
24 | ```
25 | // Jenkinsfile
26 | @Library(['terraform-pipeline@v5.3']) _
27 |
28 | Jenkinsfile.init(this)
29 |
30 | TfvarsFilesPlugin.withDirectory('./tfvars')
31 | .withGlobalVarFile('global.tfvars')
32 | .init()
33 |
34 | def validate = new TerraformValidateStage()
35 |
36 | def deployQA = new TerraformEnvironmentStage('qa')
37 | def deployUat = new TerraformEnvironmentStage('uat')
38 | def deployProd = new TerraformEnvironmentStage('prod')
39 |
40 | validate.then(deployQA)
41 | .then(deployUat)
42 | .then(deployProd)
43 | .build()
44 | ```
45 |
--------------------------------------------------------------------------------
/docs/ValidateFormatPlugin.md:
--------------------------------------------------------------------------------
1 | ## [ValidateFormatPlugin](../src/ValidateFormatPlugin.groovy)
2 |
3 | Enable this plugin to run `terraform fmt -check` as part of the TerraformValidateStage. If no changes are necessary, TerraformValidateStage will pass. If any format changes are necessary, the TerraformValidateStage will fail.
4 |
5 | ```
6 | // Jenkinsfile
7 | @Library(['terraform-pipeline']) _
8 |
9 | Jenkinsfile.init(this)
10 |
11 | ValidateFormatPlugin.init()
12 |
13 | // Runs `terraform fmt -check` in addition to `terraform validate`.
14 | // TerraformValidateStage fails if code requires validation.
15 | def validate = new TerraformValidateStage()
16 | def deployQA = new TerraformEnvironmentStage('qa')
17 | def deployUat = new TerraformEnvironmentStage('uat')
18 | def deployProd = new TerraformEnvironmentStage('prod')
19 |
20 | validate.then(deployQA)
21 | .then(deployUat)
22 | .then(deployProd)
23 | .build()
24 | ```
25 |
26 | Additional options are available, to search directories recursively, and to display diffs.
27 |
28 | ```
29 | // Jenkinsfile
30 | @Library(['terraform-pipeline']) _
31 |
32 | Jenkinsfile.init(this)
33 |
34 | ValidateFormatPlugin.init()
35 | TerraformFormatCommand.withRecursive()
36 | .withDiff()
37 |
38 | // Runs `terraform fmt -check` in addition to `terraform validate`.
39 | // TerraformValidateStage fails if code requires validation.
40 | def validate = new TerraformValidateStage()
41 | def deployQA = new TerraformEnvironmentStage('qa')
42 | def deployUat = new TerraformEnvironmentStage('uat')
43 | def deployProd = new TerraformEnvironmentStage('prod')
44 |
45 | validate.then(deployQA)
46 | .then(deployUat)
47 | .then(deployProd)
48 | .build()
49 | ```
50 |
51 |
--------------------------------------------------------------------------------
/docs/WithAwsPlugin.md:
--------------------------------------------------------------------------------
1 | ## [WithAwsPlugin](../src/WithAwsPlugin.groovy)
2 |
3 | Enable this plugin to manage AWS Authentication with the [pipeline-aws-plugin](https://github.com/jenkinsci/pipeline-aws-plugin).
4 |
5 | One-time setup
6 |
7 | * Have the [pipeline-aws-plugin] installed on your Jenkins instance.
8 | * (Optional) Define an AWS_ROLE_ARN variable, or environment-specific `${env}_AWS_ROLE_ARN`
9 |
10 | Example pipeline using the WithAwsPlugin using an explicit role:
11 |
12 | ```
13 | // Jenkinsfile
14 | @Library(['terraform-pipeline@v4.3']) _
15 |
16 | Jenkinsfile.init(this)
17 |
18 | WithAwsPlugin.withRole(MY_ROLE_ARN).init()
19 |
20 | def validate = new TerraformValidateStage()
21 |
22 | // withAws(role: MY_ROLE_ARN)
23 | def deployQA = new TerraformEnvironmentStage('qa')
24 |
25 | // withAws(role: MY_ROLE_ARN)
26 | def deployUat = new TerraformEnvironmentStage('uat')
27 |
28 | // withAws(role: MY_ROLE_ARN)
29 | def deployProd = new TerraformEnvironmentStage('prod')
30 |
31 | validate.then(deployQa)
32 | .then(deployUat)
33 | .then(deployProd)
34 | .build()
35 | ```
36 |
37 | Example pipeline using the WithAwsPlugin using implicit roles:
38 |
39 | ```
40 | // Jenkinsfile
41 | @Library(['terraform-pipeline@v4.3']) _
42 |
43 | Jenkinsfile.init(this)
44 |
45 | WithAwsPlugin.withRole().init()
46 |
47 | def validate = new TerraformValidateStage()
48 |
49 | // withAws(role: AWS_ROLE_ARN) or withAws(role: QA_AWS_ROLE_ARN), where either AWS_ROLE_ARN or QA_AWS_ROLE_ARN are defined. Nothing if neither is defined.
50 | def deployQA = new TerraformEnvironmentStage('qa')
51 |
52 | // withAws(role: AWS_ROLE_ARN) or withAws(role: UAT_AWS_ROLE_ARN), where either AWS_ROLE_ARN or UAT_AWS_ROLE_ARN are defined. Nothing if neither is defined.
53 | def deployUat = new TerraformEnvironmentStage('uat')
54 |
55 | // withAws(role: AWS_ROLE_ARN) or withAws(role: PROD_AWS_ROLE_ARN), where either AWS_ROLE_ARN or PROD_AWS_ROLE_ARN are defined. Nothing if neither is defined.
56 | def deployProd = new TerraformEnvironmentStage('prod')
57 |
58 | validate.then(deployQa)
59 | .then(deployUat)
60 | .then(deployProd)
61 | .build()
62 | ```
63 |
64 | If you want to specify a role session duration other than the default of 1 hour (3600 seconds), you can do so by providing an integer duration to `withDuration()`:
65 |
66 | ```
67 | WithAwsPlugin.withDuration(43200).init()
68 | ```
69 |
70 | or, with a specific role ARN
71 |
72 | ```
73 | WithAwsPlugin.withRole('MY_ROLE_ARN').withDuration(43200).init()
74 | ```
75 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/manheim/terraform-pipeline/0b618f10644fb16025e6cc72599b2e16e74c2646/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.0-bin.zip
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/images/MultibranchPipeline.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/manheim/terraform-pipeline/0b618f10644fb16025e6cc72599b2e16e74c2646/images/MultibranchPipeline.png
--------------------------------------------------------------------------------
/images/NewItem.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/manheim/terraform-pipeline/0b618f10644fb16025e6cc72599b2e16e74c2646/images/NewItem.png
--------------------------------------------------------------------------------
/images/configure-project.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/manheim/terraform-pipeline/0b618f10644fb16025e6cc72599b2e16e74c2646/images/configure-project.png
--------------------------------------------------------------------------------
/images/custom-declarative-pipeline.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/manheim/terraform-pipeline/0b618f10644fb16025e6cc72599b2e16e74c2646/images/custom-declarative-pipeline.png
--------------------------------------------------------------------------------
/images/declarative-pipeline.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/manheim/terraform-pipeline/0b618f10644fb16025e6cc72599b2e16e74c2646/images/declarative-pipeline.png
--------------------------------------------------------------------------------
/images/default-pipeline-success.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/manheim/terraform-pipeline/0b618f10644fb16025e6cc72599b2e16e74c2646/images/default-pipeline-success.png
--------------------------------------------------------------------------------
/images/destroy-pipeline.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/manheim/terraform-pipeline/0b618f10644fb16025e6cc72599b2e16e74c2646/images/destroy-pipeline.png
--------------------------------------------------------------------------------
/images/import-terraform-pipeline.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/manheim/terraform-pipeline/0b618f10644fb16025e6cc72599b2e16e74c2646/images/import-terraform-pipeline.png
--------------------------------------------------------------------------------
/images/restart-from-stage-numbers.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/manheim/terraform-pipeline/0b618f10644fb16025e6cc72599b2e16e74c2646/images/restart-from-stage-numbers.png
--------------------------------------------------------------------------------
/images/restart-from-stage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/manheim/terraform-pipeline/0b618f10644fb16025e6cc72599b2e16e74c2646/images/restart-from-stage.png
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | /*
2 | * This file was generated by the Gradle 'init' task.
3 | *
4 | * The settings file is used to specify which projects to include in your build.
5 | *
6 | * Detailed information about configuring a multi-project build in Gradle can be found
7 | * in the user guide at https://docs.gradle.org/4.8.1/userguide/multi_project_builds.html
8 | */
9 |
10 | rootProject.name = 'terraform-pipeline'
11 |
--------------------------------------------------------------------------------
/src/AgentNodePlugin.groovy:
--------------------------------------------------------------------------------
1 | import static TerraformValidateStage.ALL
2 | import static TerraformEnvironmentStage.ALL
3 |
4 | public class AgentNodePlugin implements TerraformValidateStagePlugin, TerraformEnvironmentStagePlugin, Resettable {
5 | private static String dockerImage
6 | private static String dockerfile
7 | private static String dockerBuildOptions
8 | private static String dockerOptions
9 |
10 | AgentNodePlugin() { }
11 |
12 | public static void init() {
13 | def plugin = new AgentNodePlugin()
14 |
15 | TerraformValidateStage.addPlugin(plugin)
16 | TerraformEnvironmentStage.addPlugin(plugin)
17 | }
18 |
19 | public static withAgentDockerImage(String dockerImage) {
20 | this.dockerImage = dockerImage
21 | return this
22 | }
23 |
24 | public static withAgentDockerImageOptions(String dockerOptions) {
25 | this.dockerOptions = dockerOptions
26 | return this
27 | }
28 |
29 | public static withAgentDockerBuildOptions(String dockerBuildOptions) {
30 | this.dockerBuildOptions = dockerBuildOptions
31 | return this
32 | }
33 |
34 | public static withAgentDockerfile(String dockerfile = 'Dockerfile') {
35 | this.dockerfile = dockerfile
36 | return this
37 | }
38 |
39 | @Override
40 | public void apply(TerraformValidateStage stage) {
41 | stage.decorate(TerraformValidateStage.ALL, addAgent())
42 | }
43 |
44 | @Override
45 | public void apply(TerraformEnvironmentStage stage) {
46 | stage.decorate(TerraformEnvironmentStage.ALL, addAgent())
47 | }
48 |
49 | public Closure addAgent() {
50 | return { closure ->
51 | if (this.dockerImage && this.dockerfile == null) {
52 | docker.image(this.dockerImage).inside(this.dockerOptions) {
53 | closure()
54 | }
55 | } else if (this.dockerImage && this.dockerfile) {
56 | def buildCommand = "-f ${dockerfile} ."
57 | if (this.dockerBuildOptions) {
58 | buildCommand = "${this.dockerBuildOptions} ${buildCommand}"
59 | }
60 |
61 | docker.build(this.dockerImage, buildCommand.toString()).inside(this.dockerOptions) {
62 | closure()
63 | }
64 | } else {
65 | closure()
66 | }
67 | }
68 | }
69 |
70 | public static void reset() {
71 | dockerImage = null
72 | dockerfile = null
73 | dockerBuildOptions = null
74 | dockerOptions = null
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/src/AnsiColorPlugin.groovy:
--------------------------------------------------------------------------------
1 | import static TerraformEnvironmentStage.PLAN
2 | import static TerraformEnvironmentStage.APPLY
3 |
4 | class AnsiColorPlugin implements TerraformValidateStagePlugin, TerraformEnvironmentStagePlugin {
5 |
6 | public static void init() {
7 | TerraformValidateStage.addPlugin(new AnsiColorPlugin())
8 | TerraformEnvironmentStage.addPlugin(new AnsiColorPlugin())
9 | }
10 |
11 | @Override
12 | public void apply(TerraformValidateStage stage) {
13 | stage.decorate(addColor())
14 | }
15 |
16 | @Override
17 | public void apply(TerraformEnvironmentStage stage) {
18 | stage.decorate(PLAN, addColor())
19 | stage.decorate(APPLY, addColor())
20 | }
21 |
22 | public Closure addColor() {
23 | return { closure -> ansiColor('xterm') { closure() } }
24 | }
25 |
26 | }
27 |
--------------------------------------------------------------------------------
/src/AwssumePlugin.groovy:
--------------------------------------------------------------------------------
1 | class AwssumePlugin implements TerraformInitCommandPlugin, TerraformPlanCommandPlugin, TerraformApplyCommandPlugin {
2 | public static void init() {
3 | AwssumePlugin plugin = new AwssumePlugin()
4 |
5 | TerraformInitCommand.addPlugin(plugin)
6 | TerraformPlanCommand.addPlugin(plugin)
7 | TerraformApplyCommand.addPlugin(plugin)
8 | }
9 |
10 | @Override
11 | public void apply(TerraformInitCommand command) {
12 | applyToCommand(command)
13 | }
14 |
15 | @Override
16 | public void apply(TerraformPlanCommand command) {
17 | applyToCommand(command)
18 | }
19 |
20 | @Override
21 | public void apply(TerraformApplyCommand command) {
22 | applyToCommand(command)
23 | }
24 |
25 | // We lost type safety here - we should have a TerraformCommand interface
26 | private void applyToCommand(command) {
27 | String environment = command.getEnvironment()
28 | String region = getRegion(environment)
29 | String iamArn = getAwsRoleArn(environment)
30 |
31 | if (iamArn) {
32 | command.withPrefix("AWS_REGION=${region} AWS_ROLE_ARN=${iamArn} awssume")
33 | } else {
34 | println("No AWS_ROLE_ARN is set, so awssume will not be used for terraform in the ${environment} environment.")
35 | }
36 | }
37 |
38 | public String getAwsRoleArn(String environment) {
39 | String role = Jenkinsfile.instance.getEnv()['AWS_ROLE_ARN']
40 |
41 | if (role == null) {
42 | role = Jenkinsfile.instance.getEnv()["${environment.toUpperCase()}_AWS_ROLE_ARN"]
43 | }
44 |
45 | if (role == null) {
46 | role = Jenkinsfile.instance.getEnv()["${environment}_AWS_ROLE_ARN"]
47 | }
48 |
49 | return role
50 | }
51 |
52 | public String getRegion(String environment) {
53 | String region = Jenkinsfile.instance.getEnv()['AWS_REGION']
54 |
55 | if (region == null) {
56 | region = Jenkinsfile.instance.getEnv()["${environment.toUpperCase()}_AWS_REGION"]
57 | }
58 |
59 | if (region == null) {
60 | region = Jenkinsfile.instance.getEnv()['AWS_DEFAULT_REGION']
61 | }
62 |
63 | return region
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/BuildGraph.groovy:
--------------------------------------------------------------------------------
1 | class BuildGraph implements Stage {
2 | private List stages
3 |
4 | public BuildGraph(Stage start) {
5 | this.stages = [start]
6 | }
7 |
8 | public Stage then(Stage nextStage) {
9 | this.stages << nextStage
10 | return this
11 | }
12 |
13 | public void build() {
14 | Jenkinsfile.build(stages)
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/BuildStage.groovy:
--------------------------------------------------------------------------------
1 | import static TerraformEnvironmentStage.ALL
2 |
3 | class BuildStage implements Stage, DecoratableStage, TerraformEnvironmentStagePlugin, Resettable {
4 | private final String ARTIFACT_STASH_KEY = 'buildArtifact'
5 |
6 | public String buildCommand
7 |
8 | private String artifactIncludePattern
9 | private StageDecorations decorations
10 | private Jenkinsfile jenkinsfile
11 |
12 | private static plugins = []
13 |
14 | public BuildStage() {
15 | this("./build.sh")
16 | }
17 |
18 | public BuildStage(String buildCommand) {
19 | this.buildCommand = buildCommand
20 | this.jenkinsfile = Jenkinsfile.instance
21 | this.decorations = new StageDecorations()
22 | }
23 |
24 | public BuildStage saveArtifact(String artifactIncludePattern) {
25 | this.artifactIncludePattern = artifactIncludePattern
26 | TerraformEnvironmentStage.addPlugin(this)
27 | return this
28 | }
29 |
30 | public Stage then(Stage nextStage) {
31 | return new BuildGraph(this).then(nextStage)
32 | }
33 |
34 | public void build() {
35 | Jenkinsfile.build(pipelineConfiguration())
36 | }
37 |
38 | @Override
39 | public void apply(TerraformEnvironmentStage stage) {
40 | stage.decorate(ALL, unstashArtifact(ARTIFACT_STASH_KEY))
41 | }
42 |
43 | private Closure unstashArtifact(String artifactStashKey) {
44 | return { closure ->
45 | unstash "${artifactStashKey}"
46 | closure()
47 | }
48 | }
49 |
50 | public void decorate(Closure decoration) {
51 | decorations.add(decoration)
52 | }
53 |
54 | protected Closure pipelineConfiguration() {
55 | applyPlugins()
56 |
57 | return {
58 | node(jenkinsfile.getNodeName()) {
59 | stage("build") {
60 | decorations.apply {
61 | checkout(scm)
62 | sh buildCommand
63 | if (artifactIncludePattern != null) {
64 | stash includes: artifactIncludePattern, name: ARTIFACT_STASH_KEY
65 | }
66 | }
67 | }
68 | }
69 | }
70 | }
71 |
72 | public static getPlugins() {
73 | return plugins
74 | }
75 |
76 | public static void reset() {
77 | this.plugins = []
78 | }
79 |
80 | public static void addPlugin(plugin) {
81 | plugins << plugin
82 | }
83 |
84 | public void applyPlugins() {
85 | for (plugin in plugins) {
86 | plugin.apply(this)
87 | }
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/src/BuildStagePlugin.groovy:
--------------------------------------------------------------------------------
1 | interface BuildStagePlugin {
2 | public void apply(BuildStage stage)
3 | }
4 |
--------------------------------------------------------------------------------
/src/BuildWithParametersPlugin.groovy:
--------------------------------------------------------------------------------
1 | public class BuildWithParametersPlugin implements BuildStagePlugin,
2 | TerraformValidateStagePlugin,
3 | TerraformEnvironmentStagePlugin,
4 | RegressionStagePlugin,
5 | Resettable {
6 |
7 | private static globalBuildParameters = []
8 | private static appliedOnce = false
9 |
10 | public static void init() {
11 | def plugin = new BuildWithParametersPlugin()
12 |
13 | BuildStage.addPlugin(plugin)
14 | TerraformValidateStage.addPlugin(plugin)
15 | TerraformEnvironmentStage.addPlugin(plugin)
16 | RegressionStage.addPlugin(plugin)
17 | }
18 |
19 | @Override
20 | public void apply(BuildStage stage) {
21 | applyToAllStages(stage)
22 | }
23 |
24 | @Override
25 | public void apply(TerraformValidateStage stage) {
26 | applyToAllStages(stage)
27 | }
28 |
29 | @Override
30 | public void apply(TerraformEnvironmentStage stage) {
31 | applyToAllStages(stage)
32 | }
33 |
34 | @Override
35 | public void apply(RegressionStage stage) {
36 | applyToAllStages(stage)
37 | }
38 |
39 | private void applyToAllStages(DecoratableStage stage) {
40 | stage.decorate(addParameterToFirstStageOnly())
41 | }
42 |
43 | public Closure addParameterToFirstStageOnly() {
44 | return { innerClosure ->
45 | if (hasParameters() && !appliedOnce) {
46 | properties([
47 | parameters(getBuildParameters())
48 | ])
49 | appliedOnce = true
50 | }
51 |
52 | innerClosure()
53 | }
54 | }
55 |
56 | public static withBooleanParameter(Map options) {
57 | def optionDefaults = [
58 | defaultValue: false,
59 | $class: 'hudson.model.BooleanParameterDefinition'
60 | ]
61 |
62 | globalBuildParameters << (optionDefaults + options)
63 | }
64 |
65 | public static withStringParameter(Map options) {
66 | def optionDefaults = [
67 | defaultValue: '',
68 | $class: 'hudson.model.StringParameterDefinition'
69 | ]
70 |
71 | globalBuildParameters << (optionDefaults + options)
72 | }
73 |
74 | public static withParameter(Map options) {
75 | globalBuildParameters << options
76 | }
77 |
78 | public boolean hasParameters() {
79 | return !globalBuildParameters.isEmpty()
80 | }
81 |
82 | public List getBuildParameters() {
83 | return globalBuildParameters
84 | }
85 |
86 | public static reset() {
87 | globalBuildParameters = []
88 | appliedOnce = false
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/src/ConditionalApplyPlugin.groovy:
--------------------------------------------------------------------------------
1 | import static TerraformEnvironmentStage.CONFIRM
2 | import static TerraformEnvironmentStage.APPLY
3 |
4 | public class ConditionalApplyPlugin implements TerraformEnvironmentStagePlugin, Resettable {
5 |
6 | private static enabled = true
7 | private static DEFAULT_BRANCHES = ['main', 'master']
8 | private static branches = DEFAULT_BRANCHES
9 | private static environments = []
10 |
11 | public static withApplyOnBranch(String... enabledBranches) {
12 | branches = enabledBranches.clone()
13 | return this
14 | }
15 |
16 | public static withApplyOnEnvironment(String... enabledEnvironments) {
17 | environments = enabledEnvironments.clone()
18 | return this
19 | }
20 |
21 | public static disable() {
22 | enabled = false
23 | }
24 |
25 | public static void reset() {
26 | branches = DEFAULT_BRANCHES
27 | enabled = true
28 | }
29 |
30 | @Override
31 | public void apply(TerraformEnvironmentStage stage) {
32 | stage.decorateAround(CONFIRM, onlyOnExpectedBranch(stage.getEnvironment()))
33 | stage.decorateAround(APPLY, onlyOnExpectedBranch(stage.getEnvironment()))
34 | }
35 |
36 | public Closure onlyOnExpectedBranch(String environment) {
37 | return { closure ->
38 | if (shouldApply(environment)) {
39 | closure()
40 | } else {
41 | echo "Skipping Confirm/Apply steps, based on the configuration of ConditionalApplyPlugin."
42 | }
43 | }
44 | }
45 |
46 | public boolean shouldApply(String environment) {
47 | if (!enabled) {
48 | return true
49 | }
50 |
51 | if (environments.contains(environment)) {
52 | return true
53 | }
54 |
55 | if (branches.contains(Jenkinsfile.instance.getEnv().BRANCH_NAME)) {
56 | println("Current branch '${Jenkinsfile.instance.getEnv().BRANCH_NAME}' matches expected branches '${branches}', stage branch-condition is met and will run.")
57 | return true
58 | } else if (null == Jenkinsfile.instance.getEnv().BRANCH_NAME) {
59 | println("Current branch is null - you're probably using a single-branch job which doesn't make your branch name available. Assume that apply should be enabled.")
60 | return true
61 | }
62 |
63 | return false
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/ConfirmApplyPlugin.groovy:
--------------------------------------------------------------------------------
1 | import static TerraformEnvironmentStage.CONFIRM
2 |
3 | class ConfirmApplyPlugin implements TerraformEnvironmentStagePlugin, Resettable {
4 |
5 | public static final String DEFAULT_SUBMITTER_PARAMETER = 'approver'
6 | public static parameters = []
7 | public static confirmConditions = []
8 | public static enabled = true
9 | public static String confirmMessage = 'Are you absolutely sure the plan above is correct, and should be IMMEDIATELY DEPLOYED via "terraform apply" on stage ${environment}?'
10 | public static String okMessage = 'Run terraform apply now'
11 | public static String submitter
12 |
13 | public static void init() {
14 | TerraformEnvironmentStage.addPlugin(new ConfirmApplyPlugin())
15 | }
16 |
17 | @Override
18 | public void apply(TerraformEnvironmentStage stage) {
19 | if (enabled) {
20 | stage.decorate(CONFIRM, addConfirmation(stage.getEnvironment()))
21 | }
22 | }
23 |
24 | public Closure addConfirmation(String environment) {
25 | return { closure ->
26 | def userInput
27 | try {
28 | timeout(time: 15, unit: 'MINUTES') {
29 | userInput = input(getInputOptions(environment))
30 | checkConfirmConditions(userInput, environment)
31 | }
32 | } catch (ex) {
33 | throw ex
34 | }
35 |
36 | closure()
37 | }
38 | }
39 |
40 | private Map getInputOptions(String environment) {
41 | Map inputOptions = interpolateMap([
42 | message: confirmMessage,
43 | ok: okMessage,
44 | submitterParameter: submitter ?: DEFAULT_SUBMITTER_PARAMETER
45 | ], environment)
46 |
47 | if (!parameters.isEmpty()) {
48 | inputOptions['parameters'] = parameters.collect { item -> interpolateMap(item, environment) }
49 | }
50 |
51 | return inputOptions
52 | }
53 |
54 | public Map interpolateMap(Map input, String environment) {
55 | return input.inject([:]) { memo, key, value ->
56 | memo[key] = value.replaceAll('\\$\\{environment\\}', environment)
57 | memo
58 | }
59 | }
60 |
61 | public void checkConfirmConditions(userInput, environment) {
62 | if (confirmConditions.isEmpty()) {
63 | return
64 | }
65 |
66 | def options = [ input: userInput, environment: environment ]
67 | def hasFailures = confirmConditions.collect { condition -> condition.call(options) }
68 | .contains(false)
69 |
70 | if (hasFailures) {
71 | throw new RuntimeException('Confirmation Failed')
72 | }
73 | }
74 |
75 | public static withConfirmCondition(Closure condition) {
76 | confirmConditions << condition
77 | return this
78 | }
79 |
80 | public static withParameter(Map parameterOptions) {
81 | parameters << parameterOptions
82 | return this
83 | }
84 |
85 | public static withConfirmMessage(String newMessage) {
86 | this.confirmMessage = newMessage
87 | return this
88 | }
89 |
90 | public static withOkMessage(String newMessage) {
91 | this.okMessage = newMessage
92 | return this
93 | }
94 |
95 | public static withSubmitterParameter(String newParam) {
96 | this.submitter = newParam
97 | return this
98 | }
99 |
100 | public static disable() {
101 | this.enabled = false
102 | return this
103 | }
104 |
105 | public static enable() {
106 | this.enabled = true
107 | return this
108 | }
109 |
110 | public static reset() {
111 | this.enabled = true
112 | this.parameters = []
113 | this.confirmConditions = []
114 | this.submitter = null
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/src/ConsulBackendPlugin.groovy:
--------------------------------------------------------------------------------
1 | class ConsulBackendPlugin implements TerraformInitCommandPlugin {
2 |
3 | public static String defaultAddress
4 | public static Closure pathPattern
5 |
6 | public static void init() {
7 | ConsulBackendPlugin plugin = new ConsulBackendPlugin()
8 |
9 | TerraformInitCommand.addPlugin(plugin)
10 | }
11 |
12 | @Override
13 | public void apply(TerraformInitCommand command) {
14 | String environment = command.getEnvironment()
15 | String backendPath = getBackendPath(environment)
16 | command.withBackendConfig("path=${backendPath}")
17 |
18 | String consulAddress = getConsulAddress()
19 | if (consulAddress) {
20 | command.withBackendConfig("address=${consulAddress}")
21 | }
22 | }
23 |
24 | public String getBackendPath(String environment) {
25 | Closure backendPathPattern = pathPattern
26 |
27 | if (backendPathPattern == null) {
28 | String repoSlug = Jenkinsfile.instance.getStandardizedRepoSlug()
29 | backendPathPattern = { String env -> "terraform/${repoSlug}_${env}" }
30 | }
31 |
32 | return backendPathPattern.call(environment)
33 | }
34 |
35 | public String getConsulAddress() {
36 | return defaultAddress ?: Jenkinsfile.instance.getEnv().DEFAULT_CONSUL_ADDRESS
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/CredentialsPlugin.groovy:
--------------------------------------------------------------------------------
1 | class CredentialsPlugin implements BuildStagePlugin, RegressionStagePlugin, TerraformEnvironmentStagePlugin, TerraformValidateStagePlugin, Resettable {
2 | private static bindings = []
3 |
4 | public static void init() {
5 | def plugin = new CredentialsPlugin()
6 |
7 | BuildStage.addPlugin(plugin)
8 | RegressionStage.addPlugin(plugin)
9 | TerraformEnvironmentStage.addPlugin(plugin)
10 | TerraformValidateStage.addPlugin(plugin)
11 | }
12 |
13 | public static withBinding(Closure binding) {
14 | bindings << binding
15 | return this
16 | }
17 |
18 | // Deprecated: Remove this with Issue #404 and the next major release
19 | public static withBuildCredentials(Map options = [:], String credentialsId) {
20 | Map optionsWithDefaults = populateDefaults(options, credentialsId)
21 | bindings << { usernamePassword(optionsWithDefaults) }
22 | return this
23 | }
24 |
25 | @Override
26 | public void apply(BuildStage buildStage) {
27 | buildStage.decorate(addBuildCredentials())
28 | }
29 |
30 | @Override
31 | public void apply(RegressionStage regressionStage) {
32 | regressionStage.decorate(addBuildCredentials())
33 | }
34 |
35 | @Override
36 | public void apply(TerraformEnvironmentStage environmentStage) {
37 | environmentStage.decorate(addBuildCredentials())
38 | }
39 |
40 | @Override
41 | public void apply(TerraformValidateStage validateStage) {
42 | validateStage.decorate(addBuildCredentials())
43 | }
44 |
45 | private addBuildCredentials() {
46 | return { innerClosure ->
47 | def workflowScript = delegate
48 | def appliedBindings = getBindings().collect { it -> it.delegate = workflowScript; it() }
49 |
50 | withCredentials(appliedBindings, innerClosure)
51 | }
52 | }
53 |
54 | public static Map populateDefaults(Map options = [:], String credentialsId) {
55 | def credentialsOptions = options.clone()
56 | credentialsOptions['credentialsId'] = credentialsId
57 | credentialsOptions['usernameVariable'] = credentialsOptions['usernameVariable'] ?: "${toEnvironmentVariable(credentialsId)}_USERNAME".toString()
58 | credentialsOptions['passwordVariable'] = credentialsOptions['passwordVariable'] ?: "${toEnvironmentVariable(credentialsId)}_PASSWORD".toString()
59 |
60 | return credentialsOptions
61 | }
62 |
63 | public static String toEnvironmentVariable(String value) {
64 | value.toUpperCase().replaceAll('-', '_')
65 | }
66 |
67 | public static getBindings() {
68 | return bindings
69 | }
70 |
71 | public static void reset() {
72 | bindings = []
73 | }
74 |
75 | }
76 |
--------------------------------------------------------------------------------
/src/DecoratableStage.groovy:
--------------------------------------------------------------------------------
1 | // This is temporary, and should be pushed into Stage interface. See Issue #152
2 | interface DecoratableStage {
3 | public void decorate(Closure closure)
4 | }
5 |
--------------------------------------------------------------------------------
/src/DefaultEnvironmentPlugin.groovy:
--------------------------------------------------------------------------------
1 | import static TerraformEnvironmentStage.ALL
2 |
3 | class DefaultEnvironmentPlugin implements TerraformEnvironmentStagePlugin {
4 |
5 | @Override
6 | public void apply(TerraformEnvironmentStage stage) {
7 | String environment = stage.getEnvironment()
8 |
9 | stage.decorate(ALL, addEnvironmentTerraformVariable(environment))
10 | }
11 |
12 | public static Closure addEnvironmentTerraformVariable(String environment) {
13 | return { closure -> withEnv(["TF_VAR_environment=${environment}"]) { closure() } }
14 | }
15 |
16 | }
17 |
--------------------------------------------------------------------------------
/src/DestroyPlugin.groovy:
--------------------------------------------------------------------------------
1 | class DestroyPlugin implements TerraformPlanCommandPlugin,
2 | TerraformApplyCommandPlugin,
3 | Resettable {
4 |
5 | private static arguments = []
6 | public static DESTROY_CONFIRM_MESSAGE = 'DANGER! Your ${environment} environment will be IMMEDIATELY DESTROYED via "terraform destroy". Review your plan to see all the resources that will be destroyed, and confirm. YOU CANNOT UNDO THIS.'
7 | public static DESTROY_OK_MESSAGE = "Run terraform DESTROY now"
8 |
9 | public static void init() {
10 | DestroyPlugin plugin = new DestroyPlugin()
11 |
12 | def appName = Jenkinsfile.instance.getRepoName()
13 |
14 | ConfirmApplyPlugin.withConfirmMessage(DESTROY_CONFIRM_MESSAGE)
15 | ConfirmApplyPlugin.withOkMessage(DESTROY_OK_MESSAGE)
16 | ConfirmApplyPlugin.withParameter([
17 | $class: 'hudson.model.StringParameterDefinition',
18 | name: 'CONFIRM_DESTROY',
19 | description: "Type \"destroy ${appName} \${environment}\" to confirm and continue."
20 | ])
21 | ConfirmApplyPlugin.withConfirmCondition(getConfirmCondition(appName))
22 |
23 | TerraformEnvironmentStage.withStageNamePattern { options -> "${options['command']}-DESTROY-${options['environment']}" }
24 |
25 | TerraformPlanCommand.addPlugin(plugin)
26 | TerraformApplyCommand.addPlugin(plugin)
27 | }
28 |
29 | public void apply(TerraformPlanCommand command) {
30 | command.withArgument('-destroy')
31 | }
32 |
33 | public void apply(TerraformApplyCommand command) {
34 | command.withCommand('destroy')
35 | for (arg in arguments) {
36 | command.withArgument(arg)
37 | }
38 | }
39 |
40 | public static withArgument(String arg) {
41 | arguments << arg
42 | return this
43 | }
44 |
45 | public static getConfirmCondition(String appName) {
46 | return { options ->
47 | "destroy ${appName} ${options['environment']}".toString() == options['input']['CONFIRM_DESTROY']
48 | }
49 | }
50 |
51 | public static reset() {
52 | arguments = []
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/EnvironmentVariablePlugin.groovy:
--------------------------------------------------------------------------------
1 | import static TerraformEnvironmentStage.ALL
2 |
3 | class EnvironmentVariablePlugin implements TerraformEnvironmentStagePlugin {
4 | private String key
5 | private String value
6 |
7 | @Override
8 | public void apply(TerraformEnvironmentStage stage) {
9 | stage.decorate(ALL, withEnvClosure(key, value))
10 | }
11 |
12 | public withEnv(String key, String value) {
13 | this.key = key
14 | this.value = value
15 | }
16 |
17 | private Closure withEnvClosure(String key, String value) {
18 | return { closure ->
19 | withEnv(["${key}=${value}"]) {
20 | closure()
21 | }
22 | }
23 | }
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/src/FileParametersPlugin.groovy:
--------------------------------------------------------------------------------
1 | import static TerraformEnvironmentStage.ALL
2 |
3 | import groovy.text.StreamingTemplateEngine
4 |
5 | class FileParametersPlugin implements TerraformEnvironmentStagePlugin {
6 | public static void init() {
7 | FileParametersPlugin plugin = new FileParametersPlugin()
8 |
9 | TerraformEnvironmentStage.addPlugin(plugin)
10 | }
11 |
12 | @Override
13 | public void apply(TerraformEnvironmentStage stage) {
14 | String environment = stage.getEnvironment()
15 |
16 | stage.decorate(ALL, addEnvironmentSpecificVariables(environment))
17 | }
18 |
19 | public Closure addEnvironmentSpecificVariables(String environment) {
20 | String environmentFilename = "${environment}.properties"
21 |
22 | return { closure ->
23 | if (fileExists(environmentFilename)) {
24 | echo "Found file: ${environmentFilename} - loading the contents as environment variables."
25 | String fileContent = readFile(environmentFilename)
26 | List variables = getVariables(fileContent)
27 |
28 | withEnv(variables) { closure() }
29 | } else {
30 | echo "No environment properties file found. Create a ${environmentFilename} file to add environment-specific variables to this stage."
31 | closure()
32 | }
33 | }
34 | }
35 |
36 | public List getVariables(String fileContent) {
37 | return fileContent.split('\\r?\\n').collect { String value -> interpolate(value) }
38 | }
39 |
40 | public String interpolate(String value) {
41 | return new StreamingTemplateEngine().createTemplate(value).make([env: getEnv()]).toString()
42 | }
43 |
44 | public getEnv() {
45 | return (Jenkinsfile.instance != null) ? Jenkinsfile.instance.getEnv() : [:]
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/FlywayCommand.groovy:
--------------------------------------------------------------------------------
1 | class FlywayCommand implements Resettable {
2 | private String command
3 | private String binary = "flyway"
4 | private static String locations
5 | private static String url
6 | private static String user
7 | private static String password
8 | private static Collection additionalParameters = []
9 |
10 | public FlywayCommand(String command) {
11 | this.command = command
12 | }
13 |
14 | public String toString() {
15 | def pieces = []
16 | pieces << binary
17 | pieces << command
18 |
19 | if (locations) {
20 | pieces << "-locations=${locations}"
21 | }
22 |
23 | if (url) {
24 | pieces << "-url=${url}"
25 | }
26 |
27 | if (user) {
28 | pieces << "-user=${user}"
29 | }
30 |
31 | if (password) {
32 | pieces << "-password=${password}"
33 | }
34 |
35 | pieces.addAll(additionalParameters)
36 |
37 | return pieces.join(' ')
38 | }
39 |
40 | public static withUser(String user) {
41 | this.user = user
42 | return this
43 | }
44 |
45 | public static withPassword(String password) {
46 | this.password = password
47 | return this
48 | }
49 |
50 | public static withLocations(String locations) {
51 | this.locations = locations
52 | return this
53 | }
54 |
55 | public static withUrl(String url) {
56 | this.url = url
57 | return this
58 | }
59 |
60 | public static withAdditionalParameter(String parameter) {
61 | this.additionalParameters << parameter
62 | println "withAdditionalParameter: ${parameter}, ${this.additionalParameters}"
63 | return this
64 | }
65 |
66 | public static reset() {
67 | this.locations = null
68 | this.url = null
69 | this.user = null
70 | this.password = null
71 | this.additionalParameters = []
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/src/HookPoint.groovy:
--------------------------------------------------------------------------------
1 | class HookPoint {
2 | String runBefore = null
3 | String runAfterOnSuccess = null
4 | String runAfterOnFailure = null
5 | String runAfterAlways = null
6 | private String hookName
7 |
8 | HookPoint(String hookName) {
9 | this.hookName = hookName
10 | }
11 |
12 | public String getName() {
13 | return this.hookName
14 | }
15 |
16 | public Boolean isConfigured() {
17 | return ! (this.runBefore == null && this.runAfterOnSuccess == null && this.runAfterOnFailure == null && this.runAfterAlways == null)
18 | }
19 |
20 | public Closure getClosure() {
21 | return { closure ->
22 | try {
23 | if (this.runBefore != null) { sh this.runBefore }
24 | closure()
25 | if (this.runAfterOnSuccess != null) { sh this.runAfterOnSuccess }
26 | } catch (Exception e) {
27 | if (this.runAfterOnFailure != null) { sh this.runAfterOnFailure }
28 | throw e
29 | } finally {
30 | if (this.runAfterAlways != null) { sh this.runAfterAlways }
31 | }
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/ParameterStoreBuildWrapperPlugin.groovy:
--------------------------------------------------------------------------------
1 | import static TerraformValidateStage.ALL
2 | import static TerraformEnvironmentStage.PLAN
3 | import static TerraformEnvironmentStage.APPLY
4 |
5 | class ParameterStoreBuildWrapperPlugin implements TerraformValidateStagePlugin, TerraformEnvironmentStagePlugin, Resettable {
6 | private static globalPathPattern
7 | private static List globalParameterOptions = []
8 | private static defaultPathPattern = { options -> "/${options['organization']}/${options['repoName']}/${options['environment']}/" }
9 |
10 | public static void init() {
11 | TerraformEnvironmentStage.addPlugin(new ParameterStoreBuildWrapperPlugin())
12 | TerraformValidateStage.addPlugin(new ParameterStoreBuildWrapperPlugin())
13 | }
14 |
15 | public static withPathPattern(Closure newPathPattern) {
16 | globalPathPattern = newPathPattern
17 | return this
18 | }
19 |
20 | public static withGlobalParameter(String path, Map options = [:]) {
21 | globalParameterOptions << [path: path] + options
22 | return this
23 | }
24 |
25 | @Override
26 | public void apply(TerraformValidateStage stage) {
27 | globalParameterOptions.each { gp ->
28 | stage.decorate(ALL, addParameterStoreBuildWrapper(gp))
29 | }
30 | }
31 |
32 | @Override
33 | public void apply(TerraformEnvironmentStage stage) {
34 | def environment = stage.getEnvironment()
35 | List options = getParameterOptions(environment)
36 |
37 | options.each { option ->
38 | stage.decorate(PLAN, addParameterStoreBuildWrapper(option))
39 | stage.decorate(APPLY, addParameterStoreBuildWrapper(option))
40 | }
41 | }
42 |
43 | List getParameterOptions(String environment) {
44 | List options = []
45 | options.add(getEnvironmentParameterOptions(environment))
46 | options.addAll(getGlobalParameterOptions())
47 |
48 | return options
49 | }
50 |
51 | List getGlobalParameterOptions() {
52 | return globalParameterOptions
53 | }
54 |
55 | Map getEnvironmentParameterOptions(String environment) {
56 | return [
57 | path: pathForEnvironment(environment),
58 | credentialsId: "${environment.toUpperCase()}_PARAMETER_STORE_ACCESS"
59 | ]
60 | }
61 |
62 | String pathForEnvironment(String environment) {
63 | String organization = Jenkinsfile.instance.getOrganization()
64 | String repoName = Jenkinsfile.instance.getRepoName()
65 | def patternOptions = [ environment: environment,
66 | repoName: repoName,
67 | organization: organization ]
68 |
69 | def pathPattern = globalPathPattern ?: defaultPathPattern
70 |
71 | return pathPattern(patternOptions)
72 | }
73 |
74 | public Closure addParameterStoreBuildWrapper(Map options = [:]) {
75 | def Map defaultOptions = [
76 | naming: 'basename'
77 | ]
78 |
79 | def parameterStoreOptions = defaultOptions + options
80 |
81 | return { closure ->
82 | withAWSParameterStore(parameterStoreOptions) {
83 | closure()
84 | }
85 | }
86 | }
87 |
88 | public static reset() {
89 | globalPathPattern = null
90 | globalParameterOptions = []
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/src/ParameterStoreExecPlugin.groovy:
--------------------------------------------------------------------------------
1 | import static TerraformEnvironmentStage.PLAN
2 | import static TerraformEnvironmentStage.APPLY
3 |
4 | class ParameterStoreExecPlugin implements TerraformEnvironmentStagePlugin, TerraformPlanCommandPlugin, TerraformApplyCommandPlugin {
5 | public static void init() {
6 | ParameterStoreExecPlugin plugin = new ParameterStoreExecPlugin()
7 |
8 | TerraformEnvironmentStage.addPlugin(plugin)
9 | TerraformPlanCommand.addPlugin(plugin)
10 | TerraformApplyCommand.addPlugin(plugin)
11 | }
12 |
13 | @Override
14 | public void apply(TerraformEnvironmentStage stage) {
15 | def environment = stage.getEnvironment()
16 | def parameterStorePath = pathForEnvironment(environment)
17 |
18 | stage.decorate(PLAN, addEnvVariables(parameterStorePath))
19 | stage.decorate(APPLY, addEnvVariables(parameterStorePath))
20 | }
21 |
22 | public String pathForEnvironment(String environment) {
23 | String organization = Jenkinsfile.instance.getOrganization()
24 | String repoName = Jenkinsfile.instance.getRepoName()
25 |
26 | return "/${organization}/${repoName}/${environment}/"
27 | }
28 |
29 | public static Closure addEnvVariables(String path) {
30 | return { closure ->
31 | withEnv(["PARAMETER_STORE_EXEC_PATH=${path}", "PARAMETER_STORE_EXEC_DISABLE_TRANSLATION=true", "AWS_REGION=us-east-1"]) {
32 | closure()
33 | }
34 | }
35 | }
36 |
37 | @Override
38 | public void apply(TerraformPlanCommand command) {
39 | command.withPrefix('parameter-store-exec')
40 | }
41 |
42 | @Override
43 | public void apply(TerraformApplyCommand command) {
44 | command.withPrefix('parameter-store-exec')
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/PassPlanFilePlugin.groovy:
--------------------------------------------------------------------------------
1 | import static TerraformEnvironmentStage.PLAN_COMMAND
2 | import static TerraformEnvironmentStage.APPLY_COMMAND
3 |
4 | class PassPlanFilePlugin implements TerraformPlanCommandPlugin, TerraformApplyCommandPlugin, TerraformEnvironmentStagePlugin, Resettable {
5 |
6 | private static String directory = "./"
7 |
8 | public static void init() {
9 | PassPlanFilePlugin plugin = new PassPlanFilePlugin()
10 |
11 | TerraformEnvironmentStage.addPlugin(plugin)
12 | TerraformPlanCommand.addPlugin(plugin)
13 | TerraformApplyCommand.addPlugin(plugin)
14 | }
15 |
16 | public static withDirectory(String directory) {
17 | PassPlanFilePlugin.directory = directory
18 | return this
19 | }
20 |
21 | @Override
22 | public void apply(TerraformEnvironmentStage stage) {
23 | stage.decorate(PLAN_COMMAND, stashPlan(stage.getEnvironment()))
24 | stage.decorate(APPLY_COMMAND, unstashPlan(stage.getEnvironment()))
25 | }
26 |
27 | @Override
28 | public void apply(TerraformPlanCommand command) {
29 | String env = command.getEnvironment()
30 | command.withArgument("-out=tfplan-" + env)
31 | }
32 |
33 | @Override
34 | public void apply(TerraformApplyCommand command) {
35 | String env = command.getEnvironment()
36 | command.withPlanFile("tfplan-" + env)
37 | }
38 |
39 | public Closure stashPlan(String env) {
40 | return { closure ->
41 | closure()
42 | String planFile = "tfplan-" + env
43 | echo "Stashing ${planFile} file"
44 | dir(directory) {
45 | stash name: planFile, includes: planFile
46 | }
47 | }
48 | }
49 |
50 | public Closure unstashPlan(String env) {
51 | return { closure ->
52 | String planFile = "tfplan-" + env
53 | echo "Unstashing ${planFile} file"
54 | dir(directory) {
55 | unstash planFile
56 | }
57 | closure()
58 | }
59 | }
60 |
61 | public static reset() {
62 | directory = "./"
63 | }
64 |
65 | }
66 |
--------------------------------------------------------------------------------
/src/PlanOnlyPlugin.groovy:
--------------------------------------------------------------------------------
1 | import static TerraformEnvironmentStage.APPLY
2 | import static TerraformEnvironmentStage.CONFIRM
3 |
4 | class PlanOnlyPlugin implements TerraformEnvironmentStagePlugin, TerraformPlanCommandPlugin {
5 |
6 | public static void init() {
7 | PlanOnlyPlugin plugin = new PlanOnlyPlugin()
8 |
9 | BuildWithParametersPlugin.withBooleanParameter([
10 | name: "PLAN_ONLY",
11 | description: 'Run `terraform plan` only, skipping `terraform apply`.'
12 | ])
13 |
14 | BuildWithParametersPlugin.withBooleanParameter([
15 | name: "FAIL_PLAN_ON_CHANGES",
16 | description: 'Plan run with -detailed-exitcode; ANY CHANGES will cause failure'
17 | ])
18 |
19 | TerraformPlanCommand.addPlugin(plugin)
20 | TerraformEnvironmentStage.addPlugin(plugin)
21 | }
22 |
23 | public Closure skipStage(String stageName) {
24 | return { closure ->
25 | echo "Skipping ${stageName} stage. PlanOnlyPlugin is enabled."
26 | }
27 | }
28 |
29 | @Override
30 | public void apply(TerraformEnvironmentStage stage) {
31 | if (Jenkinsfile.instance.getEnv().PLAN_ONLY == 'true') {
32 | stage.decorateAround(CONFIRM, skipStage(CONFIRM))
33 | stage.decorateAround(APPLY, skipStage(APPLY))
34 | }
35 | }
36 |
37 | @Override
38 | public void apply(TerraformPlanCommand command) {
39 | if (Jenkinsfile.instance.getEnv().FAIL_PLAN_ON_CHANGES == 'true') {
40 | // set -e: fail on error
41 | // set -o pipefail: return non-zero exit code if any command fails.
42 | // useful when commands are piped together (ie. `terraform plan | landscape`)
43 | command.withPrefix('set -e; set -o pipefail;')
44 | command.withArgument('-detailed-exitcode')
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/RegressionStage.groovy:
--------------------------------------------------------------------------------
1 | class RegressionStage implements Stage, DecoratableStage, Resettable {
2 |
3 | public String testCommand
4 | public List automationRepoList = []
5 | private String testCommandDirectory
6 | private Jenkinsfile jenkinsfile
7 |
8 | private StageDecorations decorations
9 |
10 | private static plugins = []
11 |
12 | public RegressionStage() {
13 | this("./bin/test.sh")
14 | }
15 |
16 | public RegressionStage(String testCommand) {
17 | this.testCommand = testCommand
18 | this.jenkinsfile = Jenkinsfile.instance
19 | this.decorations = new StageDecorations()
20 | }
21 |
22 | public RegressionStage withScm(String automationRepo) {
23 | this.automationRepoList.add(automationRepo)
24 | return this
25 | }
26 |
27 | public RegressionStage changeDirectory(String directory) {
28 | this.testCommandDirectory = directory
29 | return this
30 | }
31 |
32 | public Stage then(Stage nextStage) {
33 | return new BuildGraph(this).then(nextStage)
34 | }
35 |
36 | public void build() {
37 | Jenkinsfile.build(pipelineConfiguration())
38 | }
39 |
40 | private Closure pipelineConfiguration() {
41 | applyPlugins()
42 |
43 | return {
44 | node(jenkinsfile.getNodeName()) {
45 | stage("test") {
46 | decorations.apply {
47 | if (automationRepoList.isEmpty()) {
48 | checkout scm
49 | sh testCommand
50 | } else if (automationRepoList.size() == 1) {
51 | checkout resolveScm(source: [$class: 'GitSCMSource', remote: automationRepoList.first(), traits: [[$class: 'jenkins.plugins.git.traits.BranchDiscoveryTrait'], [$class: 'LocalBranchTrait']]], targets: [BRANCH_NAME, 'main'])
52 | sh testCommand
53 | } else {
54 | for (url in automationRepoList) {
55 | def dirName = url.split('/').last() - '.git'
56 | dir(dirName) {
57 | checkout resolveScm(source: [$class: 'GitSCMSource', remote: url, traits: [[$class: 'jenkins.plugins.git.traits.BranchDiscoveryTrait'], [$class: 'LocalBranchTrait']]], targets: [BRANCH_NAME, 'main'])
58 | }
59 | }
60 |
61 | if (this.testCommandDirectory) {
62 | dir(this.testCommandDirectory) {
63 | sh testCommand
64 | }
65 | } else {
66 | sh testCommand
67 | }
68 | }
69 | }
70 | }
71 | }
72 | }
73 | }
74 |
75 | public void decorate(Closure decoration) {
76 | decorations.add(decoration)
77 | }
78 |
79 | public static getPlugins() {
80 | return plugins
81 | }
82 |
83 | public static void reset() {
84 | this.plugins = []
85 | }
86 |
87 | public static void addPlugin(plugin) {
88 | plugins << plugin
89 | }
90 |
91 | public void applyPlugins() {
92 | for (plugin in plugins) {
93 | plugin.apply(this)
94 | }
95 | }
96 |
97 | }
98 |
--------------------------------------------------------------------------------
/src/RegressionStagePlugin.groovy:
--------------------------------------------------------------------------------
1 | interface RegressionStagePlugin {
2 | public void apply(RegressionStage stage)
3 | }
4 |
--------------------------------------------------------------------------------
/src/Resettable.groovy:
--------------------------------------------------------------------------------
1 | public interface Resettable {
2 | }
3 |
--------------------------------------------------------------------------------
/src/SemanticVersion.groovy:
--------------------------------------------------------------------------------
1 | class SemanticVersion implements Comparable {
2 |
3 | private String versionString
4 | private List components
5 |
6 | SemanticVersion(String versionString) {
7 | this.versionString = versionString
8 | this.components = versionString.tokenize(/._/)
9 | }
10 |
11 | String getVersion() {
12 | this.versionString
13 | }
14 |
15 | String toString() {
16 | return "SemanticVersion: ${versionString}"
17 | }
18 |
19 | @Override
20 | int compareTo(SemanticVersion other) {
21 | for (i in 0.. 1.0.1 : 1.0.1 <=> 1.0.1-rc1
24 | } else if (i == other.components.size()) {
25 | return this.components[i].isInteger() ? 1 : -1 //1.0.1 <=> 1.0 : 1.0.1-rc1 <=> 1.0.1
26 | }
27 |
28 | if (this.components[i].isInteger() && other.components[i].isInteger()) { //1.0 <=> 1.1 or //1.1 <=> 1.0
29 | int diff = (this.components[i] as int) <=> (other.components[i] as int)
30 | if (diff != 0) {
31 | return diff
32 | }
33 | } else if (this.components[i].isInteger()) { //1.0.3.4 <=> 1.0.3.4b goes to the former. Hmm.
34 | return 1
35 | } else if (other.components[i].isInteger()) { //1.0.3.4-rc3 <=> 1.0.3.4 goes to the later.
36 | return -1
37 | } else {
38 | int diff = this.components[i] <=> other.components[i] //1.0.3.3 <=> 1.0.3.4 works at least. :-/
39 | if (diff != 0) {
40 | return diff
41 | }
42 | }
43 | }
44 | return 0
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/Stage.groovy:
--------------------------------------------------------------------------------
1 | interface Stage {
2 | public Stage then(Stage nextStage)
3 | public void build()
4 | }
5 |
--------------------------------------------------------------------------------
/src/StageDecorations.groovy:
--------------------------------------------------------------------------------
1 | class StageDecorations {
2 | private Map decorations = [:]
3 |
4 | public apply(String group = null, Closure innerClosure) {
5 | def currentDecorations = decorations.get(group) ?: { stage -> stage() }
6 | currentDecorations.delegate = innerClosure.owner
7 | currentDecorations(innerClosure)
8 | }
9 |
10 | public add(String group = null, Closure decoration) {
11 | def existingDecoration = decorations.get(group) ?: { stage -> stage() }
12 | def newDecoration = { stage ->
13 | decoration.delegate = delegate
14 | decoration() {
15 | stage.delegate = delegate
16 | existingDecoration.delegate = delegate
17 | existingDecoration(stage)
18 | }
19 | }
20 |
21 | decorations.put(group, newDecoration)
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/TagPlugin.groovy:
--------------------------------------------------------------------------------
1 | class TagPlugin implements TerraformPlanCommandPlugin,
2 | TerraformApplyCommandPlugin,
3 | Resettable {
4 |
5 | private static variableName
6 | private static disableOnApply = false
7 | private static writeToFile = false
8 | private static List tagClosures = []
9 |
10 | public static init() {
11 | def plugin = new TagPlugin()
12 |
13 | TerraformPlanCommand.addPlugin(plugin)
14 | TerraformApplyCommand.addPlugin(plugin)
15 | }
16 |
17 | @Override
18 | public void apply(TerraformPlanCommand command) {
19 | applyToCommand(command)
20 | }
21 |
22 | @Override
23 | public void apply(TerraformApplyCommand command) {
24 | if (disableOnApply) {
25 | return
26 | }
27 |
28 | applyToCommand(command)
29 | }
30 |
31 | private void applyToCommand(command) {
32 | String variableName = getVariableName()
33 | Map tags = getTags(command)
34 |
35 | if (writeToFile) {
36 | command.withVariableFile(variableName, tags)
37 | } else {
38 | command.withVariable(variableName, tags)
39 | }
40 | }
41 |
42 | public static withVariableName(String variableName) {
43 | this.variableName = variableName
44 | }
45 |
46 | public static withEnvironmentTag(String tagKey = 'environment') {
47 | tagClosures << { command ->
48 | Map tag = [:]
49 | tag[tagKey] = command.getEnvironment()
50 | tag
51 | }
52 | return this
53 | }
54 |
55 | public static withTag(String key, String value) {
56 | tagClosures << { command ->
57 | Map tag = [:]
58 | tag[key] = value
59 | tag
60 | }
61 | return this
62 | }
63 |
64 | public static withTagFromFile(String key, String filename) {
65 | tagClosures << { command ->
66 | Map tag = [:]
67 | tag[key] = Jenkinsfile.readFile(filename)
68 | tag
69 | }
70 | return this
71 | }
72 |
73 | public static withTagFromEnvironmentVariable(String key, String variable) {
74 | tagClosures << { command ->
75 | Map tag = [:]
76 | tag[key] = Jenkinsfile.getEnvironmentVariable(variable)
77 | tag
78 | }
79 | return this
80 | }
81 |
82 | public static disableOnApply() {
83 | disableOnApply = true
84 | return this
85 | }
86 |
87 | public static writeToFile() {
88 | writeToFile = true
89 | return this
90 | }
91 |
92 | private static getVariableName() {
93 | return variableName ?: 'tags'
94 | }
95 |
96 | public Map getTags(TerraformCommand command) {
97 | return tagClosures.inject([:]) { memo, tagClosure ->
98 | memo + tagClosure.call(command)
99 | }
100 | }
101 |
102 | public static reset() {
103 | tagClosures = []
104 | variableName = null
105 | disableOnApply = false
106 | writeToFile = false
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/src/TargetPlugin.groovy:
--------------------------------------------------------------------------------
1 | class TargetPlugin implements TerraformPlanCommandPlugin, TerraformApplyCommandPlugin {
2 | public static void init() {
3 | TargetPlugin plugin = new TargetPlugin()
4 |
5 | BuildWithParametersPlugin.withStringParameter([
6 | name: "RESOURCE_TARGETS",
7 | description: 'comma-separated list of resource addresses to pass to plan and apply "-target=" parameters'
8 | ])
9 |
10 | TerraformPlanCommand.addPlugin(plugin)
11 | TerraformApplyCommand.addPlugin(plugin)
12 | }
13 |
14 | @Override
15 | public void apply(TerraformPlanCommand command) {
16 | def targets = Jenkinsfile.instance.getEnv().RESOURCE_TARGETS ?: ''
17 | targets.split(',')
18 | .collect { item -> item.trim() }
19 | .findAll { item -> item != '' }
20 | .each { item -> command.withArgument("-target ${item}") }
21 | }
22 |
23 | @Override
24 | public void apply(TerraformApplyCommand command) {
25 | def targets = Jenkinsfile.instance.getEnv().RESOURCE_TARGETS ?: ''
26 | targets.split(',')
27 | .collect { item -> item.trim() }
28 | .findAll { item -> item != '' }
29 | .each { item -> command.withArgument("-target ${item}") }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/TerraformApplyCommandPlugin.groovy:
--------------------------------------------------------------------------------
1 | interface TerraformApplyCommandPlugin {
2 | public void apply(TerraformApplyCommand command)
3 | }
4 |
--------------------------------------------------------------------------------
/src/TerraformCommand.groovy:
--------------------------------------------------------------------------------
1 | /**
2 | * `TerraformCommand` is an interface that can be implemented by a class that wraps a Terraform command.
3 | */
4 | interface TerraformCommand {
5 | public String getEnvironment()
6 | }
7 |
--------------------------------------------------------------------------------
/src/TerraformDirectoryPlugin.groovy:
--------------------------------------------------------------------------------
1 | class TerraformDirectoryPlugin implements TerraformInitCommandPlugin, TerraformValidateCommandPlugin, TerraformPlanCommandPlugin, TerraformApplyCommandPlugin, Resettable {
2 |
3 | private static String directory = "./terraform/"
4 |
5 | public static void init() {
6 | TerraformDirectoryPlugin plugin = new TerraformDirectoryPlugin()
7 |
8 | TerraformInitCommand.addPlugin(plugin)
9 | TerraformValidateCommand.addPlugin(plugin)
10 | TerraformPlanCommand.addPlugin(plugin)
11 | TerraformApplyCommand.addPlugin(plugin)
12 | }
13 |
14 | public static withDirectory(String directory) {
15 | TerraformDirectoryPlugin.directory = directory
16 | return this
17 | }
18 |
19 | @Override
20 | public void apply(TerraformInitCommand command) {
21 | command.withDirectory(directory)
22 | }
23 |
24 | @Override
25 | public void apply(TerraformValidateCommand command) {
26 | command.withDirectory(directory)
27 | }
28 |
29 | @Override
30 | public void apply(TerraformPlanCommand command) {
31 | command.withDirectory(directory)
32 | }
33 |
34 | @Override
35 | public void apply(TerraformApplyCommand command) {
36 | command.withDirectory(directory)
37 | }
38 |
39 | public static reset() {
40 | directory = "./terraform/"
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/TerraformEnvironmentStagePlugin.groovy:
--------------------------------------------------------------------------------
1 | interface TerraformEnvironmentStagePlugin {
2 | public void apply(TerraformEnvironmentStage stage)
3 | }
4 |
--------------------------------------------------------------------------------
/src/TerraformEnvironmentStageShellHookPlugin.groovy:
--------------------------------------------------------------------------------
1 | import static TerraformEnvironmentStage.ALL
2 | import static TerraformEnvironmentStage.INIT_COMMAND
3 | import static TerraformEnvironmentStage.PLAN
4 | import static TerraformEnvironmentStage.PLAN_COMMAND
5 | import static TerraformEnvironmentStage.APPLY
6 | import static TerraformEnvironmentStage.APPLY_COMMAND
7 |
8 | class TerraformEnvironmentStageShellHookPlugin implements TerraformEnvironmentStagePlugin, Resettable {
9 | static hooks = [
10 | (ALL): new HookPoint(ALL),
11 | (INIT_COMMAND): new HookPoint(INIT_COMMAND),
12 | (PLAN): new HookPoint(PLAN),
13 | (PLAN_COMMAND): new HookPoint(PLAN_COMMAND),
14 | (APPLY): new HookPoint(APPLY),
15 | (APPLY_COMMAND): new HookPoint(APPLY_COMMAND)
16 | ]
17 |
18 | public static void init() {
19 | TerraformEnvironmentStageShellHookPlugin plugin = new TerraformEnvironmentStageShellHookPlugin()
20 | TerraformEnvironmentStage.addPlugin(plugin)
21 | }
22 |
23 | public static withHook(String hookPoint, String shellCommand, WhenToRun whenToRun = WhenToRun.ON_SUCCESS) {
24 | switch ( whenToRun ) {
25 | case WhenToRun.BEFORE:
26 | hooks[hookPoint].runBefore = shellCommand
27 | break
28 | case WhenToRun.AFTER:
29 | hooks[hookPoint].runAfterAlways = shellCommand
30 | break
31 | case WhenToRun.ON_FAILURE:
32 | hooks[hookPoint].runAfterOnFailure = shellCommand
33 | break
34 | case WhenToRun.ON_SUCCESS:
35 | hooks[hookPoint].runAfterOnSuccess = shellCommand
36 | break
37 | }
38 | return this
39 | }
40 |
41 | @Override
42 | public void apply(TerraformEnvironmentStage stage) {
43 | hooks.each { pointName, hookPoint ->
44 | if (hookPoint.isConfigured()) {
45 | stage.decorate(pointName, hookPoint.getClosure())
46 | }
47 | }
48 | }
49 |
50 | public static void reset() {
51 | hooks = [
52 | (ALL): new HookPoint(ALL),
53 | (INIT_COMMAND): new HookPoint(INIT_COMMAND),
54 | (PLAN): new HookPoint(PLAN),
55 | (PLAN_COMMAND): new HookPoint(PLAN_COMMAND),
56 | (APPLY): new HookPoint(APPLY),
57 | (APPLY_COMMAND): new HookPoint(APPLY_COMMAND)
58 | ]
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/TerraformFormatCommand.groovy:
--------------------------------------------------------------------------------
1 | class TerraformFormatCommand implements Resettable {
2 | private static boolean check = false
3 | private static boolean recursive = false
4 | private static boolean diff = false
5 |
6 | private Closure checkOptionPattern
7 | private Closure recursiveOptionPattern
8 | private Closure diffOptionPattern
9 |
10 | private static plugins = []
11 | private appliedPlugins = []
12 |
13 | public String toString() {
14 | applyPlugins()
15 | def pattern
16 | def parts = []
17 | parts << 'terraform fmt'
18 |
19 | pattern = checkOptionPattern ?: { it ? '-check=true' : null }
20 | parts << pattern(check)
21 |
22 | pattern = recursiveOptionPattern ?: { println "recursive is default in Terraform 0.11.x - this is an unsupported option" }
23 | parts << pattern(recursive)
24 |
25 | pattern = diffOptionPattern ?: { it ? '-diff=true' : null }
26 | parts << pattern(diff)
27 |
28 | parts.removeAll { it == null }
29 | return parts.join(' ')
30 | }
31 |
32 | public static withCheck(newValue = true) {
33 | check = newValue
34 | return this
35 | }
36 |
37 | public static boolean isCheckEnabled() {
38 | return check
39 | }
40 |
41 | public static withRecursive(newValue = true) {
42 | recursive = newValue
43 | return this
44 | }
45 |
46 | public static withDiff(newValue = true) {
47 | diff = newValue
48 | return this
49 | }
50 |
51 | public withCheckOptionPattern(Closure pattern) {
52 | checkOptionPattern = pattern
53 | return this
54 | }
55 |
56 | public withRecursiveOptionPattern(Closure pattern) {
57 | recursiveOptionPattern = pattern
58 | return this
59 | }
60 |
61 | public withDiffOptionPattern(Closure pattern) {
62 | diffOptionPattern = pattern
63 | return this
64 | }
65 |
66 | public applyPlugins() {
67 | def remainingPlugins = plugins - appliedPlugins
68 |
69 | for (TerraformFormatCommandPlugin plugin in remainingPlugins) {
70 | plugin.apply(this)
71 | appliedPlugins << plugin
72 | }
73 | }
74 |
75 | public static void addPlugin(TerraformFormatCommandPlugin plugin) {
76 | plugins << plugin
77 | }
78 |
79 | public static void setPlugins(plugins) {
80 | this.plugins = plugins
81 | }
82 |
83 | public static getPlugins() {
84 | return plugins
85 | }
86 |
87 | public static void reset() {
88 | check = false
89 | recursive = false
90 | diff = false
91 | this.plugins = []
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/src/TerraformFormatCommandPlugin.groovy:
--------------------------------------------------------------------------------
1 | interface TerraformFormatCommandPlugin {
2 | public void apply(TerraformFormatCommand command)
3 | }
4 |
--------------------------------------------------------------------------------
/src/TerraformImportCommand.groovy:
--------------------------------------------------------------------------------
1 | class TerraformImportCommand implements TerraformCommand, Resettable {
2 | private String environment
3 | private static String resource
4 | private static String targetPath
5 | private static plugins = []
6 | private appliedPlugins = []
7 |
8 | TerraformImportCommand(String environment) {
9 | this.environment = environment
10 | }
11 |
12 | public String toString() {
13 | applyPlugins()
14 | if (resource) {
15 | if (targetPath) {
16 | def pieces = []
17 | pieces << 'terraform'
18 | pieces << 'import'
19 | pieces << targetPath
20 | pieces << resource
21 |
22 | return pieces.join(' ')
23 | }
24 | else {
25 | return "echo \"No target path set, skipping 'terraform import'."
26 | }
27 | }
28 |
29 | return "echo \"No resource set, skipping 'terraform import'."
30 | }
31 |
32 | public TerraformImportCommand withResource(String resource) {
33 | this.resource = resource
34 | return this
35 | }
36 |
37 | public String getTargetPath() {
38 | return this.targetPath
39 | }
40 |
41 | public TerraformImportCommand withTargetPath(String targetPath) {
42 | this.targetPath = targetPath
43 | return this
44 | }
45 |
46 | public String getResource() {
47 | return this.resource
48 | }
49 |
50 | public static void reset() {
51 | this.resource = ''
52 | this.targetPath = ''
53 | this.resetPlugins()
54 | }
55 |
56 | /**
57 | * Assures that all plugins are applied, and are applied at most once. It
58 | * can be safely called multiple times.
59 | */
60 | public applyPlugins() {
61 | def remainingPlugins = plugins - appliedPlugins
62 |
63 | for (TerraformImportCommandPlugin plugin in remainingPlugins) {
64 | plugin.apply(this)
65 | appliedPlugins << plugin
66 | }
67 | }
68 |
69 | /**
70 | * Accepts a plugin of the appropriate type and adds it to the list of plugins.
71 | *
72 | * @param plugin The plugin to add
73 | */
74 | public static void addPlugin(TerraformImportCommandPlugin plugin) {
75 | plugins << plugin
76 | }
77 |
78 | public static void setPlugins(plugins) {
79 | this.plugins = plugins
80 | }
81 |
82 | public static getPlugins() {
83 | return plugins
84 | }
85 |
86 | /**
87 | * Reset plugins will reset the plugin list to a default set of plugins.
88 | *
89 | * @param defaultPlugins list of plugins to set, default: []
90 | */
91 | public static void resetPlugins(defaultPlugins = []) {
92 | this.plugins = defaultPlugins.clone()
93 | }
94 |
95 | public static instanceFor(String environment) {
96 | return new TerraformImportCommand(environment)
97 | }
98 |
99 | public String getEnvironment() {
100 | return this.environment
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/src/TerraformImportCommandPlugin.groovy:
--------------------------------------------------------------------------------
1 | interface TerraformImportCommandPlugin {
2 | public void apply(TerraformImportCommand command)
3 | }
4 |
--------------------------------------------------------------------------------
/src/TerraformImportPlugin.groovy:
--------------------------------------------------------------------------------
1 | import static TerraformEnvironmentStage.PLAN_COMMAND
2 |
3 | class TerraformImportPlugin implements TerraformEnvironmentStagePlugin, TerraformImportCommandPlugin {
4 |
5 | public static void init() {
6 | TerraformImportPlugin plugin = new TerraformImportPlugin()
7 |
8 | BuildWithParametersPlugin.withStringParameter([
9 | name: "IMPORT_RESOURCE",
10 | description: "Run `terraform import` on the resource specified prior to planning and applying."
11 | ])
12 | BuildWithParametersPlugin.withStringParameter([
13 | name: "IMPORT_TARGET_PATH",
14 | description: "The path in the Terraform state to import the spcified resource to."
15 | ])
16 | BuildWithParametersPlugin.withStringParameter([
17 | name: "IMPORT_ENVIRONMENT",
18 | description: "The environment in which to run the import."
19 | ])
20 |
21 | TerraformEnvironmentStage.addPlugin(plugin)
22 | TerraformImportCommand.addPlugin(plugin)
23 | }
24 |
25 | @Override
26 | public void apply(TerraformEnvironmentStage stage) {
27 | def resource = Jenkinsfile.instance.getEnv().IMPORT_RESOURCE
28 | def targetPath = Jenkinsfile.instance.getEnv().IMPORT_TARGET_PATH
29 | def environment = Jenkinsfile.instance.getEnv().IMPORT_ENVIRONMENT
30 | if (resource && targetPath && stage.environment == environment) {
31 | stage.decorate(PLAN_COMMAND, runTerraformImportCommand(stage.environment))
32 | }
33 | }
34 |
35 | @Override
36 | public void apply(TerraformImportCommand command) {
37 | def resource = Jenkinsfile.instance.getEnv().IMPORT_RESOURCE
38 | def targetPath = Jenkinsfile.instance.getEnv().IMPORT_TARGET_PATH
39 | if (resource && targetPath) {
40 | command.withResource(resource).withTargetPath(targetPath)
41 | }
42 | }
43 |
44 | public Closure runTerraformImportCommand(String environment) {
45 | def importCommand = TerraformImportCommand.instanceFor(environment)
46 | return { closure ->
47 | importCommand.applyPlugins()
48 | if (importCommand.resource) {
49 | echo "Running '${importCommand.toString()}'. TerraformImportPlugin is enabled."
50 | sh importCommand.toString()
51 | }
52 | closure()
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/TerraformInitCommand.groovy:
--------------------------------------------------------------------------------
1 | class TerraformInitCommand implements TerraformCommand, Resettable {
2 | private boolean input = false
3 | private String terraformBinary = "terraform"
4 | private String command = "init"
5 | String environment
6 | private prefixes = []
7 | private suffixes = []
8 | private backendConfigs = []
9 | private boolean doBackend = true
10 | private String directory
11 | private boolean chdir_flag = false
12 | private static plugins = []
13 | private appliedPlugins = []
14 |
15 | public TerraformInitCommand(String environment) {
16 | this.environment = environment
17 | }
18 |
19 | public TerraformInitCommand withInput(boolean input) {
20 | this.input = input
21 | return this
22 | }
23 |
24 | public TerraformInitCommand withPrefix(String prefix) {
25 | prefixes << prefix
26 | return this
27 | }
28 |
29 | public TerraformInitCommand withSuffix(String suffix) {
30 | suffixes << suffix
31 | return this
32 | }
33 |
34 | public TerraformInitCommand withDirectory(String directory) {
35 | this.directory = directory
36 | return this
37 | }
38 |
39 | public TerraformInitCommand withChangeDirectoryFlag() {
40 | this.chdir_flag = true
41 | return this
42 | }
43 |
44 | public TerraformInitCommand withBackendConfig(String backendConfig) {
45 | this.backendConfigs << backendConfig
46 | return this
47 | }
48 |
49 | public TerraformInitCommand withoutBackend() {
50 | this.doBackend = false
51 | return this
52 | }
53 |
54 | public String toString() {
55 | applyPlugins()
56 | def pieces = []
57 | pieces += prefixes
58 | pieces << terraformBinary
59 | if (directory && chdir_flag) {
60 | pieces << "-chdir=${directory}"
61 | }
62 | pieces << command
63 | if (!input) {
64 | pieces << "-input=false"
65 | }
66 | if (doBackend) {
67 | backendConfigs.each { config ->
68 | pieces << "-backend-config=${config}"
69 | }
70 | } else {
71 | pieces << "-backend=false"
72 | }
73 | if (directory && !chdir_flag) {
74 | pieces << directory
75 | }
76 |
77 | pieces += suffixes
78 |
79 | return pieces.join(' ')
80 | }
81 |
82 | public static TerraformInitCommand instanceFor(String environment) {
83 | return new TerraformInitCommand(environment)
84 | }
85 |
86 | public applyPlugins() {
87 | def remainingPlugins = plugins - appliedPlugins
88 |
89 | for (TerraformInitCommandPlugin plugin in remainingPlugins) {
90 | plugin.apply(this)
91 | appliedPlugins << plugin
92 | }
93 | }
94 |
95 | public static void addPlugin(TerraformInitCommandPlugin plugin) {
96 | plugins << plugin
97 | }
98 |
99 | public static void setPlugins(plugins) {
100 | this.plugins = plugins
101 | }
102 |
103 | public static getPlugins() {
104 | return plugins
105 | }
106 |
107 | public static void reset() {
108 | this.plugins = []
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/src/TerraformInitCommandPlugin.groovy:
--------------------------------------------------------------------------------
1 | interface TerraformInitCommandPlugin {
2 | public void apply(TerraformInitCommand command)
3 | }
4 |
--------------------------------------------------------------------------------
/src/TerraformLandscapePlugin.groovy:
--------------------------------------------------------------------------------
1 | class TerraformLandscapePlugin implements TerraformPlanCommandPlugin {
2 | public static void init() {
3 | TerraformLandscapePlugin plugin = new TerraformLandscapePlugin()
4 |
5 | TerraformPlanCommand.addPlugin(plugin)
6 | }
7 |
8 | @Override
9 | public void apply(TerraformPlanCommand command) {
10 | command.withSuffix("| landscape")
11 | }
12 |
13 | }
14 |
--------------------------------------------------------------------------------
/src/TerraformOutputCommand.groovy:
--------------------------------------------------------------------------------
1 | class TerraformOutputCommand implements TerraformCommand, Resettable {
2 | private String command = "output"
3 | private String terraformBinary = "terraform"
4 | String environment
5 | private boolean json = false
6 | private String redirectFile
7 | private String stateFilePath
8 | private static plugins = []
9 | private appliedPlugins = []
10 |
11 | public TerraformOutputCommand(String environment) {
12 | this.environment = environment
13 | }
14 |
15 | public TerraformOutputCommand withJson(boolean json) {
16 | this.json = json
17 | return this
18 | }
19 |
20 | public TerraformOutputCommand withRedirectFile(String redirectFile) {
21 | this.redirectFile = redirectFile
22 | return this
23 | }
24 |
25 | public String toString() {
26 | applyPlugins()
27 | def pieces = []
28 | pieces << terraformBinary
29 | pieces << command
30 |
31 | if (json) {
32 | pieces << "-json"
33 | }
34 |
35 | if (redirectFile) {
36 | pieces << ">${redirectFile}"
37 | }
38 |
39 | return pieces.join(' ')
40 | }
41 |
42 | public static TerraformOutputCommand instanceFor(String environment) {
43 | return new TerraformOutputCommand(environment).withJson(false)
44 | }
45 |
46 | public String getEnvironment() {
47 | return environment
48 | }
49 |
50 | public applyPlugins() {
51 | def remainingPlugins = plugins - appliedPlugins
52 |
53 | for (TerraformOutputCommandPlugin plugin in remainingPlugins) {
54 | plugin.apply(this)
55 | appliedPlugins << plugin
56 | }
57 | }
58 |
59 | public static void addPlugin(TerraformOutputCommandPlugin plugin) {
60 | plugins << plugin
61 | }
62 |
63 | public static void setPlugins(plugins) {
64 | this.plugins = plugins
65 | }
66 |
67 | public static getPlugins() {
68 | return plugins
69 | }
70 |
71 | public static void reset() {
72 | this.plugins = []
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/src/TerraformOutputCommandPlugin.groovy:
--------------------------------------------------------------------------------
1 | interface TerraformOutputCommandPlugin {
2 | public void apply(TerraformOutputCommand command)
3 | }
4 |
--------------------------------------------------------------------------------
/src/TerraformOutputOnlyPlugin.groovy:
--------------------------------------------------------------------------------
1 | import static TerraformEnvironmentStage.INIT_COMMAND
2 | import static TerraformEnvironmentStage.PLAN_COMMAND
3 | import static TerraformEnvironmentStage.APPLY
4 | import static TerraformEnvironmentStage.CONFIRM
5 |
6 | class TerraformOutputOnlyPlugin implements TerraformEnvironmentStagePlugin, TerraformOutputCommandPlugin {
7 |
8 | public static void init() {
9 | TerraformOutputOnlyPlugin plugin = new TerraformOutputOnlyPlugin()
10 |
11 | BuildWithParametersPlugin.withBooleanParameter([
12 | name: "SHOW_OUTPUTS_ONLY",
13 | description: "Only run 'terraform output' to show outputs, skipping plan and apply."
14 | ])
15 | BuildWithParametersPlugin.withBooleanParameter([
16 | name: "JSON_FORMAT_OUTPUTS",
17 | description: "Render 'terraform output' results as JSON. Only applies if SHOW_OUTPUTS_ONLY is selected."
18 | ])
19 | BuildWithParametersPlugin.withStringParameter([
20 | name: "REDIRECT_OUTPUTS_TO_FILE",
21 | description: "Filename relative to the current workspace to redirect output to. Only applies if 'SHOW_OUTPUTS_ONLY' is selected."
22 | ])
23 |
24 | TerraformEnvironmentStage.addPlugin(plugin)
25 | TerraformOutputCommand.addPlugin(plugin)
26 | }
27 |
28 | public Closure skipStage(String stageName) {
29 | return { closure ->
30 | echo "Skipping ${stageName} stage. TerraformOutputOnlyPlugin is enabled."
31 | }
32 | }
33 |
34 | public Closure runTerraformOutputCommand(String environment) {
35 | def outputCommand = TerraformOutputCommand.instanceFor(environment)
36 | return { closure ->
37 | closure()
38 | echo "Running 'terraform output'. TerraformOutputOnlyPlugin is enabled."
39 | sh outputCommand.toString()
40 | }
41 | }
42 |
43 | @Override
44 | public void apply(TerraformEnvironmentStage stage) {
45 | if (Jenkinsfile.instance.getEnv().SHOW_OUTPUTS_ONLY == 'true') {
46 | stage.decorate(INIT_COMMAND, runTerraformOutputCommand(stage.getEnvironment()))
47 | stage.decorate(PLAN_COMMAND, skipStage(PLAN_COMMAND))
48 | stage.decorateAround(CONFIRM, skipStage(CONFIRM))
49 | stage.decorateAround(APPLY, skipStage(APPLY))
50 | }
51 | }
52 |
53 | @Override
54 | public void apply(TerraformOutputCommand command) {
55 | if (Jenkinsfile.instance.getEnv().JSON_FORMAT_OUTPUTS == 'true') {
56 | command.withJson(true)
57 | }
58 | if (Jenkinsfile.instance.getEnv().REDIRECT_OUTPUTS_TO_FILE) {
59 | command.withRedirectFile(Jenkinsfile.instance.getEnv().REDIRECT_OUTPUTS_TO_FILE)
60 | }
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/TerraformPlanCommandPlugin.groovy:
--------------------------------------------------------------------------------
1 | interface TerraformPlanCommandPlugin {
2 | public void apply(TerraformPlanCommand command)
3 | }
4 |
--------------------------------------------------------------------------------
/src/TerraformPluginVersion.groovy:
--------------------------------------------------------------------------------
1 | abstract class TerraformPluginVersion implements TerraformValidateStagePlugin,
2 | TerraformValidateCommandPlugin,
3 | TerraformInitCommandPlugin,
4 | TerraformPlanCommandPlugin,
5 | TerraformApplyCommandPlugin,
6 | TerraformFormatCommandPlugin {
7 | @Override
8 | public void apply(TerraformValidateStage validateStage) {
9 | }
10 |
11 | @Override
12 | public void apply(TerraformValidateCommand command) {
13 | }
14 |
15 | @Override
16 | public void apply(TerraformInitCommand command) {
17 | }
18 |
19 | @Override
20 | public void apply(TerraformPlanCommand command) {
21 | }
22 |
23 | @Override
24 | public void apply(TerraformApplyCommand command) {
25 | }
26 |
27 | @Override
28 | public void apply(TerraformFormatCommand command) {
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/TerraformPluginVersion11.groovy:
--------------------------------------------------------------------------------
1 | class TerraformPluginVersion11 extends TerraformPluginVersion {
2 |
3 | @Override
4 | public void apply(TerraformValidateCommand command) {
5 | command.withArgument('-check-variables=false')
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/src/TerraformPluginVersion12.groovy:
--------------------------------------------------------------------------------
1 | class TerraformPluginVersion12 extends TerraformPluginVersion {
2 |
3 | @Override
4 | public void apply(TerraformValidateStage validateStage) {
5 | validateStage.decorate(TerraformValidateStage.VALIDATE, addInitBefore())
6 | }
7 |
8 | public Closure addInitBefore() {
9 | return { closure ->
10 | def initCommand = initCommandForValidate()
11 | sh initCommand.toString()
12 |
13 | closure()
14 | }
15 | }
16 |
17 | public static TerraformInitCommand initCommandForValidate() {
18 | return TerraformInitCommand.instanceFor('validate').withoutBackend()
19 | }
20 |
21 | public void apply(TerraformPlanCommand command) {
22 | command.withVariablePattern { key, value -> "-var='${key}=${value}'" }
23 | .withMapPattern { map ->
24 | def result = map.collect { key, value -> "\"${key}\":\"${value}\"" }.join(',')
25 | return "{${result}}"
26 | }
27 | }
28 |
29 | public void apply(TerraformApplyCommand command) {
30 | command.withVariablePattern { key, value -> "-var='${key}=${value}'" }
31 | .withMapPattern { map ->
32 | def result = map.collect { key, value -> "\"${key}\":\"${value}\"" }.join(',')
33 | return "{${result}}"
34 | }
35 | }
36 |
37 | public void apply(TerraformFormatCommand command) {
38 | command.withCheckOptionPattern { it ? '-check' : null }
39 | command.withRecursiveOptionPattern { it ? '-recursive' : null }
40 | command.withDiffOptionPattern { it ? '-diff' : null }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/TerraformPluginVersion15.groovy:
--------------------------------------------------------------------------------
1 | class TerraformPluginVersion15 extends TerraformPluginVersion12 {
2 |
3 | public void apply(TerraformValidateCommand command) {
4 | super.apply(command.withChangeDirectoryFlag())
5 | }
6 |
7 | public void apply(TerraformInitCommand command) {
8 | super.apply(command.withChangeDirectoryFlag())
9 | }
10 |
11 | public void apply(TerraformPlanCommand command) {
12 | super.apply(command.withChangeDirectoryFlag())
13 | }
14 |
15 | public void apply(TerraformApplyCommand command) {
16 | super.apply(command.withChangeDirectoryFlag())
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/TerraformStartDirectoryPlugin.groovy:
--------------------------------------------------------------------------------
1 | import static TerraformValidateStage.ALL
2 | import static TerraformEnvironmentStage.ALL
3 |
4 | public class TerraformStartDirectoryPlugin implements TerraformValidateStagePlugin, TerraformEnvironmentStagePlugin, Resettable {
5 | private static String directory = "./terraform/"
6 |
7 | public static void init() {
8 | def plugin = new TerraformStartDirectoryPlugin()
9 |
10 | TerraformValidateStage.addPlugin(plugin)
11 | TerraformEnvironmentStage.addPlugin(plugin)
12 | }
13 |
14 | public static withDirectory(String directory) {
15 | this.directory = directory
16 | return this
17 | }
18 |
19 | @Override
20 | public void apply(TerraformValidateStage stage) {
21 | stage.decorate(TerraformValidateStage.ALL, addDirectory())
22 | }
23 |
24 | @Override
25 | public void apply(TerraformEnvironmentStage stage) {
26 | stage.decorate(TerraformEnvironmentStage.ALL, addDirectory())
27 | }
28 |
29 | public Closure addDirectory() {
30 | return { closure ->
31 | dir(this.directory) {
32 | closure()
33 | }
34 | }
35 | }
36 |
37 | public static reset() {
38 | directory = "./terraform/"
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/TerraformTaintCommand.groovy:
--------------------------------------------------------------------------------
1 | class TerraformTaintCommand implements TerraformCommand, Resettable {
2 | private String command = "taint"
3 | private String resource
4 | private String environment
5 | private static plugins = []
6 | private appliedPlugins = []
7 |
8 | public TerraformTaintCommand(String environment) {
9 | this.environment = environment
10 | }
11 |
12 | public TerraformTaintCommand withResource(String resource) {
13 | this.resource = resource
14 | return this
15 | }
16 |
17 | public String toString() {
18 | applyPlugins()
19 | def parts = []
20 | parts << 'terraform'
21 | parts << command
22 | parts << resource
23 |
24 | parts.removeAll { it == null }
25 | return parts.join(' ')
26 | }
27 |
28 | public String getResource() {
29 | return this.resource
30 | }
31 |
32 | public static TerraformTaintCommand instanceFor(String environment) {
33 | return new TerraformTaintCommand(environment)
34 | }
35 |
36 | public String getEnvironment() {
37 | return this.environment
38 | }
39 |
40 | public applyPlugins() {
41 | def remainingPlugins = plugins - appliedPlugins
42 |
43 | for (TerraformTaintCommandPlugin plugin in remainingPlugins) {
44 | plugin.apply(this)
45 | appliedPlugins << plugin
46 | }
47 | }
48 |
49 | public static void addPlugin(TerraformTaintCommandPlugin plugin) {
50 | plugins << plugin
51 | }
52 |
53 | public static void setPlugins(plugins) {
54 | this.plugins = plugins
55 | }
56 |
57 | public static getPlugins() {
58 | return plugins
59 | }
60 |
61 | public static void reset() {
62 | this.plugins = []
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/src/TerraformTaintCommandPlugin.groovy:
--------------------------------------------------------------------------------
1 | interface TerraformTaintCommandPlugin {
2 | public void apply(TerraformTaintCommand command)
3 | }
4 |
--------------------------------------------------------------------------------
/src/TerraformTaintPlugin.groovy:
--------------------------------------------------------------------------------
1 | import static TerraformEnvironmentStage.PLAN_COMMAND
2 |
3 | class TerraformTaintPlugin implements TerraformEnvironmentStagePlugin, TerraformTaintCommandPlugin, TerraformUntaintCommandPlugin, Resettable {
4 | private static DEFAULT_BRANCHES = ['main', 'master']
5 | private static branches = DEFAULT_BRANCHES
6 |
7 | public static void init() {
8 | TerraformTaintPlugin plugin = new TerraformTaintPlugin()
9 |
10 | BuildWithParametersPlugin.withStringParameter([
11 | name: "TAINT_RESOURCE",
12 | description: 'Run `terraform taint` on the resource specified prior to planning and applying.'
13 | ])
14 |
15 | BuildWithParametersPlugin.withStringParameter([
16 | name: "UNTAINT_RESOURCE",
17 | description: 'Run `terraform untaint` on the resource specified prior to planning and applying.'
18 | ])
19 |
20 | TerraformEnvironmentStage.addPlugin(plugin)
21 | TerraformTaintCommand.addPlugin(plugin)
22 | TerraformUntaintCommand.addPlugin(plugin)
23 | }
24 |
25 | public static onBranch(String branchName) {
26 | this.branches << branchName
27 | return this
28 | }
29 |
30 | public void apply(TerraformEnvironmentStage stage) {
31 | stage.decorate(PLAN_COMMAND, runTerraformUntaintCommand(stage.getEnvironment()))
32 | stage.decorate(PLAN_COMMAND, runTerraformTaintCommand(stage.getEnvironment()))
33 | }
34 |
35 | public void apply(TerraformTaintCommand command) {
36 | def resource = Jenkinsfile.instance.getEnv().TAINT_RESOURCE
37 | if (resource) {
38 | command.withResource(resource)
39 | }
40 | }
41 |
42 | public void apply(TerraformUntaintCommand command) {
43 | def resource = Jenkinsfile.instance.getEnv().UNTAINT_RESOURCE
44 | if (resource) {
45 | command.withResource(resource)
46 | }
47 | }
48 |
49 | public boolean shouldApply() {
50 | // Check branches
51 | if (branches.contains(Jenkinsfile.instance.getEnv().BRANCH_NAME)) {
52 | return true
53 | } else if (null == Jenkinsfile.instance.getEnv().BRANCH_NAME) {
54 | return true
55 | }
56 |
57 | return false
58 | }
59 |
60 | public Closure runTerraformTaintCommand(String environment) {
61 | def taintCommand = TerraformTaintCommand.instanceFor(environment)
62 | return { closure ->
63 | if (shouldApply() && Jenkinsfile.instance.getEnv().TAINT_RESOURCE) {
64 | echo "Running '${taintCommand.toString()}'. TerraformTaintPlugin is enabled."
65 | sh taintCommand.toString()
66 | }
67 | closure()
68 | }
69 | }
70 |
71 | public Closure runTerraformUntaintCommand(String environment) {
72 | def untaintCommand = TerraformUntaintCommand.instanceFor(environment)
73 | return { closure ->
74 | if (shouldApply() && Jenkinsfile.instance.getEnv().UNTAINT_RESOURCE) {
75 | echo "Running '${untaintCommand.toString()}'. TerraformTaintPlugin is enabled."
76 | sh untaintCommand.toString()
77 | }
78 | closure()
79 | }
80 | }
81 |
82 | public static reset() {
83 | this.branches = DEFAULT_BRANCHES.clone()
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/src/TerraformUntaintCommand.groovy:
--------------------------------------------------------------------------------
1 | class TerraformUntaintCommand implements TerraformCommand, Resettable {
2 | private String command = "untaint"
3 | private String resource
4 | private String environment
5 | private static plugins = []
6 | private appliedPlugins = []
7 |
8 | public TerraformUntaintCommand(String environment) {
9 | this.environment = environment
10 | }
11 |
12 | public TerraformUntaintCommand withResource(String resource) {
13 | this.resource = resource
14 | return this
15 | }
16 |
17 | public String toString() {
18 | applyPlugins()
19 | def parts = []
20 | parts << 'terraform'
21 | parts << command
22 | parts << resource
23 |
24 | parts.removeAll { it == null }
25 | return parts.join(' ')
26 | }
27 |
28 | public String getResource() {
29 | return this.resource
30 | }
31 |
32 | public static TerraformUntaintCommand instanceFor(String environment) {
33 | return new TerraformUntaintCommand(environment)
34 | }
35 |
36 | public String getEnvironment() {
37 | return this.environment
38 | }
39 |
40 | public applyPlugins() {
41 | def remainingPlugins = plugins - appliedPlugins
42 |
43 | for (TerraformUntaintCommandPlugin plugin in remainingPlugins) {
44 | plugin.apply(this)
45 | appliedPlugins << plugin
46 | }
47 | }
48 |
49 | public static void addPlugin(TerraformUntaintCommandPlugin plugin) {
50 | plugins << plugin
51 | }
52 |
53 | public static void setPlugins(plugins) {
54 | this.plugins = plugins
55 | }
56 |
57 | public static getPlugins() {
58 | return plugins
59 | }
60 |
61 | public static void reset() {
62 | this.plugins = []
63 | }
64 | }
65 |
66 |
--------------------------------------------------------------------------------
/src/TerraformUntaintCommandPlugin.groovy:
--------------------------------------------------------------------------------
1 | interface TerraformUntaintCommandPlugin {
2 | public void apply(TerraformUntaintCommand command)
3 | }
4 |
--------------------------------------------------------------------------------
/src/TerraformValidateCommand.groovy:
--------------------------------------------------------------------------------
1 | class TerraformValidateCommand implements Resettable {
2 | private String terraformBinary = "terraform"
3 | private String command = "validate"
4 | private arguments = []
5 | private prefixes = []
6 | private suffixes = []
7 | private String directory
8 | private boolean chdir_flag = false
9 | private static plugins = []
10 | private appliedPlugins = []
11 |
12 | public TerraformValidateCommand() {
13 | }
14 |
15 | public TerraformValidateCommand withArgument(String argument) {
16 | this.arguments << argument
17 | return this
18 | }
19 |
20 | public TerraformValidateCommand withPrefix(String prefix) {
21 | prefixes << prefix
22 | return this
23 | }
24 |
25 | public TerraformValidateCommand withSuffix(String suffix) {
26 | suffixes << suffix
27 | return this
28 | }
29 |
30 | public TerraformValidateCommand withDirectory(String directory) {
31 | this.directory = directory
32 | return this
33 | }
34 |
35 | public TerraformValidateCommand withChangeDirectoryFlag() {
36 | this.chdir_flag = true
37 | return this
38 | }
39 |
40 | public String toString() {
41 | applyPlugins()
42 | def pieces = []
43 | pieces = pieces + prefixes
44 | pieces << terraformBinary
45 | if (directory && chdir_flag) {
46 | pieces << "-chdir=${directory}"
47 | }
48 | pieces << command
49 | for (String argument in arguments) {
50 | pieces << argument
51 | }
52 | if (directory && !chdir_flag) {
53 | pieces << directory
54 | }
55 | pieces += suffixes
56 |
57 | return pieces.join(' ')
58 | }
59 |
60 | public static TerraformValidateCommand instance() {
61 | return new TerraformValidateCommand()
62 | }
63 |
64 | public applyPlugins() {
65 | def remainingPlugins = plugins - appliedPlugins
66 |
67 | for (TerraformValidateCommandPlugin plugin in remainingPlugins) {
68 | plugin.apply(this)
69 | appliedPlugins << plugin
70 | }
71 | }
72 |
73 | public static void addPlugin(TerraformValidateCommandPlugin plugin) {
74 | plugins << plugin
75 | }
76 |
77 | public static void setPlugins(plugins) {
78 | this.plugins = plugins
79 | }
80 |
81 | public static getPlugins() {
82 | return plugins
83 | }
84 |
85 | public static void reset() {
86 | this.plugins = []
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/src/TerraformValidateCommandPlugin.groovy:
--------------------------------------------------------------------------------
1 | interface TerraformValidateCommandPlugin {
2 | public void apply(TerraformValidateCommand command)
3 | }
4 |
--------------------------------------------------------------------------------
/src/TerraformValidateStage.groovy:
--------------------------------------------------------------------------------
1 | class TerraformValidateStage implements Stage, DecoratableStage, Resettable {
2 | private Jenkinsfile jenkinsfile
3 | private StageDecorations decorations
4 |
5 | private static globalPlugins = []
6 |
7 | public static final String ALL = 'all'
8 | public static final String VALIDATE = 'validate'
9 |
10 | public TerraformValidateStage() {
11 | this.jenkinsfile = Jenkinsfile.instance
12 | this.decorations = new StageDecorations()
13 | }
14 |
15 | public Stage then(Stage nextStage) {
16 | return new BuildGraph(this).then(nextStage)
17 | }
18 |
19 | public void build() {
20 | Jenkinsfile.build(pipelineConfiguration())
21 | }
22 |
23 | public Closure pipelineConfiguration() {
24 | applyPlugins()
25 |
26 | def validateCommand = TerraformValidateCommand.instance()
27 |
28 | return {
29 | node(jenkinsfile.getNodeName()) {
30 | deleteDir()
31 | checkout(scm)
32 |
33 | decorations.apply(ALL) {
34 | stage("validate") {
35 | decorations.apply(VALIDATE) {
36 | sh validateCommand.toString()
37 | }
38 | }
39 | }
40 | }
41 | }
42 | }
43 |
44 | public void decorate(Closure decoration) {
45 | decorations.add(ALL, decoration)
46 | }
47 |
48 | public decorate(String stageName, Closure decoration) {
49 | decorations.add(stageName, decoration)
50 | }
51 |
52 | public static addPlugin(plugin) {
53 | this.globalPlugins << plugin
54 | }
55 |
56 | public void applyPlugins() {
57 | for (plugin in globalPlugins) {
58 | plugin.apply(this)
59 | }
60 | }
61 |
62 | public static getPlugins() {
63 | return this.globalPlugins
64 | }
65 |
66 | public static void reset() {
67 | this.globalPlugins = []
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/TerraformValidateStagePlugin.groovy:
--------------------------------------------------------------------------------
1 | interface TerraformValidateStagePlugin {
2 | public void apply(TerraformValidateStage stage)
3 | }
4 |
--------------------------------------------------------------------------------
/src/TfvarsFilesPlugin.groovy:
--------------------------------------------------------------------------------
1 | class TfvarsFilesPlugin implements TerraformPlanCommandPlugin, TerraformApplyCommandPlugin, Resettable {
2 |
3 | static String directory = "."
4 | static List globalFiles = []
5 |
6 | static withDirectory(String directory) {
7 | TfvarsFilesPlugin.directory = directory
8 | return this
9 | }
10 |
11 | static withGlobalVarFile(String fileName) {
12 | TfvarsFilesPlugin.globalFiles << fileName
13 | return this
14 | }
15 |
16 | static void init() {
17 | def plugin = new TfvarsFilesPlugin()
18 |
19 | TerraformPlanCommand.addPlugin(plugin)
20 | TerraformApplyCommand.addPlugin(plugin)
21 | }
22 |
23 | private originalContext = Jenkinsfile.instance.original
24 |
25 | @Override
26 | void apply(TerraformApplyCommand command) {
27 | applyToCommand(command)
28 | }
29 |
30 | @Override
31 | void apply(TerraformPlanCommand command) {
32 | applyToCommand(command)
33 | }
34 |
35 | void applyToCommand(command) {
36 | def files = globalFiles.collect { file ->
37 | return "${directory}/${file}"
38 | } + "${directory}/${command.environment}.tfvars"
39 |
40 | files.each { file ->
41 | if (originalContext.fileExists(file)) {
42 | command.withArgument("-var-file=${file}")
43 | } else {
44 | originalContext.echo "${file} does not exist."
45 | }
46 | }
47 | }
48 |
49 | public static void reset() {
50 | directory = "."
51 | globalFiles = []
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/ValidateFormatPlugin.groovy:
--------------------------------------------------------------------------------
1 | class ValidateFormatPlugin implements TerraformValidateStagePlugin {
2 | public static init() {
3 | TerraformValidateStage.addPlugin(new ValidateFormatPlugin())
4 | TerraformFormatCommand.withCheck()
5 | }
6 |
7 | public void apply(TerraformValidateStage stage) {
8 | stage.decorate(TerraformValidateStage.VALIDATE, formatClosure())
9 | }
10 |
11 | public Closure formatClosure() {
12 | return { closure ->
13 | closure()
14 | def formatCommand = new TerraformFormatCommand()
15 | sh formatCommand.toString()
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/WhenToRun.groovy:
--------------------------------------------------------------------------------
1 | public final enum WhenToRun {
2 | BEFORE, ON_SUCCESS, ON_FAILURE, AFTER
3 | }
4 |
--------------------------------------------------------------------------------
/src/WithAwsPlugin.groovy:
--------------------------------------------------------------------------------
1 | import static TerraformEnvironmentStage.ALL
2 |
3 | class WithAwsPlugin implements TerraformEnvironmentStagePlugin, Resettable {
4 | private static role
5 | private static duration
6 |
7 | public static void init() {
8 | WithAwsPlugin plugin = new WithAwsPlugin()
9 |
10 | TerraformEnvironmentStage.addPlugin(plugin)
11 | }
12 |
13 | @Override
14 | public void apply(TerraformEnvironmentStage stage) {
15 | String environment = stage.getEnvironment()
16 |
17 | stage.decorate(ALL, addWithAwsRole(environment))
18 | }
19 |
20 | public Closure addWithAwsRole(String environment) {
21 | return { closure ->
22 | String iamRole = getRole(environment)
23 | Integer sessionDuration = getDuration()
24 |
25 | if (iamRole != null) {
26 | withAWS(role: iamRole, duration: sessionDuration) {
27 | sh "echo Running AWS commands under the role: ${iamRole}"
28 | closure()
29 | }
30 | } else {
31 | sh "echo no role found. Skipping withAWS"
32 | closure()
33 | }
34 | }
35 | }
36 |
37 | public static withRole(String role = null) {
38 | this.role = role
39 |
40 | return this
41 | }
42 |
43 | public static withDuration(Integer duration = 3600) {
44 | this.duration = duration
45 |
46 | return this
47 | }
48 |
49 | public String getRole(String environment) {
50 | def tempRole = this.role
51 |
52 | if (tempRole == null) {
53 | tempRole = Jenkinsfile.instance.getEnv()['AWS_ROLE_ARN']
54 | }
55 |
56 | if (tempRole == null) {
57 | tempRole = Jenkinsfile.instance.getEnv()["${environment.toUpperCase()}_AWS_ROLE_ARN"]
58 | }
59 |
60 | if (tempRole == null) {
61 | tempRole = Jenkinsfile.instance.getEnv()["${environment}_AWS_ROLE_ARN"]
62 | }
63 |
64 | return tempRole
65 | }
66 |
67 | public Integer getDuration() {
68 | def tempDuration = this.@duration
69 |
70 | if (tempDuration == null) {
71 | tempDuration = 3600
72 | }
73 |
74 | return tempDuration
75 | }
76 |
77 | public static void reset() {
78 | this.role = null
79 | this.duration = 3600
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/test/AnsiColorPluginTest.groovy:
--------------------------------------------------------------------------------
1 | import static org.hamcrest.MatcherAssert.assertThat
2 | import static org.hamcrest.Matchers.equalTo
3 | import static org.hamcrest.Matchers.hasItem
4 | import static org.hamcrest.Matchers.instanceOf
5 | import static org.mockito.Mockito.any;
6 | import static org.mockito.Mockito.doReturn;
7 | import static org.mockito.Mockito.eq;
8 | import static org.mockito.Mockito.mock;
9 | import static org.mockito.Mockito.spy;
10 | import static org.mockito.Mockito.verify;
11 |
12 | import org.junit.jupiter.api.Nested
13 | import org.junit.jupiter.api.Test
14 | import org.junit.jupiter.api.extension.ExtendWith
15 |
16 | @ExtendWith(ResetStaticStateExtension.class)
17 | class AnsiColorPluginTest {
18 | @Nested
19 | public class Init {
20 | @Test
21 | void modifiesTerraformValidateStage() {
22 | AnsiColorPlugin.init()
23 |
24 | Collection actualPlugins = TerraformValidateStage.getPlugins()
25 | assertThat(actualPlugins, hasItem(instanceOf(AnsiColorPlugin.class)))
26 | }
27 |
28 | @Test
29 | void modifiesTerraformEnvironmentStage() {
30 | AnsiColorPlugin.init()
31 |
32 | Collection actualPlugins = TerraformEnvironmentStage.getPlugins()
33 | assertThat(actualPlugins, hasItem(instanceOf(AnsiColorPlugin.class)))
34 | }
35 | }
36 |
37 | @Nested
38 | public class ApplyTerraformValidateStage {
39 | @Test
40 | void decoratesTheStage() {
41 | def stage = mock(TerraformValidateStage.class)
42 | def plugin = spy(new AnsiColorPlugin())
43 | def expectedClosure = { -> }
44 | doReturn(expectedClosure).when(plugin).addColor()
45 |
46 | plugin.apply(stage)
47 |
48 | verify(stage).decorate(expectedClosure)
49 | }
50 | }
51 |
52 | @Nested
53 | public class ApplyTerraformEnvironmentStage {
54 | @Test
55 | void decoratesThePlanStep() {
56 | def stage = mock(TerraformEnvironmentStage.class)
57 | def plugin = spy(new AnsiColorPlugin())
58 | def expectedClosure = { -> }
59 | doReturn(expectedClosure).when(plugin).addColor()
60 |
61 | plugin.apply(stage)
62 |
63 | verify(stage).decorate(TerraformEnvironmentStage.PLAN, expectedClosure)
64 | }
65 |
66 | @Test
67 | void decoratesTheApplyStep() {
68 | def stage = mock(TerraformEnvironmentStage.class)
69 | def plugin = spy(new AnsiColorPlugin())
70 | def expectedClosure = { -> }
71 | doReturn(expectedClosure).when(plugin).addColor()
72 |
73 | plugin.apply(stage)
74 |
75 | verify(stage).decorate(TerraformEnvironmentStage.APPLY, expectedClosure)
76 | }
77 | }
78 |
79 | @Nested
80 | public class AddColor {
81 | @Test
82 | void executesTheInnerClosure() {
83 | def wasCalled = false
84 | def mockWorkflowScript = new MockWorkflowScript()
85 | def innerClosure = { -> wasCalled = true }
86 | def plugin = new AnsiColorPlugin()
87 |
88 | def colorClosure = plugin.addColor()
89 | colorClosure.delegate = mockWorkflowScript
90 | colorClosure(innerClosure)
91 |
92 | assertThat(wasCalled, equalTo(true))
93 | }
94 |
95 | @Test
96 | void callsAnsiColorWithXTermArgument() {
97 | def mockWorkflowScript = spy(new MockWorkflowScript())
98 | def plugin = new AnsiColorPlugin()
99 |
100 | def colorClosure = plugin.addColor()
101 | colorClosure.delegate = mockWorkflowScript
102 | colorClosure() { -> }
103 |
104 | verify(mockWorkflowScript).ansiColor(eq('xterm'), any(Closure.class))
105 | }
106 | }
107 | }
108 |
109 |
--------------------------------------------------------------------------------
/test/BuildGraphTest.groovy:
--------------------------------------------------------------------------------
1 | import static org.mockito.Mockito.inOrder
2 | import static org.mockito.Mockito.mock
3 | import static org.mockito.Mockito.times
4 | import static org.mockito.Mockito.verify
5 |
6 | import org.mockito.InOrder
7 | import org.junit.jupiter.api.Nested
8 | import org.junit.jupiter.api.Test
9 |
10 | class BuildGraphTest {
11 | @Nested
12 | public class WithASingleStage {
13 | @Test
14 | void buildsTheStageThatItWasCreatedWith() {
15 | Stage startStage = mock(Stage.class)
16 | BuildGraph graph = new BuildGraph(startStage)
17 |
18 | graph.build()
19 |
20 | verify(startStage, times(1)).build()
21 | }
22 | }
23 |
24 | @Nested
25 | public class WithMultipleStages {
26 | @Test
27 | void buildsTheStagesInOrder() {
28 | Stage stage1 = mock(Stage.class)
29 | Stage stage2 = mock(Stage.class)
30 | Stage stage3 = mock(Stage.class)
31 | Stage stage4 = mock(Stage.class)
32 |
33 | BuildGraph graph = new BuildGraph(stage1).then(stage2)
34 | .then(stage3)
35 | .then(stage4)
36 | .build()
37 |
38 | InOrder inOrder = inOrder(stage1, stage2, stage3, stage4);
39 | inOrder.verify(stage1, times(1)).build()
40 | inOrder.verify(stage2, times(1)).build()
41 | inOrder.verify(stage3, times(1)).build()
42 | inOrder.verify(stage4, times(1)).build()
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/test/BuildStageTest.groovy:
--------------------------------------------------------------------------------
1 | import static org.mockito.Mockito.spy
2 | import static org.mockito.Mockito.doReturn
3 |
4 | import org.junit.jupiter.api.Nested
5 | import org.junit.jupiter.api.Test
6 |
7 | class BuildStageTest {
8 |
9 | @Nested
10 | public class Build {
11 | @Test
12 | void buildsWithoutError() {
13 | def expectedPipelineConfig = { -> }
14 | MockJenkinsfile.withMockedOriginal()
15 |
16 | BuildStage stage = spy(new BuildStage())
17 | doReturn(expectedPipelineConfig).when(stage).pipelineConfiguration()
18 |
19 | stage.build()
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/test/DefaultEnvironmentPluginTest.groovy:
--------------------------------------------------------------------------------
1 | import static org.hamcrest.Matchers.hasItem
2 | import static org.hamcrest.Matchers.instanceOf
3 | import static org.hamcrest.MatcherAssert.assertThat
4 |
5 | import org.junit.jupiter.api.Nested
6 | import org.junit.jupiter.api.Test
7 |
8 | class DefaultEnvironmentPluginTest {
9 | @Nested
10 | public class Init {
11 | @Test
12 | void modifiesTerraformEnvironmentStageByDefault() {
13 | Collection actualPlugins = TerraformEnvironmentStage.getPlugins()
14 |
15 | assertThat(actualPlugins, hasItem(instanceOf(DefaultEnvironmentPlugin.class)))
16 | }
17 | }
18 | }
19 |
20 |
--------------------------------------------------------------------------------
/test/FileParametersPluginTest.groovy:
--------------------------------------------------------------------------------
1 | import static org.hamcrest.MatcherAssert.assertThat
2 | import static org.hamcrest.Matchers.hasItem
3 | import static org.hamcrest.Matchers.instanceOf
4 | import static org.junit.jupiter.api.Assertions.assertEquals
5 |
6 | import org.junit.jupiter.api.BeforeEach
7 | import org.junit.jupiter.api.Nested
8 | import org.junit.jupiter.api.Test
9 | import org.junit.jupiter.api.extension.ExtendWith
10 |
11 | @ExtendWith(ResetStaticStateExtension.class)
12 | class FileParametersPluginTest {
13 | @Nested
14 | public class Init {
15 | @Test
16 | void modifiesTerraformEnvironmentStage() {
17 | FileParametersPlugin.init()
18 |
19 | Collection actualPlugins = TerraformEnvironmentStage.getPlugins()
20 | assertThat(actualPlugins, hasItem(instanceOf(FileParametersPlugin.class)))
21 | }
22 | }
23 |
24 | @Nested
25 | public class GetVariables {
26 | @BeforeEach
27 | void setupJenkinsfile() {
28 | MockJenkinsfile.withEnv()
29 | }
30 |
31 | @Test
32 | void returnsAValueForEachLine() {
33 | List expectedValues = [ "VAR1=VALUE1", "VAR2=VALUE2" ]
34 | String fileContents = expectedValues.join('\n')
35 |
36 | FileParametersPlugin plugin = new FileParametersPlugin()
37 | List actualValues = plugin.getVariables(fileContents)
38 |
39 | assertEquals(expectedValues, actualValues)
40 | }
41 |
42 | @Test
43 | void ignoresTrailingNewline() {
44 | List expectedValues = [ "VAR1=VALUE1", "VAR2=VALUE2" ]
45 | String fileContents = expectedValues.join('\n') + '\n\n'
46 |
47 | FileParametersPlugin plugin = new FileParametersPlugin()
48 | List actualValues = plugin.getVariables(fileContents)
49 |
50 | assertEquals(expectedValues, actualValues)
51 | }
52 |
53 | @Test
54 | void handlesCarriageReturnCharacters() {
55 | List expectedValues = [ "VAR1=VALUE1", "VAR2=VALUE2" ]
56 | String fileContents = expectedValues.join('\r\n')
57 |
58 | FileParametersPlugin plugin = new FileParametersPlugin()
59 | List actualValues = plugin.getVariables(fileContents)
60 |
61 | assertEquals(expectedValues, actualValues)
62 | }
63 |
64 | @Test
65 | void interpolatesReferencesToOtherEnvironmentVariables() {
66 | String fileContents = 'SOME_VARIABLE=${env.OTHER_VARIABLE}'
67 | MockJenkinsfile.withEnv(OTHER_VARIABLE: 'VALUE1')
68 | FileParametersPlugin plugin = new FileParametersPlugin()
69 |
70 | List actualValues = plugin.getVariables(fileContents)
71 |
72 | assertEquals(["SOME_VARIABLE=VALUE1"], actualValues)
73 | }
74 | }
75 | }
76 |
77 |
--------------------------------------------------------------------------------
/test/MockJenkinsfile.groovy:
--------------------------------------------------------------------------------
1 | import static org.mockito.Mockito.doReturn
2 | import static org.mockito.Mockito.mock
3 | import static org.mockito.Mockito.mockingDetails
4 | import static org.mockito.Mockito.spy
5 | import static org.mockito.Mockito.when
6 |
7 | public class MockJenkinsfile {
8 |
9 | public static withEnv(Map env = [:]) {
10 | if (needsJenkinsfileInstanceMock()) {
11 | mockJenkinsfileInstance()
12 | }
13 |
14 | when(Jenkinsfile.instance.getEnv()).thenReturn(env)
15 | return this
16 | }
17 |
18 | public static withMockedOriginal() {
19 | Jenkinsfile.original = new MockWorkflowScript()
20 | return this
21 | }
22 |
23 | public static withStandardizedRepoSlug(String slug) {
24 | if (needsJenkinsfileInstanceMock()) {
25 | mockJenkinsfileInstance()
26 | }
27 |
28 | when(Jenkinsfile.instance.getStandardizedRepoSlug()).thenReturn(slug)
29 | return this
30 | }
31 |
32 | public static withRepoName(String repoName) {
33 | if (needsJenkinsfileInstanceMock()) {
34 | mockJenkinsfileInstance()
35 | }
36 |
37 | when(Jenkinsfile.instance.getRepoName()).thenReturn(repoName)
38 | return this
39 | }
40 |
41 | public static withOrganization(String organization) {
42 | if (needsJenkinsfileInstanceMock()) {
43 | mockJenkinsfileInstance()
44 | }
45 |
46 | when(Jenkinsfile.instance.getOrganization()).thenReturn(organization)
47 | return this
48 | }
49 |
50 | public static withParsedScmUrl(Map results) {
51 | if (needsJenkinsfileInstanceMock()) {
52 | mockJenkinsfileInstance()
53 | }
54 |
55 | when(Jenkinsfile.instance.getParsedScmUrl()).thenReturn(results)
56 | return this
57 | }
58 |
59 | public static withFile(String filePath, String fileContent = '') {
60 | def original = spy(new MockWorkflowScript())
61 | doReturn(true).when(original).fileExists(filePath)
62 | doReturn(fileContent).when(original).readFile(filePath)
63 |
64 | Jenkinsfile.original = original
65 | return this
66 | }
67 |
68 | public static needsJenkinsfileInstanceMock() {
69 | return Jenkinsfile.instance == null || !mockingDetails(Jenkinsfile.instance).isMock()
70 | }
71 |
72 | public static mockJenkinsfileInstance() {
73 | Jenkinsfile.instance = mock(Jenkinsfile.class)
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/test/MockWorkflowScript.groovy:
--------------------------------------------------------------------------------
1 | class MockWorkflowScript {
2 | public docker
3 | public scm
4 | public env = [:]
5 | public static BRANCH_NAME
6 |
7 | public MockWorkflowScript() {
8 | docker = this
9 | }
10 |
11 | public docker(Closure closure = null) {
12 | return this
13 | }
14 |
15 | public image(String dockerImage) { return this }
16 | public inside(String dockerImage, Closure closure) {
17 | closure()
18 | return this
19 | }
20 | public build(String image, String options) { return this }
21 | public echo(String message) { println "MockWorkflowScript.echo ${message}" }
22 | public pwd(Map options) { return '/MockWorkflowScript/currentDir' }
23 | public writeFile(Map options) { println "MockWorkflowScript.writeFile: ${options.toString()}" }
24 | public writeFile(String filename, String content) { println "MockWorkflowScript.writeFile: ${filename} : ${content}" }
25 | public readFile(String filename) { return "MockWorkflowScript.readFile(${filename}): some content" }
26 | public fileExists(String filename) { return false }
27 | public sh(String command) { println "MockWorkflowScript.sh: ${command.toString()}"; return 'MockWorkflowScript.sh output' }
28 | public sh(Map options) { println "MockWorkflowScript.sh: ${options.toString()}"; return 'MockWorkflowScript.sh output' }
29 | public string(Map options) { println "MockWorkflowScript.string: ${options.toString()}" }
30 | public parameters(params) { println "MockWorkflowScript.parameters: ${params.toString()}" }
31 | public properties(props) { println "MockWorkflowScript.properties: ${props.toString()}" }
32 | public node(String nodeName, Closure closure) {
33 | println "MockWorkflowScript.node(${nodeName})"
34 | closure()
35 | }
36 | public node(Closure closure) {
37 | println "MockWorkflowScript.node { }"
38 | closure()
39 | }
40 | public deleteDir() { println "MockWorkflowScript.deleteDir" }
41 | public checkout(scm) { println "MockWorkflowScript.checkout(${scm})" }
42 | public withEnv(Collection env, Closure closure) {
43 | println "MockWorkflowScript.withEnv(${env})"
44 | closure()
45 | }
46 | public stage(String stageName, Closure closure) {
47 | println "MockWorkflowScript.stage(${stageName})"
48 | closure()
49 | }
50 | public timeout(options, Closure closure) {
51 | println "MockWorkflowScript.timeout(${options})"
52 | closure()
53 | }
54 | public input(options) {
55 | println "MockWorkflowScript.input(${options})"
56 | }
57 | public ApplyJenkinsfileClosure(Closure closure) {
58 | println "MockWorkflowScript.ApplyJenkinsfileClosure"
59 | closure.delegate = this
60 | closure()
61 | }
62 | public stash(args) {
63 | println "MockWorkflowScript.stash(${args})"
64 | }
65 | public unstash(args) {
66 | println "MockWorkflowScript.unstash(${args})"
67 | }
68 |
69 | public dir(String directory, Closure closure) {
70 | println "MockWorkflowScript.dir(${directory})"
71 | closure.delegate = this
72 | closure()
73 | }
74 |
75 | public resolveScm(Map args) {
76 | println "MockWorkflowScript.resolveScm(${args})"
77 | }
78 |
79 | public ansiColor(String arg, Closure closure) {
80 | print "MockWorkflowScript.ansiColor(${arg})"
81 | closure.delegate = this
82 | closure()
83 | }
84 |
85 | public withCredentials(bindings, Closure closure) {
86 | print "MockworkflowScript.withCredentials(${bindings})"
87 | closure.delegate = this
88 | closure()
89 | }
90 | }
91 |
92 |
--------------------------------------------------------------------------------
/test/ParameterStoreExecPluginTest.groovy:
--------------------------------------------------------------------------------
1 | import static org.hamcrest.Matchers.containsString
2 | import static org.hamcrest.Matchers.equalTo
3 | import static org.hamcrest.Matchers.hasItem
4 | import static org.hamcrest.Matchers.instanceOf
5 | import static org.hamcrest.MatcherAssert.assertThat
6 |
7 | import org.junit.jupiter.api.Nested
8 | import org.junit.jupiter.api.Test
9 | import org.junit.jupiter.api.extension.ExtendWith
10 |
11 | @ExtendWith(ResetStaticStateExtension.class)
12 | class ParameterStoreExecPluginTest {
13 | @Nested
14 | public class Init {
15 | @Test
16 | void modifiesTerraformEnvironmentStage() {
17 | ParameterStoreExecPlugin.init()
18 |
19 | Collection actualPlugins = TerraformEnvironmentStage.getPlugins()
20 | assertThat(actualPlugins, hasItem(instanceOf(ParameterStoreExecPlugin.class)))
21 | }
22 |
23 | @Test
24 | void modifiesTerraformPlanCommand() {
25 | ParameterStoreExecPlugin.init()
26 |
27 | Collection actualPlugins = TerraformPlanCommand.getPlugins()
28 | assertThat(actualPlugins, hasItem(instanceOf(ParameterStoreExecPlugin.class)))
29 | }
30 |
31 | @Test
32 | void modifiesTerraformApplyCommand() {
33 | ParameterStoreExecPlugin.init()
34 |
35 | Collection actualPlugins = TerraformApplyCommand.getPlugins()
36 | assertThat(actualPlugins, hasItem(instanceOf(ParameterStoreExecPlugin.class)))
37 | }
38 | }
39 |
40 | @Nested
41 | public class Apply {
42 | @Test
43 | void addsParameterStorePrefixToTerraformPlan() {
44 | ParameterStoreExecPlugin plugin = new ParameterStoreExecPlugin()
45 | TerraformPlanCommand command = new TerraformPlanCommand()
46 |
47 | plugin.apply(command)
48 |
49 | String result = command.toString()
50 | assertThat(result, containsString("parameter-store-exec terraform plan"))
51 | }
52 |
53 | @Test
54 | void addsParameterStorePrefixToTerraformApply() {
55 | ParameterStoreExecPlugin plugin = new ParameterStoreExecPlugin()
56 | TerraformApplyCommand command = new TerraformApplyCommand()
57 |
58 | plugin.apply(command)
59 |
60 | String result = command.toString()
61 | assertThat(result, containsString("parameter-store-exec terraform apply"))
62 | }
63 | }
64 |
65 | @Nested
66 | public class PathForEnvironment {
67 | @Test
68 | void constructPathUsingOrgRepoAndEnvironment() {
69 | String organization = 'SomeOrg'
70 | String repoName = 'SomeRepo'
71 | String environment = "qa"
72 |
73 | MockJenkinsfile.withOrganization(organization).withRepoName(repoName)
74 | ParameterStoreExecPlugin plugin = new ParameterStoreExecPlugin()
75 |
76 | String actual = plugin.pathForEnvironment(environment)
77 | assertThat(actual, equalTo("/${organization}/${repoName}/${environment}/".toString()))
78 | }
79 | }
80 |
81 | }
82 |
83 |
--------------------------------------------------------------------------------
/test/RegressionStageTest.groovy:
--------------------------------------------------------------------------------
1 | import static org.mockito.Mockito.mock
2 | import static org.mockito.Mockito.verify
3 |
4 | import org.junit.jupiter.api.BeforeEach
5 | import org.junit.jupiter.api.Nested
6 | import org.junit.jupiter.api.Test
7 | import org.junit.jupiter.api.extension.ExtendWith
8 |
9 | @ExtendWith(ResetStaticStateExtension.class)
10 | class RegressionStageTest {
11 |
12 | @Nested
13 | public class AutomationRepo {
14 | @BeforeEach
15 | void mockJenkinsfileOriginal() {
16 | MockJenkinsfile.withMockedOriginal()
17 | }
18 |
19 | @Test
20 | void automationRepoSpecifiedSuccessfullyCallApply() {
21 | RegressionStagePlugin fakePlugin = mock(RegressionStagePlugin.class)
22 | RegressionStage.addPlugin(fakePlugin)
23 |
24 | RegressionStage stage = new RegressionStage().withScm("git:someHost:someUser/someRepo.git")
25 | stage.build()
26 |
27 | verify(fakePlugin).apply(stage)
28 | }
29 |
30 | @Test
31 | void automationRepoAndAppRepoSpecifiedSuccessfullyCallApply() {
32 | RegressionStagePlugin fakePlugin = mock(RegressionStagePlugin.class)
33 | RegressionStage.addPlugin(fakePlugin)
34 |
35 | RegressionStage stage = new RegressionStage().withScm("git:someHost:someUser/someRepo.git")
36 | .withScm("git:someHost:someUser/someOtherRepo.git")
37 | stage.build()
38 |
39 | verify(fakePlugin).apply(stage)
40 | }
41 |
42 | @Test
43 | void automationRepoAndAppRepoWithChangeDirectorySpecifiedSuccessfullyCallApply() {
44 | RegressionStagePlugin fakePlugin = mock(RegressionStagePlugin.class)
45 | RegressionStage.addPlugin(fakePlugin)
46 |
47 | RegressionStage stage = new RegressionStage().withScm("git:someHost:someUser/someRepo.git")
48 | .withScm("git:someHost:someUser/someOtherRepo.git")
49 | .changeDirectory("someDir")
50 | stage.build()
51 |
52 | verify(fakePlugin).apply(stage)
53 | }
54 |
55 | @Test
56 | void noAutomationRepoSpecifiedSuccessfullyCallApply() {
57 | RegressionStagePlugin fakePlugin = mock(RegressionStagePlugin.class)
58 | RegressionStage.addPlugin(fakePlugin)
59 |
60 | RegressionStage stage = new RegressionStage()
61 | stage.build()
62 |
63 | verify(fakePlugin).apply(stage)
64 | }
65 | }
66 |
67 | @Nested
68 | public class AddedPlugins {
69 | @Test
70 | void willHaveApplyCalled() {
71 | RegressionStagePlugin fakePlugin = mock(RegressionStagePlugin.class)
72 | RegressionStage.addPlugin(fakePlugin)
73 |
74 | RegressionStage stage = new RegressionStage()
75 | stage.applyPlugins()
76 |
77 | verify(fakePlugin).apply(stage)
78 | }
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/test/ResetStaticStateExtension.groovy:
--------------------------------------------------------------------------------
1 | import org.codehaus.groovy.transform.trait.Traits
2 | import org.junit.jupiter.api.extension.AfterEachCallback
3 | import org.junit.jupiter.api.extension.BeforeEachCallback
4 | import org.junit.jupiter.api.extension.ExtensionContext
5 | import org.reflections.Reflections
6 | import groovy.transform.Memoized
7 |
8 | public class ResetStaticStateExtension implements BeforeEachCallback,
9 | AfterEachCallback {
10 |
11 | @Override
12 | public void beforeEach(ExtensionContext context) {
13 | reset()
14 | }
15 |
16 | @Override
17 | public void afterEach(ExtensionContext context) {
18 | reset()
19 | }
20 |
21 | public reset() {
22 | def resetList = findAllResettableClasses()
23 |
24 | resetList.each { needsReset ->
25 | needsReset.reset()
26 | }
27 | }
28 |
29 | @Memoized
30 | public findAllResettableClasses() {
31 | return new Reflections(Resettable.getPackage().getName()).getSubTypesOf( Resettable.class ).findAll { !Traits.isTrait(it) }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/test/SemanticVersionTest.groovy:
--------------------------------------------------------------------------------
1 | import static org.hamcrest.MatcherAssert.assertThat;
2 | import static org.hamcrest.Matchers.equalTo
3 |
4 | import org.junit.jupiter.api.Test
5 |
6 | class SemanticVersionTest {
7 |
8 | @Test
9 | void sortsCorrectly() {
10 | 100.times {
11 | List versions = SORTED.clone().collect { v -> new SemanticVersion(v) }
12 | // Randomize
13 | Collections.shuffle(versions)
14 | // Then sort
15 | versions.sort()
16 |
17 | def result = versions.collect { sv -> sv.version }
18 | assertThat(result, equalTo(SORTED))
19 | }
20 | }
21 |
22 | static SORTED = [
23 | 'fixme',
24 | '0.2-beta',
25 | '0.1',
26 | '0.1.0.2',
27 | '0.1.0.2',
28 | '0.1.0.10',
29 | '0.1.0.23',
30 | '0.2',
31 | '0.2.0.3.1',
32 | '0.2.0.4',
33 | '1.0RC1',
34 | '1.0RC2',
35 | '1.0',
36 | '1.0.1.2',
37 | '1.0.2',
38 | '1.2.2.3',
39 | '1.2.3',
40 | '1.5.2_04',
41 | '1.5.2_05',
42 | '1.5.2_10',
43 | '1.6.0_01',
44 | '2.0',
45 | '2.0.0_02',
46 | '3.1'
47 | ]
48 | }
49 |
--------------------------------------------------------------------------------
/test/TerraformImportCommandTest.groovy:
--------------------------------------------------------------------------------
1 | import static org.hamcrest.Matchers.containsString
2 | import static org.hamcrest.Matchers.blankString
3 | import static org.hamcrest.MatcherAssert.assertThat
4 | import static org.mockito.Mockito.mock
5 | import static org.mockito.Mockito.times
6 | import static org.mockito.Mockito.verify
7 |
8 | import org.junit.jupiter.api.Nested
9 | import org.junit.jupiter.api.Test
10 | import org.junit.jupiter.api.extension.ExtendWith
11 |
12 | @ExtendWith(ResetStaticStateExtension.class)
13 | class TerraformImportCommandTest {
14 | @Nested
15 | public class WithResource {
16 | @Test
17 | void defaultsToEmpty() {
18 | def command = new TerraformImportCommand()
19 |
20 | def actualCommand = command.toString()
21 | assertThat(actualCommand, containsString("No resource set, skipping 'terraform import'."))
22 | }
23 |
24 | @Test
25 | void doesntRunWithoutTargetPath() {
26 | def command = new TerraformImportCommand().withResource("foo")
27 |
28 | def actualCommand = command.toString()
29 | assertThat(actualCommand, containsString("No target path set, skipping 'terraform import'."))
30 | }
31 |
32 | @Test
33 | void runsCommandWhenTargetIsAlsoSet() {
34 | def command = new TerraformImportCommand().withResource("foo").withTargetPath("target.foo")
35 |
36 | def actualCommand = command.toString()
37 | assertThat(actualCommand, containsString("terraform import target.foo foo"))
38 | }
39 | }
40 |
41 | @Nested
42 | public class WithTargetPath {
43 | @Test
44 | void defaultsToEmpty() {
45 | def command = new TerraformImportCommand()
46 |
47 | assertThat(command.targetPath, blankString())
48 | }
49 |
50 | @Test
51 | void doesntRunWithoutResource() {
52 | def command = new TerraformImportCommand().withTargetPath("target.foo")
53 |
54 | def actualCommand = command.toString()
55 | assertThat(actualCommand, containsString("No resource set, skipping 'terraform import'."))
56 | }
57 | }
58 |
59 | @Nested
60 | public class Plugins {
61 | @Test
62 | void areAppliedToTheCommand() {
63 | TerraformImportCommandPlugin plugin = mock(TerraformImportCommandPlugin.class)
64 | TerraformImportCommand.addPlugin(plugin)
65 |
66 | TerraformImportCommand command = TerraformImportCommand.instanceFor("env")
67 | command.toString()
68 |
69 | verify(plugin).apply(command)
70 | }
71 |
72 | @Test
73 | void areAppliedExactlyOnce() {
74 | TerraformImportCommandPlugin plugin = mock(TerraformImportCommandPlugin.class)
75 | TerraformImportCommand.addPlugin(plugin)
76 |
77 | TerraformImportCommand command = TerraformImportCommand.instanceFor("env")
78 |
79 | String firstCommand = command.toString()
80 | String secondCommand = command.toString()
81 |
82 | verify(plugin, times(1)).apply(command)
83 | }
84 |
85 | @Test
86 | void areAppliedEvenAfterCommandAlreadyInstantiated() {
87 | TerraformImportCommandPlugin firstPlugin = mock(TerraformImportCommandPlugin.class)
88 | TerraformImportCommandPlugin secondPlugin = mock(TerraformImportCommandPlugin.class)
89 |
90 | TerraformImportCommand.addPlugin(firstPlugin)
91 | TerraformImportCommand command = TerraformImportCommand.instanceFor("env")
92 |
93 | TerraformImportCommand.addPlugin(secondPlugin)
94 |
95 | command.toString()
96 |
97 | verify(secondPlugin, times(1)).apply(command)
98 | }
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/test/TerraformLandscapePluginTest.groovy:
--------------------------------------------------------------------------------
1 | import static org.hamcrest.Matchers.containsString
2 | import static org.hamcrest.Matchers.hasItem
3 | import static org.hamcrest.Matchers.instanceOf
4 | import static org.hamcrest.MatcherAssert.assertThat
5 |
6 | import org.junit.jupiter.api.Nested
7 | import org.junit.jupiter.api.Test
8 | import org.junit.jupiter.api.extension.ExtendWith
9 |
10 | @ExtendWith(ResetStaticStateExtension.class)
11 | class TerraformLandscapePluginTest {
12 | @Nested
13 | public class Init {
14 | @Test
15 | void modifiesTerraformPlanCommand() {
16 | TerraformLandscapePlugin.init()
17 |
18 | Collection actualPlugins = TerraformPlanCommand.getPlugins()
19 | assertThat(actualPlugins, hasItem(instanceOf(TerraformLandscapePlugin.class)))
20 | }
21 | }
22 |
23 | @Nested
24 | public class Apply {
25 | @Test
26 | void addsLandscapeArgumentToTerraformPlan() {
27 | TerraformLandscapePlugin plugin = new TerraformLandscapePlugin()
28 | TerraformPlanCommand command = new TerraformPlanCommand()
29 |
30 | plugin.apply(command)
31 |
32 | String result = command.toString()
33 | assertThat(result, containsString("| landscape"))
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/test/TerraformOutputCommandTest.groovy:
--------------------------------------------------------------------------------
1 | import static org.hamcrest.Matchers.containsString
2 | import static org.hamcrest.Matchers.not
3 | import static org.hamcrest.MatcherAssert.assertThat
4 | import static org.mockito.Mockito.mock
5 | import static org.mockito.Mockito.times
6 | import static org.mockito.Mockito.verify
7 |
8 | import org.junit.jupiter.api.Nested
9 | import org.junit.jupiter.api.Test
10 | import org.junit.jupiter.api.extension.ExtendWith
11 |
12 | @ExtendWith(ResetStaticStateExtension.class)
13 | class TerraformOutputCommandTest {
14 | @Nested
15 | public class WithJson {
16 | @Test
17 | void defaultsToFalse() {
18 | def command = new TerraformOutputCommand()
19 |
20 | def actualCommand = command.toString()
21 | assertThat(actualCommand, not(containsString("-json")))
22 | }
23 |
24 | @Test
25 | void skipsJsonFlagWhenFalse() {
26 | def command = new TerraformOutputCommand().withJson(false)
27 |
28 | def actualCommand = command.toString()
29 | assertThat(actualCommand, not(containsString(" -json")))
30 | }
31 |
32 | @Test
33 | void addsJsonFlagWhenTrue() {
34 | def command = new TerraformOutputCommand().withJson(true)
35 |
36 | def actualCommand = command.toString()
37 | assertThat(actualCommand, containsString(" -json"))
38 | }
39 | }
40 |
41 | @Nested
42 | public class WithRedirectFile {
43 | @Test
44 | void defaultsToEmpty() {
45 | def command = new TerraformOutputCommand()
46 |
47 | def actualCommand = command.toString()
48 | assertThat(actualCommand, not(containsString(">")))
49 | }
50 |
51 | @Test
52 | void addsRedirectWhenSet() {
53 | def command = new TerraformOutputCommand().withRedirectFile("foo")
54 |
55 | def actualCommand = command.toString()
56 | assertThat(actualCommand, containsString(">foo"))
57 | }
58 | }
59 |
60 | @Nested
61 | public class Plugins {
62 | @Test
63 | void areAppliedToTheCommand() {
64 | TerraformOutputCommandPlugin plugin = mock(TerraformOutputCommandPlugin.class)
65 | TerraformOutputCommand.addPlugin(plugin)
66 |
67 | TerraformOutputCommand command = TerraformOutputCommand.instanceFor("env")
68 | command.toString()
69 |
70 | verify(plugin).apply(command)
71 | }
72 |
73 | @Test
74 | void areAppliedExactlyOnce() {
75 | TerraformOutputCommandPlugin plugin = mock(TerraformOutputCommandPlugin.class)
76 | TerraformOutputCommand.addPlugin(plugin)
77 |
78 | TerraformOutputCommand command = TerraformOutputCommand.instanceFor("env")
79 |
80 | String firstCommand = command.toString()
81 | String secondCommand = command.toString()
82 |
83 | verify(plugin, times(1)).apply(command)
84 | }
85 |
86 | @Test
87 | void areAppliedEvenAfterCommandAlreadyInstantiated() {
88 | TerraformOutputCommandPlugin firstPlugin = mock(TerraformOutputCommandPlugin.class)
89 | TerraformOutputCommandPlugin secondPlugin = mock(TerraformOutputCommandPlugin.class)
90 |
91 | TerraformOutputCommand.addPlugin(firstPlugin)
92 | TerraformOutputCommand command = TerraformOutputCommand.instanceFor("env")
93 |
94 | TerraformOutputCommand.addPlugin(secondPlugin)
95 |
96 | command.toString()
97 |
98 | verify(secondPlugin, times(1)).apply(command)
99 | }
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/test/TerraformPluginVersion11Test.groovy:
--------------------------------------------------------------------------------
1 | import static org.hamcrest.Matchers.containsString
2 | import static org.hamcrest.MatcherAssert.assertThat
3 | import static org.mockito.Mockito.spy
4 | import static org.mockito.Mockito.verify;
5 |
6 | import org.junit.jupiter.api.Nested
7 | import org.junit.jupiter.api.Test
8 | import org.junit.jupiter.api.extension.ExtendWith
9 |
10 | @ExtendWith(ResetStaticStateExtension.class)
11 | class TerraformPluginVersion11Test {
12 | @Nested
13 | class ModifiesTerraformValidateCommand {
14 | @Test
15 | void addsCheckVariablesFalseToValidateCommand() {
16 | def validateCommand = spy(new TerraformValidateCommand())
17 | def version11 = new TerraformPluginVersion11()
18 |
19 | version11.apply((TerraformValidateCommand)validateCommand)
20 |
21 | verify(validateCommand).withArgument('-check-variables=false')
22 | }
23 | }
24 |
25 | @Nested
26 | class ModifiesTerraformPlanCommand {
27 | @Test
28 | void toPreserveTerraform11CliSyntaxForVariables() {
29 | def plan = new TerraformPlanCommand()
30 | def version11 = new TerraformPluginVersion11()
31 |
32 | version11.apply(plan)
33 | plan.withVariable('key', 'value')
34 | def result = plan.toString()
35 |
36 | assertThat(result, containsString("-var 'key=value'"))
37 | }
38 |
39 | @Test
40 | void toPreserveTerraform11CliFormatForMapVariables() {
41 | def plan = new TerraformPlanCommand()
42 | def version11 = new TerraformPluginVersion11()
43 |
44 | version11.apply(plan)
45 | plan.withVariable('myMap', [key1:'value1', key2: 'value2'])
46 | def result = plan.toString()
47 |
48 | assertThat(result, containsString("-var 'myMap={key1=\"value1\",key2=\"value2\"}'"))
49 | }
50 | }
51 |
52 | @Nested
53 | class ModifiesTerraformApplyCommand {
54 | @Test
55 | void toPreserveTerraform11CliSyntaxForVariables() {
56 | def applyCommand = new TerraformApplyCommand()
57 | def version11 = new TerraformPluginVersion11()
58 |
59 | version11.apply(applyCommand)
60 | applyCommand.withVariable('key', 'value')
61 | def result = applyCommand.toString()
62 |
63 | assertThat(result, containsString("-var 'key=value'"))
64 | }
65 |
66 | @Test
67 | void toPreserveTerraform11CliFormatForMapVariables() {
68 | def applyCommand = new TerraformPlanCommand()
69 | def version11 = new TerraformPluginVersion11()
70 |
71 | version11.apply(applyCommand)
72 | applyCommand.withVariable('myMap', [key1:'value1', key2: 'value2'])
73 | def result = applyCommand.toString()
74 |
75 | assertThat(result, containsString("-var 'myMap={key1=\"value1\",key2=\"value2\"}'"))
76 | }
77 | }
78 | }
79 |
80 |
--------------------------------------------------------------------------------
/test/TerraformPluginVersion15Test.groovy:
--------------------------------------------------------------------------------
1 | import static org.hamcrest.Matchers.containsString
2 | import static org.hamcrest.MatcherAssert.assertThat
3 | import org.junit.jupiter.api.Nested
4 | import org.junit.jupiter.api.Test
5 | import org.junit.jupiter.api.extension.ExtendWith
6 |
7 | @ExtendWith(ResetStaticStateExtension.class)
8 | class TerraformPluginVersion15Test {
9 | @Nested
10 | class ModifiesTerraformValidateCommand {
11 | @Test
12 | void toUseTerraform15CliSyntaxForDirectory() {
13 | def validate = new TerraformValidateCommand()
14 | def version15 = new TerraformPluginVersion15()
15 |
16 | version15.apply(validate)
17 | validate.withDirectory('foobar')
18 | def result = validate.toString()
19 |
20 | assertThat(result, containsString(" -chdir=foobar"))
21 | }
22 | }
23 |
24 | @Nested
25 | class ModifiesTerraformInitCommand {
26 | @Test
27 | void toUseTerraform15CliSyntaxForDirectory() {
28 | def init = new TerraformInitCommand()
29 | def version15 = new TerraformPluginVersion15()
30 |
31 | version15.apply(init)
32 | init.withDirectory('foobar')
33 | def result = init.toString()
34 |
35 | assertThat(result, containsString(" -chdir=foobar"))
36 | }
37 | }
38 |
39 | @Nested
40 | class ModifiesTerraformPlanCommand {
41 | @Test
42 | void toUseTerraform15CliSyntaxForDirectory() {
43 | def plan = new TerraformPlanCommand()
44 | def version15 = new TerraformPluginVersion15()
45 |
46 | version15.apply(plan)
47 | plan.withDirectory('foobar')
48 | def result = plan.toString()
49 |
50 | assertThat(result, containsString(" -chdir=foobar"))
51 | }
52 | }
53 |
54 | @Nested
55 | class ModifiesTerraformApplyCommand {
56 | @Test
57 | void toUseTerraform15CliSyntaxForDirectory() {
58 | def apply = new TerraformApplyCommand()
59 | def version15 = new TerraformPluginVersion15()
60 |
61 | version15.apply(apply)
62 | apply.withDirectory('foobar')
63 | def result = apply.toString()
64 |
65 | assertThat(result, containsString(" -chdir=foobar"))
66 | }
67 | }
68 | }
69 |
70 |
--------------------------------------------------------------------------------
/test/TerraformTaintCommandTest.groovy:
--------------------------------------------------------------------------------
1 | import static org.hamcrest.Matchers.equalTo
2 | import static org.hamcrest.MatcherAssert.assertThat
3 | import static org.mockito.Mockito.mock
4 | import static org.mockito.Mockito.times
5 | import static org.mockito.Mockito.verify
6 |
7 | import org.junit.jupiter.api.Nested
8 | import org.junit.jupiter.api.Test
9 | import org.junit.jupiter.api.extension.ExtendWith
10 |
11 | @ExtendWith(ResetStaticStateExtension.class)
12 | class TerraformTaintCommandTest {
13 | @Nested
14 | public class WithResource {
15 | @Test
16 | void defaultsToEmpty() {
17 | def command = new TerraformTaintCommand()
18 |
19 | def actualCommand = command.toString()
20 | assertThat(actualCommand, equalTo("terraform taint"))
21 | }
22 |
23 | @Test
24 | void addsResourceWhenSet() {
25 | def command = new TerraformTaintCommand().withResource("foo")
26 |
27 | def actualCommand = command.toString()
28 | assertThat(actualCommand, equalTo("terraform taint foo"))
29 | }
30 | }
31 |
32 | @Nested
33 | public class Plugins {
34 | @Test
35 | void areAppliedToTheCommand() {
36 | TerraformTaintCommandPlugin plugin = mock(TerraformTaintCommandPlugin.class)
37 | TerraformTaintCommand.addPlugin(plugin)
38 |
39 | TerraformTaintCommand command = new TerraformTaintCommand()
40 | command.environment = "env"
41 | command.toString()
42 |
43 | verify(plugin).apply(command)
44 | }
45 |
46 | @Test
47 | void areAppliedExactlyOnce() {
48 | TerraformTaintCommandPlugin plugin = mock(TerraformTaintCommandPlugin.class)
49 | TerraformTaintCommand.addPlugin(plugin)
50 |
51 | TerraformTaintCommand command = new TerraformTaintCommand()
52 | command.environment = "env"
53 |
54 | String firstCommand = command.toString()
55 | String secondCommand = command.toString()
56 |
57 | verify(plugin, times(1)).apply(command)
58 | }
59 |
60 | @Test
61 | void areAppliedEvenAfterCommandAlreadyInstantiated() {
62 | TerraformTaintCommandPlugin firstPlugin = mock(TerraformTaintCommandPlugin.class)
63 | TerraformTaintCommandPlugin secondPlugin = mock(TerraformTaintCommandPlugin.class)
64 |
65 | TerraformTaintCommand.addPlugin(firstPlugin)
66 | TerraformTaintCommand command = new TerraformTaintCommand()
67 | command.environment = "env"
68 |
69 | TerraformTaintCommand.addPlugin(secondPlugin)
70 |
71 | command.toString()
72 |
73 | verify(firstPlugin, times(1)).apply(command)
74 | verify(secondPlugin, times(1)).apply(command)
75 | }
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/test/TerraformUntaintCommandTest.groovy:
--------------------------------------------------------------------------------
1 | import static org.hamcrest.Matchers.equalTo
2 | import static org.hamcrest.MatcherAssert.assertThat
3 | import static org.mockito.Mockito.mock
4 | import static org.mockito.Mockito.times
5 | import static org.mockito.Mockito.verify
6 |
7 | import org.junit.jupiter.api.Nested
8 | import org.junit.jupiter.api.Test
9 | import org.junit.jupiter.api.extension.ExtendWith
10 |
11 | @ExtendWith(ResetStaticStateExtension.class)
12 | class TerraformUntaintCommandTest {
13 | @Nested
14 | public class WithResource {
15 | @Test
16 | void defaultsToEmpty() {
17 | def command = new TerraformUntaintCommand()
18 |
19 | def actualCommand = command.toString()
20 | assertThat(actualCommand, equalTo("terraform untaint"))
21 | }
22 |
23 | @Test
24 | void addsResourceWhenSet() {
25 | def command = new TerraformUntaintCommand().withResource("foo")
26 |
27 | def actualCommand = command.toString()
28 | assertThat(actualCommand, equalTo("terraform untaint foo"))
29 | }
30 | }
31 |
32 | @Nested
33 | public class Plugins {
34 | @Test
35 | void areAppliedToTheCommand() {
36 | TerraformUntaintCommandPlugin plugin = mock(TerraformUntaintCommandPlugin.class)
37 | TerraformUntaintCommand.addPlugin(plugin)
38 |
39 | TerraformUntaintCommand command = new TerraformUntaintCommand()
40 | command.environment = "env"
41 | command.toString()
42 |
43 | verify(plugin).apply(command)
44 | }
45 |
46 | @Test
47 | void areAppliedExactlyOnce() {
48 | TerraformUntaintCommandPlugin plugin = mock(TerraformUntaintCommandPlugin.class)
49 | TerraformUntaintCommand.addPlugin(plugin)
50 |
51 | TerraformUntaintCommand command = new TerraformUntaintCommand()
52 | command.environment = "env"
53 |
54 | String firstCommand = command.toString()
55 | String secondCommand = command.toString()
56 |
57 | verify(plugin, times(1)).apply(command)
58 | }
59 |
60 | @Test
61 | void areAppliedEvenAfterCommandAlreadyInstantiated() {
62 | TerraformUntaintCommandPlugin firstPlugin = mock(TerraformUntaintCommandPlugin.class)
63 | TerraformUntaintCommandPlugin secondPlugin = mock(TerraformUntaintCommandPlugin.class)
64 |
65 | TerraformUntaintCommand.addPlugin(firstPlugin)
66 | TerraformUntaintCommand command = new TerraformUntaintCommand()
67 | command.environment = "env"
68 |
69 | TerraformUntaintCommand.addPlugin(secondPlugin)
70 |
71 | command.toString()
72 |
73 | verify(firstPlugin, times(1)).apply(command)
74 | verify(secondPlugin, times(1)).apply(command)
75 | }
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/test/TerraformValidateStageTest.groovy:
--------------------------------------------------------------------------------
1 | import static org.mockito.Mockito.mock
2 | import static org.mockito.Mockito.spy
3 | import static org.hamcrest.MatcherAssert.assertThat
4 | import static org.hamcrest.CoreMatchers.is
5 | import static org.hamcrest.CoreMatchers.instanceOf
6 |
7 | import org.junit.jupiter.api.Nested
8 | import org.junit.jupiter.api.Test
9 | import org.junit.jupiter.api.extension.ExtendWith
10 |
11 | @ExtendWith(ResetStaticStateExtension.class)
12 | class TerraformValidateStageTest {
13 | @Nested
14 | public class PipelineConfiguration {
15 | @Test
16 | void returnsAJobDslClosure() {
17 | def validateStage = new TerraformValidateStage()
18 |
19 | def result = validateStage.pipelineConfiguration()
20 |
21 | assertThat(result, is(instanceOf(Closure.class)))
22 | }
23 |
24 | // This should be split into separate tests, and assert behavior
25 | @Test
26 | void justExerciseClosureNoAssertions() {
27 | Jenkinsfile.instance = spy(new Jenkinsfile())
28 | Jenkinsfile.original = new MockWorkflowScript()
29 | def validateStage = new TerraformValidateStage()
30 |
31 | def closure = validateStage.pipelineConfiguration()
32 | closure.delegate = Jenkinsfile.original
33 | closure()
34 | }
35 | }
36 |
37 | @Nested
38 | public class Then {
39 | @Test
40 | void nextStageisCalled() {
41 | def stage = new TerraformValidateStage()
42 | def stage2 = mock(Stage.class)
43 |
44 | def result = stage.then(stage2)
45 |
46 | assertThat(result, is(instanceOf(BuildGraph.class)))
47 | }
48 | }
49 |
50 | @Nested
51 | public class Build {
52 | @Test
53 | void justExerciseNoAssertions() {
54 | def stage = new TerraformValidateStage()
55 | MockJenkinsfile.withMockedOriginal()
56 |
57 | stage.build()
58 | }
59 | }
60 |
61 | @Nested
62 | public class Decorate {
63 | @Test
64 | void justExerciseNoAssertions() {
65 | def stage = new TerraformValidateStage()
66 |
67 | stage.decorate { }
68 | }
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/test/ValidateFormatPluginTest.groovy:
--------------------------------------------------------------------------------
1 | import static org.hamcrest.Matchers.hasItem
2 | import static org.hamcrest.Matchers.instanceOf
3 | import static org.hamcrest.MatcherAssert.assertThat
4 | import static org.junit.jupiter.api.Assertions.assertTrue
5 | import static org.mockito.Mockito.doReturn;
6 | import static org.mockito.Mockito.spy;
7 | import static org.mockito.Mockito.verify;
8 |
9 | import org.junit.jupiter.api.Nested
10 | import org.junit.jupiter.api.Test
11 | import org.junit.jupiter.api.extension.ExtendWith
12 |
13 | @ExtendWith(ResetStaticStateExtension.class)
14 | class ValidateFormatPluginTest {
15 | public class Init {
16 | @Test
17 | void modifiesTerraformValidateStage() {
18 | ValidateFormatPlugin.init()
19 |
20 | Collection actualPlugins = TerraformValidateStage.getPlugins()
21 | assertThat(actualPlugins, hasItem(instanceOf(ValidateFormatPlugin.class)))
22 | }
23 |
24 | @Test
25 | void enablesCheckOnTerraformFormat() {
26 | ValidateFormatPlugin.init()
27 |
28 | assertTrue(TerraformFormatCommand.isCheckEnabled())
29 | }
30 | }
31 |
32 | @Nested
33 | public class ApplyForValidateStage {
34 | @Test
35 | void addsClosureToRunTerraformFormat() {
36 | def expectedClosure = { -> }
37 | def validateStage = spy(new TerraformValidateStage())
38 | def plugin = spy(new ValidateFormatPlugin())
39 | doReturn(expectedClosure).when(plugin).formatClosure()
40 |
41 | plugin.apply(validateStage)
42 |
43 | verify(validateStage).decorate(TerraformValidateStage.VALIDATE, expectedClosure)
44 | }
45 | }
46 |
47 | @Nested
48 | public class FormatClosure {
49 | @Test
50 | void runsTheGivenInnerClosure() {
51 | def wasRun = false
52 | def innerClosure = { -> wasRun = true }
53 | def original = spy(new MockWorkflowScript())
54 | def plugin = new ValidateFormatPlugin()
55 |
56 | def formatClosure = plugin.formatClosure()
57 | formatClosure.delegate = original
58 | formatClosure.call(innerClosure)
59 |
60 | assertTrue(wasRun)
61 | }
62 |
63 | @Test
64 | void runsTerraformFormatCommandInAShell() {
65 | def expectedFormatCommand = 'terraform fmt'
66 | def original = spy(new MockWorkflowScript())
67 | def plugin = new ValidateFormatPlugin()
68 |
69 | def formatClosure = plugin.formatClosure()
70 | formatClosure.delegate = original
71 | formatClosure.call { -> }
72 |
73 | verify(original).sh(expectedFormatCommand)
74 | }
75 | }
76 | }
77 |
78 |
--------------------------------------------------------------------------------
/test/WithAwsPluginTest.groovy:
--------------------------------------------------------------------------------
1 | import static org.hamcrest.MatcherAssert.assertThat
2 | import static org.hamcrest.Matchers.equalTo
3 | import static org.hamcrest.Matchers.hasItem
4 | import static org.hamcrest.Matchers.instanceOf
5 | import static org.hamcrest.Matchers.is
6 |
7 | import org.junit.jupiter.api.Nested
8 | import org.junit.jupiter.api.Test
9 | import org.junit.jupiter.api.extension.ExtendWith
10 |
11 | @ExtendWith(ResetStaticStateExtension.class)
12 | class WithAwsPluginTest {
13 | @Nested
14 | public class Init {
15 | @Test
16 | void modifiesTerraformEnvironmentStage() {
17 | WithAwsPlugin.init()
18 |
19 | Collection actualPlugins = TerraformEnvironmentStage.getPlugins()
20 | assertThat(actualPlugins, hasItem(instanceOf(WithAwsPlugin.class)))
21 | }
22 | }
23 |
24 | public class WithRole {
25 | @Test
26 | void isFluentAndReturnsThePluginClass() {
27 | def result = WithAwsPlugin.withRole()
28 |
29 | assertThat(result, equalTo(WithAwsPlugin))
30 | }
31 | }
32 |
33 | @Nested
34 | public class WithImplicitRole {
35 | @Test
36 | void returnsGenericRoleIfPresent() {
37 | def expectedRole = "myRole"
38 | def plugin = new WithAwsPlugin()
39 | MockJenkinsfile.withEnv(AWS_ROLE_ARN: expectedRole)
40 |
41 | plugin.withRole()
42 |
43 | def actualRole = plugin.getRole()
44 | assertThat(actualRole, is(expectedRole))
45 | }
46 |
47 | @Test
48 | void returnsEnvironmentSpecificRoleIfPresent() {
49 | def expectedRole = "myRole"
50 | def plugin = new WithAwsPlugin()
51 | MockJenkinsfile.withEnv(QA_AWS_ROLE_ARN: expectedRole)
52 |
53 | plugin.withRole()
54 |
55 | def actualRole = plugin.getRole('qa')
56 | assertThat(actualRole, is(expectedRole))
57 | }
58 |
59 | @Test
60 | void returnsCaseInsensitiveEnvironmentSpecificRoleIfPresent() {
61 | def expectedRole = "myRole"
62 | def plugin = new WithAwsPlugin()
63 | MockJenkinsfile.withEnv(qa_AWS_ROLE_ARN: expectedRole)
64 |
65 | plugin.withRole()
66 |
67 | def actualRole = plugin.getRole('qa')
68 | assertThat(actualRole, is(expectedRole))
69 | }
70 |
71 | @Test
72 | void prefersGenericRoleOverEnvironmentRole() {
73 | def expectedRole = "correctRole"
74 | def plugin = new WithAwsPlugin()
75 | MockJenkinsfile.withEnv([
76 | AWS_ROLE_ARN: expectedRole,
77 | QA_AWS_ROLE_ARN: 'incorrectRole'
78 | ])
79 |
80 | plugin.withRole()
81 |
82 | def actualRole = plugin.getRole('qa')
83 | assertThat(actualRole, is(expectedRole))
84 | }
85 | }
86 |
87 | @Nested
88 | public class WithDefaultDuration {
89 | @Test
90 | void returnsDefaultDuration() {
91 | def expectedDuration = 3600
92 | def plugin = new WithAwsPlugin()
93 | MockJenkinsfile.withEnv(AWS_ROLE_ARN: 'foo')
94 |
95 | def actualDuration = plugin.getDuration()
96 | assertThat(actualDuration, is(expectedDuration))
97 | }
98 | }
99 |
100 | @Nested
101 | public class WithExplicitDuration {
102 | @Test
103 | void returnsExplicitDuration() {
104 | def expectedDuration = 43200
105 | def plugin = new WithAwsPlugin()
106 |
107 | plugin.withDuration(expectedDuration)
108 |
109 | def actualDuration = plugin.getDuration()
110 |
111 | assertThat(actualDuration, is(expectedDuration))
112 | }
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/vars/ApplyJenkinsfileClosure.groovy:
--------------------------------------------------------------------------------
1 | def call(closure) {
2 | closure.delegate = this
3 | closure.call()
4 | }
5 |
--------------------------------------------------------------------------------
/vars/Pipeline2Stage.groovy:
--------------------------------------------------------------------------------
1 | def call(args) {
2 | pipeline {
3 | agent none
4 | options { preserveStashes() }
5 |
6 | stages {
7 | stage('1') {
8 | steps {
9 | script {
10 | ((Stage)args.getAt(0)).build()
11 | }
12 | }
13 | }
14 |
15 | stage('2') {
16 | steps {
17 | script {
18 | ((Stage)args.getAt(1)).build()
19 | }
20 | }
21 | }
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/vars/Pipeline3Stage.groovy:
--------------------------------------------------------------------------------
1 | def call(args) {
2 | pipeline {
3 | agent none
4 | options { preserveStashes() }
5 |
6 | stages {
7 | stage('1') {
8 | steps {
9 | script {
10 | ((Stage)args.getAt(0)).build()
11 | }
12 | }
13 | }
14 |
15 | stage('2') {
16 | steps {
17 | script {
18 | ((Stage)args.getAt(1)).build()
19 | }
20 | }
21 | }
22 |
23 | stage('3') {
24 | steps {
25 | script {
26 | ((Stage)args.getAt(2)).build()
27 | }
28 | }
29 | }
30 | }
31 | }
32 | }
33 |
34 |
--------------------------------------------------------------------------------
/vars/Pipeline4Stage.groovy:
--------------------------------------------------------------------------------
1 | def call(args) {
2 | pipeline {
3 | agent none
4 | options { preserveStashes() }
5 |
6 | stages {
7 | stage('1') {
8 | steps {
9 | script {
10 | ((Stage)args.getAt(0)).build()
11 | }
12 | }
13 | }
14 |
15 | stage('2') {
16 | steps {
17 | script {
18 | ((Stage)args.getAt(1)).build()
19 | }
20 | }
21 | }
22 |
23 | stage('3') {
24 | steps {
25 | script {
26 | ((Stage)args.getAt(2)).build()
27 | }
28 | }
29 | }
30 |
31 | stage('4') {
32 | steps {
33 | script {
34 | ((Stage)args.getAt(3)).build()
35 | }
36 | }
37 | }
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/vars/Pipeline5Stage.groovy:
--------------------------------------------------------------------------------
1 | def call(args) {
2 | pipeline {
3 | agent none
4 | options { preserveStashes() }
5 |
6 | stages {
7 | stage('1') {
8 | steps {
9 | script {
10 | ((Stage)args.getAt(0)).build()
11 | }
12 | }
13 | }
14 |
15 | stage('2') {
16 | steps {
17 | script {
18 | ((Stage)args.getAt(1)).build()
19 | }
20 | }
21 | }
22 |
23 | stage('3') {
24 | steps {
25 | script {
26 | ((Stage)args.getAt(2)).build()
27 | }
28 | }
29 | }
30 |
31 | stage('4') {
32 | steps {
33 | script {
34 | ((Stage)args.getAt(3)).build()
35 | }
36 | }
37 | }
38 |
39 | stage('5') {
40 | steps {
41 | script {
42 | ((Stage)args.getAt(4)).build()
43 | }
44 | }
45 | }
46 | }
47 | }
48 | }
49 |
50 |
--------------------------------------------------------------------------------
/vars/Pipeline6Stage.groovy:
--------------------------------------------------------------------------------
1 | def call(args) {
2 | pipeline {
3 | agent none
4 | options { preserveStashes() }
5 |
6 | stages {
7 | stage('1') {
8 | steps {
9 | script {
10 | ((Stage)args.getAt(0)).build()
11 | }
12 | }
13 | }
14 |
15 | stage('2') {
16 | steps {
17 | script {
18 | ((Stage)args.getAt(1)).build()
19 | }
20 | }
21 | }
22 |
23 | stage('3') {
24 | steps {
25 | script {
26 | ((Stage)args.getAt(2)).build()
27 | }
28 | }
29 | }
30 |
31 | stage('4') {
32 | steps {
33 | script {
34 | ((Stage)args.getAt(3)).build()
35 | }
36 | }
37 | }
38 |
39 | stage('5') {
40 | steps {
41 | script {
42 | ((Stage)args.getAt(4)).build()
43 | }
44 | }
45 | }
46 |
47 | stage('6') {
48 | steps {
49 | script {
50 | ((Stage)args.getAt(5)).build()
51 | }
52 | }
53 | }
54 | }
55 | }
56 | }
57 |
58 |
--------------------------------------------------------------------------------
/vars/Pipeline7Stage.groovy:
--------------------------------------------------------------------------------
1 | def call(args) {
2 | pipeline {
3 | agent none
4 | options { preserveStashes() }
5 |
6 | stages {
7 | stage('1') {
8 | steps {
9 | script {
10 | ((Stage)args.getAt(0)).build()
11 | }
12 | }
13 | }
14 |
15 | stage('2') {
16 | steps {
17 | script {
18 | ((Stage)args.getAt(1)).build()
19 | }
20 | }
21 | }
22 |
23 | stage('3') {
24 | steps {
25 | script {
26 | ((Stage)args.getAt(2)).build()
27 | }
28 | }
29 | }
30 |
31 | stage('4') {
32 | steps {
33 | script {
34 | ((Stage)args.getAt(3)).build()
35 | }
36 | }
37 | }
38 |
39 | stage('5') {
40 | steps {
41 | script {
42 | ((Stage)args.getAt(4)).build()
43 | }
44 | }
45 | }
46 |
47 | stage('6') {
48 | steps {
49 | script {
50 | ((Stage)args.getAt(5)).build()
51 | }
52 | }
53 | }
54 |
55 | stage('7') {
56 | steps {
57 | script {
58 | ((Stage)args.getAt(6)).build()
59 | }
60 | }
61 | }
62 | }
63 | }
64 | }
65 |
66 |
--------------------------------------------------------------------------------