cfndsl 0.0.5 → 0.0.6
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.
- data/lib/cfndsl.rb +14 -443
- data/lib/cfndsl/CloudFormationTemplate.rb +151 -0
- data/lib/cfndsl/Errors.rb +33 -0
- data/lib/cfndsl/JSONable.rb +161 -0
- data/lib/cfndsl/Mappings.rb +25 -0
- data/lib/cfndsl/Metadata.rb +10 -0
- data/lib/cfndsl/Outputs.rb +13 -0
- data/lib/cfndsl/Parameters.rb +32 -0
- data/lib/cfndsl/Plurals.rb +28 -0
- data/lib/cfndsl/Properties.rb +25 -0
- data/lib/cfndsl/RefCheck.rb +48 -0
- data/lib/cfndsl/Resources.rb +28 -0
- data/lib/cfndsl/Types.rb +116 -0
- data/lib/cfndsl/aws_types.yaml +442 -0
- data/lib/cfndsl/module.rb +70 -0
- metadata +15 -1
@@ -0,0 +1,151 @@
|
|
1
|
+
require 'cfndsl/JSONable'
|
2
|
+
|
3
|
+
module CfnDsl
|
4
|
+
class CloudFormationTemplate < JSONable
|
5
|
+
##
|
6
|
+
# Handles the overall template object
|
7
|
+
dsl_attr_setter :AWSTemplateFormatVersion, :Description
|
8
|
+
dsl_content_object :Parameter, :Output, :Resource, :Mapping
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
@AWSTemplateFormatVersion = "2010-09-09"
|
12
|
+
end
|
13
|
+
|
14
|
+
def generateOutput()
|
15
|
+
puts self.to_json # uncomment for pretty printing # {:space => ' ', :indent => ' ', :object_nl => "\n", :array_nl => "\n" }
|
16
|
+
end
|
17
|
+
|
18
|
+
@@globalRefs = {
|
19
|
+
"AWS::NotificationARNs" => 1,
|
20
|
+
"AWS::Region" => 1,
|
21
|
+
"AWS::StackId" => 1,
|
22
|
+
"AWS::StackName" => 1
|
23
|
+
}
|
24
|
+
|
25
|
+
def isValidRef( ref, origin=nil)
|
26
|
+
ref = ref.to_s
|
27
|
+
origin = origin.to_s if origin
|
28
|
+
|
29
|
+
return true if @@globalRefs.has_key?( ref )
|
30
|
+
|
31
|
+
return true if @Parameters && @Parameters.has_key?( ref )
|
32
|
+
|
33
|
+
if( @Resources.has_key?( ref ) ) then
|
34
|
+
return !origin || !@_ResourceRefs || !@_ResourceRefs[ref] || !@_ResourceRefs[ref].has_key?(origin)
|
35
|
+
end
|
36
|
+
|
37
|
+
return false
|
38
|
+
end
|
39
|
+
|
40
|
+
def checkRefs()
|
41
|
+
invalids = []
|
42
|
+
@_ResourceRefs = {}
|
43
|
+
if(@Resources) then
|
44
|
+
@Resources.keys.each do |resource|
|
45
|
+
@_ResourceRefs[resource.to_s] = @Resources[resource].references({})
|
46
|
+
end
|
47
|
+
@_ResourceRefs.keys.each do |origin|
|
48
|
+
@_ResourceRefs[origin].keys.each do |ref|
|
49
|
+
invalids.push "Invalid Reference: Resource #{origin} refers to #{ref}" unless isValidRef(ref,origin)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
outputRefs = {}
|
54
|
+
if(@Outputs) then
|
55
|
+
@Outputs.keys.each do |resource|
|
56
|
+
outputRefs[resource.to_s] = @Outputs[resource].references({})
|
57
|
+
end
|
58
|
+
outputRefs.keys.each do |origin|
|
59
|
+
outputRefs[origin].keys.each do |ref|
|
60
|
+
invalids.push "Invalid Reference: Output #{origin} refers to #{ref}" unless isValidRef(ref,nil)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
return invalids.length>0 ? invalids : nil
|
65
|
+
end
|
66
|
+
|
67
|
+
|
68
|
+
names = {}
|
69
|
+
nametypes = {}
|
70
|
+
CfnDsl::Types::AWS_Types["Resources"].each_pair do |name, type|
|
71
|
+
# Subclass ResourceDefintion and generate property methods
|
72
|
+
klass = Class.new(CfnDsl::ResourceDefinition)
|
73
|
+
klassname = name.split("::").join("_")
|
74
|
+
CfnDsl::Types.const_set( klassname, klass )
|
75
|
+
type["Properties"].each_pair do |pname, ptype|
|
76
|
+
if( ptype.instance_of? String )
|
77
|
+
create_klass = CfnDsl::Types.const_get( ptype );
|
78
|
+
klass.class_eval do
|
79
|
+
define_method(pname) do |*values, &block|
|
80
|
+
if( values.length <1 ) then
|
81
|
+
values.push create_klass.new
|
82
|
+
end
|
83
|
+
@Properties ||= {}
|
84
|
+
@Properties[pname] ||= CfnDsl::PropertyDefinition.new( *values )
|
85
|
+
@Properties[pname].value.instance_eval &block if block
|
86
|
+
@Properties[pname].value
|
87
|
+
end
|
88
|
+
end
|
89
|
+
else
|
90
|
+
#Array version
|
91
|
+
sing_name = CfnDsl::Plurals.singularize( pname )
|
92
|
+
create_klass = CfnDsl::Types.const_get( ptype[0] )
|
93
|
+
klass.class_eval do
|
94
|
+
define_method(pname) do |*values, &block|
|
95
|
+
if( values.length < 1 ) then
|
96
|
+
values.push []
|
97
|
+
end
|
98
|
+
@Properties ||= {}
|
99
|
+
@Properties[pname] ||= PropertyDefinition.new( *values )
|
100
|
+
@Properties[pname].value.instance_eval &block if block
|
101
|
+
@Properties[pname].value
|
102
|
+
end
|
103
|
+
|
104
|
+
define_method(sing_name) do |value=nil, &block|
|
105
|
+
@Properties ||= {}
|
106
|
+
@Properties[pname] ||= PropertyDefinition.new( [] )
|
107
|
+
if( !value ) then
|
108
|
+
value = create_klass.new
|
109
|
+
end
|
110
|
+
@Properties[pname].value.push value
|
111
|
+
value.instance_eval &block if block
|
112
|
+
value
|
113
|
+
end
|
114
|
+
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
end
|
119
|
+
parts = name.split "::"
|
120
|
+
while( parts.length > 0)
|
121
|
+
abreve_name = parts.join "_"
|
122
|
+
if( names.has_key? abreve_name ) then
|
123
|
+
# this only happens if there is an ambiguity
|
124
|
+
names[abreve_name] = nil
|
125
|
+
else
|
126
|
+
names[abreve_name] = CfnDsl::Types.const_get(klassname)
|
127
|
+
nametypes[abreve_name] = name
|
128
|
+
end
|
129
|
+
parts.shift
|
130
|
+
end
|
131
|
+
|
132
|
+
|
133
|
+
end
|
134
|
+
|
135
|
+
#Define property setter methods for each of the unambiguous type names
|
136
|
+
names.each_pair do |typename,type|
|
137
|
+
if(type) then
|
138
|
+
class_eval do
|
139
|
+
define_method( typename) do |name,*values,&block|
|
140
|
+
name = name.to_s
|
141
|
+
@Resources ||= {}
|
142
|
+
resource = @Resources[name] ||= type.new(*values)
|
143
|
+
resource.instance_eval &block if block
|
144
|
+
resource.instance_variable_set( "@Type", nametypes[typename] )
|
145
|
+
resource
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module CfnDsl
|
2
|
+
module Errors
|
3
|
+
@@errors = []
|
4
|
+
|
5
|
+
def self.error( err, idx=nil )
|
6
|
+
if(idx.nil?) then
|
7
|
+
@@errors.push ( err + "\n" + caller.join("\n") + "\n" )
|
8
|
+
else
|
9
|
+
if( m = caller[idx].match(/^.*?:\d+:/ ) ) then
|
10
|
+
err_loc = m[0];
|
11
|
+
else
|
12
|
+
err_loc = caller[idx]
|
13
|
+
end
|
14
|
+
|
15
|
+
@@errors.push ( err_loc + " " + err + "\n" )
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.clear()
|
20
|
+
@@errors = []
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.report()
|
24
|
+
@@errors.each do |err|
|
25
|
+
puts err
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.errors?()
|
30
|
+
return @@errors.length > 0
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,161 @@
|
|
1
|
+
require 'cfndsl/Errors'
|
2
|
+
require 'cfndsl/RefCheck'
|
3
|
+
|
4
|
+
module CfnDsl
|
5
|
+
module Functions
|
6
|
+
|
7
|
+
def Ref(value)
|
8
|
+
##
|
9
|
+
# Equivalent to the CloudFormation template built in function Ref
|
10
|
+
RefDefinition.new(value)
|
11
|
+
end
|
12
|
+
|
13
|
+
def FnBase64( value )
|
14
|
+
##
|
15
|
+
# Equivalent to the CloudFormation template built in function Fn::Base64
|
16
|
+
Fn.new("Base64", value);
|
17
|
+
end
|
18
|
+
|
19
|
+
def FnFindInMap( map, key, value)
|
20
|
+
##
|
21
|
+
# Equivalent to the CloudFormation template built in function Fn::FindInMap
|
22
|
+
Fn.new("FindInMap", [map,key,value] )
|
23
|
+
end
|
24
|
+
|
25
|
+
def FnGetAtt(logicalResource, attribute)
|
26
|
+
##
|
27
|
+
# Equivalent to the CloudFormation template built in function Fn::GetAtt
|
28
|
+
Fn.new( "GetAtt", [logicalResource, attribute], [logicalResource] )
|
29
|
+
end
|
30
|
+
|
31
|
+
def FnGetAZs(region)
|
32
|
+
##
|
33
|
+
# Equivalent to the CloudFormation template built in function Fn::GetAZs
|
34
|
+
Fn.new("GetAZs", region)
|
35
|
+
end
|
36
|
+
|
37
|
+
def FnJoin(string, array)
|
38
|
+
##
|
39
|
+
# Equivalent to the CloudFormation template built in function Fn::Join
|
40
|
+
Fn.new("Join", [ string, array] )
|
41
|
+
end
|
42
|
+
|
43
|
+
def FnFormat(string, *arguments)
|
44
|
+
##
|
45
|
+
# Usage
|
46
|
+
# FnFormat( "This is a %0. It is 100%% %1","test", "effective")
|
47
|
+
#
|
48
|
+
# This will generate a call to Fn::Join that when evaluated will produce
|
49
|
+
# the string "This is a test. It is 100% effective."
|
50
|
+
#
|
51
|
+
# Think of this as %0,%1, etc in the format string being replaced by the
|
52
|
+
# corresponding arguments given after the format string. '%%' is replaced
|
53
|
+
# by the '%' character.
|
54
|
+
#
|
55
|
+
# The actual Fn::Join call corresponding to the above FnFormat call would be
|
56
|
+
# {"Fn::Join": ["",["This is a ","test",". It is 100","%"," ","effective"]]}
|
57
|
+
array = [];
|
58
|
+
string.scan( /(.*?)(%(%|\d+)|\z)/m ) do |x,y|
|
59
|
+
array.push $1 if $1 && $1 != ""
|
60
|
+
if( $3 == '%' ) then
|
61
|
+
array.push '%'
|
62
|
+
elsif( $3 ) then
|
63
|
+
array.push arguments[ $3.to_i ]
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
Fn.new("Join", ["", array])
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
class JSONable
|
72
|
+
##
|
73
|
+
# This is the base class for just about everything useful in the
|
74
|
+
# DSL. It knows how to turn DSL Objects into the corresponding
|
75
|
+
# json, and it lets you create new built in function objects
|
76
|
+
# from inside the context of a dsl object.
|
77
|
+
|
78
|
+
include Functions
|
79
|
+
extend Functions
|
80
|
+
include RefCheck
|
81
|
+
|
82
|
+
def to_json(*a)
|
83
|
+
##
|
84
|
+
# Use instance variables to build a json object. Instance
|
85
|
+
# variables that begin with a single underscore are elided.
|
86
|
+
# Instance variables that begin with two underscores have one of
|
87
|
+
# them removed.
|
88
|
+
hash = {}
|
89
|
+
self.instance_variables.each do |var|
|
90
|
+
name = var[1..-1]
|
91
|
+
|
92
|
+
if( name =~ /^__/ ) then
|
93
|
+
# if a variable starts with double underscore, strip one off
|
94
|
+
name = name[1..-1]
|
95
|
+
elsif( name =~ /^_/ ) then
|
96
|
+
# Hide variables that start with single underscore
|
97
|
+
name = nil
|
98
|
+
end
|
99
|
+
|
100
|
+
hash[name] = self.instance_variable_get var if name
|
101
|
+
end
|
102
|
+
hash.to_json(*a)
|
103
|
+
end
|
104
|
+
|
105
|
+
def ref_children
|
106
|
+
return self.instance_variables.map { |var| self.instance_variable_get var }
|
107
|
+
end
|
108
|
+
|
109
|
+
def declare(&block)
|
110
|
+
self.instance_eval &block if block_given?
|
111
|
+
end
|
112
|
+
|
113
|
+
def method_missing(meth,*args,&block)
|
114
|
+
if(args) then
|
115
|
+
arg = "(" + args.inspect[1..-2] + ")"
|
116
|
+
else
|
117
|
+
arg = ""
|
118
|
+
end
|
119
|
+
CfnDsl::Errors.error( "Undefined symbol: #{meth}#{arg}", 1 )
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
|
124
|
+
class Fn < JSONable
|
125
|
+
##
|
126
|
+
# Handles all of the Fn:: objects
|
127
|
+
def initialize( function, argument, refs=[] )
|
128
|
+
@function = function
|
129
|
+
@argument = argument
|
130
|
+
@_refs = refs
|
131
|
+
end
|
132
|
+
|
133
|
+
def to_json(*a)
|
134
|
+
hash = {}
|
135
|
+
hash["Fn::#{@function}"] = @argument
|
136
|
+
hash.to_json(*a)
|
137
|
+
end
|
138
|
+
|
139
|
+
def get_references()
|
140
|
+
return @_refs
|
141
|
+
end
|
142
|
+
|
143
|
+
def ref_children
|
144
|
+
return [@argument]
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
|
149
|
+
class RefDefinition < JSONable
|
150
|
+
##
|
151
|
+
# Handles the Ref objects
|
152
|
+
def initialize( value )
|
153
|
+
@Ref = value
|
154
|
+
end
|
155
|
+
|
156
|
+
def get_references()
|
157
|
+
[@Ref]
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'cfndsl/JSONable'
|
2
|
+
|
3
|
+
module CfnDsl
|
4
|
+
class MappingDefinition < JSONable
|
5
|
+
##
|
6
|
+
# Handles mapping objects
|
7
|
+
#
|
8
|
+
# Usage:
|
9
|
+
# Mapping("AWSRegionArch2AMI", {
|
10
|
+
# "us-east-1" => { "32" => "ami-6411e20d", "64" => "ami-7a11e213" },
|
11
|
+
# "us-west-1" => { "32" => "ami-c9c7978c", "64" => "ami-cfc7978a" },
|
12
|
+
# "eu-west-1" => { "32" => "ami-37c2f643", "64" => "ami-31c2f645" },
|
13
|
+
# "ap-southeast-1" => { "32" => "ami-66f28c34", "64" => "ami-60f28c32" },
|
14
|
+
# "ap-northeast-1" => { "32" => "ami-9c03a89d", "64" => "ami-a003a8a1" }
|
15
|
+
# })
|
16
|
+
|
17
|
+
def initialize(value)
|
18
|
+
@value = value
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_json(*a)
|
22
|
+
@value.to_json(*a)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'cfndsl/JSONable'
|
2
|
+
|
3
|
+
module CfnDsl
|
4
|
+
|
5
|
+
class ParameterDefinition < JSONable
|
6
|
+
##
|
7
|
+
# Handles input parameter objects
|
8
|
+
dsl_attr_setter :Type, :Default, :NoEcho, :AllowedValues, :AllowedPattern, :MaxLength, :MinLength, :MaxValue, :MinValue, :Description, :ConstraintDescription
|
9
|
+
def initialize
|
10
|
+
@Type = :String
|
11
|
+
end
|
12
|
+
|
13
|
+
def String
|
14
|
+
@Type = :String
|
15
|
+
end
|
16
|
+
|
17
|
+
def Number
|
18
|
+
@Type = :Number
|
19
|
+
end
|
20
|
+
|
21
|
+
def CommaDelimitedList
|
22
|
+
@Type = :CommaDelimitedList
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_hash()
|
26
|
+
h = {}
|
27
|
+
h[:Type] = @Type;
|
28
|
+
h[:Default] = @Default if @Default
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module CfnDsl
|
2
|
+
module Plurals
|
3
|
+
##
|
4
|
+
# Plural names for lists of content objects
|
5
|
+
#
|
6
|
+
|
7
|
+
@@plurals = {
|
8
|
+
"Metadata" => "Metadata",
|
9
|
+
"Property" => "Properties",
|
10
|
+
"Policy" => "Policies"
|
11
|
+
}
|
12
|
+
|
13
|
+
@@singles = {}
|
14
|
+
@@plurals.each_pair { |key,val| @@singles[val] = key }
|
15
|
+
|
16
|
+
def self.pluralize(name)
|
17
|
+
name = name.to_s
|
18
|
+
return @@plurals[name] if( @@plurals.has_key? name )
|
19
|
+
return "#{name}s"
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.singularize(name)
|
23
|
+
name = name.to_s
|
24
|
+
return @@singles[name] if( @@singles.has_key? name )
|
25
|
+
return name[0..-2]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|