cfncli 0.3.0 → 0.3.1

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,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- ZWQ3YjUwZTM2YzUyNzBlOGJjMzhjZmJjZTkxMDI1N2JhNTA3N2NmYw==
4
+ OTFjZTBhZDk1Y2JjZWM5YWE0MWFlOTlhNTEwZDEyYjRjZWFkNDk2Zg==
5
5
  data.tar.gz: !binary |-
6
- Yjc2NTBmY2Y5ZWY3YWZiMzNjZDdlMDdkYjI3NWY3ZjJkNmU2YWE4Mg==
6
+ OTFiMzU2MzM0YjVkNjNiNWY0Yzg3YjdhYjBhOTg0Y2FlZGRjMWFlNQ==
7
7
  SHA512:
8
8
  metadata.gz: !binary |-
9
- NjFlMTEzZWQ4ZDgxN2YzN2RlZDdmZDY1NzRhY2E4YWUyOGI4M2IwYTc0Yzg2
10
- YWFkYjA3MTRjZGVlZmI4N2MxZDFlNjM2YjZkYTE3YjYwMTc4MWMzZjg0ZTFj
11
- YTBkZmI0NDE0MWM4NTAyNjY1ZjU5Njk1ZmM2Zjk0YmE3OWU3M2Q=
9
+ ZDM5NmE0MzczOTMzN2E1ODljYjgwYjE4ZjNhZjllYWVjM2Y4ZWI0NjEwMzFj
10
+ MzFmM2EyNWM4ZDA2NTE3YjU3ZjM3NzljMDdiMzczNDkwM2VlYWRhZWFlNTYy
11
+ MmEwZjg0ZWFhZmI5ZjVhYTE2ZGU1MTFmMGRhMTRiNDdjZmZiNGE=
12
12
  data.tar.gz: !binary |-
13
- YjgzNmY4N2QzNjQ5MDliOTRjYWIzNjM0ZjE2MjIyODFhN2EzYmFmYmVkYTBi
14
- YTQzZmFhMTkyMGNkOTRlODk0ZDM3NDljMTY0NDBhYmFmYjBhNjQ0YjE3YzA5
15
- MDc0YzU5ZjNjYzQ4MmFkZDRiNTE4NmQ0OWU3Njk0OWMyYzZkY2E=
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
@@ -1,5 +1,4 @@
1
1
  #!/usr/bin/env ruby
2
2
  require 'cfncli/cli'
3
3
 
4
- args = CfnCli::Config.load_from_file('cfncli.yml').to_thor(ARGV)
5
- CfnCli::Cli.start(args)
4
+ CfnCli::Cli.start
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
- interval = consume_option(opts, 'interval')
126
+ fail ArgumentError, 'stack_name is required' unless stack_name
127
+
119
128
  timeout = consume_option(opts, 'timeout')
120
- retries = timeout / interval
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.events(stack_name, client_config)
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(stack_name, config)
43
- stack = create_stack_obj(stack_name, config)
44
- stack.list_events(EventPoller.new, config)
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
- # Returns the stack stack
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
- @next_token = stack.events(@next_token).each do |event|
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 = EventStreamer.new(self, config)
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
@@ -1,3 +1,3 @@
1
1
  module CfnCli
2
- VERSION = "0.3.0"
2
+ VERSION = "0.3.1"
3
3
  end
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.0
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-03-04 00:00:00.000000000 Z
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: []