sfn 1.1.2 → 1.1.4

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: 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