capistrano-terraform 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 233beb5261914e15999372e9e910378d04729118
4
+ data.tar.gz: 4402e39655b48ad886f8831f00400a3c0349d691
5
+ SHA512:
6
+ metadata.gz: 65c2e03330f18da9299a8393c588c6757642ecadbedc2fee5386069f555555ee9cbbb1bdcb4ffbae2a1e51e2bb780cac77aeff8fe40bb281d58866a0d2d0e431
7
+ data.tar.gz: 9a8f156c5c6eab3e7220eac45e2da8d14370ec4968670e11bd8bb18f4d7c378cbde00433ae3f959c8a9f8ff71e0392d4c6ddfad2d9d86fe9eccfc553c83c7b26
File without changes
@@ -0,0 +1,68 @@
1
+ AllCops:
2
+ TargetRubyVersion: 2.4
3
+
4
+ Style/FrozenStringLiteralComment:
5
+ Enabled: true
6
+
7
+ Style/StringLiterals:
8
+ EnforcedStyle: single_quotes
9
+ # Exclude:
10
+ # - 'db/schema.rb'
11
+ # - 'db/migrate/*.rb'
12
+
13
+ Style/RedundantReturn:
14
+ Enabled: false
15
+ Style/HashSyntax:
16
+ Enabled: false
17
+ Style/UnneededPercentQ:
18
+ Enabled: false
19
+ Style/GuardClause:
20
+ Enabled: false
21
+ Style/IfUnlessModifier:
22
+ Enabled: false
23
+ Style/UnlessElse:
24
+ Enabled: false
25
+ Style/SymbolArray:
26
+ Enabled: false
27
+ Style/Documentation:
28
+ Enabled: false
29
+ Style/ConditionalAssignment:
30
+ Enabled: false
31
+ Style/ClassVars:
32
+ Enabled: false
33
+ Style/TrailingCommaInHashLiteral:
34
+ EnforcedStyleForMultiline: consistent_comma
35
+ Style/TrailingCommaInArrayLiteral:
36
+ EnforcedStyleForMultiline: comma
37
+ Style/ExpandPathArguments:
38
+ Enabled: false
39
+
40
+ Layout/EmptyLinesAroundBlockBody:
41
+ Enabled: false
42
+ Layout/SpaceBeforeBlockBraces:
43
+ Enabled: false
44
+ Layout/SpaceInsideBlockBraces:
45
+ Enabled: false
46
+ Layout/EmptyLinesAroundModuleBody:
47
+ Enabled: false
48
+ Layout/EmptyLinesAroundClassBody:
49
+ Enabled: false
50
+ Layout/EmptyLines:
51
+ Enabled: false
52
+
53
+ Metrics/PerceivedComplexity:
54
+ Enabled: false
55
+ Metrics/CyclomaticComplexity:
56
+ Enabled: false
57
+ Metrics/BlockLength:
58
+ Enabled: false
59
+ Metrics/AbcSize:
60
+ Enabled: false
61
+ Metrics/LineLength:
62
+ Enabled: false
63
+ Max: 160
64
+ Metrics/MethodLength:
65
+ Enabled: false
66
+
67
+ Bundler/OrderedGems:
68
+ Enabled: false
@@ -0,0 +1,20 @@
1
+ # capistrano-terraform 1.x Changelog
2
+
3
+ All notable changes to this project will be documented in this file, in reverse chronological order.
4
+
5
+ **capistrano-terraform strives to follow [SemVer](http://semver.org)**, similar to the Ruby on Rails project. For a `X.Y.Z` release:
6
+
7
+ * `Z` indicates bug fixes only; no breaking changes and no new features, except as necessary for security fixes.
8
+ * `Y` is bumped when we add new features. Occasionally a `Y` release may include small breaking changes. We will notify via CHANGELOG entries and/or deprecation notices if there are breaking changes.
9
+ * `X` is incremented for significant breaking changes. This is reserved for special occasions, like a complete rewrite.
10
+
11
+ **capistrano-terraform has no current release cadence. That stuff is for adults!** At the moment, this project is a one-man-show. I release stuff when I damn well please, when it pleases me the most. If you don't like that, speak up and become a contributor. If you do that, we can change this line in this file to something less antagonistic.
12
+
13
+ ### I'm not dedicating any real time here just yet
14
+ Until this project becomes more meaningfully maintained, the following update notes will be updated as deemed fit and/or by chance of my mind recognizing documentation significance. If you include this code in your project, your crap may break whenever I happenstantially push things that also makes my own crap break. I'll probably be working to fix it soon'ish, but also maybe I'll spend the weekend eating tacos and sleeping instead. The risk is all yours.
15
+
16
+
17
+ # Update notes
18
+
19
+ ## [`1.0.0`] (2018-07-16)
20
+ First public release.
data/Gemfile ADDED
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ # Specify your gem's dependencies in capistrano-docker-build.gemspec
6
+ gemspec
7
+
8
+ # gem 'byebug'
9
+ gem 'rspec'
10
+ gem 'rspec-core'
11
+ gem 'pry'
@@ -0,0 +1,59 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ capistrano-terraform (0.1.0)
5
+ capistrano (>= 3.11)
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ airbrussh (1.3.0)
11
+ sshkit (>= 1.6.1, != 1.7.0)
12
+ capistrano (3.11.0)
13
+ airbrussh (>= 1.0.0)
14
+ i18n
15
+ rake (>= 10.0.0)
16
+ sshkit (>= 1.9.0)
17
+ coderay (1.1.2)
18
+ concurrent-ruby (1.0.5)
19
+ diff-lcs (1.3)
20
+ i18n (1.0.1)
21
+ concurrent-ruby (~> 1.0)
22
+ method_source (0.9.0)
23
+ net-scp (1.2.1)
24
+ net-ssh (>= 2.6.5)
25
+ net-ssh (5.0.2)
26
+ pry (0.11.3)
27
+ coderay (~> 1.1.0)
28
+ method_source (~> 0.9.0)
29
+ rake (12.0.0)
30
+ rspec (3.7.0)
31
+ rspec-core (~> 3.7.0)
32
+ rspec-expectations (~> 3.7.0)
33
+ rspec-mocks (~> 3.7.0)
34
+ rspec-core (3.7.1)
35
+ rspec-support (~> 3.7.0)
36
+ rspec-expectations (3.7.0)
37
+ diff-lcs (>= 1.2.0, < 2.0)
38
+ rspec-support (~> 3.7.0)
39
+ rspec-mocks (3.7.0)
40
+ diff-lcs (>= 1.2.0, < 2.0)
41
+ rspec-support (~> 3.7.0)
42
+ rspec-support (3.7.1)
43
+ sshkit (1.16.1)
44
+ net-scp (>= 1.1.2)
45
+ net-ssh (>= 2.8.0)
46
+
47
+ PLATFORMS
48
+ ruby
49
+
50
+ DEPENDENCIES
51
+ bundler (~> 1.14)
52
+ capistrano-terraform!
53
+ pry
54
+ rake (~> 12.0)
55
+ rspec
56
+ rspec-core
57
+
58
+ BUNDLED WITH
59
+ 1.14.5
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2018 Eric Shorkey
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,179 @@
1
+ # capistrano-terraform
2
+
3
+ Run Terraform tasks as part of your Capistrano v3 deployments; or just plain use Capistrano v3 to run your Terraform, even if you don't deploy your code with Capistrano.
4
+
5
+ ```sh
6
+ cap production terraform:deploy # run all registered terraform deployment tasks
7
+ ```
8
+
9
+ This plugin also hooks into the default `cap <environment> deploy` flow as a build action. See [Usage](#usage) for more details.
10
+
11
+ Hence this also works:
12
+ ```sh
13
+ cap production deploy # run all app deployment tasks, and terraform is going to run in there somewhere based on your configs
14
+ ```
15
+
16
+
17
+ ----
18
+
19
+ ## Installation
20
+
21
+ Add these lines to your application's Gemfile:
22
+
23
+ ```ruby
24
+ gem 'capistrano', '~> 3.11' # capistrano at least version 3.11
25
+ gem 'capistrano-terraform', '~> 1.0' # the meaty bits of this plugin - 1.X to ensure you always have the best available
26
+ ```
27
+
28
+ And then execute:
29
+
30
+ ```sh
31
+ $ bundle
32
+ ```
33
+
34
+ Or install it yourself as:
35
+
36
+ ```sh
37
+ $ gem install capistrano-terraform
38
+ ```
39
+
40
+ ----
41
+
42
+ ## Usage
43
+
44
+ ### Configure your project
45
+
46
+ #### Require in Capfile:
47
+ ```ruby
48
+ require 'capistrano/terraform'
49
+ ```
50
+
51
+ #### Optionally define a Terraform host in deploy.rb or the appropriate deploy/stage.rb:
52
+ ```ruby
53
+ role :terraform, %w{localhost} # role syntax, this one making localhost the terraform agent
54
+ # -- or... --
55
+ server 'my.build.server', roles: %w{terraform} # server syntax, declaring a remote server
56
+ ```
57
+
58
+ If no `:terraform` host/role is defined, all Terraform actions will be ran directly from the host currently running `cap` according to the contents of the current working project directory (some use-cases make this desirable, for example some automated CI/CD pipelines work better this way). A maximum of one `:terraform` host can be defined.
59
+
60
+ > **Note**: Setting the `:terraform` host/role to `localhost` is *not* the same as leaving it undefined (or set to nil). If the `:terraform` role is defined, the full code checkout process is expected to take place prior to terraform actions.
61
+
62
+ > _Note: There is currently no embedded mechanism to ensure the declared terraform host/role (or the localhost if none defined) has the necessary permissions to perform the desired terraform actions against the IAAS service provider. It's entirely up to you to preemptively set up any necessary AWS keys, SSH keys, environment variables, etc, to enable the worker to perform the desired terraform actions. For AWS, I've found it easiest to simply set up the aws-cli environment variables/tokens on the host prior to running `cap`. Discussions/proposals around this are welcome as github issues._
63
+
64
+ #### Optionally define the Terraform root directory:
65
+ ```ruby
66
+ set :terraform_root, "infra"
67
+ ```
68
+ This is the sub-directory from which all `capistrano/terraform` related files/references will be evaluated from. If left undefined, it will be assumed to be the project root directory (ie: where `Capfile` is defined)
69
+
70
+ #### Declare global Terraform variables, variable files, etc
71
+
72
+ ```ruby
73
+ append :terraform_var_file, 'common.tfvars'
74
+ ```
75
+
76
+ #### Declare your Terraform project directories and their specific configurations:
77
+ The minimum terraform step definition includes a name. The path (relative to `:terraform_root`) is actually optional. For example:
78
+ ```ruby
79
+ terraform :my_terraform_action, path: 'directory/path'
80
+ ```
81
+
82
+ If no `:path` option is provided, the `:terraform_root` is the assumed directory.
83
+
84
+ No checking is performed to ensure that declared terraform project directories are unique by path, only the first parameter for the `terraform` DSL method (ie, the name) is used to determine uniqueness.
85
+
86
+ All future references to the same terraform action (by name) are additive. This allows you to declare basic Terraform options within the `deploy.rb` config file, and then add/replace options specific to a particular deployment stage within that stage specific `deploy/stagename.rb` config file.
87
+
88
+ This plugin automatically hooks the normal [Capistrano deploy flow](https://capistranorb.com/documentation/getting-started/flow/) around the _publish_ stage. The default timing is to run terraforms _before_ `deploy:publishing`. However, through a configuration option, terraforms can be selectively performed _after_ `deploy:published` instead.
89
+
90
+ To select the after publish timing, simply add the `after_publish: true` option setting to individual `terraform` declaration:
91
+ ```ruby
92
+ terraform :my_terraform_action, path: 'directory/path', after_publish: true
93
+ ```
94
+
95
+ To opt-out of the deploy flow for all of `capistrano/terraform`, add this line to your `config/deploy.rb` or to a specific `config/deploy/<stage>.rb` file:
96
+ ```ruby
97
+ set :terraform_deploy, false
98
+ ```
99
+
100
+ To opt-out of the deploy flow for an individual terraform declaration, simply add `deploy: false` to the declaration options:
101
+ ```ruby
102
+ terraform :my_terraform, path: 'directory/path', deploy: false
103
+ ```
104
+ Individually excluding a terraform from deploys via `deploy: false` also removes it from the `cap <stage> terraform:deploy` flow.
105
+
106
+ Any terraform actions/project-directories that are excluded from the deploy flow are still available to run individually via `cap terraform:my_terraform:deploy` (or their related tasks).
107
+
108
+
109
+ ### Run a deploy:
110
+
111
+ For a normal full deploy:
112
+ ```ruby
113
+ cap <stage> deploy # the default hooks will run terraform actions at the appropriate times
114
+ ```
115
+
116
+ For independent terraform runs:
117
+ ```ruby
118
+ cap <stage> terraform:deploy # runs both the before and after publish tasks, hooks in that order, without attempting to publishing code -- if you're running this within a CI pipeline (like Circle or CodeShip) this is probably what you want for simple terraform-first style projects
119
+ ```
120
+
121
+ #### Getting a little more granular...
122
+
123
+ You should also notice there are a number of granular cap tasks automatically defined around your terraform projects/directories, allowing you to run only what you want, when you want.
124
+
125
+ > Beware: All granular `cap <stage> terraform:X` tasks will still honor the `:terraform` server role, if one is defined. Something to be aware of...
126
+
127
+ For example, to selectively run before and after publish deploy stages (without involving the code/app deploy bits):
128
+ - `cap <stage> terraform:deploy_before`
129
+ - `cap <stage> terraform:deploy_after`
130
+
131
+ Additionally, every `terraform` declaration within the cap deploy configs generates a number of individually addressable cap tasks that can be ran independently:
132
+ - `cap <stage> terraform:<name>:init` # just terraform init
133
+ - `cap <stage> terraform:<name>:plan` # just terraform plan
134
+ - `cap <stage> terraform:<name>:apply` # just terraform apply
135
+ - `cap <stage> terraform:<name>:deploy` # terraform init -> plan -> apply
136
+ - ???
137
+ - `cap <stage> terraform:<name>:destroy` # just terraform apply
138
+ - `cap <stage> terraform:<name>:clean` # delete any left over plan files and cleans up the .terraform temporary directory
139
+
140
+
141
+ To view all the cap tasks (and their descriptions) that are readily runnable for your project, based on the current config files, run `cap -T` to generate the full list.
142
+
143
+
144
+
145
+ ### Configurable options:
146
+
147
+ Global
148
+
149
+ terraform DSL method
150
+
151
+
152
+
153
+
154
+
155
+
156
+
157
+
158
+
159
+
160
+ ## Why?
161
+
162
+ Because.
163
+
164
+ One day I literally put "`capistrano terraform`" into Google and surprisingly didn't find any projects that already directly addressed running Terraform from Capistrano. So here we are.
165
+
166
+ ## Development
167
+
168
+ After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
169
+
170
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
171
+
172
+ ## Contributing
173
+
174
+ Bug reports and pull requests are welcome on GitHub at https://github.com/eshork/capistrano-terraform
175
+
176
+
177
+ ## License
178
+
179
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task :default => :spec
@@ -0,0 +1,41 @@
1
+ # coding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ lib = File.expand_path('../lib', __FILE__)
5
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
6
+
7
+ Gem::Specification.new do |spec|
8
+ spec.name = 'capistrano-terraform'
9
+ spec.version = '0.1.0'
10
+ spec.authors = ['Eric Shorkey']
11
+ spec.email = ['eric.shorkey@gmail.com']
12
+ spec.summary = 'Terraform plugin for Capistrano'
13
+ spec.description = 'Run Terraform tasks as part of your Capistrano v3 deployments,' \
14
+ ' or just simply use Capistrano to manage your Terraform.' \
15
+ ' Multi-stage -- run your Terraform from a pre or post release hook (or a little of both).' \
16
+ ' Runs directly from localhost or a remote build/deploy host.'
17
+ spec.homepage = 'https://github.com/eshork/capistrano-terraform'
18
+ spec.license = 'MIT'
19
+
20
+ # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
21
+ # to allow pushing to a single host or delete this section to allow pushing to any host.
22
+ # if spec.respond_to?(:metadata)
23
+ # spec.metadata['allowed_push_host'] = "TODO: Set to 'http://mygemserver.com'"
24
+ # else
25
+ # raise 'RubyGems 2.0 or newer is required to protect against public gem pushes.'
26
+ # end
27
+
28
+ # spec.files = `git ls-files -z`.split("\x0").reject do |f|
29
+ # f.match(%r{^(test|spec|features)/})
30
+ # end
31
+
32
+ spec.files = `git ls-files`.split($/)
33
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
34
+ spec.require_paths = ['lib']
35
+
36
+ spec.add_dependency 'capistrano', '>= 3.11'
37
+
38
+ spec.add_development_dependency 'bundler', '~> 1.14'
39
+ spec.add_development_dependency 'rake', '~> 12.0'
40
+ # spec.add_development_dependency 'rspec', '~> 3.0'
41
+ end
@@ -0,0 +1 @@
1
+ # an empty .rb file for bundler to auto-require
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ set :terraform_plan_opts, ['-input=false']
4
+ set :terraform_init_opts, ['-input=false']
5
+ set :terraform_apply_opts, nil
6
+
7
+ namespace :terraform do
8
+ desc 'initialize all terraform directories'
9
+ task :init do
10
+ ::Capistrano::Terraforms.all.each_pair do |terraform_id, terraform_obj|
11
+ invoke "terraform:#{terraform_id}:init" if terraform_obj.deploy?
12
+ end
13
+ end
14
+
15
+ desc 'plan all terraform directories'
16
+ task :plan do
17
+ ::Capistrano::Terraforms.all.each_pair do |terraform_id, terraform_obj|
18
+ invoke "terraform:#{terraform_id}:plan" if terraform_obj.deploy?
19
+ end
20
+ end
21
+
22
+ desc 'apply all planned terraform directories'
23
+ task :apply do
24
+ ::Capistrano::Terraforms.all.each_pair do |terraform_id, terraform_obj|
25
+ invoke "terraform:#{terraform_id}:apply" if terraform_obj.deploy?
26
+ end
27
+ end
28
+
29
+ desc 'clean all terraform directories'
30
+ task :clean do
31
+ ::Capistrano::Terraforms.all.each_pair do |terraform_id, terraform_obj|
32
+ invoke "terraform:#{terraform_id}:clean" if terraform_obj.deploy?
33
+ end
34
+ end
35
+
36
+ # desc 'clear all terraform initializations'
37
+ # task :distclean
38
+
39
+ # desc 'list all terraform directories'
40
+ # task :list
41
+
42
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Add ourself to the standard 'cap <stage> doctor' invocation
4
+ task doctor: 'doctor:terraform'
5
+
6
+ # alias common misusage
7
+ task 'terraform:doctor' => 'doctor:terraform'
8
+
9
+ namespace :doctor do
10
+ desc 'Display the effective terraform configuration'
11
+ task :terraform do
12
+ Capistrano::Doctor::TerraformDoctor.new.call
13
+ end
14
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ # add the build->push process onto the end of the deploy:published stack
4
+ # after 'deploy:published', 'docker:capdeploy_hook'
5
+
6
+ # push the no_release auto-cull to the very front of the task stack
7
+ # before 'deploy:starting', 'docker:trim_release_roles'
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ # include some special sauce
4
+ require_relative 'terraform/terraforms.rb'
5
+ require_relative 'terraform/terraform.rb'
6
+
7
+ # include our DSL updates
8
+ require_relative 'terraform/dsl.rb'
9
+
10
+ # include standard tasks and cap flow hooks
11
+ require_relative 'terraform/tasks'
12
+ require_relative 'terraform/hooks'
13
+ require_relative 'terraform/doctor'
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'doctor/terraform_doctor'
4
+
5
+ load File.expand_path('../../tasks/terraform_doctor.rake', __FILE__)
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'capistrano/doctor/output_helpers'
4
+
5
+ module Capistrano
6
+ module Doctor
7
+ class TerraformDoctor
8
+ include Capistrano::Doctor::OutputHelpers
9
+
10
+ def call
11
+ title('Terraform Globals')
12
+ tf_globals = [
13
+ { var: :terraform_root, resolver: ->{ ::Capistrano::Terraforms.root } },
14
+ { var: :terraform_deploy, resolver: ->{ ::Capistrano::Terraforms.deploy } },
15
+ { var: :terraform_var, resolver: ->{ ::Capistrano::Terraforms.vars } },
16
+ { var: :terraform_var_file, resolver: ->{ ::Capistrano::Terraforms.var_files } },
17
+ { var: :terraform_target, resolver: ->{ ::Capistrano::Terraforms.targets } },
18
+ ]
19
+
20
+ table(tf_globals) do |tf_global, row|
21
+ vname = tf_global[:var]
22
+ vvalue = nil
23
+ if tf_global[:resolver]
24
+ vvalue = tf_global[:resolver].call
25
+ end
26
+
27
+ row << ":#{vname}"
28
+ row << 'nil' if vvalue.nil?
29
+ if vvalue.is_a?(String)
30
+ row << "\"#{vvalue}\""
31
+ else
32
+ row << vvalue
33
+ end
34
+ end
35
+ puts
36
+
37
+ all_terrforms = ::Capistrano::Terraforms.all
38
+ title("Terraform Paths/Projects (#{all_terrforms.size})")
39
+ table(all_terrforms) do |terraform, row|
40
+ row << terraform[0].to_s
41
+ row << terraform[1].to_s
42
+ end
43
+ puts
44
+ end
45
+
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Augments the standard Capistrano DSL with terraform specific methods
4
+
5
+ module Capistrano
6
+ module DSL
7
+ module Env
8
+ def terraform(terraform_id, params = {})
9
+ _terraform_upsert(terraform_id, params)
10
+ end
11
+
12
+ private
13
+
14
+ def _terraform_upsert(terraform_id, params)
15
+ return ::Capistrano::Terraforms.upsert(terraform_id, params)
16
+ end
17
+
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ load File.expand_path('../../tasks/terraform_hooks.rake', __FILE__)
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ load File.expand_path('../../tasks/terraform.rake', __FILE__)
@@ -0,0 +1,277 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Capistrano
4
+ class Terraform
5
+
6
+ def initialize(params = {})
7
+ @path = nil
8
+ @var = nil
9
+ @var_file = nil
10
+ @target = nil
11
+ @after_publish = nil
12
+ @deploy = nil
13
+ @backend_config = nil
14
+ update(params)
15
+ end
16
+
17
+ def update(params = {})
18
+ # handle path updates
19
+ @path = params[:path] if params.key?(:path)
20
+
21
+ # handle var_file upserts
22
+ if params.key?(:var_file)
23
+ @var_file ||= []
24
+ @var_file.push(params[:var_file])
25
+ @var_file.flatten!
26
+ end
27
+ @var_file = nil if @var_file == []
28
+
29
+ # handle var upserts
30
+ if params.key?(:var)
31
+ @var ||= []
32
+ @var.push(params[:var])
33
+ @var&.flatten!&.uniq!
34
+ end
35
+ @var = nil if @var == []
36
+
37
+ # handle target upserts
38
+ if params.key?(:target)
39
+ @target ||= []
40
+ @target.push(params[:target])
41
+ @target&.flatten!&.uniq!
42
+ end
43
+ @target = nil if @target == []
44
+
45
+ # handle after_publish upserts
46
+ if params.key?(:after_publish)
47
+ @after_publish = params[:after_publish]
48
+ end
49
+ @after_publish = nil unless @after_publish == true
50
+
51
+ # handle deploy upserts
52
+ if params.key?(:deploy)
53
+ @deploy = params[:deploy]
54
+ end
55
+ @deploy = nil unless @deploy == false
56
+
57
+ # handle backend_config upserts
58
+ if params.key?(:backend_config)
59
+ @backend_config ||= []
60
+ @backend_config.push(params[:backend_config])
61
+ @backend_config&.flatten!&.uniq!
62
+ end
63
+ @backend_config = nil if @backend_config == []
64
+ end
65
+
66
+ def to_hash
67
+ return {
68
+ path: @path,
69
+ var: @var,
70
+ var_file: @var_file,
71
+ target: @target,
72
+ backend_config: @backend_config,
73
+ after_publish: @after_publish,
74
+ deploy: @deploy,
75
+ }.compact
76
+ end
77
+
78
+ def to_s
79
+ return to_hash
80
+ end
81
+
82
+ def deploy?
83
+ return @deploy != false
84
+ end
85
+
86
+ def targets
87
+ targets_ary = []
88
+ Terraforms.targets.each do |t|
89
+ targets_ary << t
90
+ end
91
+ @target&.each do |t|
92
+ targets_ary << t
93
+ end
94
+ return targets_ary.uniq
95
+ end
96
+
97
+ def plan_outfile
98
+ # fetch :terraform_plan_outfile, 'tf.plan'
99
+ 'tf.plan'
100
+ end
101
+
102
+ def run_path
103
+ return Terraforms.root_path.join(@path) if @path
104
+ return Terraforms.root_path
105
+ end
106
+
107
+ def init_cmd_line
108
+ cmd_ary = [Terraforms.terraform_cmd, :init]
109
+ cmd_ary << fetch(:terraform_init_opts, [])
110
+ cmd_ary << '-reconfigure' # always reconfigure to preserve states across stages
111
+
112
+ root_relative = Terraforms.root_path.relative_path_from(run_path)
113
+ fetch(:terraform_backend_config, []).uniq.each do |bec|
114
+ if bec.include?('=')
115
+ cmd_ary << "-backend-config='#{bec}'"
116
+ else
117
+ cmd_ary << "-backend-config='#{root_relative.join(bec)}'"
118
+ end
119
+ end
120
+
121
+ @backend_config&.each do |bec|
122
+ cmd_ary << "-backend-config='#{bec}'"
123
+ end
124
+
125
+ return cmd_ary.flatten
126
+ end
127
+
128
+ def plan_cmd_line
129
+ cmd_ary = [Terraforms.terraform_cmd, :plan]
130
+ cmd_ary << fetch(:terraform_plan_opts, [])
131
+ cmd_ary << "-out='#{plan_outfile}'"
132
+
133
+ root_relative = Terraforms.root_path.relative_path_from(run_path)
134
+
135
+ fetch(:terraform_var_file, []).uniq.each do |tf_var_file|
136
+ cmd_ary << "-var-file='#{root_relative.join(tf_var_file)}'"
137
+ end
138
+
139
+ fetch(:terraform_var, []).uniq.each do |tf_var|
140
+ cmd_ary << "-var='#{tf_var}'"
141
+ end
142
+
143
+ @var_file&.each do |var_file|
144
+ cmd_ary << "-var-file='#{var_file}'"
145
+ end
146
+
147
+ @var&.each do |var|
148
+ cmd_ary << "-var='#{var}'"
149
+ end
150
+
151
+ targets.each do |target|
152
+ cmd_ary << "-target='#{target}'"
153
+ end
154
+
155
+ return cmd_ary.flatten
156
+ end
157
+
158
+ def apply_cmd_line
159
+ cmd_ary = [Terraforms.terraform_cmd, :apply]
160
+ cmd_ary << fetch(:terraform_apply_opts, [])
161
+ cmd_ary << "'#{plan_outfile}'"
162
+ return cmd_ary.flatten
163
+ end
164
+
165
+ def create_tasks(terraform_id)
166
+ create_init_task(terraform_id)
167
+ create_plan_task(terraform_id)
168
+ create_clean_task(terraform_id)
169
+ create_apply_task(terraform_id)
170
+ end
171
+
172
+ def create_init_task(terraform_id)
173
+ task = Rake::Task.define_task "terraform:#{terraform_id}:init" do
174
+ terraform_obj = Terraforms.find(terraform_id)
175
+ terraform_cmd = terraform_obj.init_cmd_line
176
+
177
+ if roles(:terraform).first
178
+ on roles(:terraform).first do |_terraform_remote|
179
+ info "Running terraform:#{terraform_id}:init..."
180
+ within terraform_obj.run_path do
181
+ execute :pwd
182
+ raise 'nyi'
183
+ end
184
+ end
185
+ else
186
+ run_locally do
187
+ info "Running terraform:#{terraform_id}:init..."
188
+ within terraform_obj.run_path do
189
+ execute :pwd
190
+ execute(*terraform_cmd)
191
+ end
192
+ end
193
+ end
194
+ end
195
+ task.comment = "initialize specific terraform: #{terraform_id}"
196
+ end
197
+
198
+ def create_plan_task(terraform_id)
199
+ task = Rake::Task.define_task "terraform:#{terraform_id}:plan" do
200
+ terraform_obj = Terraforms.find(terraform_id)
201
+ terraform_cmd = terraform_obj.plan_cmd_line
202
+
203
+ if roles(:terraform).first
204
+ on roles(:terraform).first do |_terraform_remote|
205
+ info "Running terraform:#{terraform_id}:plan..."
206
+ within terraform_obj.run_path do
207
+ execute :pwd
208
+ execute(*terraform_cmd)
209
+ end
210
+ end
211
+ else
212
+ run_locally do
213
+ info "Running terraform:#{terraform_id}:plan..."
214
+ within terraform_obj.run_path do
215
+ execute :pwd
216
+ execute(*terraform_cmd)
217
+ end
218
+ end
219
+ end
220
+ end
221
+ task.comment = "plan specific terraform: #{terraform_id}"
222
+ end
223
+
224
+ def create_apply_task(terraform_id)
225
+ task = Rake::Task.define_task "terraform:#{terraform_id}:apply" do
226
+ terraform_obj = Terraforms.find(terraform_id)
227
+ terraform_cmd = terraform_obj.apply_cmd_line
228
+
229
+ if roles(:terraform).first
230
+ on roles(:terraform).first do |_terraform_remote|
231
+ info "Running terraform:#{terraform_id}:apply..."
232
+ within terraform_obj.run_path do
233
+ execute :pwd
234
+ execute(*terraform_cmd)
235
+ end
236
+ end
237
+ else
238
+ run_locally do
239
+ info "Running terraform:#{terraform_id}:apply..."
240
+ within terraform_obj.run_path do
241
+ execute :pwd
242
+ execute(*terraform_cmd)
243
+ end
244
+ end
245
+ end
246
+ end
247
+ task.comment = "apply specific terraform: #{terraform_id}"
248
+ end
249
+
250
+ def create_clean_task(terraform_id)
251
+ task = Rake::Task.define_task "terraform:#{terraform_id}:clean" do
252
+ terraform_obj = Terraforms.find(terraform_id)
253
+ terraform_cmd = [:rm, '-rf', '.terraform', plan_outfile]
254
+
255
+ if roles(:terraform).first
256
+ on roles(:terraform).first do |_terraform_remote|
257
+ info "Cleaning terraform:#{terraform_id}..."
258
+ within terraform_obj.run_path do
259
+ execute :pwd
260
+ execute(*terraform_cmd)
261
+ end
262
+ end
263
+ else
264
+ run_locally do
265
+ info "Cleaning terraform:#{terraform_id}:plan..."
266
+ within terraform_obj.run_path do
267
+ execute :pwd
268
+ execute(*terraform_cmd)
269
+ end
270
+ end
271
+ end
272
+ end
273
+ task.comment = "clean specific terraform: #{terraform_id}"
274
+ end
275
+
276
+ end
277
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'terraform.rb'
4
+
5
+ module Capistrano
6
+ class Terraforms
7
+
8
+ def self.terraform_cmd
9
+ fetch(:terraform_cmd, :terraform)
10
+ end
11
+
12
+ def self.root
13
+ fetch :terraform_root, nil
14
+ end
15
+
16
+ def self.vars
17
+ fetch :terraform_var, []
18
+ end
19
+
20
+ def self.var_files
21
+ fetch(:terraform_var_file, []).uniq
22
+ end
23
+
24
+ def self.targets
25
+ [fetch(:terraform_target, [])].flatten.uniq
26
+ end
27
+
28
+ def self.deploy
29
+ fetch :terraform_deploy, true
30
+ end
31
+
32
+ def self.root_path
33
+ if roles(:terraform).first
34
+ return deploy_path.join(fetch(:current_directory, 'current')).join(fetch(:terraform_root, '.'))
35
+ else
36
+ return Pathname.new(fetch(:terraform_root, '.'))
37
+ end
38
+
39
+ # return deploy_path.join(fetch(:current_directory, 'current')) if build_dir.nil?
40
+ # return Pathname.new(build_dir.strip) if build_dir.strip[0] == '/'
41
+ # return deploy_path.join(fetch(:current_directory, 'current'), build_dir)
42
+ end
43
+
44
+ def self.upsert(terraform_id, params = {})
45
+ @@terraforms ||= {} # auto-init class variable on usage
46
+ terraform_id = terraform_id.to_sym
47
+ if @@terraforms.key?(terraform_id)
48
+ @@terraforms[terraform_id].update(params)
49
+ else
50
+ @@terraforms[terraform_id] = ::Capistrano::Terraform.new(params)
51
+ @@terraforms[terraform_id].create_tasks(terraform_id)
52
+ end
53
+ end
54
+
55
+ def self.all
56
+ @@terraforms ||= {} # auto-init class variable on usage
57
+ return @@terraforms
58
+ end
59
+
60
+ def self.find(terraform_id)
61
+ return all[terraform_id]
62
+ end
63
+
64
+ end
65
+ end
metadata ADDED
@@ -0,0 +1,110 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: capistrano-terraform
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Eric Shorkey
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2018-08-15 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: capistrano
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '3.11'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '3.11'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.14'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.14'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '12.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '12.0'
55
+ description: Run Terraform tasks as part of your Capistrano v3 deployments, or just
56
+ simply use Capistrano to manage your Terraform. Multi-stage -- run your Terraform
57
+ from a pre or post release hook (or a little of both). Runs directly from localhost
58
+ or a remote build/deploy host.
59
+ email:
60
+ - eric.shorkey@gmail.com
61
+ executables: []
62
+ extensions: []
63
+ extra_rdoc_files: []
64
+ files:
65
+ - ".rspec_status"
66
+ - ".rubocop.yml"
67
+ - CHANGELOG.md
68
+ - Gemfile
69
+ - Gemfile.lock
70
+ - LICENSE.txt
71
+ - README.md
72
+ - Rakefile
73
+ - capistrano-terraform.gemspec
74
+ - lib/capistrano-terraform.rb
75
+ - lib/capistrano/tasks/terraform.rake
76
+ - lib/capistrano/tasks/terraform_doctor.rake
77
+ - lib/capistrano/tasks/terraform_hooks.rake
78
+ - lib/capistrano/terraform.rb
79
+ - lib/capistrano/terraform/doctor.rb
80
+ - lib/capistrano/terraform/doctor/terraform_doctor.rb
81
+ - lib/capistrano/terraform/dsl.rb
82
+ - lib/capistrano/terraform/hooks.rb
83
+ - lib/capistrano/terraform/tasks.rb
84
+ - lib/capistrano/terraform/terraform.rb
85
+ - lib/capistrano/terraform/terraforms.rb
86
+ homepage: https://github.com/eshork/capistrano-terraform
87
+ licenses:
88
+ - MIT
89
+ metadata: {}
90
+ post_install_message:
91
+ rdoc_options: []
92
+ require_paths:
93
+ - lib
94
+ required_ruby_version: !ruby/object:Gem::Requirement
95
+ requirements:
96
+ - - ">="
97
+ - !ruby/object:Gem::Version
98
+ version: '0'
99
+ required_rubygems_version: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ requirements: []
105
+ rubyforge_project:
106
+ rubygems_version: 2.6.8
107
+ signing_key:
108
+ specification_version: 4
109
+ summary: Terraform plugin for Capistrano
110
+ test_files: []