stack_master 0.0.1 → 0.0.2

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
  SHA1:
3
- metadata.gz: be7dbbbfb5ac18598037c53174655253fef33f73
4
- data.tar.gz: e1b2cb9dd3846993f497dc51a606053fc7670907
3
+ metadata.gz: e2c8551f813ec21a55926c685bf281cf9aae2f33
4
+ data.tar.gz: eabcb88f9bc07a017659ebac349e5a476aca605c
5
5
  SHA512:
6
- metadata.gz: 5fea5f38e52302091b2ab880cb59ca68ad9e60547a8f09b7eceb52611aa23475b4c3131fa62b9074580264315f7a75c0796c9fe9bebb1fd535c63c11ebe7d130
7
- data.tar.gz: 4442a6772b1a8efd992660b9ba720f73208935a1bb0f5760a5e4b2b8ea5d4a1c3675964f4f4c3288d968bf45d3c752c4b05fba49f8487894f995e8cfc4537dbb
6
+ metadata.gz: 2b3242170d4615b24c88b85f1bf948bd69c00eacb83bafb6120dd37b247928648e4f9f0890e0ba3516b4e5e10eaa7bfacb5d1806bc7c3771a53bd5d02a7cd187
7
+ data.tar.gz: 0466d851277d7ec84ff4d1d1ed619f078a08bd0da8de70aad2c5e020537faa99a2a7ab6fa6c5c7229504a9455b5d2db87b140e7438707bfda86d4f21ebda73dd
data/README.md CHANGED
@@ -160,6 +160,15 @@ notification_topic:
160
160
  sns_topic: PagerDuty
161
161
  ```
162
162
 
163
+ ### Latest AMI by Tag
164
+
165
+ Looks up the latest AMI ID by a given set of tags.
166
+
167
+ ```yaml
168
+ web_ami:
169
+ latest_ami_by_tags: role=web,application=myapp
170
+ ```
171
+
163
172
  ## Commands
164
173
 
165
174
  ```bash
@@ -172,7 +181,7 @@ stack_master delete [region-or-alias] [stack-name] # Delete a stack
172
181
  stack_master events [region-or-alias] [stack-name] # Display events for a stack
173
182
  stack_master outputs [region-or-alias] [stack-name] # Display outputs for a stack
174
183
  stack_master resources [region-or-alias] [stack-name] # Display outputs for a stack
175
- stack_master status # Displays the status of each stacks
184
+ stack_master status # Displays the status of each stack
176
185
  ```
177
186
 
178
187
  ## Applying updates
@@ -80,7 +80,7 @@ Feature: Apply command
80
80
  | + "Vpc": { |
81
81
  | Parameters diff: |
82
82
  | KeyName: my-key |
83
- | 2020-10-29 00:00:00 +1100 myapp-vpc AWS::CloudFormation::Stack CREATE_COMPLETE |
83
+ And the output should match /2020-10-29 00:00:00 \+[0-9]{4} myapp-vpc AWS::CloudFormation::Stack CREATE_COMPLETE/
84
84
  Then the exit status should be 0
85
85
 
86
86
  Scenario: Run apply and don't create the stack
@@ -94,8 +94,7 @@ Feature: Apply command
94
94
  | Parameters diff: |
95
95
  | KeyName: my-key |
96
96
  | aborted |
97
- And the output should not contain all of these lines:
98
- | 2020-10-29 00:00:00 +1100 myapp-vpc AWS::CloudFormation::Stack CREATE_COMPLETE |
97
+ And the output should not match /2020-10-29 00:00:00 \+[0-9]{4} myapp-vpc AWS::CloudFormation::Stack CREATE_COMPLETE/
99
98
  Then the exit status should be 0
100
99
 
101
100
  Scenario: Run apply on an existing stack
@@ -171,7 +170,7 @@ Feature: Apply command
171
170
  | + "TestSg": { |
172
171
  | Parameters diff: |
173
172
  | VpcId: vpc-xxxxxx |
174
- | 2020-10-29 00:00:00 +1100 myapp-web AWS::CloudFormation::Stack CREATE_COMPLETE |
173
+ And the output should match /2020-10-29 00:00:00 \+[0-9]{4} myapp-web AWS::CloudFormation::Stack CREATE_COMPLETE/
175
174
  Then the exit status should be 0
176
175
 
177
176
  Scenario: Create a stack with a notification ARN and a stack update policy
@@ -16,8 +16,7 @@ Feature: Delete command
16
16
  | stack_id | event_id | stack_name | logical_resource_id | resource_status | resource_type | timestamp |
17
17
  | 1 | 1 | myapp-vpc | myapp-vpc | DELETE_COMPLETE | AWS::CloudFormation::Stack | 2020-10-29 00:00:00 |
18
18
  When I run `stack_master delete us-east-1 myapp-vpc --trace` interactively
19
- And the output should contain all of these lines:
20
- | 2020-10-29 00:00:00 +1100 myapp-vpc AWS::CloudFormation::Stack DELETE_COMPLETE |
19
+ And the output should match /2020-10-29 00:00:00 \+[0-9]{4} myapp-vpc AWS::CloudFormation::Stack DELETE_COMPLETE/
21
20
  Then the exit status should be 0
22
21
 
23
22
  Scenario: Run a delete command on a stack that does not exists
@@ -34,5 +34,4 @@ Feature: Events command
34
34
  | 1 | 1 | myapp-vpc | TestSg | CREATE_COMPLETE | AWS::EC2::SecurityGroup | 2020-10-29 00:00:00 |
35
35
  | 1 | 1 | myapp-vpc | myapp-vpc | CREATE_COMPLETE | AWS::CloudFormation::Stack | 2020-10-29 00:00:00 |
36
36
  When I run `stack_master events us-east-1 myapp-vpc --trace`
37
- And the output should contain all of these lines:
38
- | 2020-10-29 00:00:00 +1100 myapp-vpc AWS::CloudFormation::Stack CREATE_COMPLETE |
37
+ And the output should match /2020-10-29 00:00:00 \+[0-9]{4} myapp-vpc AWS::CloudFormation::Stack CREATE_COMPLETE/
@@ -62,5 +62,5 @@ Feature: Region aliases
62
62
  | + "Vpc": { |
63
63
  | Parameters diff: |
64
64
  | KeyName: my-key |
65
- | 2020-10-29 00:00:00 +1100 myapp-vpc AWS::CloudFormation::Stack CREATE_COMPLETE |
65
+ And the output should match /2020-10-29 00:00:00 \+[0-9]{4} myapp-vpc AWS::CloudFormation::Stack CREATE_COMPLETE/
66
66
  Then the exit status should be 0
data/lib/stack_master.rb CHANGED
@@ -9,6 +9,7 @@ require 'active_support/core_ext/string'
9
9
  require "erb"
10
10
  require 'sparkle_formation'
11
11
  require 'dotgpg'
12
+ require 'ruby-progressbar'
12
13
 
13
14
  require "stack_master/ctrl_c"
14
15
  require "stack_master/command"
@@ -29,6 +30,7 @@ require "stack_master/parameter_resolvers/stack_output"
29
30
  require "stack_master/parameter_resolvers/secret"
30
31
  require "stack_master/parameter_resolvers/sns_topic_name"
31
32
  require "stack_master/parameter_resolvers/security_group"
33
+ require "stack_master/parameter_resolvers/latest_ami_by_tags"
32
34
  require "stack_master/utils"
33
35
  require "stack_master/config"
34
36
  require "stack_master/stack_definition"
@@ -127,8 +127,15 @@ module StackMaster
127
127
  say "Invalid arguments. stack_master delete [region] [stack_name]"
128
128
  return
129
129
  end
130
- StackMaster.cloud_formation_driver.set_region(args[0])
131
- StackMaster::Commands::Delete.perform(*args)
130
+ # Because delete can work without a stack_master.yml
131
+ if options.config and File.file?(options.config)
132
+ config = load_config(options.config)
133
+ region = Utils.underscore_to_hyphen(config.unalias_region(args[0]))
134
+ else
135
+ region = args[0]
136
+ end
137
+ StackMaster.cloud_formation_driver.set_region(region)
138
+ StackMaster::Commands::Delete.perform(region, args[1])
132
139
  end
133
140
  end
134
141
 
@@ -3,17 +3,31 @@ module StackMaster
3
3
  class Status
4
4
  include Command
5
5
 
6
- def initialize(config)
6
+ def initialize(config, show_progress = true)
7
7
  @config = config
8
+ @show_progress = show_progress
8
9
  end
9
10
 
10
11
  def perform
12
+ progress if @show_progress
13
+ status = @config.stacks.map do |stack_definition|
14
+ status = get_status(stack_definition)
15
+ progress.increment if @show_progress
16
+ status
17
+ end
11
18
  tp.set :io, StackMaster.stdout
12
- tp @config.stacks.map { |stack_definition| get_status(stack_definition) }
19
+ tp status
20
+ StackMaster.stdout.puts " * No echo parameters can't be diffed"
13
21
  end
14
22
 
15
23
  private
16
24
 
25
+ def progress
26
+ @progress ||= ProgressBar.create(title: "Fetching stack information",
27
+ total: @config.stacks.size,
28
+ output: StackMaster.stdout)
29
+ end
30
+
17
31
  def sort_params(hash)
18
32
  hash.sort.to_h
19
33
  end
@@ -30,6 +44,7 @@ module StackMaster
30
44
  differ = StackMaster::StackDiffer.new(proposed_stack, stack)
31
45
  different = differ.body_different? || differ.params_different?
32
46
  stack_status = stack.stack_status
47
+ noecho = !differ.noecho_keys.empty?
33
48
  else
34
49
  different = true
35
50
  stack_status = nil
@@ -39,7 +54,7 @@ module StackMaster
39
54
  different = true
40
55
  end
41
56
 
42
- { region: region, stack_name: stack_name, stack_status: stack_status, different: different ? "Yes" : "No" }
57
+ { region: region, stack_name: stack_name, stack_status: stack_status, different: different ? "Yes" : (noecho ? "No *" : "No") }
43
58
  end
44
59
 
45
60
  end
@@ -0,0 +1,36 @@
1
+ module StackMaster
2
+ module ParameterResolvers
3
+ class LatestAmiByTags
4
+ def initialize(config, stack_definition)
5
+ @config = config
6
+ @stack_definition = stack_definition
7
+ end
8
+
9
+ def resolve(value)
10
+ filters = build_filters(value)
11
+ find_latest_ami(filters).try(:image_id)
12
+ end
13
+
14
+ private
15
+
16
+ def ec2
17
+ @ec2 ||= Aws::EC2::Client.new(region: @stack_definition.region)
18
+ end
19
+
20
+ def build_filters(value)
21
+ value.split(',').map do |tag_with_value|
22
+ tag, value = tag_with_value.strip.split('=')
23
+ { name: "tag:#{tag}", values: [value] }
24
+ end
25
+ end
26
+
27
+ def find_latest_ami(filters)
28
+ images = ec2.describe_images(filters: filters).images
29
+ sorted_images = images.sort do |a, b|
30
+ Time.parse(a.creation_date) <=> Time.parse(b.creation_date)
31
+ end
32
+ sorted_images.last
33
+ end
34
+ end
35
+ end
36
+ end
@@ -8,7 +8,6 @@ module StackMaster
8
8
  end
9
9
 
10
10
  def find(reference)
11
- STDERR.puts "Resolving security group reference '#{reference}'"
12
11
  raise ArgumentError, 'Security group references must be non-empty strings' unless reference.is_a?(String) && !reference.empty?
13
12
 
14
13
  groups = @resource.security_groups({
@@ -7,7 +7,6 @@ module StackMaster
7
7
  end
8
8
 
9
9
  def find(reference)
10
- $stderr.puts "Resolving SNS topic reference '#{reference}'"
11
10
  raise ArgumentError, 'SNS topic references must be non-empty strings' unless reference.is_a?(String) && !reference.empty?
12
11
 
13
12
  topic = @resource.topics.detect { |t| topic_name_from_arn(t.arn) == reference }
@@ -18,7 +18,14 @@ module StackMaster
18
18
  end
19
19
 
20
20
  def proposed_parameters
21
- YAML.dump(sort_params(@proposed_stack.parameters_with_defaults))
21
+ # **** out any secret parameters in the current stack.
22
+ params = @proposed_stack.parameters_with_defaults
23
+ if @current_stack
24
+ noecho_keys.each do |key|
25
+ params[key] = "****"
26
+ end
27
+ end
28
+ YAML.dump(sort_params(params))
22
29
  end
23
30
 
24
31
  def body_different?
@@ -33,6 +40,9 @@ module StackMaster
33
40
  if @current_stack
34
41
  text_diff('Stack', current_template, proposed_template, context: 7, include_diff_info: true)
35
42
  text_diff('Parameters', current_parameters, proposed_parameters)
43
+ unless noecho_keys.empty?
44
+ StackMaster.stdout.puts " * can not tell if NoEcho parameters are different."
45
+ end
36
46
  else
37
47
  text_diff('Stack', '', proposed_template)
38
48
  text_diff('Parameters', '', proposed_parameters)
@@ -40,6 +50,16 @@ module StackMaster
40
50
  end
41
51
  end
42
52
 
53
+ def noecho_keys
54
+ if @current_stack
55
+ @current_stack.parameters_with_defaults.select do |key, value|
56
+ value == "****"
57
+ end.keys
58
+ else
59
+ []
60
+ end
61
+ end
62
+
43
63
  private
44
64
 
45
65
  def text_diff(thing, current, proposed, diff_opts = {})
@@ -1,3 +1,3 @@
1
1
  module StackMaster
2
- VERSION = "0.0.1"
2
+ VERSION = "0.0.2"
3
3
  end
@@ -1,5 +1,5 @@
1
1
  RSpec.describe StackMaster::Commands::Status do
2
- subject(:status) { described_class.new(config) }
2
+ subject(:status) { described_class.new(config, false) }
3
3
  let(:config) { instance_double(StackMaster::Config, stacks: stacks) }
4
4
  let(:stacks) { [stack_definition_1, stack_definition_2] }
5
5
  let(:stack_definition_1) { double(:stack_definition_1, region: 'us-east-1', stack_name: 'stack1') }
@@ -19,7 +19,7 @@ RSpec.describe StackMaster::Commands::Status do
19
19
  let(:proposed_stack1) { double(:proposed_stack1, template_body: "{}", parameters_with_defaults: {a: 1}) }
20
20
  let(:proposed_stack2) { double(:proposed_stack2, template_body: "{}", parameters_with_defaults: {a: 1}) }
21
21
  it "returns the status of call stacks" do
22
- out = "REGION | STACK_NAME | STACK_STATUS | DIFFERENT\n----------|------------|-----------------|----------\nus-east-1 | stack1 | UPDATE_COMPLETE | No \nus-east-1 | stack2 | CREATE_COMPLETE | Yes \n"
22
+ out = "REGION | STACK_NAME | STACK_STATUS | DIFFERENT\n----------|------------|-----------------|----------\nus-east-1 | stack1 | UPDATE_COMPLETE | No \nus-east-1 | stack2 | CREATE_COMPLETE | Yes \n * No echo parameters can't be diffed\n"
23
23
  expect { status.perform }.to output(out).to_stdout
24
24
  end
25
25
  end
@@ -30,7 +30,7 @@ RSpec.describe StackMaster::Commands::Status do
30
30
  let(:proposed_stack1) { double(:proposed_stack1, template_body: "{}", parameters_with_defaults: {a: 1}) }
31
31
  let(:proposed_stack2) { double(:proposed_stack2, template_body: "{}", parameters_with_defaults: {a: 1}) }
32
32
  it "returns the status of call stacks" do
33
- out = "REGION | STACK_NAME | STACK_STATUS | DIFFERENT\n----------|------------|-----------------|----------\nus-east-1 | stack1 | UPDATE_COMPLETE | Yes \nus-east-1 | stack2 | CREATE_COMPLETE | No \n"
33
+ out = "REGION | STACK_NAME | STACK_STATUS | DIFFERENT\n----------|------------|-----------------|----------\nus-east-1 | stack1 | UPDATE_COMPLETE | Yes \nus-east-1 | stack2 | CREATE_COMPLETE | No \n * No echo parameters can't be diffed\n"
34
34
  expect { status.perform }.to output(out).to_stdout
35
35
  end
36
36
  end
@@ -0,0 +1,33 @@
1
+ RSpec.describe StackMaster::ParameterResolvers::LatestAmiByTags do
2
+ let(:config) { double(base_dir: '/base') }
3
+ let(:stack_definition) { double(stack_name: 'mystack', region: 'us-east-1') }
4
+ subject(:resolver) { described_class.new(config, stack_definition) }
5
+ let(:ec2) { Aws::EC2::Client.new }
6
+
7
+ before do
8
+ allow(Aws::EC2::Client).to receive(:new).and_return(ec2)
9
+ end
10
+
11
+ context 'when matches are found' do
12
+ before do
13
+ ec2.stub_responses(:describe_images, images: [
14
+ { image_id: '1', creation_date: '2015-01-02 00:00:00', tags: [{ key: 'my-tag', value: 'my-value' }] },
15
+ { image_id: '2', creation_date: '2015-01-03 00:00:00', tags: [{ key: 'my-tag', value: 'my-value' }] }
16
+ ])
17
+ end
18
+
19
+ it 'returns the latest one' do
20
+ expect(resolver.resolve('my-tag=my-value')).to eq '2'
21
+ end
22
+ end
23
+
24
+ context 'when no matches are found' do
25
+ before do
26
+ ec2.stub_responses(:describe_images, images: [])
27
+ end
28
+
29
+ it 'returns nil' do
30
+ expect(resolver.resolve('my-tag=my-value')).to be_nil
31
+ end
32
+ end
33
+ end
@@ -1,17 +1,27 @@
1
1
  RSpec.describe StackMaster::StackDiffer do
2
2
  subject(:differ) { described_class.new(proposed_stack, stack) }
3
+ let(:current_params) { Hash.new }
4
+ let(:proposed_params) { { 'param1' => 'hello'} }
3
5
  let(:stack) { StackMaster::Stack.new(stack_name: stack_name,
4
6
  region: region,
5
7
  stack_id: 123,
6
8
  template_body: '{}',
7
- parameters: {}) }
9
+ parameters: current_params) }
8
10
  let(:proposed_stack) { StackMaster::Stack.new(stack_name: stack_name,
9
11
  region: region,
10
- parameters: { 'param1' => 'hello'},
12
+ parameters: proposed_params,
11
13
  template_body: "{\"a\": 1}") }
12
14
  let(:stack_name) { 'myapp-vpc' }
13
15
  let(:region) { 'us-east-1' }
14
16
 
17
+ describe "#proposed_parameters" do
18
+ let(:current_params) { { 'param1' => 'hello',
19
+ 'param2' => '****'} }
20
+ it "stars out noecho params" do
21
+ expect(differ.proposed_parameters).to eq "---\nparam1: hello\nparam2: \"****\"\n"
22
+ end
23
+ end
24
+
15
25
  describe "#output_diff" do
16
26
  context "entirely new stack" do
17
27
  let(:stack) { nil }
@@ -12,7 +12,7 @@ RSpec.describe StackMaster::StackEvents::Presenter do
12
12
  subject(:print_event) { described_class.print_event($stdout, event) }
13
13
 
14
14
  it "nicely presents event data" do
15
- expect { print_event }.to output("\e[0;33;49m2001-01-01 02:02:02 +1100 MyAwesomeQueue AWS::SQS::Queue CREATE_IN_PROGRESS Resource creation Initiated\e[0m\n").to_stdout
15
+ expect { print_event }.to output("\e[0;33;49m2001-01-01 02:02:02 #{Time.now.strftime('%z')} MyAwesomeQueue AWS::SQS::Queue CREATE_IN_PROGRESS Resource creation Initiated\e[0m\n").to_stdout
16
16
  end
17
17
  end
18
18
  end
data/stack_master.gemspec CHANGED
@@ -25,6 +25,7 @@ Gem::Specification.new do |spec|
25
25
  spec.add_development_dependency "cucumber"
26
26
  spec.add_development_dependency "aruba"
27
27
  spec.add_development_dependency "timecop"
28
+ spec.add_dependency "ruby-progressbar"
28
29
  spec.add_dependency "commander"
29
30
  spec.add_dependency "virtus"
30
31
  spec.add_dependency "aws-sdk"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: stack_master
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Steve Hodgkiss
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2015-11-04 00:00:00.000000000 Z
12
+ date: 2015-11-06 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bundler
@@ -109,6 +109,20 @@ dependencies:
109
109
  - - ">="
110
110
  - !ruby/object:Gem::Version
111
111
  version: '0'
112
+ - !ruby/object:Gem::Dependency
113
+ name: ruby-progressbar
114
+ requirement: !ruby/object:Gem::Requirement
115
+ requirements:
116
+ - - ">="
117
+ - !ruby/object:Gem::Version
118
+ version: '0'
119
+ type: :runtime
120
+ prerelease: false
121
+ version_requirements: !ruby/object:Gem::Requirement
122
+ requirements:
123
+ - - ">="
124
+ - !ruby/object:Gem::Version
125
+ version: '0'
112
126
  - !ruby/object:Gem::Dependency
113
127
  name: commander
114
128
  requirement: !ruby/object:Gem::Requirement
@@ -302,6 +316,7 @@ files:
302
316
  - lib/stack_master/ctrl_c.rb
303
317
  - lib/stack_master/parameter_loader.rb
304
318
  - lib/stack_master/parameter_resolver.rb
319
+ - lib/stack_master/parameter_resolvers/latest_ami_by_tags.rb
305
320
  - lib/stack_master/parameter_resolvers/secret.rb
306
321
  - lib/stack_master/parameter_resolvers/security_group.rb
307
322
  - lib/stack_master/parameter_resolvers/sns_topic_name.rb
@@ -338,6 +353,7 @@ files:
338
353
  - spec/stack_master/config_spec.rb
339
354
  - spec/stack_master/parameter_loader_spec.rb
340
355
  - spec/stack_master/parameter_resolver_spec.rb
356
+ - spec/stack_master/parameter_resolvers/latest_ami_by_tag_spec.rb
341
357
  - spec/stack_master/parameter_resolvers/secret_spec.rb
342
358
  - spec/stack_master/parameter_resolvers/security_group_spec.rb
343
359
  - spec/stack_master/parameter_resolvers/sns_topic_name_spec.rb
@@ -409,6 +425,7 @@ test_files:
409
425
  - spec/stack_master/config_spec.rb
410
426
  - spec/stack_master/parameter_loader_spec.rb
411
427
  - spec/stack_master/parameter_resolver_spec.rb
428
+ - spec/stack_master/parameter_resolvers/latest_ami_by_tag_spec.rb
412
429
  - spec/stack_master/parameter_resolvers/secret_spec.rb
413
430
  - spec/stack_master/parameter_resolvers/security_group_spec.rb
414
431
  - spec/stack_master/parameter_resolvers/sns_topic_name_spec.rb