cloudformer2 1.0.18
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 +18 -0
- data/CHANGELOG.md +11 -0
- data/Dockerfile +5 -0
- data/Gemfile +3 -0
- data/LICENSE.txt +22 -0
- data/README.md +222 -0
- data/Rakefile +12 -0
- data/cloudformer2.gemspec +27 -0
- data/docker-compose.yml +2 -0
- data/lib/cloudformer/cloudformer.rb +6 -0
- data/lib/cloudformer/stack.rb +189 -0
- data/lib/cloudformer/tasks.rb +114 -0
- data/lib/cloudformer/version.rb +3 -0
- data/samples/Rakefile +9 -0
- data/samples/basic_template.json +31 -0
- data/spec/stack_spec.rb +87 -0
- metadata +149 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 507b885f8e308609bc0790cb86f143032b5b2c8c
|
4
|
+
data.tar.gz: 92308039a57f4a25fb641cc39322ad8654f2a833
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: d7b4bce27d8b8b8a506c9147ecf683ce7b11579bc9f5cc6560e90223146c1369c4d5d22569bb012eeee1379f987887c87c7a2ba0f58b30df7e244a7158629ea8
|
7
|
+
data.tar.gz: 82c9575f3f6784b742e4c8fa6a7bd81697abb86f2888d4a93bb496063d73a6bb0cf8e44c6a0b76aa72fc3fdabd8199a9df64f2ccc6cf32de4b382812be143f1f
|
data/.gitignore
ADDED
data/CHANGELOG.md
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
# Change Log
|
2
|
+
All notable changes to this project will be documented in this file.
|
3
|
+
|
4
|
+
The format is based on [Keep a Changelog](http://keepachangelog.com/)
|
5
|
+
and this project adheres to [Semantic Versioning](http://semver.org/).
|
6
|
+
|
7
|
+
## 1.0.19 (Next Release)
|
8
|
+
|
9
|
+
## [1.0.18]
|
10
|
+
* Initial release of cloudformer fork that uses the aws-sdk-v1 gem and aws-sdk ~> 2.10.9
|
11
|
+
|
data/Dockerfile
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Arvind Kunday
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,222 @@
|
|
1
|
+
# CloudFormer 2
|
2
|
+
|
3
|
+
Cloudformer attempts to simplify AWS Cloudformation stack creation process in ruby projects by providing reusable rake tasks to perform common operations such as apply(create/update), delete, recreate on stack along with validations on templates. Task executions which enforce a stack change will wait until ROLLBACK/COMPLETE or DELETE is signalled on the stack (useful in continuous deployment environments to wait until deployment is successful). Refer [examples section](#example) for more information.
|
4
|
+
|
5
|
+
### Note:
|
6
|
+
Forked from abandoned https://github.com/kunday/cloudformer
|
7
|
+
|
8
|
+
The list of rake tasks provided are:
|
9
|
+
|
10
|
+
```
|
11
|
+
|
12
|
+
rake apply # Apply Stack with Cloudformation script and parameters(And wait till complete - supports updates)
|
13
|
+
rake delete # Delete stack from CloudFormation(And wait till stack is complete)
|
14
|
+
rake events # Get the recent events from the stack
|
15
|
+
rake outputs # Get the outputs of stack
|
16
|
+
rake recreate # Recreate stack(runs delete & apply)
|
17
|
+
rake status # Get the deployed app status
|
18
|
+
|
19
|
+
```
|
20
|
+
|
21
|
+
|
22
|
+
## Installation
|
23
|
+
|
24
|
+
Add this line to your application's Gemfile:
|
25
|
+
|
26
|
+
gem 'cloudformer2'
|
27
|
+
|
28
|
+
And then execute:
|
29
|
+
|
30
|
+
$ bundle
|
31
|
+
|
32
|
+
Or install it yourself as:
|
33
|
+
|
34
|
+
$ gem install cloudformer2
|
35
|
+
|
36
|
+
## AWS Environment configuration
|
37
|
+
|
38
|
+
Cloudformer depends on the aws-sdk gem to query AWS API. You will need to export AWS configuration to environment variables to your .bashrc/.bash_profile or your build server:
|
39
|
+
|
40
|
+
export AWS_ACCESS_KEY=your access key
|
41
|
+
export AWS_REGION=ap-southeast-2
|
42
|
+
export AWS_SECRET_ACCESS_KEY=your secret access key
|
43
|
+
|
44
|
+
|
45
|
+
## Configuration
|
46
|
+
|
47
|
+
You can add cloudformer tasks to your project by adding the following to your rake file:
|
48
|
+
|
49
|
+
require 'cloudformer2'
|
50
|
+
Cloudformer::Tasks.new("earmark") do |t|
|
51
|
+
t.template = "cloudformation/cloudformation.json"
|
52
|
+
t.parameters = parameters
|
53
|
+
end
|
54
|
+
|
55
|
+
where `cloudformation/cloudformation.json` is the stack json file and parameters is a hash of parameters used in the template.
|
56
|
+
For a template which takes the following parameters:
|
57
|
+
|
58
|
+
"Parameters": {
|
59
|
+
"PackageUrl": {
|
60
|
+
"Type": "String"
|
61
|
+
},
|
62
|
+
"PackageVersion": {
|
63
|
+
"Type": "String"
|
64
|
+
}
|
65
|
+
}
|
66
|
+
|
67
|
+
the parameter hash(Ruby Object) would look like:
|
68
|
+
|
69
|
+
{
|
70
|
+
"PackageUrl" => "http://localhost/app.rpm",
|
71
|
+
"PackageVersion" => "123"
|
72
|
+
}
|
73
|
+
|
74
|
+
If you have a template with no parameters, pass an empty hash `{}` instead.
|
75
|
+
|
76
|
+
## Example
|
77
|
+
|
78
|
+
Here is a simple Cloudformation Stack(Code available in the samples directory) with a Single EC2 Server:
|
79
|
+
|
80
|
+
{
|
81
|
+
"AWSTemplateFormatVersion": "2010-09-09",
|
82
|
+
"Description": "Cloudformer - Demo App",
|
83
|
+
"Parameters": {
|
84
|
+
"AmiId": {
|
85
|
+
"Type": "String"
|
86
|
+
}
|
87
|
+
},
|
88
|
+
"Resources": {
|
89
|
+
"ApplicationServer": {
|
90
|
+
"Type": "AWS::EC2::Instance",
|
91
|
+
"Properties": {
|
92
|
+
"ImageId": {
|
93
|
+
"Ref": "AmiId"
|
94
|
+
},
|
95
|
+
"InstanceType": "t1.micro",
|
96
|
+
"Monitoring": "false"
|
97
|
+
}
|
98
|
+
}
|
99
|
+
},
|
100
|
+
"Outputs": {
|
101
|
+
"Server": {
|
102
|
+
"Value": {
|
103
|
+
"Fn::GetAtt": [
|
104
|
+
"ApplicationServer",
|
105
|
+
"PrivateIp"
|
106
|
+
]
|
107
|
+
}
|
108
|
+
}
|
109
|
+
}
|
110
|
+
}
|
111
|
+
|
112
|
+
Then, in your Rakefile add,
|
113
|
+
|
114
|
+
require 'cloudformer/tasks'
|
115
|
+
|
116
|
+
Cloudformer::Tasks.new("app") do |t|
|
117
|
+
t.template = "basic_template.json"
|
118
|
+
t.tags = [{'Key' => 'Name', 'Value' => 'BASIC-TMPLT'},
|
119
|
+
{'Key' => 'Owner', 'Value' => 'APPOWNER'}]
|
120
|
+
|
121
|
+
#AMI Works in Sydney region only, ensure you supply the right AMI.
|
122
|
+
t.parameters = {"AmiId" => "ami-8da439b7"}
|
123
|
+
end
|
124
|
+
|
125
|
+
You should then see following commands:
|
126
|
+
|
127
|
+
rake apply # Apply Stack with Cloudformation script and parameters
|
128
|
+
rake delete # Delete stack from CloudFormation
|
129
|
+
rake events # Get the recent events from the stack
|
130
|
+
rake outputs # Get the outputs of stack
|
131
|
+
rake recreate # Recreate stack
|
132
|
+
rake status # Get the deployed app status
|
133
|
+
|
134
|
+
Running `rake status` gives us:
|
135
|
+
|
136
|
+
===============================================
|
137
|
+
app - Not Deployed
|
138
|
+
================================================
|
139
|
+
|
140
|
+
Running `rake apply` will create an environment or update existing depending on the nature of action requested in parameters:
|
141
|
+
|
142
|
+
Initializing stack creation...
|
143
|
+
==================================================================================================
|
144
|
+
2013-10-24 07:55:24 UTC - AWS::CloudFormation::Stack - CREATE_IN_PROGRESS - User Initiated
|
145
|
+
2013-10-24 07:55:36 UTC - AWS::EC2::Instance - CREATE_IN_PROGRESS -
|
146
|
+
2013-10-24 07:55:37 UTC - AWS::EC2::Instance - CREATE_IN_PROGRESS - Resource creation Initiated
|
147
|
+
2013-10-24 07:56:25 UTC - AWS::EC2::Instance - CREATE_COMPLETE -
|
148
|
+
2013-10-24 07:56:26 UTC - AWS::CloudFormation::Stack - CREATE_COMPLETE -
|
149
|
+
==================================================================================================
|
150
|
+
|
151
|
+
Running `rake apply` again gives us:
|
152
|
+
|
153
|
+
No updates are to be performed.
|
154
|
+
|
155
|
+
To remove the stack `rake delete` gives us:
|
156
|
+
|
157
|
+
==============================================================================================
|
158
|
+
Attempting to delete stack - app
|
159
|
+
==============================================================================================
|
160
|
+
2013-10-24 07:55:24 UTC - AWS::CloudFormation::Stack - CREATE_IN_PROGRESS - User Initiated
|
161
|
+
2013-10-24 07:55:36 UTC - AWS::EC2::Instance - CREATE_IN_PROGRESS -
|
162
|
+
2013-10-24 07:55:37 UTC - AWS::EC2::Instance - CREATE_IN_PROGRESS - Resource creation Initiated
|
163
|
+
2013-10-24 07:56:25 UTC - AWS::EC2::Instance - CREATE_COMPLETE -
|
164
|
+
2013-10-24 07:56:26 UTC - AWS::CloudFormation::Stack - CREATE_COMPLETE -
|
165
|
+
2013-10-24 07:58:54 UTC - AWS::CloudFormation::Stack - DELETE_IN_PROGRESS - User Initiated
|
166
|
+
2013-10-24 07:58:56 UTC - AWS::EC2::Instance - DELETE_IN_PROGRESS -
|
167
|
+
|
168
|
+
Attempts to delete a non-existing stack will result in:
|
169
|
+
|
170
|
+
==============================================
|
171
|
+
Attempting to delete stack - app
|
172
|
+
==============================================
|
173
|
+
Stack not up.
|
174
|
+
==============================================
|
175
|
+
|
176
|
+
To recreate the stack use `rake recreate`:
|
177
|
+
|
178
|
+
=================================================================================================
|
179
|
+
Attempting to delete stack - app
|
180
|
+
=================================================================================================
|
181
|
+
2013-10-24 08:04:11 UTC - AWS::CloudFormation::Stack - CREATE_IN_PROGRESS - User Initiated
|
182
|
+
2013-10-24 08:04:22 UTC - AWS::EC2::Instance - CREATE_IN_PROGRESS -
|
183
|
+
2013-10-24 08:04:23 UTC - AWS::EC2::Instance - CREATE_IN_PROGRESS - Resource creation Initiated
|
184
|
+
2013-10-24 08:05:12 UTC - AWS::EC2::Instance - CREATE_COMPLETE -
|
185
|
+
2013-10-24 08:05:13 UTC - AWS::CloudFormation::Stack - CREATE_COMPLETE -
|
186
|
+
2013-10-24 08:05:52 UTC - AWS::CloudFormation::Stack - DELETE_IN_PROGRESS - User Initiated
|
187
|
+
2013-10-24 08:06:02 UTC - AWS::EC2::Instance - DELETE_IN_PROGRESS -
|
188
|
+
Stack deleted successfully.
|
189
|
+
Initializing stack creation...
|
190
|
+
=================================================================================================
|
191
|
+
2013-10-24 08:06:31 UTC - AWS::CloudFormation::Stack - CREATE_IN_PROGRESS - User Initiated
|
192
|
+
2013-10-24 08:06:52 UTC - AWS::EC2::Instance - CREATE_IN_PROGRESS -
|
193
|
+
2013-10-24 08:06:54 UTC - AWS::EC2::Instance - CREATE_IN_PROGRESS - Resource creation Initiated
|
194
|
+
2013-10-24 08:07:41 UTC - AWS::EC2::Instance - CREATE_COMPLETE -
|
195
|
+
=================================================================================================
|
196
|
+
=================================================================================================
|
197
|
+
Server - - 172.31.3.52
|
198
|
+
=================================================================================================
|
199
|
+
|
200
|
+
To see the stack outputs `rake outputs`:
|
201
|
+
|
202
|
+
==============================
|
203
|
+
Server - - 172.31.3.52
|
204
|
+
==============================
|
205
|
+
|
206
|
+
To see recent events on the stack `rake events`:
|
207
|
+
|
208
|
+
==================================================================================================
|
209
|
+
2013-10-24 08:06:31 UTC - AWS::CloudFormation::Stack - CREATE_IN_PROGRESS - User Initiated
|
210
|
+
2013-10-24 08:06:52 UTC - AWS::EC2::Instance - CREATE_IN_PROGRESS -
|
211
|
+
2013-10-24 08:06:54 UTC - AWS::EC2::Instance - CREATE_IN_PROGRESS - Resource creation Initiated
|
212
|
+
2013-10-24 08:07:41 UTC - AWS::EC2::Instance - CREATE_COMPLETE -
|
213
|
+
2013-10-24 08:07:43 UTC - AWS::CloudFormation::Stack - CREATE_COMPLETE -
|
214
|
+
==================================================================================================
|
215
|
+
|
216
|
+
## Contributing
|
217
|
+
|
218
|
+
1. Fork it
|
219
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
220
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
221
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
222
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'cloudformer/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "cloudformer2"
|
8
|
+
spec.version = Cloudformer::VERSION
|
9
|
+
spec.authors = ["Sentia MPC", "Dennis Vink"]
|
10
|
+
spec.email = ["dennis.vink@sentia.com"]
|
11
|
+
spec.description = %q{Cloudformation tasks for apply(create/update), delete, recreate on stack along with validations on templates}
|
12
|
+
spec.summary = %q{Cloudformer attempts to simplify AWS Cloudformation stack creation process in ruby projects by providing reusable rake tasks to perform common operations such as apply(create/update), delete, recreate on stack along with validations on templates.}
|
13
|
+
spec.homepage = "https://github.com/sentialabs/cloudformer2"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
22
|
+
spec.add_development_dependency "rspec"
|
23
|
+
spec.add_dependency "rake"
|
24
|
+
spec.add_dependency "aws-sdk-v1", '~> 1.67.0'
|
25
|
+
spec.add_dependency "aws-sdk", '~> 2.10.9'
|
26
|
+
spec.add_dependency "httparty"
|
27
|
+
end
|
data/docker-compose.yml
ADDED
@@ -0,0 +1,189 @@
|
|
1
|
+
require "aws-sdk-v1"
|
2
|
+
require "aws-sdk"
|
3
|
+
require "httparty"
|
4
|
+
|
5
|
+
# Class to simplify AWS CloudFormation stack creation
|
6
|
+
class Stack
|
7
|
+
attr_accessor :stack, :name, :deployed
|
8
|
+
|
9
|
+
SUCESS_STATES = %w[CREATE_COMPLETE UPDATE_COMPLETE].freeze
|
10
|
+
FAILURE_STATES = %w[
|
11
|
+
CREATE_FAILED DELETE_FAILED UPDATE_ROLLBACK_FAILED
|
12
|
+
ROLLBACK_FAILED ROLLBACK_COMPLETE ROLLBACK_FAILED
|
13
|
+
UPDATE_ROLLBACK_COMPLETE UPDATE_ROLLBACK_FAILED
|
14
|
+
].freeze
|
15
|
+
END_STATES = SUCESS_STATES + FAILURE_STATES
|
16
|
+
|
17
|
+
# WAITING_STATES = ["CREATE_IN_PROGRESS","DELETE_IN_PROGRESS","ROLLBACK_IN_PROGRESS","UPDATE_COMPLETE_CLEANUP_IN_PROGRESS","UPDATE_IN_PROGRESS","UPDATE_ROLLBACK_COMPLETE_CLEANUP_IN_PROGRESS","UPDATE_ROLLBACK_IN_PROGRESS"]
|
18
|
+
def initialize(stack_name)
|
19
|
+
@name = stack_name
|
20
|
+
@cf = AWS::CloudFormation.new
|
21
|
+
@stack = @cf.stacks[name]
|
22
|
+
@ec2 = AWS::EC2.new
|
23
|
+
end
|
24
|
+
|
25
|
+
def deployed
|
26
|
+
stack.exists?
|
27
|
+
end
|
28
|
+
|
29
|
+
def apply(template_file, parameters, disable_rollback = false,
|
30
|
+
capabilities = [], notify = [], tags = [])
|
31
|
+
if template_file =~ %r{^https:\/\/s3\S+\.amazonaws\.com\/(.*)}
|
32
|
+
template = template_file
|
33
|
+
elsif template_file =~ %r{^http.*(.json)$/}
|
34
|
+
begin
|
35
|
+
response = HTTParty.get(template_file)
|
36
|
+
template = response.body
|
37
|
+
rescue => e
|
38
|
+
puts "Unable to retieve json file for template from #{template_file} - #{e.class}, #{e}"
|
39
|
+
return :Failed
|
40
|
+
end
|
41
|
+
else
|
42
|
+
template = File.read(template_file)
|
43
|
+
end
|
44
|
+
validation = validate(template)
|
45
|
+
unless validation["valid"]
|
46
|
+
puts "Unable to update - #{validation['response'][:code]} - #{validation['response'][:message]}"
|
47
|
+
return :Failed
|
48
|
+
end
|
49
|
+
pending_operations = false
|
50
|
+
begin
|
51
|
+
if deployed
|
52
|
+
pending_operations = update(template, parameters, capabilities)
|
53
|
+
else
|
54
|
+
pending_operations = create(template, parameters, disable_rollback, capabilities, notify, tags)
|
55
|
+
end
|
56
|
+
rescue ::AWS::CloudFormation::Errors::ValidationError => e
|
57
|
+
puts e.message
|
58
|
+
return e.message == "No updates are to be performed." ? :NoUpdates : :Failed
|
59
|
+
end
|
60
|
+
wait_until_end if pending_operations
|
61
|
+
deploy_succeeded? ? :Succeeded : :Failed
|
62
|
+
end
|
63
|
+
|
64
|
+
def deploy_succeeded?
|
65
|
+
return true unless FAILURE_STATES.include?(stack.status)
|
66
|
+
puts "Unable to deploy template. Check log for more information."
|
67
|
+
false
|
68
|
+
end
|
69
|
+
|
70
|
+
def stop_instances
|
71
|
+
update_instances("stop")
|
72
|
+
end
|
73
|
+
|
74
|
+
def start_instances
|
75
|
+
update_instances("start")
|
76
|
+
end
|
77
|
+
|
78
|
+
def delete
|
79
|
+
with_highlight do
|
80
|
+
puts "Attempting to delete stack - #{name}"
|
81
|
+
stack.delete
|
82
|
+
wait_until_end
|
83
|
+
return deploy_succeeded?
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def status
|
88
|
+
with_highlight do
|
89
|
+
if deployed
|
90
|
+
puts "#{stack.name} - #{stack.status} - #{stack.status_reason}"
|
91
|
+
else
|
92
|
+
puts "#{name} - Not Deployed"
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def events(options = {})
|
98
|
+
with_highlight do
|
99
|
+
if !deployed
|
100
|
+
puts "Stack not up."
|
101
|
+
return
|
102
|
+
end
|
103
|
+
stack.events.sort_by { |a| a.timestamp }.each do |event|
|
104
|
+
puts "#{event.timestamp} - #{event.physical_resource_id.to_s} - #{event.logical_resource_id} - #{event.resource_type} - #{event.resource_status} - #{event.resource_status_reason.to_s}"
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def outputs
|
110
|
+
with_highlight do
|
111
|
+
if !deployed
|
112
|
+
puts "Stack not up."
|
113
|
+
return 1
|
114
|
+
end
|
115
|
+
stack.outputs.each do |output|
|
116
|
+
puts "#{output.key} - #{output.description} - #{output.value}"
|
117
|
+
end
|
118
|
+
end
|
119
|
+
return 0
|
120
|
+
end
|
121
|
+
|
122
|
+
def validate(template)
|
123
|
+
response = @cf.validate_template(template)
|
124
|
+
return {
|
125
|
+
"valid" => response[:code].nil?,
|
126
|
+
"response" => response
|
127
|
+
}
|
128
|
+
end
|
129
|
+
|
130
|
+
private
|
131
|
+
|
132
|
+
def wait_until_end
|
133
|
+
printed = []
|
134
|
+
current_time = Time.now
|
135
|
+
with_highlight do
|
136
|
+
if !deployed
|
137
|
+
puts "Stack not up."
|
138
|
+
return
|
139
|
+
end
|
140
|
+
loop do
|
141
|
+
printable_events = stack.events.reject { |a| (a.timestamp < current_time) }.sort_by { |a| a.timestamp }.reject { |a| a if printed.include?(a.event_id) }
|
142
|
+
printable_events.each { |event| puts "#{event.timestamp} - #{event.physical_resource_id.to_s} - #{event.resource_type} - #{event.resource_status} - #{event.resource_status_reason.to_s}" }
|
143
|
+
printed.concat(printable_events.map(&:event_id))
|
144
|
+
break if END_STATES.include?(stack.status)
|
145
|
+
sleep(30)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
def with_highlight(&block)
|
151
|
+
cols = `tput cols`.chomp!.to_i
|
152
|
+
puts "=" * cols
|
153
|
+
yield
|
154
|
+
puts "=" * cols
|
155
|
+
end
|
156
|
+
|
157
|
+
def update(template, parameters, capabilities)
|
158
|
+
stack.update({
|
159
|
+
:template => template,
|
160
|
+
:parameters => parameters,
|
161
|
+
:capabilities => capabilities
|
162
|
+
})
|
163
|
+
return true
|
164
|
+
end
|
165
|
+
|
166
|
+
def create(template, parameters, disable_rollback, capabilities, notify, tags)
|
167
|
+
puts "Initializing stack creation..."
|
168
|
+
@cf.stacks.create(name, template, :parameters => parameters, :disable_rollback => disable_rollback, :capabilities => capabilities, :notify => notify, :tags => tags)
|
169
|
+
sleep 10
|
170
|
+
return true
|
171
|
+
end
|
172
|
+
|
173
|
+
def update_instances(action)
|
174
|
+
with_highlight do
|
175
|
+
puts "Attempting to #{action} all ec2 instances in the stack #{stack.name}"
|
176
|
+
return "Stack not up" if !deployed
|
177
|
+
stack.resources.each do |resource|
|
178
|
+
begin
|
179
|
+
next if resource.resource_type != "AWS::EC2::Instance"
|
180
|
+
physical_resource_id = resource.physical_resource_id
|
181
|
+
puts "Attempting to #{action} Instance with physical_resource_id: #{physical_resource_id}"
|
182
|
+
@ec2.instances[physical_resource_id].send(action)
|
183
|
+
rescue
|
184
|
+
puts "Some resources are not up."
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
require "cloudformer/version"
|
2
|
+
require "cloudformer/stack"
|
3
|
+
require "rake/tasklib"
|
4
|
+
|
5
|
+
module Cloudformer
|
6
|
+
# CloudFormer Rake Tasks
|
7
|
+
class Tasks < Rake::TaskLib
|
8
|
+
def initialize(stack_name)
|
9
|
+
@stack_name = stack_name
|
10
|
+
@stack = Stack.new(stack_name)
|
11
|
+
if block_given?
|
12
|
+
yield self
|
13
|
+
define_tasks
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
attr_accessor :template, :parameters, :disable_rollback, :retry_delete, :capabilities, :notify, :tags
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def define_tasks
|
22
|
+
define_create_task
|
23
|
+
define_validate_task
|
24
|
+
define_delete_task
|
25
|
+
define_delete_with_stop_task
|
26
|
+
define_events_task
|
27
|
+
define_status_task
|
28
|
+
define_outputs_task
|
29
|
+
define_recreate_task
|
30
|
+
define_stop_task
|
31
|
+
define_start_task
|
32
|
+
end
|
33
|
+
|
34
|
+
def define_create_task
|
35
|
+
desc "Apply Stack with Cloudformation script and parameters."
|
36
|
+
task :apply do
|
37
|
+
retry_delete && @stack.delete
|
38
|
+
result = @stack.apply(template, parameters, disable_rollback, capabilities, notify, tags)
|
39
|
+
result == :Failed && exit(1)
|
40
|
+
result == :NoUpdates && exit(0)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def define_delete_task
|
45
|
+
desc "Delete stack from CloudFormation"
|
46
|
+
task :delete do
|
47
|
+
begin
|
48
|
+
exit 1 unless @stack.delete
|
49
|
+
rescue
|
50
|
+
puts "Stack deleted successfully."
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def define_delete_with_stop_task
|
56
|
+
desc "Delete stack after stopping all instances"
|
57
|
+
task :force_delete do
|
58
|
+
begin
|
59
|
+
@stack.stop_instances
|
60
|
+
exit 1 unless @stack.delete
|
61
|
+
rescue => e
|
62
|
+
puts "Stack delete message - #{e.message}"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def define_status_task
|
68
|
+
desc "Get the deployed app status."
|
69
|
+
task :status do
|
70
|
+
@stack.status
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def define_events_task
|
75
|
+
desc "Get the recent events from the stack."
|
76
|
+
task :events do
|
77
|
+
@stack.events
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def define_outputs_task
|
82
|
+
desc "Get the outputs of stack."
|
83
|
+
task :outputs do
|
84
|
+
@stack.outputs
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def define_recreate_task
|
89
|
+
desc "Recreate stack."
|
90
|
+
task :recreate => %i[delete apply outputs]
|
91
|
+
end
|
92
|
+
|
93
|
+
def define_stop_task
|
94
|
+
desc "Stop EC2 instances in stack."
|
95
|
+
task :stop do
|
96
|
+
@stack.stop_instances
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def define_start_task
|
101
|
+
desc "Start EC2 instances in stack."
|
102
|
+
task :start do
|
103
|
+
@stack.start_instances
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def define_validate_task
|
108
|
+
desc "Validate the Stack."
|
109
|
+
task :validate do
|
110
|
+
@stack.validate(template)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
data/samples/Rakefile
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
require 'cloudformer2'
|
2
|
+
|
3
|
+
Cloudformer::Tasks.new("dennis-test") do |t|
|
4
|
+
t.template = "basic_template.json"
|
5
|
+
#AMI Works in Sydney region only, ensure you supply the right AMI.
|
6
|
+
t.parameters = {"AmiId" => "ami-8da439b7"}
|
7
|
+
t.disable_rollback = true
|
8
|
+
t.capabilities=[]
|
9
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
{
|
2
|
+
"AWSTemplateFormatVersion": "2010-09-09",
|
3
|
+
"Description": "Cloudformer - Demo App",
|
4
|
+
"Parameters": {
|
5
|
+
"AmiId": {
|
6
|
+
"Type": "String"
|
7
|
+
}
|
8
|
+
},
|
9
|
+
"Resources": {
|
10
|
+
"ApplicationServer": {
|
11
|
+
"Type": "AWS::EC2::Instance",
|
12
|
+
"Properties": {
|
13
|
+
"ImageId": {
|
14
|
+
"Ref": "AmiId"
|
15
|
+
},
|
16
|
+
"InstanceType": "t1.micro",
|
17
|
+
"Monitoring": "false"
|
18
|
+
}
|
19
|
+
}
|
20
|
+
},
|
21
|
+
"Outputs": {
|
22
|
+
"Server": {
|
23
|
+
"Value": {
|
24
|
+
"Fn::GetAtt": [
|
25
|
+
"ApplicationServer",
|
26
|
+
"PrivateIp"
|
27
|
+
]
|
28
|
+
}
|
29
|
+
}
|
30
|
+
}
|
31
|
+
}
|
data/spec/stack_spec.rb
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
require 'cloudformer/stack'
|
2
|
+
|
3
|
+
describe Stack do
|
4
|
+
before :each do
|
5
|
+
@cf = double(AWS::CloudFormation)
|
6
|
+
@cf_stack = double(AWS::CloudFormation::Stack)
|
7
|
+
@collection = double(AWS::CloudFormation::StackCollection)
|
8
|
+
AWS::CloudFormation.should_receive(:new).and_return(@cf)
|
9
|
+
@collection.should_receive(:[]).and_return(@cf_stack)
|
10
|
+
@cf.should_receive(:stacks).and_return(@collection)
|
11
|
+
end
|
12
|
+
describe "when deployed" do
|
13
|
+
before :each do
|
14
|
+
@stack = Stack.new("stack")
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should report as the stack being deployed" do
|
18
|
+
@cf_stack.should_receive(:exists?).and_return(true)
|
19
|
+
@stack.deployed.should be
|
20
|
+
end
|
21
|
+
|
22
|
+
## Broken spec from original repo
|
23
|
+
# describe "#delete" do
|
24
|
+
# it "should return a true if delete fails" do
|
25
|
+
# pending
|
26
|
+
# @cf_stack.should_receive(:exists?).and_return(false)
|
27
|
+
# @cf_stack.should_receive(:status)
|
28
|
+
# @stack.delete.should be
|
29
|
+
# end
|
30
|
+
# end
|
31
|
+
end
|
32
|
+
|
33
|
+
describe "when stack is not deployed" do
|
34
|
+
before :each do
|
35
|
+
@stack = Stack.new("stack")
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should report as the stack not being deployed" do
|
39
|
+
@cf_stack.should_receive(:exists?).and_return(false)
|
40
|
+
@stack.deployed.should_not be
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
describe "when stack operation throws ValidationError" do
|
45
|
+
before :each do
|
46
|
+
@stack = Stack.new("stack")
|
47
|
+
@cf_stack.should_receive(:exists?).and_return(true)
|
48
|
+
File.should_receive(:read).and_return("template")
|
49
|
+
@cf.should_receive(:validate_template).and_return({"valid" => true})
|
50
|
+
@cf_stack.should_receive(:update).and_raise(AWS::CloudFormation::Errors::ValidationError)
|
51
|
+
end
|
52
|
+
|
53
|
+
it "apply should return Failed to signal the error" do
|
54
|
+
@stack.apply(nil, nil).should be(:Failed)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
describe "when stack operation throws ValidationError because no updates are to be performed" do
|
59
|
+
before :each do
|
60
|
+
@stack = Stack.new("stack")
|
61
|
+
@cf_stack.should_receive(:exists?).and_return(true)
|
62
|
+
File.should_receive(:read).and_return("template")
|
63
|
+
@cf.should_receive(:validate_template).and_return({"valid" => true})
|
64
|
+
@cf_stack.should_receive(:update).and_raise(AWS::CloudFormation::Errors::ValidationError.new("No updates are to be performed."))
|
65
|
+
end
|
66
|
+
|
67
|
+
it "apply should return NoUpdate to signal the error" do
|
68
|
+
@stack.apply(nil, nil).should be(:NoUpdates)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
describe "when stack update succeeds" do
|
73
|
+
before :each do
|
74
|
+
@stack = Stack.new("stack")
|
75
|
+
@cf_stack.should_receive(:exists?).at_least(:once).and_return(true)
|
76
|
+
File.should_receive(:read).and_return("template")
|
77
|
+
@cf.should_receive(:validate_template).and_return({"valid" => true})
|
78
|
+
@cf_stack.should_receive(:update).and_return(false)
|
79
|
+
@cf_stack.should_receive(:events).and_return([])
|
80
|
+
@cf_stack.should_receive(:status).at_least(:once).and_return("UPDATE_COMPLETE")
|
81
|
+
end
|
82
|
+
|
83
|
+
it "apply should return Succeeded" do
|
84
|
+
@stack.apply(nil, nil).should be(:Succeeded)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
metadata
ADDED
@@ -0,0 +1,149 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: cloudformer2
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.18
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Sentia MPC
|
8
|
+
- Dennis Vink
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2017-07-18 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: bundler
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - "~>"
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: '1.3'
|
21
|
+
type: :development
|
22
|
+
prerelease: false
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - "~>"
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: '1.3'
|
28
|
+
- !ruby/object:Gem::Dependency
|
29
|
+
name: rspec
|
30
|
+
requirement: !ruby/object:Gem::Requirement
|
31
|
+
requirements:
|
32
|
+
- - ">="
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: '0'
|
35
|
+
type: :development
|
36
|
+
prerelease: false
|
37
|
+
version_requirements: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - ">="
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: '0'
|
42
|
+
- !ruby/object:Gem::Dependency
|
43
|
+
name: rake
|
44
|
+
requirement: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - ">="
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: '0'
|
49
|
+
type: :runtime
|
50
|
+
prerelease: false
|
51
|
+
version_requirements: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - ">="
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '0'
|
56
|
+
- !ruby/object:Gem::Dependency
|
57
|
+
name: aws-sdk-v1
|
58
|
+
requirement: !ruby/object:Gem::Requirement
|
59
|
+
requirements:
|
60
|
+
- - "~>"
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: 1.67.0
|
63
|
+
type: :runtime
|
64
|
+
prerelease: false
|
65
|
+
version_requirements: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - "~>"
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: 1.67.0
|
70
|
+
- !ruby/object:Gem::Dependency
|
71
|
+
name: aws-sdk
|
72
|
+
requirement: !ruby/object:Gem::Requirement
|
73
|
+
requirements:
|
74
|
+
- - "~>"
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: 2.10.9
|
77
|
+
type: :runtime
|
78
|
+
prerelease: false
|
79
|
+
version_requirements: !ruby/object:Gem::Requirement
|
80
|
+
requirements:
|
81
|
+
- - "~>"
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: 2.10.9
|
84
|
+
- !ruby/object:Gem::Dependency
|
85
|
+
name: httparty
|
86
|
+
requirement: !ruby/object:Gem::Requirement
|
87
|
+
requirements:
|
88
|
+
- - ">="
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
version: '0'
|
91
|
+
type: :runtime
|
92
|
+
prerelease: false
|
93
|
+
version_requirements: !ruby/object:Gem::Requirement
|
94
|
+
requirements:
|
95
|
+
- - ">="
|
96
|
+
- !ruby/object:Gem::Version
|
97
|
+
version: '0'
|
98
|
+
description: Cloudformation tasks for apply(create/update), delete, recreate on stack
|
99
|
+
along with validations on templates
|
100
|
+
email:
|
101
|
+
- dennis.vink@sentia.com
|
102
|
+
executables: []
|
103
|
+
extensions: []
|
104
|
+
extra_rdoc_files: []
|
105
|
+
files:
|
106
|
+
- ".gitignore"
|
107
|
+
- CHANGELOG.md
|
108
|
+
- Dockerfile
|
109
|
+
- Gemfile
|
110
|
+
- LICENSE.txt
|
111
|
+
- README.md
|
112
|
+
- Rakefile
|
113
|
+
- cloudformer2.gemspec
|
114
|
+
- docker-compose.yml
|
115
|
+
- lib/cloudformer/cloudformer.rb
|
116
|
+
- lib/cloudformer/stack.rb
|
117
|
+
- lib/cloudformer/tasks.rb
|
118
|
+
- lib/cloudformer/version.rb
|
119
|
+
- samples/Rakefile
|
120
|
+
- samples/basic_template.json
|
121
|
+
- spec/stack_spec.rb
|
122
|
+
homepage: https://github.com/sentialabs/cloudformer2
|
123
|
+
licenses:
|
124
|
+
- MIT
|
125
|
+
metadata: {}
|
126
|
+
post_install_message:
|
127
|
+
rdoc_options: []
|
128
|
+
require_paths:
|
129
|
+
- lib
|
130
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
131
|
+
requirements:
|
132
|
+
- - ">="
|
133
|
+
- !ruby/object:Gem::Version
|
134
|
+
version: '0'
|
135
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
136
|
+
requirements:
|
137
|
+
- - ">="
|
138
|
+
- !ruby/object:Gem::Version
|
139
|
+
version: '0'
|
140
|
+
requirements: []
|
141
|
+
rubyforge_project:
|
142
|
+
rubygems_version: 2.5.1
|
143
|
+
signing_key:
|
144
|
+
specification_version: 4
|
145
|
+
summary: Cloudformer attempts to simplify AWS Cloudformation stack creation process
|
146
|
+
in ruby projects by providing reusable rake tasks to perform common operations such
|
147
|
+
as apply(create/update), delete, recreate on stack along with validations on templates.
|
148
|
+
test_files:
|
149
|
+
- spec/stack_spec.rb
|