cfoo 0.0.1 → 0.0.2
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/README.md +90 -15
- data/TODO +3 -5
- data/bin/cfoo +2 -7
- data/features/error_reporting.feature +23 -0
- data/features/function_expansion.feature +139 -0
- data/features/step_definitions/cfoo_steps.rb +14 -2
- data/lib/cfoo.rb +4 -0
- data/lib/cfoo/array.rb +13 -0
- data/lib/cfoo/cfoo.rb +6 -2
- data/lib/cfoo/constants.rb +3 -0
- data/lib/cfoo/el_parser.rb +35 -20
- data/lib/cfoo/factory.rb +25 -0
- data/lib/cfoo/file_system.rb +20 -0
- data/lib/cfoo/module.rb +0 -4
- data/lib/cfoo/parser.rb +66 -19
- data/lib/cfoo/processor.rb +10 -10
- data/lib/cfoo/version.rb +1 -1
- data/lib/cfoo/yaml_parser.rb +2 -1
- data/spec/cfoo/array_spec.rb +23 -0
- data/spec/cfoo/cfoo_spec.rb +20 -1
- data/spec/cfoo/el_parser_spec.rb +31 -0
- data/spec/cfoo/factory_spec.rb +22 -0
- data/spec/cfoo/file_system_spec.rb +38 -0
- data/spec/cfoo/parser_spec.rb +13 -1
- data/spec/cfoo/processor_spec.rb +111 -3
- data/spec/cfoo/yaml_parser_spec.rb +5 -0
- data/spec/cfoo/yaml_spec.rb +36 -0
- metadata +146 -135
- checksums.yaml +0 -7
data/README.md
CHANGED
@@ -4,7 +4,11 @@
|
|
4
4
|
[](https://coveralls.io/r/drrb/cfoo)
|
5
5
|
[](https://codeclimate.com/github/drrb/cfoo)
|
6
6
|
|
7
|
-
|
7
|
+
[](https://badge.fury.io/rb/cfoo)
|
8
|
+
[](https://gemnasium.com/drrb/cfoo)
|
9
|
+
|
10
|
+
Cfoo (pronounced "sifu") lets you write your [CloudFormation](https://aws.amazon.com/cloudformation)
|
11
|
+
templates [in YAML](#templates), and makes it easier with some [helpers](#shortcuts).
|
8
12
|
|
9
13
|
## Installation
|
10
14
|
|
@@ -14,9 +18,17 @@ Cfoo can be installed as a Ruby Gem
|
|
14
18
|
|
15
19
|
## Usage
|
16
20
|
|
17
|
-
|
21
|
+
1. Write your CloudFormation templates using Cfoo YAML
|
18
22
|
|
19
|
-
|
23
|
+
2. Turn your Cfoo templates into normal CloudFormation templates
|
24
|
+
```terminal
|
25
|
+
$ cfoo web-server.template.yml database.template.yml > web-stack.template.json
|
26
|
+
```
|
27
|
+
|
28
|
+
3. Create your stack with CloudFormation
|
29
|
+
```terminal
|
30
|
+
$ cfn-create-stack --stack-name WebStack -f web-stack.template.json
|
31
|
+
```
|
20
32
|
|
21
33
|
## Templates
|
22
34
|
|
@@ -72,7 +84,41 @@ Properties:
|
|
72
84
|
/opt/aws/bin/cfn-signal -e 0 -r "cfn-init completed" '$(WaitHandle)'
|
73
85
|
```
|
74
86
|
|
75
|
-
|
87
|
+
## Projects
|
88
|
+
|
89
|
+
Using Cfoo, it is possible to split your templates up into logical components that will
|
90
|
+
combined to form your CloudFormation template.
|
91
|
+
|
92
|
+
First, create a directory in your project directory called `modules`. For each module,
|
93
|
+
create some Cfoo templates defining the different parts of your app. Your project
|
94
|
+
structure will look like this:
|
95
|
+
|
96
|
+
```
|
97
|
+
my-web-app
|
98
|
+
└── modules
|
99
|
+
├── application
|
100
|
+
│ ├── app_servers.yml
|
101
|
+
│ ├── database.yml
|
102
|
+
│ └── public_load_balancer.yml
|
103
|
+
├── instances
|
104
|
+
│ └── instance_mappings.yml
|
105
|
+
└── network
|
106
|
+
├── bastion.yml
|
107
|
+
├── cfn_user.yml
|
108
|
+
├── dns.yml
|
109
|
+
├── nat.yml
|
110
|
+
├── private_subnet.yml
|
111
|
+
├── public_subnet.yml
|
112
|
+
└── vpc.yml
|
113
|
+
```
|
114
|
+
|
115
|
+
Use Cfoo to generate your project's CloudFormation template
|
116
|
+
|
117
|
+
```terminal
|
118
|
+
$ cfoo > web-app.template.json
|
119
|
+
```
|
120
|
+
|
121
|
+
## Shortcuts
|
76
122
|
|
77
123
|
Cfoo allows you to simplify CloudFormation [intrinsic function](http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference.html)
|
78
124
|
references using its own shorthand
|
@@ -106,31 +152,61 @@ Cfoo Shortcut: `$(HostedZone).`
|
|
106
152
|
### YAML Types
|
107
153
|
|
108
154
|
Cfoo gives you the option of using YAML custom data-types where it helps to make your templates easier to read.
|
109
|
-
The table below uses inline YAML lists, but multiline lists can also be used.
|
110
155
|
|
111
156
|
##### Reference
|
112
157
|
|
113
|
-
CloudFormation:
|
158
|
+
CloudFormation:
|
159
|
+
```json
|
160
|
+
{ "Ref" : "InstanceType" }
|
161
|
+
```
|
114
162
|
|
115
|
-
YAML Type:
|
163
|
+
YAML Type:
|
164
|
+
```yaml
|
165
|
+
!Ref InstanceType
|
166
|
+
```
|
116
167
|
|
117
168
|
##### Mapping Reference
|
118
169
|
|
119
|
-
CloudFormation:
|
170
|
+
CloudFormation:
|
171
|
+
```json
|
172
|
+
{ "FindInMap" : [ "SubnetConfig", "VPC", "CIDR" ] }
|
173
|
+
```
|
120
174
|
|
121
|
-
YAML Type:
|
175
|
+
YAML Type:
|
176
|
+
```yaml
|
177
|
+
!FindInMap [ SubnetConfig, VPC, CIDR ]
|
178
|
+
```
|
122
179
|
|
123
180
|
##### Attribute Reference
|
124
181
|
|
125
|
-
CloudFormation:
|
182
|
+
CloudFormation:
|
183
|
+
```json
|
184
|
+
{ "Fn::GetAtt" : [ "Ec2Instance", "PublicIp" ] }
|
185
|
+
```
|
126
186
|
|
127
|
-
YAML Type:
|
187
|
+
YAML Type:
|
188
|
+
```yaml
|
189
|
+
!GetAtt [ Ec2Instance, PublicIp ]
|
190
|
+
```
|
128
191
|
|
129
192
|
##### Base64 String
|
130
193
|
|
131
|
-
CloudFormation:
|
194
|
+
CloudFormation:
|
195
|
+
```json
|
196
|
+
{ "Fn::Base64" : "#!/bin/bash\necho 'Running script...'" }
|
197
|
+
```
|
198
|
+
|
199
|
+
YAML Type:
|
200
|
+
```yaml
|
201
|
+
!Base64 "#!/bin/bash\necho 'running script...'"
|
202
|
+
```
|
132
203
|
|
133
|
-
YAML Type:
|
204
|
+
Alternative YAML Type:
|
205
|
+
```yaml
|
206
|
+
!Base64 |
|
207
|
+
#!/bin/bash
|
208
|
+
echo 'running script...'
|
209
|
+
```
|
134
210
|
|
135
211
|
## Goals
|
136
212
|
|
@@ -144,9 +220,8 @@ Cfoo aims to let developers simplify CloudFormation templates by:
|
|
144
220
|
|
145
221
|
### Secondary Goals
|
146
222
|
|
147
|
-
Cfoo aims (subject to Primary Goals) to:
|
223
|
+
Cfoo also aims (subject to Primary Goals) to:
|
148
224
|
|
149
|
-
- allow all aspects of CloudFormation templates to be expressed in YAML (so you don't need to use JSON)
|
150
225
|
- allow inclusion existing JSON templates (so you don't have to switch all at once)
|
151
226
|
|
152
227
|
### Non-goals
|
data/TODO
CHANGED
@@ -2,13 +2,12 @@ Features
|
|
2
2
|
----------
|
3
3
|
More tests for EL parsing
|
4
4
|
Spaces in EL?
|
5
|
-
|
5
|
+
Allow lone dollar signs in strings? (escaping is only necessary if the dollar is in front of a bracket)
|
6
6
|
Shorthand for AWS variables (e.g. { "Ref" : "AWS::StackId" } )
|
7
|
-
|
8
|
-
Better errors when parsing fails
|
7
|
+
Allow arrays as function args (e.g. $(Join("", [1,2,3])))
|
9
8
|
Split into resources/mappings/parameters/etc by putting templates into directories with those names
|
10
|
-
Allow directory for raw templates
|
11
9
|
Allow traditional JSON CloudFormation templates (including modules, splitting, etc)
|
10
|
+
Expose parser, for use with Fog (github fog/fog)
|
12
11
|
Vim highlighting
|
13
12
|
Use multi-json instead of json gem (spec.add_dependency 'multi_json', '~> 1.0')
|
14
13
|
Disablable warnings about naming conventions
|
@@ -23,7 +22,6 @@ Tests
|
|
23
22
|
|
24
23
|
Tasks
|
25
24
|
----------
|
26
|
-
Refactor EL parser
|
27
25
|
|
28
26
|
Bugs
|
29
27
|
----------
|
data/bin/cfoo
CHANGED
@@ -11,13 +11,8 @@ end
|
|
11
11
|
|
12
12
|
filenames = ARGV
|
13
13
|
|
14
|
-
|
15
|
-
|
16
|
-
project = Cfoo::Project.new(file_system)
|
17
|
-
parser = Cfoo::Parser.new(file_system)
|
18
|
-
processor = Cfoo::Processor.new(parser, project)
|
19
|
-
renderer = Cfoo::Renderer.new
|
20
|
-
cfoo = Cfoo::Cfoo.new(processor, renderer, STDOUT)
|
14
|
+
factory = Cfoo::Factory.new(STDOUT, STDERR)
|
15
|
+
cfoo = factory.cfoo
|
21
16
|
|
22
17
|
if filenames.empty?
|
23
18
|
cfoo.build_project
|
@@ -0,0 +1,23 @@
|
|
1
|
+
Feature: Error reporting
|
2
|
+
As a Cfoo user
|
3
|
+
I want to get useful parse errors
|
4
|
+
So that I can debug my templates
|
5
|
+
|
6
|
+
Scenario: YAML Parse error
|
7
|
+
Given I have a file "bad_yaml.yml" containing
|
8
|
+
"""
|
9
|
+
EntryPoint: Key: Value:
|
10
|
+
"""
|
11
|
+
When I process "bad_yaml.yml"
|
12
|
+
Then I should see an error containing "bad_yaml.yml"
|
13
|
+
And I should see an error containing "line"
|
14
|
+
And I should see an error containing "col"
|
15
|
+
|
16
|
+
Scenario: EL Parse error
|
17
|
+
Given I have a file "bad_el.yml" containing
|
18
|
+
"""
|
19
|
+
EntryPoint: $(
|
20
|
+
"""
|
21
|
+
When I process "bad_el.yml"
|
22
|
+
Then I should see an error containing "Source: $("
|
23
|
+
And I should see an error containing "Location: bad_el.yml line 1, column 13"
|
@@ -0,0 +1,139 @@
|
|
1
|
+
Feature: Expand Function Calls
|
2
|
+
As a CloudFormation user
|
3
|
+
I want to use an expression language as a shorthand for function calls
|
4
|
+
So that I my templates are easier to read
|
5
|
+
|
6
|
+
Scenario: Function with no parameters
|
7
|
+
Given I have a file "autoscaling_group.yml" containing
|
8
|
+
"""
|
9
|
+
WebServerGroup:
|
10
|
+
Type: AWS::AutoScaling::AutoScalingGroup
|
11
|
+
Properties:
|
12
|
+
AvailabilityZones: $(GetAZs())
|
13
|
+
"""
|
14
|
+
When I process "autoscaling_group.yml"
|
15
|
+
Then the output should match JSON
|
16
|
+
"""
|
17
|
+
{
|
18
|
+
"AWSTemplateFormatVersion" : "2010-09-09",
|
19
|
+
"WebServerGroup" : {
|
20
|
+
"Type" : "AWS::AutoScaling::AutoScalingGroup",
|
21
|
+
"Properties" : {
|
22
|
+
"AvailabilityZones" : { "Fn::GetAZs" : "" }
|
23
|
+
}
|
24
|
+
}
|
25
|
+
}
|
26
|
+
"""
|
27
|
+
|
28
|
+
Scenario: Function with one parameter
|
29
|
+
Given I have a file "autoscaling_group.yml" containing
|
30
|
+
"""
|
31
|
+
WebServerGroup:
|
32
|
+
Type: AWS::AutoScaling::AutoScalingGroup
|
33
|
+
Properties:
|
34
|
+
AvailabilityZones: $(GetAZs(us-east-1))
|
35
|
+
"""
|
36
|
+
When I process "autoscaling_group.yml"
|
37
|
+
Then the output should match JSON
|
38
|
+
"""
|
39
|
+
{
|
40
|
+
"AWSTemplateFormatVersion" : "2010-09-09",
|
41
|
+
"WebServerGroup" : {
|
42
|
+
"Type" : "AWS::AutoScaling::AutoScalingGroup",
|
43
|
+
"Properties" : {
|
44
|
+
"AvailabilityZones" : { "Fn::GetAZs" : "us-east-1" }
|
45
|
+
}
|
46
|
+
}
|
47
|
+
}
|
48
|
+
"""
|
49
|
+
|
50
|
+
Scenario: Function with multiple parameters
|
51
|
+
Given I have a file "autoscaling_group.yml" containing
|
52
|
+
"""
|
53
|
+
FrontendFleet:
|
54
|
+
Type: AWS::AutoScaling::AutoScalingGroup
|
55
|
+
Properties:
|
56
|
+
AvailabilityZones:
|
57
|
+
- $(GetAtt(PrivateSubnet, AvailabilityZone))
|
58
|
+
"""
|
59
|
+
When I process "autoscaling_group.yml"
|
60
|
+
Then the output should match JSON
|
61
|
+
"""
|
62
|
+
{
|
63
|
+
"AWSTemplateFormatVersion" : "2010-09-09",
|
64
|
+
"FrontendFleet" : {
|
65
|
+
"Type" : "AWS::AutoScaling::AutoScalingGroup",
|
66
|
+
"Properties" : {
|
67
|
+
"AvailabilityZones" : [{ "Fn::GetAtt" : [ "PrivateSubnet", "AvailabilityZone" ] }]
|
68
|
+
}
|
69
|
+
}
|
70
|
+
}
|
71
|
+
"""
|
72
|
+
|
73
|
+
Scenario: Function with no spaces between arguments
|
74
|
+
Given I have a file "autoscaling_group.yml" containing
|
75
|
+
"""
|
76
|
+
FrontendFleet:
|
77
|
+
Type: AWS::AutoScaling::AutoScalingGroup
|
78
|
+
Properties:
|
79
|
+
AvailabilityZones:
|
80
|
+
- $(GetAtt(PrivateSubnet,AvailabilityZone))
|
81
|
+
"""
|
82
|
+
When I process "autoscaling_group.yml"
|
83
|
+
Then the output should match JSON
|
84
|
+
"""
|
85
|
+
{
|
86
|
+
"AWSTemplateFormatVersion" : "2010-09-09",
|
87
|
+
"FrontendFleet" : {
|
88
|
+
"Type" : "AWS::AutoScaling::AutoScalingGroup",
|
89
|
+
"Properties" : {
|
90
|
+
"AvailabilityZones" : [{ "Fn::GetAtt" : [ "PrivateSubnet", "AvailabilityZone" ] }]
|
91
|
+
}
|
92
|
+
}
|
93
|
+
}
|
94
|
+
"""
|
95
|
+
|
96
|
+
Scenario: Function with lots of spaces between and around arguments
|
97
|
+
Given I have a file "autoscaling_group.yml" containing
|
98
|
+
"""
|
99
|
+
FrontendFleet:
|
100
|
+
Type: AWS::AutoScaling::AutoScalingGroup
|
101
|
+
Properties:
|
102
|
+
AvailabilityZones:
|
103
|
+
- $(GetAtt( PrivateSubnet , AvailabilityZone ))
|
104
|
+
"""
|
105
|
+
When I process "autoscaling_group.yml"
|
106
|
+
Then the output should match JSON
|
107
|
+
"""
|
108
|
+
{
|
109
|
+
"AWSTemplateFormatVersion" : "2010-09-09",
|
110
|
+
"FrontendFleet" : {
|
111
|
+
"Type" : "AWS::AutoScaling::AutoScalingGroup",
|
112
|
+
"Properties" : {
|
113
|
+
"AvailabilityZones" : [{ "Fn::GetAtt" : [ "PrivateSubnet", "AvailabilityZone" ] }]
|
114
|
+
}
|
115
|
+
}
|
116
|
+
}
|
117
|
+
"""
|
118
|
+
|
119
|
+
Scenario: Function with embedded EL
|
120
|
+
Given I have a file "app_servers.yml" containing
|
121
|
+
"""
|
122
|
+
AppServerLaunchConfig:
|
123
|
+
Type: AWS::AutoScaling::LaunchConfiguration
|
124
|
+
Properties:
|
125
|
+
ImageId: $(FindInMap(AWSRegionArch2AMI, $(AWS::Region), $(AWSInstanceType2Arch[$(FrontendInstanceType)][Arch])))
|
126
|
+
"""
|
127
|
+
When I process "app_servers.yml"
|
128
|
+
Then the output should match JSON
|
129
|
+
"""
|
130
|
+
{
|
131
|
+
"AWSTemplateFormatVersion" : "2010-09-09",
|
132
|
+
"AppServerLaunchConfig" : {
|
133
|
+
"Type": "AWS::AutoScaling::LaunchConfiguration",
|
134
|
+
"Properties" : {
|
135
|
+
"ImageId": { "Fn::FindInMap" : [ "AWSRegionArch2AMI", { "Ref" : "AWS::Region" }, { "Fn::FindInMap" : [ "AWSInstanceType2Arch", { "Ref" : "FrontendInstanceType" }, "Arch" ] } ] }
|
136
|
+
}
|
137
|
+
}
|
138
|
+
}
|
139
|
+
"""
|
@@ -26,7 +26,7 @@ When /^I build the project$/ do
|
|
26
26
|
cfoo.build_project
|
27
27
|
end
|
28
28
|
|
29
|
-
Then
|
29
|
+
Then /^the output should match JSON$/ do |expected_json|
|
30
30
|
begin
|
31
31
|
expected = JSON.parse(expected_json)
|
32
32
|
rescue StandardError => e
|
@@ -42,6 +42,14 @@ Then(/^the output should match JSON$/) do |expected_json|
|
|
42
42
|
actual.should == expected
|
43
43
|
end
|
44
44
|
|
45
|
+
Then /^I should see "(.*?)"$/ do |expected_output|
|
46
|
+
stdout.messages.join("\n").should include expected_output
|
47
|
+
end
|
48
|
+
|
49
|
+
Then /^I should see an error containing "(.*?)"$/ do |expected_output|
|
50
|
+
stderr.messages.join("\n").should include expected_output
|
51
|
+
end
|
52
|
+
|
45
53
|
def write_file(filename, content)
|
46
54
|
file_fqn = resolve_file(filename)
|
47
55
|
mkdir_p(File.dirname file_fqn)
|
@@ -59,7 +67,7 @@ def resolve_file(filename)
|
|
59
67
|
end
|
60
68
|
|
61
69
|
def cfoo
|
62
|
-
@cfoo ||= Cfoo::Cfoo.new(processor, renderer, stdout)
|
70
|
+
@cfoo ||= Cfoo::Cfoo.new(processor, renderer, stdout, stderr)
|
63
71
|
end
|
64
72
|
|
65
73
|
def processor
|
@@ -92,6 +100,10 @@ def stdout
|
|
92
100
|
@output ||= Output.new
|
93
101
|
end
|
94
102
|
|
103
|
+
def stderr
|
104
|
+
@output ||= Output.new
|
105
|
+
end
|
106
|
+
|
95
107
|
After do
|
96
108
|
rm_rf project_root if File.directory? project_root
|
97
109
|
end
|
data/lib/cfoo.rb
CHANGED
@@ -1,5 +1,8 @@
|
|
1
|
+
require "cfoo/array"
|
1
2
|
require "cfoo/cfoo"
|
3
|
+
require "cfoo/constants"
|
2
4
|
require "cfoo/el_parser"
|
5
|
+
require "cfoo/factory"
|
3
6
|
require "cfoo/file_system"
|
4
7
|
require "cfoo/module"
|
5
8
|
require "cfoo/parser"
|
@@ -8,4 +11,5 @@ require "cfoo/project"
|
|
8
11
|
require "cfoo/renderer"
|
9
12
|
require "cfoo/version"
|
10
13
|
require "cfoo/yaml"
|
14
|
+
require "cfoo/yaml_parser"
|
11
15
|
|
data/lib/cfoo/array.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
class Array
|
2
|
+
def join_adjacent_strings
|
3
|
+
return clone if empty?
|
4
|
+
self[1..-1].inject([first]) do |combined_parts, part|
|
5
|
+
previous = combined_parts.pop
|
6
|
+
if previous.class == String && part.class == String
|
7
|
+
combined_parts << previous + part
|
8
|
+
else
|
9
|
+
combined_parts << previous << part
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
data/lib/cfoo/cfoo.rb
CHANGED
@@ -1,15 +1,19 @@
|
|
1
1
|
module Cfoo
|
2
2
|
class Cfoo
|
3
|
-
def initialize(processor, renderer, stdout)
|
4
|
-
@processor, @renderer, @stdout = processor, renderer, stdout
|
3
|
+
def initialize(processor, renderer, stdout, stderr)
|
4
|
+
@processor, @renderer, @stdout, @stderr = processor, renderer, stdout, stderr
|
5
5
|
end
|
6
6
|
|
7
7
|
def process(*filenames)
|
8
8
|
@stdout.puts(@renderer.render @processor.process(*filenames))
|
9
|
+
rescue Exception => error
|
10
|
+
@stderr.puts error
|
9
11
|
end
|
10
12
|
|
11
13
|
def build_project
|
12
14
|
@stdout.puts(@renderer.render @processor.process_all)
|
15
|
+
rescue Exception => error
|
16
|
+
@stderr.puts error
|
13
17
|
end
|
14
18
|
end
|
15
19
|
end
|