terraspace 0.6.4 → 0.6.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 56392f488a05d42760586d379da36022589979733b654ea3f5f3d5fcb879de26
4
- data.tar.gz: 690b3d0fa3e192aee02fd34ae187ab4628caccd335a1733ce10040ff2ef55b32
3
+ metadata.gz: 0d88e968da03e187d230fad5e1785995ffa95415146a9a2b7cfe500c0e54473a
4
+ data.tar.gz: 49535538ad977e0d7b36620037a70478e6bfdf9c9bc607feff4576724fc351fd
5
5
  SHA512:
6
- metadata.gz: 4dcc7b6c1d1f2d21be6115d02cc34bbeb9f773e6ece16e8549ac3a8e6a4167fb6392a2917399399a0ba0ddde4ff880c9dff8de2ee5261def5250fc2525527ce1
7
- data.tar.gz: 476d2c10897c2915a3cd601b556e783a639cb29a97c11505b5e34f32d8079c311e5ad8f6efeeee6cf9a143a2f63b9b46b76a9b6741d78802fd53baf8de319b29
6
+ metadata.gz: 3d0a9476e10f46a504c555212aa538968045d27845eabcec4c43306a008422dce9b19c28b4d0ce16defce89bc78674c01098c08291af6e321f85f2385e6390f0
7
+ data.tar.gz: 27563820dafa65f85df07ce9e28430a75b7ffb6330b2440444b6a88803efa3f851d9524ae5c0417039a3d698a62896b2071fb84b8de30e93a42c03ad64941dbd
data/CHANGELOG.md CHANGED
@@ -3,6 +3,23 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  This project *loosely tries* to adhere to [Semantic Versioning](http://semver.org/), even before v1.0.
5
5
 
6
+ ## [0.6.9] - 2021-05-07
7
+ - [#112](https://github.com/boltops-tools/terraspace/pull/112) fix smart auto retry
8
+
9
+ ## [0.6.8] - 2021-05-07
10
+ - [#110](https://github.com/boltops-tools/terraspace/pull/110) fix popen deadlock with large amounts of output [#97](https://github.com/boltops-tools/terraspace/pull/97) Terraspace hangs when TF_LOG=TRACE environment variable exists #97
11
+
12
+ ## [0.6.7] - 2021-05-05
13
+ - [#108](https://github.com/boltops-tools/terraspace/pull/108) provide runner context to terraspace hook
14
+
15
+ ## [0.6.6] - 2021-04-15
16
+ - [#101](https://github.com/boltops-tools/terraspace/pull/101) terraspace force-unlock command
17
+ - [#102](https://github.com/boltops-tools/terraspace/pull/102) fix terraspace all summarized logging
18
+ - [#103](https://github.com/boltops-tools/terraspace/pull/103) config.build.pass_files with default files use pass strategy
19
+
20
+ ## [0.6.5] - 2021-03-24
21
+ - [#96](https://github.com/boltops-tools/terraspace/pull/96) terraspace fmt: ability to specific module or stack
22
+
6
23
  ## [0.6.4] - 2021-03-22
7
24
  - [#94](https://github.com/boltops-tools/terraspace/pull/94) terraspace fmt command
8
25
 
@@ -104,7 +104,7 @@ module Terraspace::All
104
104
  def run_terraspace(mod_name)
105
105
  set_log_path!(mod_name)
106
106
  name = command_map(@command)
107
- o = @options.merge(mod: mod_name, yes: true, build: false, input: false)
107
+ o = @options.merge(mod: mod_name, yes: true, build: false, input: false, log_to_stderr: true)
108
108
  o.merge!(quiet: false) if @command == "init" # noisy so can filter and summarize output
109
109
  case @command
110
110
  when "up"
@@ -27,6 +27,8 @@ module Terraspace
27
27
  config.build.cache_dir = ":CACHE_ROOT/:REGION/:ENV/:BUILD_DIR"
28
28
  config.build.cache_root = nil # defaults to /full/path/to/.terraspace-cache
29
29
  config.build.clean_cache = nil # defaults to /full/path/to/.terraspace-cache
30
+ config.build.default_pass_files = ["/files/"]
31
+ config.build.pass_files = []
30
32
  config.bundle = ActiveSupport::OrderedOptions.new
31
33
  config.bundle.logger = ts_logger
32
34
  config.init = ActiveSupport::OrderedOptions.new
@@ -27,6 +27,9 @@ module Terraspace
27
27
  reconfigure_option = Proc.new {
28
28
  option :reconfigure, type: :boolean, desc: "Add terraform -reconfigure option"
29
29
  }
30
+ type_option = Proc.new {
31
+ option :type, default: "stack", aliases: %w[t], desc: "Type: stack, module, or all"
32
+ }
30
33
 
31
34
  desc "all SUBCOMMAND", "all subcommands"
32
35
  long_desc Help.text(:all)
@@ -82,10 +85,18 @@ module Terraspace
82
85
  Down.new(options.merge(mod: mod)).run
83
86
  end
84
87
 
88
+ desc "force_unlock", "Calls terrform force-unlock"
89
+ long_desc Help.text(:force_unlock)
90
+ instance_option.call
91
+ def force_unlock(mod, lock_id)
92
+ Commander.new("force-unlock", options.merge(mod: mod, lock_id: lock_id)).run
93
+ end
94
+
85
95
  desc "fmt", "Run terraform fmt"
86
96
  long_desc Help.text(:fmt)
87
- def fmt
88
- Fmt.new(options).run
97
+ type_option.call
98
+ def fmt(mod=nil)
99
+ Fmt.new(options.merge(mod: mod)).run
89
100
  end
90
101
 
91
102
  desc "info STACK", "Get info about stack."
@@ -106,7 +117,7 @@ module Terraspace
106
117
 
107
118
  desc "list", "List stacks and modules."
108
119
  long_desc Help.text(:list)
109
- option :type, default: "stack", aliases: %w[t], desc: "Type: stack, module, or all"
120
+ type_option.call
110
121
  def list
111
122
  List.new(options).run
112
123
  end
@@ -5,11 +5,12 @@ class Terraspace::CLI
5
5
 
6
6
  def initialize(options={})
7
7
  @options = options
8
+ @mod_name = options[:mod]
8
9
  end
9
10
 
10
11
  def run
11
12
  logger.info "Formating terraform files"
12
- app_source_dirs.each do |dir|
13
+ dirs.each do |dir|
13
14
  format(dir)
14
15
  end
15
16
  end
@@ -17,5 +18,23 @@ class Terraspace::CLI
17
18
  def format(dir)
18
19
  Runner.new(dir).format!
19
20
  end
21
+
22
+ private
23
+ def dirs
24
+ if @mod_name
25
+ type_dirs.select { |p| p.include?(@mod_name) }
26
+ else
27
+ type_dirs
28
+ end
29
+ end
30
+
31
+ def type_dirs
32
+ type = @options[:type]
33
+ if type
34
+ app_source_dirs.select { |p| p.include?("/#{type.pluralize}/") }
35
+ else
36
+ app_source_dirs
37
+ end
38
+ end
20
39
  end
21
40
  end
@@ -1,5 +1,7 @@
1
1
  ## Example
2
2
 
3
+ Format all source files.
4
+
3
5
  $ terraspace fmt
4
6
  Formating terraform files
5
7
  app/modules/example
@@ -7,4 +9,14 @@
7
9
  outputs.tf
8
10
  variables.tf
9
11
  app/stacks/demo
10
- main.tf
12
+ main.tf
13
+
14
+ Format specific module or stack.
15
+
16
+ $ terraspace fmt stack1
17
+ $ terraspace fmt module1
18
+
19
+ Format scoping to module or stack types. In case there's a module and stack with the same name.
20
+
21
+ $ terraspace fmt example -t module
22
+ $ terraspace fmt demo -t stacke
@@ -0,0 +1,7 @@
1
+ ## Example
2
+
3
+ terraspace force_unlock demo ab7f3469-2a5f-07b9-29c5-dec1537ec8b0
4
+
5
+ Instance option:
6
+
7
+ terraspace force_unlock demo -i 2 ab7f3469-2a5f-07b9-29c5-dec1537ec8b0
@@ -1,14 +1,15 @@
1
1
  module Terraspace::Compiler::Strategy
2
2
  class Mod < AbstractBase
3
3
  def run
4
- ext = File.extname(@src_path).sub('.','')
5
- klass = strategy_class(ext)
4
+ klass = strategy_class(@src_path)
6
5
  strategy = klass.new(@mod, @src_path) # IE: Terraspace::Compiler::Strategy::Mod::Rb.new
7
6
  strategy.run
8
7
  end
9
8
 
10
- def strategy_class(ext)
9
+ def strategy_class(path)
10
+ ext = File.extname(path).sub('.','')
11
11
  return Mod::Pass if ext.empty? # infinite loop without this
12
+ return Mod::Pass if Terraspace.pass_file?(path)
12
13
  "Terraspace::Compiler::Strategy::Mod::#{ext.camelize}".constantize rescue Mod::Pass
13
14
  end
14
15
  end
@@ -10,11 +10,17 @@ module Terraspace::Compiler
10
10
  end
11
11
 
12
12
  def dest_path
13
- name = @dest_name || @src_path.sub('.rb','.tf.json')
13
+ name = get_name
14
14
  name = basename(name)
15
15
  "#{dest_dir}/#{name}"
16
16
  end
17
17
 
18
+ def get_name
19
+ return @dest_name if @dest_name
20
+ return @src_path if Terraspace.pass_file?(@src_path)
21
+ @src_path.sub('.rb','.tf.json')
22
+ end
23
+
18
24
  def dest_dir
19
25
  if @mod.is_a?(Terraspace::Mod::Remote)
20
26
  File.dirname(@src_path) # for Mod::Remote src is dest
@@ -51,5 +51,12 @@ module Terraspace
51
51
  def logger=(v)
52
52
  @@logger = v
53
53
  end
54
+
55
+ def pass_file?(path)
56
+ pass_files = config.build.pass_files + config.build.default_pass_files
57
+ pass_files.uniq.detect do |i|
58
+ i.is_a?(Regexp) ? path =~ i : path.include?(i)
59
+ end
60
+ end
54
61
  end
55
62
  end
@@ -2,6 +2,17 @@ module Terraspace::Hooks
2
2
  class Runner
3
3
  include Terraspace::Util
4
4
 
5
+ # exposing mod and hook so terraspace hooks have access to them via runner context. IE:
6
+ #
7
+ # class EnvExporter
8
+ # def call(runner)
9
+ # puts "runner.hook #{runner.hook}"
10
+ # end
11
+ # end
12
+ #
13
+ # Docs: http://terraspace.cloud/docs/config/hooks/ruby/#method-argument
14
+ #
15
+ attr_reader :mod, :hook
5
16
  def initialize(mod, hook)
6
17
  @mod, @hook = mod, hook
7
18
  @execute = @hook["execute"]
@@ -12,12 +23,24 @@ module Terraspace::Hooks
12
23
  when String
13
24
  Terraspace::Shell.new(@mod, @execute, exit_on_fail: @hook["exit_on_fail"]).run
14
25
  when -> (e) { e.respond_to?(:public_instance_methods) && e.public_instance_methods.include?(:call) }
15
- @execute.new.call
26
+ executor = @execute.new
16
27
  when -> (e) { e.respond_to?(:call) }
17
- @execute.call
28
+ executor = @execute
18
29
  else
19
30
  logger.warn "WARN: execute option not set for hook: #{@hook.inspect}"
20
31
  end
32
+
33
+ return unless executor
34
+
35
+ meth = executor.method(:call)
36
+ case meth.arity
37
+ when 0
38
+ executor.call # backwards compatibility
39
+ when 1
40
+ executor.call(self)
41
+ else
42
+ raise "The #{executor} call method definition has been more than 1 arguments and is not supported"
43
+ end
21
44
  end
22
45
  end
23
46
  end
@@ -29,70 +29,98 @@ module Terraspace
29
29
 
30
30
  def popen3(env)
31
31
  Open3.popen3(env, @command, chdir: @mod.cache_dir) do |stdin, stdout, stderr, wait_thread|
32
- mimic_terraform_input(stdin, stdout)
33
- while out = stdout.gets
34
- terraform_to_stdout(out)
35
- end
36
-
37
- while err = stderr.gets
38
- @error ||= Error.new
39
- @error.lines << err # aggregate all error lines
40
- unless @error.known?
41
- # Sometimes may print a "\e[31m\n" which like during dependencies fetcher init
42
- # suppress it so dont get a bunch of annoying "newlines"
43
- next if err == "\e[31m\n" && @options[:suppress_error_color]
44
- logger.error(err)
45
- end
46
- end
47
-
32
+ handle_streams(stdin, stdout, stderr)
48
33
  status = wait_thread.value.exitstatus
49
34
  exit_status(status)
50
35
  end
51
36
  end
52
37
 
53
- def exit_status(status)
54
- return if status == 0
38
+ BLOCK_SIZE = Integer(ENV['TS_BUFFER_BLOCK_SIZE'] || 102400)
39
+ BUFFER_TIMEOUT = Integer(ENV['TS_BUFFER_TIMEOUT'] || 3600) # 3600s = 1h
40
+ def handle_streams(stdin, stdout, stderr)
41
+ files = [stdout, stderr]
42
+ # note: t=0 and t=nil means no timeout. See: https://bit.ly/2PURlCX
43
+ t = BUFFER_TIMEOUT.to_i unless BUFFER_TIMEOUT.nil?
44
+ Timeout::timeout(t) do
45
+ until all_eof?(files) do
46
+ ready = IO.select(files, nil, nil, 0.1)
47
+ next unless ready
55
48
 
56
- exit_on_fail = @options[:exit_on_fail].nil? ? true : @options[:exit_on_fail]
57
- if @error && @error.known?
58
- raise @error.instance
59
- elsif exit_on_fail
60
- logger.error "Error running command: #{@command}".color(:red)
61
- exit status
49
+ readable = ready[0]
50
+ readable.each do |f|
51
+ buffer = f.read_nonblock(BLOCK_SIZE, exception: false)
52
+ next unless buffer
53
+
54
+ lines = buffer.split("\n")
55
+ lines.each do |line|
56
+ if f.fileno == stdout.fileno
57
+ handle_stdout(line)
58
+ handle_input(stdin, line)
59
+ else
60
+ handle_stderr(line)
61
+ end
62
+ end
63
+ end
64
+ end
62
65
  end
63
66
  end
64
67
 
68
+ def handle_stderr(line)
69
+ @error ||= Error.new
70
+ @error.lines << line # aggregate all error lines
71
+
72
+ return if @error.known?
73
+ # Sometimes may print a "\e[31m\n" which like during dependencies fetcher init
74
+ # suppress it so dont get a bunch of annoying "newlines"
75
+ return if line == "\e[31m\n" && @options[:suppress_error_color]
76
+
77
+ logger.error(line)
78
+ end
79
+
80
+ def all_eof?(files)
81
+ files.find { |f| !f.eof }.nil?
82
+ end
83
+
65
84
  # Terraform doesnt seem to stream the line that prompts with "Enter a value:" when using Open3.popen3
66
85
  # Hack around it by mimicking the "Enter a value:" prompt
67
86
  #
68
87
  # Note: system does stream the prompt but using Open3.popen3 so we can capture output to save to logs.
69
- def mimic_terraform_input(stdin, stdout)
70
- shown = false
88
+ def handle_input(stdin, line)
89
+ # stdout doesnt seem to flush and show "Enter a value: " look for earlier output
71
90
  patterns = [
72
91
  "Only 'yes' will be accepted", # prompt for apply. can happen on apply
73
92
  "\e[0m\e[1mvar.", # prompts for variable input. can happen on plan or apply. looking for bold marker also in case "var." shows up somewhere else
74
93
  ]
75
- while out = stdout.gets
76
- terraform_to_stdout(out) unless shown && out.include?("Enter a value:")
77
- shown = false if out.include?("Enter a value:") # reset shown in case of multiple input prompts
78
-
79
- # Sometimes stdout doesnt flush and show "Enter a value: ", so mimic it
80
- if patterns.any? { |pattern| out.include?(pattern) }
81
- print " Enter a value: ".bright
82
- shown = true
83
- stdin.write_nonblock($stdin.gets)
84
- end
94
+ if patterns.any? { |pattern| line.include?(pattern) }
95
+ print "\n Enter a value: ".bright
96
+ stdin.write_nonblock($stdin.gets)
97
+ end
98
+ end
99
+
100
+ def exit_status(status)
101
+ return if status == 0
102
+
103
+ exit_on_fail = @options[:exit_on_fail].nil? ? true : @options[:exit_on_fail]
104
+ if @error && @error.known?
105
+ raise @error.instance
106
+ elsif exit_on_fail
107
+ logger.error "Error running command: #{@command}".color(:red)
108
+ exit status
85
109
  end
86
110
  end
87
111
 
88
- # Allows piping to jq. IE:
89
- # terraspace show demo --json | jq
90
- def terraform_to_stdout(out)
91
- # so terraform output goes stdout
112
+ def handle_stdout(line)
113
+ prompted = line.include?('Enter a value')
114
+ @prompt_shown ||= prompted
115
+ return if @prompt_shown && prompted
116
+
117
+ # Terraspace logger has special stdout method so original terraform output
118
+ # can be piped to jq. IE:
119
+ # terraspace show demo --json | jq
92
120
  if logger.respond_to?(:stdout) && !@options[:log_to_stderr]
93
- logger.stdout(out)
121
+ logger.stdout(line)
94
122
  else
95
- logger.info(out)
123
+ logger.info(line)
96
124
  end
97
125
  end
98
126
  end
@@ -3,7 +3,7 @@ require "tempfile"
3
3
  module Terraspace::Terraform::Args
4
4
  class Default
5
5
  def initialize(mod, name, options={})
6
- @mod, @name, @options = mod, name, options
6
+ @mod, @name, @options = mod, name.underscore, options
7
7
  @quiet = @options[:quiet].nil? ? true : @options[:quiet]
8
8
  end
9
9
 
@@ -11,14 +11,18 @@ module Terraspace::Terraform::Args
11
11
  # https://terraspace.cloud/docs/ci-automation/
12
12
  ENV['TF_IN_AUTOMATION'] = '1' if @options[:auto]
13
13
 
14
- if %w[apply destroy init output plan show].include?(@name)
15
- meth = "#{@name}_args"
16
- send(meth)
14
+ args_meth = "#{@name}_args"
15
+ if respond_to?(args_meth)
16
+ send(args_meth)
17
17
  else
18
18
  []
19
19
  end
20
20
  end
21
21
 
22
+ def force_unlock_args
23
+ [" -force #{@options[:lock_id]}"]
24
+ end
25
+
22
26
  def apply_args
23
27
  args = auto_approve_arg
24
28
  var_files = @options[:var_files]
@@ -1,3 +1,3 @@
1
1
  module Terraspace
2
- VERSION = "0.6.4"
2
+ VERSION = "0.6.9"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: terraspace
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.4
4
+ version: 0.6.9
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tung Nguyen
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-03-22 00:00:00.000000000 Z
11
+ date: 2021-05-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -508,6 +508,7 @@ files:
508
508
  - lib/terraspace/cli/help/console.md
509
509
  - lib/terraspace/cli/help/down.md
510
510
  - lib/terraspace/cli/help/fmt.md
511
+ - lib/terraspace/cli/help/force_unlock.md
511
512
  - lib/terraspace/cli/help/info.md
512
513
  - lib/terraspace/cli/help/init.md
513
514
  - lib/terraspace/cli/help/list.md