cfoo 0.0.1 → 0.0.2

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