stack_master 1.6.0-x64-mingw32
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 +7 -0
- data/README.md +548 -0
- data/bin/stack_master +17 -0
- data/lib/stack_master.rb +159 -0
- data/lib/stack_master/aws_driver/cloud_formation.rb +41 -0
- data/lib/stack_master/aws_driver/s3.rb +68 -0
- data/lib/stack_master/change_set.rb +109 -0
- data/lib/stack_master/cli.rb +208 -0
- data/lib/stack_master/command.rb +57 -0
- data/lib/stack_master/commands/apply.rb +221 -0
- data/lib/stack_master/commands/delete.rb +53 -0
- data/lib/stack_master/commands/diff.rb +31 -0
- data/lib/stack_master/commands/events.rb +39 -0
- data/lib/stack_master/commands/init.rb +111 -0
- data/lib/stack_master/commands/list_stacks.rb +20 -0
- data/lib/stack_master/commands/outputs.rb +31 -0
- data/lib/stack_master/commands/resources.rb +33 -0
- data/lib/stack_master/commands/status.rb +46 -0
- data/lib/stack_master/commands/terminal_helper.rb +28 -0
- data/lib/stack_master/commands/validate.rb +17 -0
- data/lib/stack_master/config.rb +133 -0
- data/lib/stack_master/ctrl_c.rb +4 -0
- data/lib/stack_master/paged_response_accumulator.rb +29 -0
- data/lib/stack_master/parameter_loader.rb +49 -0
- data/lib/stack_master/parameter_resolver.rb +98 -0
- data/lib/stack_master/parameter_resolvers/ami_finder.rb +36 -0
- data/lib/stack_master/parameter_resolvers/env.rb +18 -0
- data/lib/stack_master/parameter_resolvers/latest_ami.rb +19 -0
- data/lib/stack_master/parameter_resolvers/latest_ami_by_tags.rb +18 -0
- data/lib/stack_master/parameter_resolvers/parameter_store.rb +31 -0
- data/lib/stack_master/parameter_resolvers/secret.rb +52 -0
- data/lib/stack_master/parameter_resolvers/security_group.rb +22 -0
- data/lib/stack_master/parameter_resolvers/sns_topic_name.rb +31 -0
- data/lib/stack_master/parameter_resolvers/stack_output.rb +76 -0
- data/lib/stack_master/prompter.rb +21 -0
- data/lib/stack_master/resolver_array.rb +35 -0
- data/lib/stack_master/security_group_finder.rb +28 -0
- data/lib/stack_master/sns_topic_finder.rb +26 -0
- data/lib/stack_master/sparkle_formation/compile_time/allowed_pattern_validator.rb +35 -0
- data/lib/stack_master/sparkle_formation/compile_time/allowed_values_validator.rb +37 -0
- data/lib/stack_master/sparkle_formation/compile_time/definitions_validator.rb +33 -0
- data/lib/stack_master/sparkle_formation/compile_time/empty_validator.rb +32 -0
- data/lib/stack_master/sparkle_formation/compile_time/max_length_validator.rb +36 -0
- data/lib/stack_master/sparkle_formation/compile_time/max_size_validator.rb +36 -0
- data/lib/stack_master/sparkle_formation/compile_time/min_length_validator.rb +36 -0
- data/lib/stack_master/sparkle_formation/compile_time/min_size_validator.rb +36 -0
- data/lib/stack_master/sparkle_formation/compile_time/number_validator.rb +35 -0
- data/lib/stack_master/sparkle_formation/compile_time/parameters_validator.rb +27 -0
- data/lib/stack_master/sparkle_formation/compile_time/state_builder.rb +32 -0
- data/lib/stack_master/sparkle_formation/compile_time/string_validator.rb +33 -0
- data/lib/stack_master/sparkle_formation/compile_time/value_builder.rb +40 -0
- data/lib/stack_master/sparkle_formation/compile_time/value_validator.rb +40 -0
- data/lib/stack_master/sparkle_formation/compile_time/value_validator_factory.rb +41 -0
- data/lib/stack_master/sparkle_formation/template_file.rb +115 -0
- data/lib/stack_master/stack.rb +105 -0
- data/lib/stack_master/stack_definition.rb +103 -0
- data/lib/stack_master/stack_differ.rb +111 -0
- data/lib/stack_master/stack_events/fetcher.rb +38 -0
- data/lib/stack_master/stack_events/presenter.rb +27 -0
- data/lib/stack_master/stack_events/streamer.rb +68 -0
- data/lib/stack_master/stack_states.rb +34 -0
- data/lib/stack_master/stack_status.rb +61 -0
- data/lib/stack_master/template_compiler.rb +30 -0
- data/lib/stack_master/template_compilers/cfndsl.rb +13 -0
- data/lib/stack_master/template_compilers/json.rb +22 -0
- data/lib/stack_master/template_compilers/sparkle_formation.rb +71 -0
- data/lib/stack_master/template_compilers/yaml.rb +14 -0
- data/lib/stack_master/template_utils.rb +31 -0
- data/lib/stack_master/test_driver/cloud_formation.rb +193 -0
- data/lib/stack_master/test_driver/s3.rb +34 -0
- data/lib/stack_master/testing.rb +9 -0
- data/lib/stack_master/utils.rb +50 -0
- data/lib/stack_master/validator.rb +33 -0
- data/lib/stack_master/version.rb +3 -0
- metadata +457 -0
data/bin/stack_master
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'stack_master'
|
5
|
+
|
6
|
+
if ENV['STUB_AWS'] == 'true'
|
7
|
+
require 'stack_master/testing'
|
8
|
+
end
|
9
|
+
|
10
|
+
trap("SIGINT") { raise StackMaster::CtrlC }
|
11
|
+
|
12
|
+
begin
|
13
|
+
result = StackMaster::CLI.new(ARGV.dup).execute!
|
14
|
+
exit !!result
|
15
|
+
rescue StackMaster::CtrlC
|
16
|
+
StackMaster.stdout.puts "Exiting..."
|
17
|
+
end
|
data/lib/stack_master.rb
ADDED
@@ -0,0 +1,159 @@
|
|
1
|
+
require 'commander'
|
2
|
+
require 'yaml'
|
3
|
+
require "aws-sdk-cloudformation"
|
4
|
+
require "aws-sdk-ec2"
|
5
|
+
require "aws-sdk-s3"
|
6
|
+
require "aws-sdk-sns"
|
7
|
+
require "aws-sdk-ssm"
|
8
|
+
require "colorize"
|
9
|
+
require 'active_support/core_ext/string'
|
10
|
+
require 'multi_json'
|
11
|
+
|
12
|
+
MultiJson.use :json_gem
|
13
|
+
|
14
|
+
module StackMaster
|
15
|
+
extend self
|
16
|
+
|
17
|
+
autoload :Initializable, 'stack_master/utils/initializable'
|
18
|
+
autoload :ChangeSet, 'stack_master/change_set'
|
19
|
+
autoload :CLI, 'stack_master/cli'
|
20
|
+
autoload :CtrlC, 'stack_master/ctrl_c'
|
21
|
+
autoload :Command, 'stack_master/command'
|
22
|
+
autoload :VERSION, 'stack_master/version'
|
23
|
+
autoload :Stack, 'stack_master/stack'
|
24
|
+
autoload :Prompter, 'stack_master/prompter'
|
25
|
+
autoload :StackStates, 'stack_master/stack_states'
|
26
|
+
autoload :StackStatus, 'stack_master/stack_status'
|
27
|
+
autoload :SnsTopicFinder, 'stack_master/sns_topic_finder'
|
28
|
+
autoload :SecurityGroupFinder, 'stack_master/security_group_finder'
|
29
|
+
autoload :ParameterLoader, 'stack_master/parameter_loader'
|
30
|
+
autoload :ParameterResolver, 'stack_master/parameter_resolver'
|
31
|
+
autoload :ResolverArray, 'stack_master/resolver_array'
|
32
|
+
autoload :Resolver, 'stack_master/resolver_array'
|
33
|
+
autoload :Utils, 'stack_master/utils'
|
34
|
+
autoload :TemplateUtils, 'stack_master/template_utils'
|
35
|
+
autoload :Config, 'stack_master/config'
|
36
|
+
autoload :PagedResponseAccumulator, 'stack_master/paged_response_accumulator'
|
37
|
+
autoload :StackDefinition, 'stack_master/stack_definition'
|
38
|
+
autoload :TemplateCompiler, 'stack_master/template_compiler'
|
39
|
+
|
40
|
+
autoload :StackDiffer, 'stack_master/stack_differ'
|
41
|
+
autoload :Validator, 'stack_master/validator'
|
42
|
+
|
43
|
+
require 'stack_master/template_compilers/sparkle_formation'
|
44
|
+
require 'stack_master/template_compilers/json'
|
45
|
+
require 'stack_master/template_compilers/yaml'
|
46
|
+
require 'stack_master/template_compilers/cfndsl'
|
47
|
+
|
48
|
+
module Commands
|
49
|
+
autoload :TerminalHelper, 'stack_master/commands/terminal_helper'
|
50
|
+
autoload :Apply, 'stack_master/commands/apply'
|
51
|
+
autoload :Events, 'stack_master/commands/events'
|
52
|
+
autoload :Outputs, 'stack_master/commands/outputs'
|
53
|
+
autoload :Init, 'stack_master/commands/init'
|
54
|
+
autoload :Diff, 'stack_master/commands/diff'
|
55
|
+
autoload :ListStacks, 'stack_master/commands/list_stacks'
|
56
|
+
autoload :Validate, 'stack_master/commands/validate'
|
57
|
+
autoload :Resources, 'stack_master/commands/resources'
|
58
|
+
autoload :Delete, 'stack_master/commands/delete'
|
59
|
+
autoload :Status, 'stack_master/commands/status'
|
60
|
+
end
|
61
|
+
|
62
|
+
module ParameterResolvers
|
63
|
+
autoload :AmiFinder, 'stack_master/parameter_resolvers/ami_finder'
|
64
|
+
autoload :StackOutput, 'stack_master/parameter_resolvers/stack_output'
|
65
|
+
autoload :Secret, 'stack_master/parameter_resolvers/secret'
|
66
|
+
autoload :SnsTopicName, 'stack_master/parameter_resolvers/sns_topic_name'
|
67
|
+
autoload :SecurityGroup, 'stack_master/parameter_resolvers/security_group'
|
68
|
+
autoload :LatestAmiByTags, 'stack_master/parameter_resolvers/latest_ami_by_tags'
|
69
|
+
autoload :LatestAmi, 'stack_master/parameter_resolvers/latest_ami'
|
70
|
+
autoload :Env, 'stack_master/parameter_resolvers/env'
|
71
|
+
autoload :ParameterStore, 'stack_master/parameter_resolvers/parameter_store'
|
72
|
+
end
|
73
|
+
|
74
|
+
module AwsDriver
|
75
|
+
autoload :CloudFormation, 'stack_master/aws_driver/cloud_formation'
|
76
|
+
autoload :S3, 'stack_master/aws_driver/s3'
|
77
|
+
end
|
78
|
+
|
79
|
+
module TestDriver
|
80
|
+
autoload :CloudFormation, 'stack_master/test_driver/cloud_formation'
|
81
|
+
autoload :S3, 'stack_master/test_driver/s3'
|
82
|
+
end
|
83
|
+
|
84
|
+
module StackEvents
|
85
|
+
autoload :Fetcher, 'stack_master/stack_events/fetcher'
|
86
|
+
autoload :Presenter, 'stack_master/stack_events/presenter'
|
87
|
+
autoload :Streamer, 'stack_master/stack_events/streamer'
|
88
|
+
end
|
89
|
+
|
90
|
+
def interactive?
|
91
|
+
!non_interactive?
|
92
|
+
end
|
93
|
+
|
94
|
+
def non_interactive?
|
95
|
+
@non_interactive
|
96
|
+
end
|
97
|
+
@non_interactive = false
|
98
|
+
|
99
|
+
def non_interactive!
|
100
|
+
@non_interactive = true
|
101
|
+
end
|
102
|
+
|
103
|
+
def debug!
|
104
|
+
@debug = true
|
105
|
+
end
|
106
|
+
@debug = false
|
107
|
+
|
108
|
+
def debug?
|
109
|
+
@debug
|
110
|
+
end
|
111
|
+
|
112
|
+
def debug(message)
|
113
|
+
return unless debug?
|
114
|
+
stderr.puts "[DEBUG] #{message}".colorize(:green)
|
115
|
+
end
|
116
|
+
|
117
|
+
attr_accessor :non_interactive_answer
|
118
|
+
@non_interactive_answer = 'y'
|
119
|
+
|
120
|
+
def base_dir
|
121
|
+
File.expand_path(File.join(File.dirname(__FILE__), ".."))
|
122
|
+
end
|
123
|
+
|
124
|
+
def cloud_formation_driver
|
125
|
+
@cloud_formation_driver ||= AwsDriver::CloudFormation.new
|
126
|
+
end
|
127
|
+
|
128
|
+
def cloud_formation_driver=(value)
|
129
|
+
@cloud_formation_driver = value
|
130
|
+
end
|
131
|
+
|
132
|
+
def s3_driver
|
133
|
+
@s3_driver ||= AwsDriver::S3.new
|
134
|
+
end
|
135
|
+
|
136
|
+
def s3_driver=(value)
|
137
|
+
@s3_driver = value
|
138
|
+
end
|
139
|
+
|
140
|
+
def stdout
|
141
|
+
@stdout || $stdout
|
142
|
+
end
|
143
|
+
|
144
|
+
def stdout=(io)
|
145
|
+
@stdout = io
|
146
|
+
end
|
147
|
+
|
148
|
+
def stdin
|
149
|
+
$stdin
|
150
|
+
end
|
151
|
+
|
152
|
+
def stderr
|
153
|
+
@stderr || $stderr
|
154
|
+
end
|
155
|
+
|
156
|
+
def stderr=(io)
|
157
|
+
@stderr = io
|
158
|
+
end
|
159
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module StackMaster
|
2
|
+
module AwsDriver
|
3
|
+
class CloudFormation
|
4
|
+
extend Forwardable
|
5
|
+
|
6
|
+
def region
|
7
|
+
@region ||= ENV['AWS_REGION'] || Aws.config[:region] || Aws.shared_config.region
|
8
|
+
end
|
9
|
+
|
10
|
+
def set_region(value)
|
11
|
+
if region != value
|
12
|
+
@region = value
|
13
|
+
@cf = nil
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def_delegators :cf, :create_change_set,
|
18
|
+
:describe_change_set,
|
19
|
+
:execute_change_set,
|
20
|
+
:delete_change_set,
|
21
|
+
:delete_stack,
|
22
|
+
:cancel_update_stack,
|
23
|
+
:describe_stack_resources,
|
24
|
+
:get_template,
|
25
|
+
:get_stack_policy,
|
26
|
+
:set_stack_policy,
|
27
|
+
:describe_stack_events,
|
28
|
+
:update_stack,
|
29
|
+
:create_stack,
|
30
|
+
:validate_template,
|
31
|
+
:describe_stacks
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def cf
|
36
|
+
@cf ||= Aws::CloudFormation::Client.new(region: region, retry_limit: 10)
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'digest/md5'
|
2
|
+
|
3
|
+
module StackMaster
|
4
|
+
module AwsDriver
|
5
|
+
class S3ConfigurationError < StandardError; end
|
6
|
+
|
7
|
+
class S3
|
8
|
+
def set_region(region)
|
9
|
+
@region = region
|
10
|
+
@s3 = nil
|
11
|
+
end
|
12
|
+
|
13
|
+
def upload_files(bucket: nil, prefix: nil, region: nil, files: {})
|
14
|
+
raise StackMaster::AwsDriver::S3ConfigurationError, 'A bucket must be specified in order to use S3' unless bucket
|
15
|
+
|
16
|
+
return if files.empty?
|
17
|
+
|
18
|
+
s3 = new_s3_client(region: region)
|
19
|
+
|
20
|
+
current_objects = s3.list_objects(
|
21
|
+
prefix: prefix,
|
22
|
+
bucket: bucket
|
23
|
+
).map(&:contents).flatten.inject({}){|h,obj|
|
24
|
+
h.merge(obj.key => obj)
|
25
|
+
}
|
26
|
+
|
27
|
+
StackMaster.stdout.puts "Uploading files to S3:"
|
28
|
+
|
29
|
+
files.each do |template, file|
|
30
|
+
body = file.fetch(:body)
|
31
|
+
path = file.fetch(:path)
|
32
|
+
object_key = template.dup
|
33
|
+
object_key.prepend("#{prefix}/") if prefix
|
34
|
+
compiled_template_md5 = Digest::MD5.hexdigest(body).to_s
|
35
|
+
s3_md5 = current_objects[object_key] ? current_objects[object_key].etag.gsub("\"", '') : nil
|
36
|
+
|
37
|
+
next if compiled_template_md5 == s3_md5
|
38
|
+
s3_uri = "s3://#{bucket}/#{object_key}"
|
39
|
+
StackMaster.stdout.print "- #{File.basename(path)} => #{s3_uri} "
|
40
|
+
|
41
|
+
s3.put_object(
|
42
|
+
bucket: bucket,
|
43
|
+
key: object_key,
|
44
|
+
body: body,
|
45
|
+
metadata: { md5: compiled_template_md5 }
|
46
|
+
)
|
47
|
+
StackMaster.stdout.puts "done."
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def url(bucket:, prefix:, region:, template:)
|
52
|
+
if region == 'us-east-1'
|
53
|
+
["https://s3.amazonaws.com", bucket, prefix, template].compact.join('/')
|
54
|
+
elsif region.start_with? "cn-"
|
55
|
+
["https://s3.#{region}.amazonaws.com.cn", bucket, prefix, template].compact.join('/')
|
56
|
+
else
|
57
|
+
["https://s3-#{region}.amazonaws.com", bucket, prefix, template].compact.join('/')
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def new_s3_client(region: nil)
|
64
|
+
Aws::S3::Client.new(region: region || @region)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
module StackMaster
|
2
|
+
class ChangeSet
|
3
|
+
END_STATES = [
|
4
|
+
'CREATE_COMPLETE',
|
5
|
+
'DELETE_COMPLETE',
|
6
|
+
'FAILED'
|
7
|
+
]
|
8
|
+
|
9
|
+
def self.generate_change_set_name(stack_name)
|
10
|
+
stack_name + '-StackMaster' + Time.now.strftime('%Y-%m-%d-%H%M-%s')
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.create(create_options)
|
14
|
+
cf = StackMaster.cloud_formation_driver
|
15
|
+
change_set_name = generate_change_set_name(create_options.fetch(:stack_name))
|
16
|
+
change_set_id = cf.create_change_set(create_options.merge(change_set_name: change_set_name)).id
|
17
|
+
find(change_set_id)
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.find(id)
|
21
|
+
begin
|
22
|
+
response = PagedResponseAccumulator.call(cf, :describe_change_set, { change_set_name: id }, :changes)
|
23
|
+
end while !END_STATES.include?(response.status)
|
24
|
+
new(response)
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.delete(id)
|
28
|
+
cf.delete_change_set(change_set_name: id)
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.execute(id, stack_name)
|
32
|
+
cf.execute_change_set(change_set_name: id,
|
33
|
+
stack_name: stack_name)
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.cf
|
37
|
+
StackMaster.cloud_formation_driver
|
38
|
+
end
|
39
|
+
|
40
|
+
def initialize(describe_change_set_response)
|
41
|
+
@response = describe_change_set_response
|
42
|
+
end
|
43
|
+
|
44
|
+
def display(io)
|
45
|
+
io.puts <<-EOL
|
46
|
+
|
47
|
+
========================================
|
48
|
+
Proposed change set:
|
49
|
+
EOL
|
50
|
+
@response.changes.each do |change|
|
51
|
+
display_resource_change(io, change.resource_change)
|
52
|
+
end
|
53
|
+
io.puts "========================================"
|
54
|
+
end
|
55
|
+
|
56
|
+
def failed?
|
57
|
+
@response.status == 'FAILED'
|
58
|
+
end
|
59
|
+
|
60
|
+
def status_reason
|
61
|
+
@response.status_reason
|
62
|
+
end
|
63
|
+
|
64
|
+
def id
|
65
|
+
@response.change_set_id
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
def display_resource_change(io, resource_change)
|
71
|
+
action_name = if resource_change.replacement == 'True'
|
72
|
+
'Replace'
|
73
|
+
else
|
74
|
+
resource_change.action
|
75
|
+
end
|
76
|
+
message = "#{action_name} #{resource_change.resource_type} #{resource_change.logical_resource_id}"
|
77
|
+
color = action_color(action_name)
|
78
|
+
io.puts message.colorize(color)
|
79
|
+
resource_change.details.each do |detail|
|
80
|
+
display_resource_change_detail(io, action_name, color, detail)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def display_resource_change_detail(io, action_name, color, detail)
|
85
|
+
target_name = [detail.target.attribute, detail.target.name].compact.join('.')
|
86
|
+
detail_messages = [target_name]
|
87
|
+
if action_name == 'Replace'
|
88
|
+
detail_messages << "#{detail.target.requires_recreation} requires recreation"
|
89
|
+
end
|
90
|
+
triggered_by = [detail.change_source, detail.causing_entity].compact.join('.')
|
91
|
+
if detail.evaluation != 'Static'
|
92
|
+
triggered_by << "(#{detail.evaluation})"
|
93
|
+
end
|
94
|
+
detail_messages << "Triggered by: #{triggered_by}"
|
95
|
+
io.puts "- #{detail_messages.join('. ')}. ".colorize(color)
|
96
|
+
end
|
97
|
+
|
98
|
+
def action_color(action_name)
|
99
|
+
case action_name
|
100
|
+
when 'Add'
|
101
|
+
:green
|
102
|
+
when 'Modify'
|
103
|
+
:yellow
|
104
|
+
when 'Remove', 'Replace'
|
105
|
+
:red
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
@@ -0,0 +1,208 @@
|
|
1
|
+
require 'commander'
|
2
|
+
require 'table_print'
|
3
|
+
|
4
|
+
module StackMaster
|
5
|
+
class CLI
|
6
|
+
include Commander::Methods
|
7
|
+
|
8
|
+
def initialize(argv, stdin=STDIN, stdout=STDOUT, stderr=STDERR, kernel=Kernel)
|
9
|
+
@argv, @stdin, @stdout, @stderr, @kernel = argv, stdin, stdout, stderr, kernel
|
10
|
+
Commander::Runner.instance_variable_set('@singleton', Commander::Runner.new(argv))
|
11
|
+
StackMaster.stdout = @stdout
|
12
|
+
StackMaster.stderr = @stderr
|
13
|
+
TablePrint::Config.io = StackMaster.stdout
|
14
|
+
end
|
15
|
+
|
16
|
+
def default_config_file
|
17
|
+
"stack_master.yml"
|
18
|
+
end
|
19
|
+
|
20
|
+
def execute!
|
21
|
+
program :name, 'StackMaster'
|
22
|
+
program :version, StackMaster::VERSION
|
23
|
+
program :description, 'AWS Stack Management'
|
24
|
+
|
25
|
+
global_option '-c', '--config FILE', String, 'Config file to use'
|
26
|
+
global_option '--changed', 'filter stack selection to only ones that have changed'
|
27
|
+
global_option '-y', '--yes', 'Run in non-interactive mode answering yes to any prompts' do
|
28
|
+
StackMaster.non_interactive!
|
29
|
+
StackMaster.non_interactive_answer = 'y'
|
30
|
+
end
|
31
|
+
global_option '-n', '--no', 'Run in non-interactive mode answering no to any prompts' do
|
32
|
+
StackMaster.non_interactive!
|
33
|
+
StackMaster.non_interactive_answer = 'n'
|
34
|
+
end
|
35
|
+
global_option '-d', '--debug', 'Run in debug mode' do
|
36
|
+
StackMaster.debug!
|
37
|
+
end
|
38
|
+
|
39
|
+
command :apply do |c|
|
40
|
+
c.syntax = 'stack_master apply [region_or_alias] [stack_name]'
|
41
|
+
c.summary = 'Creates or updates a stack'
|
42
|
+
c.description = "Creates or updates a stack. Shows a diff of the proposed stack's template and parameters. Tails stack events until CloudFormation has completed."
|
43
|
+
c.example 'update a stack named myapp-vpc in us-east-1', 'stack_master apply us-east-1 myapp-vpc'
|
44
|
+
c.option '--on-failure ACTION', String, "Action to take on CREATE_FAILURE. Valid Values: [ DO_NOTHING | ROLLBACK | DELETE ]. Default: ROLLBACK\nNote: You cannot use this option with Serverless Application Model (SAM) templates."
|
45
|
+
c.action do |args, options|
|
46
|
+
options.defaults config: default_config_file
|
47
|
+
execute_stacks_command(StackMaster::Commands::Apply, args, options)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
command :outputs do |c|
|
52
|
+
c.syntax = 'stack_master outputs [region_or_alias] [stack_name]'
|
53
|
+
c.summary = 'Displays outputs for a stack'
|
54
|
+
c.description = "Displays outputs for a stack"
|
55
|
+
c.action do |args, options|
|
56
|
+
options.defaults config: default_config_file
|
57
|
+
execute_stacks_command(StackMaster::Commands::Outputs, args, options)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
command :init do |c|
|
62
|
+
c.syntax = 'stack_master init [region_or_alias] [stack_name]'
|
63
|
+
c.summary = 'Initialises the expected directory structure and stack_master.yml file'
|
64
|
+
c.description = 'Initialises the expected directory structure and stack_master.yml file'
|
65
|
+
c.option('--overwrite', 'Overwrite existing files')
|
66
|
+
c.action do |args, options|
|
67
|
+
options.defaults config: default_config_file
|
68
|
+
unless args.size == 2
|
69
|
+
say "Invalid arguments. stack_master init [region] [stack_name]"
|
70
|
+
else
|
71
|
+
StackMaster::Commands::Init.perform(options.overwrite, *args)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
command :diff do |c|
|
77
|
+
c.syntax = 'stack_master diff [region_or_alias] [stack_name]'
|
78
|
+
c.summary = "Shows a diff of the proposed stack's template and parameters"
|
79
|
+
c.description = "Shows a diff of the proposed stack's template and parameters"
|
80
|
+
c.example 'diff a stack named myapp-vpc in us-east-1', 'stack_master diff us-east-1 myapp-vpc'
|
81
|
+
c.action do |args, options|
|
82
|
+
options.defaults config: default_config_file
|
83
|
+
execute_stacks_command(StackMaster::Commands::Diff, args, options)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
command :events do |c|
|
88
|
+
c.syntax = 'stack_master events [region_or_alias] [stack_name]'
|
89
|
+
c.summary = "Shows events for a stack"
|
90
|
+
c.description = "Shows events for a stack"
|
91
|
+
c.example 'show events for myapp-vpc in us-east-1', 'stack_master events us-east-1 myapp-vpc'
|
92
|
+
c.option '--number Integer', Integer, 'Number of recent events to show'
|
93
|
+
c.option '--all', 'Show all events'
|
94
|
+
c.option '--tail', 'Tail events'
|
95
|
+
c.action do |args, options|
|
96
|
+
options.defaults config: default_config_file
|
97
|
+
execute_stacks_command(StackMaster::Commands::Events, args, options)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
command :resources do |c|
|
102
|
+
c.syntax = 'stack_master resources [region] [stack_name]'
|
103
|
+
c.summary = "Shows stack resources"
|
104
|
+
c.description = "Shows stack resources"
|
105
|
+
c.action do |args, options|
|
106
|
+
options.defaults config: default_config_file
|
107
|
+
execute_stacks_command(StackMaster::Commands::Resources, args, options)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
command :list do |c|
|
112
|
+
c.syntax = 'stack_master list'
|
113
|
+
c.summary = 'List stack definitions'
|
114
|
+
c.description = 'List stack definitions'
|
115
|
+
c.action do |args, options|
|
116
|
+
options.defaults config: default_config_file
|
117
|
+
say "Invalid arguments." if args.size > 0
|
118
|
+
config = load_config(options.config)
|
119
|
+
StackMaster::Commands::ListStacks.perform(config)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
command :validate do |c|
|
124
|
+
c.syntax = 'stack_master validate [region_or_alias] [stack_name]'
|
125
|
+
c.summary = 'Validate a template'
|
126
|
+
c.description = 'Validate a template'
|
127
|
+
c.example 'validate a stack named myapp-vpc in us-east-1', 'stack_master validate us-east-1 myapp-vpc'
|
128
|
+
c.action do |args, options|
|
129
|
+
options.defaults config: default_config_file
|
130
|
+
execute_stacks_command(StackMaster::Commands::Validate, args, options)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
command :status do |c|
|
135
|
+
c.syntax = 'stack_master status'
|
136
|
+
c.summary = 'Check the current status stacks.'
|
137
|
+
c.description = 'Checks the status of all stacks defined in the stack_master.yml file. Warning this operation can be somewhat slow.'
|
138
|
+
c.example 'description', 'Check the status of all stack definitions'
|
139
|
+
c.action do |args, options|
|
140
|
+
options.defaults config: default_config_file
|
141
|
+
say "Invalid arguments. stack_master status" and return unless args.size == 0
|
142
|
+
config = load_config(options.config)
|
143
|
+
StackMaster::Commands::Status.perform(config)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
command :delete do |c|
|
148
|
+
c.syntax = 'stack_master delete [region] [stack_name]'
|
149
|
+
c.summary = 'Delete an existing stack'
|
150
|
+
c.description = 'Deletes a stack. The stack does not necessarily have to appear in the stack_master.yml file.'
|
151
|
+
c.example 'description', 'Delete a stack'
|
152
|
+
c.action do |args, options|
|
153
|
+
options.default config: default_config_file
|
154
|
+
unless args.size == 2
|
155
|
+
say "Invalid arguments. stack_master delete [region] [stack_name]"
|
156
|
+
return
|
157
|
+
end
|
158
|
+
|
159
|
+
# Because delete can work without a stack_master.yml
|
160
|
+
if options.config and File.file?(options.config)
|
161
|
+
config = load_config(options.config)
|
162
|
+
region = Utils.underscore_to_hyphen(config.unalias_region(args[0]))
|
163
|
+
else
|
164
|
+
region = args[0]
|
165
|
+
end
|
166
|
+
|
167
|
+
StackMaster.cloud_formation_driver.set_region(region)
|
168
|
+
StackMaster::Commands::Delete.perform(region, args[1])
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
run!
|
173
|
+
end
|
174
|
+
|
175
|
+
def load_config(file)
|
176
|
+
stack_file = file || default_config_file
|
177
|
+
StackMaster::Config.load!(stack_file)
|
178
|
+
rescue Errno::ENOENT => e
|
179
|
+
say "Failed to load config file #{stack_file}"
|
180
|
+
exit 1
|
181
|
+
end
|
182
|
+
|
183
|
+
def execute_stacks_command(command, args, options)
|
184
|
+
command_results = []
|
185
|
+
config = load_config(options.config)
|
186
|
+
args = [nil, nil] if args.size == 0
|
187
|
+
args.each_slice(2) do |aliased_region, stack_name|
|
188
|
+
region = Utils.underscore_to_hyphen(config.unalias_region(aliased_region))
|
189
|
+
stack_name = Utils.underscore_to_hyphen(stack_name)
|
190
|
+
stack_definitions = config.filter(region, stack_name)
|
191
|
+
if stack_definitions.empty?
|
192
|
+
StackMaster.stdout.puts "Could not find stack definition #{stack_name} in region #{region}"
|
193
|
+
end
|
194
|
+
stack_definitions = stack_definitions.select do |stack_definition|
|
195
|
+
StackStatus.new(config, stack_definition).changed?
|
196
|
+
end if options.changed
|
197
|
+
stack_definitions.each do |stack_definition|
|
198
|
+
StackMaster.cloud_formation_driver.set_region(stack_definition.region)
|
199
|
+
StackMaster.stdout.puts "Executing #{command.command_name} on #{stack_definition.stack_name} in #{stack_definition.region}"
|
200
|
+
command_results.push command.perform(config, stack_definition, options).success?
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
# Return success/failure
|
205
|
+
command_results.all?
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|