tfwrapper 0.4.1 → 0.6.2

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
- 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