sfn 1.1.2 → 1.1.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 57c0b106d6e595a6ccae1b8242e1b57dbf1418db
4
- data.tar.gz: b5aaa5b140fc279e39edcaa83bfd3a004e3b3208
3
+ metadata.gz: 1ff41b031d25b35807d32b321e518418884367eb
4
+ data.tar.gz: c640f2e35f83d0f3e5244e75c4237e2860586d5c
5
5
  SHA512:
6
- metadata.gz: 8d8e5a5bd254e7112df68538b644663b678797ca64dc78f971b252ae42fb7599a0207050d45f97ee333a18dd739e0ead80e58bd466b6f7794d7793826417acc7
7
- data.tar.gz: 63983740fed1f8fb70317410da8a1e844181de88115f2c745ce8be1091636ec8310ca44099125fe8af52cf4d7339706e7b46d37b5a65ca5d9efa5caaa5f15ffd
6
+ metadata.gz: 809fa0ab54b3e669963ed04f6e02cee4fac79d9983e4e2857e20074d54157582ed7a1093d25428b8ecb035bcb8cb835e43c256c3bb1b2ad49c97c173032158b4
7
+ data.tar.gz: 45bb35b42769af49bb4042417fe2034b16f07316001273ab59721c3e5128106985e17f14d212e1947e71652b6552a698b3a09d61b0ab3aea0c9455fb2df16823
@@ -1,3 +1,6 @@
1
+ ## v1.1.4
2
+ * [feature] Add new `diff` command
3
+
1
4
  ## v1.1.2
2
5
  * [fix] stack update when extracting compile time state
3
6
  * [fix] remove use of `root_path` method when shallow nesting is in use
data/README.md CHANGED
@@ -163,6 +163,7 @@ end
163
163
  * `sfn describe`
164
164
  * `sfn inspect`
165
165
  * `sfn validate`
166
+ * `sfn diff`
166
167
 
167
168
  _NOTE: All commands respond to `--help` and will provide a full list of valid options._
168
169
 
@@ -251,6 +252,11 @@ resources are supported.
251
252
 
252
253
  Update an existing stack.
253
254
 
255
+ ### `sfn diff STACK`
256
+
257
+ View resources differences that would be applied to a running stack
258
+ on update.
259
+
254
260
  ### `sfn destroy STACK`
255
261
 
256
262
  Destroy an existing stack.
@@ -17,6 +17,11 @@ are generally invoked in two places:
17
17
  * `before` - Prior to the command's remote API request
18
18
  * `after` - Following the command's remote API request
19
19
 
20
+ There are also callbacks available prior to the execution
21
+ of a command. These can also be isolated to specific commands:
22
+
23
+ * `before_config` - Prior to the execution of the command.
24
+
20
25
  ### Enabling Callbacks
21
26
 
22
27
  Callbacks can be applied globally (to all commands) or
@@ -10,6 +10,10 @@ module Sfn
10
10
  attr_reader :ui
11
11
  # @return [Smash]
12
12
  attr_reader :config
13
+ # @return [Array<String>] CLI arguments
14
+ attr_reader :arguments
15
+ # @return [Miasma::Models::Orchestration] remote API
16
+ attr_reader :api
13
17
 
14
18
  # Create a new callback instance
15
19
  #
@@ -7,6 +7,7 @@ module Sfn
7
7
  autoload :Create, 'sfn/command/create'
8
8
  autoload :Describe, 'sfn/command/describe'
9
9
  autoload :Destroy, 'sfn/command/destroy'
10
+ autoload :Diff, 'sfn/command/diff'
10
11
  autoload :Events, 'sfn/command/events'
11
12
  autoload :Export, 'sfn/command/export'
12
13
  autoload :Import, 'sfn/command/import'
@@ -23,6 +24,8 @@ module Sfn
23
24
  discover_config(cli_opts)
24
25
  end
25
26
  super(cli_opts, args)
27
+ run_callbacks_for(:after_config)
28
+ run_callbacks_for("after_config_#{Bogo::Utility.snake(self.class.name)}")
26
29
  end
27
30
 
28
31
  # @return [Smash]
@@ -3,7 +3,7 @@ require 'sfn'
3
3
 
4
4
  module Sfn
5
5
  class Command
6
- # Cloudformation create command
6
+ # Create command
7
7
  class Create < Command
8
8
 
9
9
  include Sfn::CommandModule::Base
@@ -0,0 +1,168 @@
1
+ require 'sparkle_formation'
2
+ require 'sfn'
3
+ require 'hashdiff'
4
+
5
+ module Sfn
6
+ class Command
7
+ # Diff command
8
+ class Diff < Command
9
+
10
+ include Sfn::CommandModule::Base
11
+ include Sfn::CommandModule::Template
12
+ include Sfn::CommandModule::Stack
13
+
14
+ # Diff the stack with existing stack
15
+ def execute!
16
+ name_required!
17
+ name = name_args.first
18
+
19
+ begin
20
+ stack = provider.connection.stacks.get(name)
21
+ rescue Miasma::Error::ApiError::RequestError
22
+ stack = nil
23
+ end
24
+
25
+ if(stack)
26
+ config[:print_only] = true
27
+ file = load_template_file
28
+ file.delete('sfn_nested_stack')
29
+ file = Sfn::Utils::StackParameterScrubber.scrub!(file)
30
+ file = translate_template(file)
31
+
32
+ ui.info "#{ui.color('SparkleFormation:', :bold)} #{ui.color('diff', :blue)} - #{name}"
33
+ ui.puts
34
+
35
+ diff_stack(stack, MultiJson.load(MultiJson.dump(file)).to_smash)
36
+ else
37
+ ui.fatal "Failed to locate requested stack: #{ui.color(name, :red, :bold)}"
38
+ raise "Failed to locate stack: #{name}"
39
+ end
40
+ end
41
+
42
+ def diff_stack(stack, file, parent_names=[])
43
+ stack_template = stack.template
44
+ nested_stacks = Hash[
45
+ file['Resources'].find_all do |name, value|
46
+ value.fetch('Properties', {})['Stack']
47
+ end
48
+ ]
49
+ nested_stacks.each do |name, value|
50
+ n_stack = stack.nested_stacks(false).detect do |ns|
51
+ ns.data[:logical_id] == name
52
+ end
53
+ if(n_stack)
54
+ diff_stack(n_stack, value['Properties']['Stack'], [*parent_names, stack.data.fetch(:logical_id, stack.name)].compact)
55
+ end
56
+ file['Resources'][name]['Properties'].delete('Stack')
57
+ end
58
+
59
+ ui.info "#{ui.color('Stack diff:', :bold)} #{ui.color((parent_names + [stack.data.fetch(:logical_id, stack.name)]).compact.join(' > '), :blue)}"
60
+
61
+ stack_diff = HashDiff.diff(stack.template, file)
62
+
63
+ if(config[:raw_diff])
64
+ ui.info "Dumping raw template diff:"
65
+ require 'pp'
66
+ pp stack_diff
67
+ else
68
+
69
+ added_resources = stack_diff.find_all do |item|
70
+ item.first == '+' && item[1].match(/Resources\.[^.]+$/)
71
+ end
72
+ removed_resources = stack_diff.find_all do |item|
73
+ item.first == '-' && item[1].match(/Resources\.[^.]+$/)
74
+ end
75
+ modified_resources = stack_diff.find_all do |item|
76
+ item[1].start_with?('Resources.') &&
77
+ !item[1].end_with?('TemplateURL') &&
78
+ !item[1].include?('Properties.Parameters')
79
+ end - added_resources - removed_resources
80
+
81
+ if(added_resources.empty? && removed_resources.empty? && modified_resources.empty?)
82
+ ui.info 'No changes detected'
83
+ ui.puts
84
+ else
85
+
86
+ unless(added_resources.empty?)
87
+ ui.info ui.color('Added Resources:', :green, :bold)
88
+ added_resources.each do |item|
89
+ ui.print ui.color(" -> #{item[1].split('.').last}", :green)
90
+ ui.puts " [#{item[2]['Type']}]"
91
+ end
92
+ ui.puts
93
+ end
94
+
95
+ unless(modified_resources.empty?)
96
+ ui.info ui.color('Modified Resources:', :yellow, :bold)
97
+ m_resources = Hash.new.tap do |hash|
98
+ modified_resources.each do |item|
99
+ _, key, path = item[1].split('.', 3)
100
+ hash[key] ||= {}
101
+ prefix, a_key = path.split('.', 2)
102
+ hash[key][prefix] ||= []
103
+ matched = hash[key][prefix].detect do |i|
104
+ i[:path] == a_key
105
+ end
106
+ if(matched)
107
+ if(item.first == '-')
108
+ matched[:original] = item[2]
109
+ else
110
+ matched[:new] = item[2]
111
+ end
112
+ else
113
+ hash[key][prefix] << Hash.new.tap do |info|
114
+ info[:path] = a_key
115
+ case item.first
116
+ when '~'
117
+ info[:original] = item[2]
118
+ info[:new] = item[3]
119
+ when '+'
120
+ info[:new] = item[2]
121
+ else
122
+ info[:original] = item[2]
123
+ end
124
+ end
125
+ end
126
+ end
127
+ end.to_smash(:sorted).each do |key, value|
128
+ ui.puts ui.color(" - #{key}", :yellow) + " [#{stack.template['Resources'][key]['Type']}]"
129
+ value.each do |prefix, items|
130
+ ui.puts ui.color(" #{prefix}:", :bold)
131
+ items.each do |item|
132
+ original = item[:original].nil? ? ui.color('(none)', :yellow) : ui.color(item[:original].inspect, :red)
133
+ new_val = item[:new].nil? ? ui.color('(deleted)', :red) : ui.color(item[:new].inspect, :green)
134
+ ui.puts " #{item[:path]}: #{original} -> #{new_val}"
135
+ end
136
+ end
137
+ end
138
+ ui.puts
139
+ end
140
+
141
+ unless(removed_resources.empty?)
142
+ ui.info ui.color('Removed Resources:', :red, :bold)
143
+ removed_resources.each do |item|
144
+ ui.print ui.color(" <- #{item[1].split('.').last}", :red)
145
+ ui.puts " [#{item[2]['Type']}]"
146
+ end
147
+ ui.puts
148
+ end
149
+
150
+ run_callbacks_for(:after_stack_diff,
151
+ :diff => stack_diff,
152
+ :diff_info => {
153
+ :added => added_resources,
154
+ :modified => modified_resources,
155
+ :removed => removed_resources
156
+ },
157
+ :api_stack => stack,
158
+ :new_template => file
159
+ )
160
+ end
161
+
162
+ end
163
+
164
+ end
165
+
166
+ end
167
+ end
168
+ end
@@ -233,6 +233,7 @@ module Sfn
233
233
  if(current_stack && current_stack.data[:parent_stack])
234
234
  current_parameters.merge!(current_stack.data[:parent_stack].template.fetch('Resources', stack_name, 'Properties', 'Parameters', Smash.new))
235
235
  end
236
+ full_stack_name = stack.root_path.map(&:name).map(&:to_s).join('_')
236
237
  unless(config[:print_only])
237
238
  result = Smash.new(
238
239
  'Parameters' => populate_parameters!(stack,
@@ -247,7 +248,6 @@ module Sfn
247
248
  unless(bucket)
248
249
  raise "Failed to locate configured bucket for stack template storage (#{bucket})!"
249
250
  end
250
- full_stack_name = stack.root_path.map(&:name).map(&:to_s).join('_')
251
251
  file = bucket.files.build
252
252
  file.name = "#{full_stack_name}.json"
253
253
  file.content_type = 'text/json'
@@ -13,6 +13,7 @@ module Sfn
13
13
  autoload :Describe, 'sfn/config/describe'
14
14
  autoload :Destroy, 'sfn/config/destroy'
15
15
  autoload :Describe, 'sfn/config/describe'
16
+ autoload :Diff, 'sfn/config/diff'
16
17
  autoload :Events, 'sfn/config/events'
17
18
  autoload :Export, 'sfn/config/export'
18
19
  autoload :Import, 'sfn/config/import'
@@ -0,0 +1,14 @@
1
+ require 'sfn'
2
+
3
+ module Sfn
4
+ class Config
5
+ # Diff new template with existing stack template
6
+ class Diff < Update
7
+ attribute(
8
+ :raw_diff, [TrueClass, FalseClass],
9
+ :default => false,
10
+ :description => 'Display raw diff information'
11
+ )
12
+ end
13
+ end
14
+ end
@@ -1,4 +1,4 @@
1
1
  module Sfn
2
2
  # Current library version
3
- VERSION = Gem::Version.new('1.1.2')
3
+ VERSION = Gem::Version.new('1.1.4')
4
4
  end
@@ -15,6 +15,7 @@ Gem::Specification.new do |s|
15
15
  s.add_dependency 'miasma-aws', '~> 0.1.16'
16
16
  s.add_dependency 'net-ssh'
17
17
  s.add_dependency 'sparkle_formation', '~> 1.1'
18
+ s.add_dependency 'hashdiff', '~> 0.2.2'
18
19
  s.executables << 'sfn'
19
20
  s.files = Dir['{lib,bin,docs}/**/*'] + %w(sfn.gemspec README.md CHANGELOG.md LICENSE)
20
21
  s.post_install_message = <<-EOF
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sfn
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.2
4
+ version: 1.1.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chris Roberts
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-10-26 00:00:00.000000000 Z
11
+ date: 2015-10-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bogo-cli
@@ -80,6 +80,20 @@ dependencies:
80
80
  - - "~>"
81
81
  - !ruby/object:Gem::Version
82
82
  version: '1.1'
83
+ - !ruby/object:Gem::Dependency
84
+ name: hashdiff
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: 0.2.2
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: 0.2.2
83
97
  description: SparkleFormation CLI
84
98
  email: code@chrisroberts.org
85
99
  executables:
@@ -113,6 +127,7 @@ files:
113
127
  - lib/sfn/command/create.rb
114
128
  - lib/sfn/command/describe.rb
115
129
  - lib/sfn/command/destroy.rb
130
+ - lib/sfn/command/diff.rb
116
131
  - lib/sfn/command/events.rb
117
132
  - lib/sfn/command/export.rb
118
133
  - lib/sfn/command/import.rb
@@ -131,6 +146,7 @@ files:
131
146
  - lib/sfn/config/create.rb
132
147
  - lib/sfn/config/describe.rb
133
148
  - lib/sfn/config/destroy.rb
149
+ - lib/sfn/config/diff.rb
134
150
  - lib/sfn/config/events.rb
135
151
  - lib/sfn/config/export.rb
136
152
  - lib/sfn/config/import.rb