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
checksums.yaml CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- M2I1MmU3MTY0MTcyYTZkNjljMzYxZWJkY2M3YzVlOGMwODA0YTU5YQ==
4
+ NWE5MWZmZDg3OGZlNWE4YmNkNmVhY2U3NTFkZGFmODhiMGMzOWE1MA==
5
5
  data.tar.gz: !binary |-
6
- NzZlYjVmM2U4MjQ3OTg2Njk1NWJkMzA3MzlhOGU3OGJjMWM1OTQ0Zg==
6
+ NjA1MWMyZjY1YjljN2U0NjM3MTY3OTM2Y2I0YzlmNTQ4NzhhOTAyNA==
7
7
  SHA512:
8
8
  metadata.gz: !binary |-
9
- MzcwMzQyOGVjMDM0ZjdhNWQ2OThjNzgwMDQ3MzVhYjY5NDQwZWQ0Mjg4ZGI3
10
- OTljNjgyM2ZmMTFiOWQxNjZjOWEwNDM3M2UwMzIzNDNmZmNmNmNjNzljMDk0
11
- NjRiZjRjMjJkYWYzNTVjOGMzYzk1NGQ4ODBiY2I4ODkwYjdkNjI=
9
+ YTFkNjY4MjY4MDkxNjNmZWNhYTQ1YTVmYWY4NWFhODkxYWY2YjJjZWE0YjM0
10
+ ZjEwMjE3M2YzMjc1NDU4ZjU0N2Y5YjQ3NDAzYTc2ODUwOWU0OGI4NGE2ZjE2
11
+ YmQzMWEwZWJkOWQwMmIxMTQyYmE4NmM5ODY0MDM3MWNiOWM2ZjE=
12
12
  data.tar.gz: !binary |-
13
- YTFkMDkxZTc1ZWU4ZDNjOTk1NTg4Mjc1ZjE0OGFjNTdjNDBhMDg3ZTliNmQ5
14
- OTc2NWFjM2Q5MDJmYzMxMzcyMDRhMjdlNGM2Y2ZkZjk0MGE0YjE4OTI4Y2E2
15
- OTVjZTBmMDYwYjgzODNmZjk5YjhiYzk0ZGUxZThjYzBiMWFjMjU=
13
+ NDJmZDc5YjE2YTQwMTVmMTU0NjJiYTQ4Y2ZjZGUxOWNlMWY2YTZjMWEwNDY5
14
+ YmYwZmU2ZjEzNjNmMmU5YzQyNmJiMzIwMzllYmY0N2M0MTE1MWI0MDY0NmZm
15
+ N2NkYWMxZDE5ZmQ1YzZmYmFlMTU0ZjYzNWJiOGQ1YWY5MjFmZjc=
data/.gitignore CHANGED
@@ -15,3 +15,22 @@ mkmf.log
15
15
  terraform/
16
16
  .idea/
17
17
  output/
18
+
19
+ # ignore bundlers vendor dir
20
+ /vendor/bundle/
21
+
22
+ # Default ignores for vim editor
23
+ .*.sw[a-z]
24
+ *.un~
25
+ Session.vim
26
+ .netrwhist
27
+ # Default ignores for emacs editor
28
+ *~
29
+ \#*\#
30
+ /.emacs.desktop
31
+ /.emacs.desktop.lock
32
+ .elc
33
+ auto-save-list
34
+ tramp
35
+ .\#*
36
+
data/.rspec ADDED
@@ -0,0 +1,5 @@
1
+ --color
2
+ --tty
3
+ --warnings
4
+ --require spec_helper
5
+ --format documentation
@@ -0,0 +1,25 @@
1
+ AllCops:
2
+ Exclude:
3
+ - 'spec/spec_helper.rb'
4
+ - 'vendor/**/*'
5
+ Style/FileName:
6
+ Exclude:
7
+ # usability - module name should match the gem name
8
+ - 'lib/rake-terraform.rb'
9
+ Metrics/AbcSize:
10
+ Exclude:
11
+ # TODO - fix this
12
+ - 'lib/rake-terraform/plan_task/task.rb'
13
+ Metrics/MethodLength:
14
+ Max: 12
15
+ Metrics/ModuleLength:
16
+ Exclude:
17
+ - 'spec/unit/*_spec.rb'
18
+ Metrics/CyclomaticComplexity:
19
+ Exclude:
20
+ # TODO: fix tf_plan method to support splat arguments and break
21
+ # down into one or more command constructor methods
22
+ - 'lib/rake-terraform/terraformcmd.rb'
23
+ Metrics/PerceivedComplexity:
24
+ Exclude:
25
+ - 'lib/rake-terraform/terraformcmd.rb'
@@ -1,5 +1,6 @@
1
1
  language: ruby
2
2
  rvm:
3
+ - 2.2.3
3
4
  - 2.1.0
4
5
  - 2.0.0
5
6
  deploy:
data/README.md CHANGED
@@ -52,7 +52,7 @@ You can set the following configuration for the task:
52
52
  * `t.execution_path`: The path from which to execute the plan (default: `./terraform`)
53
53
  * This is useful if you are referencing cloud-config files using relative paths
54
54
 
55
- To use these tasks, you should `require 'rake_terraform'` at the top of your Rakefile.
55
+ To use these tasks, you should `require 'rake-terraform'` at the top of your Rakefile.
56
56
 
57
57
  ### Default Tasks
58
58
 
@@ -96,6 +96,98 @@ The following environment variables can be set to tweak `default_task`'s behavio
96
96
  * `ENV['TERRAFORM_ENVIRONMENT_GLOB']` - Dir glob used to discover terraform environments (default: `terraform/**/*.tf`)
97
97
  * `ENV['TERRAFORM_OUTPUT_BASE']` - Directory to which plan files are saved/read. The environment name is appended to this automatically (default: `output/terraform`)
98
98
  * `ENV['TERRAFORM_CREDENTIAL_FILE']` - The path to your AWS credentials file (default: `~/.aws/credentials`)
99
+ * `ENV['TERRAFORM_UNIQUE_STATE']` - Whether to use a unique state for this run. Requires `TERRAFORM_STATE_FILE` OR `TERRAFORM_STATE_DIR_VAR`. Can be any truthy or falsey looking string from [this list][wannabe_bool_string] (e.g `TRUE` or `FALSE`)
100
+ * `ENV['TERRAFORM_STATE_FILE']` - The full path to a state file to use for this run. Only used when `TERRAFORM_UNIQUE_STATE` is true, and cannot be used in conjunction with `TERRAFORM_STATE_DIR_VAR`.
101
+ * `ENV['TERRAFORM_STATE_DIR_VAR']` - The name of an environment variable that holds a variable that will be used to reference a directory in which to store state files in for this run. This directory will be a subdirectory within the terraform environment. Only used when `TERRAFORM_STATE_DIR` is true, and cannot be used in conjunction with `TERRAFORM_STATE_FILE`
102
+
103
+ [wannabe_bool_string]: https://github.com/prodis/wannabe_bool#string
104
+
105
+ #### Unique States
106
+
107
+ Bu default, `rake-terraform` stores state within a given environment directory.
108
+
109
+ Sometimes, you will have several infrastructure environments ("infrastructure
110
+ environment" in this block here taken to mean e.g "staging" or "production"
111
+ rather than the broader "terraform environment" used more generally in this
112
+ doc) that are relatively homogeneous in terms of resources, where all changes
113
+ are rolled out through those infrastructure environments in a cascading manner.
114
+ Through application of [variable interpolation][tf_doc_var_interpol] and other
115
+ methods, you can provide differing configuration for each of your resources to
116
+ match those infrastructure environments. The issue with this is that Terraform
117
+ does not support resource names as variables, so when you come to apply the
118
+ same resource layout with differing configuration to the next infrastructure
119
+ environment, Terraform will see those configuration changes as needing to be
120
+ applied to the existing deployed resources.
121
+
122
+ One solution to this problem is to keep each of your infrastructure
123
+ environments in separate directories ("terraform environments"), each with
124
+ their own state file. An issue with this solution is that it does not confirm
125
+ to DRY principles, and also depends on you manually diffing changes between
126
+ files etc, rather than relying on `git diff` or similar. Another solution
127
+ might be to use separate divergent git branches, and cherry-pick relevant
128
+ commits between them. Again, depends on clean commit hygiene and easy to mess
129
+ up manual steps.
130
+
131
+ By using a unique state file for each of your infrastructure environments,
132
+ whilst utilising a single terraform environment, you can avoid repeating
133
+ yourself and manage roll out changes to each of your infrastructure
134
+ environments better.
135
+
136
+ To enable unique state files, you need to set the environment variable
137
+ `TERRAFORM_UNIQUE_STATE` to a [truthy value][wannabe_bool_string], then you
138
+ need to EITHER set `TERRAFORM_STATE_FILE` to the full path of your chosen state
139
+ file, OR set `TERRAFORM_STATE_DIR_VAR` to the name of another environment
140
+ variable containing the name of your infrastructure environment.
141
+
142
+ [tf_doc_var_interpol]: https://www.terraform.io/docs/configuration/interpolation.html
143
+
144
+ ##### Examples
145
+
146
+ Use a specific state file
147
+
148
+ $ export TERRAFORM_UNIQUE_STATE="TRUE"
149
+ $ export TERRAFORM_STATE_FILE="/home/dave/.tf_states/staging/web_tier.tfstate"
150
+ $ bundle exec rake terraform:plan_web_tier
151
+ ...
152
+
153
+ Use a variable to lookup the state file directory
154
+
155
+ $ export TF_VAR_infra_env="staging"
156
+ $ export TERRAFORM_UNIQUE_STATE="TRUE"
157
+ $ export TERRAFORM_STATE_DIR_VAR="TF_VAR_infra_env"
158
+ $ bundle exec rake terraform:plan_web_tier
159
+ ...
160
+
161
+ This would result in a directory layout resembling the following:
162
+
163
+ terraform
164
+ web_tier
165
+ main.tf
166
+ variables.tf
167
+ output.tf
168
+ state
169
+ staging
170
+ terraform.tfstate
171
+ terraform.tfstate.backup
172
+ production
173
+ terraform.tfstate
174
+ terraform.tfstate.backup
175
+ app_tier
176
+ main.tf
177
+ variables.tf
178
+ output.tf
179
+ state
180
+ staging
181
+ terraform.tfstate
182
+ terraform.tfstate.backup
183
+ production
184
+ terraform.tfstate
185
+ terraform.tfstate.backup
186
+
187
+ ## Testing
188
+
189
+ There is currently a basic rspec-based test harness in place. The default task
190
+ runs unit tests and rubocop tests.
99
191
 
100
192
  ## Contributing
101
193
 
data/Rakefile CHANGED
@@ -1,7 +1,26 @@
1
1
  require 'bundler/gem_tasks'
2
- require 'rake-terraform/default_tasks'
3
2
  require 'rubocop/rake_task'
3
+ require 'rspec/core/rake_task'
4
+ require 'rake-terraform'
5
+ # include project rake tasks
6
+ require 'rake-terraform/default_tasks'
7
+
8
+ namespace :test do
9
+ # default unit tests
10
+ desc 'Run all unit tests in default spec suite'
11
+ RSpec::Core::RakeTask.new(:unit) do |task|
12
+ task.pattern = 'spec/(unit|rake-terraform)/*_spec.rb'
13
+ end
14
+ # add rubocop tasks to test namespace
15
+ RuboCop::RakeTask.new
16
+ # run all non-integration/default tests
17
+ desc 'Run all default test suites'
18
+ task default: [:unit, :rubocop]
19
+ end
4
20
 
5
- task default: :rubocop
21
+ # default task is to run all the default test suites
22
+ task default: ['test:default']
6
23
 
7
- RuboCop::RakeTask.new
24
+ # keep a rubocop alias in place for backwards compatibility
25
+ desc 'Alias for test:rubocop'
26
+ task rubocop: ['test:rubocop']
@@ -1,5 +1,6 @@
1
1
  require 'rake-terraform/version'
2
2
  require 'rake-terraform/dsl'
3
+ require 'rake-terraform/errors'
3
4
 
4
5
  # Top-level module
5
6
  module RakeTerraform
@@ -0,0 +1,47 @@
1
+ require 'rake-terraform/env_process'
2
+
3
+ module RakeTerraform
4
+ module ApplyTask
5
+ # Configuration data for terraform apply task
6
+ class Config
7
+ prepend RakeTerraform::EnvProcess
8
+
9
+ attr_writer :plan
10
+
11
+ def initialize
12
+ # initialize RakeTerraform::EnvProcess
13
+ super
14
+ end
15
+
16
+ def execution_path
17
+ @execution_path ||= File.expand_path 'terraform'
18
+ end
19
+
20
+ # setter method for execution_path triggers setters for tf_environment and
21
+ # state_file so that these are dynamically updated on change (but only if
22
+ # we are using directory state, and not explicit path to a state file)
23
+ def execution_path=(dir)
24
+ @tf_environment = dir
25
+ @state_file = tf_state_file if @state_dir
26
+ @execution_path = dir
27
+ end
28
+
29
+ def plan
30
+ @plan ||= File.expand_path(default_plan)
31
+ end
32
+
33
+ def opts
34
+ Map.new(plan: plan,
35
+ execution_path: execution_path,
36
+ unique_state: unique_state,
37
+ state_file: state_file)
38
+ end
39
+
40
+ private
41
+
42
+ def default_plan
43
+ File.join('output', 'terraform', 'plan.tf')
44
+ end
45
+ end
46
+ end
47
+ end
@@ -1,53 +1,41 @@
1
+ require 'highline/import'
2
+ require 'rake-terraform/basetask'
3
+ require 'rake-terraform/terraformcmd'
4
+
1
5
  module RakeTerraform
2
6
  module ApplyTask
3
- # Configuration data for terraform apply task
4
- class Config
5
- attr_accessor :plan
6
- attr_accessor :execution_path
7
- def initialize
8
- @plan = File.expand_path(default_plan)
9
- @execution_path = File.expand_path 'terraform'
10
- end
11
-
12
- def opts
13
- Map.new(plan: @plan,
14
- execution_path: @execution_path)
15
- end
16
-
17
- private
18
-
19
- def default_plan
20
- File.join('output', 'terraform', 'plan.tf')
21
- end
22
- end
23
-
24
7
  # Custom rake task to run `terraform apply`
25
8
  class Task < BaseTask
9
+ include RakeTerraform::TerraformCmd
10
+
26
11
  def initialize(opts)
27
12
  @opts = opts
28
13
  end
29
14
 
30
15
  def execute
31
- require 'highline/import'
32
-
33
16
  plan = @opts.get(:plan)
34
- validate_terraform_installed
35
- ensure_plan_exists plan
36
-
37
- system "terraform show --module-depth=2 #{plan}"
38
-
39
- say 'The above changes will be applied to your environment.'
40
- exit unless agree 'Are you sure you want to execute this plan? (y/n)'
41
-
17
+ pre_execute_checks(plan)
42
18
  Dir.chdir(@opts.get(:execution_path)) do
43
- system "terraform apply #{plan}"
19
+ if @opts[:unique_state]
20
+ tf_apply(plan, @opts[:state_file])
21
+ else
22
+ tf_apply(plan)
23
+ end
44
24
  end
45
25
  end
46
26
 
47
27
  private
48
28
 
29
+ # run pre execution checks
30
+ def pre_execute_checks(plan = @opts.get(:plan))
31
+ validate_terraform_installed
32
+ ensure_plan_exists plan
33
+ tf_show(plan)
34
+ say 'The above changes will be applied to your environment.'
35
+ exit unless agree 'Are you sure you want to execute this plan? (y/n)'
36
+ end
37
+
49
38
  def ensure_plan_exists(plan)
50
- require 'fileutils'
51
39
  fail "Plan #{plan} does not exist! Aborting!" unless File.exist? plan
52
40
  end
53
41
  end
@@ -0,0 +1,11 @@
1
+ require 'rake-terraform/apply_task/config'
2
+ require 'rake-terraform/apply_task/task'
3
+
4
+ module RakeTerraform
5
+ # == RakeTerraform::ApplyTask
6
+ #
7
+ # Helper classes for applying a terraform plan
8
+ #
9
+ module ApplyTask
10
+ end
11
+ end
@@ -1,4 +1,5 @@
1
1
  require 'rake/task'
2
+ require 'fileutils'
2
3
  require 'iniparse'
3
4
  require 'English'
4
5
 
@@ -7,7 +8,7 @@ module RakeTerraform
7
8
  class BaseTask < Rake::Task
8
9
  def validate_terraform_installed
9
10
  error = 'Please ensure you have terraform installed and on your path!'
10
- fail error unless terraform_installed?
11
+ fail TerraformNotInstalled, error unless terraform_installed?
11
12
  end
12
13
 
13
14
  def terraform_installed?
@@ -1,7 +1,11 @@
1
- require 'rake_terraform'
1
+ require 'rake-terraform'
2
2
  require 'pathname'
3
3
 
4
4
  namespace :terraform do
5
+ # TODO: refactor all environment variable processing into
6
+ # RakeTerraform::EnvProcess, include in this task and pass to the
7
+ # relevant Config classes, rather than including directly in the Config
8
+ # classes
5
9
  env_glob = ENV['TERRAFORM_ENVIRONMENT_GLOB'] || 'terraform/**/*.tf'
6
10
  output_base = ENV['TERRAFORM_OUTPUT_BASE'] || 'output/terraform'
7
11
  credential_file = ENV['TERRAFORM_CREDENTIAL_FILE']
@@ -25,7 +29,6 @@ namespace :terraform do
25
29
 
26
30
  short_name = abs_relative_path.to_s.tr('/', '_')
27
31
  plan_path = File.expand_path File.join(output_base, "#{short_name}.tf")
28
-
29
32
  desc "Plan migration of #{short_name}" if hide_tasks == 'false'
30
33
  terraform_plan "plan_#{short_name}" do |t|
31
34
  t.input_dir = env
@@ -7,7 +7,7 @@ module RakeTerraform
7
7
  # Definitions of methods for custom rake tasks
8
8
  module DSL
9
9
  def terraform_plan(*args)
10
- require 'rake-terraform/tasks/plantask'
10
+ require 'rake-terraform/plantask'
11
11
  Rake::Task.define_task(*args) do
12
12
  c = RakeTerraform::PlanTask::Config.new
13
13
  yield c
@@ -16,7 +16,7 @@ module RakeTerraform
16
16
  end
17
17
 
18
18
  def terraform_apply(*args)
19
- require 'rake-terraform/tasks/applytask'
19
+ require 'rake-terraform/applytask'
20
20
  Rake::Task.define_task(*args) do
21
21
  c = RakeTerraform::ApplyTask::Config.new
22
22
  yield c
@@ -0,0 +1,142 @@
1
+ require 'wannabe_bool'
2
+
3
+ module RakeTerraform
4
+ # == RakeTerraform::EnvProcess
5
+ #
6
+ # Mixin for processing environment variables
7
+ #
8
+ # TODO: refactor all non accessor methods as private methods
9
+ #
10
+ module EnvProcess
11
+ attr_reader :unique_state, :state_file, :state_dir_var, :state_dir,
12
+ :tf_environment
13
+
14
+ def initialize
15
+ @unique_state, @state_file, @state_dir_var, @state_dir = nil
16
+ @tf_environment = nil
17
+ tf_unique_state_valid? && @unique_state = tf_unique_state
18
+ tf_state_dir_var_valid? && @state_dir_var = tf_state_dir_var
19
+ # tf_state_file represents the full path to the calculated file within
20
+ # tf_state_dir if given
21
+ if tf_state_dir_valid?
22
+ @state_dir = tf_state_dir
23
+ @state_file = tf_state_file
24
+ end
25
+ tf_state_file_valid? && @state_file = tf_state_file
26
+ end
27
+
28
+ # whether or not unique states are enabled and required args are also given
29
+ def tf_unique_state
30
+ state_var = ENV['TERRAFORM_UNIQUE_STATE'].to_b
31
+ return false if state_var == false
32
+ unless tf_unique_state_valid?
33
+ fail(
34
+ ArgumentError,
35
+ 'Both or neither of TERRAFORM_STATE_FILE or TERRAFORM_STATE_DIR_VAR' \
36
+ ' given, or missing target for TERRAFORM_STATE_DIR_VAR'
37
+ )
38
+ end
39
+ ENV['TERRAFORM_UNIQUE_STATE'].to_b
40
+ end
41
+
42
+ # if we are using tf_state_dir_var and that is valid, then return the full
43
+ # path to the calculated state file. Otherwise return the value of a valid
44
+ # TERRAFORM_STATE_FILE variable
45
+ def tf_state_file
46
+ return state_dir_full_path if tf_state_dir_valid?
47
+ return nil if ENV['TERRAFORM_STATE_FILE'].nil?
48
+ unless tf_state_file_valid?
49
+ fail(
50
+ ArgumentError,
51
+ 'Argument for TERRAFORM_STATE_FILE is invalid'
52
+ )
53
+ end
54
+ ENV['TERRAFORM_STATE_FILE']
55
+ end
56
+
57
+ # return the value if tf_state_dir_var
58
+ # see also: tf_state_dir
59
+ def tf_state_dir_var
60
+ return nil if ENV['TERRAFORM_STATE_DIR_VAR'].nil?
61
+ unless tf_state_dir_var_valid?
62
+ fail(
63
+ ArgumentError,
64
+ 'Argument for TERRAFORM_STATE_DIR_VAR is invalid'
65
+ )
66
+ end
67
+ ENV['TERRAFORM_STATE_DIR_VAR']
68
+ end
69
+
70
+ # return the target of tf_state_dir_var
71
+ # see also: tf_state_dir_var
72
+ def tf_state_dir
73
+ return nil if ENV['TERRAFORM_STATE_DIR_VAR'].nil?
74
+ unless tf_state_dir_valid?
75
+ fail(
76
+ ArgumentError,
77
+ 'Argument for TERRAFORM_STATE_DIR_VAR is invalid'
78
+ )
79
+ end
80
+ dir_var = ENV['TERRAFORM_STATE_DIR_VAR']
81
+ "#{tf_env_string}state/#{ENV[dir_var]}"
82
+ end
83
+
84
+ # calculate the full path to a state file within tf_state_dir
85
+ def state_dir_full_path(dir = tf_state_dir)
86
+ File.expand_path(
87
+ File.join(dir, default_state_file_name)
88
+ )
89
+ end
90
+
91
+ # if @tf_environment is set then return that, postfixed by a '/' -
92
+ # otherwise return 'terraform/'
93
+ def tf_env_string
94
+ if @tf_environment
95
+ "#{@tf_environment}/"
96
+ else
97
+ 'terraform/'
98
+ end
99
+ end
100
+
101
+ # validate tf_unique_state
102
+ def tf_unique_state_valid?
103
+ state_var = ENV['TERRAFORM_UNIQUE_STATE'].to_b
104
+ return true if state_var == false
105
+ if tf_state_file_valid? && tf_state_dir_var_valid?
106
+ return false
107
+ else
108
+ tf_state_file_valid? || tf_state_dir_var_valid?
109
+ end
110
+ end
111
+
112
+ # validate tf_state_file_valid?
113
+ # returns false if no environment set, or the file does not have a single
114
+ # a-z0-9 char
115
+ # TODO: improve regex?
116
+ def tf_state_file_valid?
117
+ return false if ENV['TERRAFORM_STATE_FILE'].nil?
118
+ ENV['TERRAFORM_STATE_FILE'] =~ /[a-z0-9]/i
119
+ end
120
+
121
+ # validate tf_state_dir_var (and corresponding target) is valid
122
+ # returns false if
123
+ # * TERRAFORM_STATE_DIR_VAR is nil
124
+ # * We cannot find the variable referenced by TERRAFORM_STATE_DIR_VAR
125
+ # * The variable referenced contains something other than a-z0-9_ chars
126
+ def tf_state_dir_var_valid?
127
+ return false if ENV['TERRAFORM_STATE_DIR_VAR'].nil?
128
+ dir_var = ENV['TERRAFORM_STATE_DIR_VAR']
129
+ return false unless dir_var =~ /^[a-z0-9_]+$/i
130
+ value = ENV[dir_var]
131
+ return false if value.nil?
132
+ value =~ /^[a-z0-9_]+$/i
133
+ end
134
+
135
+ alias_method :tf_state_dir_valid?, :tf_state_dir_var_valid?
136
+
137
+ # name of the default state file
138
+ def default_state_file_name
139
+ 'terraform.tfstate'
140
+ end
141
+ end
142
+ end