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.
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
+