cfncli 0.3.0 → 0.3.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +8 -8
- data/README.md +1 -0
- data/exe/cfncli +1 -2
- data/lib/cfncli/cli.rb +70 -9
- data/lib/cfncli/cloudformation.rb +36 -10
- data/lib/cfncli/config.rb +0 -63
- data/lib/cfncli/event_streamer.rb +13 -4
- data/lib/cfncli/stack.rb +20 -5
- data/lib/cfncli/thor_yaml.rb +9 -0
- data/lib/cfncli/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
---
|
2
2
|
!binary "U0hBMQ==":
|
3
3
|
metadata.gz: !binary |-
|
4
|
-
|
4
|
+
OTFjZTBhZDk1Y2JjZWM5YWE0MWFlOTlhNTEwZDEyYjRjZWFkNDk2Zg==
|
5
5
|
data.tar.gz: !binary |-
|
6
|
-
|
6
|
+
OTFiMzU2MzM0YjVkNjNiNWY0Yzg3YjdhYjBhOTg0Y2FlZGRjMWFlNQ==
|
7
7
|
SHA512:
|
8
8
|
metadata.gz: !binary |-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
ZDM5NmE0MzczOTMzN2E1ODljYjgwYjE4ZjNhZjllYWVjM2Y4ZWI0NjEwMzFj
|
10
|
+
MzFmM2EyNWM4ZDA2NTE3YjU3ZjM3NzljMDdiMzczNDkwM2VlYWRhZWFlNTYy
|
11
|
+
MmEwZjg0ZWFhZmI5ZjVhYTE2ZGU1MTFmMGRhMTRiNDdjZmZiNGE=
|
12
12
|
data.tar.gz: !binary |-
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
OGViNTIwM2FjOGE4NjViM2M4MzA5ZTdkY2U3OWJmZTM2YThlMjVjOWMwNzdm
|
14
|
+
YWVhMjM1YjJmZGI2ZTU4OWVhM2FhODExNzU2MzcwNTVmNmJkYTFjNzZmMTgz
|
15
|
+
M2IzMmVlM2U3MjU3ZWJjNGMyMDE4ZDFkMWU2MWE5Njk3MzNlMmE=
|
data/README.md
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
# cfncli
|
2
|
+
[![Gem Version](https://badge.fury.io/rb/cfncli.svg)](https://badge.fury.io/rb/cfncli)
|
2
3
|
[![Code Climate](https://codeclimate.com/github/lethalpaga/cfncli/badges/gpa.svg)](https://codeclimate.com/github/lethalpaga/cfncli)
|
3
4
|
[![Test Coverage](https://codeclimate.com/github/lethalpaga/cfncli/badges/coverage.svg)](https://codeclimate.com/github/lethalpaga/cfncli/coverage)
|
4
5
|
|
data/exe/cfncli
CHANGED
data/lib/cfncli/cli.rb
CHANGED
@@ -3,9 +3,14 @@ require 'aws-sdk'
|
|
3
3
|
|
4
4
|
require 'cfncli/cloudformation'
|
5
5
|
require 'cfncli/config'
|
6
|
+
require 'cfncli/thor_yaml'
|
7
|
+
require 'cfncli/logger'
|
8
|
+
require 'cfncli/version'
|
6
9
|
|
7
10
|
module CfnCli
|
8
11
|
class Cli < Thor
|
12
|
+
include ThorYamlLoader
|
13
|
+
include Loggable
|
9
14
|
|
10
15
|
module ExitCode
|
11
16
|
OK = 0
|
@@ -18,12 +23,16 @@ module CfnCli
|
|
18
23
|
type: :numeric,
|
19
24
|
default: 1,
|
20
25
|
desc: 'Log level to display (0=DEBUG, 1=INFO, 2=ERROR, 3=CRITICAL)'
|
26
|
+
|
27
|
+
class_option 'config_file',
|
28
|
+
type: :string,
|
29
|
+
default: 'cfncli.yml',
|
30
|
+
desc: 'Configuration file'
|
21
31
|
|
22
32
|
# Stack options
|
23
33
|
method_option 'stack_name',
|
24
34
|
alias: '-n',
|
25
35
|
type: :string,
|
26
|
-
required: true,
|
27
36
|
desc: 'Cloudformation stack name'
|
28
37
|
|
29
38
|
method_option 'template_body',
|
@@ -114,22 +123,27 @@ module CfnCli
|
|
114
123
|
opts = process_params(options)
|
115
124
|
|
116
125
|
stack_name = opts['stack_name']
|
117
|
-
|
118
|
-
|
126
|
+
fail ArgumentError, 'stack_name is required' unless stack_name
|
127
|
+
|
119
128
|
timeout = consume_option(opts, 'timeout')
|
120
|
-
|
129
|
+
interval = consume_option(opts, 'interval')
|
130
|
+
retries = timeout / interval
|
121
131
|
fail_on_noop = consume_option(opts, 'fail_on_noop')
|
122
132
|
list_events = consume_option(opts, 'list_events')
|
133
|
+
config_file = consume_option(opts, 'config_file')
|
123
134
|
|
124
135
|
ENV['CFNCLI_LOG_LEVEL'] = consume_option(opts, 'log_level').to_s
|
125
136
|
|
137
|
+
logger.debug "Apply parameters: #{options.inspect}"
|
138
|
+
|
126
139
|
client_config = Config::CfnClient.new(interval, retries, fail_on_noop)
|
127
140
|
|
128
141
|
res = ExitCode::OK
|
129
|
-
cfn.create_stack(opts, client_config)
|
130
142
|
if list_events
|
131
|
-
cfn.
|
143
|
+
cfn.apply_and_list_events(opts, client_config)
|
132
144
|
res = ExitCode::STACK_ERROR unless cfn.stack_successful? stack_name
|
145
|
+
else
|
146
|
+
cfn.create_stack(opts, client_config)
|
133
147
|
end
|
134
148
|
|
135
149
|
puts "Stack creation #{res == 0 ? 'successful' : 'failed'}"
|
@@ -142,10 +156,9 @@ module CfnCli
|
|
142
156
|
method_option 'stack_name',
|
143
157
|
alias: '-n',
|
144
158
|
type: :string,
|
145
|
-
required: true,
|
146
159
|
desc: 'Name or ID of the Cloudformation stack'
|
147
|
-
|
148
|
-
# Application options
|
160
|
+
|
161
|
+
# Application options.
|
149
162
|
method_option 'interval',
|
150
163
|
type: :numeric,
|
151
164
|
default: 10,
|
@@ -159,9 +172,57 @@ module CfnCli
|
|
159
172
|
desc 'events', 'Displays the events for a stack in realtime'
|
160
173
|
def events
|
161
174
|
stack_name = options['stack_name']
|
175
|
+
|
176
|
+
fail ArgumentError, 'stack_name is required' unless stack_name
|
177
|
+
|
162
178
|
config = Config::CfnClient.new(options['interval'], options['retries'])
|
163
179
|
cfn.events(stack_name, config)
|
164
180
|
end
|
181
|
+
|
182
|
+
method_option 'stack_name',
|
183
|
+
aliases: ['-n'],
|
184
|
+
type: :string,
|
185
|
+
desc: 'Name or ID of the Cloudformation stack'
|
186
|
+
|
187
|
+
# Application options.
|
188
|
+
method_option 'interval',
|
189
|
+
type: :numeric,
|
190
|
+
default: 10,
|
191
|
+
desc: 'Polling interval (in seconds) for the cloudformation events'
|
192
|
+
|
193
|
+
method_option 'timeout',
|
194
|
+
type: :numeric,
|
195
|
+
default: 1800,
|
196
|
+
desc: 'Timeout (in seconds) for the stack event listing'
|
197
|
+
|
198
|
+
desc 'delete', 'Deletes a stack'
|
199
|
+
def delete
|
200
|
+
opts = options.dup
|
201
|
+
stack_name = opts['stack_name']
|
202
|
+
|
203
|
+
fail ArgumentError, 'stack_name is required' unless stack_name
|
204
|
+
|
205
|
+
interval = consume_option(opts, 'interval')
|
206
|
+
timeout = consume_option(opts, 'timeout')
|
207
|
+
consume_option(opts, 'log_level')
|
208
|
+
consume_option(opts, 'config_file')
|
209
|
+
retries = timeout / interval
|
210
|
+
|
211
|
+
config = Config::CfnClient.new(interval, retries)
|
212
|
+
cfn.delete_stack(opts, config)
|
213
|
+
end
|
214
|
+
|
215
|
+
method_option 'verbose',
|
216
|
+
aliases: ['-v'],
|
217
|
+
type: :boolean,
|
218
|
+
default: false,
|
219
|
+
desc: 'Displays the full path to the command'
|
220
|
+
desc 'version', 'Display the version'
|
221
|
+
def version
|
222
|
+
program_name = $PROGRAM_NAME
|
223
|
+
program_name = File.basename program_name unless options['verbose']
|
224
|
+
puts "#{program_name} v#{CfnCli::VERSION}"
|
225
|
+
end
|
165
226
|
|
166
227
|
no_tasks do
|
167
228
|
# Reads an option from a hash and deletes it
|
@@ -15,7 +15,7 @@ module CfnCli
|
|
15
15
|
end
|
16
16
|
|
17
17
|
# Creates a stack and wait for the creation to be finished
|
18
|
-
# @param options [Hash] Options for the stack creation
|
18
|
+
# @param options [Hash] Options for the stack creation
|
19
19
|
# (@see http://docs.aws.amazon.com/sdkforruby/api/Aws/CloudFormation/Client.html)
|
20
20
|
def create_stack(options, config = nil)
|
21
21
|
create_or_update_stack(options, config)
|
@@ -24,27 +24,51 @@ module CfnCli
|
|
24
24
|
# Creates a stack if it doesn't exist otherwise update it
|
25
25
|
def create_or_update_stack(options, config = nil)
|
26
26
|
opts = process_params(options.dup)
|
27
|
-
|
27
|
+
|
28
28
|
stack_name = opts['stack_name']
|
29
|
-
|
30
29
|
stack = create_stack_obj(stack_name, config)
|
31
|
-
|
30
|
+
|
32
31
|
if stack.exists?
|
33
32
|
stack.update(opts)
|
34
33
|
else
|
35
34
|
stack.create(opts)
|
36
35
|
end
|
37
|
-
|
36
|
+
|
38
37
|
stack
|
39
38
|
end
|
40
39
|
|
40
|
+
# Creates or update the stack and list events
|
41
|
+
def apply_and_list_events(options, config = nil)
|
42
|
+
# Create/update the stack
|
43
|
+
logger.debug "Creating stack #{options['stack_name']}"
|
44
|
+
stack = create_or_update_stack(options, config)
|
45
|
+
|
46
|
+
events(stack.stack_id)
|
47
|
+
end
|
48
|
+
|
41
49
|
# List stack events
|
42
|
-
def events(
|
43
|
-
stack =
|
44
|
-
stack
|
50
|
+
def events(stack_or_name, reset_events = true, poller = nil, streamer = nil, config = nil)
|
51
|
+
stack = stack_or_name
|
52
|
+
stack = create_stack_obj(stack_or_name, config) unless stack_or_name.is_a? CfnCli::Stack
|
53
|
+
|
54
|
+
poller ||= EventPoller.new
|
55
|
+
streamer ||= EventStreamer.new(stack, config)
|
56
|
+
|
57
|
+
streamer.reset_events if reset_events
|
58
|
+
|
59
|
+
logger.debug "Listing events for stack #{stack.stack_name}"
|
60
|
+
stack.list_events(poller, streamer, config)
|
45
61
|
end
|
46
62
|
|
47
|
-
#
|
63
|
+
# Delete a stack
|
64
|
+
def delete_stack(options, config)
|
65
|
+
stack = create_stack_obj(options['stack_name'], config)
|
66
|
+
options['stack_name'] = stack.stack_id
|
67
|
+
stack.delete(options, config)
|
68
|
+
events(stack.stack_id, config)
|
69
|
+
end
|
70
|
+
|
71
|
+
# Returns the stack status
|
48
72
|
def stack_successful?(stack_name)
|
49
73
|
Stack.new(stack_name).succeeded?
|
50
74
|
end
|
@@ -66,7 +90,9 @@ module CfnCli
|
|
66
90
|
# Creates a new stack object
|
67
91
|
# Mainly useful to mock it in unit tests
|
68
92
|
def create_stack_obj(stack_name, config)
|
69
|
-
CfnCli::Stack.new(stack_name, config)
|
93
|
+
stack = CfnCli::Stack.new(stack_name, config)
|
94
|
+
stack.fetch_stack_id if stack.exists?
|
95
|
+
stack
|
70
96
|
end
|
71
97
|
|
72
98
|
# Process the parameters
|
data/lib/cfncli/config.rb
CHANGED
@@ -20,68 +20,5 @@ module CfnCli
|
|
20
20
|
@fail_on_noop = fail_on_noop
|
21
21
|
end
|
22
22
|
end
|
23
|
-
|
24
|
-
class Parameters
|
25
|
-
def initialize(content)
|
26
|
-
@content = content
|
27
|
-
end
|
28
|
-
|
29
|
-
# Converts parameters to command-line arguments
|
30
|
-
def to_args(content = nil)
|
31
|
-
content ||= to_a
|
32
|
-
content.join(' ')
|
33
|
-
end
|
34
|
-
|
35
|
-
# Get an array of parameters
|
36
|
-
def to_a
|
37
|
-
from_hash(@content) if @content.is_a? Hash
|
38
|
-
end
|
39
|
-
|
40
|
-
# Format parameters for thor
|
41
|
-
# @param given_args [Array] Optional array of existing arguments
|
42
|
-
def to_thor(given_args = nil)
|
43
|
-
args = []
|
44
|
-
|
45
|
-
if given_args
|
46
|
-
given_args = given_args.dup
|
47
|
-
args << given_args.shift
|
48
|
-
end
|
49
|
-
args += to_a
|
50
|
-
args += given_args if given_args
|
51
|
-
|
52
|
-
args
|
53
|
-
end
|
54
|
-
|
55
|
-
protected
|
56
|
-
|
57
|
-
def from_hash(content)
|
58
|
-
args = []
|
59
|
-
content.each_pair do |key, value|
|
60
|
-
case value
|
61
|
-
when Hash
|
62
|
-
value = parse_hash(value)
|
63
|
-
when Array
|
64
|
-
value = parse_array(value)
|
65
|
-
end
|
66
|
-
|
67
|
-
args += ["--#{key}", value]
|
68
|
-
end
|
69
|
-
|
70
|
-
args
|
71
|
-
end
|
72
|
-
|
73
|
-
def parse_hash(content)
|
74
|
-
args = []
|
75
|
-
content.each_pair do |key, value|
|
76
|
-
args += ["#{key}:#{value}"]
|
77
|
-
end
|
78
|
-
|
79
|
-
args
|
80
|
-
end
|
81
|
-
|
82
|
-
def parse_array(value)
|
83
|
-
"[#{value.join(',')}]"
|
84
|
-
end
|
85
|
-
end
|
86
23
|
end
|
87
24
|
end
|
@@ -16,16 +16,25 @@ module CfnCli
|
|
16
16
|
# Wait for events. This will exit when the
|
17
17
|
# stack reaches a finished state
|
18
18
|
# @yields [CfnEvent] Events for the stack
|
19
|
-
def each_event
|
19
|
+
def each_event(&block)
|
20
20
|
Waiting.wait(interval: config.interval, max_attempts: config.retries) do |waiter|
|
21
|
-
|
22
|
-
yield event unless seen?(event)
|
23
|
-
end
|
21
|
+
list_events(&block)
|
24
22
|
|
25
23
|
waiter.done if stack.finished?
|
26
24
|
end
|
27
25
|
end
|
28
26
|
|
27
|
+
def list_events(&block)
|
28
|
+
@next_token = stack.events(@next_token).each do |event|
|
29
|
+
yield event unless seen?(event) if block_given?
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Mark all the existing events as 'seen'
|
34
|
+
def reset_events
|
35
|
+
list_events do; end
|
36
|
+
end
|
37
|
+
|
29
38
|
private
|
30
39
|
|
31
40
|
attr_accessor :seen_events
|
data/lib/cfncli/stack.rb
CHANGED
@@ -15,7 +15,7 @@ module CfnCli
|
|
15
15
|
attr_reader :stack_name
|
16
16
|
|
17
17
|
class StackNotFoundError < StandardError; end
|
18
|
-
|
18
|
+
|
19
19
|
def initialize(stack_name, config = nil)
|
20
20
|
@stack = nil
|
21
21
|
@stack_id = nil
|
@@ -65,7 +65,15 @@ module CfnCli
|
|
65
65
|
raise e
|
66
66
|
end
|
67
67
|
end
|
68
|
-
|
68
|
+
|
69
|
+
# Deletes an existing stack
|
70
|
+
def delete(opts, config)
|
71
|
+
logger.debug "Deleting stack #{opts.inspect}"
|
72
|
+
# Always use the real stack ID as the stack won't be available once deleted
|
73
|
+
id = fetch_stack_id
|
74
|
+
cfn.client.delete_stack(stack_name: id)
|
75
|
+
end
|
76
|
+
|
69
77
|
# Waits for a stack to be in a finished state
|
70
78
|
# @return A boolean indicating if the operation was succesful
|
71
79
|
def wait_for_completion
|
@@ -80,13 +88,13 @@ module CfnCli
|
|
80
88
|
|
81
89
|
# List all events in real time
|
82
90
|
# @param poller [CfnCli::Poller] Poller class to display events
|
83
|
-
def list_events(poller, config = nil)
|
84
|
-
streamer
|
91
|
+
def list_events(poller, streamer = nil, config = nil)
|
92
|
+
streamer ||= EventStreamer.new(self, config)
|
85
93
|
streamer.each_event do |event|
|
86
94
|
poller.event(event)
|
87
95
|
end
|
88
96
|
end
|
89
|
-
|
97
|
+
|
90
98
|
# Get the events from the cfn stack
|
91
99
|
def events(next_token)
|
92
100
|
stack.events(next_token)
|
@@ -111,6 +119,13 @@ module CfnCli
|
|
111
119
|
transitive_states.include? stack.stack_status
|
112
120
|
end
|
113
121
|
|
122
|
+
# Gets stack id from the cfn API
|
123
|
+
def fetch_stack_id
|
124
|
+
@stack = cfn.stack(stack_id)
|
125
|
+
@stack_id = @stack.stack_id
|
126
|
+
@stack_id
|
127
|
+
end
|
128
|
+
|
114
129
|
private
|
115
130
|
|
116
131
|
# Gets stack info from the cfn API
|
@@ -0,0 +1,9 @@
|
|
1
|
+
module ThorYamlLoader
|
2
|
+
def options
|
3
|
+
original_options = super
|
4
|
+
config_file = original_options['config_file']
|
5
|
+
return original_options unless File.exists?(config_file)
|
6
|
+
defaults = ::YAML::load_file(config_file) || {}
|
7
|
+
Thor::CoreExt::HashWithIndifferentAccess.new(defaults.merge(original_options))
|
8
|
+
end
|
9
|
+
end
|
data/lib/cfncli/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cfncli
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.3.
|
4
|
+
version: 0.3.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- lethalpaga
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-
|
11
|
+
date: 2016-04-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -208,6 +208,7 @@ files:
|
|
208
208
|
- lib/cfncli/logger.rb
|
209
209
|
- lib/cfncli/stack.rb
|
210
210
|
- lib/cfncli/states.rb
|
211
|
+
- lib/cfncli/thor_yaml.rb
|
211
212
|
- lib/cfncli/version.rb
|
212
213
|
homepage: https://github.com/lethalpaga/cfncli
|
213
214
|
licenses: []
|