stackit 0.1.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 +7 -0
- data/.gitignore +10 -0
- data/.rspec +2 -0
- data/.travis.yml +4 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +16 -0
- data/README.md +38 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/bin/stackit +4 -0
- data/lib/stackit/aws.rb +3 -0
- data/lib/stackit/cli.rb +168 -0
- data/lib/stackit/stack/default_notifier.rb +45 -0
- data/lib/stackit/stack/managed_stack.rb +268 -0
- data/lib/stackit/stack.rb +177 -0
- data/lib/stackit/template.rb +51 -0
- data/lib/stackit/version.rb +3 -0
- data/lib/stackit/wait.rb +71 -0
- data/lib/stackit.rb +43 -0
- data/stackit.gemspec +40 -0
- metadata +178 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 426eae8667b341e8834521f00605632edd98b19d
|
4
|
+
data.tar.gz: dc9890de3f68a1ee7198ea22597f46bbd7266a39
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 8cc56791184964fbf5752e5cf493b3c4cb5845c43beae200d33845dbc0bce23b5e34ffe3a4903a1c223943817fe942d3f7cc9198150b2279a0053d98039fb52f
|
7
|
+
data.tar.gz: 196c75963e0d8fe4c4ebde6645a2d4eab0103028c18622856b87ae3c198dfe3ca287b74a6fc878e70fb3944e94f86ad406cb7be84b4f82c2135724ad86fae9fa
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
stackit :: Simple, elegant CloudFormation dependency management.
|
2
|
+
Copyright (C) 2016 Jeremy Hahn
|
3
|
+
|
4
|
+
This program is free software: you can redistribute it and/or modify
|
5
|
+
it under the terms of the GNU General Public License as published by
|
6
|
+
the Free Software Foundation, either version 3 of the License, or
|
7
|
+
(at your option) any later version.
|
8
|
+
|
9
|
+
This program is distributed in the hope that it will be useful,
|
10
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
11
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
12
|
+
GNU General Public License for more details.
|
13
|
+
|
14
|
+
You should have received a copy of the GNU General Public License
|
15
|
+
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
16
|
+
|
data/README.md
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
# stackit
|
2
|
+
|
3
|
+
Simple, elegant CloudFormation dependency management.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
gem 'stackit'
|
11
|
+
```
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
$ bundle
|
16
|
+
|
17
|
+
Or install it yourself as:
|
18
|
+
|
19
|
+
$ gem install stackit
|
20
|
+
|
21
|
+
## Usage
|
22
|
+
|
23
|
+
TODO: Write usage instructions here
|
24
|
+
|
25
|
+
## Development
|
26
|
+
|
27
|
+
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.
|
28
|
+
|
29
|
+
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).
|
30
|
+
|
31
|
+
## Contributing
|
32
|
+
|
33
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/jeremyhahn/stackit.
|
34
|
+
|
35
|
+
|
36
|
+
## License
|
37
|
+
|
38
|
+
The gem is available as open source under the terms of the [GNU GENERAL PUBLIC LICENSE](http://www.gnu.org/licenses/gpl-3.0.en.html).
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "stackit"
|
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/bin/stackit
ADDED
data/lib/stackit/aws.rb
ADDED
data/lib/stackit/cli.rb
ADDED
@@ -0,0 +1,168 @@
|
|
1
|
+
require 'stackit'
|
2
|
+
require 'thor'
|
3
|
+
|
4
|
+
module Stackit
|
5
|
+
class Cli < Thor
|
6
|
+
|
7
|
+
class_option :environment, :aliases => '-e', :desc => "Your stack environment (dev, staging, prod, etc)", :default => 'default'
|
8
|
+
class_option :profile, desc: 'AWS profile'
|
9
|
+
class_option :region, desc: 'AWS region', default: 'us-east-1'
|
10
|
+
class_option :debug, type: :boolean, default: false
|
11
|
+
class_option :verbose, type: :boolean, default: false
|
12
|
+
class_option :assume_role, type: :hash, :desc => 'IAM role name and optional duration to keep the STS token valid'
|
13
|
+
|
14
|
+
def initialize(*args)
|
15
|
+
super(*args)
|
16
|
+
init_cli
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.banner(task, namespace = true, subcommand = false)
|
20
|
+
#"#{basename} #{subcommand_prefix} #{task.usage}"
|
21
|
+
"#{basename} #{task.usage}"
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.subcommand_prefix
|
25
|
+
self.name.gsub(%r{.*::}, '').gsub(%r{^[A-Z]}) { |match| match[0].downcase }.gsub(%r{[A-Z]}) { |match| "-#{match[0].downcase}" }
|
26
|
+
end
|
27
|
+
|
28
|
+
desc 'create', 'Creates a new CloudFormation stack'
|
29
|
+
method_option :template, aliases: '-t', desc: 'The cloudformation template', :required => true
|
30
|
+
method_option :stack_name, aliases: '-n', desc: 'The stack name. Defaults to the camelized template file name', :required => true
|
31
|
+
method_option :stack_policy, :aliases => '-p', :desc => 'A local file system or S3 (HTTPS) path to the stack policy'
|
32
|
+
method_option :depends, :aliases => '-d', :type => :array, :default => [], :desc => 'Space delimited list of stack names to automatically map parameter values from'
|
33
|
+
method_option :parameters, aliases: '-p', type: :hash, desc: 'Parameters supplied to the cloudformation template', default: {}
|
34
|
+
method_option :parameters_file, desc: 'Parameters supplied to the cloudformation template'
|
35
|
+
method_option :parameter_map, :aliases => '-pm', type: :hash, default: {}, desc: 'Parameter map used to direct dependent parameter values to stack template parameters'
|
36
|
+
method_option :wait, :aliases => '-w', type: :boolean, default: false, desc: 'Wait for the stack to enter STATUS_COMPLETE before returning or raise an exception if it times out'
|
37
|
+
method_option :force, :desc => 'Force a stack update on unchanged templates'
|
38
|
+
method_option :dry_run, :type => :boolean, :default => false, :desc => 'Run all code except AWS API calls'
|
39
|
+
def create
|
40
|
+
ManagedStack.new({
|
41
|
+
template: options[:template],
|
42
|
+
stack_name: options[:stack_name],
|
43
|
+
stack_policy: options[:stack_policy],
|
44
|
+
depends: options[:depends],
|
45
|
+
user_defined_parameters: options[:parameters],
|
46
|
+
parameters_file: options[:parameters_file],
|
47
|
+
parameter_map: options[:parameter_map],
|
48
|
+
wait: options[:wait],
|
49
|
+
force: options[:force],
|
50
|
+
dry_run: options[:dry_run],
|
51
|
+
debug: !!options[:debug]
|
52
|
+
}).create!
|
53
|
+
end
|
54
|
+
|
55
|
+
desc 'update', 'Updates an existing CloudFormation stack'
|
56
|
+
method_option :template, aliases: '-t', desc: 'The cloudformation template', :required => true
|
57
|
+
method_option :stack_name, aliases: '-n', desc: 'The stack name. Defaults to the camelized template file name', :required => true
|
58
|
+
method_option :stack_policy, :aliases => '-p', :desc => 'A local file system or S3 (HTTPS) path to the stack policy'
|
59
|
+
method_option :stack_policy_during_update, :aliases => '-pu', :desc => 'A local file system or S3 (HTTPS) path to the stack policy to use during update'
|
60
|
+
method_option :depends, :aliases => '-d', :type => :array, :default => [], :desc => 'Space delimited list of stack names to automatically map parameter values from'
|
61
|
+
method_option :parameters, aliases: '-p', type: :hash, desc: 'Parameters supplied to the cloudformation template', default: {}
|
62
|
+
method_option :parameters_file, desc: 'Parameters supplied to the cloudformation template'
|
63
|
+
method_option :parameter_map, :aliases => '-pm', type: :hash, default: {}, desc: 'Parameter map used to direct dependent parameter values to stack template parameters'
|
64
|
+
method_option :wait, :aliases => '-w', type: :boolean, default: false, desc: 'Wait for the stack to enter STATUS_COMPLETE before returning or raise an exception if it times out'
|
65
|
+
method_option :force, :desc => 'Force a stack update on unchanged templates'
|
66
|
+
method_option :dry_run, :type => :boolean, :default => false, :desc => 'Run all code except AWS API calls'
|
67
|
+
def update
|
68
|
+
ManagedStack.new({
|
69
|
+
template: options[:template],
|
70
|
+
stack_name: options[:stack_name],
|
71
|
+
stack_policy: options[:stack_policy],
|
72
|
+
stack_policy_during_update: options[:stack_policy_during_update],
|
73
|
+
depends: options[:depends],
|
74
|
+
user_defined_parameters: options[:parameters],
|
75
|
+
parameters_file: options[:parameters_file],
|
76
|
+
parameter_map: options[:parameter_map],
|
77
|
+
wait: options[:wait],
|
78
|
+
force: options[:force],
|
79
|
+
dry_run: options[:dry_run],
|
80
|
+
debug: !!options[:debug]
|
81
|
+
}).update!
|
82
|
+
end
|
83
|
+
|
84
|
+
desc 'delete', 'Deletes a CloudFormation stack'
|
85
|
+
method_option :stack_name, aliases: '-n', desc: 'The stack name. Defaults to the camelized template file name', :required => true
|
86
|
+
method_option :retain_resources, :aliases => '-r', :type => :array, :desc => 'Space delimited list of logical resource ids to retain after the stack is deleted'
|
87
|
+
method_option :wait, :aliases => '-w', type: :boolean, default: false, desc: 'Wait for the stack to enter STATUS_COMPLETE before returning or raise an exception if it times out'
|
88
|
+
method_option :dry_run, :type => :boolean, :default => false, :desc => 'Run all code except AWS API calls'
|
89
|
+
def delete
|
90
|
+
ManagedStack.new({
|
91
|
+
stack_name: options[:stack_name],
|
92
|
+
wait: options[:wait],
|
93
|
+
dry_run: options[:dry_run],
|
94
|
+
debug: !!options[:debug]
|
95
|
+
}).delete!
|
96
|
+
end
|
97
|
+
|
98
|
+
desc 'create-keypair', 'Creates a new EC2 keypair'
|
99
|
+
method_option :name, desc: 'The name of the keypair', :required => true
|
100
|
+
def create_keypair
|
101
|
+
puts Stackit.aws.ec2.create_key_pair({
|
102
|
+
key_name: options['name']
|
103
|
+
})['key_material']
|
104
|
+
end
|
105
|
+
|
106
|
+
desc 'delete-keypair', 'Deletes an existing EC2 keypair'
|
107
|
+
method_option :name, desc: 'The name of the keypair', :required => true
|
108
|
+
def delete_keypair
|
109
|
+
Stackit.aws.ec2.delete_key_pair({
|
110
|
+
key_name: options['name']
|
111
|
+
})
|
112
|
+
end
|
113
|
+
|
114
|
+
desc 'version', 'Displays StackIT version'
|
115
|
+
def version
|
116
|
+
puts <<-LOGO
|
117
|
+
_____ _ _ _______ _______
|
118
|
+
(_____) (_)_ (_) _ (_______)(__ _ __)
|
119
|
+
(_)___ (___) ____ ___ (_)(_) (_) (_)
|
120
|
+
(___)_ (_) (____) _(___)(___) (_) (_)
|
121
|
+
____(_)(_)_( )_( )(_)___ (_)(_) __(_)__ (_)
|
122
|
+
(_____) (__)(__)_) (____)(_) (_)(_______) (_) v#{Stackit::VERSION}
|
123
|
+
|
124
|
+
Simple, elegant CloudFormation dependency management.
|
125
|
+
|
126
|
+
LOGO
|
127
|
+
end
|
128
|
+
|
129
|
+
def self.exit_on_failure?
|
130
|
+
true
|
131
|
+
end
|
132
|
+
|
133
|
+
no_commands do
|
134
|
+
|
135
|
+
def init_cli
|
136
|
+
|
137
|
+
Stackit.aws.region = options[:region] if options[:region]
|
138
|
+
Stackit.environment = options[:environment].to_sym if options[:environment]
|
139
|
+
Stackit.debug = !!options[:debug]
|
140
|
+
if Stackit.debug
|
141
|
+
Stackit.logger.level = Logger::DEBUG
|
142
|
+
Stackit.logger.debug "Initializing CLI in debug mode."
|
143
|
+
begin
|
144
|
+
require 'pry-byebug'
|
145
|
+
rescue LoadError; end
|
146
|
+
elsif options[:verbose]
|
147
|
+
Stackit.logger.level = Logger::INFO
|
148
|
+
else
|
149
|
+
Stackit.logger.level = Logger::ERROR
|
150
|
+
end
|
151
|
+
if options[:profile]
|
152
|
+
Stackit.aws.profile = options[:profile]
|
153
|
+
elsif options[:environment]
|
154
|
+
Stackit.aws.credentials = Stackit.aws.load_credentials(
|
155
|
+
options[:environment]
|
156
|
+
)
|
157
|
+
end
|
158
|
+
if options[:assume_role] && options[:assume_role].has_key?('name')
|
159
|
+
name = options[:assume_role]['name']
|
160
|
+
duration = options[:assume_role].has_key?('duration') ? options[:assume_role]['duration'] : 3600
|
161
|
+
Stackit.aws.assume_role!(name, duration)
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
end
|
166
|
+
|
167
|
+
end
|
168
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'thor'
|
2
|
+
|
3
|
+
module Stackit
|
4
|
+
|
5
|
+
class ThorNotifier < Thor
|
6
|
+
|
7
|
+
include Thor::Actions
|
8
|
+
|
9
|
+
def initialize(*args)
|
10
|
+
super(*args)
|
11
|
+
end
|
12
|
+
|
13
|
+
no_commands do
|
14
|
+
|
15
|
+
def backtrace(e)
|
16
|
+
puts
|
17
|
+
puts e.backtrace
|
18
|
+
end
|
19
|
+
|
20
|
+
def error(message)
|
21
|
+
say_status 'ERROR', message, :red
|
22
|
+
end
|
23
|
+
|
24
|
+
def success(message)
|
25
|
+
say_status 'OK', message
|
26
|
+
end
|
27
|
+
|
28
|
+
def response(response, message = 'Success', respond_to_key = 'stack_id')
|
29
|
+
if response.is_a?(::Seahorse::Client::Response)
|
30
|
+
if response.respond_to?(respond_to_key)
|
31
|
+
success(response.send(respond_to_key))
|
32
|
+
else
|
33
|
+
success(message)
|
34
|
+
end
|
35
|
+
else
|
36
|
+
error(response)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
class DefaultNotifier < ThorNotifier; end
|
44
|
+
|
45
|
+
end
|
@@ -0,0 +1,268 @@
|
|
1
|
+
module Stackit
|
2
|
+
class ManagedStack < Stack
|
3
|
+
|
4
|
+
attr_accessor :template
|
5
|
+
attr_accessor :file_parameters
|
6
|
+
attr_accessor :user_defined_parameters
|
7
|
+
attr_accessor :parameter_map
|
8
|
+
attr_accessor :dry_run
|
9
|
+
attr_accessor :depends
|
10
|
+
attr_accessor :debug
|
11
|
+
attr_accessor :force
|
12
|
+
attr_accessor :wait
|
13
|
+
attr_accessor :notifier
|
14
|
+
|
15
|
+
def initialize(options={})
|
16
|
+
super(options)
|
17
|
+
options = options.to_h.symbolize_keys!
|
18
|
+
@template = create_template(options[:template])
|
19
|
+
@user_defined_parameters = symbolized_user_defined_parameters(options[:user_defined_parameters])
|
20
|
+
@parameter_map = symbolized_parameter_map(options[:parameter_map])
|
21
|
+
@stack_name = options[:stack_name] || default_stack_name
|
22
|
+
@depends = options[:depends] || []
|
23
|
+
@debug = !!options[:debug] || Stackit.debug
|
24
|
+
@force = options[:force]
|
25
|
+
@wait = options[:wait]
|
26
|
+
@dry_run = options[:dry_run]
|
27
|
+
@notifier = options[:notifier] || Stackit::ThorNotifier.new
|
28
|
+
parse_file_parameters(options[:parameters_file]) if options[:parameters_file]
|
29
|
+
end
|
30
|
+
|
31
|
+
def symbolized_user_defined_parameters(params)
|
32
|
+
if params
|
33
|
+
params.symbolize_keys!
|
34
|
+
else
|
35
|
+
{}
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def symbolized_parameter_map(param_map)
|
40
|
+
if param_map
|
41
|
+
param_map.symbolize_keys!
|
42
|
+
else
|
43
|
+
[]
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def default_stack_name
|
48
|
+
self.class.name.demodulize
|
49
|
+
end
|
50
|
+
|
51
|
+
def create_template(t)
|
52
|
+
template_path = t ? t : File.join(Dir.pwd, 'cloudformation', "#{stack_name.underscore.dasherize}.json")
|
53
|
+
return unless File.exist?(template_path)
|
54
|
+
template = Template.new(:template_path => template_path)
|
55
|
+
template.parse!
|
56
|
+
end
|
57
|
+
|
58
|
+
def parse_file_parameters(parameters_file)
|
59
|
+
if File.exist?(parameters_file)
|
60
|
+
@file_parameters = {}
|
61
|
+
file_params = JSON.parse(File.read(parameters_file))
|
62
|
+
file_params.inject(@file_parameters) do |hash, param|
|
63
|
+
hash.merge!(param['ParameterKey'].to_sym => param['ParameterValue'])
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def create!
|
69
|
+
begin
|
70
|
+
response = cloudformation_request(:create_stack)
|
71
|
+
notifier.response(response)
|
72
|
+
rescue ::Aws::CloudFormation::Errors::AlreadyExistsException => e
|
73
|
+
notifier.backtrace(e) if Stackit.debug
|
74
|
+
notifier.error(e.message)
|
75
|
+
rescue ::Aws::CloudFormation::Errors::ValidationError => e
|
76
|
+
notifier.backtrace(e) if Stackit.debug
|
77
|
+
notifier.error(e.message)
|
78
|
+
end
|
79
|
+
response
|
80
|
+
end
|
81
|
+
|
82
|
+
def update!
|
83
|
+
begin
|
84
|
+
response = cloudformation_request(:update_stack)
|
85
|
+
notifier.response(response)
|
86
|
+
rescue ::Aws::CloudFormation::Errors::AlreadyExistsException => e
|
87
|
+
notifier.backtrace(e) if Stackit.debug
|
88
|
+
notifier.error(e.message)
|
89
|
+
rescue ::Aws::CloudFormation::Errors::ValidationError => e
|
90
|
+
if e.message =~ /No updates are to be performed./
|
91
|
+
notifier.success(e.message)
|
92
|
+
else
|
93
|
+
notifier.backtrace(e) if Stackit.debug
|
94
|
+
notifier.error(e.message)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
response
|
98
|
+
end
|
99
|
+
|
100
|
+
def delete!
|
101
|
+
begin
|
102
|
+
response = cloudformation_request(:delete_stack)
|
103
|
+
notifier.response(response)
|
104
|
+
rescue ::Aws::CloudFormation::Errors::AlreadyExistsException => e
|
105
|
+
notifier.backtrace(e) if Stackit.debug
|
106
|
+
notifier.error(e.message)
|
107
|
+
rescue ::Aws::CloudFormation::Errors::ValidationError => e
|
108
|
+
notifier.backtrace(e) if Stackit.debug
|
109
|
+
notifier.error(e.message)
|
110
|
+
end
|
111
|
+
response
|
112
|
+
end
|
113
|
+
|
114
|
+
def deploy!
|
115
|
+
if !File.exist?(template)
|
116
|
+
delete!
|
117
|
+
wait_for_stack_to_delete
|
118
|
+
notifier.success('Delete successful')
|
119
|
+
elsif exists?
|
120
|
+
begin
|
121
|
+
update!
|
122
|
+
wait_for_stack
|
123
|
+
notifier.success('Update successful')
|
124
|
+
rescue ::Aws::CloudFormation::Errors::ValidationError => e
|
125
|
+
if e.message.include? "No updates are to be performed"
|
126
|
+
Stackit.logger.info "No updates are to be performed"
|
127
|
+
elsif e.message.include? "_FAILED state and can not be updated"
|
128
|
+
Stackit.logger.info 'Stack is in a failed state and can\'t be updated. Deleting/creating a new stack.'
|
129
|
+
delete!
|
130
|
+
wait_for_stack_to_delete
|
131
|
+
create!
|
132
|
+
wait_for_stack
|
133
|
+
notifier.success('Stack deleted and re-created')
|
134
|
+
else
|
135
|
+
raise e
|
136
|
+
end
|
137
|
+
end
|
138
|
+
else
|
139
|
+
begin
|
140
|
+
create!
|
141
|
+
wait_for_stack
|
142
|
+
notifier.success('Created successfully')
|
143
|
+
rescue ::Aws::CloudFormation::Errors::ValidationError => e
|
144
|
+
if e.message.include? "_FAILED state and can not be updated"
|
145
|
+
Stackit.logger.info 'Stack already exists, is in a failed state, and can\'t be updated. Deleting and creating a new stack.'
|
146
|
+
delete!
|
147
|
+
wait_for_stack_to_delete
|
148
|
+
create!
|
149
|
+
wait_for_stack
|
150
|
+
notifier.success('Stack deleted and re-created')
|
151
|
+
else
|
152
|
+
raise e
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
def exist?
|
159
|
+
describe != nil
|
160
|
+
end
|
161
|
+
|
162
|
+
def describe
|
163
|
+
response = cloudformation_request(:describe_stacks)
|
164
|
+
if response && response[:stacks]
|
165
|
+
response[:stacks].first
|
166
|
+
else
|
167
|
+
nil
|
168
|
+
end
|
169
|
+
rescue ::Aws::CloudFormation::Errors::ValidationError
|
170
|
+
nil
|
171
|
+
end
|
172
|
+
|
173
|
+
private
|
174
|
+
|
175
|
+
DRY_RUN_RESPONSE = Struct.new(:stack_id)
|
176
|
+
REDACTED_TEXT = '****redacted****'
|
177
|
+
|
178
|
+
def capabilities
|
179
|
+
template.needs_iam_capability? ? ['CAPABILITY_IAM'] : []
|
180
|
+
end
|
181
|
+
|
182
|
+
def cloudformation_request(action)
|
183
|
+
Stackit.logger.debug "ManagedStack CloudFormation API request: #{action}"
|
184
|
+
cloudformation_options = create_cloudformation_options(action)
|
185
|
+
if debug
|
186
|
+
Stackit.logger.debug "#{action} request parameters: "
|
187
|
+
opts = cloudformation_options.clone
|
188
|
+
if opts[:parameters]
|
189
|
+
opts[:parameters].each do |param|
|
190
|
+
key = param['parameter_key']
|
191
|
+
param['parameter_value'] = REDACTED_TEXT if key =~ /username|password/i && !debug
|
192
|
+
end
|
193
|
+
end
|
194
|
+
opts[:template_body] = REDACTED_TEXT if opts[:template_body]
|
195
|
+
opts[:stack_policy_body] = JSON.parse(opts[:stack_policy_body]) if opts[:stack_policy_body]
|
196
|
+
opts[:stack_policy_during_update_body] =
|
197
|
+
JSON.parse(opts[:stack_policy_during_update_body]) if opts[:stack_policy_during_update_body]
|
198
|
+
pp opts
|
199
|
+
end
|
200
|
+
response = dry_run ? dry_run_response : cloudformation.send(action, cloudformation_options)
|
201
|
+
wait_for_stack if wait
|
202
|
+
response
|
203
|
+
end
|
204
|
+
|
205
|
+
def create_cloudformation_options(action)
|
206
|
+
case action
|
207
|
+
when :create_stack
|
208
|
+
{
|
209
|
+
parameters: to_request_parameters(merged_parameters),
|
210
|
+
capabilities: capabilities
|
211
|
+
}.reverse_merge(template.options).reverse_merge(create_stack_request_params)
|
212
|
+
when :update_stack
|
213
|
+
{
|
214
|
+
parameters: to_request_parameters(merged_parameters),
|
215
|
+
capabilities: capabilities
|
216
|
+
}.merge(template.options).merge(update_stack_request_params)
|
217
|
+
else
|
218
|
+
delete_stack_request_params
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
def dependent_parameters
|
223
|
+
depends.inject([]) do |arr, dep|
|
224
|
+
arr.push(Stackit::Stack.new(stack_name))
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
def merged_parameters
|
229
|
+
|
230
|
+
parsed_parameters = template.parsed_parameters
|
231
|
+
return parsed_parameters unless depends.length
|
232
|
+
|
233
|
+
validated_parameters = parsed_parameters.clone
|
234
|
+
|
235
|
+
# merge file parameters
|
236
|
+
validated_parameters.merge!(file_parameters) if file_parameters
|
237
|
+
|
238
|
+
# merge --depends
|
239
|
+
depends.each do |dependent_stack_name|
|
240
|
+
this_stack = Stack.new({
|
241
|
+
cloudformation: cloudformation,
|
242
|
+
stack_name: dependent_stack_name
|
243
|
+
})
|
244
|
+
validated_parameters.select { |param|
|
245
|
+
!this_stack[mapped_key(param.to_s)].nil?
|
246
|
+
}.each do | param_name, param_value |
|
247
|
+
validated_parameters.merge!(param_name => this_stack[mapped_key(param_name)])
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
# merge user defined parameters
|
252
|
+
validated_parameters.merge!(user_defined_parameters)
|
253
|
+
end
|
254
|
+
|
255
|
+
def mapped_key(param)
|
256
|
+
if parameter_map.has_key?(param.to_sym)
|
257
|
+
parameter_map[param.to_sym]
|
258
|
+
else
|
259
|
+
param.to_sym
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
def dry_run_response
|
264
|
+
DRY_RUN_RESPONSE.new('arn:stackit:dry-run:complete')
|
265
|
+
end
|
266
|
+
|
267
|
+
end
|
268
|
+
end
|
@@ -0,0 +1,177 @@
|
|
1
|
+
module Stackit
|
2
|
+
class Stack
|
3
|
+
|
4
|
+
attr_accessor :cloudformation
|
5
|
+
attr_accessor :stack_id
|
6
|
+
attr_accessor :stack_name
|
7
|
+
attr_accessor :description
|
8
|
+
attr_accessor :parameters
|
9
|
+
attr_accessor :creation_time
|
10
|
+
attr_accessor :last_updated_time
|
11
|
+
attr_accessor :stack_status
|
12
|
+
attr_accessor :stack_status_reason
|
13
|
+
attr_accessor :disable_rollback
|
14
|
+
attr_accessor :timeout_in_minutes
|
15
|
+
attr_accessor :stack_policy_body
|
16
|
+
attr_accessor :stack_policy_url
|
17
|
+
attr_accessor :stack_policy_during_update_body
|
18
|
+
attr_accessor :stack_policy_during_update_url
|
19
|
+
attr_accessor :on_failure
|
20
|
+
attr_accessor :use_previous_template
|
21
|
+
attr_accessor :retain_resources
|
22
|
+
|
23
|
+
def initialize(options = {})
|
24
|
+
options = options.to_h.symbolize_keys!
|
25
|
+
@cloudformation = options[:cloudformation] || Stackit.cloudformation
|
26
|
+
@stack_name = options[:stack_name]
|
27
|
+
@description = options[:description]
|
28
|
+
@parameters = options[:parameters]
|
29
|
+
@disable_rollback = options[:disable_rollback]
|
30
|
+
@notification_arns = options[:notification_arns]
|
31
|
+
@timeout_in_minutes = options[:timeout_in_minutes]
|
32
|
+
@capabilities = options[:capabilities]
|
33
|
+
@tags = options[:tags]
|
34
|
+
@on_failure = options[:on_failure]
|
35
|
+
@use_previous_template = options[:use_previous_template] || true
|
36
|
+
@retain_resources = options[:retain_resources]
|
37
|
+
end
|
38
|
+
|
39
|
+
def [](key)
|
40
|
+
parameters[key] || outputs[key] || resources[key]
|
41
|
+
end
|
42
|
+
|
43
|
+
def parameters
|
44
|
+
@parameters ||= begin
|
45
|
+
stack.parameters.inject({}) do |hash, param|
|
46
|
+
hash.merge(param[:parameter_key] => param[:parameter_value])
|
47
|
+
end
|
48
|
+
rescue ::Aws::CloudFormation::Errors::ValidationError => e
|
49
|
+
[] if e.message =~ /Stack with id #{stack_name} does not exist/
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def outputs
|
54
|
+
@outputs ||= begin
|
55
|
+
stack.outputs.inject({}) do |hash, output|
|
56
|
+
hash.merge(output[:output_key] => output[:output_value])
|
57
|
+
end
|
58
|
+
rescue ::Aws::CloudFormation::Errors::ValidationError => e
|
59
|
+
[] if e.message =~ /Stack with id #{stack_name} does not exist/
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def resources
|
64
|
+
@resources ||= list_stack_resources.inject({}) do |hash, resource|
|
65
|
+
hash.merge(resource[:logical_resource_id] => resource[:physical_resource_id])
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def notification_arns
|
70
|
+
@notification_arns ||= begin
|
71
|
+
stack.notification_arns.inject([]) do |arr, arn|
|
72
|
+
arr.push(arn)
|
73
|
+
end
|
74
|
+
rescue ::Aws::CloudFormation::Errors::ValidationError => e
|
75
|
+
[] if e.message =~ /Stack with id #{stack_name} does not exist/
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def capabilities
|
80
|
+
@capabilities ||= stack.capabilities.inject([]) do |arr, capability|
|
81
|
+
arr.push(capability)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def tags
|
86
|
+
@tags ||= begin
|
87
|
+
stack.tags.inject([]) do |arr, tag|
|
88
|
+
arr.push(tag)
|
89
|
+
end
|
90
|
+
rescue ::Aws::CloudFormation::Errors::ValidationError => e
|
91
|
+
[] if e.message =~ /Stack with id #{stack_name} does not exist/
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def hydrate!
|
96
|
+
@stack_id = stack.stack_id
|
97
|
+
@description = stack.description
|
98
|
+
@creation_time = stack.creation_time
|
99
|
+
@last_updated_time = stack.last_updated_time
|
100
|
+
@stack_status = stack.stack_status
|
101
|
+
@stack_status_reason = stack.stack_status_reason
|
102
|
+
@disable_rollback = stack.disable_rollback
|
103
|
+
@timeout_in_minutes = stack.timeout_in_minutes
|
104
|
+
@stack_policy_body = stack.stack_policy_body if stack.respond_to?(:stack_policy_body)
|
105
|
+
@stack_policy_url = stack.stack_policy_url if stack.respond_to?(:stack_policy_url)
|
106
|
+
self
|
107
|
+
end
|
108
|
+
|
109
|
+
def create_stack_request_params
|
110
|
+
{
|
111
|
+
stack_name: stack_name,
|
112
|
+
parameters: to_request_parameters,
|
113
|
+
disable_rollback: disable_rollback,
|
114
|
+
timeout_in_minutes: timeout_in_minutes,
|
115
|
+
notification_arns: notification_arns,
|
116
|
+
on_failure: on_failure,
|
117
|
+
stack_policy_body: stack_policy_body,
|
118
|
+
stack_policy_url: stack_policy_url,
|
119
|
+
tags: to_request_tags
|
120
|
+
}
|
121
|
+
end
|
122
|
+
|
123
|
+
def update_stack_request_params
|
124
|
+
{
|
125
|
+
stack_name: stack_name,
|
126
|
+
use_previous_template: use_previous_template,
|
127
|
+
stack_policy_during_update_body: stack_policy_during_update_body,
|
128
|
+
stack_policy_during_update_url: stack_policy_during_update_url,
|
129
|
+
parameters: to_request_parameters,
|
130
|
+
stack_policy_body: stack_policy_body,
|
131
|
+
stack_policy_url: stack_policy_url,
|
132
|
+
tags: to_request_tags
|
133
|
+
}
|
134
|
+
end
|
135
|
+
|
136
|
+
def delete_stack_request_params
|
137
|
+
{
|
138
|
+
stack_name: stack_name,
|
139
|
+
retain_resources: retain_resources
|
140
|
+
}
|
141
|
+
end
|
142
|
+
|
143
|
+
private
|
144
|
+
|
145
|
+
def stack
|
146
|
+
@stack ||= cloudformation.describe_stacks(
|
147
|
+
stack_name: stack_name
|
148
|
+
)[:stacks].first
|
149
|
+
end
|
150
|
+
|
151
|
+
def list_stack_resources(next_token = nil)
|
152
|
+
result = []
|
153
|
+
response = cloudformation.list_stack_resources(
|
154
|
+
stack_name: stack.stack_id
|
155
|
+
).inject([]) do |arr, page|
|
156
|
+
arr.concat(page[:stack_resource_summaries])
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
def to_request_parameters(params = parameters)
|
161
|
+
params.map do |k, v|
|
162
|
+
if v == :use_previous_value
|
163
|
+
{ 'parameter_key' => k, 'use_previous_value' => true }
|
164
|
+
else
|
165
|
+
{ 'parameter_key' => k, 'parameter_value' => v || '' }
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
def to_request_tags(tagz = tags)
|
171
|
+
tagz.map do |k, v|
|
172
|
+
{ 'key' => k, 'value' => v || '' }
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
end
|
177
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module Stackit
|
2
|
+
class Template
|
3
|
+
|
4
|
+
attr_accessor :cloudformation
|
5
|
+
attr_accessor :template_path
|
6
|
+
attr_accessor :options
|
7
|
+
attr_accessor :parsed_parameters
|
8
|
+
|
9
|
+
def initialize(options = {})
|
10
|
+
@cloudformation = options[:cloudformation] || Stackit.cloudformation
|
11
|
+
@template_path = options[:template_path]
|
12
|
+
@options = {}
|
13
|
+
end
|
14
|
+
|
15
|
+
def parse!
|
16
|
+
Stackit.logger.info "Parsing cloudformation template: #{template_path}"
|
17
|
+
if @template_path =~ /^https:\/\/s3\.amazonaws\.com/
|
18
|
+
@options[:template_url] = @template_path
|
19
|
+
else
|
20
|
+
@options[:template_body] = body
|
21
|
+
end
|
22
|
+
@options[:parameters] = validate
|
23
|
+
self
|
24
|
+
end
|
25
|
+
|
26
|
+
def parsed_parameters
|
27
|
+
@parsed_parameters ||= @options[:parameters].inject({}) do |hash, param|
|
28
|
+
hash.merge(param['parameter_key'].to_sym => param['default_value'])
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def needs_iam_capability?
|
33
|
+
body =~ /AWS::IAM::AccessKey|AWS::IAM::Group|AWS::IAM::InstanceProfile|AWS::IAM::Policy|AWS::IAM::Role|AWS::IAM::User|AWS::IAM::UserToGroupAddition/ ? true : false
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def body
|
39
|
+
path = File.exist?(template_path) ? template_path : File.join(Dir.pwd, template_path)
|
40
|
+
raise "Unable to stat filesystem template #{template_path}" if !File.exist?(path)
|
41
|
+
File.read(path)
|
42
|
+
end
|
43
|
+
|
44
|
+
def validate
|
45
|
+
cloudformation.validate_template(
|
46
|
+
options.slice(:template_body, :template_url)
|
47
|
+
)[:parameters]
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
end
|
data/lib/stackit/wait.rb
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
module Stackit
|
2
|
+
|
3
|
+
class WaitError < StandardError; end
|
4
|
+
|
5
|
+
module Wait
|
6
|
+
|
7
|
+
def wait_until_stack_info_has_key(key)
|
8
|
+
Stackit.logger.debug "Waiting until stack #{stack_name} has key #{key}..."
|
9
|
+
wait_for(timeout: 15.minutes) do
|
10
|
+
stack = Stack.new(stack_name: stack_name).hydrate!
|
11
|
+
raise "Stack failure: #{stack.stack_status}" if stack.stack_status =~ /FAILED/
|
12
|
+
if stack[key]
|
13
|
+
yield stack if block_given?
|
14
|
+
true
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def wait_for_stack(status_pattern = /COMPLETE/)
|
20
|
+
Stackit.logger.debug "Waiting for stack #{stack_name} to complete..."
|
21
|
+
wait_for(timeout: 15.minutes) do
|
22
|
+
stack = Stack.new(stack_name: stack_name).hydrate!
|
23
|
+
case stack.stack_status
|
24
|
+
when /FAILED/
|
25
|
+
raise WaitError, "Stack failed during wait: #{stack_name}"
|
26
|
+
when status_pattern
|
27
|
+
yield stack if block_given?
|
28
|
+
true
|
29
|
+
else
|
30
|
+
false
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def wait_for_stack_to_delete
|
36
|
+
Stackit.logger.debug "Waiting for stack #{stack_name} to delete..."
|
37
|
+
wait_for(timeout: 15.minutes) do
|
38
|
+
begin
|
39
|
+
stack = Stack.new(stack_name: stack_name).hydrate!
|
40
|
+
if stack.nil?
|
41
|
+
yield stack if block_given?
|
42
|
+
true
|
43
|
+
else
|
44
|
+
false
|
45
|
+
end
|
46
|
+
rescue Aws::CloudFormation::Errors::ValidationError => e
|
47
|
+
if e.message.include?("does not exist")
|
48
|
+
true
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def wait_for(opts={})
|
57
|
+
raise ArgumentError, 'block expected' unless block_given?
|
58
|
+
timeout = opts[:timeout] || 600
|
59
|
+
interval = opts[:interval] || 10
|
60
|
+
attempts = timeout / interval
|
61
|
+
Stackit.logger.debug "Timeout: #{timeout}"
|
62
|
+
Stackit.logger.debug "Attempts: #{attempts}"
|
63
|
+
actual_attempts = attempts.times do
|
64
|
+
break if yield
|
65
|
+
sleep interval
|
66
|
+
end
|
67
|
+
actual_attempts != attempts
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
end
|
data/lib/stackit.rb
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
require "logger"
|
2
|
+
require "pp"
|
3
|
+
require "stackit/version"
|
4
|
+
require 'json'
|
5
|
+
require "active_support"
|
6
|
+
require "active_support/all"
|
7
|
+
require "awsclient"
|
8
|
+
require "stackit/aws"
|
9
|
+
require "stackit/template"
|
10
|
+
require "stackit/stack/default_notifier"
|
11
|
+
require "stackit/stack"
|
12
|
+
require "stackit/stack/managed_stack"
|
13
|
+
|
14
|
+
module Stackit
|
15
|
+
class << self
|
16
|
+
|
17
|
+
attr_accessor :aws
|
18
|
+
attr_accessor :cloudformation
|
19
|
+
attr_accessor :environment
|
20
|
+
attr_accessor :debug
|
21
|
+
|
22
|
+
def aws
|
23
|
+
@aws ||= Stackit::Aws.new
|
24
|
+
end
|
25
|
+
|
26
|
+
def cloudformation
|
27
|
+
@cloudformation ||= Stackit::Aws.new.cloudformation
|
28
|
+
end
|
29
|
+
|
30
|
+
def logger
|
31
|
+
@logger ||= Logger.new(STDOUT)
|
32
|
+
end
|
33
|
+
|
34
|
+
def environment
|
35
|
+
@environment ||= :default
|
36
|
+
end
|
37
|
+
|
38
|
+
def debug
|
39
|
+
@debug ||= false
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
data/stackit.gemspec
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
lib = File.expand_path('../lib', __FILE__)
|
2
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
+
require 'stackit/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "stackit"
|
7
|
+
spec.version = Stackit::VERSION
|
8
|
+
spec.authors = ["Jeremy Hahn"]
|
9
|
+
spec.email = ["mail@jeremyhahn.com"]
|
10
|
+
|
11
|
+
spec.summary = %q{Simple, elegant CloudFormation dependency management.}
|
12
|
+
spec.description = %q{Use existing stack values (output, resource, or parameters) as input parmeters to templates.}
|
13
|
+
spec.homepage = "https://github.com/jeremyhahn/stackit"
|
14
|
+
spec.license = "GPLv3"
|
15
|
+
|
16
|
+
# Prevent pushing this gem to RubyGems.org by setting 'allowed_push_host', or
|
17
|
+
# delete this section to allow pushing this gem to any host.
|
18
|
+
if spec.respond_to?(:metadata)
|
19
|
+
spec.metadata['allowed_push_host'] = "https://rubygems.org"
|
20
|
+
else
|
21
|
+
raise "RubyGems 2.0 or newer is required to protect against public gem pushes."
|
22
|
+
end
|
23
|
+
|
24
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
25
|
+
spec.bindir = "exe"
|
26
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
27
|
+
spec.require_paths = ["lib"]
|
28
|
+
|
29
|
+
spec.add_development_dependency "bundler", "~> 1.11"
|
30
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
31
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
32
|
+
spec.add_development_dependency 'pry-byebug', '~> 2.0'
|
33
|
+
|
34
|
+
spec.add_runtime_dependency 'awsclient'
|
35
|
+
spec.add_runtime_dependency 'aws-sdk', '~> 2'
|
36
|
+
spec.add_runtime_dependency 'thor', '~> 0.19'
|
37
|
+
spec.add_runtime_dependency 'activesupport', '~> 4.2'
|
38
|
+
|
39
|
+
end
|
40
|
+
|
metadata
ADDED
@@ -0,0 +1,178 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: stackit
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Jeremy Hahn
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-03-31 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.11'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.11'
|
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: '3.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '3.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: pry-byebug
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '2.0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '2.0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: awsclient
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: aws-sdk
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '2'
|
90
|
+
type: :runtime
|
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: thor
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0.19'
|
104
|
+
type: :runtime
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0.19'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: activesupport
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - "~>"
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '4.2'
|
118
|
+
type: :runtime
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - "~>"
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '4.2'
|
125
|
+
description: Use existing stack values (output, resource, or parameters) as input
|
126
|
+
parmeters to templates.
|
127
|
+
email:
|
128
|
+
- mail@jeremyhahn.com
|
129
|
+
executables: []
|
130
|
+
extensions: []
|
131
|
+
extra_rdoc_files: []
|
132
|
+
files:
|
133
|
+
- ".gitignore"
|
134
|
+
- ".rspec"
|
135
|
+
- ".travis.yml"
|
136
|
+
- Gemfile
|
137
|
+
- LICENSE.txt
|
138
|
+
- README.md
|
139
|
+
- Rakefile
|
140
|
+
- bin/console
|
141
|
+
- bin/setup
|
142
|
+
- bin/stackit
|
143
|
+
- lib/stackit.rb
|
144
|
+
- lib/stackit/aws.rb
|
145
|
+
- lib/stackit/cli.rb
|
146
|
+
- lib/stackit/stack.rb
|
147
|
+
- lib/stackit/stack/default_notifier.rb
|
148
|
+
- lib/stackit/stack/managed_stack.rb
|
149
|
+
- lib/stackit/template.rb
|
150
|
+
- lib/stackit/version.rb
|
151
|
+
- lib/stackit/wait.rb
|
152
|
+
- stackit.gemspec
|
153
|
+
homepage: https://github.com/jeremyhahn/stackit
|
154
|
+
licenses:
|
155
|
+
- GPLv3
|
156
|
+
metadata:
|
157
|
+
allowed_push_host: https://rubygems.org
|
158
|
+
post_install_message:
|
159
|
+
rdoc_options: []
|
160
|
+
require_paths:
|
161
|
+
- lib
|
162
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
163
|
+
requirements:
|
164
|
+
- - ">="
|
165
|
+
- !ruby/object:Gem::Version
|
166
|
+
version: '0'
|
167
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
168
|
+
requirements:
|
169
|
+
- - ">="
|
170
|
+
- !ruby/object:Gem::Version
|
171
|
+
version: '0'
|
172
|
+
requirements: []
|
173
|
+
rubyforge_project:
|
174
|
+
rubygems_version: 2.4.8
|
175
|
+
signing_key:
|
176
|
+
specification_version: 4
|
177
|
+
summary: Simple, elegant CloudFormation dependency management.
|
178
|
+
test_files: []
|