stack_master 0.6.0 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +22 -0
  3. data/features/apply.feature +1 -1
  4. data/features/apply_with_s3.feature +106 -0
  5. data/features/step_definitions/stack_steps.rb +5 -0
  6. data/features/support/env.rb +1 -0
  7. data/lib/stack_master.rb +78 -58
  8. data/lib/stack_master/aws_driver/s3.rb +66 -0
  9. data/lib/stack_master/cli.rb +1 -0
  10. data/lib/stack_master/commands/apply.rb +52 -5
  11. data/lib/stack_master/commands/init.rb +2 -0
  12. data/lib/stack_master/commands/list_stacks.rb +2 -0
  13. data/lib/stack_master/commands/outputs.rb +2 -0
  14. data/lib/stack_master/commands/status.rb +3 -0
  15. data/lib/stack_master/parameter_resolver.rb +1 -1
  16. data/lib/stack_master/parameter_resolvers/secret.rb +2 -0
  17. data/lib/stack_master/stack.rb +20 -13
  18. data/lib/stack_master/stack_definition.rb +63 -13
  19. data/lib/stack_master/stack_differ.rb +2 -0
  20. data/lib/stack_master/stack_events/fetcher.rb +1 -1
  21. data/lib/stack_master/stack_events/presenter.rb +1 -1
  22. data/lib/stack_master/stack_events/streamer.rb +1 -1
  23. data/lib/stack_master/template_compiler.rb +3 -1
  24. data/lib/stack_master/template_compilers/cfndsl.rb +4 -2
  25. data/lib/stack_master/template_compilers/json.rb +4 -0
  26. data/lib/stack_master/template_compilers/sparkle_formation.rb +5 -0
  27. data/lib/stack_master/template_compilers/yaml.rb +4 -0
  28. data/lib/stack_master/test_driver/cloud_formation.rb +50 -36
  29. data/lib/stack_master/test_driver/s3.rb +34 -0
  30. data/lib/stack_master/testing.rb +1 -0
  31. data/lib/stack_master/utils.rb +19 -0
  32. data/lib/stack_master/version.rb +1 -1
  33. data/spec/fixtures/stack_master.yml +4 -1
  34. data/spec/stack_master/aws_driver/s3_spec.rb +130 -0
  35. data/spec/stack_master/commands/apply_spec.rb +26 -0
  36. data/spec/stack_master/config_spec.rb +8 -1
  37. data/spec/stack_master/parameter_resolver_spec.rb +5 -0
  38. data/spec/stack_master/sparkle_formation/user_data_file_spec.rb +2 -0
  39. data/spec/stack_master/stack_events/presenter_spec.rb +1 -1
  40. data/spec/stack_master/stack_spec.rb +23 -5
  41. data/spec/stack_master/template_compiler_spec.rb +1 -0
  42. data/spec/stack_master/template_compilers/cfndsl_spec.rb +2 -0
  43. data/spec/stack_master/test_driver/s3_spec.rb +17 -0
  44. data/stack_master.gemspec +0 -1
  45. metadata +10 -16
@@ -1,3 +1,5 @@
1
+ require "erb"
2
+
1
3
  module StackMaster
2
4
  module Commands
3
5
  class Init
@@ -1,3 +1,5 @@
1
+ require 'table_print'
2
+
1
3
  module StackMaster
2
4
  module Commands
3
5
  class ListStacks
@@ -1,3 +1,5 @@
1
+ require 'table_print'
2
+
1
3
  module StackMaster
2
4
  module Commands
3
5
  class Outputs
@@ -1,3 +1,6 @@
1
+ require 'table_print'
2
+ require 'ruby-progressbar'
3
+
1
4
  module StackMaster
2
5
  module Commands
3
6
  class Status
@@ -42,7 +42,7 @@ module StackMaster
42
42
  end
43
43
 
44
44
  def resolve_parameter_value(key, parameter_value)
45
- return parameter_value.to_s if Numeric === parameter_value
45
+ return parameter_value.to_s if Numeric === parameter_value || parameter_value == true || parameter_value == false
46
46
  return parameter_value.join(',') if Array === parameter_value
47
47
  return parameter_value unless Hash === parameter_value
48
48
  validate_parameter_value!(key, parameter_value)
@@ -1,3 +1,5 @@
1
+ require 'dotgpg'
2
+
1
3
  module StackMaster
2
4
  module ParameterResolvers
3
5
  class Secret < Resolver
@@ -1,19 +1,21 @@
1
1
  module StackMaster
2
2
  class Stack
3
3
  MAX_TEMPLATE_SIZE = 51200
4
+ MAX_S3_TEMPLATE_SIZE = 460800
4
5
 
5
- include Virtus.model
6
+ attr_reader :stack_name,
7
+ :region,
8
+ :stack_id,
9
+ :stack_status,
10
+ :parameters,
11
+ :template_body,
12
+ :notification_arns,
13
+ :outputs,
14
+ :stack_policy_body,
15
+ :tags,
16
+ :files
6
17
 
7
- attribute :stack_name, String
8
- attribute :region, String
9
- attribute :stack_id, String
10
- attribute :stack_status, String
11
- attribute :parameters, Hash
12
- attribute :template_body, String
13
- attribute :notification_arns, Array[String]
14
- attribute :outputs, Array
15
- attribute :stack_policy_body, String
16
- attribute :tags, Hash
18
+ include Utils::Initializable
17
19
 
18
20
  def template_hash
19
21
  if template_body
@@ -87,8 +89,13 @@ module StackMaster
87
89
  stack_policy_body: stack_policy_body)
88
90
  end
89
91
 
90
- def too_big?
91
- maybe_compressed_template_body.size > MAX_TEMPLATE_SIZE
92
+ def max_template_size(use_s3)
93
+ return MAX_S3_TEMPLATE_SIZE if use_s3
94
+ MAX_TEMPLATE_SIZE
95
+ end
96
+
97
+ def too_big?(use_s3 = false)
98
+ maybe_compressed_template_body.size > max_template_size(use_s3)
92
99
  end
93
100
 
94
101
  def aws_parameters
@@ -1,34 +1,84 @@
1
1
  module StackMaster
2
2
  class StackDefinition
3
- include Virtus.value_object(strict: true, required: false)
3
+ attr_accessor :region,
4
+ :stack_name,
5
+ :template,
6
+ :tags,
7
+ :notification_arns,
8
+ :base_dir,
9
+ :secret_file,
10
+ :stack_policy_file,
11
+ :additional_parameter_lookup_dirs,
12
+ :s3,
13
+ :files
4
14
 
5
- values do
6
- attribute :region, String
7
- attribute :stack_name, String
8
- attribute :template, String
9
- attribute :tags, Hash
10
- attribute :notification_arns, Array[String]
11
- attribute :base_dir, String
12
- attribute :secret_file, String
13
- attribute :stack_policy_file, String
14
- attribute :additional_parameter_lookup_dirs, Array[String]
15
+ include Utils::Initializable
16
+
17
+ def initialize(attributes = {})
18
+ @additional_parameter_lookup_dirs = []
19
+ @notification_arns = []
20
+ @s3 = {}
21
+ @files = []
22
+ super
23
+ end
24
+
25
+ def ==(other)
26
+ self.class === other &&
27
+ @region == other.region &&
28
+ @stack_name == other.stack_name &&
29
+ @template == other.template &&
30
+ @tags == other.tags &&
31
+ @notification_arns == other.notification_arns &&
32
+ @base_dir == other.base_dir &&
33
+ @secret_file == other.secret_file &&
34
+ @stack_policy_file == other.stack_policy_file &&
35
+ @additional_parameter_lookup_dirs == other.additional_parameter_lookup_dirs &&
36
+ @s3 == other.s3
37
+ end
38
+
39
+ def template_dir
40
+ File.join(base_dir, 'templates')
15
41
  end
16
42
 
17
43
  def template_file_path
18
- File.join(base_dir, 'templates', template)
44
+ File.join(template_dir, template)
45
+ end
46
+
47
+ def files_dir
48
+ File.join(base_dir, 'files')
49
+ end
50
+
51
+ def s3_files
52
+ files.inject({}) do |hash, file|
53
+ path = File.join(files_dir, file)
54
+ hash[file] = {
55
+ path: path,
56
+ body: File.read(path)
57
+ }
58
+ hash
59
+ end
60
+ end
61
+
62
+ def s3_template_file_name
63
+ Utils.change_extension(template, 'json')
19
64
  end
20
65
 
21
66
  def parameter_files
22
- [ default_parameter_file_path, region_parameter_file_path ] + additional_parameter_lookup_file_paths
67
+ [ default_parameter_file_path, region_parameter_file_path, additional_parameter_lookup_file_paths ].flatten.compact
23
68
  end
24
69
 
25
70
  def stack_policy_file_path
26
71
  File.join(base_dir, 'policies', stack_policy_file) if stack_policy_file
27
72
  end
28
73
 
74
+ def s3_configured?
75
+ !s3.nil?
76
+ end
77
+
29
78
  private
30
79
 
31
80
  def additional_parameter_lookup_file_paths
81
+ return unless additional_parameter_lookup_dirs
32
82
  additional_parameter_lookup_dirs.map do |a|
33
83
  File.join(base_dir, 'parameters', a, "#{underscored_stack_name}.yml")
34
84
  end
@@ -1,3 +1,5 @@
1
+ require "diffy"
2
+
1
3
  module StackMaster
2
4
  class StackDiffer
3
5
  def initialize(proposed_stack, current_stack)
@@ -1,5 +1,5 @@
1
1
  module StackMaster
2
- class StackEvents
2
+ module StackEvents
3
3
  class Fetcher
4
4
  def self.fetch(*args)
5
5
  new(*args).fetch
@@ -1,5 +1,5 @@
1
1
  module StackMaster
2
- class StackEvents
2
+ module StackEvents
3
3
  class Presenter
4
4
  def self.print_event(io, event)
5
5
  new(io).print_event(event)
@@ -1,5 +1,5 @@
1
1
  module StackMaster
2
- class StackEvents
2
+ module StackEvents
3
3
  class Streamer
4
4
  def self.stream(*args, &block)
5
5
  new(*args, &block).stream
@@ -3,7 +3,9 @@ module StackMaster
3
3
  TemplateCompilationFailed = Class.new(RuntimeError)
4
4
 
5
5
  def self.compile(config, template_file_path)
6
- template_compiler_for_file(template_file_path, config).compile(template_file_path)
6
+ compiler = template_compiler_for_file(template_file_path, config)
7
+ compiler.require_dependencies
8
+ compiler.compile(template_file_path)
7
9
  rescue
8
10
  raise TemplateCompilationFailed.new("Failed to compile #{template_file_path}.")
9
11
  end
@@ -1,9 +1,11 @@
1
1
  module StackMaster::TemplateCompilers
2
2
  class Cfndsl
3
+ def self.require_dependencies
4
+ require 'cfndsl'
5
+ end
3
6
 
4
7
  def self.compile(template_file_path)
5
- require 'cfndsl'
6
- CfnDsl.eval_file_with_extras(template_file_path).to_json
8
+ ::CfnDsl.eval_file_with_extras(template_file_path).to_json
7
9
  end
8
10
 
9
11
  StackMaster::TemplateCompiler.register(:cfndsl, self)
@@ -3,6 +3,10 @@ module StackMaster::TemplateCompilers
3
3
  MAX_TEMPLATE_SIZE = 51200
4
4
  private_constant :MAX_TEMPLATE_SIZE
5
5
 
6
+ def self.require_dependencies
7
+ require 'json'
8
+ end
9
+
6
10
  def self.compile(template_file_path)
7
11
  template_body = File.read(template_file_path)
8
12
  if template_body.size > MAX_TEMPLATE_SIZE
@@ -1,5 +1,10 @@
1
1
  module StackMaster::TemplateCompilers
2
2
  class SparkleFormation
3
+ def self.require_dependencies
4
+ require 'sparkle_formation'
5
+ require 'stack_master/sparkle_formation/user_data_file'
6
+ end
7
+
3
8
  def self.compile(template_file_path)
4
9
  ::SparkleFormation.sparkle_path = File.dirname(template_file_path)
5
10
  JSON.pretty_generate(::SparkleFormation.compile(template_file_path))
@@ -1,5 +1,9 @@
1
1
  module StackMaster::TemplateCompilers
2
2
  class Yaml
3
+ def self.require_dependencies
4
+ require 'yaml'
5
+ require 'json'
6
+ end
3
7
 
4
8
  def self.compile(template_file_path)
5
9
  template_body = File.read(template_file_path)
@@ -1,48 +1,62 @@
1
1
  module StackMaster
2
2
  module TestDriver
3
3
  class Stack
4
- include Virtus.model
5
- attribute :stack_id, String
6
- attribute :stack_name, String
7
- attribute :description, String
8
- attribute :parameters, Array[OpenStruct]
9
- attribute :creation_time, String
10
- attribute :last_update_time, String
11
- attribute :stack_status, String
12
- attribute :stack_status_reason, String
13
- attribute :disable_rollback, String
14
- attribute :notification_arns, Array
15
- attribute :timeout_in_minutes, Integer
16
- attribute :capabilities, Array
17
- attribute :outputs, Array[OpenStruct]
18
- attribute :tags, Array[OpenStruct]
4
+ attr_reader :stack_id,
5
+ :stack_name,
6
+ :description,
7
+ :parameters,
8
+ :creation_time,
9
+ :last_update_time,
10
+ :stack_status,
11
+ :stack_status_reason,
12
+ :disable_rollback,
13
+ :notification_arns,
14
+ :timeout_in_minutes,
15
+ :capabilities,
16
+ :outputs,
17
+ :tags
18
+
19
+ include Utils::Initializable
20
+
21
+ def parameters
22
+ @parameters.map do |hash|
23
+ OpenStruct.new(parameter_key: hash[:parameter_key],
24
+ parameter_value: hash[:parameter_value])
25
+ end
26
+ end
19
27
  end
20
28
 
21
29
  class StackEvent
22
- include Virtus.model
23
- attribute :stack_id, String
24
- attribute :event_id, String
25
- attribute :stack_name, String
26
- attribute :logical_resource_id, String
27
- attribute :physical_resource_id, String
28
- attribute :resource_type, String
29
- attribute :timestamp, Time
30
- attribute :resource_status, String
31
- attribute :resource_status_reason, String
32
- attribute :resource_properties, String
30
+ attr_reader :stack_id,
31
+ :event_id,
32
+ :stack_name,
33
+ :logical_resource_id,
34
+ :physical_resource_id,
35
+ :resource_type,
36
+ :timestamp,
37
+ :resource_status,
38
+ :resource_status_reason,
39
+ :resource_properties
40
+
41
+ include Utils::Initializable
42
+
43
+ def timestamp
44
+ Time.parse(@timestamp) if @timestamp
45
+ end
33
46
  end
34
47
 
35
48
  class StackResource
36
- include Virtus.model
37
- attribute :stack_name, String
38
- attribute :stack_id, String
39
- attribute :logical_resource_id, String
40
- attribute :physical_resource_id, String
41
- attribute :resource_type, String
42
- attribute :timestamp, Time
43
- attribute :resource_status, String
44
- attribute :resource_status_reason, String
45
- attribute :description, String
49
+ attr_reader :stack_name,
50
+ :stack_id,
51
+ :logical_resource_id,
52
+ :physical_resource_id,
53
+ :resource_type,
54
+ :timestamp,
55
+ :resource_status,
56
+ :resource_status_reason,
57
+ :description
58
+
59
+ include Utils::Initializable
46
60
  end
47
61
 
48
62
  class CloudFormation
@@ -0,0 +1,34 @@
1
+ module StackMaster
2
+ module TestDriver
3
+ class S3
4
+ def initialize
5
+ reset
6
+ end
7
+
8
+ def set_region(_)
9
+ end
10
+
11
+ def reset
12
+ @files = Hash.new { |hash, key| hash[key] = Hash.new }
13
+ end
14
+
15
+ def upload_files(bucket: nil, prefix: nil, region: nil, files: {})
16
+ return if files.empty?
17
+
18
+ files.each do |template, file|
19
+ object_key = [prefix, template].compact.join('/')
20
+ @files[bucket][object_key] = file[:body]
21
+ end
22
+ end
23
+
24
+ def url(bucket:, prefix:, region:, template:)
25
+ ["https://s3-#{region}.amazonaws.com", bucket, prefix, template].compact.join('/')
26
+ end
27
+
28
+ # test only method
29
+ def find_file(bucket:, object_key:)
30
+ @files[bucket][object_key]
31
+ end
32
+ end
33
+ end
34
+ end
@@ -5,4 +5,5 @@ Aws.config[:stub_responses] = true
5
5
  require 'stack_master/test_driver/cloud_formation'
6
6
 
7
7
  StackMaster.cloud_formation_driver = StackMaster::TestDriver::CloudFormation.new
8
+ StackMaster.s3_driver = StackMaster::TestDriver::S3.new
8
9
  StackMaster.non_interactive!
@@ -1,7 +1,26 @@
1
1
  module StackMaster
2
2
  module Utils
3
+ module Initializable
4
+ def initialize(attributes = {})
5
+ self.attributes = attributes
6
+ end
7
+
8
+ def attributes=(attributes)
9
+ attributes.each do |k, v|
10
+ instance_variable_set("@#{k}", v)
11
+ end
12
+ end
13
+ end
14
+
3
15
  extend self
4
16
 
17
+ def change_extension(file_name, extension)
18
+ [
19
+ File.basename(file_name, '.*'),
20
+ extension
21
+ ].join('.')
22
+ end
23
+
5
24
  def hash_to_aws_parameters(params)
6
25
  params.inject([]) do |params, (key, value)|
7
26
  params << { parameter_key: key, parameter_value: value }