cfndsl 0.0.5 → 0.0.6
Sign up to get free protection for your applications and to get access to all the features.
- 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
|