bora 1.6.0 → 1.7.0

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