tfwrapper 0.4.1 → 0.6.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 4033a083750714db82cc06f4c948aa3539c1f608
4
- data.tar.gz: bda28f44bd9e007263ca8c4d378e29209a4f1ffa
2
+ SHA256:
3
+ metadata.gz: 4a404a81c05bcc07e29ef34eef1d881aa9c47faa1e5ba0ed127a34b8ae1202c5
4
+ data.tar.gz: f0078c62815fd5d7e49c14d201d9da2154ed91f6c4786a7a7be65914582cd4d8
5
5
  SHA512:
6
- metadata.gz: 7f9c16e12f2f3927003eca182bf31e8ee1c9768e921a8f328424c6c0c9c522874b48c30ca1fd0f16e6d9eea7873310886a8e217083fd0602b4d642e497684eca
7
- data.tar.gz: 8481b9c9e6975710e5525b5d27849a0a94a4aec7dd53749dfe8764b719edc0cbbede1489dccc214d83128b5dbae4c62a12fc9178317e6d6377c8a1419c49cb29
6
+ metadata.gz: db129accb7e439c570ca8a8eb2d56a259acb98221a5acf5e451cc4aa44516627abd6e29603574abbf4dcd16c968b58607552bbec3a1b50ce7df44de9659968a7
7
+ data.tar.gz: f9f99bcc2e51e2aa4cedabd0e1179a1954dbc3e3e4d44149ab2d171218cc6e55a940ef2372869c6cd694c77be0282d5230f7a7d142eaea53479794c78550e810
data/.gitignore CHANGED
@@ -13,6 +13,7 @@
13
13
  /vendor/
14
14
  .terraform
15
15
  build.tfvars.json
16
+ **/*_build.tfvars.json
16
17
  **/terraform.tfstate.backup
17
18
 
18
19
  # Used by dotenv library to load environment variables.
@@ -2,10 +2,10 @@ AllCops:
2
2
  TargetRubyVersion: 2.0
3
3
 
4
4
  Metrics/AbcSize:
5
- Max: 40
5
+ Max: 50
6
6
 
7
7
  Metrics/BlockLength:
8
- Max: 1300
8
+ Max: 1500
9
9
 
10
10
  Metrics/ClassLength:
11
11
  Max: 500
@@ -14,10 +14,10 @@ Metrics/MethodLength:
14
14
  Max: 100
15
15
 
16
16
  Metrics/PerceivedComplexity:
17
- Max: 8
17
+ Max: 10
18
18
 
19
19
  Metrics/CyclomaticComplexity:
20
- Max: 8
20
+ Max: 10
21
21
 
22
22
  # These are to compensate for some warnings that differ by
23
23
  # rubocop/ruby version
@@ -30,7 +30,7 @@ Style/MutableConstant:
30
30
  - 'lib/tfwrapper/version.rb'
31
31
  - 'spec/acceptance/acceptance_spec.rb'
32
32
 
33
- Style/MultilineMethodCallIndentation:
33
+ Layout/MultilineMethodCallIndentation:
34
34
  Exclude:
35
35
  - 'spec/unit/raketasks_spec.rb'
36
36
 
@@ -0,0 +1,62 @@
1
+ ---
2
+ dist: xenial
3
+ language: ruby
4
+ cache: bundler
5
+ before_install:
6
+ - gem uninstall -v '>= 2' -i $(rvm gemdir)@global -ax bundler || true
7
+ - gem install bundler -v '< 2'
8
+ install: true
9
+ matrix:
10
+ include:
11
+ - rvm: ruby
12
+ install: bundle install --without landscape
13
+ script: ["bundle exec ruby --version", "bundle exec rake spec:unit"]
14
+ name: "ruby-latest unit"
15
+ - rvm: ruby
16
+ install: bundle install --without landscape
17
+ script: bundle exec rake rubocop
18
+ name: "ruby-latest rubocop"
19
+ - rvm: ruby
20
+ install: bundle install --without landscape
21
+ script: bundle exec rake yard:generate
22
+ name: "ruby-latest yard"
23
+ - rvm: 2.0
24
+ install: bundle install --without landscape
25
+ script: bundle exec rake spec:unit
26
+ name: "ruby-2.0 unit"
27
+ - rvm: 2.1
28
+ install: bundle install --without landscape
29
+ script: bundle exec rake spec:unit
30
+ name: "ruby-2.1 unit"
31
+ - rvm: 2.2
32
+ install: bundle install --without landscape
33
+ script: bundle exec rake spec:unit
34
+ name: "ruby-2.2 unit"
35
+ - rvm: 2.3
36
+ install: bundle install --without landscape
37
+ script: bundle exec rake spec:unit
38
+ name: "ruby-2.3 unit"
39
+ - rvm: 2.3
40
+ install: bundle install
41
+ script: bundle exec rake spec:acceptance TF_VERSION=0.10.0
42
+ name: "ruby-2.3 acceptance TF 0.10.0"
43
+ - rvm: 2.3
44
+ install: bundle install
45
+ script: bundle exec rake spec:acceptance TF_VERSION=0.10.2
46
+ name: "ruby-2.3 acceptance TF 0.10.2"
47
+ - rvm: 2.3
48
+ install: bundle install
49
+ script: bundle exec rake spec:acceptance TF_VERSION=0.11.2
50
+ name: "ruby-2.3 acceptance TF 0.11.2"
51
+ - rvm: 2.3
52
+ install: bundle install
53
+ script: bundle exec rake spec:acceptance TF_VERSION=0.11.14
54
+ name: "ruby-2.3 acceptance TF 0.11.14"
55
+ - rvm: 2.4
56
+ install: bundle install
57
+ script: bundle exec rake spec:unit
58
+ name: "ruby-2.4 unit"
59
+ - rvm: 2.4
60
+ install: bundle install
61
+ script: bundle exec rake spec:acceptance TF_VERSION=0.11.14
62
+ name: "ruby-2.4 acceptance TF 0.11.14"
@@ -1,3 +1,34 @@
1
+ Version 0.6.2
2
+
3
+ - Add ``tf_sensitive_vars`` option.
4
+
5
+ Version 0.6.1
6
+
7
+ - Add ``allowed_empty_vars`` option.
8
+ - Only support terraform up to 0.11.14.
9
+
10
+ Version 0.6.0
11
+
12
+ - Include full terraform output when a terraform run fails with output including ``hrottling``, ``status code: 403``, or ``status code: 401``. Previously terraform output was suppressed in these cases, which causes confusion with providers other than "aws" that include these strings in their failure output.
13
+ - In ``Gemfile``, for testing, pin terraform-landscape gem to 0.2.2 for Ruby 2.x compatibility.
14
+ - Pin ``rb-inotify`` dependency to 0.9.10 to allow build with Ruby < 2.2
15
+ - Switch testing from circleci.com to travis-ci.org
16
+ - Pin terraform_landscape dependency to 0.2.2 for compatibility with ruby < 2.5
17
+ - Acceptance tests:
18
+ - Use HashiCorp checkpoint API instead of GitHub Releases API to find latest terraform version, to work around query failures
19
+ - Pin consul provider versions to 1.0.0 to fix intermittent failures
20
+ - Switch Ruby versions used in tests to latest TravisCI versions
21
+ - Stop acceptance testing terraform 0.9.x, as newer versions require pinning the consul provider version but 0.9 doesn't support versioned providers.
22
+
23
+ Version 0.5.1
24
+
25
+ - Fix bug where terraform plan errors were suppressed if a plan run with landscape support enabled exited non-zero.
26
+
27
+ Version 0.5.0
28
+
29
+ - Add support for using terraform_landscape gem, if present, to reformat plan output; see README for usage.
30
+ - Add CircleCI testing under ruby 2.4.1, and acceptance tests for terraform 0.11.2.
31
+
1
32
  Version 0.4.1
2
33
 
3
34
  - Upgrade rubocop, yard and diplomat development dependency versions
data/Gemfile CHANGED
@@ -2,3 +2,7 @@
2
2
 
3
3
  source 'https://rubygems.org'
4
4
  gemspec
5
+
6
+ group :landscape do
7
+ gem 'terraform_landscape', '0.2.2'
8
+ end
data/README.md CHANGED
@@ -1,10 +1,10 @@
1
1
  # tfwrapper
2
2
 
3
- Build of master branch: [![CircleCI](https://circleci.com/gh/manheim/tfwrapper.svg?style=svg)](https://circleci.com/gh/manheim/tfwrapper)
3
+ Build of master branch: [![TravisCI](https://api.travis-ci.org/manheim/tfwrapper.svg?branch=master)](https://travis-ci.org/manheim/tfwrapper)
4
4
 
5
5
  Documentation: [http://www.rubydoc.info/gems/tfwrapper/](http://www.rubydoc.info/gems/tfwrapper/)
6
6
 
7
- tfwrapper provides Rake tasks for working with [Hashicorp Terraform](https://www.terraform.io/) 0.9+, ensuring proper initialization and passing in variables from the environment or Ruby, as well as optionally pushing some information to Consul. tfwrapper also attempts to detect and retry
7
+ tfwrapper provides Rake tasks for working with [Hashicorp Terraform](https://www.terraform.io/) 0.9 through 0.11, ensuring proper initialization and passing in variables from the environment or Ruby, as well as optionally pushing some information to Consul. tfwrapper also attempts to detect and retry
8
8
  failed runs due to AWS throttling or access denied errors.
9
9
 
10
10
  ## Overview
@@ -18,7 +18,9 @@ This Gem provides the following Rake tasks:
18
18
  one or more optional [resource address](https://www.terraform.io/docs/internals/resource-addressing.html) targets to pass to
19
19
  terraform with the ``-target`` flag as Rake task arguments, i.e. ``bundle exec rake tf:plan[aws_instance.foo[1]]`` or
20
20
  ``bundle exec rake tf:plan[aws_instance.foo[1],aws_instance.bar[2]]``; see the
21
- [plan documentation](https://www.terraform.io/docs/commands/plan.html) for more information.
21
+ [plan documentation](https://www.terraform.io/docs/commands/plan.html) for more information. By default this will use
22
+ [terraform_landscape](https://github.com/coinbase/terraform-landscape/blob/master/README.md) for formatting the plan
23
+ output if the ``terraform_landscape`` gem is installed; see the [section below](#terraform-landscape) for more information.
22
24
  * __tf:apply__ - run ``terraform apply`` with all variables and configuration, and TF variables written to disk. You can specify
23
25
  one or more optional [resource address](https://www.terraform.io/docs/internals/resource-addressing.html) targets to pass to
24
26
  terraform with the ``-target`` flag as Rake task arguments, i.e. ``bundle exec rake tf:apply[aws_instance.foo[1]]`` or
@@ -42,12 +44,29 @@ with 1.9.3 is simply too high to justify.
42
44
  Add to your ``Gemfile``:
43
45
 
44
46
  ```ruby
45
- gem 'tfwrapper', '~> 0.2.0'
47
+ gem 'tfwrapper', '~> 0.6.1'
46
48
  ```
47
49
 
48
50
  ### Supported Terraform Versions
49
51
 
50
- tfwrapper only supports terraform 0.9+. It is tested against multiple versions from 0.9.2 to 0.10.2 and the current release.
52
+ tfwrapper only supports terraform 0.9-0.11. It is tested against multiple versions from 0.9.2 to 0.11.14.
53
+
54
+ ### Terraform Landscape
55
+
56
+ The [terraform_landscape](https://github.com/coinbase/terraform-landscape/blob/master/README.md) gem provides enhanced formatting of ``terraform plan`` output including colorization of changes and human-friendly diffs (i.e. diffs of JSON rendered with pretty-printing). By default ``plan`` output will be passed through terraform_landscape if the ``terraform_landscape`` gem is available. To enable this, add ``gem 'terraform_landscape', '~> 0.1.17'`` to your ``Gemfile``. __Note__ that we rely on an undocumented internal API of terraform_landscape to achieve this; the formatting code will fall back to unformatted output in case of an error.
57
+
58
+ If you wish to disable terraform_landscape output even when the gem is installed, pass ``disable_landscape: true`` as an option to ``install_tasks()``:
59
+
60
+ ```ruby
61
+ TFWrapper::RakeTasks.install_tasks('.', disable_landscape: true)
62
+ ```
63
+
64
+ In previous versions or when terraform_landscape is not installed, the output of all terraform commands is streamed in realtime. Since terraform_landscape requires the full and complete ``plan`` output in order to reformat it, this is no longer the case. By default when terraform_landscape is installed and not disabled the ``plan`` task will not produce any output until complete, at which point it will print the landscape-formatted output all at once. This behavior can be controlled with the ``:landscape_progress`` option of ``install_tasks()``, which takes one of the following values:
65
+
66
+ * ``nil``, the default, to not produce any output until the command is complete at which point the landscape-formatted output will be shown.
67
+ * ``:dots`` to print a dot to STDOUT for every line of ``terraform plan`` output and then print the landscape-formatted output when complete.
68
+ * ``:lines`` to print a dot followed by a newline to STDOUT for every line of ``terraform plan`` output and then print the landscape-formatted output when complete. This is useful for systems like Jenkins that line-buffer output (and don't display anything until a newline is encountered).
69
+ * ``:stream`` to stream the ``terraform plan`` output in realtime (as was the previous behavior) and then print the landscape-formatted output when complete. Note that this will result in the output containing the complete unformatted ``terraform plan`` output, followed by the landscape-formatted output.
51
70
 
52
71
  ## Usage
53
72
 
@@ -128,6 +147,10 @@ TFWrapper::RakeTasks.install_tasks(
128
147
  )
129
148
  ```
130
149
 
150
+ These variables are tested to be set in the environment with a non-empty value, and will raise an error if any are
151
+ missing or empty. If some should be allowed to be missing or empty empty, pass a ``allowed_empty_vars`` list with
152
+ their environment variable names.
153
+
131
154
  ### Ruby Variables to Terraform Variables
132
155
 
133
156
  If you wish to explicitly bind values from your Ruby code to terraform variables, you can do this with
@@ -307,6 +330,26 @@ $ consul kv get terraform/inputs/foo
307
330
  {"FOO":"one", "BAR":"two"}
308
331
  ```
309
332
 
333
+ ### Sensitive Environment Variables
334
+ If you wish for certain variables to be marked as "redacted", use the ``tf_sensitive_vars`` option. This is an array of variables that will not be printed.
335
+
336
+ Note: ``aws_access_key`` and ``aws_secret_key`` will always be redacted without requiring configuration.
337
+
338
+
339
+ Example to redact the vaule for ``secret``:
340
+
341
+ Rakefile:
342
+
343
+ ```ruby
344
+ require 'tfwrapper/raketasks'
345
+
346
+ TFWrapper::RakeTasks.install_tasks(
347
+ '.',
348
+ tf_vars_from_env: {'foo' => 'FOO', 'bar' => 'BAR', 'secret' => 'abc'},
349
+ tf_sensitive_vars: ['secret']
350
+ )
351
+ ```
352
+
310
353
  ## Development
311
354
 
312
355
  1. ``bundle install --path vendor``
@@ -31,8 +31,20 @@ module TFWrapper
31
31
  #
32
32
  # @param cmd [String] command to run
33
33
  # @param pwd [String] directory/path to run command in
34
+ # @option opts [Hash] :progress How to handle streaming output. Possible
35
+ # values are ``:stream`` (default) to stream each line in STDOUT/STDERR
36
+ # to STDOUT, ``:dots`` to print a dot for each line, ``:lines`` to print
37
+ # a dot followed by a newline for each line, or ``nil`` to not stream any
38
+ # output at all.
34
39
  # @return [Array] - out_err [String], exit code [Fixnum]
35
- def self.run_cmd_stream_output(cmd, pwd)
40
+ def self.run_cmd_stream_output(cmd, pwd, opts = {})
41
+ stream_type = opts.fetch(:progress, :stream)
42
+ unless [:dots, :lines, :stream, nil].include?(stream_type)
43
+ raise(
44
+ ArgumentError,
45
+ 'progress option must be one of: [:dots, :lines, :stream, nil]'
46
+ )
47
+ end
36
48
  old_sync = $stdout.sync
37
49
  $stdout.sync = true
38
50
  all_out_err = ''.dup
@@ -41,7 +53,13 @@ module TFWrapper
41
53
  stdin.close_write
42
54
  begin
43
55
  while (line = stdout_and_err.gets)
44
- puts line
56
+ if stream_type == :stream
57
+ puts line
58
+ elsif stream_type == :dots
59
+ STDOUT.print '.'
60
+ elsif stream_type == :lines
61
+ puts '.'
62
+ end
45
63
  all_out_err << line
46
64
  end
47
65
  rescue IOError => e
@@ -51,6 +69,7 @@ module TFWrapper
51
69
  end
52
70
  # rubocop:disable Style/RedundantReturn
53
71
  $stdout.sync = old_sync
72
+ puts '' if stream_type == :dots
54
73
  return all_out_err, exit_status
55
74
  # rubocop:enable Style/RedundantReturn
56
75
  end
@@ -59,9 +78,12 @@ module TFWrapper
59
78
  # non-empty. Raise StandardError if any aren't.
60
79
  #
61
80
  # @param required [Array] list of required environment variables
62
- def self.check_env_vars(required)
81
+ # @param allowed_missing [Array] list of environment variables to allow to
82
+ # be empty or missing.
83
+ def self.check_env_vars(required, allowed_missing)
63
84
  missing = []
64
85
  required.each do |name|
86
+ next if allowed_missing.include?(name)
65
87
  if !ENV.include?(name)
66
88
  puts "ERROR: Environment variable '#{name}' must be set."
67
89
  missing << name
@@ -6,6 +6,15 @@ require 'rake'
6
6
  require 'rubygems'
7
7
  require 'tfwrapper/version'
8
8
 
9
+ # :nocov:
10
+ begin
11
+ require 'terraform_landscape'
12
+ HAVE_LANDSCAPE = true
13
+ rescue LoadError
14
+ HAVE_LANDSCAPE = false
15
+ end
16
+ # :nocov:
17
+
9
18
  module TFWrapper
10
19
  # Generates Rake tasks for working with Terraform.
11
20
  class RakeTasks
@@ -46,8 +55,12 @@ module TFWrapper
46
55
  # configurations in the same Rakefile.
47
56
  # @option opts [Hash] :tf_vars_from_env hash of Terraform variables to the
48
57
  # (required) environment variables to populate their values from
58
+ # @option opts [Hash] :allowed_empty_vars array of environment variable
59
+ # names (specified in :tf_vars_from_env) to allow to be empty or missing.
49
60
  # @option opts [Hash] :tf_extra_vars hash of Terraform variables to their
50
61
  # values; overrides any same-named keys in ``tf_vars_from_env``
62
+ # @option opts [Array] :tf_sensitive_vars list of Terraform variables
63
+ # which should not be printed
51
64
  # @option opts [String] :consul_url URL to access Consul at, for the
52
65
  # ``:consul_env_vars_prefix`` option.
53
66
  # @option opts [String] :consul_env_vars_prefix if specified and not nil,
@@ -63,6 +76,22 @@ module TFWrapper
63
76
  # the body of each task. Called with two arguments, the String full
64
77
  # (namespaced) name of the task being executed, and ``tf_dir``. This will
65
78
  # not execute if the body of the task fails.
79
+ # @option opts [Bool] :disable_landscape By default, if the
80
+ # ``terraform_landscape`` gem can be loaded, it will be used to reformat
81
+ # the output of ``terraform plan``. If this is not desired, set to
82
+ # ``true`` to disable landscale. Default: ``false``.
83
+ # @option opts [Symbol, nil] :landscape_progress The ``terraform_landscape``
84
+ # code used to reformat plan output requires the full output of the
85
+ # complete ``plan`` execution. By default, this means that when landscape
86
+ # is used, no output will appear from the time ``terraform plan`` begins
87
+ # until the command is complete. If progress output is desired, this option
88
+ # can be set to one of the following: ``:dots`` to print a dot to STDOUT
89
+ # for every line of ``terraform plan`` output, ``:lines`` to print a dot
90
+ # followed by a newline (e.g. for systems like Jenkins that line buffer)
91
+ # for every line of ``terraform plan`` output, or ``:stream`` to stream
92
+ # the raw ``terraform plan`` output (which will then be followed by the
93
+ # reformatted landscape output). Default is ``nil`` to show no progress
94
+ # output.
66
95
  def initialize(tf_dir, opts = {})
67
96
  # find the directory that contains the Rakefile
68
97
  rakedir = File.realpath(Rake.application.rakefile)
@@ -71,9 +100,20 @@ module TFWrapper
71
100
  @ns_prefix = opts.fetch(:namespace_prefix, nil)
72
101
  @consul_env_vars_prefix = opts.fetch(:consul_env_vars_prefix, nil)
73
102
  @tf_vars_from_env = opts.fetch(:tf_vars_from_env, {})
103
+ @allowed_empty_vars = opts.fetch(:allowed_empty_vars, [])
104
+ @tf_sensitive_vars = opts.fetch(:tf_sensitive_vars, [])
74
105
  @tf_extra_vars = opts.fetch(:tf_extra_vars, {})
75
106
  @backend_config = opts.fetch(:backend_config, {})
76
107
  @consul_url = opts.fetch(:consul_url, nil)
108
+ @disable_landscape = opts.fetch(:disable_landscape, false)
109
+ @landscape_progress = opts.fetch(:landscape_progress, nil)
110
+ unless [:dots, :lines, :stream, nil].include?(@landscape_progress)
111
+ raise(
112
+ ArgumentError,
113
+ 'landscape_progress option must be one of: ' \
114
+ '[:dots, :lines, :stream, nil]'
115
+ )
116
+ end
77
117
  @before_proc = opts.fetch(:before_proc, nil)
78
118
  if !@before_proc.nil? && !@before_proc.is_a?(Proc)
79
119
  raise(
@@ -128,7 +168,9 @@ module TFWrapper
128
168
  desc 'Run terraform init with appropriate arguments'
129
169
  task :init do |t|
130
170
  @before_proc.call(t.name, @tf_dir) unless @before_proc.nil?
131
- TFWrapper::Helpers.check_env_vars(@tf_vars_from_env.values)
171
+ TFWrapper::Helpers.check_env_vars(
172
+ @tf_vars_from_env.values, @allowed_empty_vars
173
+ )
132
174
  @tf_version = check_tf_version
133
175
  cmd = [
134
176
  'terraform',
@@ -160,7 +202,13 @@ module TFWrapper
160
202
  args.extras
161
203
  )
162
204
 
163
- terraform_runner(cmd)
205
+ stream_type = if HAVE_LANDSCAPE && !@disable_landscape
206
+ @landscape_progress
207
+ else
208
+ :stream
209
+ end
210
+ outerr = terraform_runner(cmd, progress: stream_type)
211
+ landscape_format(outerr) if HAVE_LANDSCAPE && !@disable_landscape
164
212
  @after_proc.call(t.name, @tf_dir) unless @after_proc.nil?
165
213
  end
166
214
  end
@@ -274,7 +322,9 @@ module TFWrapper
274
322
  tf_vars = terraform_vars
275
323
  puts 'Terraform vars:'
276
324
  tf_vars.sort.map do |k, v|
277
- if %w[aws_access_key aws_secret_key].include?(k)
325
+ redacted_list = (%w[aws_access_key aws_secret_key] +
326
+ @tf_sensitive_vars)
327
+ if redacted_list.include?(k)
278
328
  puts "#{k} => (redacted)"
279
329
  else
280
330
  puts "#{k} => #{v}"
@@ -296,13 +346,39 @@ module TFWrapper
296
346
  res
297
347
  end
298
348
 
349
+ # Given a string of terraform plan output, format it with
350
+ # terraform_landscape and print the result to STDOUT.
351
+ def landscape_format(output)
352
+ p = TerraformLandscape::Printer.new(
353
+ TerraformLandscape::Output.new(STDOUT)
354
+ )
355
+ p.process_string(output)
356
+ rescue StandardError, ScriptError => ex
357
+ STDERR.puts 'Exception calling terraform_landscape to reformat ' \
358
+ "output: #{ex.class.name}: #{ex}"
359
+ puts output unless @landscape_progress == :stream
360
+ end
361
+
299
362
  # Run a Terraform command, providing some useful output and handling AWS
300
363
  # API rate limiting gracefully. Raises StandardError on failure. The command
301
364
  # is run in @tf_dir.
302
365
  #
303
366
  # @param cmd [String] Terraform command to run
367
+ # @option opts [Hash] :progress How to handle streaming output. Possible
368
+ # values are ``:stream`` (default) to stream each line in STDOUT/STDERR
369
+ # to STDOUT, ``:dots`` to print a dot for each line, ``:lines`` to print
370
+ # a dot followed by a newline for each line, or ``nil`` to not stream any
371
+ # output at all.
372
+ # @return [String] combined STDOUT and STDERR
304
373
  # rubocop:disable Metrics/PerceivedComplexity
305
- def terraform_runner(cmd)
374
+ def terraform_runner(cmd, opts = {})
375
+ stream_type = opts.fetch(:progress, :stream)
376
+ unless [:dots, :lines, :stream, nil].include?(stream_type)
377
+ raise(
378
+ ArgumentError,
379
+ 'progress option must be one of: [:dots, :lines, :stream, nil]'
380
+ )
381
+ end
306
382
  require 'retries'
307
383
  STDERR.puts "terraform_runner command: '#{cmd}' (in #{@tf_dir})"
308
384
  out_err = nil
@@ -321,25 +397,30 @@ module TFWrapper
321
397
  ) do
322
398
  # this streams STDOUT and STDERR as a combined stream,
323
399
  # and also captures them as a combined string
324
- out_err, status = TFWrapper::Helpers.run_cmd_stream_output(cmd, @tf_dir)
400
+ out_err, status = TFWrapper::Helpers.run_cmd_stream_output(
401
+ cmd, @tf_dir, progress: stream_type
402
+ )
325
403
  if status != 0 && out_err.include?('hrottling')
326
- raise StandardError, 'Terraform hit AWS API rate limiting'
404
+ raise StandardError, "#{out_err}\nTerraform hit AWS API rate limiting"
327
405
  end
328
406
  if status != 0 && out_err.include?('status code: 403')
329
- raise StandardError, 'Terraform command got 403 error - access ' \
330
- 'denied or credentials not propagated'
407
+ raise StandardError, "#{out_err}\nTerraform command got 403 error " \
408
+ '- access denied or credentials not propagated'
331
409
  end
332
410
  if status != 0 && out_err.include?('status code: 401')
333
- raise StandardError, 'Terraform command got 401 error - access ' \
334
- 'denied or credentials not propagated'
411
+ raise StandardError, "#{out_err}\nTerraform command got 401 error " \
412
+ '- access denied or credentials not propagated'
335
413
  end
336
414
  end
337
415
  # end exponential backoff
338
416
  unless status.zero?
417
+ # if we weren't streaming output, send it now
418
+ STDERR.puts out_err unless stream_type == :stream
339
419
  raise StandardError, "Errors have occurred executing: '#{cmd}' " \
340
420
  "(exited #{status})"
341
421
  end
342
422
  STDERR.puts "terraform_runner command '#{cmd}' finished and exited 0"
423
+ out_err
343
424
  end
344
425
  # rubocop:enable Metrics/PerceivedComplexity
345
426