rake-terraform 0.0.8 → 0.2.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.
Files changed (40) hide show
  1. checksums.yaml +8 -8
  2. data/.gitignore +19 -0
  3. data/.rspec +5 -0
  4. data/.rubocop.yml +25 -0
  5. data/.travis.yml +1 -0
  6. data/README.md +93 -1
  7. data/Rakefile +22 -3
  8. data/lib/{rake_terraform.rb → rake-terraform.rb} +1 -0
  9. data/lib/rake-terraform/apply_task/config.rb +47 -0
  10. data/lib/rake-terraform/{tasks/applytask.rb → apply_task/task.rb} +21 -33
  11. data/lib/rake-terraform/applytask.rb +11 -0
  12. data/lib/rake-terraform/{tasks/basetask.rb → basetask.rb} +2 -1
  13. data/lib/rake-terraform/default_tasks.rb +5 -2
  14. data/lib/rake-terraform/dsl.rb +2 -2
  15. data/lib/rake-terraform/env_process.rb +142 -0
  16. data/lib/rake-terraform/errors.rb +4 -0
  17. data/lib/rake-terraform/plan_task/config.rb +61 -0
  18. data/lib/rake-terraform/plan_task/task.rb +51 -0
  19. data/lib/rake-terraform/plantask.rb +13 -0
  20. data/lib/rake-terraform/terraformcmd.rb +56 -0
  21. data/lib/rake-terraform/version.rb +1 -1
  22. data/rake-terraform.gemspec +3 -0
  23. data/spec/fixtures/envprocess_uniq_state_dir_var_invalid.env +3 -0
  24. data/spec/fixtures/envprocess_uniq_state_dir_var_valid.env +4 -0
  25. data/spec/fixtures/envprocess_uniq_state_false.env +2 -0
  26. data/spec/fixtures/envprocess_uniq_state_false_file_valid.env +3 -0
  27. data/spec/fixtures/envprocess_uniq_state_invalid_state_file_str.env +3 -0
  28. data/spec/fixtures/envprocess_uniq_state_missing_dir_var.env +4 -0
  29. data/spec/fixtures/envprocess_uniq_state_true.env +5 -0
  30. data/spec/fixtures/envprocess_uniq_state_true_both.env +6 -0
  31. data/spec/fixtures/envprocess_uniq_state_true_file_valid.env +3 -0
  32. data/spec/fixtures/set_all_variables_nil.env +8 -0
  33. data/spec/spec_helper.rb +86 -0
  34. data/spec/unit/applytask_spec.rb +112 -0
  35. data/spec/unit/basetask_spec.rb +10 -0
  36. data/spec/unit/envprocess_spec.rb +263 -0
  37. data/spec/unit/plantask_spec.rb +118 -0
  38. data/spec/unit/terraformcmd_spec.rb +174 -0
  39. metadata +90 -7
  40. data/lib/rake-terraform/tasks/plantask.rb +0 -87
@@ -0,0 +1,4 @@
1
+ module RakeTerraform
2
+ # Something wrong with a configured nameserver
3
+ class TerraformNotInstalled < SystemCallError; end
4
+ end
@@ -0,0 +1,61 @@
1
+ require 'rake-terraform/env_process'
2
+
3
+ module RakeTerraform
4
+ module PlanTask
5
+ # Configuration data for terraform plan task
6
+ class Config
7
+ prepend RakeTerraform::EnvProcess
8
+
9
+ attr_writer :aws_project, :credentials, :output_file
10
+
11
+ def initialize
12
+ # initialize RakeTerraform::EnvProcess
13
+ super
14
+ end
15
+
16
+ def aws_project
17
+ @aws_project ||= 'default'
18
+ end
19
+
20
+ def credentials
21
+ @credentials ||= File.expand_path(default_credentials)
22
+ end
23
+
24
+ def output_file
25
+ @output_file ||= File.expand_path(default_output)
26
+ end
27
+
28
+ def input_dir
29
+ @input_dir ||= File.expand_path 'terraform'
30
+ end
31
+
32
+ # setter method for input_dir triggers setters for tf_environment and
33
+ # state_file so that these are dynamically updated on change (but only if
34
+ # we are using directory state, and not explicit path to a state file)
35
+ def input_dir=(dir)
36
+ @tf_environment = dir
37
+ @state_file = tf_state_file if @state_dir
38
+ @input_dir = dir
39
+ end
40
+
41
+ def opts
42
+ Map.new(input_dir: input_dir,
43
+ output_file: output_file,
44
+ credentials: credentials,
45
+ aws_project: aws_project,
46
+ unique_state: unique_state,
47
+ state_file: state_file)
48
+ end
49
+
50
+ private
51
+
52
+ def default_output
53
+ File.join('output', 'terraform', 'plan.tf')
54
+ end
55
+
56
+ def default_credentials
57
+ File.join('~', '.aws', 'credentials')
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,51 @@
1
+ require 'rake-terraform/basetask'
2
+ require 'rake-terraform/terraformcmd'
3
+ module RakeTerraform
4
+ module PlanTask
5
+ # Custom rake task to run `terraform plan`
6
+ class Task < BaseTask
7
+ include RakeTerraform::TerraformCmd
8
+ attr_accessor :creds_file
9
+
10
+ def initialize(opts)
11
+ @creds_file = opts.get(:credentials)
12
+ @opts = opts
13
+ creds
14
+ end
15
+
16
+ def execute
17
+ pre_execute_checks
18
+ Dir.chdir(@opts.get(:input_dir)) do
19
+ puts "=> Generating plan for #{@opts.get(:input_dir)}..."
20
+ if @opts[:unique_state]
21
+ tf_plan(@creds[:accesskey], @creds[:secretkey],
22
+ @opts[:output_file], @opts[:state_file])
23
+ else
24
+ tf_plan(@creds[:accesskey], @creds[:secretkey], @opts[:output_file])
25
+ end
26
+ end
27
+ end
28
+
29
+ def creds
30
+ @creds ||= get_aws_credentials(@creds_file, @opts.get(:aws_project))
31
+ end
32
+
33
+ private
34
+
35
+ # run pre execution checks
36
+ def pre_execute_checks
37
+ validate_terraform_installed
38
+ ensure_output_directory
39
+ Dir.chdir(@opts.get(:input_dir)) do
40
+ puts '=> Fetching modules...'
41
+ tf_get
42
+ end
43
+ end
44
+
45
+ def ensure_output_directory
46
+ dir = File.dirname @opts.get(:output_file)
47
+ FileUtils.mkdir_p dir unless File.exist? dir
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,13 @@
1
+ require 'rake-terraform/plan_task/config'
2
+ require 'rake-terraform/plan_task/task'
3
+ # TODO: only needed in < 1.9?
4
+ require 'map'
5
+
6
+ module RakeTerraform
7
+ # == RakeTerraform::PlanTask
8
+ #
9
+ # Helper classes for creating a terraform plan
10
+ #
11
+ module PlanTask
12
+ end
13
+ end
@@ -0,0 +1,56 @@
1
+ module RakeTerraform
2
+ # == RakeTerraform::TerraformCmd
3
+ #
4
+ # Helper module for running wrapping terraform calls
5
+ #
6
+ # TODO: refactor public methods to take splat arguments and write private
7
+ # command builder methods
8
+ #
9
+ module TerraformCmd
10
+ # perform a 'terraform get'
11
+ def tf_get(update = false)
12
+ cmd = 'terraform get'
13
+ update && cmd << ' -update'
14
+ system(cmd)
15
+ end
16
+
17
+ # perform a 'terraform plan'
18
+ #
19
+ # access_key and secret_key are optional as terraform and it's underlying
20
+ # library have supported the standard AWS_PROFILE env var for a while
21
+ #
22
+ def tf_plan(access_key = nil, secret_key = nil,
23
+ output_file = nil, state_file = nil, module_depth = 2)
24
+ cmd = 'terraform plan'
25
+ cmd << " -module-depth #{module_depth}"
26
+ state_file && cmd << " -state #{state_file}"
27
+ if access_key && secret_key
28
+ # TODO: additional escaped quotes required?
29
+ cmd << " -var access_key=\"#{access_key}\""
30
+ cmd << " -var secret_key=\"#{secret_key}\""
31
+ elsif access_key || secret_key
32
+ fail ArgumentError, 'Only one of access_key or secret_key given'
33
+ end
34
+ output_file && cmd << " -out #{output_file}"
35
+ system(cmd)
36
+ end
37
+
38
+ # perform a 'terraform show'
39
+ #
40
+ def tf_show(plan_file, module_depth = 2)
41
+ cmd = 'terraform show'
42
+ cmd << " -module-depth #{module_depth}"
43
+ cmd << " #{plan_file}"
44
+ system(cmd)
45
+ end
46
+
47
+ # perform a 'terraform apply'
48
+ #
49
+ def tf_apply(plan_file, state_file = nil)
50
+ cmd = 'terraform apply'
51
+ state_file && cmd << " -state #{state_file}"
52
+ cmd << " #{plan_file}"
53
+ system(cmd)
54
+ end
55
+ end
56
+ end
@@ -1,4 +1,4 @@
1
1
  # Version of the gem
2
2
  module RakeTerraform
3
- VERSION = '0.0.8'
3
+ VERSION = '0.2.0'
4
4
  end
@@ -23,6 +23,9 @@ Gem::Specification.new do |spec|
23
23
 
24
24
  spec.add_development_dependency 'bundler', '~> 1'
25
25
  spec.add_development_dependency 'rubocop', '~> 0.29'
26
+ spec.add_development_dependency 'rspec', '~> 3.0.0'
27
+ spec.add_dependency 'dotenv', '~> 2.0.0'
28
+ spec.add_dependency 'wannabe_bool', '~> 0.3.0'
26
29
  spec.add_dependency 'rake', '~> 10.0'
27
30
  spec.add_dependency 'map', '~> 6.5'
28
31
  spec.add_runtime_dependency 'highline', '~> 1.7'
@@ -0,0 +1,3 @@
1
+ TERRAFORM_UNIQUE_STATE="FALSE"
2
+ TERRAFORM_STATE_FILE=""
3
+ TERRAFORM_STATE_DIR_VAR="something/another_thing"
@@ -0,0 +1,4 @@
1
+ TERRAFORM_UNIQUE_STATE="FALSE"
2
+ TERRAFORM_STATE_FILE=""
3
+ TERRAFORM_STATE_DIR_VAR="SOMETHING"
4
+ SOMETHING="staging"
@@ -0,0 +1,2 @@
1
+ # dotenv fixtures for RakeTerraform::EnvProcess testing for TERRAFORM_UNIQUE_STATE="FALSE"
2
+ TERRAFORM_UNIQUE_STATE="FALSE"
@@ -0,0 +1,3 @@
1
+ TERRAFORM_UNIQUE_STATE="FALSE"
2
+ TERRAFORM_STATE_FILE="/tmp/some_state.tfstate"
3
+ TERRAFORM_STATE_DIR_VAR=""
@@ -0,0 +1,3 @@
1
+ TERRAFORM_STATE_FILE="/._"
2
+ TERRAFORM_STATE_DIR_VAR=""
3
+ TERRAFORM_UNIQUE_STATE="FALSE"
@@ -0,0 +1,4 @@
1
+ # dotenv fixtures for RakeTerraform::EnvProcess testing for
2
+ # where we are missing a target for TERRAFORM_STATE_DIR_VAR
3
+ TERRAFORM_UNIQUE_STATE="TRUE"
4
+ TERRAFORM_STATE_DIR_VAR="missing"
@@ -0,0 +1,5 @@
1
+ # dotenv fixtures for RakeTerraform::EnvProcess testing for TERRAFORM_UNIQUE_STATE="TRUE"
2
+ TERRAFORM_UNIQUE_STATE="TRUE"
3
+ TERRAFORM_STATE_FILE=""
4
+ TERRAFORM_STATE_DIR_VAR=""
5
+
@@ -0,0 +1,6 @@
1
+ # dotenv fixtures for RakeTerraform::EnvProcess testing for
2
+ # TERRAFORM_UNIQUE_STATE="TRUE" and more than one optional var given
3
+ TERRAFORM_UNIQUE_STATE="TRUE"
4
+ TERRAFORM_STATE_FILE="/tmp/some_state.tfstate"
5
+ TF_TEST="something"
6
+ TERRAFORM_STATE_DIR_VAR="TF_TEST"
@@ -0,0 +1,3 @@
1
+ TERRAFORM_UNIQUE_STATE="TRUE"
2
+ TERRAFORM_STATE_FILE="/tmp/some_state.tfstate"
3
+ TERRAFORM_STATE_DIR_VAR=""
@@ -0,0 +1,8 @@
1
+ # sets all known environment vars to nil
2
+ TERRAFORM_UNIQUE_STATE=""
3
+ TERRAFORM_STATE_FILE=""
4
+ TERRAFORM_STATE_DIR_VAR=""
5
+ TERRAFORM_AWS_PROJECT=""
6
+ TERRAFORM_ENVIRONMENT_GLOB=""
7
+ TERRAFORM_OUTPUT_BASE=""
8
+ TERRAFORM_CREDENTIAL_FILE=""
@@ -0,0 +1,86 @@
1
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
3
+ require 'rake-terraform'
4
+ require 'dotenv'
5
+
6
+ PROJECT_ROOT = File.expand_path('../../', __FILE__)
7
+
8
+ # This file was generated by the `rspec --init` command. Conventionally, all
9
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
10
+ # The generated `.rspec` file contains `--require spec_helper` which will cause
11
+ # this file to always be loaded, without a need to explicitly require it in any
12
+ # files.
13
+ #
14
+ # Given that it is always loaded, you are encouraged to keep this file as
15
+ # light-weight as possible. Requiring heavyweight dependencies from this file
16
+ # will add to the boot time of your test suite on EVERY test run, even for an
17
+ # individual file that may not need all of that loaded. Instead, make a
18
+ # separate helper file that requires this one and then use it only in the specs
19
+ # that actually need it.
20
+ #
21
+ # The `.rspec` file also contains a few flags that are not defaults but that
22
+ # users commonly want.
23
+ #
24
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
25
+ RSpec.configure do |config|
26
+ # The settings below are suggested to provide a good initial experience
27
+ # with RSpec, but feel free to customize to your heart's content.
28
+ =begin
29
+ # These two settings work together to allow you to limit a spec run
30
+ # to individual examples or groups you care about by tagging them with
31
+ # `:focus` metadata. When nothing is tagged with `:focus`, all examples
32
+ # get run.
33
+ config.filter_run :focus
34
+ config.run_all_when_everything_filtered = true
35
+
36
+ # Many RSpec users commonly either run the entire suite or an individual
37
+ # file, and it's useful to allow more verbose output when running an
38
+ # individual spec file.
39
+ if config.files_to_run.one?
40
+ # Use the documentation formatter for detailed output,
41
+ # unless a formatter has already been configured
42
+ # (e.g. via a command-line flag).
43
+ config.default_formatter = 'doc'
44
+ end
45
+
46
+ # Print the 10 slowest examples and example groups at the
47
+ # end of the spec run, to help surface which specs are running
48
+ # particularly slow.
49
+ config.profile_examples = 10
50
+
51
+ # Run specs in random order to surface order dependencies. If you find an
52
+ # order dependency and want to debug it, you can fix the order by providing
53
+ # the seed, which is printed after each run.
54
+ # --seed 1234
55
+ config.order = :random
56
+
57
+ # Seed global randomization in this process using the `--seed` CLI option.
58
+ # Setting this allows you to use `--seed` to deterministically reproduce
59
+ # test failures related to randomization by passing the same `--seed` value
60
+ # as the one that triggered the failure.
61
+ Kernel.srand config.seed
62
+
63
+ # rspec-expectations config goes here. You can use an alternate
64
+ # assertion/expectation library such as wrong or the stdlib/minitest
65
+ # assertions if you prefer.
66
+ config.expect_with :rspec do |expectations|
67
+ # Enable only the newer, non-monkey-patching expect syntax.
68
+ # For more details, see:
69
+ # - http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax
70
+ expectations.syntax = :expect
71
+ end
72
+
73
+ # rspec-mocks config goes here. You can use an alternate test double
74
+ # library (such as bogus or mocha) by changing the `mock_with` option here.
75
+ config.mock_with :rspec do |mocks|
76
+ # Enable only the newer, non-monkey-patching expect syntax.
77
+ # For more details, see:
78
+ # - http://teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
79
+ mocks.syntax = :expect
80
+
81
+ # Prevents you from mocking or stubbing a method that does not exist on
82
+ # a real object. This is generally recommended.
83
+ mocks.verify_partial_doubles = true
84
+ end
85
+ =end
86
+ end
@@ -0,0 +1,112 @@
1
+ require 'spec_helper'
2
+ require 'rake-terraform/applytask'
3
+
4
+ module RakeTerraform
5
+ module ApplyTask
6
+ describe Config do
7
+ let(:default_plan_string) { "#{PROJECT_ROOT}/output/terraform/plan.tf" }
8
+ let(:non_existent_plan) { "#{PROJECT_ROOT}/some/none/existent/path.tf" }
9
+ let(:default_exec_path_string) { "#{PROJECT_ROOT}/terraform" }
10
+ let(:non_existent_path) { "#{PROJECT_ROOT}/some/none/existent/path" }
11
+ before(:each) do
12
+ # reset default config object before each test
13
+ @default_config = RakeTerraform::ApplyTask::Config.new
14
+ end
15
+ before(:all) do
16
+ Dotenv.overload(
17
+ 'spec/fixtures/set_all_variables_nil.env'
18
+ )
19
+ end
20
+ it 'should initialize successfully with no arguments' do
21
+ expect { RakeTerraform::ApplyTask::Config.new }.to_not raise_error
22
+ end
23
+
24
+ describe 'plan' do
25
+ context 'with a default config object' do
26
+ it 'should be a string matching default_plan_string' do
27
+ expect(@default_config.plan).to eq(default_plan_string)
28
+ end
29
+ it 'should let me set the path to a non-existent path' do
30
+ expect { @default_config.plan = non_existent_plan }
31
+ .to_not raise_error
32
+ expect(@default_config.plan).to eq(non_existent_plan)
33
+ end
34
+ end
35
+ end
36
+
37
+ describe 'execution_path' do
38
+ after(:all) do
39
+ Dotenv.overload(
40
+ 'spec/fixtures/set_all_variables_nil.env'
41
+ )
42
+ end
43
+ let(:tf_environment) { 'terraform/eu-west-1' }
44
+ let(:tf_state_dir_value) { "#{tf_environment}/state/staging" }
45
+ let(:tf_state_file_path) do
46
+ "#{PROJECT_ROOT}/#{tf_state_dir_value}/terraform.tfstate"
47
+ end
48
+ context 'with a default config object' do
49
+ it 'should be a string matching default_exec_path_string' do
50
+ expect(@default_config.execution_path)
51
+ .to eq(default_exec_path_string)
52
+ end
53
+ it 'should let me set the path to a non-existent path' do
54
+ expect { @default_config.execution_path = non_existent_path }
55
+ .to_not raise_error
56
+ expect(@default_config.execution_path).to eq(non_existent_path)
57
+ end
58
+ end
59
+ context 'when I set the execution_path to something else and ' \
60
+ 'state_dir is true' do
61
+ Dotenv.overload(
62
+ 'spec/fixtures/envprocess_uniq_state_dir_var_valid.env'
63
+ ) do
64
+ it 'should update execution_path , tf_environment and state_file' do
65
+ @default_config.execution_path = tf_environment
66
+ expect(@default_config.tf_environment).to eq(tf_environment)
67
+ expect(@default_config.state_file).to eq(tf_state_file_path)
68
+ end
69
+ end
70
+ end
71
+ context 'when I set execution_path to something else and state_dir ' \
72
+ 'is false' do
73
+ Dotenv.overload(
74
+ 'spec/fixtures/envprocess_uniq_state_true_file_valid.env'
75
+ ) do
76
+ it 'should update execution_path, tf_environment but _not_ ' \
77
+ 'state_file' do
78
+ @default_config.execution_path = tf_environment
79
+ expect(@default_config.tf_environment).to eq(tf_environment)
80
+ expect(@default_config.state_file).to eq(nil)
81
+ end
82
+ end
83
+ end
84
+ end
85
+
86
+ describe 'opts' do
87
+ context 'with a default config object' do
88
+ it 'should return the default plan and execution path as keys' do
89
+ expect(@default_config.opts[:plan]).to eq(default_plan_string)
90
+ expect(@default_config.opts[:execution_path])
91
+ .to eq(default_exec_path_string)
92
+ end
93
+
94
+ it 'should reflect new values when the config object is updated' do
95
+ @default_config.plan = non_existent_plan
96
+ @default_config.execution_path = non_existent_path
97
+ expect(@default_config.opts[:plan]).to eq(non_existent_plan)
98
+ expect(@default_config.opts[:execution_path])
99
+ .to eq(non_existent_path)
100
+ end
101
+ end
102
+ end
103
+ end
104
+
105
+ describe Task do
106
+ it 'should raise an ArgumentError with no arguments' do
107
+ expect { RakeTerraform::ApplyTask::Task.new }
108
+ .to raise_error(ArgumentError)
109
+ end
110
+ end
111
+ end
112
+ end