cfndsl 0.4.4 → 0.5.0.pre
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -13
- data/.rubocop.yml +23 -0
- data/Gemfile +4 -0
- data/Rakefile +19 -17
- data/bin/cfndsl +20 -20
- data/cfndsl.gemspec +16 -15
- data/lib/cfndsl.rb +62 -68
- data/lib/cfndsl/aws/cloud_formation_template.rb +16 -0
- data/lib/cfndsl/aws/types.rb +12 -0
- data/lib/cfndsl/{aws_types.yaml → aws/types.yaml} +0 -0
- data/lib/cfndsl/{Conditions.rb → conditions.rb} +5 -7
- data/lib/cfndsl/creation_policy.rb +21 -0
- data/lib/cfndsl/errors.rb +29 -0
- data/lib/cfndsl/generate_types.rb +154 -0
- data/lib/cfndsl/jsonable.rb +214 -0
- data/lib/cfndsl/mappings.rb +23 -0
- data/lib/cfndsl/metadata.rb +16 -0
- data/lib/cfndsl/module.rb +52 -51
- data/lib/cfndsl/names.rb +5 -5
- data/lib/cfndsl/orchestration_template.rb +173 -0
- data/lib/cfndsl/os/heat_template.rb +16 -0
- data/lib/cfndsl/os/types.rb +12 -0
- data/lib/cfndsl/{os_types.yaml → os/types.yaml} +11 -11
- data/lib/cfndsl/{Outputs.rb → outputs.rb} +3 -4
- data/lib/cfndsl/{Parameters.rb → parameters.rb} +12 -13
- data/lib/cfndsl/plurals.rb +34 -0
- data/lib/cfndsl/properties.rb +21 -0
- data/lib/cfndsl/rake_task.rb +9 -7
- data/lib/cfndsl/ref_check.rb +44 -0
- data/lib/cfndsl/{Resources.rb → resources.rb} +13 -15
- data/lib/cfndsl/types.rb +151 -0
- data/lib/cfndsl/update_policy.rb +25 -0
- data/lib/cfndsl/version.rb +1 -1
- data/sample/autoscale.rb +152 -158
- data/sample/autoscale2.rb +151 -155
- data/sample/circular.rb +30 -33
- data/sample/codedeploy.rb +35 -36
- data/sample/config_service.rb +120 -0
- data/sample/ecs.rb +39 -39
- data/sample/iam_policies.rb +82 -0
- data/sample/lambda.rb +20 -24
- data/sample/s3.rb +11 -11
- data/sample/t1.rb +7 -9
- data/sample/vpc_example.rb +50 -0
- data/sample/vpc_with_vpn_example.rb +97 -0
- data/spec/cfndsl_spec.rb +22 -11
- data/spec/fixtures/heattest.rb +13 -14
- data/spec/fixtures/test.rb +56 -53
- metadata +36 -30
- data/lib/cfndsl/CloudFormationTemplate.rb +0 -267
- data/lib/cfndsl/CreationPolicy.rb +0 -25
- data/lib/cfndsl/Errors.rb +0 -31
- data/lib/cfndsl/JSONable.rb +0 -235
- data/lib/cfndsl/Mappings.rb +0 -25
- data/lib/cfndsl/Metadata.rb +0 -22
- data/lib/cfndsl/Plurals.rb +0 -35
- data/lib/cfndsl/Properties.rb +0 -25
- data/lib/cfndsl/RefCheck.rb +0 -48
- data/lib/cfndsl/Types.rb +0 -309
- data/lib/cfndsl/UpdatePolicy.rb +0 -29
- data/sample/config-service.rb +0 -119
- data/sample/iam-policies.rb +0 -82
- data/sample/vpc-example.rb +0 -51
- data/sample/vpc-with-vpn-example.rb +0 -97
File without changes
|
@@ -1,13 +1,11 @@
|
|
1
|
-
require 'cfndsl/
|
1
|
+
require 'cfndsl/jsonable'
|
2
2
|
|
3
3
|
module CfnDsl
|
4
|
+
# Handles condition objects
|
5
|
+
#
|
6
|
+
# Usage:
|
7
|
+
# Condition :ConditionName, FnEqual(Ref(:ParameterName), 'helloworld')
|
4
8
|
class ConditionDefinition < JSONable
|
5
|
-
#
|
6
|
-
# Handles condition objects
|
7
|
-
#
|
8
|
-
# Usage:
|
9
|
-
# Condition :ConditionName, FnEqual(Ref(:ParameterName), 'helloworld')
|
10
|
-
|
11
9
|
def initialize(value)
|
12
10
|
@value = value
|
13
11
|
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'cfndsl/jsonable'
|
2
|
+
|
3
|
+
module CfnDsl
|
4
|
+
# Handles creation policy objects for Resources
|
5
|
+
#
|
6
|
+
# Usage
|
7
|
+
# Resource("aaa") {
|
8
|
+
# CreationPolicy('ResourceSignal', { 'Count' => 1, 'Timeout' => 'PT10M' })
|
9
|
+
# }
|
10
|
+
class CreationPolicyDefinition < JSONable
|
11
|
+
attr_reader :value
|
12
|
+
|
13
|
+
def initialize(value)
|
14
|
+
@value = value
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_json(*a)
|
18
|
+
@value.to_json(*a)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module CfnDsl
|
2
|
+
# Keeps track of errors
|
3
|
+
module Errors
|
4
|
+
@errors = []
|
5
|
+
|
6
|
+
def self.error(err, idx = nil)
|
7
|
+
if idx.nil?
|
8
|
+
@errors.push(err + "\n" + caller.join("\n") + "\n")
|
9
|
+
else
|
10
|
+
m = caller[idx].match(/^.*?:\d+:/)
|
11
|
+
err_loc = m ? m[0] : caller[idx]
|
12
|
+
|
13
|
+
@errors.push(err_loc + ' ' + err + "\n")
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.clear
|
18
|
+
@errors = []
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.errors
|
22
|
+
@errors
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.errors?
|
26
|
+
!@errors.empty?
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,154 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'cfndsl/jsonable'
|
3
|
+
require 'cfndsl/plurals'
|
4
|
+
require 'cfndsl/names'
|
5
|
+
|
6
|
+
module CfnDsl
|
7
|
+
# Type generation helper
|
8
|
+
module GenerateTypes
|
9
|
+
# declare classes for all of the types with named methods for setting the values
|
10
|
+
class Type < JSONable
|
11
|
+
end
|
12
|
+
|
13
|
+
# rubocop:disable Metrics/MethodLength, Metrics/AbcSize, Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
|
14
|
+
def generate_types(filename)
|
15
|
+
types = YAML.load(File.open(filename))
|
16
|
+
const_set('Types_Internal', types)
|
17
|
+
|
18
|
+
validate_types(types)
|
19
|
+
|
20
|
+
classes = {}
|
21
|
+
|
22
|
+
# Go through and declare all of the types first
|
23
|
+
types['Types'].each_key do |typename|
|
24
|
+
if !const_defined?(typename)
|
25
|
+
klass = const_set(typename, Class.new(self))
|
26
|
+
classes[typename] = klass
|
27
|
+
else
|
28
|
+
classes[typename] = const_get(typename)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# Now go through them again and define attribute setter methods
|
33
|
+
classes.each_pair do |typename, type|
|
34
|
+
typeval = types['Types'][typename]
|
35
|
+
next unless typeval.respond_to?(:each_pair)
|
36
|
+
typeval.each_pair do |attr_name, attr_type|
|
37
|
+
if attr_type.is_a?(Array)
|
38
|
+
klass = const_get(attr_type[0])
|
39
|
+
variable = "@#{attr_name}".to_sym
|
40
|
+
|
41
|
+
method = CfnDsl::Plurals.singularize(attr_name)
|
42
|
+
methods = attr_name
|
43
|
+
all_methods = CfnDsl.method_names(method) + CfnDsl.method_names(methods)
|
44
|
+
type.class_eval do
|
45
|
+
all_methods.each do |method_name|
|
46
|
+
define_method(method_name) do |value = nil, *rest, &block|
|
47
|
+
existing = instance_variable_get(variable)
|
48
|
+
# For no-op invocations, get out now
|
49
|
+
return existing if value.nil? && rest.empty? && !block
|
50
|
+
|
51
|
+
# We are going to modify the value in some
|
52
|
+
# way, make sure that we have an array to mess
|
53
|
+
# with if we start with nothing
|
54
|
+
existing = instance_variable_set(variable, []) unless existing
|
55
|
+
|
56
|
+
# special case for just a block, no args
|
57
|
+
if value.nil? && rest.empty? && block
|
58
|
+
val = klass.new
|
59
|
+
existing.push val
|
60
|
+
value.instance_eval(&block(val))
|
61
|
+
return existing
|
62
|
+
end
|
63
|
+
|
64
|
+
# Glue all of our parameters together into
|
65
|
+
# a giant array - flattening one level deep, if needed
|
66
|
+
array_params = []
|
67
|
+
if value.is_a?(Array)
|
68
|
+
value.each { |x| array_params.push x }
|
69
|
+
else
|
70
|
+
array_params.push value
|
71
|
+
end
|
72
|
+
unless rest.empty?
|
73
|
+
rest.each do |v|
|
74
|
+
if v.is_a?(Array)
|
75
|
+
array_params += rest
|
76
|
+
else
|
77
|
+
array_params.push v
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# Here, if we were given multiple arguments either
|
83
|
+
# as method [a,b,c], method(a,b,c), or even
|
84
|
+
# method( a, [b], c) we end up with
|
85
|
+
# array_params = [a,b,c]
|
86
|
+
#
|
87
|
+
# array_params will have at least one item
|
88
|
+
# unless the user did something like pass in
|
89
|
+
# a bunch of empty arrays.
|
90
|
+
if block
|
91
|
+
# TODO: Is this a bug? We don't do anything with the array conetns
|
92
|
+
array_params.each do |_array_params_value|
|
93
|
+
value = klass.new
|
94
|
+
existing.push value
|
95
|
+
value.instance_eval(&block(val)) if block
|
96
|
+
end
|
97
|
+
else
|
98
|
+
# List of parameters with no block -
|
99
|
+
# hope that the user knows what he is
|
100
|
+
# doing and stuff them into our existing
|
101
|
+
# array
|
102
|
+
# TODO: Is this a bug? We don't do anything with the array conetns
|
103
|
+
array_params.each do |_array_params_value|
|
104
|
+
existing.push value
|
105
|
+
end
|
106
|
+
end
|
107
|
+
return existing
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
else
|
112
|
+
klass = const_get(attr_type)
|
113
|
+
variable = "@#{attr_name}".to_sym
|
114
|
+
|
115
|
+
type.class_eval do
|
116
|
+
CfnDsl.method_names(attr_name) do |inner_method|
|
117
|
+
define_method(inner_method) do |value = nil, *_rest, &block|
|
118
|
+
value ||= klass.new
|
119
|
+
instance_variable_set(variable, value)
|
120
|
+
value.instance_eval(&block) if block
|
121
|
+
value
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
# rubocop:enable Metrics/MethodLength, Metrics/AbcSize, Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
|
130
|
+
|
131
|
+
private
|
132
|
+
|
133
|
+
# Do a little sanity checking - all of the types referenced in Resources
|
134
|
+
# should be represented in Types
|
135
|
+
def validate_types(types)
|
136
|
+
types['Resources'].values.each do |resource|
|
137
|
+
resource.values.each do |thing|
|
138
|
+
thing.values.flatten.each do |type|
|
139
|
+
puts "unknown type #{type}" unless types['Types'].key?(type)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
# All of the type values should also be references
|
145
|
+
types['Types'].values do |type|
|
146
|
+
next unless type.respond_to?(:values)
|
147
|
+
|
148
|
+
type.values.each do |tv|
|
149
|
+
puts "unknown type #{tv}" unless types['Types'].key?(tv)
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
@@ -0,0 +1,214 @@
|
|
1
|
+
require 'cfndsl/errors'
|
2
|
+
require 'cfndsl/ref_check'
|
3
|
+
|
4
|
+
module CfnDsl
|
5
|
+
# These functions are available anywhere inside
|
6
|
+
# a block for a JSONable object.
|
7
|
+
module Functions
|
8
|
+
# Equivalent to the CloudFormation template built in function Ref
|
9
|
+
def Ref(value)
|
10
|
+
RefDefinition.new(value)
|
11
|
+
end
|
12
|
+
|
13
|
+
# Equivalent to the CloudFormation template built in function Fn::Base64
|
14
|
+
def FnBase64(value)
|
15
|
+
Fn.new('Base64', value)
|
16
|
+
end
|
17
|
+
|
18
|
+
# Equivalent to the CloudFormation template built in function Fn::FindInMap
|
19
|
+
def FnFindInMap(map, key, value)
|
20
|
+
Fn.new('FindInMap', [map, key, value])
|
21
|
+
end
|
22
|
+
|
23
|
+
# Equivalent to the CloudFormation template built in function Fn::GetAtt
|
24
|
+
def FnGetAtt(logical_resource, attribute)
|
25
|
+
Fn.new('GetAtt', [logical_resource, attribute])
|
26
|
+
end
|
27
|
+
|
28
|
+
# Equivalent to the CloudFormation template built in function Fn::GetAZs
|
29
|
+
def FnGetAZs(region)
|
30
|
+
Fn.new('GetAZs', region)
|
31
|
+
end
|
32
|
+
|
33
|
+
# Equivalent to the CloudFormation template built in function Fn::Join
|
34
|
+
def FnJoin(string, array)
|
35
|
+
Fn.new('Join', [string, array])
|
36
|
+
end
|
37
|
+
|
38
|
+
# Equivalent to the CloudFormation template built in function Fn::And
|
39
|
+
def FnAnd(array)
|
40
|
+
if !array || array.count < 2 || array.count > 10
|
41
|
+
raise 'The array passed to Fn::And must have at least 2 elements and no more than 10'
|
42
|
+
end
|
43
|
+
Fn.new('And', array)
|
44
|
+
end
|
45
|
+
|
46
|
+
# Equivalent to the Cloudformation template built in function Fn::Equals
|
47
|
+
def FnEquals(value1, value2)
|
48
|
+
Fn.new('Equals', [value1, value2])
|
49
|
+
end
|
50
|
+
|
51
|
+
# Equivalent to the Cloudformation template built in function Fn::If
|
52
|
+
def FnIf(condition_name, true_value, false_value)
|
53
|
+
Fn.new('If', [condition_name, true_value, false_value])
|
54
|
+
end
|
55
|
+
|
56
|
+
# Equivalent to the Cloudformation template built in function Fn::Not
|
57
|
+
def FnNot(value)
|
58
|
+
Fn.new('Not', value)
|
59
|
+
end
|
60
|
+
|
61
|
+
# Equivalent to the CloudFormation template built in function Fn::Or
|
62
|
+
def FnOr(array)
|
63
|
+
if !array || array.count < 2 || array.count > 10
|
64
|
+
raise 'The array passed to Fn::Or must have at least 2 elements and no more than 10'
|
65
|
+
end
|
66
|
+
Fn.new('Or', array)
|
67
|
+
end
|
68
|
+
|
69
|
+
# Equivalent to the CloudFormation template built in function Fn::Select
|
70
|
+
def FnSelect(index, array)
|
71
|
+
Fn.new('Select', [index, array])
|
72
|
+
end
|
73
|
+
|
74
|
+
# Usage
|
75
|
+
# FnFormat('This is a %0. It is 100%% %1', 'test', 'effective')
|
76
|
+
# or
|
77
|
+
# FnFormat('This is a %{test}. It is 100%% %{effective}',
|
78
|
+
# :test => 'test",
|
79
|
+
# :effective => 'effective')
|
80
|
+
#
|
81
|
+
# These will each generate a call to Fn::Join that when
|
82
|
+
# evaluated will produce the string "This is a test. It is 100%
|
83
|
+
# effective."
|
84
|
+
#
|
85
|
+
# Think of this as %0, %1, etc in the format string being replaced by the
|
86
|
+
# corresponding arguments given after the format string. '%%' is replaced
|
87
|
+
# by the '%' character.
|
88
|
+
#
|
89
|
+
# The actual Fn::Join call corresponding to the above FnFormat call would be
|
90
|
+
# {"Fn::Join": ["",["This is a ","test",". It is 100","%"," ","effective"]]}
|
91
|
+
#
|
92
|
+
# If no arguments are given, or if a hash is given and the format
|
93
|
+
# variable name does not exist in the hash, it is used as a Ref
|
94
|
+
# to an existing resource or parameter.
|
95
|
+
#
|
96
|
+
# TODO Can we simplyfy this somehow?
|
97
|
+
# rubocop:disable Metrics/AbcSize, Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
|
98
|
+
def FnFormat(string, *arguments)
|
99
|
+
array = []
|
100
|
+
|
101
|
+
if arguments.empty? || (arguments.length == 1 && arguments[0].instance_of?(Hash))
|
102
|
+
hash = arguments[0] || {}
|
103
|
+
string.scan(/(.*?)(?:%(%|\{([\w:]+)\})|\z)/m) do |x, y, z|
|
104
|
+
array.push x if x && !x.empty?
|
105
|
+
|
106
|
+
next unless y
|
107
|
+
|
108
|
+
array.push(y == '%' ? '%' : (hash[z] || hash[z.to_sym] || Ref(z)))
|
109
|
+
end
|
110
|
+
else
|
111
|
+
string.scan(/(.*?)(?:%(%|\d+)|\z)/m) do |x, y|
|
112
|
+
array.push x if x && !x.empty?
|
113
|
+
|
114
|
+
next unless y
|
115
|
+
|
116
|
+
array.push(y == '%' ? '%' : arguments[y.to_i])
|
117
|
+
end
|
118
|
+
end
|
119
|
+
Fn.new('Join', ['', array])
|
120
|
+
end
|
121
|
+
# rubocop:enable Metrics/AbcSize, Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
|
122
|
+
end
|
123
|
+
|
124
|
+
# This is the base class for just about everything useful in the
|
125
|
+
# DSL. It knows how to turn DSL Objects into the corresponding
|
126
|
+
# json, and it lets you create new built in function objects
|
127
|
+
# from inside the context of a dsl object.
|
128
|
+
class JSONable
|
129
|
+
include Functions
|
130
|
+
extend Functions
|
131
|
+
include RefCheck
|
132
|
+
|
133
|
+
# Use instance variables to build a json object. Instance
|
134
|
+
# variables that begin with a single underscore are elided.
|
135
|
+
# Instance variables that begin with two underscores have one of
|
136
|
+
# them removed.
|
137
|
+
def to_json(*a)
|
138
|
+
hash = {}
|
139
|
+
instance_variables.each do |var|
|
140
|
+
name = var[1..-1]
|
141
|
+
|
142
|
+
if name =~ /^__/
|
143
|
+
# if a variable starts with double underscore, strip one off
|
144
|
+
name = name[1..-1]
|
145
|
+
elsif name =~ /^_/
|
146
|
+
# Hide variables that start with single underscore
|
147
|
+
name = nil
|
148
|
+
end
|
149
|
+
|
150
|
+
hash[name] = instance_variable_get(var) if name
|
151
|
+
end
|
152
|
+
hash.to_json(*a)
|
153
|
+
end
|
154
|
+
|
155
|
+
def ref_children
|
156
|
+
instance_variables.map { |var| instance_variable_get(var) }
|
157
|
+
end
|
158
|
+
|
159
|
+
def declare(&block)
|
160
|
+
instance_eval(&block) if block_given?
|
161
|
+
end
|
162
|
+
|
163
|
+
def method_missing(meth, *args, &_block)
|
164
|
+
error = "Undefined symbol: #{meth}"
|
165
|
+
error = "#{error}(" + args.inspect[1..-2] + ')' unless args.empty?
|
166
|
+
error = "#{error}\n\nTry '#{titleize(meth)}' instead" if incorrect_capitalization?(meth)
|
167
|
+
CfnDsl::Errors.error(error, 1)
|
168
|
+
end
|
169
|
+
|
170
|
+
def incorrect_capitalization?(method)
|
171
|
+
method != titleize(method) && respond_to?(titleize(method))
|
172
|
+
end
|
173
|
+
|
174
|
+
def titleize(method)
|
175
|
+
method.to_s.clone.tap do |m|
|
176
|
+
m[0] = m[0, 1].upcase
|
177
|
+
end.to_sym
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
# Handles all of the Fn:: objects
|
182
|
+
class Fn < JSONable
|
183
|
+
def initialize(function, argument, refs = [])
|
184
|
+
@function = function
|
185
|
+
@argument = argument
|
186
|
+
@_refs = refs
|
187
|
+
end
|
188
|
+
|
189
|
+
def to_json(*a)
|
190
|
+
hash = {}
|
191
|
+
hash["Fn::#{@function}"] = @argument
|
192
|
+
hash.to_json(*a)
|
193
|
+
end
|
194
|
+
|
195
|
+
def references
|
196
|
+
@_refs
|
197
|
+
end
|
198
|
+
|
199
|
+
def ref_children
|
200
|
+
[@argument]
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
# Handles the Ref objects
|
205
|
+
class RefDefinition < JSONable
|
206
|
+
def initialize(value)
|
207
|
+
@Ref = value
|
208
|
+
end
|
209
|
+
|
210
|
+
def all_refs
|
211
|
+
[@Ref]
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'cfndsl/jsonable'
|
2
|
+
|
3
|
+
module CfnDsl
|
4
|
+
# Handles mapping objects
|
5
|
+
#
|
6
|
+
# Usage:
|
7
|
+
# Mapping("AWSRegionArch2AMI", {
|
8
|
+
# "us-east-1" => { "32" => "ami-6411e20d", "64" => "ami-7a11e213" },
|
9
|
+
# "us-west-1" => { "32" => "ami-c9c7978c", "64" => "ami-cfc7978a" },
|
10
|
+
# "eu-west-1" => { "32" => "ami-37c2f643", "64" => "ami-31c2f645" },
|
11
|
+
# "ap-southeast-1" => { "32" => "ami-66f28c34", "64" => "ami-60f28c32" },
|
12
|
+
# "ap-northeast-1" => { "32" => "ami-9c03a89d", "64" => "ami-a003a8a1" }
|
13
|
+
# })
|
14
|
+
class MappingDefinition < JSONable
|
15
|
+
def initialize(value)
|
16
|
+
@value = value
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_json(*a)
|
20
|
+
@value.to_json(*a)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|