bora 1.6.0 → 1.7.0

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.
@@ -2,15 +2,12 @@ require 'bora/cfn/status'
2
2
 
3
3
  class Bora
4
4
  module Cfn
5
-
6
5
  class StackStatus
7
- DOES_NOT_EXIST_MESSAGE = "Stack does not exist"
6
+ DOES_NOT_EXIST_MESSAGE = 'Stack does not exist'.freeze
8
7
 
9
8
  def initialize(underlying_stack)
10
9
  @stack = underlying_stack
11
- if @stack
12
- @status = Status.new(@stack.stack_status)
13
- end
10
+ @status = Status.new(@stack.stack_status) if @stack
14
11
  end
15
12
 
16
13
  def exists?
@@ -23,13 +20,12 @@ class Bora
23
20
 
24
21
  def to_s
25
22
  if @stack
26
- status_reason = @stack.stack_status_reason ? " - #{@stack.stack_status_reason}" : ""
23
+ status_reason = @stack.stack_status_reason ? " - #{@stack.stack_status_reason}" : ''
27
24
  "#{@stack.stack_name} - #{@status}#{status_reason}"
28
25
  else
29
26
  DOES_NOT_EXIST_MESSAGE
30
27
  end
31
28
  end
32
29
  end
33
-
34
30
  end
35
31
  end
@@ -8,15 +8,15 @@ class Bora
8
8
  end
9
9
 
10
10
  def success?
11
- @status.end_with?("_COMPLETE") && !@status.include?("ROLLBACK")
11
+ @status.end_with?('_COMPLETE') && !@status.include?('ROLLBACK')
12
12
  end
13
13
 
14
14
  def failure?
15
- @status.end_with?("FAILED") || @status.include?("ROLLBACK")
15
+ @status.end_with?('FAILED') || @status.include?('ROLLBACK')
16
16
  end
17
17
 
18
18
  def deleted?
19
- @status == "DELETE_COMPLETE"
19
+ @status == 'DELETE_COMPLETE'
20
20
  end
21
21
 
22
22
  def complete?
@@ -27,17 +27,14 @@ class Bora
27
27
  @status.colorize(color)
28
28
  end
29
29
 
30
-
31
30
  private
32
31
 
33
32
  def color
34
- case
35
- when success?; :green
36
- when failure?; :red
37
- else; :yellow;
33
+ if success? then :green
34
+ elsif failure? then :red
35
+ else; :yellow
38
36
  end
39
37
  end
40
38
  end
41
-
42
39
  end
43
40
  end
@@ -1,101 +1,103 @@
1
- require "thor"
2
- require "bora"
3
- require "bora/cli_base"
4
- require "bora/cli_change_set"
1
+ require 'thor'
2
+ require 'bora'
3
+ require 'bora/cli_base'
4
+ require 'bora/cli_change_set'
5
5
 
6
6
  class Bora
7
7
  class Cli < CliBase
8
-
9
- class_option :file,
8
+ class_option(
9
+ :file,
10
10
  type: :string,
11
11
  aliases: :f,
12
12
  default: Bora::DEFAULT_CONFIG_FILE,
13
- desc: "The Bora config file to use"
14
-
15
- class_option :region,
13
+ desc: 'The Bora config file to use'
14
+ )
15
+ class_option(
16
+ :region,
16
17
  type: :string,
17
18
  aliases: :r,
18
19
  default: nil,
19
- desc: "The region to use for the stack operation. Overrides any regions specified in the Bora config file."
20
-
21
- class_option "cfn-stack-name",
20
+ desc: 'The region to use for the stack operation. Overrides any regions specified in the Bora config file.'
21
+ )
22
+ class_option(
23
+ 'cfn-stack-name',
22
24
  type: :string,
23
25
  aliases: :n,
24
26
  default: nil,
25
- desc: "The name to give the stack in CloudFormation. Overrides any CFN stack name setting in the Bora config file."
27
+ desc: 'The name to give the stack in CloudFormation. Overrides any CFN stack name setting in the Bora config file.'
28
+ )
26
29
 
27
- desc "list", "Lists the available stacks"
30
+ desc 'list', 'Lists the available stacks'
28
31
  def list
29
32
  templates = bora(options.file).templates
30
- stacks = templates.collect { |t| t.stacks }.flatten
31
- stack_names = stacks.collect { |s| s.stack_name }
33
+ stacks = templates.collect(&:stacks).flatten
34
+ stack_names = stacks.collect(&:stack_name)
32
35
  puts stack_names.join("\n")
33
36
  end
34
37
 
35
- desc "apply STACK_NAME", "Creates or updates the stack"
38
+ desc 'apply STACK_NAME', 'Creates or updates the stack'
36
39
  option :params, type: :array, aliases: :p, desc: "Parameters to be passed to the template, eg: --params 'instance_type=t2.micro'"
37
- option :pretty, type: :boolean, default: false, desc: "Send pretty (formatted) JSON to AWS (only works for cfndsl templates)"
40
+ option :pretty, type: :boolean, default: false, desc: 'Send pretty (formatted) JSON to AWS (only works for cfndsl templates)'
38
41
  def apply(stack_name)
39
42
  stack(options.file, stack_name).apply(params, options.pretty)
40
43
  end
41
44
 
42
- desc "delete STACK_NAME", "Deletes the stack"
45
+ desc 'delete STACK_NAME', 'Deletes the stack'
43
46
  def delete(stack_name)
44
47
  stack(options.file, stack_name).delete
45
48
  end
46
49
 
47
- desc "diff STACK_NAME", "Diffs the new template with the stack's current template"
50
+ desc 'diff STACK_NAME', "Diffs the new template with the stack's current template"
48
51
  option :params, type: :array, aliases: :p, desc: "Parameters to be passed to the template, eg: --params 'instance_type=t2.micro'"
49
- option :context, type: :numeric, aliases: :c, default: 3, desc: "Number of lines of context to show around the differences"
52
+ option :context, type: :numeric, aliases: :c, default: 3, desc: 'Number of lines of context to show around the differences'
50
53
  def diff(stack_name)
51
54
  stack(options.file, stack_name).diff(params, options.context)
52
55
  end
53
56
 
54
- desc "events STACK_NAME", "Outputs the latest events from the stack"
57
+ desc 'events STACK_NAME', 'Outputs the latest events from the stack'
55
58
  def events(stack_name)
56
59
  stack(options.file, stack_name).events
57
60
  end
58
61
 
59
- desc "outputs STACK_NAME", "Shows the outputs from the stack"
62
+ desc 'outputs STACK_NAME', 'Shows the outputs from the stack'
60
63
  def outputs(stack_name)
61
64
  stack(options.file, stack_name).outputs
62
65
  end
63
66
 
64
- desc "parameters STACK_NAME", "Shows the parameters from the stack"
67
+ desc 'parameters STACK_NAME', 'Shows the parameters from the stack'
65
68
  def parameters(stack_name)
66
69
  stack(options.file, stack_name).parameters
67
70
  end
68
71
 
69
- desc "recreate STACK_NAME", "Recreates (deletes then creates) the stack"
72
+ desc 'recreate STACK_NAME', 'Recreates (deletes then creates) the stack'
70
73
  option :params, type: :array, aliases: :p, desc: "Parameters to be passed to the template, eg: --params 'instance_type=t2.micro'"
71
74
  def recreate(stack_name)
72
75
  stack(options.file, stack_name).recreate(params)
73
76
  end
74
77
 
75
- desc "show STACK_NAME", "Shows the new template for stack"
78
+ desc 'show STACK_NAME', 'Shows the new template for stack'
76
79
  option :params, type: :array, aliases: :p, desc: "Parameters to be passed to the template, eg: --params 'instance_type=t2.micro'"
77
80
  def show(stack_name)
78
81
  stack(options.file, stack_name).show(params)
79
82
  end
80
83
 
81
- desc "show_current STACK_NAME", "Shows the current template for the stack"
84
+ desc 'show_current STACK_NAME', 'Shows the current template for the stack'
82
85
  def show_current(stack_name)
83
86
  stack(options.file, stack_name).show_current
84
87
  end
85
88
 
86
- desc "status STACK_NAME", "Displays the current status of the stack"
89
+ desc 'status STACK_NAME', 'Displays the current status of the stack'
87
90
  def status(stack_name)
88
91
  stack(options.file, stack_name).status
89
92
  end
90
93
 
91
- desc "validate STACK_NAME", "Checks the stack's template for validity"
94
+ desc 'validate STACK_NAME', "Checks the stack's template for validity"
92
95
  option :params, type: :array, aliases: :p, desc: "Parameters to be passed to the template, eg: --params 'instance_type=t2.micro'"
93
96
  def validate(stack_name)
94
97
  stack(options.file, stack_name).validate(params)
95
98
  end
96
99
 
97
- desc "changeset SUBCOMMAND ...ARGS", "Manage CloudFormation change sets"
98
- subcommand "changeset", CliChangeSet
99
-
100
+ desc 'changeset SUBCOMMAND ...ARGS', 'Manage CloudFormation change sets'
101
+ subcommand 'changeset', CliChangeSet
100
102
  end
101
103
  end
@@ -1,11 +1,11 @@
1
- require "thor"
1
+ require 'thor'
2
2
 
3
3
  class Bora
4
4
  class CliBase < Thor
5
5
  # Fix for incorrect subcommand help. See https://github.com/erikhuda/thor/issues/261
6
- def self.banner(command, namespace = nil, subcommand = false)
6
+ def self.banner(command, _namespace = nil, subcommand = false)
7
7
  subcommand = subcommand_prefix
8
- subcommand_str = subcommand ? " #{subcommand}" : ""
8
+ subcommand_str = subcommand ? " #{subcommand}" : ''
9
9
  "#{basename}#{subcommand_str} #{command.usage}"
10
10
  end
11
11
 
@@ -16,15 +16,15 @@ class Bora
16
16
  no_commands do
17
17
  def stack(config_file, stack_name)
18
18
  region = options.region
19
- cfn_stack_name = options["cfn-stack-name"]
19
+ cfn_stack_name = options['cfn-stack-name']
20
20
 
21
21
  override_config = {}
22
- override_config["default_region"] = region if region
23
- override_config["cfn_stack_name"] = cfn_stack_name if cfn_stack_name
22
+ override_config['default_region'] = region if region
23
+ override_config['cfn_stack_name'] = cfn_stack_name if cfn_stack_name
24
24
 
25
25
  bora = bora(config_file, override_config)
26
26
  stack = bora.stack(stack_name)
27
- if !stack
27
+ unless stack
28
28
  STDERR.puts "Could not find stack #{stack_name}"
29
29
  exit(1)
30
30
  end
@@ -36,7 +36,7 @@ class Bora
36
36
  end
37
37
 
38
38
  def params
39
- options.params ? Hash[options.params.map { |param| param.split("=", 2) }] : {}
39
+ options.params ? Hash[options.params.map { |param| param.split('=', 2) }] : {}
40
40
  end
41
41
  end
42
42
  end
@@ -1,40 +1,39 @@
1
- require "thor"
1
+ require 'thor'
2
2
  require 'thor/group'
3
3
 
4
4
  class Bora
5
5
  class CliChangeSet < CliBase
6
6
  # Fix for incorrect subcommand help. See https://github.com/erikhuda/thor/issues/261
7
7
  def self.subcommand_prefix
8
- "changeset"
8
+ 'changeset'
9
9
  end
10
10
 
11
- desc "create STACK_NAME CHANGE_SET_NAME", "Creates a change set"
11
+ desc 'create STACK_NAME CHANGE_SET_NAME', 'Creates a change set'
12
12
  option :params, type: :array, aliases: :p, desc: "Parameters to be passed to the template, eg: --params 'instance_type=t2.micro'"
13
- option :description, type: :string, aliases: :d, desc: "A description for this change set"
14
- option :pretty, type: :boolean, default: false, desc: "Send pretty (formatted) JSON to AWS (only works for cfndsl templates)"
13
+ option :description, type: :string, aliases: :d, desc: 'A description for this change set'
14
+ option :pretty, type: :boolean, default: false, desc: 'Send pretty (formatted) JSON to AWS (only works for cfndsl templates)'
15
15
  def create(stack_name, change_set_name)
16
16
  stack(options.file, stack_name).create_change_set(change_set_name, options.description, params, options.pretty)
17
17
  end
18
18
 
19
- desc "list STACK_NAME", "Lists all change sets for stack STACK_NAME"
19
+ desc 'list STACK_NAME', 'Lists all change sets for stack STACK_NAME'
20
20
  def list(stack_name)
21
21
  stack(options.file, stack_name).list_change_sets
22
22
  end
23
23
 
24
- desc "show STACK_NAME CHANGE_SET_NAME", "Shows the details of the named change set"
24
+ desc 'show STACK_NAME CHANGE_SET_NAME', 'Shows the details of the named change set'
25
25
  def show(stack_name, change_set_name)
26
26
  stack(options.file, stack_name).describe_change_set(change_set_name)
27
27
  end
28
28
 
29
- desc "delete STACK_NAME CHANGE_SET_NAME", "Deletes the named change set"
29
+ desc 'delete STACK_NAME CHANGE_SET_NAME', 'Deletes the named change set'
30
30
  def delete(stack_name, change_set_name)
31
31
  stack(options.file, stack_name).delete_change_set(change_set_name)
32
32
  end
33
33
 
34
- desc "apply STACK_NAME CHANGE_SET_NAME", "Executes the named change set"
34
+ desc 'apply STACK_NAME CHANGE_SET_NAME', 'Executes the named change set'
35
35
  def apply(stack_name, change_set_name)
36
36
  stack(options.file, stack_name).execute_change_set(change_set_name)
37
37
  end
38
-
39
38
  end
40
39
  end
@@ -5,7 +5,10 @@ class Bora
5
5
  class ParameterResolver
6
6
  UnresolvedSubstitutionError = Class.new(StandardError)
7
7
 
8
- PLACEHOLDER_REGEX = /\${[^}]+}/
8
+ # Regular expression that can match placeholders nested to two levels.
9
+ # For example it will match: "${foo-${bar}}".
10
+ # See https://stackoverflow.com/questions/17759004/how-to-match-string-within-parentheses-nested-in-java
11
+ PLACEHOLDER_REGEX = /\${([^{}]*|{[^{}]*})*}/
9
12
 
10
13
  def initialize(stack)
11
14
  @stack = stack
@@ -20,7 +23,7 @@ class Bora
20
23
  placeholders_were_substituted = false
21
24
  params.each do |k, v|
22
25
  resolved_value = process_param_substitutions(v, params)
23
- unresolved_placeholders_still_remain ||= has_unresolved_placeholder?(resolved_value)
26
+ unresolved_placeholders_still_remain ||= unresolved_placeholder?(resolved_value)
24
27
  placeholders_were_substituted ||= resolved_value != v
25
28
  params[k] = resolved_value
26
29
  end
@@ -31,13 +34,18 @@ class Bora
31
34
  params
32
35
  end
33
36
 
34
-
35
37
  private
36
38
 
37
39
  def process_param_substitutions(val, params)
38
40
  result = val
39
41
  if val.is_a? String
40
42
  result = val.gsub(PLACEHOLDER_REGEX) do |placeholder|
43
+ # Handle nested substitutions, like "${foo-${bar}}
44
+ if unresolved_placeholder?(placeholder)
45
+ placeholder_contents = placeholder[2..-2]
46
+ placeholder = "${#{process_param_substitutions(placeholder_contents, params)}}"
47
+ end
48
+
41
49
  process_placeholder(placeholder, params)
42
50
  end
43
51
  elsif val.is_a? Array
@@ -53,7 +61,7 @@ class Bora
53
61
  if !uri.scheme
54
62
  # This token refers to another parameter, rather than a resolver
55
63
  value_to_substitute = params[uri.path]
56
- return !value_to_substitute || has_unresolved_placeholder?(value_to_substitute) ? placeholder : value_to_substitute
64
+ return !value_to_substitute || unresolved_placeholder?(value_to_substitute) ? placeholder : value_to_substitute
57
65
  else
58
66
  # This token needs to be resolved by a resolver
59
67
  resolver_name = uri.scheme
@@ -62,14 +70,14 @@ class Bora
62
70
  end
63
71
  end
64
72
 
65
- def has_unresolved_placeholder?(val)
73
+ def unresolved_placeholder?(val)
66
74
  result = false
67
75
  if val.is_a? String
68
76
  result = val =~ PLACEHOLDER_REGEX
69
77
  elsif val.is_a? Array
70
- result = val.find { |i| has_unresolved_placeholder?(i) }
78
+ result = val.find { |i| unresolved_placeholder?(i) }
71
79
  elsif val.is_a? Hash
72
- result = val.find { |_, v| has_unresolved_placeholder?(v) }
80
+ result = val.find { |_, v| unresolved_placeholder?(v) }
73
81
  end
74
82
  result
75
83
  end
@@ -79,15 +87,14 @@ class Bora
79
87
 
80
88
  # Support for legacy CFN substitutions without a scheme, eg: ${stack/outputs/foo}.
81
89
  # Will be removed in next breaking version.
82
- if !uri.scheme && uri.path && uri.path.count("/") == 2
90
+ if !uri.scheme && uri.path && uri.path.count('/') == 2
83
91
  uri = URI("cfn://#{s}")
84
92
  end
85
93
  uri
86
94
  end
87
95
 
88
96
  def unresolved_placeholders_as_string(params)
89
- params.select { |k, v| has_unresolved_placeholder?(v) }.to_a.map { |k, v| "#{k}: #{v}" }.join("\n")
97
+ params.select { |_k, v| unresolved_placeholder?(v) }.to_a.map { |k, v| "#{k}: #{v}" }.join("\n")
90
98
  end
91
-
92
99
  end
93
100
  end
@@ -3,7 +3,7 @@ class Bora
3
3
  ResolverNotFound = Class.new(StandardError)
4
4
 
5
5
  def load_resolver(name)
6
- resolver_class = name.split("_").reject(&:empty?).map { |s| s.capitalize }.join
6
+ resolver_class = name.split('_').reject(&:empty?).map(&:capitalize).join
7
7
  class_name = "Bora::Resolver::#{resolver_class}"
8
8
  begin
9
9
  resolver_class = Kernel.const_get(class_name)
@@ -14,7 +14,6 @@ class Bora
14
14
  resolver_class
15
15
  end
16
16
 
17
-
18
17
  private
19
18
 
20
19
  def require_resolver_file(name)
@@ -25,7 +24,5 @@ class Bora
25
24
  raise ResolverNotFound, "Could not find resolver for '#{name}'. Expected to find it at '#{require_path}'"
26
25
  end
27
26
  end
28
-
29
-
30
27
  end
31
28
  end
@@ -21,7 +21,6 @@ class Bora
21
21
  owners << 'self'
22
22
  end
23
23
 
24
-
25
24
  ec2 = Aws::EC2::Client.new(region: @stack.region)
26
25
  begin
27
26
  images = ec2.describe_images(
@@ -14,22 +14,22 @@ class Bora
14
14
 
15
15
  def resolve(uri)
16
16
  stack_name = uri.host
17
- section, name = uri.path.split("/").reject(&:empty?)
17
+ section, name = uri.path.split('/').reject(&:empty?)
18
18
  if !stack_name || !section || !name || section != 'outputs'
19
19
  raise InvalidParameter, "Invalid parameter substitution: #{uri}"
20
20
  end
21
21
 
22
- stack_name, uri_region = stack_name.split(".")
22
+ stack_name, uri_region = stack_name.split('.')
23
23
  region = uri_region || @stack.region
24
24
 
25
25
  param_stack = @stack_cache[stack_name] || Bora::Cfn::Stack.new(stack_name, region)
26
- if !param_stack.exists?
26
+ unless param_stack.exists?
27
27
  raise StackDoesNotExist, "Output #{name} not found in stack #{stack_name} as the stack does not exist"
28
28
  end
29
29
 
30
30
  outputs = param_stack.outputs || []
31
31
  matching_output = outputs.find { |output| output.key == name }
32
- if !matching_output
32
+ unless matching_output
33
33
  raise ValueNotFound, "Output #{name} not found in stack #{stack_name}"
34
34
  end
35
35
 
@@ -1,5 +1,6 @@
1
1
  require 'aws-sdk'
2
2
  require 'bora/cfn/stack'
3
+ require 'English'
3
4
 
4
5
  class Bora
5
6
  module Resolver
@@ -11,31 +12,30 @@ class Bora
11
12
  end
12
13
 
13
14
  def resolve(uri)
14
- raise InvalidParameter, "Invalid credstash parameter #{uri}: no credstash key" if !uri.path
15
+ raise InvalidParameter, "Invalid credstash parameter #{uri}: no credstash key" unless uri.path
15
16
  key = uri.path[1..-1]
16
17
  raise InvalidParameter, "Invalid credstash parameter #{uri}: no credstash key" if !key || key.empty?
17
18
  region = resolve_region(uri, @stack)
18
19
  context = parse_key_context(uri)
19
20
  output = `credstash --region #{region} get #{key}#{context}`
20
- exit_code = $?
21
- raise NotFound, output if exit_code.exitstatus != 0
21
+ # exit_code = $?
22
+ raise NotFound, output unless $CHILD_STATUS.success?
22
23
  output.rstrip
23
24
  end
24
25
 
25
-
26
26
  private
27
27
 
28
28
  def resolve_region(uri, stack)
29
29
  region = uri.host || stack.region
30
+ region
30
31
  end
31
32
 
32
33
  def parse_key_context(uri)
33
- return "" if !uri.query
34
- query = URI::decode_www_form(uri.query).to_h
35
- context_params = query.map { |k,v| "#{k}=#{v}" }.join(" ")
34
+ return '' unless uri.query
35
+ query = URI.decode_www_form(uri.query).to_h
36
+ context_params = query.map { |k, v| "#{k}=#{v}" }.join(' ')
36
37
  " #{context_params}"
37
38
  end
38
-
39
39
  end
40
40
  end
41
41
  end