tfwrapper 0.2.0.beta1

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,851 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+ require 'tfwrapper/raketasks'
5
+ require 'tfwrapper/helpers'
6
+ require 'tfwrapper/version'
7
+ require 'json'
8
+ require 'retries'
9
+ require 'diplomat'
10
+ require 'rubygems'
11
+
12
+ describe TFWrapper::RakeTasks do
13
+ subject do
14
+ allow(Rake.application).to receive(:rakefile).and_return('Rakefile')
15
+ allow(File).to receive(:realpath) { |p| p }
16
+ subj = TFWrapper::RakeTasks.new('tfdir')
17
+ subj.instance_variable_set('@tf_dir', 'tfdir')
18
+ subj
19
+ end
20
+ describe '#install_tasks' do
21
+ it 'calls constructor without opts if none are passed' do
22
+ dbl = double(TFWrapper::RakeTasks)
23
+ allow(TFWrapper::RakeTasks)
24
+ .to receive(:new).and_return(dbl)
25
+ allow(dbl).to receive(:install)
26
+
27
+ expect(TFWrapper::RakeTasks).to receive(:new).once
28
+ .with('tfdir', {})
29
+ expect(dbl).to receive(:install).once
30
+ TFWrapper::RakeTasks.install_tasks('tfdir')
31
+ end
32
+ it 'passes opts to constructor' do
33
+ dbl = double(TFWrapper::RakeTasks)
34
+ allow(TFWrapper::RakeTasks)
35
+ .to receive(:new).and_return(dbl)
36
+ allow(dbl).to receive(:install)
37
+
38
+ expect(TFWrapper::RakeTasks).to receive(:new).once
39
+ .with('tfdir', tf_vars_from_env: { 'foo' => 'bar' })
40
+ expect(dbl).to receive(:install).once
41
+ TFWrapper::RakeTasks.install_tasks(
42
+ 'tfdir', tf_vars_from_env: { 'foo' => 'bar' }
43
+ )
44
+ end
45
+ end
46
+ describe '#initialize' do
47
+ it 'sets instance variable defaults' do
48
+ allow(ENV).to receive(:[])
49
+ allow(ENV).to receive(:[]).with('CONSUL_HOST').and_return('chost')
50
+ allow(ENV).to receive(:[]).with('ENVIRONMENT').and_return('myenv')
51
+ allow(ENV).to receive(:[]).with('PROJECT').and_return('myproj')
52
+ allow(Rake.application).to receive(:rakefile)
53
+ .and_return('/rake/dir/Rakefile')
54
+ allow(File).to receive(:realpath) { |p| p.sub('../', '') }
55
+ allow(File).to receive(:file?).and_return(true)
56
+ cls = TFWrapper::RakeTasks.new('tfdir')
57
+ expect(cls.instance_variable_get('@tf_dir')).to eq('/rake/dir/tfdir')
58
+ expect(cls.instance_variable_get('@consul_env_vars_prefix')).to eq(nil)
59
+ expect(cls.instance_variable_get('@tf_vars_from_env')).to eq({})
60
+ expect(cls.instance_variable_get('@tf_extra_vars')).to eq({})
61
+ expect(cls.instance_variable_get('@backend_config')).to eq({})
62
+ expect(cls.instance_variable_get('@consul_url')).to eq(nil)
63
+ end
64
+ it 'sets options' do
65
+ allow(ENV).to receive(:[])
66
+ allow(ENV).to receive(:[]).with('CONSUL_HOST').and_return('chost')
67
+ allow(Rake.application).to receive(:rakefile)
68
+ .and_return('/path/to')
69
+ allow(File).to receive(:realpath) { |p| p.sub('../', '') }
70
+ cls = TFWrapper::RakeTasks.new(
71
+ 'tf/dir',
72
+ consul_env_vars_prefix: 'cvprefix',
73
+ tf_vars_from_env: { 'foo' => 'bar' },
74
+ tf_extra_vars: { 'baz' => 'blam' },
75
+ consul_url: 'foobar'
76
+ )
77
+ expect(cls.instance_variable_get('@tf_dir'))
78
+ .to eq('/path/to/tf/dir')
79
+ expect(cls.instance_variable_get('@consul_env_vars_prefix'))
80
+ .to eq('cvprefix')
81
+ expect(cls.instance_variable_get('@tf_vars_from_env'))
82
+ .to eq('foo' => 'bar')
83
+ expect(cls.instance_variable_get('@tf_extra_vars'))
84
+ .to eq('baz' => 'blam')
85
+ expect(cls.instance_variable_get('@backend_config')).to eq({})
86
+ expect(cls.instance_variable_get('@consul_url')).to eq('foobar')
87
+ end
88
+ context 'when consul_url is nil but consul_env_vars_prefix is not' do
89
+ it 'raises an error' do
90
+ allow(Rake.application).to receive(:original_dir)
91
+ .and_return('/rake/dir')
92
+ allow(Rake.application).to receive(:rakefile).and_return('Rakefile')
93
+ allow(File).to receive(:realpath) { |p| p }
94
+ expect do
95
+ TFWrapper::RakeTasks.new(
96
+ 'tfdir',
97
+ consul_env_vars_prefix: 'cvprefix',
98
+ tf_vars_from_env: { 'foo' => 'bar' }
99
+ )
100
+ end.to raise_error(
101
+ StandardError,
102
+ 'Cannot set env vars in Consul when consul_url option is nil.'
103
+ )
104
+ end
105
+ end
106
+ end
107
+ describe '#nsprefix' do
108
+ context 'return value' do
109
+ it 'returns default if namespace_prefix nil' do
110
+ subject.instance_variable_set('@ns_prefix', nil)
111
+ expect(subject.nsprefix).to eq(:tf)
112
+ end
113
+ it 'prefixes namespace if namespace_prefix is not nill' do
114
+ subject.instance_variable_set('@ns_prefix', 'foo')
115
+ expect(subject.nsprefix).to eq(:foo_tf)
116
+ end
117
+ end
118
+ context 'jobs' do
119
+ let(:tasknames) do
120
+ %w[
121
+ init
122
+ plan
123
+ apply
124
+ refresh
125
+ destroy
126
+ write_tf_vars
127
+ ]
128
+ end
129
+ describe 'when namespace_prefix nil' do
130
+ # these let/before/after come from bundler's gem_helper_spec.rb
131
+ let!(:rake_application) { Rake.application }
132
+ before(:each) do
133
+ Rake::Task.clear
134
+ Rake.application = Rake::Application.new
135
+ end
136
+ after(:each) do
137
+ Rake.application = rake_application
138
+ end
139
+ before do
140
+ subject.install
141
+ end
142
+
143
+ it 'sets the default namespace' do
144
+ tasknames.each do |tname|
145
+ expect(Rake.application["tf:#{tname}"])
146
+ .to be_instance_of(Rake::Task)
147
+ end
148
+ end
149
+ it 'includes the correct namespace in all dependencies' do
150
+ tasknames.each do |tname|
151
+ Rake.application["tf:#{tname}"].prerequisites.each do |prer|
152
+ expect(prer.to_s).to start_with('tf:')
153
+ end
154
+ end
155
+ end
156
+ end
157
+ describe 'when namespace_prefix set' do
158
+ # these let/before/after come from bundler's gem_helper_spec.rb
159
+ let!(:rake_application) { Rake.application }
160
+ before(:each) do
161
+ Rake::Task.clear
162
+ Rake.application = Rake::Application.new
163
+ end
164
+ after(:each) do
165
+ Rake.application = rake_application
166
+ end
167
+ before do
168
+ subject.instance_variable_set('@ns_prefix', 'foo')
169
+ subject.install
170
+ end
171
+
172
+ it 'sets the default namespace' do
173
+ tasknames.each do |tname|
174
+ expect(Rake.application["foo_tf:#{tname}"])
175
+ .to be_instance_of(Rake::Task)
176
+ end
177
+ end
178
+ it 'includes the correct namespace in all dependencies' do
179
+ tasknames.each do |tname|
180
+ Rake.application["foo_tf:#{tname}"].prerequisites.each do |prer|
181
+ expect(prer.to_s).to start_with('foo_tf:')
182
+ end
183
+ end
184
+ end
185
+ end
186
+ end
187
+ end
188
+ describe '#var_file_path' do
189
+ context 'return value' do
190
+ it 'returns default if namespace_prefix nil' do
191
+ allow(File).to receive(:absolute_path)
192
+ .and_return('/foo/build.tfvars.json')
193
+ expect(File).to receive(:absolute_path).once.with('build.tfvars.json')
194
+ subject.instance_variable_set('@ns_prefix', nil)
195
+ expect(subject.var_file_path).to eq('/foo/build.tfvars.json')
196
+ end
197
+ it 'prefixes if namespace_prefix is not nill' do
198
+ allow(File).to receive(:absolute_path)
199
+ .and_return('/foo/foo_build.tfvars.json')
200
+ subject.instance_variable_set('@ns_prefix', 'foo')
201
+ expect(File).to receive(:absolute_path).once
202
+ .with('foo_build.tfvars.json')
203
+ expect(subject.var_file_path).to eq('/foo/foo_build.tfvars.json')
204
+ end
205
+ end
206
+ end
207
+ describe '#install' do
208
+ it 'calls the install methods' do
209
+ allow(subject).to receive(:install_init)
210
+ allow(subject).to receive(:install_plan)
211
+ allow(subject).to receive(:install_apply)
212
+ allow(subject).to receive(:install_refresh)
213
+ allow(subject).to receive(:install_destroy)
214
+ allow(subject).to receive(:install_write_tf_vars)
215
+ expect(subject).to receive(:install_init).once
216
+ expect(subject).to receive(:install_plan).once
217
+ expect(subject).to receive(:install_apply).once
218
+ expect(subject).to receive(:install_refresh).once
219
+ expect(subject).to receive(:install_destroy).once
220
+ expect(subject).to receive(:install_write_tf_vars).once
221
+ subject.install
222
+ end
223
+ end
224
+ describe '#install_init' do
225
+ # these let/before/after come from bundler's gem_helper_spec.rb
226
+ let!(:rake_application) { Rake.application }
227
+ before(:each) do
228
+ Rake::Task.clear
229
+ Rake.application = Rake::Application.new
230
+ end
231
+ after(:each) do
232
+ Rake.application = rake_application
233
+ end
234
+ before do
235
+ subject.install_init
236
+ end
237
+
238
+ it 'adds the init task' do
239
+ expect(Rake.application['tf:init']).to be_instance_of(Rake::Task)
240
+ end
241
+ it 'runs the init command with backend_config options' do
242
+ Rake.application['tf:init'].clear_prerequisites
243
+ vars = { foo: 'bar', baz: 'blam' }
244
+ subject.instance_variable_set('@tf_vars_from_env', vars)
245
+ allow(TFWrapper::Helpers).to receive(:check_env_vars)
246
+ allow(ENV).to receive(:[])
247
+ subject.instance_variable_set(
248
+ '@backend_config',
249
+ 'address' => 'chost',
250
+ 'path' => 'consulprefix',
251
+ 'foo' => 'bar'
252
+ )
253
+ allow(subject).to receive(:terraform_runner)
254
+ allow(subject).to receive(:check_tf_version)
255
+ expect(TFWrapper::Helpers)
256
+ .to receive(:check_env_vars).once.ordered.with(vars.values)
257
+ expect(subject).to receive(:check_tf_version).once.ordered
258
+ expect(subject).to receive(:terraform_runner).once.ordered
259
+ .with('terraform init -input=false '\
260
+ '-backend-config=\'address=chost\'' \
261
+ ' -backend-config=\'path=consulprefix\''\
262
+ ' -backend-config=\'foo=bar\'')
263
+ Rake.application['tf:init'].invoke
264
+ end
265
+ it 'runs the init command without backend_config options' do
266
+ Rake.application['tf:init'].clear_prerequisites
267
+ vars = { foo: 'bar', baz: 'blam' }
268
+ subject.instance_variable_set('@tf_vars_from_env', vars)
269
+ allow(TFWrapper::Helpers).to receive(:check_env_vars)
270
+ allow(ENV).to receive(:[])
271
+ subject.instance_variable_set('@backend_config', {})
272
+ allow(subject).to receive(:terraform_runner)
273
+ allow(subject).to receive(:check_tf_version)
274
+ expect(TFWrapper::Helpers)
275
+ .to receive(:check_env_vars).once.ordered.with(vars.values)
276
+ expect(subject).to receive(:check_tf_version).once.ordered
277
+ expect(subject).to receive(:terraform_runner).once.ordered
278
+ .with('terraform init -input=false')
279
+ Rake.application['tf:init'].invoke
280
+ end
281
+ end
282
+ describe '#install_plan' do
283
+ # these let/before/after come from bundler's gem_helper_spec.rb
284
+ let!(:rake_application) { Rake.application }
285
+ before(:each) do
286
+ Rake::Task.clear
287
+ Rake.application = Rake::Application.new
288
+ end
289
+ after(:each) do
290
+ Rake.application = rake_application
291
+ end
292
+ before do
293
+ subject.install_plan
294
+ end
295
+
296
+ it 'adds the plan task' do
297
+ expect(Rake.application['tf:plan']).to be_instance_of(Rake::Task)
298
+ expect(Rake.application['tf:plan'].prerequisites)
299
+ .to eq(%w[tf:init tf:write_tf_vars])
300
+ expect(Rake.application['tf:plan'].arg_names).to eq([:target])
301
+ end
302
+ it 'runs the plan command with no targets' do
303
+ Rake.application['tf:plan'].clear_prerequisites
304
+ allow(subject).to receive(:var_file_path).and_return('file.tfvars.json')
305
+ allow(subject).to receive(:terraform_runner)
306
+ expect(subject).to receive(:terraform_runner).once
307
+ .with('terraform plan -var-file file.tfvars.json')
308
+ Rake.application['tf:plan'].invoke
309
+ end
310
+ it 'runs the plan command with one target' do
311
+ Rake.application['tf:plan'].clear_prerequisites
312
+ allow(subject).to receive(:var_file_path).and_return('file.tfvars.json')
313
+ allow(subject).to receive(:terraform_runner)
314
+ expect(subject).to receive(:terraform_runner).once
315
+ .with('terraform plan -var-file file.tfvars.json ' \
316
+ '-target tar.get[1]')
317
+ Rake.application['tf:plan'].invoke('tar.get[1]')
318
+ end
319
+ it 'runs the plan command with three targets' do
320
+ Rake.application['tf:plan'].clear_prerequisites
321
+ allow(subject).to receive(:var_file_path).and_return('file.tfvars.json')
322
+ allow(subject).to receive(:terraform_runner)
323
+ expect(subject).to receive(:terraform_runner).once
324
+ .with('terraform plan -var-file file.tfvars.json ' \
325
+ '-target tar.get[1] -target t.gt[2] -target my.target[3]')
326
+ Rake.application['tf:plan'].invoke(
327
+ 'tar.get[1]', 't.gt[2]', 'my.target[3]'
328
+ )
329
+ end
330
+ end
331
+ describe '#install_apply' do
332
+ # these let/before/after come from bundler's gem_helper_spec.rb
333
+ let!(:rake_application) { Rake.application }
334
+ before(:each) do
335
+ Rake::Task.clear
336
+ Rake.application = Rake::Application.new
337
+ end
338
+ after(:each) do
339
+ Rake.application = rake_application
340
+ end
341
+ before do
342
+ subject.install_apply
343
+ end
344
+
345
+ it 'adds the apply task' do
346
+ expect(Rake.application['tf:apply']).to be_instance_of(Rake::Task)
347
+ expect(Rake.application['tf:apply'].prerequisites)
348
+ .to eq(%w[tf:init tf:write_tf_vars tf:plan])
349
+ expect(Rake.application['tf:apply'].arg_names).to eq([:target])
350
+ end
351
+ it 'runs the apply command with no targets' do
352
+ Rake.application['tf:apply'].clear_prerequisites
353
+ allow(subject).to receive(:var_file_path).and_return('file.tfvars.json')
354
+ allow(subject).to receive(:terraform_runner)
355
+ allow(subject).to receive(:update_consul_stack_env_vars)
356
+ expect(subject).to receive(:terraform_runner).once
357
+ .with('terraform apply -var-file file.tfvars.json')
358
+ expect(subject).to_not receive(:update_consul_stack_env_vars)
359
+ Rake.application['tf:apply'].invoke
360
+ end
361
+ it 'runs the apply command with one target' do
362
+ Rake.application['tf:apply'].clear_prerequisites
363
+ allow(subject).to receive(:var_file_path).and_return('file.tfvars.json')
364
+ allow(subject).to receive(:terraform_runner)
365
+ expect(subject).to receive(:terraform_runner).once
366
+ .with('terraform apply -var-file file.tfvars.json ' \
367
+ '-target tar.get[1]')
368
+ Rake.application['tf:apply'].invoke('tar.get[1]')
369
+ end
370
+ it 'runs the apply command with three targets' do
371
+ Rake.application['tf:apply'].clear_prerequisites
372
+ allow(subject).to receive(:var_file_path).and_return('file.tfvars.json')
373
+ allow(subject).to receive(:terraform_runner)
374
+ expect(subject).to receive(:terraform_runner).once
375
+ .with('terraform apply -var-file file.tfvars.json ' \
376
+ '-target tar.get[1] -target t.gt[2] -target my.target[3]')
377
+ Rake.application['tf:apply'].invoke(
378
+ 'tar.get[1]', 't.gt[2]', 'my.target[3]'
379
+ )
380
+ end
381
+ it 'runs update_consul_stack_env_vars if consul_env_vars_prefix not nil' do
382
+ subject.instance_variable_set('@consul_env_vars_prefix', 'foo')
383
+ Rake.application['tf:apply'].clear_prerequisites
384
+ allow(subject).to receive(:var_file_path).and_return('file.tfvars.json')
385
+ allow(subject).to receive(:terraform_runner)
386
+ allow(subject).to receive(:update_consul_stack_env_vars)
387
+ expect(subject).to receive(:terraform_runner).once
388
+ .with('terraform apply -var-file file.tfvars.json')
389
+ expect(subject).to receive(:update_consul_stack_env_vars).once
390
+ Rake.application['tf:apply'].invoke
391
+ end
392
+ end
393
+ describe '#install_refresh' do
394
+ # these let/before/after come from bundler's gem_helper_spec.rb
395
+ let!(:rake_application) { Rake.application }
396
+ before(:each) do
397
+ Rake::Task.clear
398
+ Rake.application = Rake::Application.new
399
+ end
400
+ after(:each) do
401
+ Rake.application = rake_application
402
+ end
403
+ before do
404
+ subject.install_refresh
405
+ end
406
+
407
+ it 'adds the refresh task' do
408
+ expect(Rake.application['tf:refresh']).to be_instance_of(Rake::Task)
409
+ expect(Rake.application['tf:refresh'].prerequisites)
410
+ .to eq(%w[tf:init tf:write_tf_vars])
411
+ end
412
+ it 'runs the refresh command' do
413
+ Rake.application['tf:refresh'].clear_prerequisites
414
+ allow(subject).to receive(:var_file_path).and_return('file.tfvars.json')
415
+ allow(subject).to receive(:terraform_runner)
416
+ expect(subject).to receive(:terraform_runner).once
417
+ .with('terraform refresh -var-file file.tfvars.json')
418
+ Rake.application['tf:refresh'].invoke
419
+ end
420
+ end
421
+ describe '#install_destroy' do
422
+ # these let/before/after come from bundler's gem_helper_spec.rb
423
+ let!(:rake_application) { Rake.application }
424
+ before(:each) do
425
+ Rake::Task.clear
426
+ Rake.application = Rake::Application.new
427
+ end
428
+ after(:each) do
429
+ Rake.application = rake_application
430
+ end
431
+ before do
432
+ subject.install_destroy
433
+ end
434
+
435
+ it 'adds the destroy task' do
436
+ expect(Rake.application['tf:destroy']).to be_instance_of(Rake::Task)
437
+ expect(Rake.application['tf:destroy'].prerequisites)
438
+ .to eq(%w[tf:init tf:write_tf_vars])
439
+ expect(Rake.application['tf:destroy'].arg_names).to eq([:target])
440
+ end
441
+ it 'runs the destroy command with no targets' do
442
+ Rake.application['tf:destroy'].clear_prerequisites
443
+ allow(subject).to receive(:var_file_path).and_return('file.tfvars.json')
444
+ allow(subject).to receive(:terraform_runner)
445
+ expect(subject).to receive(:terraform_runner).once
446
+ .with('terraform destroy -force -var-file file.tfvars.json')
447
+ Rake.application['tf:destroy'].invoke
448
+ end
449
+ it 'runs the destroy command with one target' do
450
+ Rake.application['tf:destroy'].clear_prerequisites
451
+ allow(subject).to receive(:var_file_path).and_return('file.tfvars.json')
452
+ allow(subject).to receive(:terraform_runner)
453
+ expect(subject).to receive(:terraform_runner).once
454
+ .with('terraform destroy -force -var-file file.tfvars.json ' \
455
+ '-target tar.get[1]')
456
+ Rake.application['tf:destroy'].invoke('tar.get[1]')
457
+ end
458
+ it 'runs the destroy command with three targets' do
459
+ Rake.application['tf:destroy'].clear_prerequisites
460
+ allow(subject).to receive(:var_file_path).and_return('file.tfvars.json')
461
+ allow(subject).to receive(:terraform_runner)
462
+ expect(subject).to receive(:terraform_runner).once
463
+ .with('terraform destroy -force -var-file file.tfvars.json ' \
464
+ '-target tar.get[1] -target t.gt[2] -target my.target[3]')
465
+ Rake.application['tf:destroy'].invoke(
466
+ 'tar.get[1]', 't.gt[2]', 'my.target[3]'
467
+ )
468
+ end
469
+ end
470
+ describe '#install_write_tf_vars' do
471
+ # these let/before/after come from bundler's gem_helper_spec.rb
472
+ context 'when ns_prefix is nil' do
473
+ let!(:rake_application) { Rake.application }
474
+ before(:each) do
475
+ Rake::Task.clear
476
+ Rake.application = Rake::Application.new
477
+ end
478
+ after(:each) do
479
+ Rake.application = rake_application
480
+ end
481
+ before do
482
+ subject.install_write_tf_vars
483
+ end
484
+
485
+ it 'adds the write_tf_vars task' do
486
+ expect(Rake.application['tf:write_tf_vars'])
487
+ .to be_instance_of(Rake::Task)
488
+ expect(Rake.application['tf:write_tf_vars'].prerequisites).to eq([])
489
+ end
490
+ it 'runs the write_tf_vars command' do
491
+ vars = {
492
+ 'foo' => 'bar',
493
+ 'baz' => 'blam',
494
+ 'aws_access_key' => 'ak'
495
+ }
496
+ allow(subject).to receive(:terraform_vars).and_return(vars)
497
+ allow(subject).to receive(:var_file_path).and_return('file.tfvars.json')
498
+ f_dbl = double(File)
499
+ allow(File).to receive(:open).and_yield(f_dbl)
500
+ allow(f_dbl).to receive(:write)
501
+
502
+ expect(subject).to receive(:terraform_vars).once
503
+ expect(STDOUT).to receive(:puts).once.with('Terraform vars:')
504
+ expect(STDOUT).to receive(:puts)
505
+ .once.with('aws_access_key => (redacted)')
506
+ expect(STDOUT).to receive(:puts).once.with('baz => blam')
507
+ expect(STDOUT).to receive(:puts).once.with('foo => bar')
508
+ expect(File).to receive(:open).once.with('file.tfvars.json', 'w')
509
+ expect(f_dbl).to receive(:write).once.with(vars.to_json)
510
+ expect(STDERR).to receive(:puts)
511
+ .once.with('Terraform vars written to: file.tfvars.json')
512
+ Rake.application['tf:write_tf_vars'].invoke
513
+ end
514
+ end
515
+ context 'when ns_prefix is specified' do
516
+ let!(:rake_application) { Rake.application }
517
+ before(:each) do
518
+ Rake::Task.clear
519
+ Rake.application = Rake::Application.new
520
+ end
521
+ after(:each) do
522
+ Rake.application = rake_application
523
+ end
524
+ before do
525
+ subject.instance_variable_set('@ns_prefix', 'foo')
526
+ subject.install_write_tf_vars
527
+ end
528
+
529
+ it 'adds the write_tf_vars task' do
530
+ expect(Rake.application['foo_tf:write_tf_vars'])
531
+ .to be_instance_of(Rake::Task)
532
+ expect(Rake.application['foo_tf:write_tf_vars'].prerequisites).to eq([])
533
+ end
534
+ it 'runs the write_tf_vars command' do
535
+ vars = {
536
+ 'foo' => 'bar',
537
+ 'baz' => 'blam',
538
+ 'aws_access_key' => 'ak'
539
+ }
540
+ allow(subject).to receive(:terraform_vars).and_return(vars)
541
+ allow(subject).to receive(:var_file_path)
542
+ .and_return('foo_file.tfvars.json')
543
+ f_dbl = double(File)
544
+ allow(File).to receive(:open).and_yield(f_dbl)
545
+ allow(f_dbl).to receive(:write)
546
+
547
+ expect(subject).to receive(:terraform_vars).once
548
+ expect(STDOUT).to receive(:puts).once.with('Terraform vars:')
549
+ expect(STDOUT).to receive(:puts)
550
+ .once.with('aws_access_key => (redacted)')
551
+ expect(STDOUT).to receive(:puts).once.with('baz => blam')
552
+ expect(STDOUT).to receive(:puts).once.with('foo => bar')
553
+ expect(File).to receive(:open).once.with('foo_file.tfvars.json', 'w')
554
+ expect(f_dbl).to receive(:write).once.with(vars.to_json)
555
+ expect(STDERR).to receive(:puts)
556
+ .once.with('Terraform vars written to: foo_file.tfvars.json')
557
+ Rake.application['foo_tf:write_tf_vars'].invoke
558
+ end
559
+ end
560
+ end
561
+ describe '#terraform_vars' do
562
+ it 'builds a hash and sets overrides' do
563
+ v_from_e = { 'vfe1' => 'vfe1name', 'vfe2' => 'vfe2name' }
564
+ extra_v = { 'ev1' => 'ev1val', 'vfe2' => 'ev_vfe2val' }
565
+ subject.instance_variable_set('@tf_vars_from_env', v_from_e)
566
+ subject.instance_variable_set('@tf_extra_vars', extra_v)
567
+ allow(ENV).to receive(:[])
568
+ allow(ENV).to receive(:[]).with('vfe1name').and_return('env_vfe1val')
569
+ allow(ENV).to receive(:[]).with('vfe2name').and_return('env_vfe2val')
570
+ expect(subject.terraform_vars).to eq(
571
+ 'vfe1' => 'env_vfe1val',
572
+ 'vfe2' => 'ev_vfe2val',
573
+ 'ev1' => 'ev1val'
574
+ )
575
+ end
576
+ end
577
+ describe '#terraform_runner' do
578
+ before do
579
+ Retries.sleep_enabled = false
580
+ end
581
+ it 'outputs nothing and succeeds when command succeeds' do
582
+ allow(TFWrapper::Helpers).to receive(:run_cmd_stream_output)
583
+ .with(any_args).and_return(['', 0])
584
+ expect(TFWrapper::Helpers).to receive(:run_cmd_stream_output)
585
+ .once.with('foo', 'tfdir')
586
+ expect(STDERR).to receive(:puts).once
587
+ .with("terraform_runner command: 'foo' (in tfdir)")
588
+ expect(STDERR).to receive(:puts).once
589
+ .with("terraform_runner command 'foo' finished and exited 0")
590
+ subject.terraform_runner('foo')
591
+ end
592
+ it 'retries if needed' do
593
+ @times_called = 0
594
+ allow(TFWrapper::Helpers).to receive(:run_cmd_stream_output) do
595
+ @times_called += 1
596
+ raise StandardError if @times_called == 1
597
+ ['', 0]
598
+ end
599
+
600
+ expect(TFWrapper::Helpers).to receive(:run_cmd_stream_output)
601
+ .exactly(2).times.with('foo', 'tfdir')
602
+ expect(STDERR).to receive(:puts).once
603
+ .with("terraform_runner command: 'foo' (in tfdir)")
604
+ expect(STDERR).to receive(:puts).once
605
+ .with(/terraform_runner\sfailed\swith\sStandardError;\sretry\s
606
+ attempt\s1;\s.+\sseconds\shave\spassed\./x)
607
+ expect(STDERR).to receive(:puts).once
608
+ .with("terraform_runner command 'foo' finished and exited 0")
609
+ subject.terraform_runner('foo')
610
+ end
611
+ it 'retries if throttling' do
612
+ @times_called = 0
613
+ allow(TFWrapper::Helpers).to receive(:run_cmd_stream_output) do
614
+ @times_called += 1
615
+ if @times_called < 3
616
+ ['foo Throttling bar', 2]
617
+ else
618
+ ['', 0]
619
+ end
620
+ end
621
+
622
+ expect(TFWrapper::Helpers).to receive(:run_cmd_stream_output)
623
+ .exactly(3).times.with('foo', 'tfdir')
624
+ expect(STDERR).to receive(:puts).once
625
+ .with("terraform_runner command: 'foo' (in tfdir)")
626
+ expect(STDERR).to receive(:puts).once
627
+ .with(/terraform_runner\sfailed\swith\sTerraform\shit\sAWS\sAPI\srate\s
628
+ limiting;\sretry\sattempt\s1;\s.+\sseconds\shave\spassed\./x)
629
+ expect(STDERR).to receive(:puts).once
630
+ .with(/terraform_runner\sfailed\swith\sTerraform\shit\sAWS\sAPI\srate\s
631
+ limiting;\sretry\sattempt\s2;\s.+\sseconds\shave\spassed\./x)
632
+ expect(STDERR).to receive(:puts).once
633
+ .with("terraform_runner command 'foo' finished and exited 0")
634
+ subject.terraform_runner('foo')
635
+ end
636
+ it 'retries if status code 403' do
637
+ @times_called = 0
638
+ allow(TFWrapper::Helpers).to receive(:run_cmd_stream_output) do
639
+ @times_called += 1
640
+ if @times_called < 3
641
+ ['foo status code: 403 bar', 2]
642
+ else
643
+ ['', 0]
644
+ end
645
+ end
646
+
647
+ expect(TFWrapper::Helpers).to receive(:run_cmd_stream_output)
648
+ .exactly(3).times.with('foo', 'tfdir')
649
+ expect(STDERR).to receive(:puts).once
650
+ .with("terraform_runner command: 'foo' (in tfdir)")
651
+ expect(STDERR).to receive(:puts).once
652
+ .with(/terraform_runner\sfailed\swith\sTerraform\scommand\sgot\s403\s
653
+ error\s-\saccess\sdenied\sor\scredentials\snot\spropagated;\sretry\s
654
+ attempt\s1;\s.+\sseconds\shave\spassed\./x)
655
+ expect(STDERR).to receive(:puts).once
656
+ .with(/terraform_runner\sfailed\swith\sTerraform\scommand\sgot\s403\s
657
+ error\s-\saccess\sdenied\sor\scredentials\snot\spropagated;\sretry\s
658
+ attempt\s2;\s.+\sseconds\shave\spassed\./x)
659
+ expect(STDERR).to receive(:puts).once
660
+ .with("terraform_runner command 'foo' finished and exited 0")
661
+ subject.terraform_runner('foo')
662
+ end
663
+ it 'retries if status code 401' do
664
+ @times_called = 0
665
+ allow(TFWrapper::Helpers).to receive(:run_cmd_stream_output) do
666
+ @times_called += 1
667
+ if @times_called < 3
668
+ ['foo status code: 401 bar', 2]
669
+ else
670
+ ['', 0]
671
+ end
672
+ end
673
+
674
+ expect(TFWrapper::Helpers).to receive(:run_cmd_stream_output)
675
+ .exactly(3).times.with('foo', 'tfdir')
676
+ expect(STDERR).to receive(:puts).once
677
+ .with("terraform_runner command: 'foo' (in tfdir)")
678
+ expect(STDERR).to receive(:puts).once
679
+ .with(/terraform_runner\sfailed\swith\sTerraform\scommand\sgot\s401\s
680
+ error\s-\saccess\sdenied\sor\scredentials\snot\spropagated;\sretry\s
681
+ attempt\s1;\s.+\sseconds\shave\spassed\./x)
682
+ expect(STDERR).to receive(:puts).once
683
+ .with(/terraform_runner\sfailed\swith\sTerraform\scommand\sgot\s401\s
684
+ error\s-\saccess\sdenied\sor\scredentials\snot\spropagated;\sretry\s
685
+ attempt\s2;\s.+\sseconds\shave\spassed\./x)
686
+ expect(STDERR).to receive(:puts).once
687
+ .with("terraform_runner command 'foo' finished and exited 0")
688
+ subject.terraform_runner('foo')
689
+ end
690
+ it 'raises an error if the command exits non-zero' do
691
+ allow(TFWrapper::Helpers).to receive(:run_cmd_stream_output)
692
+ .and_return(['', 1])
693
+ expect(TFWrapper::Helpers).to receive(:run_cmd_stream_output).once
694
+ .with('foo', 'tfdir')
695
+ expect(STDERR).to receive(:puts).once
696
+ .with('terraform_runner command: \'foo\' (in tfdir)')
697
+ expect { subject.terraform_runner('foo') }
698
+ .to raise_error('Errors have occurred executing: \'foo\' (exited 1)')
699
+ end
700
+ end
701
+ describe '#check_tf_version' do
702
+ it 'fails if the command exits non-zero' do
703
+ allow(TFWrapper::Helpers).to receive(:run_cmd_stream_output)
704
+ .and_return(['myout', 2])
705
+ allow(STDOUT).to receive(:puts)
706
+ expect(TFWrapper::Helpers).to receive(:run_cmd_stream_output).once
707
+ .with('terraform version', 'tfdir')
708
+ expect(STDOUT).to_not receive(:puts)
709
+ expect { subject.check_tf_version }
710
+ .to raise_error(
711
+ StandardError,
712
+ 'ERROR: \'terraform -version\' exited 2: myout'
713
+ )
714
+ end
715
+ it 'strips build information from the version' do
716
+ ver = Gem::Version.new('3.4.5')
717
+ allow(TFWrapper::Helpers).to receive(:run_cmd_stream_output)
718
+ .and_return(['Terraform v3.4.5-dev (abcde1234+CHANGES)', 0])
719
+ allow(Gem::Version).to receive(:new).and_return(ver)
720
+ expect(Gem::Version).to receive(:new).once.with('3.4.5')
721
+ subject.check_tf_version
722
+ end
723
+ it 'fails if the version cannot be identified' do
724
+ allow(TFWrapper::Helpers).to receive(:run_cmd_stream_output)
725
+ .and_return(['myout', 0])
726
+ allow(STDOUT).to receive(:puts)
727
+ expect(TFWrapper::Helpers).to receive(:run_cmd_stream_output).once
728
+ .with('terraform version', 'tfdir')
729
+ expect(STDOUT).to_not receive(:puts)
730
+ expect { subject.check_tf_version }
731
+ .to raise_error(
732
+ StandardError,
733
+ 'ERROR: could not determine terraform version from \'terraform ' \
734
+ '-version\' output: myout'
735
+ )
736
+ end
737
+ it 'fails if the version is too old' do
738
+ allow(TFWrapper::Helpers).to receive(:run_cmd_stream_output)
739
+ .and_return(['Terraform v0.0.1-dev (foo)', 0])
740
+ allow(STDOUT).to receive(:puts)
741
+ expect(TFWrapper::Helpers).to receive(:run_cmd_stream_output).once
742
+ .with('terraform version', 'tfdir')
743
+ expect(STDOUT).to_not receive(:puts)
744
+ expect { subject.check_tf_version }
745
+ .to raise_error(
746
+ StandardError,
747
+ "ERROR: tfwrapper #{TFWrapper::VERSION} is only compatible with "\
748
+ "Terraform >= #{subject.min_tf_version} but your terraform "\
749
+ 'binary reports itself as 0.0.1 (Terraform v0.0.1-dev (foo))'
750
+ )
751
+ end
752
+ it 'prints the version if compatible' do
753
+ allow(TFWrapper::Helpers).to receive(:run_cmd_stream_output)
754
+ .and_return(['Terraform v999.9.9', 0])
755
+ allow(STDOUT).to receive(:puts)
756
+ expect(TFWrapper::Helpers).to receive(:run_cmd_stream_output).once
757
+ .with('terraform version', 'tfdir')
758
+ expect(STDOUT).to receive(:puts).once
759
+ .with('Running with: Terraform v999.9.9')
760
+ subject.check_tf_version
761
+ end
762
+ end
763
+ describe '#min_tf_version' do
764
+ it 'returns a Gem::Version for the minimum version' do
765
+ expect(subject.min_tf_version).to eq(Gem::Version.new('0.9.0'))
766
+ end
767
+ end
768
+ describe '#update_consul_stack_env_vars' do
769
+ context 'when @consul_url and @consul_env_vars_prefix are specified' do
770
+ it 'saves the variables in Consul' do
771
+ vars = { 'foo' => 'bar', 'baz' => 'blam' }
772
+ expected = { 'bar' => 'barVal', 'blam' => 'blamVal' }
773
+ subject.instance_variable_set('@tf_vars_from_env', vars)
774
+ subject.instance_variable_set('@consul_url', 'foo://bar')
775
+ subject.instance_variable_set('@consul_env_vars_prefix', 'my/prefix')
776
+ dbl_config = double
777
+ allow(dbl_config).to receive(:url=)
778
+ allow(Diplomat).to receive(:configure).and_yield(dbl_config)
779
+ allow(ENV).to receive(:[])
780
+ allow(ENV).to receive(:[]).with('CONSUL_HOST').and_return('chost')
781
+ allow(ENV).to receive(:[]).with('bar').and_return('barVal')
782
+ allow(ENV).to receive(:[]).with('blam').and_return('blamVal')
783
+ allow(Diplomat::Kv).to receive(:put)
784
+
785
+ expect(Diplomat).to receive(:configure).once
786
+ expect(dbl_config).to receive(:url=).once.with('foo://bar')
787
+ expect(STDOUT).to receive(:puts).once
788
+ .with('Writing stack information to foo://bar at: my/prefix')
789
+ expect(STDOUT).to receive(:puts).once
790
+ .with(JSON.pretty_generate(expected))
791
+ expect(Diplomat::Kv).to receive(:put)
792
+ .once.with('my/prefix', JSON.generate(expected))
793
+ subject.update_consul_stack_env_vars
794
+ end
795
+ end
796
+ end
797
+ describe '#cmd_with_targets' do
798
+ it 'creates the command string if no targets specified' do
799
+ expect(
800
+ subject.cmd_with_targets(
801
+ ['terraform', 'plan', '-var-file', 'foo'],
802
+ nil,
803
+ nil
804
+ )
805
+ )
806
+ .to eq('terraform plan -var-file foo')
807
+ end
808
+ it 'creates the command string with no targets and a long suffix' do
809
+ expect(
810
+ subject.cmd_with_targets(
811
+ ['terraform', 'plan', '-var-file', 'foo'],
812
+ nil,
813
+ nil
814
+ )
815
+ )
816
+ .to eq('terraform plan -var-file foo')
817
+ end
818
+ it 'creates the command string if one target specified' do
819
+ expect(
820
+ subject.cmd_with_targets(
821
+ ['terraform', 'plan', '-var-file', 'foo'],
822
+ 'tar.get[1]',
823
+ nil
824
+ )
825
+ )
826
+ .to eq('terraform plan -var-file foo -target tar.get[1]')
827
+ end
828
+ it 'creates the command string if two targets specified' do
829
+ expect(
830
+ subject.cmd_with_targets(
831
+ ['terraform', 'plan', '-var-file', 'foo'],
832
+ 'tar.get[1]',
833
+ ['tar.get[2]']
834
+ )
835
+ )
836
+ .to eq('terraform plan -var-file foo -target tar.get[1] ' \
837
+ '-target tar.get[2]')
838
+ end
839
+ it 'creates the command string if four targets specified' do
840
+ expect(
841
+ subject.cmd_with_targets(
842
+ ['terraform', 'plan', '-var-file', 'foo'],
843
+ 'tar.get[1]',
844
+ ['tar.get[2]', 'my.target[3]']
845
+ )
846
+ )
847
+ .to eq('terraform plan -var-file foo -target tar.get[1] ' \
848
+ '-target tar.get[2] -target my.target[3]')
849
+ end
850
+ end
851
+ end