cfer 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +15 -0
- data/.gitignore +11 -0
- data/.rspec +2 -0
- data/.travis.yml +16 -0
- data/.yardopts +1 -0
- data/CODE_OF_CONDUCT.md +13 -0
- data/Gemfile +18 -0
- data/LICENSE.txt +21 -0
- data/README.md +221 -0
- data/Rakefile +41 -0
- data/bin/cfer +13 -0
- data/bin/cfer-dbg +25 -0
- data/bin/console +14 -0
- data/bin/setup +7 -0
- data/cfer-demo.gif +0 -0
- data/cfer.gemspec +37 -0
- data/examples/instance.rb +84 -0
- data/examples/vpc.rb +84 -0
- data/lib/cfer.rb +236 -0
- data/lib/cfer/block.rb +33 -0
- data/lib/cfer/cfn/aws.rb +29 -0
- data/lib/cfer/cfn/client.rb +164 -0
- data/lib/cfer/cli.rb +138 -0
- data/lib/cfer/core/client.rb +15 -0
- data/lib/cfer/core/fn.rb +55 -0
- data/lib/cfer/core/resource.rb +44 -0
- data/lib/cfer/core/stack.rb +170 -0
- data/lib/cfer/util/error.rb +31 -0
- data/lib/cfer/version.rb +3 -0
- data/lib/cferext/aws/ec2/instance.rb +12 -0
- data/lib/cferext/provisioning.rb +6 -0
- metadata +271 -0
@@ -0,0 +1,170 @@
|
|
1
|
+
module Cfer::Core
|
2
|
+
|
3
|
+
# Defines the structure of a CloudFormation stack
|
4
|
+
class Stack < Cfer::Block
|
5
|
+
include Cfer::Core
|
6
|
+
include Cfer::Cfn
|
7
|
+
|
8
|
+
attr_reader :parameters
|
9
|
+
attr_reader :options
|
10
|
+
attr_reader :git
|
11
|
+
|
12
|
+
def converge!
|
13
|
+
if @options[:client]
|
14
|
+
@options[:client].converge self
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def tail!(&block)
|
19
|
+
if @options[:client]
|
20
|
+
@options[:client].tail self, &block
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def resolve(val)
|
25
|
+
@options[:client] ? @options[:client].resolve(val) : val
|
26
|
+
end
|
27
|
+
|
28
|
+
def initialize(options = {})
|
29
|
+
self[:AWSTemplateFormatVersion] = '2010-09-09'
|
30
|
+
self[:Description] = ''
|
31
|
+
|
32
|
+
@options = options
|
33
|
+
@git = Rugged::Repository.discover('.')
|
34
|
+
|
35
|
+
self[:Parameters] = {}
|
36
|
+
self[:Mappings] = {}
|
37
|
+
self[:Conditions] = {}
|
38
|
+
self[:Resources] = {}
|
39
|
+
self[:Outputs] = {}
|
40
|
+
|
41
|
+
@parameters = HashWithIndifferentAccess.new
|
42
|
+
|
43
|
+
if options[:parameters]
|
44
|
+
options[:parameters].each do |key, val|
|
45
|
+
@parameters[key] = resolve(val)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def pre_block
|
51
|
+
end
|
52
|
+
|
53
|
+
# Sets the description for this CloudFormation stack
|
54
|
+
def description(desc)
|
55
|
+
self[:Description] = desc
|
56
|
+
end
|
57
|
+
|
58
|
+
# Declares a CloudFormation parameter
|
59
|
+
#
|
60
|
+
# @param name [String] The parameter name
|
61
|
+
# @param options [Hash]
|
62
|
+
# @option options [String] :type The type for the CloudFormation parameter
|
63
|
+
# @option options [String] :default A value of the appropriate type for the template to use if no value is specified when a stack is created. If you define constraints for the parameter, you must specify a value that adheres to those constraints.
|
64
|
+
# @option options [String] :no_echo Whether to mask the parameter value whenever anyone makes a call that describes the stack. If you set the value to `true`, the parameter value is masked with asterisks (*****).
|
65
|
+
# @option options [String] :allowed_values An array containing the list of values allowed for the parameter.
|
66
|
+
# @option options [String] :allowed_pattern A regular expression that represents the patterns you want to allow for String types.
|
67
|
+
# @option options [Number] :max_length An integer value that determines the largest number of characters you want to allow for String types.
|
68
|
+
# @option options [Number] :min_length An integer value that determines the smallest number of characters you want to allow for String types.
|
69
|
+
# @option options [Number] :max_value A numeric value that determines the largest numeric value you want to allow for Number types.
|
70
|
+
# @option options [Number] :min_value A numeric value that determines the smallest numeric value you want to allow for Number types.
|
71
|
+
# @option options [String] :description A string of up to 4000 characters that describes the parameter.
|
72
|
+
# @option options [String] :constraint_description A string that explains the constraint when the constraint is violated. For example, without a constraint description, a parameter that has an allowed pattern of `[A-Za-z0-9]+` displays the following error message when the user specifies an invalid value:
|
73
|
+
#
|
74
|
+
# ```Malformed input-Parameter MyParameter must match pattern [A-Za-z0-9]+```
|
75
|
+
#
|
76
|
+
# By adding a constraint description, such as must only contain upper- and lowercase letters, and numbers, you can display a customized error message:
|
77
|
+
#
|
78
|
+
# ```Malformed input-Parameter MyParameter must only contain upper and lower case letters and numbers```
|
79
|
+
def parameter(name, options = {})
|
80
|
+
param = {}
|
81
|
+
options.each do |key, v|
|
82
|
+
k = key.to_s.camelize.to_sym
|
83
|
+
param[k] =
|
84
|
+
case k
|
85
|
+
when :AllowedValues
|
86
|
+
str_list = v.join(',')
|
87
|
+
verify_param(name, "Parameter #{name} must be one of: #{str_list}") { |input_val| str_list.include?(input_val) }
|
88
|
+
str_list
|
89
|
+
when :AllowedPattern
|
90
|
+
if v.class == Regexp
|
91
|
+
verify_param(name, "Parameter #{name} must match /#{v.source}/") { |input_val| v =~ input_val }
|
92
|
+
v.source
|
93
|
+
else
|
94
|
+
verify_param(name, "Parameter #{name} must match /#{v}/") { |input_val| Regexp.new(v) =~ input_val }
|
95
|
+
v
|
96
|
+
end
|
97
|
+
when :MaxLength
|
98
|
+
verify_param(name, "Parameter #{name} must have length <= #{v}") { |input_val| input_val.length <= v.to_i }
|
99
|
+
v
|
100
|
+
when :MinLength
|
101
|
+
verify_param(name, "Parameter #{name} must have length >= #{v}") { |input_val| input_val.length >= v.to_i }
|
102
|
+
v
|
103
|
+
when :MaxValue
|
104
|
+
verify_param(name, "Parameter #{name} must be <= #{v}") { |input_val| input_val.to_i <= v.to_i }
|
105
|
+
v
|
106
|
+
when :MinValue
|
107
|
+
verify_param(name, "Parameter #{name} must be >= #{v}") { |input_val| input_val.to_i >= v.to_i }
|
108
|
+
v
|
109
|
+
when :Description
|
110
|
+
Preconditions.check_argument(v.length <= 4000, "#{key} must be <= 4000 characters")
|
111
|
+
v
|
112
|
+
when :Default
|
113
|
+
@parameters[name] ||= resolve(v)
|
114
|
+
end
|
115
|
+
param[k] ||= v
|
116
|
+
end
|
117
|
+
param[:Type] ||= 'String'
|
118
|
+
self[:Parameters][name] = param
|
119
|
+
end
|
120
|
+
|
121
|
+
# Sets the mappings block for this stack. See [The CloudFormation Documentation](http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/mappings-section-structure.html) for more details
|
122
|
+
def mappings(mappings)
|
123
|
+
self[:Mappings] = mappings
|
124
|
+
end
|
125
|
+
|
126
|
+
# Adds a condition to the template.
|
127
|
+
# @param name [String] The name of the condition.
|
128
|
+
# @param expr [Hash] The CloudFormation condition to add. See [The Cloudformation Documentation](http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/conditions-section-structure.html) for more details
|
129
|
+
def condition(name, expr)
|
130
|
+
self[:Conditions][name] = expr
|
131
|
+
end
|
132
|
+
|
133
|
+
# Creates a CloudFormation resource
|
134
|
+
# @param name [String] The name of the resource (must be alphanumeric)
|
135
|
+
# @param type [String] The type of CloudFormation resource to create.
|
136
|
+
# @param options [Hash] Additional attributes to add to the resource block (such as the `UpdatePolicy` for an `AWS::AutoScaling::AutoScalingGroup`)
|
137
|
+
def resource(name, type, options = {}, &block)
|
138
|
+
Preconditions.check_argument(/[[:alnum:]]+/ =~ name, "Resource name must be alphanumeric")
|
139
|
+
|
140
|
+
clazz = "CferExt::#{type}".split('::').inject(Object) { |o, c| o.const_get c if o && o.const_defined?(c) } || Cfer::Cfn::Resource
|
141
|
+
Preconditions.check_argument clazz <= Cfer::Cfn::Resource, "#{type} is not a valid resource type because CferExt::#{type} does not inherit from `Cfer::Cfn::Resource`"
|
142
|
+
|
143
|
+
rc = clazz.new(name, type, options, &block)
|
144
|
+
|
145
|
+
self[:Resources][name] = rc
|
146
|
+
rc
|
147
|
+
end
|
148
|
+
|
149
|
+
# Adds an output to the CloudFormation stack.
|
150
|
+
# @param name [String] The Logical ID of the output parameter
|
151
|
+
# @param value [String] Value to return
|
152
|
+
# @param options [Hash] Extra options for this output parameter
|
153
|
+
# @option options [String] :Description Informationa bout the value
|
154
|
+
def output(name, value, options = {})
|
155
|
+
self[:Outputs][name] = options.merge('Value' => value)
|
156
|
+
end
|
157
|
+
|
158
|
+
# Renders the stack into a CloudFormation template.
|
159
|
+
# @return [String] The final template
|
160
|
+
def to_cfn
|
161
|
+
to_h.to_json
|
162
|
+
end
|
163
|
+
|
164
|
+
private
|
165
|
+
def verify_param(param_name, err_msg)
|
166
|
+
raise Cfer::Util::CferError, err_msg if (@parameters[param_name] && !yield(@parameters[param_name].to_s))
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Cfer::Util
|
2
|
+
require 'highline/import'
|
3
|
+
class CferError < StandardError
|
4
|
+
end
|
5
|
+
|
6
|
+
class StackExistsError < CferError
|
7
|
+
end
|
8
|
+
|
9
|
+
class TemplateError < CferError
|
10
|
+
attr_reader :template_backtrace
|
11
|
+
|
12
|
+
def initialize(template_backtrace)
|
13
|
+
@template_backtrace = template_backtrace
|
14
|
+
super
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.bug_report(e)
|
19
|
+
gather_report e
|
20
|
+
transmit_report if agree('Would you like to send this information in a bug report? (type yes/no)')
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
def self.gather_report(e)
|
25
|
+
puts e
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.transmit_report
|
29
|
+
puts "Sending report."
|
30
|
+
end
|
31
|
+
end
|
data/lib/cfer/version.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'cferext/provisioning'
|
2
|
+
|
3
|
+
module CferExt::AWS::EC2
|
4
|
+
class Instance < Cfer::Cfn::Resource
|
5
|
+
include CferExt::Provisioning
|
6
|
+
|
7
|
+
def initialize(name, type, options = {}, &block)
|
8
|
+
options[:Metadata] ||= {}
|
9
|
+
super(name, type, options, &block)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
metadata
ADDED
@@ -0,0 +1,271 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: cfer
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Sean Edwards
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-06-05 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: docile
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ! '>='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ! '>='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: thor
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ! '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ! '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: activesupport
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ! '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :runtime
|
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: aws-sdk
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ! '>='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: aws-sdk-resources
|
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: preconditions
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ! '>='
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :runtime
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ! '>='
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: semantic
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ! '>='
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :runtime
|
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: rainbow
|
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: highline
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - ! '>='
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0'
|
132
|
+
type: :runtime
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - ! '>='
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0'
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: rugged
|
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: table_print
|
155
|
+
requirement: !ruby/object:Gem::Requirement
|
156
|
+
requirements:
|
157
|
+
- - ! '>='
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
version: '0'
|
160
|
+
type: :runtime
|
161
|
+
prerelease: false
|
162
|
+
version_requirements: !ruby/object:Gem::Requirement
|
163
|
+
requirements:
|
164
|
+
- - ! '>='
|
165
|
+
- !ruby/object:Gem::Version
|
166
|
+
version: '0'
|
167
|
+
- !ruby/object:Gem::Dependency
|
168
|
+
name: rake
|
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
|
+
- !ruby/object:Gem::Dependency
|
182
|
+
name: bundler
|
183
|
+
requirement: !ruby/object:Gem::Requirement
|
184
|
+
requirements:
|
185
|
+
- - ! '>='
|
186
|
+
- !ruby/object:Gem::Version
|
187
|
+
version: '0'
|
188
|
+
type: :development
|
189
|
+
prerelease: false
|
190
|
+
version_requirements: !ruby/object:Gem::Requirement
|
191
|
+
requirements:
|
192
|
+
- - ! '>='
|
193
|
+
- !ruby/object:Gem::Version
|
194
|
+
version: '0'
|
195
|
+
- !ruby/object:Gem::Dependency
|
196
|
+
name: yard
|
197
|
+
requirement: !ruby/object:Gem::Requirement
|
198
|
+
requirements:
|
199
|
+
- - ! '>='
|
200
|
+
- !ruby/object:Gem::Version
|
201
|
+
version: '0'
|
202
|
+
type: :development
|
203
|
+
prerelease: false
|
204
|
+
version_requirements: !ruby/object:Gem::Requirement
|
205
|
+
requirements:
|
206
|
+
- - ! '>='
|
207
|
+
- !ruby/object:Gem::Version
|
208
|
+
version: '0'
|
209
|
+
description: Toolkit for automating infrastructure using AWS CloudFormation
|
210
|
+
email:
|
211
|
+
- stedwards87+git@gmail.com
|
212
|
+
executables:
|
213
|
+
- cfer
|
214
|
+
extensions: []
|
215
|
+
extra_rdoc_files: []
|
216
|
+
files:
|
217
|
+
- .gitignore
|
218
|
+
- .rspec
|
219
|
+
- .travis.yml
|
220
|
+
- .yardopts
|
221
|
+
- CODE_OF_CONDUCT.md
|
222
|
+
- Gemfile
|
223
|
+
- LICENSE.txt
|
224
|
+
- README.md
|
225
|
+
- Rakefile
|
226
|
+
- bin/cfer
|
227
|
+
- bin/cfer-dbg
|
228
|
+
- bin/console
|
229
|
+
- bin/setup
|
230
|
+
- cfer-demo.gif
|
231
|
+
- cfer.gemspec
|
232
|
+
- examples/instance.rb
|
233
|
+
- examples/vpc.rb
|
234
|
+
- lib/cfer.rb
|
235
|
+
- lib/cfer/block.rb
|
236
|
+
- lib/cfer/cfn/aws.rb
|
237
|
+
- lib/cfer/cfn/client.rb
|
238
|
+
- lib/cfer/cli.rb
|
239
|
+
- lib/cfer/core/client.rb
|
240
|
+
- lib/cfer/core/fn.rb
|
241
|
+
- lib/cfer/core/resource.rb
|
242
|
+
- lib/cfer/core/stack.rb
|
243
|
+
- lib/cfer/util/error.rb
|
244
|
+
- lib/cfer/version.rb
|
245
|
+
- lib/cferext/aws/ec2/instance.rb
|
246
|
+
- lib/cferext/provisioning.rb
|
247
|
+
homepage: https://github.com/seanedwards/cfer
|
248
|
+
licenses:
|
249
|
+
- MIT
|
250
|
+
metadata: {}
|
251
|
+
post_install_message:
|
252
|
+
rdoc_options: []
|
253
|
+
require_paths:
|
254
|
+
- lib
|
255
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
256
|
+
requirements:
|
257
|
+
- - ! '>='
|
258
|
+
- !ruby/object:Gem::Version
|
259
|
+
version: '0'
|
260
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
261
|
+
requirements:
|
262
|
+
- - ! '>='
|
263
|
+
- !ruby/object:Gem::Version
|
264
|
+
version: '0'
|
265
|
+
requirements: []
|
266
|
+
rubyforge_project:
|
267
|
+
rubygems_version: 2.4.5
|
268
|
+
signing_key:
|
269
|
+
specification_version: 4
|
270
|
+
summary: Toolkit for automating infrastructure using AWS CloudFormation
|
271
|
+
test_files: []
|