cloudformation-dsl 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +29 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +201 -0
- data/README.md +102 -0
- data/Rakefile +1 -0
- data/bin/cfntemplate-to-ruby +345 -0
- data/cloudformation-dsl.gemspec +37 -0
- data/docs/Contributing.md +21 -0
- data/docs/Releasing.md +20 -0
- data/examples/cloudformation-ruby-script.rb +204 -0
- data/examples/maps/map.json +9 -0
- data/examples/maps/map.rb +5 -0
- data/examples/maps/map.yaml +5 -0
- data/examples/maps/more_maps/map1.json +8 -0
- data/examples/maps/more_maps/map2.json +8 -0
- data/examples/maps/more_maps/map3.json +8 -0
- data/examples/maps/table.txt +5 -0
- data/examples/maps/vpc.txt +25 -0
- data/examples/userdata.sh +4 -0
- data/initial_contributions.md +5 -0
- data/lib/cloudformation-dsl.rb +14 -0
- data/lib/cloudformation-dsl/helpers.rb +118 -0
- data/lib/cloudformation-dsl/template.rb +100 -0
- data/lib/cloudformation-dsl/version.rb +19 -0
- metadata +114 -0
@@ -0,0 +1,37 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
# Copyright 2013-2014 Bazaarvoice, Inc.
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
# See the License for the specific language governing permissions and
|
15
|
+
# limitations under the License.
|
16
|
+
|
17
|
+
lib = File.expand_path('../lib', __FILE__)
|
18
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
19
|
+
require 'cloudformation-dsl/version'
|
20
|
+
|
21
|
+
Gem::Specification.new do |gem|
|
22
|
+
gem.name = "cloudformation-dsl"
|
23
|
+
gem.version = CloudFormationDSL::VERSION
|
24
|
+
gem.authors = ["Shawn Smith", "Dave Barcelo", "Morgan Fletcher", "Csongor Gyuricza", "Igor Polishchuk", "Nathaniel Eliot", "Jona Fenocchi", "Tony Cui", 'Ho-Sheng Hsiao']
|
25
|
+
gem.email = ["Shawn.Smith@bazaarvoice.com", "Dave.Barcelo@bazaarvoice.com", "Morgan.Fletcher@bazaarvoice.com", "Csongor.Gyuricza@bazaarvoice.com", "Igor.Polishchuk@bazaarvoice.com", "Nathaniel.Eliot@bazaarvoice.com", "Jona.Fenocchi@bazaarvoice.com", "Tony.Cui@bazaarvoice.com", 'talktohosh@gmail.com']
|
26
|
+
gem.description = %q{Ruby DSL library that provides a wrapper around the CloudFormation.}
|
27
|
+
gem.summary = %q{Ruby DSL library that provides a wrapper around the CloudFormation. Written by [Bazaarvoice](http://www.bazaarvoice.com).}
|
28
|
+
gem.homepage = "http://github.com/hosh/cloudformation-dsl-rb"
|
29
|
+
|
30
|
+
gem.files = `git ls-files`.split($/)
|
31
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
32
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
33
|
+
gem.require_paths = %w{lib bin}
|
34
|
+
|
35
|
+
gem.add_runtime_dependency 'json'
|
36
|
+
gem.add_runtime_dependency 'bundler'
|
37
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# Contributing
|
2
|
+
|
3
|
+
Thanks for your interest in contributing! Here are some things you should know.
|
4
|
+
|
5
|
+
## Getting started
|
6
|
+
|
7
|
+
To get started:
|
8
|
+
|
9
|
+
- fork this project on github
|
10
|
+
- create a new branch named after the change you want to make; i.e., `git checkout -b mynewfeature`
|
11
|
+
- make your changes and commit them
|
12
|
+
- send a pull request to this project from your fork/branch
|
13
|
+
|
14
|
+
Once you've sent your pull request, one of the project collaborators will review it and provide feedback. Please accept this commentary as constructive! It is intended as such.
|
15
|
+
|
16
|
+
## git
|
17
|
+
|
18
|
+
We're opinionated about git. Don't be surprised if we ask you to update your pull request to meet the following standards.
|
19
|
+
|
20
|
+
- rebase+squash your branch into a single commit. For clean git history, we'd prefer we merged in just 1 commit that contains the entire set of changes.
|
21
|
+
- don't write commit messages longer than 50 characters. See [How to Write a Git Commit Message](http://chris.beams.io/posts/git-commit/) for some examples of how to achieve this, and why.
|
data/docs/Releasing.md
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
# Releasing
|
2
|
+
|
3
|
+
## Performing releases
|
4
|
+
|
5
|
+
0. Merge the desired commits to master. But merge them cleanly! See: [merging](#merging)
|
6
|
+
1. Edit and commit the version file in `lib/cloudformation-ruby-dsl/version.rb`. Bump the version based on the [version specification](#versioning-specification)
|
7
|
+
2. `git push` to origin/master
|
8
|
+
3. `rake release`
|
9
|
+
|
10
|
+
## Versioning specification
|
11
|
+
|
12
|
+
For this project, we will follow the methodology proposed by http://semver.org/spec/v2.0.0.html.
|
13
|
+
|
14
|
+
1. Major versions break existing interfaces.
|
15
|
+
2. Minor versions are additive only.
|
16
|
+
3. Patch versions are for backward-compatible bug fixes.
|
17
|
+
|
18
|
+
## Merging
|
19
|
+
|
20
|
+
When you use the shiny green "Merge" button on a pull request, github creates a separate commit for the merge (because of the use of the `--no-ff` option). This is noisy and makes git history confusing. Instead of using the green merge button, merge the branch into master using [git-land](https://github.com/bazaarvoice/git-land#git-land) (or manually follow the steps described in the project).
|
@@ -0,0 +1,204 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# Copyright 2013-2014 Bazaarvoice, Inc.
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
# See the License for the specific language governing permissions and
|
15
|
+
# limitations under the License.
|
16
|
+
|
17
|
+
require 'bundler/setup'
|
18
|
+
require 'cloudformation-ruby-dsl/cfntemplate'
|
19
|
+
require 'cloudformation-ruby-dsl/table'
|
20
|
+
|
21
|
+
# Note: this is only intended to demonstrate the cloudformation-ruby-dsl. It compiles
|
22
|
+
# and validates correctly, but won't produce a viable CloudFormation stack.
|
23
|
+
|
24
|
+
template do
|
25
|
+
|
26
|
+
parameter 'Label',
|
27
|
+
:Description => 'The label to apply to the servers.',
|
28
|
+
:Type => 'String',
|
29
|
+
:MinLength => '2',
|
30
|
+
:MaxLength => '25',
|
31
|
+
:AllowedPattern => '[_a-zA-Z0-9]*',
|
32
|
+
:ConstraintDescription => 'Maximum length of the Label parameter may not exceed 25 characters and may only contain letters, numbers and underscores.',
|
33
|
+
# The :Immutable attribute is a Ruby CFN extension. It affects the behavior of the '<template> update ...'
|
34
|
+
# operation in that a stack update may not change the values of parameters marked w/:Immutable => true.
|
35
|
+
:Immutable => true
|
36
|
+
|
37
|
+
parameter 'InstanceType',
|
38
|
+
:Description => 'EC2 instance type',
|
39
|
+
:Type => 'String',
|
40
|
+
:Default => 'm2.xlarge',
|
41
|
+
:AllowedValues => %w(t1.micro m1.small m1.medium m1.large m1.xlarge m2.xlarge m2.2xlarge m2.4xlarge c1.medium c1.xlarge),
|
42
|
+
:ConstraintDescription => 'Must be a valid EC2 instance type.'
|
43
|
+
|
44
|
+
parameter 'ImageId',
|
45
|
+
:Description => 'EC2 Image ID',
|
46
|
+
:Type => 'String',
|
47
|
+
:Default => 'ami-255bbc4c',
|
48
|
+
:AllowedPattern => 'ami-[a-f0-9]{8}',
|
49
|
+
:ConstraintDescription => 'Must be ami-XXXXXXXX (where X is a hexadecimal digit)'
|
50
|
+
|
51
|
+
parameter 'KeyPairName',
|
52
|
+
:Description => 'Name of KeyPair to use.',
|
53
|
+
:Type => 'String',
|
54
|
+
:MinLength => '1',
|
55
|
+
:MaxLength => '40',
|
56
|
+
:Default => parameters['Label']
|
57
|
+
|
58
|
+
parameter 'EmailAddress',
|
59
|
+
:Type => 'String',
|
60
|
+
:Description => 'Email address at which to send notification events.'
|
61
|
+
|
62
|
+
mapping 'InlineExampleMap',
|
63
|
+
:team1 => {
|
64
|
+
:name => 'test1',
|
65
|
+
:email => 'test1@example.com',
|
66
|
+
},
|
67
|
+
:team2 => {
|
68
|
+
:name => 'test2',
|
69
|
+
:email => 'test2@example.com',
|
70
|
+
}
|
71
|
+
|
72
|
+
# Generates mappings from external files with various formats.
|
73
|
+
mapping 'JsonExampleMap', 'maps/map.json'
|
74
|
+
|
75
|
+
mapping 'RubyExampleMap', 'maps/map.rb'
|
76
|
+
|
77
|
+
mapping 'YamlExampleMap', 'maps/map.yaml'
|
78
|
+
|
79
|
+
# Loads JSON mappings dynamically from example directory.
|
80
|
+
Dir.entries('maps/more_maps').each_with_index do |path, index|
|
81
|
+
next if path == "." or path == ".."
|
82
|
+
mapping "ExampleMap#{index - 1}", "maps/more_maps/#{path}"
|
83
|
+
end
|
84
|
+
|
85
|
+
# Selects all rows in the table which match the name/value pairs of the predicate object and returns a
|
86
|
+
# set of nested maps, where the key for the map at level n is the key at index n in the specified keys,
|
87
|
+
# except for the last key in the specified keys which is used to determine the value of the leaf-level map.
|
88
|
+
text = Table.load 'maps/table.txt'
|
89
|
+
mapping 'TableExampleMap',
|
90
|
+
text.get_map({ :column0 => 'foo' }, :column1, :column2, :column3)
|
91
|
+
|
92
|
+
# Shows how to create a table useful for looking up subnets that correspond to a particular env/region for eg. vpc placement.
|
93
|
+
vpc = Table.load 'maps/vpc.txt'
|
94
|
+
mapping 'TableExampleMultimap',
|
95
|
+
vpc.get_multimap({ :visibility => 'private', :zone => ['a', 'c'] }, :env, :region, :subnet)
|
96
|
+
|
97
|
+
# The tag type is a DSL extension; it is not a property of actual CloudFormation templates.
|
98
|
+
# These tags are excised from the template and used to generate a series of --tag arguments which are passed to CloudFormation when a stack is created.
|
99
|
+
# They do not ultimately appear in the expanded CloudFormation template.
|
100
|
+
# The diff subcommand will compare tags with the running stack and identify any changes, but a stack update will do the diff and throw an error on any
|
101
|
+
# changes. The tags are propagated to all resources created by the stack, including the stack itself.
|
102
|
+
#
|
103
|
+
# Amazon has set the following restrictions on CloudFormation tags:
|
104
|
+
# => limit 10
|
105
|
+
# => immutable (you may not update a stack with new tags or different values for existing tags -- they will be rejected)
|
106
|
+
#
|
107
|
+
tag :MyTag => 'MyValue'
|
108
|
+
tag :MyOtherTag => 'My Value With Spaces'
|
109
|
+
|
110
|
+
resource 'SecurityGroup', :Type => 'AWS::EC2::SecurityGroup', :Properties => {
|
111
|
+
:GroupDescription => 'Lets any vpc traffic in.',
|
112
|
+
:SecurityGroupIngress => {:IpProtocol => '-1', :FromPort => '0', :ToPort => '65535', :CidrIp => "10.0.0.0/8"}
|
113
|
+
}
|
114
|
+
|
115
|
+
resource "ASG", :Type => 'AWS::AutoScaling::AutoScalingGroup', :Properties => {
|
116
|
+
:AvailabilityZones => 'us-east-1',
|
117
|
+
:HealthCheckType => 'EC2',
|
118
|
+
:LaunchConfigurationName => ref('LaunchConfig'),
|
119
|
+
:MinSize => 1,
|
120
|
+
:MaxSize => 5,
|
121
|
+
:NotificationConfiguration => {
|
122
|
+
:TopicARN => ref('EmailSNSTopic'),
|
123
|
+
:NotificationTypes => %w(autoscaling:EC2_INSTANCE_LAUNCH autoscaling:EC2_INSTANCE_LAUNCH_ERROR autoscaling:EC2_INSTANCE_TERMINATE autoscaling:EC2_INSTANCE_TERMINATE_ERROR),
|
124
|
+
},
|
125
|
+
:Tags => [
|
126
|
+
{
|
127
|
+
:Key => 'Name',
|
128
|
+
# Grabs a value in an external map file.
|
129
|
+
:Value => find_in_map('TableExampleMap', 'corge', 'grault'),
|
130
|
+
:PropagateAtLaunch => 'true',
|
131
|
+
},
|
132
|
+
{
|
133
|
+
:Key => 'Label',
|
134
|
+
:Value => parameters['Label'],
|
135
|
+
:PropagateAtLaunch => 'true',
|
136
|
+
}
|
137
|
+
],
|
138
|
+
}
|
139
|
+
|
140
|
+
resource 'EmailSNSTopic', :Type => 'AWS::SNS::Topic', :Properties => {
|
141
|
+
:Subscription => [
|
142
|
+
{
|
143
|
+
:Endpoint => ref('EmailAddress'),
|
144
|
+
:Protocol => 'email',
|
145
|
+
},
|
146
|
+
],
|
147
|
+
}
|
148
|
+
|
149
|
+
resource 'WaitConditionHandle', :Type => 'AWS::CloudFormation::WaitConditionHandle', :Properties => {}
|
150
|
+
|
151
|
+
resource 'WaitCondition', :Type => 'AWS::CloudFormation::WaitCondition', :DependsOn => 'ASG', :Properties => {
|
152
|
+
:Handle => ref('WaitConditionHandle'),
|
153
|
+
:Timeout => 1200,
|
154
|
+
:Count => "1"
|
155
|
+
}
|
156
|
+
|
157
|
+
resource 'LaunchConfig', :Type => 'AWS::AutoScaling::LaunchConfiguration', :Properties => {
|
158
|
+
:ImageId => parameters['ImageId'],
|
159
|
+
:KeyName => ref('KeyPairName'),
|
160
|
+
:IamInstanceProfile => ref('InstanceProfile'),
|
161
|
+
:InstanceType => ref('InstanceType'),
|
162
|
+
:InstanceMonitoring => 'false',
|
163
|
+
:SecurityGroups => [ref('SecurityGroup')],
|
164
|
+
:BlockDeviceMappings => [
|
165
|
+
{:DeviceName => '/dev/sdb', :VirtualName => 'ephemeral0'},
|
166
|
+
{:DeviceName => '/dev/sdc', :VirtualName => 'ephemeral1'},
|
167
|
+
{:DeviceName => '/dev/sdd', :VirtualName => 'ephemeral2'},
|
168
|
+
{:DeviceName => '/dev/sde', :VirtualName => 'ephemeral3'},
|
169
|
+
],
|
170
|
+
# Loads an external userdata script with an interpolated argument.
|
171
|
+
:UserData => base64(interpolate(file('userdata.sh'), time: Time.now)),
|
172
|
+
}
|
173
|
+
|
174
|
+
resource 'InstanceProfile', :Type => 'AWS::IAM::InstanceProfile', :Properties => {
|
175
|
+
# use cfn intrinsic conditional to choose the 2nd value because the expression evaluates to false
|
176
|
+
:Path => fn_if(equal(3, 0), '/unselected/', '/'),
|
177
|
+
:Roles => [ ref('InstanceRole') ],
|
178
|
+
}
|
179
|
+
|
180
|
+
resource 'InstanceRole', :Type => 'AWS::IAM::Role', :Properties => {
|
181
|
+
:AssumeRolePolicyDocument => {
|
182
|
+
:Statement => [
|
183
|
+
{
|
184
|
+
:Effect => 'Allow',
|
185
|
+
:Principal => { :Service => [ 'ec2.amazonaws.com' ] },
|
186
|
+
:Action => [ 'sts:AssumeRole' ],
|
187
|
+
},
|
188
|
+
],
|
189
|
+
},
|
190
|
+
:Path => '/',
|
191
|
+
}
|
192
|
+
|
193
|
+
# add conditions that can be used elsewhere in the template
|
194
|
+
condition 'myCondition', fn_and(equal("one", "two"), not_equal("three", "four"))
|
195
|
+
|
196
|
+
output 'EmailSNSTopicARN',
|
197
|
+
:Value => ref('EmailSNSTopic'),
|
198
|
+
:Description => 'ARN of SNS Topic used to send emails on events.'
|
199
|
+
|
200
|
+
output 'MappingLookup',
|
201
|
+
:Value => find_in_map('TableExampleMap', 'corge', 'grault'),
|
202
|
+
:Description => 'An example map lookup.'
|
203
|
+
|
204
|
+
end.exec!
|
@@ -0,0 +1,25 @@
|
|
1
|
+
env region zone visibility subnet
|
2
|
+
qa us-east-1 a public subnet-aaaa
|
3
|
+
qa us-east-1 a private subnet-bbbb
|
4
|
+
qa us-east-1 b public subnet-cccc
|
5
|
+
qa us-east-1 b private subnet-dddd
|
6
|
+
qa us-east-1 c public subnet-eeee
|
7
|
+
qa us-east-1 c private subnet-ffff
|
8
|
+
qa eu-west-1 a public subnet-gggg
|
9
|
+
qa eu-west-1 a private subnet-hhhh
|
10
|
+
qa eu-west-1 b public subnet-iiii
|
11
|
+
qa eu-west-1 b private subnet-jjjj
|
12
|
+
qa eu-west-1 c public subnet-kkkk
|
13
|
+
qa eu-west-1 c private subnet-llll
|
14
|
+
prod us-east-1 a public subnet-mmmm
|
15
|
+
prod us-east-1 a private subnet-nnnn
|
16
|
+
prod us-east-1 b public subnet-oooo
|
17
|
+
prod us-east-1 b private subnet-pppp
|
18
|
+
prod us-east-1 c public subnet-qqqq
|
19
|
+
prod us-east-1 c private subnet-rrrr
|
20
|
+
prod eu-west-1 a public subnet-ssss
|
21
|
+
prod eu-west-1 a private subnet-tttt
|
22
|
+
prod eu-west-1 b public subnet-uuuu
|
23
|
+
prod eu-west-1 b private subnet-vvvv
|
24
|
+
prod eu-west-1 c public subnet-wwww
|
25
|
+
prod eu-west-1 c private subnet-xxxx
|
@@ -0,0 +1,5 @@
|
|
1
|
+
Several people were instrumental in building this project during its incubation stage. Because the process of open sourcing involved squashing the repository to remove confidential information, some of their contributions have been lost from the history that Github displays.
|
2
|
+
|
3
|
+
- [Shawn Smith](https://github.com/shawnsmith): initial implementation
|
4
|
+
- [Nathaniel Eliot](https://github.com/temujin9): converted from raw library to gem, refactored table code, cleaned and prepared code for open sourcing
|
5
|
+
- [Jona Fenocchi](http://github.com/jonaf): added support for CloudFormation tags
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require "cloudformation-dsl/version"
|
2
|
+
|
3
|
+
module CloudFormationDSL
|
4
|
+
autoload :Template, 'cloudformation-dsl/template'
|
5
|
+
autoload :Helpers, 'cloudformation-dsl/helpers'
|
6
|
+
|
7
|
+
def self.describe(&block)
|
8
|
+
CloudFormationDSL::Template.new(&block)
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.load_from(filename)
|
12
|
+
CloudFormationDSL::Template.new(filename)
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
module CloudFormationDSL
|
2
|
+
module Helpers
|
3
|
+
def load_from_file(filename)
|
4
|
+
file = File.open(filename)
|
5
|
+
# Figure out what the file extension is and process accordingly.
|
6
|
+
case File.extname(filename)
|
7
|
+
when ".rb"; eval(file.read, nil, filename)
|
8
|
+
when ".json"; JSON.load(file)
|
9
|
+
when ".yaml"; YAML::load(file)
|
10
|
+
else
|
11
|
+
raise("Do not recognize extension of #{filename}.")
|
12
|
+
end
|
13
|
+
ensure
|
14
|
+
file.close
|
15
|
+
end
|
16
|
+
|
17
|
+
def find_in_map(map, key, name)
|
18
|
+
# Eagerly evaluate mappings when all keys are known at template expansion time
|
19
|
+
if map.is_a?(String) && key.is_a?(String) && name.is_a?(String)
|
20
|
+
# We don't know whether the map was built with string keys or symbol keys. Try both.
|
21
|
+
def get(map, key) map[key] || map.fetch(key.to_sym) end
|
22
|
+
get(get(@dict.fetch(:Mappings).fetch(map), key), name)
|
23
|
+
else
|
24
|
+
{ :'Fn::FindInMap' => [ map, key, name ] }
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# Formation helpers
|
29
|
+
|
30
|
+
def base64(value) { :'Fn::Base64' => value } end
|
31
|
+
|
32
|
+
def find_in_map(map, key, name) { :'Fn::FindInMap' => [ map, key, name ] } end
|
33
|
+
|
34
|
+
def get_att(resource, attribute) { :'Fn::GetAtt' => [ resource, attribute ] } end
|
35
|
+
|
36
|
+
def get_azs(region = '') { :'Fn::GetAZs' => region } end
|
37
|
+
|
38
|
+
def join(delim, *list)
|
39
|
+
case list.length
|
40
|
+
when 0 then ''
|
41
|
+
when 1 then list[0]
|
42
|
+
else join_list(delim,list)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# Variant of join that matches the native CFN syntax.
|
47
|
+
def join_list(delim, list) { :'Fn::Join' => [ delim, list ] } end
|
48
|
+
|
49
|
+
def equal(one, two) { :'Fn::Equals' => [one, two] } end
|
50
|
+
|
51
|
+
def fn_not(condition) { :'Fn::Not' => [condition] } end
|
52
|
+
|
53
|
+
def fn_or(*condition_list)
|
54
|
+
case condition_list.length
|
55
|
+
when 0..1 then raise "fn_or needs at least 2 items."
|
56
|
+
when 2..10 then { :'Fn::Or' => condition_list }
|
57
|
+
else raise "fn_or needs a list of 2-10 items that evaluate to true/false."
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def fn_and(*condition_list)
|
62
|
+
case condition_list.length
|
63
|
+
when 0..1 then raise "fn_and needs at least 2 items."
|
64
|
+
when 2..10 then { :'Fn::And' => condition_list }
|
65
|
+
else raise "fn_and needs a list of 2-10 items that evaluate to true/false."
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def fn_if(cond, if_true, if_false) { :'Fn::If' => [cond, if_true, if_false] } end
|
70
|
+
|
71
|
+
def not_equal(one, two) fn_not(equal(one,two)) end
|
72
|
+
|
73
|
+
def select(index, list) { :'Fn::Select' => [ index, list ] } end
|
74
|
+
|
75
|
+
def ref(name) { :Ref => name } end
|
76
|
+
|
77
|
+
def aws_account_id() ref("AWS::AccountId") end
|
78
|
+
|
79
|
+
def aws_notification_arns() ref("AWS::NotificationARNs") end
|
80
|
+
|
81
|
+
def aws_no_value() ref("AWS::NoValue") end
|
82
|
+
|
83
|
+
def aws_stack_id() ref("AWS::StackId") end
|
84
|
+
|
85
|
+
def aws_stack_name() ref("AWS::StackName") end
|
86
|
+
|
87
|
+
# deprecated, for backward compatibility
|
88
|
+
def no_value()
|
89
|
+
warn_deprecated('no_value()', 'aws_no_value()')
|
90
|
+
aws_no_value()
|
91
|
+
end
|
92
|
+
|
93
|
+
# Read the specified file and return its value as a string literal
|
94
|
+
def file(filename) File.read(File.absolute_path(filename, File.dirname($PROGRAM_NAME))) end
|
95
|
+
|
96
|
+
# Interpolates a string like "NAME={{ref('Service')}}" and returns a CloudFormation "Fn::Join"
|
97
|
+
# operation to collect the results. Anything between {{ and }} is interpreted as a Ruby expression
|
98
|
+
# and eval'd. This is especially useful with Ruby "here" documents.
|
99
|
+
# Local variables may also be exposed to the string via the `locals` hash.
|
100
|
+
def interpolate(string, locals={})
|
101
|
+
list = []
|
102
|
+
while string.length > 0
|
103
|
+
head, match, string = string.partition(/\{\{.*?\}\}/)
|
104
|
+
list << head if head.length > 0
|
105
|
+
list << eval(match[2..-3], nil, 'interpolated string') if match.length > 0
|
106
|
+
end
|
107
|
+
|
108
|
+
# Split out strings in an array by newline, for visibility
|
109
|
+
list = list.flat_map {|value| value.is_a?(String) ? value.lines.to_a : value }
|
110
|
+
join('', *list)
|
111
|
+
end
|
112
|
+
|
113
|
+
def join_interpolate(delim, string)
|
114
|
+
$stderr.puts "join_interpolate(delim,string) has been deprecated; use interpolate(string) instead"
|
115
|
+
interpolate(string)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|