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 +4 -4
- data/CHANGELOG.md +3 -0
- data/README.md +6 -0
- data/docs/callbacks.md +5 -0
- data/lib/sfn/callback.rb +4 -0
- data/lib/sfn/command.rb +3 -0
- data/lib/sfn/command/create.rb +1 -1
- data/lib/sfn/command/diff.rb +168 -0
- data/lib/sfn/command_module/template.rb +1 -1
- data/lib/sfn/config.rb +1 -0
- data/lib/sfn/config/diff.rb +14 -0
- data/lib/sfn/version.rb +1 -1
- data/sfn.gemspec +1 -0
- metadata +18 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1ff41b031d25b35807d32b321e518418884367eb
|
4
|
+
data.tar.gz: c640f2e35f83d0f3e5244e75c4237e2860586d5c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 809fa0ab54b3e669963ed04f6e02cee4fac79d9983e4e2857e20074d54157582ed7a1093d25428b8ecb035bcb8cb835e43c256c3bb1b2ad49c97c173032158b4
|
7
|
+
data.tar.gz: 45bb35b42769af49bb4042417fe2034b16f07316001273ab59721c3e5128106985e17f14d212e1947e71652b6552a698b3a09d61b0ab3aea0c9455fb2df16823
|
data/CHANGELOG.md
CHANGED
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.
|
data/docs/callbacks.md
CHANGED
@@ -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
|
data/lib/sfn/callback.rb
CHANGED
@@ -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
|
#
|
data/lib/sfn/command.rb
CHANGED
@@ -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]
|
data/lib/sfn/command/create.rb
CHANGED
@@ -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'
|
data/lib/sfn/config.rb
CHANGED
@@ -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
|
data/lib/sfn/version.rb
CHANGED
data/sfn.gemspec
CHANGED
@@ -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.
|
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-
|
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
|