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