cfncli 0.3.0
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 +15 -0
- data/.gitignore +11 -0
- data/.rspec +2 -0
- data/.travis.yml +18 -0
- data/Gemfile +6 -0
- data/Guardfile +5 -0
- data/README.md +44 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +7 -0
- data/cfncli.gemspec +34 -0
- data/exe/cfncli +5 -0
- data/lib/cfncli.rb +11 -0
- data/lib/cfncli/cfn_client.rb +24 -0
- data/lib/cfncli/cli.rb +254 -0
- data/lib/cfncli/cloudformation.rb +78 -0
- data/lib/cfncli/config.rb +87 -0
- data/lib/cfncli/event.rb +27 -0
- data/lib/cfncli/event_poller.rb +17 -0
- data/lib/cfncli/event_streamer.rb +40 -0
- data/lib/cfncli/logger.rb +53 -0
- data/lib/cfncli/stack.rb +122 -0
- data/lib/cfncli/states.rb +72 -0
- data/lib/cfncli/version.rb +3 -0
- metadata +235 -0
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
ZWQ3YjUwZTM2YzUyNzBlOGJjMzhjZmJjZTkxMDI1N2JhNTA3N2NmYw==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
Yjc2NTBmY2Y5ZWY3YWZiMzNjZDdlMDdkYjI3NWY3ZjJkNmU2YWE4Mg==
|
7
|
+
SHA512:
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
NjFlMTEzZWQ4ZDgxN2YzN2RlZDdmZDY1NzRhY2E4YWUyOGI4M2IwYTc0Yzg2
|
10
|
+
YWFkYjA3MTRjZGVlZmI4N2MxZDFlNjM2YjZkYTE3YjYwMTc4MWMzZjg0ZTFj
|
11
|
+
YTBkZmI0NDE0MWM4NTAyNjY1ZjU5Njk1ZmM2Zjk0YmE3OWU3M2Q=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
YjgzNmY4N2QzNjQ5MDliOTRjYWIzNjM0ZjE2MjIyODFhN2EzYmFmYmVkYTBi
|
14
|
+
YTQzZmFhMTkyMGNkOTRlODk0ZDM3NDljMTY0NDBhYmFmYjBhNjQ0YjE3YzA5
|
15
|
+
MDc0YzU5ZjNjYzQ4MmFkZDRiNTE4NmQ0OWU3Njk0OWMyYzZkY2E=
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
language: ruby
|
2
|
+
rvm:
|
3
|
+
- 2.2
|
4
|
+
sudo: false
|
5
|
+
before_install:
|
6
|
+
- gem install bundler -v '~> 1.10'
|
7
|
+
script:
|
8
|
+
- bundle exec rake spec
|
9
|
+
deploy:
|
10
|
+
provider: rubygems
|
11
|
+
api_key:
|
12
|
+
secure: "qDh1BOlLLokI/gU/5HkAmbsAEE5A384IketR+BB4Pal8BsGGnxnbbSUU79pEU94wCHhUFYDnzq7DbkxbvuBYl2R1y98iq7SkLvLD2XWr6zAYwWM3oQOrv3NTWi3hc93Mz8YxVJ6GFzyKBFf4SmYjf18OkpOHuxKSAF95T6LcSKaVljM3Vy49FOE6DsStcgHs4U7kgjQMFiaN4WI8ul7XrmsC9zLrmMG7EH6ktTcpE0TuLrpmtq4Y1dmDGLA55yEDfnYZVz/TgDpkcCAMNGC92P/3EgW6nsYAb3LJ/LD5qbbMdnMUVwKx9lOyiYkTKM9QtPoEhAxtn5I0DiWgQ+UI1MQAmdiqYScjhle8DpaRhYCSz+WDQc3MDHPEZQsKksA92Cs26Np6JRLDkpAYw2MKjLMnqFe1mDxr9UpnEJbxZJtsV+N6zgEwDMh/rwJXVdns4wIDveOT5rFP9VCfsJxn25In+DNhmeiBhLYMTeUfOPP9+SrH2xLiVnHk0OPy/IDrYTEd0BkGAPQQDQ45MCT0TOoF/bpTFQ8NREOXndTP9RyHS442eIHiQ5+kv4P/kwhBEjebhzIT4W5LYsYOBv2ZInTP2xKoHXveyVaIwMyKc0VwNluCi/xVnc/NpVhiRxgr57drW6z/Piy2uxs26VVfcve+9+EoO+qP2fBhtQ5YOWA="
|
13
|
+
on:
|
14
|
+
tags: true
|
15
|
+
addons:
|
16
|
+
code_climate:
|
17
|
+
repo_token:
|
18
|
+
secure: "AKrgx5PtDa5/pek0hLgtk55SEE4DzCBM1li4eVCPK4CWfo7WgiODN51rnE7uhnABdaeI82t/YuJIyLE37PnsU4dqkXVBXtPmijBkbZy1SZ/bmTsSnOFoMTVWkaCb/iAcr+i5yqJ16bSGLV/t3K80ksyH80WgZTdzmh75pEDKaOBDLI42mgBf/eXkLZ68iuotatLnuLFwVa4mGW79yiJYBxHNstYHEBd0B4Ahutz3BMGFUiwCBx0wer+nVf+RxN+RKkiXRLnOeucwVEcbOOHbm1nkLDhL8U1KQ8lnWNV+RSUPNoOJNoMcgMRKMavDj6TC29e2pO44PWNgICCkUqbvc9Otrac5HG566ZsYZ0gp9BhQxEIpeYkzEUh7a/znLGiQYylrohIf9gsWz3KLGOPD68y4DZfmKgQDZG8ag1ErKmIv1Cna1PmsFA/lXUrZ60hIzgc0Vdw2jb7Ww2GHK35h8kSOCuDOOgmDpLhESvPVb7PPL2ftTLwSH6FT/P7wyfoeSqgotZTKZCw6JZAnV+HjwWB1uzR7dlt/H0qjduCVekWcK7Pq4lnlNI8S4tcAYiJSaL6EcCvrQ2qUAlw2xXYar0ouRgOQgM4H3HCleqkNIZwHxc3GYgdyhhPYqBr8Uq1ziT7NnQ4X94cCu9tuAcvKoqvQiJOKc4HUGCzgPnJiqL8="
|
data/Gemfile
ADDED
data/Guardfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
# cfncli
|
2
|
+
[](https://codeclimate.com/github/lethalpaga/cfncli)
|
3
|
+
[](https://codeclimate.com/github/lethalpaga/cfncli/coverage)
|
4
|
+
|
5
|
+
cfncli is a command line tool that simplifies the creation of Cloudformation stacks.
|
6
|
+
It's designed to be a very simple wrapper around the Cloudformation API, but adds the following features compared to
|
7
|
+
the AWS cli :
|
8
|
+
* Can wait for the stack creation/update/deletion to be complete before returning
|
9
|
+
* Prints the stack events on the console
|
10
|
+
* Gives back a return code indicating if the operation was a success or failure
|
11
|
+
|
12
|
+
## Installation
|
13
|
+
|
14
|
+
Add this line to your application's Gemfile:
|
15
|
+
|
16
|
+
```ruby
|
17
|
+
gem 'cfncli'
|
18
|
+
```
|
19
|
+
|
20
|
+
And then execute:
|
21
|
+
|
22
|
+
$ bundle
|
23
|
+
|
24
|
+
Or install it yourself as:
|
25
|
+
|
26
|
+
$ gem install cfncli
|
27
|
+
|
28
|
+
## Usage
|
29
|
+
|
30
|
+
```
|
31
|
+
cfncli help
|
32
|
+
cfncli help apply
|
33
|
+
```
|
34
|
+
|
35
|
+
## Development
|
36
|
+
|
37
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
38
|
+
|
39
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
40
|
+
|
41
|
+
## Contributing
|
42
|
+
|
43
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/lethalpaga/cfncli.
|
44
|
+
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "cfncli"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start
|
data/bin/setup
ADDED
data/cfncli.gemspec
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'cfncli/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "cfncli"
|
8
|
+
spec.version = CfnCli::VERSION
|
9
|
+
spec.authors = ["lethalpaga"]
|
10
|
+
spec.email = ["lethalpaga@gmail.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{Creates cloudformation stacks}
|
13
|
+
spec.description = %q{Creates a Cloudformation stack synchronously}
|
14
|
+
spec.homepage = "https://github.com/lethalpaga/cfncli"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
17
|
+
spec.bindir = "exe"
|
18
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.10"
|
22
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
23
|
+
spec.add_development_dependency "rspec"
|
24
|
+
spec.add_development_dependency "rspec-its", "~>1.2"
|
25
|
+
spec.add_development_dependency "cucumber", "~> 2"
|
26
|
+
spec.add_development_dependency "guard", "~> 2"
|
27
|
+
spec.add_development_dependency "guard-rspec"
|
28
|
+
|
29
|
+
spec.add_dependency "thor"
|
30
|
+
spec.add_dependency "aws-sdk", "~> 2"
|
31
|
+
spec.add_dependency "waiting", "~> 0"
|
32
|
+
spec.add_dependency "activesupport", "~> 4"
|
33
|
+
spec.add_dependency "colorize", "~> 0"
|
34
|
+
end
|
data/exe/cfncli
ADDED
data/lib/cfncli.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'aws-sdk'
|
2
|
+
|
3
|
+
module CfnCli
|
4
|
+
module CfnClient
|
5
|
+
|
6
|
+
attr_accessor :stub_responses
|
7
|
+
|
8
|
+
# CloudFormation client
|
9
|
+
# @see http://docs.aws.amazon.com/sdkforruby/api/Aws/CloudFormation/Client.html
|
10
|
+
def cfn_client
|
11
|
+
@@client ||= Aws::CloudFormation::Client.new(stub_responses: stub_responses || false)
|
12
|
+
end
|
13
|
+
|
14
|
+
# Clouformation Resource
|
15
|
+
# This is used to interact with the CloudFormation API
|
16
|
+
# @see http://docs.aws.amazon.com/sdkforruby/api/Aws/CloudFormation/Resource.html
|
17
|
+
# @note this uses a class variable for the client and the resource so they can share
|
18
|
+
# the stubbed responses in the unit tests.
|
19
|
+
def cfn
|
20
|
+
@@resource ||= Aws::CloudFormation::Resource.new(client: cfn_client)
|
21
|
+
@@resource
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/lib/cfncli/cli.rb
ADDED
@@ -0,0 +1,254 @@
|
|
1
|
+
require 'thor'
|
2
|
+
require 'aws-sdk'
|
3
|
+
|
4
|
+
require 'cfncli/cloudformation'
|
5
|
+
require 'cfncli/config'
|
6
|
+
|
7
|
+
module CfnCli
|
8
|
+
class Cli < Thor
|
9
|
+
|
10
|
+
module ExitCode
|
11
|
+
OK = 0
|
12
|
+
STACK_ERROR = 1
|
13
|
+
VALIDATION_ERROR = 2
|
14
|
+
end
|
15
|
+
|
16
|
+
# Global options
|
17
|
+
class_option 'log_level',
|
18
|
+
type: :numeric,
|
19
|
+
default: 1,
|
20
|
+
desc: 'Log level to display (0=DEBUG, 1=INFO, 2=ERROR, 3=CRITICAL)'
|
21
|
+
|
22
|
+
# Stack options
|
23
|
+
method_option 'stack_name',
|
24
|
+
alias: '-n',
|
25
|
+
type: :string,
|
26
|
+
required: true,
|
27
|
+
desc: 'Cloudformation stack name'
|
28
|
+
|
29
|
+
method_option 'template_body',
|
30
|
+
type: :string,
|
31
|
+
desc: 'JSON string or file containing the template body.' \
|
32
|
+
' This is exclusive with the template_url option. Use @filename to read' \
|
33
|
+
' the template body from a file'
|
34
|
+
|
35
|
+
method_option 'template_url',
|
36
|
+
type: :string,
|
37
|
+
desc: 'S3 URL to the Cloudformation template.' \
|
38
|
+
' This is exclusive with the template_body option'
|
39
|
+
|
40
|
+
method_option 'parameters',
|
41
|
+
type: :hash,
|
42
|
+
desc: 'Stack parameters. Pass each parameter in the form --parameters key1:value1 key2:value2 or use the @filename syntax to provide a JSON file'
|
43
|
+
|
44
|
+
method_option 'parameters_file',
|
45
|
+
type: :string,
|
46
|
+
desc: 'Stack parameters file. It should be a JSON file using the same syntax as for the AWS CLI'
|
47
|
+
|
48
|
+
method_option 'disable_rollback',
|
49
|
+
type: :boolean,
|
50
|
+
default: false,
|
51
|
+
desc: 'Disable rollbacks in case of a stack update failure'\
|
52
|
+
' This is mutually exclusive with on_failure.'
|
53
|
+
|
54
|
+
method_option 'timeout_in_minutes',
|
55
|
+
type: :boolean,
|
56
|
+
desc: 'Stack creation timeout (in minutes)'
|
57
|
+
|
58
|
+
method_option 'notification_arns',
|
59
|
+
type: :array,
|
60
|
+
desc: 'List of SNS notification ARNs to publish stack related events'
|
61
|
+
|
62
|
+
method_option 'capabilities',
|
63
|
+
type: :array,
|
64
|
+
enum: ['CAPABILITY_IAM'],
|
65
|
+
desc: 'A list of capabilities that you must specify before AWS CloudFormation can create or update certain stacks'
|
66
|
+
|
67
|
+
method_option 'resource_types',
|
68
|
+
type: :array,
|
69
|
+
desc: 'The template resource types that you have permissions to work with for this create stack action, such as AWS::EC2::Instance, AWS::EC2::*, or Custom::MyCustomInstance'
|
70
|
+
|
71
|
+
method_option 'on_failure',
|
72
|
+
type: :string,
|
73
|
+
enum: ['DO_NOTHING', 'ROLLBACK', 'DELETE'],
|
74
|
+
desc: 'Determines what action will be taken if the stack creation fails.' \
|
75
|
+
' This is mutually exclusive with disable_rollback'
|
76
|
+
|
77
|
+
method_option 'stack_policy_body',
|
78
|
+
type: :string,
|
79
|
+
desc: 'JSON String containing the stack policy body. The @filename syntax can be used.'
|
80
|
+
|
81
|
+
method_option 'stack_policy_url',
|
82
|
+
type: :string,
|
83
|
+
desc: 'S3 URL to a stack policy file.' \
|
84
|
+
' This is mutually exclusive with stack_policy_body'
|
85
|
+
|
86
|
+
method_option 'tags',
|
87
|
+
type: :hash,
|
88
|
+
desc: 'Key-value pairs to associate with this stack'
|
89
|
+
|
90
|
+
# Application options
|
91
|
+
method_option 'list_events',
|
92
|
+
alias: '-l',
|
93
|
+
type: :boolean,
|
94
|
+
default: true,
|
95
|
+
desc: 'List the stack events during the operation'
|
96
|
+
|
97
|
+
method_option 'interval',
|
98
|
+
type: :numeric,
|
99
|
+
default: 10,
|
100
|
+
desc: 'Polling interval (in seconds) for the cloudformation events'
|
101
|
+
|
102
|
+
method_option 'timeout',
|
103
|
+
type: :numeric,
|
104
|
+
default: 1800,
|
105
|
+
desc: 'Timeout (in seconds) for the stack creation'
|
106
|
+
|
107
|
+
method_option 'fail_on_noop',
|
108
|
+
type: :boolean,
|
109
|
+
default: false,
|
110
|
+
desc: 'Fails if a stack has nothing to update'
|
111
|
+
|
112
|
+
desc 'apply', 'Creates a stack in Cloudformation'
|
113
|
+
def apply
|
114
|
+
opts = process_params(options)
|
115
|
+
|
116
|
+
stack_name = opts['stack_name']
|
117
|
+
|
118
|
+
interval = consume_option(opts, 'interval')
|
119
|
+
timeout = consume_option(opts, 'timeout')
|
120
|
+
retries = timeout / interval
|
121
|
+
fail_on_noop = consume_option(opts, 'fail_on_noop')
|
122
|
+
list_events = consume_option(opts, 'list_events')
|
123
|
+
|
124
|
+
ENV['CFNCLI_LOG_LEVEL'] = consume_option(opts, 'log_level').to_s
|
125
|
+
|
126
|
+
client_config = Config::CfnClient.new(interval, retries, fail_on_noop)
|
127
|
+
|
128
|
+
res = ExitCode::OK
|
129
|
+
cfn.create_stack(opts, client_config)
|
130
|
+
if list_events
|
131
|
+
cfn.events(stack_name, client_config)
|
132
|
+
res = ExitCode::STACK_ERROR unless cfn.stack_successful? stack_name
|
133
|
+
end
|
134
|
+
|
135
|
+
puts "Stack creation #{res == 0 ? 'successful' : 'failed'}"
|
136
|
+
exit res
|
137
|
+
rescue Aws::CloudFormation::Errors::ValidationError => e
|
138
|
+
puts e.message
|
139
|
+
exit ExitCode::VALIDATION_ERROR
|
140
|
+
end
|
141
|
+
|
142
|
+
method_option 'stack_name',
|
143
|
+
alias: '-n',
|
144
|
+
type: :string,
|
145
|
+
required: true,
|
146
|
+
desc: 'Name or ID of the Cloudformation stack'
|
147
|
+
|
148
|
+
# Application options
|
149
|
+
method_option 'interval',
|
150
|
+
type: :numeric,
|
151
|
+
default: 10,
|
152
|
+
desc: 'Polling interval (in seconds) for the cloudformation events'
|
153
|
+
|
154
|
+
method_option 'timeout',
|
155
|
+
type: :numeric,
|
156
|
+
default: 1800,
|
157
|
+
desc: 'Timeout (in seconds) for the stack event listing'
|
158
|
+
|
159
|
+
desc 'events', 'Displays the events for a stack in realtime'
|
160
|
+
def events
|
161
|
+
stack_name = options['stack_name']
|
162
|
+
config = Config::CfnClient.new(options['interval'], options['retries'])
|
163
|
+
cfn.events(stack_name, config)
|
164
|
+
end
|
165
|
+
|
166
|
+
no_tasks do
|
167
|
+
# Reads an option from a hash and deletes it
|
168
|
+
# @param opts [Hash] Hash containing the options
|
169
|
+
# @param option Key to consume
|
170
|
+
# @return value of Key option in opts
|
171
|
+
def consume_option(opts, option)
|
172
|
+
res = opts[option]
|
173
|
+
opts.delete(option)
|
174
|
+
res
|
175
|
+
end
|
176
|
+
|
177
|
+
# Process the parameters to make them compliant with the Cloudformation API
|
178
|
+
# @param opts [Hash] Hash containing the options. The hash will not be modified
|
179
|
+
# @return the processed options hash
|
180
|
+
def process_params(opts)
|
181
|
+
opts = opts.dup
|
182
|
+
check_exclusivity(opts.keys, ['template_body', 'template_url'])
|
183
|
+
check_exclusivity(opts.keys, ['disable_rollback', 'on_failure'])
|
184
|
+
check_exclusivity(opts.keys, ['stack_policy_body', 'stack_policy_url'])
|
185
|
+
check_exclusivity(opts.keys, ['parameters', 'parameters_file'])
|
186
|
+
|
187
|
+
opts['template_body'] = file_or_content(opts['template_body']) if opts['template_body']
|
188
|
+
opts['stack_policy_body'] = file_or_content(opts['stack_policy_body']) if opts['stack_policy_body']
|
189
|
+
opts['parameters'] = process_stack_parameters(opts['parameters']) if opts['parameters']
|
190
|
+
opts['parameters'] = process_stack_parameters_file(consume_option(opts, 'parameters_file')) if opts['parameters_file']
|
191
|
+
|
192
|
+
opts
|
193
|
+
end
|
194
|
+
|
195
|
+
# Check if only one of the arguments is specified in the options
|
196
|
+
# @param options [Arrray<String>] List of available options
|
197
|
+
# @param exclusives [Array<String>] List of mutually exclusive options
|
198
|
+
def check_exclusivity(opts, exclusives)
|
199
|
+
exclusive_options = opts & exclusives
|
200
|
+
if exclusive_options.size > 1
|
201
|
+
fail Thor::Error, "Error: #{exclusive_options} are mutually exclusive."
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
# Gets the content of a string that can either be the
|
206
|
+
# content itself or a filename if beginning by @
|
207
|
+
# @param str [String] String containing either the content or the filename to read
|
208
|
+
def file_or_content(str)
|
209
|
+
return str if str.nil?
|
210
|
+
return str unless file_param? str
|
211
|
+
|
212
|
+
content = File.read(str[1..-1])
|
213
|
+
content
|
214
|
+
end
|
215
|
+
|
216
|
+
# Indicates if the parameter is a file (as opposed to a value)
|
217
|
+
# This is indicated by a leading @
|
218
|
+
def file_param?(param)
|
219
|
+
return false unless param.is_a? String
|
220
|
+
param.start_with? '@'
|
221
|
+
end
|
222
|
+
|
223
|
+
# Converts a parameter JSON file to the format expected by CloudFormation
|
224
|
+
# @param filename Path to the JSON file containing the parameters description
|
225
|
+
# @return
|
226
|
+
def process_stack_parameters_file(filename)
|
227
|
+
content = File.read(filename)
|
228
|
+
return CloudFormation.parse_json_params(JSON.parse(content))
|
229
|
+
end
|
230
|
+
|
231
|
+
# Converts a parameters hash in the format expected by CloudFormation
|
232
|
+
# @param parameters [Hash] Hash containing the parameters to convert
|
233
|
+
def process_stack_parameters(parameters)
|
234
|
+
return {} unless parameters
|
235
|
+
|
236
|
+
# Returns the content of the file if parameters is a file
|
237
|
+
return file_or_content(parameters) if file_param? parameters
|
238
|
+
|
239
|
+
# Otherwise convert each param to the cfn structure
|
240
|
+
parameters.map do |key, value|
|
241
|
+
{
|
242
|
+
parameter_key: key,
|
243
|
+
parameter_value: value
|
244
|
+
}
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
# Cloudformation utility object
|
249
|
+
def cfn
|
250
|
+
@cfn ||= CfnCli::CloudFormation.new
|
251
|
+
end
|
252
|
+
end
|
253
|
+
end
|
254
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
require 'cfncli/cfn_client'
|
2
|
+
require 'cfncli/stack'
|
3
|
+
require 'cfncli/logger'
|
4
|
+
require 'cfncli/event_poller'
|
5
|
+
|
6
|
+
require 'waiting'
|
7
|
+
require 'pp'
|
8
|
+
|
9
|
+
module CfnCli
|
10
|
+
class CloudFormation
|
11
|
+
include CfnClient
|
12
|
+
include Loggable
|
13
|
+
|
14
|
+
def initialize
|
15
|
+
end
|
16
|
+
|
17
|
+
# Creates a stack and wait for the creation to be finished
|
18
|
+
# @param options [Hash] Options for the stack creation
|
19
|
+
# (@see http://docs.aws.amazon.com/sdkforruby/api/Aws/CloudFormation/Client.html)
|
20
|
+
def create_stack(options, config = nil)
|
21
|
+
create_or_update_stack(options, config)
|
22
|
+
end
|
23
|
+
|
24
|
+
# Creates a stack if it doesn't exist otherwise update it
|
25
|
+
def create_or_update_stack(options, config = nil)
|
26
|
+
opts = process_params(options.dup)
|
27
|
+
|
28
|
+
stack_name = opts['stack_name']
|
29
|
+
|
30
|
+
stack = create_stack_obj(stack_name, config)
|
31
|
+
|
32
|
+
if stack.exists?
|
33
|
+
stack.update(opts)
|
34
|
+
else
|
35
|
+
stack.create(opts)
|
36
|
+
end
|
37
|
+
|
38
|
+
stack
|
39
|
+
end
|
40
|
+
|
41
|
+
# List stack events
|
42
|
+
def events(stack_name, config)
|
43
|
+
stack = create_stack_obj(stack_name, config)
|
44
|
+
stack.list_events(EventPoller.new, config)
|
45
|
+
end
|
46
|
+
|
47
|
+
# Returns the stack stack
|
48
|
+
def stack_successful?(stack_name)
|
49
|
+
Stack.new(stack_name).succeeded?
|
50
|
+
end
|
51
|
+
|
52
|
+
# Converts the 'standard' json stack parameters format to the format
|
53
|
+
# expected by the API
|
54
|
+
# (see https://blogs.aws.amazon.com/application-management/post/Tx1A23GYVMVFKFD/Passing-Parameters-to-CloudFormation-Stacks-with-the-AWS-CLI-and-Powershell)
|
55
|
+
def self.parse_json_params(params)
|
56
|
+
params.map do |param|
|
57
|
+
{
|
58
|
+
parameter_key: param.first.first,
|
59
|
+
parameter_value: param.first.last
|
60
|
+
}
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
# Creates a new stack object
|
67
|
+
# Mainly useful to mock it in unit tests
|
68
|
+
def create_stack_obj(stack_name, config)
|
69
|
+
CfnCli::Stack.new(stack_name, config)
|
70
|
+
end
|
71
|
+
|
72
|
+
# Process the parameters
|
73
|
+
def process_params(opts)
|
74
|
+
opts.delete('disable_rollback')
|
75
|
+
opts
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
module CfnCli
|
4
|
+
module Config
|
5
|
+
def self.load_from_file(filename)
|
6
|
+
content = YAML::load_file(filename)
|
7
|
+
Parameters.new(content)
|
8
|
+
rescue Errno::ENOENT
|
9
|
+
nil
|
10
|
+
end
|
11
|
+
|
12
|
+
class CfnClient
|
13
|
+
attr_accessor :interval
|
14
|
+
attr_accessor :retries
|
15
|
+
attr_accessor :fail_on_noop
|
16
|
+
|
17
|
+
def initialize(interval = 10, retries = 30, fail_on_noop = false)
|
18
|
+
@interval = interval
|
19
|
+
@retries = retries
|
20
|
+
@fail_on_noop = fail_on_noop
|
21
|
+
end
|
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
|
+
end
|
87
|
+
end
|
data/lib/cfncli/event.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'cfncli/states'
|
2
|
+
|
3
|
+
module CfnCli
|
4
|
+
class Event
|
5
|
+
include CfnStates
|
6
|
+
|
7
|
+
attr_reader :event
|
8
|
+
|
9
|
+
def initialize(event)
|
10
|
+
@event = event
|
11
|
+
end
|
12
|
+
|
13
|
+
def status
|
14
|
+
event.resource_status
|
15
|
+
end
|
16
|
+
|
17
|
+
def color
|
18
|
+
return :green if succeeded?
|
19
|
+
return :yellow if in_progress?
|
20
|
+
return :red if failed?
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_s
|
24
|
+
"#{event.timestamp} #{event.resource_status} #{event.resource_type} #{event.logical_resource_id} #{event.resource_status_reason}"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'colorize'
|
2
|
+
require 'cfncli/event'
|
3
|
+
|
4
|
+
module CfnCli
|
5
|
+
class EventPoller
|
6
|
+
def initialize
|
7
|
+
end
|
8
|
+
|
9
|
+
def event(event)
|
10
|
+
colorize Event.new(event)
|
11
|
+
end
|
12
|
+
|
13
|
+
def colorize(event)
|
14
|
+
puts event.to_s.colorize(event.color)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module CfnCli
|
2
|
+
class EventStreamer
|
3
|
+
attr_reader :stack
|
4
|
+
attr_reader :config
|
5
|
+
|
6
|
+
def initialize(stack, config = nil)
|
7
|
+
@stack = stack
|
8
|
+
@config = config || default_config
|
9
|
+
@seen_events = []
|
10
|
+
end
|
11
|
+
|
12
|
+
def default_config
|
13
|
+
Config::CfnClient.new
|
14
|
+
end
|
15
|
+
|
16
|
+
# Wait for events. This will exit when the
|
17
|
+
# stack reaches a finished state
|
18
|
+
# @yields [CfnEvent] Events for the stack
|
19
|
+
def each_event
|
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
|
24
|
+
|
25
|
+
waiter.done if stack.finished?
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
attr_accessor :seen_events
|
32
|
+
|
33
|
+
# Indicates if an event has already been seen
|
34
|
+
def seen?(event)
|
35
|
+
res = seen_events.include? event.id
|
36
|
+
seen_events << event.id
|
37
|
+
res
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'logger'
|
2
|
+
|
3
|
+
module CfnCli
|
4
|
+
# Logger Mixin
|
5
|
+
module Loggable
|
6
|
+
# Gets the logger object
|
7
|
+
#
|
8
|
+
# By default it logs to STDOUT with the date-time format +%Y-%m-%d %H:%M:%S +
|
9
|
+
#
|
10
|
+
# @return [Logger]
|
11
|
+
def logger
|
12
|
+
if @logger.nil?
|
13
|
+
@logger = Logger.new(STDOUT)
|
14
|
+
@logger.level = ENV['CFNCLI_LOG_LEVEL'].to_i || Logger::INFO
|
15
|
+
@logger.formatter = proc do |severity, datetime, progname, msg|
|
16
|
+
severity = severity.ljust(7)
|
17
|
+
progname = "#{progname}: " if progname
|
18
|
+
"#{datetime} [ #{severity} ] #{progname}#{msg}\n"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
@logger
|
22
|
+
end
|
23
|
+
|
24
|
+
# Sets the logger object
|
25
|
+
# @param obj [Logger]
|
26
|
+
def logger=(obj)
|
27
|
+
@logger = obj
|
28
|
+
end
|
29
|
+
|
30
|
+
# Gets the current log level as a symbol
|
31
|
+
# @return [Symbol]
|
32
|
+
def log_level
|
33
|
+
level_map = {
|
34
|
+
Logger::DEBUG => :debug,
|
35
|
+
Logger::INFO => :info,
|
36
|
+
Logger::WARN => :warn,
|
37
|
+
Logger::ERROR => :error,
|
38
|
+
Logger::FATAL => :fatal,
|
39
|
+
Logger::UNKNOWN => :unknown
|
40
|
+
}
|
41
|
+
level_map[logger.level]
|
42
|
+
end
|
43
|
+
|
44
|
+
# Sets the current log level
|
45
|
+
def log_level=(level)
|
46
|
+
if level.is_a?(Fixnum)
|
47
|
+
logger.level = level
|
48
|
+
else
|
49
|
+
logger.level = Logger.const_get(level.to_s.upcase)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
data/lib/cfncli/stack.rb
ADDED
@@ -0,0 +1,122 @@
|
|
1
|
+
require 'cfncli/cfn_client'
|
2
|
+
require 'cfncli/logger'
|
3
|
+
require 'cfncli/config'
|
4
|
+
require 'cfncli/event_streamer'
|
5
|
+
require 'cfncli/states'
|
6
|
+
|
7
|
+
require 'waiting'
|
8
|
+
|
9
|
+
module CfnCli
|
10
|
+
class Stack
|
11
|
+
include CfnCli::CfnClient
|
12
|
+
include CfnCli::CfnStates
|
13
|
+
include Loggable
|
14
|
+
|
15
|
+
attr_reader :stack_name
|
16
|
+
|
17
|
+
class StackNotFoundError < StandardError; end
|
18
|
+
|
19
|
+
def initialize(stack_name, config = nil)
|
20
|
+
@stack = nil
|
21
|
+
@stack_id = nil
|
22
|
+
@stack_name = stack_name
|
23
|
+
@config = config || default_config
|
24
|
+
end
|
25
|
+
|
26
|
+
def default_config
|
27
|
+
Config::CfnClient.new
|
28
|
+
end
|
29
|
+
|
30
|
+
def fail_on_noop?
|
31
|
+
@config.fail_on_noop
|
32
|
+
end
|
33
|
+
|
34
|
+
def stack_id
|
35
|
+
@stack_id || @stack_name
|
36
|
+
end
|
37
|
+
|
38
|
+
def stack
|
39
|
+
fetch_stack
|
40
|
+
end
|
41
|
+
|
42
|
+
def exists?
|
43
|
+
stack.exists?
|
44
|
+
end
|
45
|
+
|
46
|
+
# Creates a new stack
|
47
|
+
# @param opts Hash containing the options for `create_stack`
|
48
|
+
# (see http://docs.aws.amazon.com/sdkforruby/api/Aws/CloudFormation/Resource.html#create_stack-instance_method)
|
49
|
+
def create(opts)
|
50
|
+
logger.debug "Creating stack #{stack_name} (#{opts.inspect})"
|
51
|
+
@stack = cfn.create_stack(opts)
|
52
|
+
stack.wait_until_exists
|
53
|
+
@stack_id = stack.stack_id
|
54
|
+
end
|
55
|
+
|
56
|
+
# Updates an existing stack
|
57
|
+
# @param opts Hash containing the options for `update_stack`
|
58
|
+
# (see http://docs.aws.amazon.com/sdkforruby/api/Aws/CloudFormation/Client.html#update_stack-instance_method)
|
59
|
+
def update(opts)
|
60
|
+
logger.debug "Updating stack #{stack_name} (#{opts.inspect})"
|
61
|
+
resp = cfn.client.update_stack(opts)
|
62
|
+
@stack_id = resp.stack_id
|
63
|
+
rescue Aws::CloudFormation::Errors::ValidationError => e
|
64
|
+
unless !fail_on_noop? && e.message.include?('No updates are to be performed')
|
65
|
+
raise e
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# Waits for a stack to be in a finished state
|
70
|
+
# @return A boolean indicating if the operation was succesful
|
71
|
+
def wait_for_completion
|
72
|
+
Waiting.wait(max_attempts: @config.retries, interval: @config.interval) do |waiter|
|
73
|
+
waiter.done if finished?
|
74
|
+
end
|
75
|
+
succeeded?
|
76
|
+
rescue Waiting::TimedOutError => e
|
77
|
+
logger.error "Timed out while waiting for the stack #{inspect} to be created(#{e.message})"
|
78
|
+
false
|
79
|
+
end
|
80
|
+
|
81
|
+
# List all events in real time
|
82
|
+
# @param poller [CfnCli::Poller] Poller class to display events
|
83
|
+
def list_events(poller, config = nil)
|
84
|
+
streamer = EventStreamer.new(self, config)
|
85
|
+
streamer.each_event do |event|
|
86
|
+
poller.event(event)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# Get the events from the cfn stack
|
91
|
+
def events(next_token)
|
92
|
+
stack.events(next_token)
|
93
|
+
end
|
94
|
+
|
95
|
+
# Indicates if the stack is in a finished state
|
96
|
+
def finished?
|
97
|
+
return false if stack.nil?
|
98
|
+
finished_states.include? stack.stack_status
|
99
|
+
end
|
100
|
+
|
101
|
+
# Indicates if the stack is in a successful state
|
102
|
+
def succeeded?
|
103
|
+
res = success_states.include? stack.stack_status
|
104
|
+
return false if stack.nil?
|
105
|
+
res
|
106
|
+
end
|
107
|
+
|
108
|
+
# Indicates if the stack is in a transition state
|
109
|
+
def in_progress?
|
110
|
+
return false if stack.nil?
|
111
|
+
transitive_states.include? stack.stack_status
|
112
|
+
end
|
113
|
+
|
114
|
+
private
|
115
|
+
|
116
|
+
# Gets stack info from the cfn API
|
117
|
+
def fetch_stack
|
118
|
+
@stack = cfn.stack(stack_id)
|
119
|
+
@stack
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
module CfnCli
|
2
|
+
module CfnStates
|
3
|
+
# Indicates if the state is finished
|
4
|
+
def finished?
|
5
|
+
finished_states.include? status
|
6
|
+
end
|
7
|
+
|
8
|
+
# Indicates if the state is successful
|
9
|
+
def succeeded?
|
10
|
+
success_states.include? status
|
11
|
+
end
|
12
|
+
|
13
|
+
# Indicates if the state is a transition
|
14
|
+
def in_progress?
|
15
|
+
transitive_states.include? status
|
16
|
+
end
|
17
|
+
|
18
|
+
# Indicates if the state is failed
|
19
|
+
def failed?
|
20
|
+
!succeeded? && !in_progress?
|
21
|
+
end
|
22
|
+
|
23
|
+
# List of possible states
|
24
|
+
def states
|
25
|
+
[
|
26
|
+
'CREATE_IN_PROGRESS',
|
27
|
+
'CREATE_IN_PROGRESS',
|
28
|
+
'CREATE_FAILED',
|
29
|
+
'CREATE_COMPLETE',
|
30
|
+
'ROLLBACK_IN_PROGRESS',
|
31
|
+
'ROLLBACK_FAILED',
|
32
|
+
'ROLLBACK_COMPLETE',
|
33
|
+
'DELETE_IN_PROGRESS',
|
34
|
+
'DELETE_FAILED',
|
35
|
+
'DELETE_COMPLETE',
|
36
|
+
'UPDATE_IN_PROGRESS',
|
37
|
+
'UPDATE_COMPLETE_CLEANUP_IN_PROGRESS',
|
38
|
+
'UPDATE_COMPLETE',
|
39
|
+
'UPDATE_ROLLBACK_IN_PROGRESS',
|
40
|
+
'UPDATE_ROLLBACK_FAILED',
|
41
|
+
'UPDATE_ROLLBACK_COMPLETE_CLEANUP_IN_PROGRESS',
|
42
|
+
'UPDATE_ROLLBACK_COMPLETE',
|
43
|
+
]
|
44
|
+
end
|
45
|
+
|
46
|
+
# List of successful states
|
47
|
+
def success_states
|
48
|
+
[
|
49
|
+
'CREATE_COMPLETE',
|
50
|
+
'DELETE_COMPLETE',
|
51
|
+
'UPDATE_COMPLETE'
|
52
|
+
]
|
53
|
+
end
|
54
|
+
|
55
|
+
# List of transitive states
|
56
|
+
def transitive_states
|
57
|
+
states.select do |state|
|
58
|
+
state.end_with? 'IN_PROGRESS'
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# List of finished states
|
63
|
+
def finished_states
|
64
|
+
states - transitive_states
|
65
|
+
end
|
66
|
+
|
67
|
+
# List of failed or unknown states
|
68
|
+
def failed_states
|
69
|
+
states - success_states - transitive_states
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
metadata
ADDED
@@ -0,0 +1,235 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: cfncli
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.3.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- lethalpaga
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-03-04 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.10'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.10'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ~>
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ~>
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ! '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ! '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rspec-its
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1.2'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ~>
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '1.2'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: cucumber
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ~>
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '2'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ~>
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '2'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: guard
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ~>
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '2'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ~>
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '2'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: guard-rspec
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ! '>='
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ! '>='
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: thor
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ! '>='
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :runtime
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ! '>='
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: aws-sdk
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - ~>
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '2'
|
132
|
+
type: :runtime
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - ~>
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '2'
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: waiting
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - ~>
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: '0'
|
146
|
+
type: :runtime
|
147
|
+
prerelease: false
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - ~>
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: '0'
|
153
|
+
- !ruby/object:Gem::Dependency
|
154
|
+
name: activesupport
|
155
|
+
requirement: !ruby/object:Gem::Requirement
|
156
|
+
requirements:
|
157
|
+
- - ~>
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
version: '4'
|
160
|
+
type: :runtime
|
161
|
+
prerelease: false
|
162
|
+
version_requirements: !ruby/object:Gem::Requirement
|
163
|
+
requirements:
|
164
|
+
- - ~>
|
165
|
+
- !ruby/object:Gem::Version
|
166
|
+
version: '4'
|
167
|
+
- !ruby/object:Gem::Dependency
|
168
|
+
name: colorize
|
169
|
+
requirement: !ruby/object:Gem::Requirement
|
170
|
+
requirements:
|
171
|
+
- - ~>
|
172
|
+
- !ruby/object:Gem::Version
|
173
|
+
version: '0'
|
174
|
+
type: :runtime
|
175
|
+
prerelease: false
|
176
|
+
version_requirements: !ruby/object:Gem::Requirement
|
177
|
+
requirements:
|
178
|
+
- - ~>
|
179
|
+
- !ruby/object:Gem::Version
|
180
|
+
version: '0'
|
181
|
+
description: Creates a Cloudformation stack synchronously
|
182
|
+
email:
|
183
|
+
- lethalpaga@gmail.com
|
184
|
+
executables:
|
185
|
+
- cfncli
|
186
|
+
extensions: []
|
187
|
+
extra_rdoc_files: []
|
188
|
+
files:
|
189
|
+
- .gitignore
|
190
|
+
- .rspec
|
191
|
+
- .travis.yml
|
192
|
+
- Gemfile
|
193
|
+
- Guardfile
|
194
|
+
- README.md
|
195
|
+
- Rakefile
|
196
|
+
- bin/console
|
197
|
+
- bin/setup
|
198
|
+
- cfncli.gemspec
|
199
|
+
- exe/cfncli
|
200
|
+
- lib/cfncli.rb
|
201
|
+
- lib/cfncli/cfn_client.rb
|
202
|
+
- lib/cfncli/cli.rb
|
203
|
+
- lib/cfncli/cloudformation.rb
|
204
|
+
- lib/cfncli/config.rb
|
205
|
+
- lib/cfncli/event.rb
|
206
|
+
- lib/cfncli/event_poller.rb
|
207
|
+
- lib/cfncli/event_streamer.rb
|
208
|
+
- lib/cfncli/logger.rb
|
209
|
+
- lib/cfncli/stack.rb
|
210
|
+
- lib/cfncli/states.rb
|
211
|
+
- lib/cfncli/version.rb
|
212
|
+
homepage: https://github.com/lethalpaga/cfncli
|
213
|
+
licenses: []
|
214
|
+
metadata: {}
|
215
|
+
post_install_message:
|
216
|
+
rdoc_options: []
|
217
|
+
require_paths:
|
218
|
+
- lib
|
219
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
220
|
+
requirements:
|
221
|
+
- - ! '>='
|
222
|
+
- !ruby/object:Gem::Version
|
223
|
+
version: '0'
|
224
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
225
|
+
requirements:
|
226
|
+
- - ! '>='
|
227
|
+
- !ruby/object:Gem::Version
|
228
|
+
version: '0'
|
229
|
+
requirements: []
|
230
|
+
rubyforge_project:
|
231
|
+
rubygems_version: 2.4.5
|
232
|
+
signing_key:
|
233
|
+
specification_version: 4
|
234
|
+
summary: Creates cloudformation stacks
|
235
|
+
test_files: []
|