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 CHANGED
@@ -4,7 +4,11 @@
4
4
  [![Coverage Status](https://coveralls.io/repos/drrb/cfoo/badge.png?branch=master)](https://coveralls.io/r/drrb/cfoo)
5
5
  [![Code Climate](https://codeclimate.com/github/drrb/cfoo.png)](https://codeclimate.com/github/drrb/cfoo)
6
6
 
7
- Write your CloudFormation templates in a YAML-based markup language
7
+ [![Gem Version](https://badge.fury.io/rb/cfoo.png)](https://badge.fury.io/rb/cfoo)
8
+ [![Dependency Status](https://gemnasium.com/drrb/cfoo.png)](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
- Process some Cfoo templates from the command line
21
+ 1. Write your CloudFormation templates using Cfoo YAML
18
22
 
19
- $ cfoo web-server-template.yml database_template.yml
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
- ### Shortcuts
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: `{ "Ref" : "InstanceType" }`
158
+ CloudFormation:
159
+ ```json
160
+ { "Ref" : "InstanceType" }
161
+ ```
114
162
 
115
- YAML Type: `!Ref InstanceType`
163
+ YAML Type:
164
+ ```yaml
165
+ !Ref InstanceType
166
+ ```
116
167
 
117
168
  ##### Mapping Reference
118
169
 
119
- CloudFormation: `{ "FindInMap" : [ "SubnetConfig", "VPC", "CIDR" ] }`
170
+ CloudFormation:
171
+ ```json
172
+ { "FindInMap" : [ "SubnetConfig", "VPC", "CIDR" ] }
173
+ ```
120
174
 
121
- YAML Type: `!FindInMap [ SubnetConfig, VPC, CIDR ]`
175
+ YAML Type:
176
+ ```yaml
177
+ !FindInMap [ SubnetConfig, VPC, CIDR ]
178
+ ```
122
179
 
123
180
  ##### Attribute Reference
124
181
 
125
- CloudFormation: `{ "Fn::GetAtt" : [ "Ec2Instance", "PublicIp" ] }`
182
+ CloudFormation:
183
+ ```json
184
+ { "Fn::GetAtt" : [ "Ec2Instance", "PublicIp" ] }
185
+ ```
126
186
 
127
- YAML Type: `!GetAtt [ Ec2Instance, PublicIp ]`
187
+ YAML Type:
188
+ ```yaml
189
+ !GetAtt [ Ec2Instance, PublicIp ]
190
+ ```
128
191
 
129
192
  ##### Base64 String
130
193
 
131
- CloudFormation: `{ "Fn::Base64" : "#!/bin/bash\necho 'Running script...'" }`
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: `!Base64 "#!/bin/bash\necho 'running script...'"`
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
- More than [A-Za-z:] in EL identifiers?
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
- Shorthand for Fn::GetAZs
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
- yaml_parser = Cfoo::YamlParser.new
15
- file_system = Cfoo::FileSystem.new(".", yaml_parser)
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(/^the output should match JSON$/) do |expected_json|
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
@@ -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
 
@@ -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
@@ -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