tfwrapper 0.2.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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