cfoo 0.0.1
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.
- 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