terraform-wrapper 1.0.1 → 1.2.3

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0d30c2203437eadddce4f57b8f845b991a2460b7c20d3fc6e223386a26707c78
4
- data.tar.gz: 1b1c50e4a9beffa0b51b847505b06522bcf4849016db8ee2fc0e2c2d811266bc
3
+ metadata.gz: f79a8c4cca3d760d2dac3766055a31b94ce3d24785d946c37cf63c8976ef8000
4
+ data.tar.gz: ff9a1986b4473b7697439d3729f79dfffa18f22301436171f736ff238ee16394
5
5
  SHA512:
6
- metadata.gz: c333afef9d9a4cf2fa35c0ad0d02a37a7945fe5c2d5261f3de0228a6f1d2f41a844f674faa63eb7c803db81b647e6df3e36e17f709a11ff4f1ea648c84d25cba
7
- data.tar.gz: 2d4264529b09c33a18c4ea15438e43a5fad87ffe10a30ccca51abbbd011984e5b509341e17c070c88cf74e76f329ac9fa035d150149ef4d518cbcf4bb7bc1d87
6
+ metadata.gz: 8e7271146e058d3166c7414710a85345fb3d5d26b5a5beaecdc36b416c6b65bf4197a0a6325cdeec04996e554f06c345fcee5878d23f0efd02b431261b047491
7
+ data.tar.gz: a097d6d1baf0373e7ed4ba14ef34d36ecf907b022c87da6ea747d2398d46dea65c3acf52a4d6dcc435b050debbf30e8d2ba288902d55219a44b18fabd459d667
@@ -22,7 +22,7 @@ module TerraformWrapper
22
22
 
23
23
  ###############################################################################
24
24
 
25
- def self.define_tasks(component:, options: Hash.new, service:)
25
+ def self.deployment_tasks(component:, options: Hash.new, service:)
26
26
  @logger.info("Building tasks for service: #{service}, component: #{component}...")
27
27
 
28
28
  @logger.fatal("Options must be specified as a hash!") unless options.kind_of?(Hash)
@@ -43,8 +43,12 @@ module TerraformWrapper
43
43
  config_options["backend-options"] = options.key?("config-backend-options") ? options["config-backend-options"] : Hash.new
44
44
  config_options["service"] = service
45
45
 
46
- binary = TerraformWrapper::Shared::Binary.new(options: binary_options)
47
- code = TerraformWrapper::Shared::Code.new(options: code_options)
46
+ provider_options = Hash.new
47
+ provider_options["platforms"] = options.key?("provider-platforms") ? options["provider-platforms"] : Array.new
48
+
49
+ provider = TerraformWrapper::Shared::Provider.new(options: provider_options)
50
+ binary = TerraformWrapper::Shared::Binary.new(options: binary_options, provider: provider)
51
+ code = TerraformWrapper::Shared::Code.new(options: code_options)
48
52
 
49
53
  tasks = Array.new
50
54
  tasks << TerraformWrapper::Tasks::Apply.new(binary: binary, code: code, options: config_options)
@@ -11,6 +11,7 @@ require_relative 'shared/binary'
11
11
  require_relative 'shared/code'
12
12
  require_relative 'shared/config'
13
13
  require_relative 'shared/latest'
14
+ require_relative 'shared/provider'
14
15
  require_relative 'shared/runner'
15
16
  require_relative 'shared/variables'
16
17
 
@@ -25,13 +25,13 @@ module TerraformWrapper
25
25
 
26
26
  ###############################################################################
27
27
 
28
- @password = nil
28
+ @keyvault = nil
29
+ @secret_username = nil
30
+ @secret_password = nil
29
31
 
30
32
  ###############################################################################
31
33
 
32
- attr_reader :subscription
33
- attr_reader :tenant
34
- attr_reader :username
34
+ @subscription
35
35
 
36
36
  ###############################################################################
37
37
 
@@ -42,10 +42,17 @@ module TerraformWrapper
42
42
  ###############################################################################
43
43
 
44
44
  def auth()
45
- ENV["ARM_SUBSCRIPTION_ID"] = @subscription
46
- ENV["ARM_TENANT_ID"] = @tenant
47
- ENV["ARM_CLIENT_ID"] = @username unless @username.nil?
48
- ENV["ARM_CLIENT_SECRET"] = @password unless @password.nil?
45
+ details = subscription_details(subscription: @subscription)
46
+
47
+ subscription = details["id"]
48
+ tenant = details["tenant"]
49
+ username = @keyvault.nil? ? nil : secret(vault: @keyvault, name: @secret_username)
50
+ password = @keyvault.nil? ? nil : secret(vault: @keyvault, name: @secret_password)
51
+
52
+ ENV["ARM_SUBSCRIPTION_ID"] = subscription
53
+ ENV["ARM_TENANT_ID"] = tenant
54
+ ENV["ARM_CLIENT_ID"] = username unless username.nil?
55
+ ENV["ARM_CLIENT_SECRET"] = password unless password.nil?
49
56
  logger.success("Azure authenticator environment variables set!")
50
57
  end
51
58
 
@@ -151,20 +158,13 @@ module TerraformWrapper
151
158
  end
152
159
 
153
160
  begin
154
- subscription = subscription % @variables.identifiers
155
- keyvault = keyvault % @variables.identifiers unless keyvault.nil?
156
- username = username % @variables.identifiers unless keyvault.nil?
157
- password = password % @variables.identifiers unless keyvault.nil?
161
+ @subscription = subscription % @variables.identifiers
162
+ @keyvault = keyvault % @variables.identifiers unless keyvault.nil?
163
+ @secret_username = username % @variables.identifiers unless keyvault.nil?
164
+ @secret_password = password % @variables.identifiers unless keyvault.nil?
158
165
  rescue
159
166
  logger.fatal("Azure authenticator options contain identifiers that are not included in the configuration file!")
160
167
  end
161
-
162
- details = subscription_details(subscription: subscription)
163
-
164
- @subscription = details["id"]
165
- @tenant = details["tenant"]
166
- @username = keyvault.nil? ? nil : secret(vault: keyvault, name: username)
167
- @password = keyvault.nil? ? nil : secret(vault: keyvault, name: password)
168
168
  end
169
169
 
170
170
  ###############################################################################
@@ -22,12 +22,13 @@ module TerraformWrapper
22
22
 
23
23
  attr_reader :base
24
24
  attr_reader :directory
25
+ attr_reader :provider
25
26
  attr_reader :platform
26
27
  attr_reader :version
27
28
 
28
29
  ###############################################################################
29
30
 
30
- def initialize(options:)
31
+ def initialize(options:, provider:)
31
32
  logger.fatal("Binary base path must be a string!") unless options["base"].kind_of?(String)
32
33
  logger.fatal("Binary base path must not be blank!") if options["base"].strip.empty?
33
34
 
@@ -39,8 +40,8 @@ module TerraformWrapper
39
40
  @version = options["version"]
40
41
 
41
42
  @platform = platform_detect
42
-
43
43
  @directory = File.join(@base, @version, @platform)
44
+ @provider = provider
44
45
  @name = "terraform"
45
46
  end
46
47
 
@@ -34,8 +34,6 @@ module TerraformWrapper
34
34
  @name = options["name"]
35
35
 
36
36
  @path = File.join(@base, @name)
37
-
38
- logger.fatal("Terraform code location: #{@path} does not exist!") unless exists
39
37
  end
40
38
 
41
39
  ###############################################################################
@@ -41,16 +41,16 @@ module TerraformWrapper
41
41
 
42
42
  @base = options["base"]
43
43
 
44
- logger.fatal("Configuration service name must be a string!") unless options["service"].kind_of?(String)
45
- logger.fatal("Configuration service name must not be blank!") if options["service"].strip.empty?
46
-
47
- @service = options["service"]
48
-
49
44
  logger.fatal("Configuration name must be a string!") unless options["name"].kind_of?(String)
50
45
  logger.fatal("Configuration name must not be blank!") if options["name"].strip.empty?
51
46
 
52
47
  @name = options["name"]
53
48
 
49
+ logger.fatal("Configuration service name must be a string!") unless options["service"].kind_of?(String)
50
+ logger.fatal("Configuration service name must not be blank!") if options["service"].strip.empty?
51
+
52
+ @service = options["service"]
53
+
54
54
  logger.fatal("Configuration authenticator for Azure enabled must be a Boolean!") unless [ true, false ].include?(options["auth-azure"])
55
55
 
56
56
  auth_azure = options["auth-azure"]
@@ -75,15 +75,28 @@ module TerraformWrapper
75
75
  logger.fatal("Invalid YAML in configuration file: #{@path}") unless yaml.kind_of?(Hash)
76
76
 
77
77
  identifiers = yaml.key?("identifiers") ? yaml["identifiers"] : Hash.new
78
- @variables = TerraformWrapper::Shared::Variables.new(config: @name, component: @code.name, service: @service, identifiers: identifiers)
79
- @variables.add_variables(variables: yaml["globals"]) if yaml.key?("globals")
78
+ @variables = TerraformWrapper::Shared::Variables.new(config: @name, component: @code.name, service: @service, identifiers: identifiers)
79
+
80
+ if yaml.key?("globals") then
81
+ logger.fatal("Key 'globals' is not a hash in configuration file: #{@path}") unless yaml["globals"].kind_of?(Hash)
82
+ globals = yaml["globals"]
83
+
84
+ @variables.add_variables(variables: globals["variables"]) if globals.key?("variables")
85
+ end
80
86
 
81
87
  if yaml.key?("terraform") then
82
88
  logger.fatal("Key 'terraform' is not a hash in configuration file: #{@path}") unless yaml["terraform"].kind_of?(Hash)
83
89
  terraform = yaml["terraform"]
84
90
 
85
- @variables.add_variables(variables: terraform["variables"]) if terraform.key?("variables")
86
- @variables.add_files(base: @base, files: terraform["files"]) if terraform.key?("files")
91
+ [ "globals", @code.name ].each do |extra|
92
+ if terraform.key?(extra) then
93
+ logger.fatal("Key '#{extra}' under 'terraform' is not a hash in configuration file: #{@path}") unless terraform[extra].kind_of?(Hash)
94
+ section = terraform[extra]
95
+
96
+ @variables.add_variables(variables: section["variables"]) if section.key?("variables")
97
+ @variables.add_files(base: @base, files: section["files"]) if section.key?("files")
98
+ end
99
+ end
87
100
  end
88
101
 
89
102
  @auths = Array.new
@@ -0,0 +1,41 @@
1
+ ###############################################################################
2
+
3
+ module TerraformWrapper
4
+
5
+ ###############################################################################
6
+
7
+ module Shared
8
+
9
+ ###############################################################################
10
+
11
+ class Provider
12
+
13
+ ###############################################################################
14
+
15
+ include TerraformWrapper::Shared::Logging
16
+
17
+ ###############################################################################
18
+
19
+ attr_reader :platforms
20
+
21
+ ###############################################################################
22
+
23
+ def initialize(options:)
24
+ logger.fatal("Provider platforms to prefetch hashs for must be an array!") unless options["platforms"].kind_of?(Array)
25
+
26
+ @platforms = options["platforms"]
27
+ end
28
+
29
+ ###############################################################################
30
+
31
+ end
32
+
33
+ ###############################################################################
34
+
35
+ end
36
+
37
+ ###############################################################################
38
+
39
+ end
40
+
41
+ ###############################################################################
@@ -14,6 +14,10 @@ module TerraformWrapper
14
14
 
15
15
  include TerraformWrapper::Shared::Logging
16
16
 
17
+ ###############################################################################
18
+
19
+ @@lockfile_name = ".terraform.lock.hcl"
20
+
17
21
  ###############################################################################
18
22
 
19
23
  attr_reader :binary
@@ -25,6 +29,8 @@ module TerraformWrapper
25
29
  ###############################################################################
26
30
 
27
31
  def initialize(binary:, code:)
32
+ logger.fatal("Terraform code location: #{code.path} does not exist!") unless code.check
33
+
28
34
  @binary = binary
29
35
  @code = code
30
36
 
@@ -33,35 +39,13 @@ module TerraformWrapper
33
39
 
34
40
  ###############################################################################
35
41
 
36
- def download(upgrade: false)
42
+ def download
37
43
  parameters = Array.new
38
44
  parameters.append("-backend=false")
39
- parameters.append("-upgrade") if upgrade
40
-
41
- @downloaded = run(action: "init", parameters: parameters)
42
- logger.fatal("Failed to download Terraform modules.") unless @downloaded
43
- end
44
-
45
- ###############################################################################
46
45
 
47
- def import(address: nil, id: nil)
48
- logger.fatal("Cannot Terraform import before initialising backend!") unless initialised
49
-
50
- logger.fatal("Terraform state address for import must be a string!") unless address.kind_of?(String)
51
- logger.fatal("Terraform state address for import must be a string!") unless address.kind_of?(String)
52
- logger.fatal("Terraform state address for import must not be blank!") if address.strip.empty?
46
+ logger.fatal("Failed to download Terraform modules.") unless run(action: "init", parameters: parameters)
53
47
 
54
- logger.fatal("Identification for infrastructure to import must be a string!") unless id.kind_of?(String)
55
- logger.fatal("Identification for infrastructure to import must not be blank!") if id.strip.empty?
56
-
57
- parameters = Array.new
58
- parameters.concat(variable_files)
59
- parameters.concat(variable_strings)
60
-
61
- parameters.append("'#{address}'")
62
- parameters.append("'#{id}'")
63
-
64
- logger.fatal("Terraform import failed!") unless run(action: "import", parameters: parameters)
48
+ @downloaded = true
65
49
  end
66
50
 
67
51
  ###############################################################################
@@ -76,28 +60,31 @@ module TerraformWrapper
76
60
 
77
61
  config.auths.map(&:auth)
78
62
 
63
+ logger.fatal("Failed to initialise Terraform with backend.") unless run(action: "init", parameters: parameters)
64
+
79
65
  @config = config
80
- @initialised = run(action: "init", parameters: parameters)
81
- logger.fatal("Failed to initialise Terraform with backend.") unless @initialised
66
+ @initialised = true
82
67
  end
83
68
 
84
69
  ###############################################################################
85
70
 
86
- def plan(destroy: false, file: nil)
87
- logger.fatal("Cannot Terraform plan before initialising backend!") unless initialised
88
-
89
- parameters = Array.new
90
- parameters.concat(variable_files)
91
- parameters.concat(variable_strings)
71
+ def upgrade
72
+ lockfile_path = File.join(@code.path, @@lockfile_name)
92
73
 
93
- if not file.nil? and file.kind_of?(String) and not file.strip.empty? then
94
- logger.fatal("Failed to create plan directory: #{directory}") unless ::TerraformWrapper.create_directory(directory: File.dirname(file), purpose: "plan")
95
- parameters.append("-out=\"#{file}\"")
74
+ if File.file?(lockfile_path)
75
+ logger.info("Removing lock file: #{lockfile_path}")
76
+ File.delete(lockfile_path)
96
77
  end
97
78
 
98
- parameters.append("-destroy") if destroy
79
+ logger.fatal("Lock file removal failed!") if File.file?(lockfile_path)
99
80
 
100
- logger.fatal("Terraform plan failed!") unless run(action: "plan", parameters: parameters)
81
+ parameters = Array.new
82
+ parameters.append("-update")
83
+ logger.fatal("Failed to upgrade Terraform modules!") unless run(action: "get", parameters: parameters)
84
+
85
+ parameters = Array.new
86
+ parameters.append("lock")
87
+ logger.fatal("Failed to upgrade Terraform providers!") unless run(action: "providers", parameters: parameters)
101
88
  end
102
89
 
103
90
  ###############################################################################
@@ -120,6 +107,25 @@ module TerraformWrapper
120
107
  logger.fatal("Terraform apply failed!") unless run(action: "apply", parameters: parameters)
121
108
  end
122
109
 
110
+ ###############################################################################
111
+
112
+ def plan(destroy: false, file: nil)
113
+ logger.fatal("Cannot Terraform plan before initialising backend!") unless initialised
114
+
115
+ parameters = Array.new
116
+ parameters.concat(variable_files)
117
+ parameters.concat(variable_strings)
118
+
119
+ if not file.nil? and file.kind_of?(String) and not file.strip.empty? then
120
+ logger.fatal("Failed to create plan directory: #{directory}") unless ::TerraformWrapper.create_directory(directory: File.dirname(file), purpose: "plan")
121
+ parameters.append("-out=\"#{file}\"")
122
+ end
123
+
124
+ parameters.append("-destroy") if destroy
125
+
126
+ logger.fatal("Terraform plan failed!") unless run(action: "plan", parameters: parameters)
127
+ end
128
+
123
129
  ###############################################################################
124
130
 
125
131
  def destroy
@@ -133,6 +139,28 @@ module TerraformWrapper
133
139
  logger.fatal("Terraform destroy failed!") unless run(action: "destroy", parameters: parameters)
134
140
  end
135
141
 
142
+ ###############################################################################
143
+
144
+ def import(address: nil, id: nil)
145
+ logger.fatal("Cannot Terraform import before initialising backend!") unless initialised
146
+
147
+ logger.fatal("Terraform state address for import must be a string!") unless address.kind_of?(String)
148
+ logger.fatal("Terraform state address for import must be a string!") unless address.kind_of?(String)
149
+ logger.fatal("Terraform state address for import must not be blank!") if address.strip.empty?
150
+
151
+ logger.fatal("Identification for infrastructure to import must be a string!") unless id.kind_of?(String)
152
+ logger.fatal("Identification for infrastructure to import must not be blank!") if id.strip.empty?
153
+
154
+ parameters = Array.new
155
+ parameters.concat(variable_files)
156
+ parameters.concat(variable_strings)
157
+
158
+ parameters.append("'#{address}'")
159
+ parameters.append("'#{id}'")
160
+
161
+ logger.fatal("Terraform import failed!") unless run(action: "import", parameters: parameters)
162
+ end
163
+
136
164
  ###############################################################################
137
165
 
138
166
  def validate
@@ -144,6 +172,27 @@ module TerraformWrapper
144
172
 
145
173
  private
146
174
 
175
+ ###############################################################################
176
+
177
+ def run(action:, parameters: Array.new)
178
+ result = false
179
+
180
+ parameters.reject! { |item| not item.kind_of?(String) or item.strip.empty? }
181
+
182
+ cmdline = [ "\"#{@binary.path}\"", action ].concat(parameters).join(" ")
183
+
184
+ logger.info("Starting Terraform, action: #{action}")
185
+
186
+ puts("\n" + ('#' * 80) + "\n\n")
187
+
188
+ Dir.chdir(@code.path)
189
+ result = system(cmdline) || false
190
+
191
+ puts("\n" + ('#' * 80) + "\n\n")
192
+
193
+ return result
194
+ end
195
+
147
196
  ###############################################################################
148
197
 
149
198
  def variable_files
@@ -172,27 +221,6 @@ module TerraformWrapper
172
221
  return result
173
222
  end
174
223
 
175
- ###############################################################################
176
-
177
- def run(action:, parameters: Array.new)
178
- result = false
179
-
180
- parameters.reject! { |item| not item.kind_of?(String) or item.strip.empty? }
181
-
182
- cmdline = [ "\"#{@binary.path}\"", action ].concat(parameters).join(" ")
183
-
184
- logger.info("Starting Terraform, action: #{action}")
185
-
186
- puts("\n" + ('#' * 80) + "\n\n")
187
-
188
- Dir.chdir(@code.path)
189
- result = system(cmdline) || false
190
-
191
- puts("\n" + ('#' * 80) + "\n\n")
192
-
193
- return result
194
- end
195
-
196
224
  ###############################################################################
197
225
 
198
226
  end
@@ -36,11 +36,11 @@ module TerraformWrapper
36
36
  @core[:config] = config
37
37
  @core[:service] = service
38
38
 
39
- user = cleanse(variables: identifiers, reserved: @core.keys)
39
+ user = cleanse(variables: identifiers, reserved: @core.keys, downcase: true)
40
40
  merged = @core.merge(user)
41
41
 
42
42
  @identifiers = sort ? merged.sort.to_h : merged
43
- @values = @identifiers
43
+ @values = Hash.new
44
44
  @files = Array.new
45
45
  end
46
46
 
@@ -89,7 +89,7 @@ module TerraformWrapper
89
89
  ###############################################################################
90
90
 
91
91
  def clear_variables()
92
- @values = @identifers
92
+ @values = Hash.new
93
93
  end
94
94
 
95
95
  ###############################################################################
@@ -98,17 +98,20 @@ module TerraformWrapper
98
98
 
99
99
  ###############################################################################
100
100
 
101
- def cleanse(variables:, reserved:)
101
+ def cleanse(variables:, reserved:, downcase: false)
102
102
  result = Hash.new
103
103
 
104
104
  variables.keys.each do |key|
105
105
  logger.fatal("Could not clean variables hash. All keys MUST be strings!") unless key.kind_of?(String)
106
- logger.fatal("Could not clean variables hash, key: #{key.downcase} is reserved or already in use and cannot be used!") if reserved.include?(key.downcase.to_sym)
107
- logger.fatal("Could not clean variables hash, duplicate key found: #{key.downcase}!") if result.key?(key.downcase.to_sym)
108
- logger.fatal("Could not clean variables hash, value for: #{key.downcase} is not a string!") unless variables[key].kind_of?(String)
109
- logger.fatal("Could not clean variables hash, value for: #{key.downcase} is empty!") if variables[key].strip.empty?
110
106
 
111
- result[key.downcase.to_sym] = variables[key].strip
107
+ sym = downcase ? key.downcase.to_sym : key.to_sym
108
+
109
+ logger.fatal("Could not clean variables hash, key: #{sym.to_s} is reserved or already in use and cannot be used!") if reserved.include?(sym)
110
+ logger.fatal("Could not clean variables hash, duplicate key found: #{sym.to_s}!") if result.key?(sym)
111
+ logger.fatal("Could not clean variables hash, value for: #{sym.to_s} is not a string!") unless variables[key].kind_of?(String)
112
+ logger.fatal("Could not clean variables hash, value for: #{sym.to_s} is empty!") if variables[key].strip.empty?
113
+
114
+ result[sym] = variables[key].strip
112
115
  end
113
116
 
114
117
  return result
@@ -34,12 +34,12 @@ module TerraformWrapper
34
34
 
35
35
  def upgrade_task
36
36
  desc "Upgrades the Terraform infrastructure component modules, providers and lock file."
37
- task :upgrade => :binary do |t, args|
37
+ task :upgrade => [:binary] do |t, args|
38
38
  runner = TerraformWrapper::Shared::Runner.new(binary: @binary, code: @code)
39
39
 
40
40
  logger.info("Upgrading Terraform component: #{@code.name}...")
41
41
 
42
- runner.download(upgrade: true)
42
+ runner.upgrade
43
43
  end
44
44
  end
45
45
 
@@ -4,7 +4,7 @@ module TerraformWrapper
4
4
 
5
5
  ###############################################################################
6
6
 
7
- VERSION = "1.0.1"
7
+ VERSION = "1.2.3"
8
8
 
9
9
  ###############################################################################
10
10
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: terraform-wrapper
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.1
4
+ version: 1.2.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Richard Lees
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-03-08 00:00:00.000000000 Z
11
+ date: 2021-06-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -71,6 +71,7 @@ files:
71
71
  - lib/terraform-wrapper/shared/latest.rb
72
72
  - lib/terraform-wrapper/shared/logger.rb
73
73
  - lib/terraform-wrapper/shared/logging.rb
74
+ - lib/terraform-wrapper/shared/provider.rb
74
75
  - lib/terraform-wrapper/shared/runner.rb
75
76
  - lib/terraform-wrapper/shared/variables.rb
76
77
  - lib/terraform-wrapper/tasks.rb
@@ -107,7 +108,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
107
108
  - !ruby/object:Gem::Version
108
109
  version: '0'
109
110
  requirements: []
110
- rubygems_version: 3.2.3
111
+ rubygems_version: 3.2.15
111
112
  signing_key:
112
113
  specification_version: 4
113
114
  summary: A ruby wrapper for managing Terraform binaries and remote state.