cfoo 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +19 -0
  3. data/.simplecov +4 -0
  4. data/.travis.yml +6 -0
  5. data/Gemfile +4 -0
  6. data/LICENSE.txt +676 -0
  7. data/README.md +166 -0
  8. data/Rakefile +10 -0
  9. data/TODO +29 -0
  10. data/bin/cfoo +26 -0
  11. data/cfoo.gemspec +35 -0
  12. data/features/attribute_expansion.feature +43 -0
  13. data/features/convert_yaml_to_json.feature +69 -0
  14. data/features/el_escaping.feature +21 -0
  15. data/features/map_reference_expansion.feature +68 -0
  16. data/features/modules.feature +101 -0
  17. data/features/parse_files.feature +46 -0
  18. data/features/reference_expansion.feature +49 -0
  19. data/features/step_definitions/cfoo_steps.rb +107 -0
  20. data/features/support/env.rb +5 -0
  21. data/features/yamly_shortcuts.feature +132 -0
  22. data/lib/cfoo.rb +11 -0
  23. data/lib/cfoo/cfoo.rb +15 -0
  24. data/lib/cfoo/el_parser.rb +105 -0
  25. data/lib/cfoo/file_system.rb +28 -0
  26. data/lib/cfoo/module.rb +25 -0
  27. data/lib/cfoo/parser.rb +82 -0
  28. data/lib/cfoo/processor.rb +38 -0
  29. data/lib/cfoo/project.rb +16 -0
  30. data/lib/cfoo/renderer.rb +9 -0
  31. data/lib/cfoo/version.rb +3 -0
  32. data/lib/cfoo/yaml.rb +34 -0
  33. data/lib/cfoo/yaml_parser.rb +16 -0
  34. data/spec/cfoo/cfoo_spec.rb +31 -0
  35. data/spec/cfoo/el_parser_spec.rb +47 -0
  36. data/spec/cfoo/file_system_spec.rb +61 -0
  37. data/spec/cfoo/module_spec.rb +16 -0
  38. data/spec/cfoo/parser_spec.rb +148 -0
  39. data/spec/cfoo/processor_spec.rb +52 -0
  40. data/spec/cfoo/project_spec.rb +16 -0
  41. data/spec/cfoo/renderer_spec.rb +19 -0
  42. data/spec/cfoo/yaml_parser_spec.rb +79 -0
  43. data/spec/spec_helper.rb +2 -0
  44. metadata +235 -0
@@ -0,0 +1,166 @@
1
+ # Cfoo
2
+
3
+ [![Build Status](https://travis-ci.org/drrb/cfoo.png?branch=master)](https://travis-ci.org/drrb/cfoo)
4
+ [![Coverage Status](https://coveralls.io/repos/drrb/cfoo/badge.png?branch=master)](https://coveralls.io/r/drrb/cfoo)
5
+ [![Code Climate](https://codeclimate.com/github/drrb/cfoo.png)](https://codeclimate.com/github/drrb/cfoo)
6
+
7
+ Write your CloudFormation templates in a YAML-based markup language
8
+
9
+ ## Installation
10
+
11
+ Cfoo can be installed as a Ruby Gem
12
+
13
+ $ gem install cfoo
14
+
15
+ ## Usage
16
+
17
+ Process some Cfoo templates from the command line
18
+
19
+ $ cfoo web-server-template.yml database_template.yml
20
+
21
+ ## Templates
22
+
23
+ ### Comparison with standard CloudFormation templates
24
+
25
+ Snippet from a CloudFormation template (based on [this example](https://s3.amazonaws.com/cloudformation-templates-us-east-1/Rails_Single_Instance.template)):
26
+
27
+ ```json
28
+ "Properties": {
29
+ "ImageId" : { "Fn::FindInMap" : [ "AWSRegion2AMI", { "Ref" : "AWS::Region" }, "AMI" ] },
30
+ "InstanceType" : { "Ref" : "InstanceType" },
31
+ "SecurityGroups" : [ {"Ref" : "FrontendGroup"} ],
32
+ "KeyName" : { "Ref" : "KeyName" },
33
+ "UserData" : { "Fn::Base64" : { "Fn::Join" : ["", [
34
+ "#!/bin/bash -v\n",
35
+ "yum update -y aws-cfn-bootstrap\n",
36
+
37
+ "function error_exit\n",
38
+ "{\n",
39
+ " /opt/aws/bin/cfn-signal -e 1 -r \"$1\" '", { "Ref" : "WaitHandle" }, "'\n",
40
+ " exit 1\n",
41
+ "}\n",
42
+
43
+ "/opt/aws/bin/cfn-init -s ", { "Ref" : "AWS::StackId" }, " -r WebServer ",
44
+ " --region ", { "Ref" : "AWS::Region" }, " || error_exit 'Failed to run cfn-init'\n",
45
+
46
+ "/opt/aws/bin/cfn-signal -e 0 -r \"cfn-init complete\" '", { "Ref" : "WaitHandle" }, "'\n"
47
+ ]]}}
48
+ }
49
+ ```
50
+
51
+ Equivalent Cfoo template snippet:
52
+
53
+ ```yaml
54
+ Properties:
55
+ ImageId : AWSRegion2AMI[$(AWS::Region)][AMI]
56
+ InstanceType: $(InstanceType)
57
+ SecurityGroups:
58
+ - $(FrontendGroup)
59
+ KeyName: $(KeyName)
60
+ UserData: !Base64 |
61
+ #!/bin/bash -v
62
+ yum update -y aws-cfn-bootstrap
63
+
64
+ function error_exit
65
+ {
66
+ /opt/aws/bin/cfn-signal -e 1 -r "$1" '$(WaitHandle)'
67
+ exit 1
68
+ }
69
+
70
+ /opt/aws/bin/cfn-init -s $(AWS::StackId) -r WebServer --region $(AWS::Region) || error_exit 'Failed to run cfn-init'
71
+
72
+ /opt/aws/bin/cfn-signal -e 0 -r "cfn-init completed" '$(WaitHandle)'
73
+ ```
74
+
75
+ ### Shortcuts
76
+
77
+ Cfoo allows you to simplify CloudFormation [intrinsic function](http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference.html)
78
+ references using its own shorthand
79
+
80
+ ##### Reference
81
+
82
+ CloudFormation: `{ "Ref" : "InstanceType" }`
83
+
84
+ Cfoo Shortcut: `$(InstanceType)`
85
+
86
+ ##### Mapping Reference
87
+
88
+ CloudFormation: `{ "FindInMap" : [ "SubnetConfig", "VPC", "CIDR" ] }`
89
+
90
+ Cfoo Shortcut: `$(SubnetConfig[VPC][CIDR])`
91
+
92
+ ##### Attribute Reference
93
+
94
+ CloudFormation: `{ "Fn::GetAtt" : [ "Ec2Instance", "PublicIp" ] }`
95
+
96
+ Cfoo Shortcut: `$(Ec2Instance.PublicIp)`
97
+
98
+ Other Shortcut: `$(Ec2Instance[PublicIp])`
99
+
100
+ ##### Embedded Reference
101
+
102
+ CloudFormation: `{ "Fn::Join" : [ "", [{"Ref" : "HostedZone"}, "." ]]}`
103
+
104
+ Cfoo Shortcut: `$(HostedZone).`
105
+
106
+ ### YAML Types
107
+
108
+ 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
+
111
+ ##### Reference
112
+
113
+ CloudFormation: `{ "Ref" : "InstanceType" }`
114
+
115
+ YAML Type: `!Ref InstanceType`
116
+
117
+ ##### Mapping Reference
118
+
119
+ CloudFormation: `{ "FindInMap" : [ "SubnetConfig", "VPC", "CIDR" ] }`
120
+
121
+ YAML Type: `!FindInMap [ SubnetConfig, VPC, CIDR ]`
122
+
123
+ ##### Attribute Reference
124
+
125
+ CloudFormation: `{ "Fn::GetAtt" : [ "Ec2Instance", "PublicIp" ] }`
126
+
127
+ YAML Type: `!GetAtt [ Ec2Instance, PublicIp ]`
128
+
129
+ ##### Base64 String
130
+
131
+ CloudFormation: `{ "Fn::Base64" : "#!/bin/bash\necho 'Running script...'" }`
132
+
133
+ YAML Type: `!Base64 "#!/bin/bash\necho 'running script...'"`
134
+
135
+ ## Goals
136
+
137
+ ### Primary Goals
138
+
139
+ Cfoo aims to let developers simplify CloudFormation templates by:
140
+
141
+ - allowing them to write templates in YAML
142
+ - providing an expression language to simplify CF Ref/Attr/etc expressions
143
+ - allowing templates to be split up into logical components (to simplify and share)
144
+
145
+ ### Secondary Goals
146
+
147
+ Cfoo aims (subject to Primary Goals) to:
148
+
149
+ - allow all aspects of CloudFormation templates to be expressed in YAML (so you don't need to use JSON)
150
+ - allow inclusion existing JSON templates (so you don't have to switch all at once)
151
+
152
+ ### Non-goals
153
+
154
+ Cfoo does not (yet) aim to:
155
+
156
+ - provide commandline utilities for interacting directly with CloudFormation (it just generates the templates for now)
157
+ - resolve/validate references (the CloudFormation API already does this)
158
+
159
+ ## Contributing
160
+
161
+ 1. Fork it
162
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
163
+ 3. Make your changes (with tests please)
164
+ 4. Commit your changes (`git commit -am 'Add some feature'`)
165
+ 5. Push to the branch (`git push origin my-new-feature`)
166
+ 6. Create new Pull Request
@@ -0,0 +1,10 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+ require 'cucumber/rake/task'
4
+ require 'coveralls/rake/task'
5
+
6
+ Coveralls::RakeTask.new
7
+ RSpec::Core::RakeTask.new(:spec)
8
+ Cucumber::Rake::Task.new(:features)
9
+
10
+ task :default => [:spec, :features, 'coveralls:push']
data/TODO ADDED
@@ -0,0 +1,29 @@
1
+ Features
2
+ ----------
3
+ More tests for EL parsing
4
+ Spaces in EL?
5
+ More than [A-Za-z:] in EL identifiers?
6
+ Shorthand for AWS variables (e.g. { "Ref" : "AWS::StackId" } )
7
+ Shorthand for Fn::GetAZs
8
+ Better errors when parsing fails
9
+ Split into resources/mappings/parameters/etc by putting templates into directories with those names
10
+ Allow directory for raw templates
11
+ Allow traditional JSON CloudFormation templates (including modules, splitting, etc)
12
+ Vim highlighting
13
+ Use multi-json instead of json gem (spec.add_dependency 'multi_json', '~> 1.0')
14
+ Disablable warnings about naming conventions
15
+ Allow module references to remote templates (inc. Git repositories) (auto-included)
16
+ Allow rendering only part of the project (in case part of the infrastructure already exists)
17
+ Render templates with ERB to allow extra flexibility/substitution
18
+ Resolve EL and complain/fail if targets don't exist
19
+ Unit testing for templates
20
+
21
+ Tests
22
+ ----------
23
+
24
+ Tasks
25
+ ----------
26
+ Refactor EL parser
27
+
28
+ Bugs
29
+ ----------
@@ -0,0 +1,26 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ lib_path = File.expand_path('../../lib', __FILE__)
4
+ $LOAD_PATH.unshift lib_path unless $LOAD_PATH.include? lib_path
5
+ require 'cfoo'
6
+
7
+ if ARGV.include? "--help"
8
+ STDERR.puts "Usage: #{File.basename $0} [filename...]"
9
+ exit 1
10
+ end
11
+
12
+ filenames = ARGV
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)
21
+
22
+ if filenames.empty?
23
+ cfoo.build_project
24
+ else
25
+ cfoo.process *filenames
26
+ end
@@ -0,0 +1,35 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'cfoo/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "cfoo"
8
+ gem.version = Cfoo::VERSION
9
+ gem.authors = ["drrb"]
10
+ gem.email = ["drrrrrrrrrrrb@gmail.com"]
11
+ gem.license = "GPL-3"
12
+ gem.description = "Cfoo: CloudFormation master"
13
+ gem.summary = <<-EOF
14
+ Cfoo (pronounced 'sifu') allows you to write your CloudFormation templates in a
15
+ YAML-based markup language, and organise it into modules to make it easier to
16
+ maintain.
17
+ EOF
18
+ gem.homepage = "https://github.com/drrb/cfoo"
19
+
20
+ gem.files = `git ls-files`.split($/)
21
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
22
+ gem.test_files = gem.files.grep(%r{^(spec|features)/})
23
+ gem.require_paths = ["lib"]
24
+
25
+ gem.add_dependency "json"
26
+ gem.add_dependency "parslet"
27
+
28
+ gem.add_development_dependency "bundler", "~> 1.3"
29
+ gem.add_development_dependency "rake"
30
+ gem.add_development_dependency "rspec"
31
+ gem.add_development_dependency "cucumber"
32
+ gem.add_development_dependency "simplecov"
33
+ gem.add_development_dependency "coveralls", ">= 0.6.3"
34
+ gem.add_development_dependency "json" # Coveralls needs it
35
+ end
@@ -0,0 +1,43 @@
1
+ Feature: Expand EL Attribute 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: Attribute expansion
7
+ Given I have a file "outputs.yml" containing
8
+ """
9
+ EntryPoint:
10
+ Description: IP address of the Bastion Host
11
+ Value: $(BastionHost.PublicIp)
12
+ """
13
+ When I process "outputs.yml"
14
+ Then the output should match JSON
15
+ """
16
+ {
17
+ "AWSTemplateFormatVersion" : "2010-09-09",
18
+ "EntryPoint" : {
19
+ "Description" : "IP address of the Bastion Host",
20
+ "Value" : { "Fn::GetAtt" : [ "BastionHost", "PublicIp" ]}
21
+ }
22
+ }
23
+ """
24
+
25
+ Scenario: Embedded attribute expansion
26
+ Given I have a file "outputs.yml" containing
27
+ """
28
+ WebSite:
29
+ Description: URL of the website
30
+ Value: http://$(PublicElasticLoadBalancer.DNSName)/index.html
31
+ """
32
+ When I process "outputs.yml"
33
+ Then the output should match JSON
34
+ """
35
+ {
36
+ "AWSTemplateFormatVersion" : "2010-09-09",
37
+ "WebSite" : {
38
+ "Description" : "URL of the website",
39
+ "Value" : { "Fn::Join" : [ "", [ "http://", { "Fn::GetAtt" : [ "PublicElasticLoadBalancer", "DNSName" ]}, "/index.html"]]}
40
+ }
41
+ }
42
+ """
43
+
@@ -0,0 +1,69 @@
1
+ Feature: Convert YAML to JSON
2
+ As a JSON provider
3
+ I want to generate my JSON from YAML
4
+ So that it's easier to read
5
+
6
+ Scenario: Basic array conversion
7
+ Given I have a file "list.yml" containing
8
+ """
9
+ list:
10
+ - One
11
+ - 2
12
+ - "3"
13
+ """
14
+ When I process "list.yml"
15
+ Then the output should match JSON
16
+ """
17
+ {
18
+ "AWSTemplateFormatVersion" : "2010-09-09",
19
+ "list" : ["One", 2, "3"]
20
+ }
21
+ """
22
+
23
+ Scenario: Basic map conversion
24
+ Given I have a file "map.yml" containing
25
+ """
26
+ 1: One
27
+ 2: Two
28
+ 3: Three
29
+ """
30
+ When I process "map.yml"
31
+ Then the output should match JSON
32
+ """
33
+ {
34
+ "AWSTemplateFormatVersion" : "2010-09-09",
35
+ "1" : "One",
36
+ "2" : "Two",
37
+ "3" : "Three"
38
+ }
39
+ """
40
+
41
+ Scenario: Embedded map structure
42
+ Given I have a file "embeddedmap.yml" containing
43
+ """
44
+ Fruit:
45
+ - Apples: [ red, green ]
46
+ - Bananas
47
+ - Grapes: [ seeded, seedless ]
48
+ Vegetables:
49
+ - Beans: [ red, black ]
50
+ - Sweet Corn
51
+ - Mirleton
52
+ """
53
+ When I process "embeddedmap.yml"
54
+ Then the output should match JSON
55
+ """
56
+ {
57
+ "AWSTemplateFormatVersion" : "2010-09-09",
58
+ "Fruit": [
59
+ { "Apples": [ "red", "green" ] },
60
+ "Bananas",
61
+ { "Grapes": [ "seeded", "seedless" ] }
62
+ ],
63
+ "Vegetables": [
64
+ { "Beans": [ "red", "black" ] },
65
+ "Sweet Corn",
66
+ "Mirleton"
67
+ ]
68
+ }
69
+ """
@@ -0,0 +1,21 @@
1
+ Feature: EL escaping
2
+ As a CloudFormation user
3
+ I want to to be able to escape the EL
4
+ So that I can type whatever text I want
5
+
6
+ Scenario: Escape EL
7
+ Given I have a file "outputs.yml" containing
8
+ """
9
+ EntryPoint:
10
+ Value: \$(BastionHost.PublicIp)
11
+ """
12
+ When I process "outputs.yml"
13
+ Then the output should match JSON
14
+ """
15
+ {
16
+ "AWSTemplateFormatVersion" : "2010-09-09",
17
+ "EntryPoint" : {
18
+ "Value" : "$(BastionHost.PublicIp)"
19
+ }
20
+ }
21
+ """
@@ -0,0 +1,68 @@
1
+ Feature: Expand EL Mapping References
2
+ As a CloudFormation user
3
+ I want to use an expression language as a shorthand for mapping references
4
+ So that I my templates are easier to read
5
+
6
+ Scenario: Map reference expansion
7
+ Given I have a file "outputs.yml" containing
8
+ """
9
+ EntryPoint:
10
+ Description: IP address of the Bastion Host
11
+ Value: $(SubnetConfig[VPC][CIDR])
12
+ """
13
+ When I process "outputs.yml"
14
+ Then the output should match JSON
15
+ """
16
+ {
17
+ "AWSTemplateFormatVersion" : "2010-09-09",
18
+ "EntryPoint" : {
19
+ "Description" : "IP address of the Bastion Host",
20
+ "Value" : { "Fn::FindInMap" : [ "SubnetConfig", "VPC", "CIDR" ]}
21
+ }
22
+ }
23
+ """
24
+
25
+ Scenario: Embedded map reference expansion
26
+ Given I have a file "outputs.yml" containing
27
+ """
28
+ WebSite:
29
+ Description: URL of the website
30
+ Value: http://$(Network[Dns][LoadBalancerDnsName])/index.html
31
+ """
32
+ When I process "outputs.yml"
33
+ Then the output should match JSON
34
+ """
35
+ {
36
+ "AWSTemplateFormatVersion" : "2010-09-09",
37
+ "WebSite" : {
38
+ "Description" : "URL of the website",
39
+ "Value" : { "Fn::Join" : [ "", [ "http://", { "Fn::FindInMap" : [ "Network", "Dns", "LoadBalancerDnsName" ]}, "/index.html"]]}
40
+ }
41
+ }
42
+ """
43
+
44
+ Scenario: Map key is reference
45
+ Given I have a file "nat.yml" containing
46
+ """
47
+ Resources:
48
+ NATDevice:
49
+ Type: AWS::EC2::Instance
50
+ Properties:
51
+ ImageId: $(AWSNATAMI[$(AWS::Region)][AMI])
52
+ """
53
+ When I process "nat.yml"
54
+ Then the output should match JSON
55
+ """
56
+ {
57
+ "AWSTemplateFormatVersion" : "2010-09-09",
58
+ "Resources" : {
59
+ "NATDevice" : {
60
+ "Type" : "AWS::EC2::Instance",
61
+ "Properties" : {
62
+ "ImageId" : { "Fn::FindInMap" : [ "AWSNATAMI" , { "Ref" : "AWS::Region" } , "AMI" ] }
63
+ }
64
+ }
65
+ }
66
+ }
67
+ """
68
+