cfoo 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +19 -0
- data/.simplecov +4 -0
- data/.travis.yml +6 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +676 -0
- data/README.md +166 -0
- data/Rakefile +10 -0
- data/TODO +29 -0
- data/bin/cfoo +26 -0
- data/cfoo.gemspec +35 -0
- data/features/attribute_expansion.feature +43 -0
- data/features/convert_yaml_to_json.feature +69 -0
- data/features/el_escaping.feature +21 -0
- data/features/map_reference_expansion.feature +68 -0
- data/features/modules.feature +101 -0
- data/features/parse_files.feature +46 -0
- data/features/reference_expansion.feature +49 -0
- data/features/step_definitions/cfoo_steps.rb +107 -0
- data/features/support/env.rb +5 -0
- data/features/yamly_shortcuts.feature +132 -0
- data/lib/cfoo.rb +11 -0
- data/lib/cfoo/cfoo.rb +15 -0
- data/lib/cfoo/el_parser.rb +105 -0
- data/lib/cfoo/file_system.rb +28 -0
- data/lib/cfoo/module.rb +25 -0
- data/lib/cfoo/parser.rb +82 -0
- data/lib/cfoo/processor.rb +38 -0
- data/lib/cfoo/project.rb +16 -0
- data/lib/cfoo/renderer.rb +9 -0
- data/lib/cfoo/version.rb +3 -0
- data/lib/cfoo/yaml.rb +34 -0
- data/lib/cfoo/yaml_parser.rb +16 -0
- data/spec/cfoo/cfoo_spec.rb +31 -0
- data/spec/cfoo/el_parser_spec.rb +47 -0
- data/spec/cfoo/file_system_spec.rb +61 -0
- data/spec/cfoo/module_spec.rb +16 -0
- data/spec/cfoo/parser_spec.rb +148 -0
- data/spec/cfoo/processor_spec.rb +52 -0
- data/spec/cfoo/project_spec.rb +16 -0
- data/spec/cfoo/renderer_spec.rb +19 -0
- data/spec/cfoo/yaml_parser_spec.rb +79 -0
- data/spec/spec_helper.rb +2 -0
- metadata +235 -0
@@ -0,0 +1,101 @@
|
|
1
|
+
Feature: Modules
|
2
|
+
As a CloudFormation user
|
3
|
+
I want to split my templates into logical modules
|
4
|
+
So that I can organise them and share them
|
5
|
+
|
6
|
+
Scenario: Module with a template
|
7
|
+
Given I have a project
|
8
|
+
And I have a module named "webapp"
|
9
|
+
And I have a file "modules/webapp/webapp.yml" containing
|
10
|
+
"""
|
11
|
+
Parameters:
|
12
|
+
FrontendSize:
|
13
|
+
Type: Integer
|
14
|
+
Default: 5
|
15
|
+
Resources:
|
16
|
+
FrontendFleet:
|
17
|
+
Type: AWS::AutoScaling::AutoScalingGroup
|
18
|
+
Properties:
|
19
|
+
MinSize: 1
|
20
|
+
MaxSize: 10
|
21
|
+
"""
|
22
|
+
When I build the project
|
23
|
+
Then the output should match JSON
|
24
|
+
"""
|
25
|
+
{
|
26
|
+
"AWSTemplateFormatVersion" : "2010-09-09",
|
27
|
+
"Parameters" : {
|
28
|
+
"FrontendSize" : { "Type" : "Integer", "Default" : 5 }
|
29
|
+
},
|
30
|
+
"Resources" : {
|
31
|
+
"FrontendFleet" : {
|
32
|
+
"Type" : "AWS::AutoScaling::AutoScalingGroup",
|
33
|
+
"Properties" : {
|
34
|
+
"MinSize" : 1,
|
35
|
+
"MaxSize" : 10
|
36
|
+
}
|
37
|
+
}
|
38
|
+
}
|
39
|
+
}
|
40
|
+
"""
|
41
|
+
|
42
|
+
Scenario: Multiple modules get merged
|
43
|
+
Given I have a project
|
44
|
+
And I have a module named "webapp"
|
45
|
+
And I have a file "modules/webapp/webapp.yml" containing
|
46
|
+
"""
|
47
|
+
Parameters:
|
48
|
+
FrontendSize:
|
49
|
+
Type: Integer
|
50
|
+
Default: 5
|
51
|
+
Resources:
|
52
|
+
FrontendFleet:
|
53
|
+
Type: AWS::AutoScaling::AutoScalingGroup
|
54
|
+
Properties:
|
55
|
+
MinSize: 1
|
56
|
+
MaxSize: $(FrontendSize)
|
57
|
+
"""
|
58
|
+
And I have a module named "database"
|
59
|
+
And I have a file "modules/database/database.yml" containing
|
60
|
+
"""
|
61
|
+
Parameters:
|
62
|
+
DbName:
|
63
|
+
Type: String
|
64
|
+
Default: My Precious
|
65
|
+
Resources:
|
66
|
+
MySQLDatabase:
|
67
|
+
Type: AWS::RDS::DBInstance
|
68
|
+
Properties:
|
69
|
+
Engine: MySQL
|
70
|
+
DBName: $(DbName)
|
71
|
+
MultiAZ: true
|
72
|
+
"""
|
73
|
+
When I build the project
|
74
|
+
Then the output should match JSON
|
75
|
+
"""
|
76
|
+
{
|
77
|
+
"AWSTemplateFormatVersion" : "2010-09-09",
|
78
|
+
"Parameters" : {
|
79
|
+
"FrontendSize" : { "Type" : "Integer", "Default" : 5 },
|
80
|
+
"DbName" : { "Type" : "String", "Default" : "My Precious" }
|
81
|
+
},
|
82
|
+
"Resources" : {
|
83
|
+
"FrontendFleet" : {
|
84
|
+
"Type" : "AWS::AutoScaling::AutoScalingGroup",
|
85
|
+
"Properties" : {
|
86
|
+
"MinSize" : 1,
|
87
|
+
"MaxSize" : { "Ref" : "FrontendSize" }
|
88
|
+
}
|
89
|
+
},
|
90
|
+
"MySQLDatabase": {
|
91
|
+
"Type": "AWS::RDS::DBInstance",
|
92
|
+
"Properties": {
|
93
|
+
"Engine" : "MySQL",
|
94
|
+
"DBName" : { "Ref": "DbName" },
|
95
|
+
"MultiAZ" : "true"
|
96
|
+
}
|
97
|
+
}
|
98
|
+
}
|
99
|
+
}
|
100
|
+
"""
|
101
|
+
|
@@ -0,0 +1,46 @@
|
|
1
|
+
Feature: Modules
|
2
|
+
As a casual Cfoo user
|
3
|
+
I want to combine arbitrary Cfoo templates
|
4
|
+
So that I can quickly generate a CloudFormation template without a project structure
|
5
|
+
|
6
|
+
Scenario: Parse files
|
7
|
+
Given I have a file "webapp.yml" containing
|
8
|
+
"""
|
9
|
+
Resources:
|
10
|
+
FrontendFleet:
|
11
|
+
Type: AWS::AutoScaling::AutoScalingGroup
|
12
|
+
Properties:
|
13
|
+
MinSize: 1
|
14
|
+
MaxSize: 10
|
15
|
+
"""
|
16
|
+
And I have a file "vpc.yml" containing
|
17
|
+
"""
|
18
|
+
Resources:
|
19
|
+
VPC:
|
20
|
+
Type: AWS::EC2::VPC
|
21
|
+
Properties:
|
22
|
+
CidrBlock: "10.0.0.0/16"
|
23
|
+
"""
|
24
|
+
When I process files "webapp.yml" and "vpc.yml"
|
25
|
+
Then the output should match JSON
|
26
|
+
"""
|
27
|
+
{
|
28
|
+
"AWSTemplateFormatVersion" : "2010-09-09",
|
29
|
+
"Resources" : {
|
30
|
+
"FrontendFleet" : {
|
31
|
+
"Type" : "AWS::AutoScaling::AutoScalingGroup",
|
32
|
+
"Properties" : {
|
33
|
+
"MinSize" : 1,
|
34
|
+
"MaxSize" : 10
|
35
|
+
}
|
36
|
+
},
|
37
|
+
"VPC" : {
|
38
|
+
"Type" : "AWS::EC2::VPC",
|
39
|
+
"Properties" : {
|
40
|
+
"CidrBlock" : "10.0.0.0/16"
|
41
|
+
}
|
42
|
+
}
|
43
|
+
}
|
44
|
+
}
|
45
|
+
"""
|
46
|
+
|
@@ -0,0 +1,49 @@
|
|
1
|
+
Feature: Expand EL References
|
2
|
+
As a CloudFormation user
|
3
|
+
I want to use an expression language as a shorthand for references
|
4
|
+
So that I my templates are easier to read
|
5
|
+
|
6
|
+
Scenario: Simple reference
|
7
|
+
Given I have a file "reference.yml" containing
|
8
|
+
"""
|
9
|
+
Server:
|
10
|
+
InstanceType: $(InstanceType)
|
11
|
+
SecurityGroups:
|
12
|
+
- sg-123456
|
13
|
+
- $(SshSecurityGroup)
|
14
|
+
- sg-987654
|
15
|
+
"""
|
16
|
+
When I process "reference.yml"
|
17
|
+
Then the output should match JSON
|
18
|
+
"""
|
19
|
+
{
|
20
|
+
"AWSTemplateFormatVersion" : "2010-09-09",
|
21
|
+
"Server" : {
|
22
|
+
"InstanceType" : { "Ref" : "InstanceType" },
|
23
|
+
"SecurityGroups" : [
|
24
|
+
"sg-123456",
|
25
|
+
{ "Ref": "SshSecurityGroup" },
|
26
|
+
"sg-987654"
|
27
|
+
]
|
28
|
+
}
|
29
|
+
}
|
30
|
+
"""
|
31
|
+
|
32
|
+
Scenario: Embedded reference
|
33
|
+
Given I have a file "reference.yml" containing
|
34
|
+
"""
|
35
|
+
content:
|
36
|
+
/var/www/html: http://$(DownloadHost)/website.tar.gz
|
37
|
+
/etc/puppet: https://github.com/$(GithubAccount)/$(RepoName).git
|
38
|
+
"""
|
39
|
+
When I process "reference.yml"
|
40
|
+
Then the output should match JSON
|
41
|
+
"""
|
42
|
+
{
|
43
|
+
"AWSTemplateFormatVersion" : "2010-09-09",
|
44
|
+
"content": {
|
45
|
+
"/var/www/html" : { "Fn::Join" : [ "", [ "http://", {"Ref" : "DownloadHost"}, "/website.tar.gz" ] ] },
|
46
|
+
"/etc/puppet" : { "Fn::Join" : [ "", [ "https://github.com/", {"Ref" : "GithubAccount"}, "/", {"Ref" : "RepoName"}, ".git" ] ] }
|
47
|
+
}
|
48
|
+
}
|
49
|
+
"""
|
@@ -0,0 +1,107 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'json'
|
3
|
+
include FileUtils
|
4
|
+
|
5
|
+
Given /^I have a project$/ do
|
6
|
+
end
|
7
|
+
|
8
|
+
Given /^I have a module named "(.*?)"$/ do |module_name|
|
9
|
+
create_dir "modules/#{module_name}"
|
10
|
+
end
|
11
|
+
|
12
|
+
Given /^I have a file "(.*?)" containing$/ do |filename, content|
|
13
|
+
write_file(filename, content)
|
14
|
+
end
|
15
|
+
|
16
|
+
When /^I process "(.*?)"$/ do |filename|
|
17
|
+
cfoo.process(filename)
|
18
|
+
end
|
19
|
+
|
20
|
+
#TODO: can we use varargs?
|
21
|
+
When /^I process files "(.*?)"(?:(?:,| and) "(.*?)")$/ do |filename_1, filename_2|
|
22
|
+
cfoo.process(filename_1, filename_2)
|
23
|
+
end
|
24
|
+
|
25
|
+
When /^I build the project$/ do
|
26
|
+
cfoo.build_project
|
27
|
+
end
|
28
|
+
|
29
|
+
Then(/^the output should match JSON$/) do |expected_json|
|
30
|
+
begin
|
31
|
+
expected = JSON.parse(expected_json)
|
32
|
+
rescue StandardError => e
|
33
|
+
puts "Couldn't parse expected ouput as JSON"
|
34
|
+
raise e
|
35
|
+
end
|
36
|
+
begin
|
37
|
+
actual = JSON.parse(stdout.messages.join "")
|
38
|
+
rescue StandardError => e
|
39
|
+
puts "Couldn't parse actual ouput as JSON"
|
40
|
+
raise e
|
41
|
+
end
|
42
|
+
actual.should == expected
|
43
|
+
end
|
44
|
+
|
45
|
+
def write_file(filename, content)
|
46
|
+
file_fqn = resolve_file(filename)
|
47
|
+
mkdir_p(File.dirname file_fqn)
|
48
|
+
File.open(file_fqn, "w") do |file|
|
49
|
+
file.puts content
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def create_dir(directory)
|
54
|
+
mkdir_p(resolve_file(directory))
|
55
|
+
end
|
56
|
+
|
57
|
+
def resolve_file(filename)
|
58
|
+
"#{project_root}/#{filename}"
|
59
|
+
end
|
60
|
+
|
61
|
+
def cfoo
|
62
|
+
@cfoo ||= Cfoo::Cfoo.new(processor, renderer, stdout)
|
63
|
+
end
|
64
|
+
|
65
|
+
def processor
|
66
|
+
@processor ||= Cfoo::Processor.new(parser, project)
|
67
|
+
end
|
68
|
+
|
69
|
+
def parser
|
70
|
+
@parser ||= Cfoo::Parser.new(file_system)
|
71
|
+
end
|
72
|
+
|
73
|
+
def renderer
|
74
|
+
@renderer ||= Cfoo::Renderer.new
|
75
|
+
end
|
76
|
+
|
77
|
+
def project
|
78
|
+
@project ||= Cfoo::Project.new(file_system)
|
79
|
+
end
|
80
|
+
|
81
|
+
def file_system
|
82
|
+
@file_system ||= Cfoo::FileSystem.new(project_root, Cfoo::YamlParser.new)
|
83
|
+
end
|
84
|
+
|
85
|
+
def project_root
|
86
|
+
@project_root ||= "/tmp/cfoo-cucumber-#{$$}"
|
87
|
+
mkdir_p(@project_root)
|
88
|
+
@project_root
|
89
|
+
end
|
90
|
+
|
91
|
+
def stdout
|
92
|
+
@output ||= Output.new
|
93
|
+
end
|
94
|
+
|
95
|
+
After do
|
96
|
+
rm_rf project_root if File.directory? project_root
|
97
|
+
end
|
98
|
+
|
99
|
+
class Output
|
100
|
+
def messages
|
101
|
+
@messages ||= []
|
102
|
+
end
|
103
|
+
|
104
|
+
def puts(message)
|
105
|
+
messages << message
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,132 @@
|
|
1
|
+
Feature: YAMLy shortcuts
|
2
|
+
I should be able to write standalone (non-embedded) CloudFormation bits using custom YAML datatypes
|
3
|
+
So that I can have a quick YAMLy way to write them
|
4
|
+
|
5
|
+
Scenario: Reference
|
6
|
+
Given I have a file "ref.yml" containing
|
7
|
+
"""
|
8
|
+
Reference: !Ref AWS::Region
|
9
|
+
"""
|
10
|
+
When I process "ref.yml"
|
11
|
+
Then the output should match JSON
|
12
|
+
"""
|
13
|
+
{
|
14
|
+
"AWSTemplateFormatVersion" : "2010-09-09",
|
15
|
+
"Reference" : { "Ref" : "AWS::Region" }
|
16
|
+
}
|
17
|
+
"""
|
18
|
+
|
19
|
+
Scenario: Attribute
|
20
|
+
Given I have a file "ref.yml" containing
|
21
|
+
"""
|
22
|
+
Attribute: !GetAtt [ BastionHost, PublicIp ]
|
23
|
+
"""
|
24
|
+
When I process "ref.yml"
|
25
|
+
Then the output should match JSON
|
26
|
+
"""
|
27
|
+
{
|
28
|
+
"AWSTemplateFormatVersion" : "2010-09-09",
|
29
|
+
"Attribute" : { "Fn::GetAtt" : [ "BastionHost", "PublicIp" ]}
|
30
|
+
}
|
31
|
+
"""
|
32
|
+
|
33
|
+
Scenario: Join function call
|
34
|
+
Given I have a file "join.yml" containing
|
35
|
+
"""
|
36
|
+
Join: !Join
|
37
|
+
- ""
|
38
|
+
- [ "string a", "string b" ]
|
39
|
+
"""
|
40
|
+
When I process "join.yml"
|
41
|
+
Then the output should match JSON
|
42
|
+
"""
|
43
|
+
{
|
44
|
+
"AWSTemplateFormatVersion" : "2010-09-09",
|
45
|
+
"Join" : { "Fn::Join" : [ "", [ "string a", "string b" ] ]}
|
46
|
+
}
|
47
|
+
"""
|
48
|
+
|
49
|
+
Scenario: Join function call with empty strings
|
50
|
+
Given I have a file "join.yml" containing
|
51
|
+
"""
|
52
|
+
Join: !Concat [ "string a", "string b" ]
|
53
|
+
"""
|
54
|
+
When I process "join.yml"
|
55
|
+
Then the output should match JSON
|
56
|
+
"""
|
57
|
+
{
|
58
|
+
"AWSTemplateFormatVersion" : "2010-09-09",
|
59
|
+
"Join" : { "Fn::Join" : ["", [ "string a", "string b" ]]}
|
60
|
+
}
|
61
|
+
"""
|
62
|
+
|
63
|
+
Scenario: FindInMap lookup
|
64
|
+
Given I have a file "map.yml" containing
|
65
|
+
"""
|
66
|
+
MapLookup: !FindInMap [Map, Key, Value]
|
67
|
+
"""
|
68
|
+
When I process "map.yml"
|
69
|
+
Then the output should match JSON
|
70
|
+
"""
|
71
|
+
{
|
72
|
+
"AWSTemplateFormatVersion" : "2010-09-09",
|
73
|
+
"MapLookup" : { "Fn::FindInMap" : ["Map", "Key", "Value"] }
|
74
|
+
}
|
75
|
+
"""
|
76
|
+
|
77
|
+
Scenario: AZ listing
|
78
|
+
Given I have a file "map.yml" containing
|
79
|
+
"""
|
80
|
+
AvailabilityZones: !GetAZs us-east-1
|
81
|
+
"""
|
82
|
+
When I process "map.yml"
|
83
|
+
Then the output should match JSON
|
84
|
+
"""
|
85
|
+
{
|
86
|
+
"AWSTemplateFormatVersion" : "2010-09-09",
|
87
|
+
"AvailabilityZones" : { "Fn::GetAZs" : "us-east-1" }
|
88
|
+
}
|
89
|
+
"""
|
90
|
+
|
91
|
+
Scenario: Base64 string
|
92
|
+
Given I have a file "multistring.yml" containing
|
93
|
+
"""
|
94
|
+
mystring: !Base64 |
|
95
|
+
Some string
|
96
|
+
across multiple
|
97
|
+
lines
|
98
|
+
"""
|
99
|
+
When I process "multistring.yml"
|
100
|
+
Then the output should match JSON
|
101
|
+
"""
|
102
|
+
{
|
103
|
+
"AWSTemplateFormatVersion" : "2010-09-09",
|
104
|
+
"mystring" : { "Fn::Base64" : "Some string\nacross multiple\nlines\n" }
|
105
|
+
}
|
106
|
+
"""
|
107
|
+
|
108
|
+
Scenario: Base64 string with embedded EL
|
109
|
+
Given I have a file "embeddedel.yml" containing
|
110
|
+
"""
|
111
|
+
mystring: !Base64 |
|
112
|
+
Some string
|
113
|
+
across $(Number)
|
114
|
+
lines
|
115
|
+
"""
|
116
|
+
When I process "embeddedel.yml"
|
117
|
+
Then the output should match JSON
|
118
|
+
"""
|
119
|
+
{
|
120
|
+
"AWSTemplateFormatVersion" : "2010-09-09",
|
121
|
+
"mystring" :
|
122
|
+
{ "Fn::Base64" :
|
123
|
+
{ "Fn::Join": [ "", [
|
124
|
+
"Some string\nacross ",
|
125
|
+
{ "Ref" : "Number" },
|
126
|
+
"\nlines\n"
|
127
|
+
]
|
128
|
+
]
|
129
|
+
}
|
130
|
+
}
|
131
|
+
}
|
132
|
+
"""
|
data/lib/cfoo.rb
ADDED