bora 0.9.4 → 1.0.0.beta1

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 051a9f9d9023048aa57a43f69a4fa40694976b39
4
- data.tar.gz: 015a4c14851b8448634116b8363455f728a75117
3
+ metadata.gz: 0274ebd129ad2e2c1e96f6c93accc024628bb960
4
+ data.tar.gz: 0b5b69a386da693777967af8692b4ba87cecb648
5
5
  SHA512:
6
- metadata.gz: 2ddc7cc56506aa31329a61310c6315c5f3b61164c0153ad3e56851e23c91bf21b056c2031e9d6598d1b6f546cebb0589f4ffc2232050bee049e9c1db22ccb1be
7
- data.tar.gz: 80ef8eb03d677a97fbf49fbf21625fc9adc8e2c104326dea6337e13462daf6e0ec0f07b95013e34962741154138ca6eb7417e81d07d2cc183fd22afd8397ad2a
6
+ metadata.gz: 3de20877d9013f467d4fe4a95656586234f6bf9383d40db73b8bb4f118224449683ceb8ba1701a585bc28f8fa51089c6227dc1d34d6c9b142622948f1eddc256
7
+ data.tar.gz: 48257c8f06b1d56a3d952b40135ae72b7c72ddd472b5da9b0c2a845676e9cdc1e1bb05ec969fd05f48cd313e2110d242e8b2fbd4a8be5270caa1fa6946090109
data/README.md CHANGED
@@ -1,169 +1,198 @@
1
1
  # Bora
2
2
 
3
- This gem contains Ruby [rake](https://github.com/ruby/rake) tasks that help you work with [CloudFormation](https://aws.amazon.com/cloudformation/) stacks.
4
- You don't need to use it with rake though - you can use the underlying classes in any Ruby app.
3
+ This Ruby gem contains a command line utility and [rake](https://github.com/ruby/rake) tasks
4
+ that help you define and work with [CloudFormation](https://aws.amazon.com/cloudformation/) stacks.
5
+
6
+ In a single YAML file you define your templates,
7
+ the stack instances built from those templates (eg: dev, uat, staging, prod, etc),
8
+ and the parameters for those stacks. Parameters can even refer to outputs of other stacks.
9
+ Templates can be written with plain CloudFormation JSON or
10
+ [cfndsl](https://github.com/stevenjack/cfndsl).
11
+
12
+ Given this config, Bora then provides commands (or Rake tasks) to work with those stacks
13
+ (create, update, delete, diff, etc).
5
14
 
6
15
 
7
16
  ## Installation
8
17
 
9
- Add this line to your application's Gemfile:
18
+ If you're using Bundler, add this line to your application's `Gemfile`:
10
19
 
11
20
  ```ruby
12
21
  gem 'bora'
13
22
  ```
14
23
 
15
- And then execute:
24
+ And then run `bundle install`.
16
25
 
17
- $ bundle
26
+ Alternatively, install directly with `gem install bora`.
18
27
 
19
- Or install it yourself as:
20
-
21
- $ gem install bora
22
28
 
23
29
  ## Usage
24
30
 
25
31
  ### Quick Start
26
32
 
27
- Add this to your `Rakefile`
33
+ Create a file `bora.yml` in your home directory, something like this:
34
+ ```yaml
35
+ templates:
36
+ example:
37
+ template_file: example.json
38
+ stacks:
39
+ uat:
40
+ params:
41
+ InstanceType: t2.micro
42
+ prod:
43
+ params:
44
+ InstanceType: m4.xlarge
45
+ ```
46
+
47
+ Now run `bora apply example-uat` to create your "uat" stack.
48
+ Bora will wait until the stack is complete (or failed),
49
+ and return stack events to you as they happen.
50
+ To get a full list of available commands, run `bora help`.
51
+
52
+ Alternatively if you prefer using Rake, add this to your `Rakefile`:
28
53
 
29
54
  ```ruby
30
55
  require 'bora'
31
-
32
- Bora::Tasks.new("example", "example.json")
56
+ Bora.new.rake_tasks
33
57
  ```
34
58
 
35
- This will give you the following rake tasks
36
-
37
- ```shell
38
- rake stack:example:apply # Creates (or updates) the 'example' stack
39
- rake stack:example:current_template # Shows the current template for 'example' stack
40
- rake stack:example:delete # Deletes the 'example' stack
41
- rake stack:example:diff # Diffs the new template with the 'example' stack's current template
42
- rake stack:example:events # Outputs the latest events from the 'example' stack
43
- rake stack:example:new_template # Shows the new template for 'example' stack
44
- rake stack:example:outputs # Shows the outputs from the 'example' stack
45
- rake stack:example:recreate # Recreates (deletes then creates) the 'example' stack
46
- rake stack:example:status # Displays the current status of the 'example' stack
47
- rake stack:example:validate # Checks the 'example' stack's template for validity
48
- ```
59
+ Then run `rake stack:example-uat:apply`.
60
+ To get a full list of available tasks run `rake -T`.
49
61
 
50
- You can add as many templates as you like into your Rakefile, simply define an instance of `Bora::Tasks` for each one.
51
62
 
52
- ### Task Options
63
+ ## File Format Reference
53
64
 
54
- When you define the Bora tasks, you can pass in a number of options that control how Bora works and what gets passed to CloudFormation.
55
- The available options are shown below with their default values.
65
+ The example below is a `bora.yml` file showing all available options:
56
66
 
57
- ```ruby
58
- Bora::Tasks.new("example", "example.json") do |t|
59
- t.stack_options = {}
60
- t.colorize = true
61
- end
62
- ```
63
- * `example.json` - this is a URL to your template. It can be anything openable by Ruby's [`open-uri`](http://ruby-doc.org/stdlib-2.3.0/libdoc/open-uri/rdoc/OpenURI.html) library (eg: a local file or http/https URL), or an `s3://` URL. This parameter is optional - if you don't supply it, you *must* specify either `template_body` or `template_url` in the `stack_options` (see below).
64
- * `stack_options` - A hash of options that are passed directly to the CloudFormation [`create_stack`](http://docs.aws.amazon.com/sdkforruby/api/Aws/CloudFormation/Client.html#create_stack-instance_method) and [`update_stack`](http://docs.aws.amazon.com/sdkforruby/api/Aws/CloudFormation/Client.html#update_stack-instance_method) APIs. If you specified a template URL in the constructor you don't need to supply `template_body` or `template_url here (you will get an error if you do).
65
- * `colorize` - A boolean that controls whether console output is colored or not
67
+ ```yaml
68
+ # A map defining all the CloudFormation templates available.
69
+ # A "template" is effectively a single CloudFormation JSON (or cfndsl template).
70
+ templates:
71
+ # A template named "app"
72
+ app:
73
+ # This template is a plain old CloudFormation JSON file
74
+ template_file: app.json
66
75
 
76
+ # Optional. An array of "capabilities" to be passed to the CloudFormation API
77
+ # (see CloudFormation docs for more details)
78
+ capabilities: [CAPABILITY_IAM]
67
79
 
68
- ### Dynamically Generated Templates
69
- If you are generating your templates dynamically using a DSL such as [cfndsl](https://github.com/stevenjack/cfndsl) you can easily hook this into the Bora tasks by defining a `generate` task within the Bora::Tasks constructor.
80
+ # A map defining all the "stacks" associated with this template
81
+ # for example, "uat" and "prod"
82
+ stacks:
83
+ # The "uat" stack
84
+ uat:
85
+ # The CloudFormation parameters to pass into the stack
86
+ params:
87
+ InstanceType: t2.micro
88
+ AMI: ami-11032472
89
+
90
+ # The "prod" stack
91
+ prod:
92
+ # Optional. The stack name to use in CloudFormation
93
+ # If you don't supply this, the name will be the template
94
+ # name concatenated with the stack name as defined in this file,
95
+ # eg: "app-prod".
96
+ stack_name: prod-application-stack
97
+ params:
98
+ InstanceType: m4.xlarge
99
+ AMI: ami-11032472
70
100
 
71
- ```ruby
72
- Bora::Tasks.new("example", "example.json") do |t|
73
- desc "Generates the template"
74
- task :generate do
75
- # Generate your template and write it into "example.json" here
76
- end
77
- end
78
- ```
101
+ # A template named "web"
102
+ web:
103
+ # This template is using cfndsl. Bora treats any template ending in
104
+ # ".rb" as a cfndsl template.
105
+ template_file: "web.rb"
106
+ stacks:
107
+ uat:
108
+ # The CloudFormation parameters to pass into the stack.
109
+ # You can define both cfndsl parameters and traditional CloudFormation
110
+ # parameters here. Cfndsl will receive all of them, but only those
111
+ # actually defined in the "Parameters" section of the template will be
112
+ # passed through to CloudFormation when the stack is applied.
113
+ params:
114
+ dns_zone: example.com
79
115
 
80
- `cfndsl` comes with a rake task that you can use by embedding it inside the Bora task definition:
116
+ # You can use complex data structures with cfndsl parameters:
117
+ users:
118
+ - id: joe
119
+ name: Joe Bloggs
120
+ - id: mary
121
+ name: Mary Bloggs
81
122
 
82
- ```ruby
83
- require 'bora'
84
- require 'cfndsl/rake_task'
85
-
86
- Bora::Tasks.new("example", "example.json") do |t|
87
- CfnDsl::RakeTask.new do |cfndsl_task|
88
- cfndsl_task.cfndsl_opts = {
89
- files: [{
90
- filename: "example.rb",
91
- output: "example.json"
92
- }]
93
- }
94
- end
95
- end
96
- ```
123
+ # You can refer to outputs of other stacks using "${}" notation too.
124
+ # See below for further details.
125
+ app_url: http://${app-uat/outputs/Domain}/api
97
126
 
98
- If you need to pass parameters from the rake command line through to your generate method,
99
- you can do so by using Rake's [`args.extras`](http://ruby-doc.org/stdlib-2.2.2/libdoc/rake/rdoc/Rake/TaskArguments.html#method-i-extras) functionality:
127
+ # Traditional CloudFormation parameters
128
+ InstanceType: t2.micro
129
+ AMI: ami-11032472
100
130
 
101
- ```ruby
102
- Bora::Tasks.new("example", "example.json") do |t|
103
- task :generate do |t, args|
104
- arg1, arg2 = args.extras
105
- # Generate your template and write it into "example.json" here
106
- end
107
- end
131
+ prod: {}
108
132
  ```
109
- ```shell
110
- $ rake stack:example:apply[arg1_value, arg2_value]
133
+
134
+ ## Parameter Substitution
135
+
136
+ Bora supports looking up parameter values from other stacks and interpolating them into input parameters.
137
+ At present you can only look up the outputs of other stacks,
138
+ however in the future it may support looking up stack parameters or resources.
139
+ Other future possibilities include looking up values from other services,
140
+ for example AMI IDs.
141
+
142
+ The format is as follows:
143
+
144
+ `${<stack_name>/outputs/<output_name>}`
145
+
146
+ For example:
147
+ ```yaml
148
+ params:
149
+ api_url: http://${api-stack/outputs/Domain}/api
111
150
  ```
112
151
 
152
+ This will look up the `Domain` output from the stack named `api-stack` and substitute it into the `api_url` parameter.
113
153
 
114
- ### API
115
154
 
116
- You can use this gem without using Rake. Most of the logic is implemented in [stack.rb](https://github.com/ampedandwired/bora/blob/master/lib/bora/stack.rb) and is fairly self-explanatory.
155
+ ## Command Line
117
156
 
118
- ```ruby
119
- require 'bora'
157
+ Run `bora help` to see all available commands.
120
158
 
121
- stack = Bora::Stack.new("my-stack")
122
- result = stack.update({template_body: File.read("example.json")}) do |event|
123
- puts event
124
- end
159
+ `bora help [command]` will show you help for a particular command,
160
+ eg: `bora help apply`.
125
161
 
126
- puts "Update #{result ? "succeeded" : "failed"}"
127
- ```
128
162
 
129
- ### YAML Configuration - Experimental
130
- You can define and configure your stacks through YAML too.
131
- This interface is currently experimental,
132
- but longer term is likely to become the primary way to use this gem.
133
- Sample usage is shown below (subject to change).
163
+ ## Rake Tasks
134
164
 
135
- Rakefile:
165
+ To use the rake tasks, simply put this in your `Rakefile`:
136
166
  ```ruby
137
- require "bora"
138
- Bora::RakeTasks.new('templates.yml')
167
+ require 'bora'
168
+ Bora.new.rake_tasks
139
169
  ```
140
170
 
141
- templates.yml:
142
- ```yaml
143
- templates:
144
- app:
145
- template_file: templates/test.json
146
- stacks:
147
- dev: {}
148
- uat: {}
171
+ To get a full list of available tasks run `rake -T`.
149
172
 
150
- web:
151
- # Templates ending in ".rb" are treated as cfndsl templates
152
- template_file: templates/test.rb
153
- capabilities: [CAPABILITY_IAM]
154
- stacks:
155
- dev:
156
- # Set stack name explicitly (otherwise would default to "web-dev")
157
- stack_name: foo-dev
158
- params:
159
- # Look up a value from the outputs of the "app-dev"stack
160
- app_sg: ${app-dev/outputs/AppSecurityGroup}
161
- uat:
162
- params:
163
- app_sg: foouatdev
164
173
 
174
+ ## Overriding Stack Parameters from the Command Line
175
+
176
+ Some commands accept a list of parameters that will override those defined in the YAML file.
177
+
178
+ If you are using the Bora command line, you can pass these parameters like this:
179
+
180
+ ```bash
181
+ $ bora apply web-uat --params 'instance_type=t2.micro' 'ami=ami-11032472'
165
182
  ```
166
183
 
184
+ For rake, he equivalent is:
185
+ ```bash
186
+ $ rake stack:web-uat:apply[instance_type=t2.micro,ami=ami-11032472]
187
+ ```
188
+
189
+
190
+ ## Related Projects
191
+ The following projects provided inspiration for Bora:
192
+ * [CfnDsl](https://github.com/stevenjack/cfndsl) - A Ruby DSL for CloudFormation templates
193
+ * [CloudFormer](https://github.com/kunday/cloudformer) - Rake tasks for CloudFormation
194
+ * [Cumulus](https://github.com/cotdsa/cumulus) - A Python YAML based tool for working with CloudFormation
195
+
167
196
 
168
197
  ## Development
169
198
 
data/bora.gemspec CHANGED
@@ -22,6 +22,7 @@ Gem::Specification.new do |spec|
22
22
  spec.add_dependency "colorize", "~> 0.7"
23
23
  spec.add_dependency "diffy", "~> 3.0"
24
24
  spec.add_dependency "rake", "~> 10.0"
25
+ spec.add_dependency "thor", "~> 0.19"
25
26
 
26
27
  spec.add_development_dependency "bundler", "~> 1.11"
27
28
  spec.add_development_dependency "rspec", "~> 3.0"
data/exe/bora ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bora/cli'
4
+
5
+ Bora::Cli.start(ARGV)
data/lib/bora.rb CHANGED
@@ -1,6 +1,43 @@
1
+ require "yaml"
2
+ require "colorize"
1
3
  require "bora/version"
2
- require "bora/status"
3
- require "bora/event"
4
- require "bora/stack"
4
+ require "bora/template"
5
5
  require "bora/tasks"
6
- require "bora/rake_tasks"
6
+
7
+ class Bora
8
+ DEFAULT_CONFIG_FILE = "bora.yml"
9
+
10
+ def initialize(config_file_or_hash: DEFAULT_CONFIG_FILE, colorize: true)
11
+ @templates = {}
12
+ config = load_config(config_file_or_hash)
13
+ String.disable_colorization = !colorize
14
+ config['templates'].each do |template_name, template_config|
15
+ @templates[template_name] = Template.new(template_name, template_config)
16
+ end
17
+ end
18
+
19
+ def template(name)
20
+ @templates[name]
21
+ end
22
+
23
+ def stack(stack_name)
24
+ t = @templates.find { |_, template| template.stack(stack_name) != nil }
25
+ t ? t[1].stack(stack_name) : nil
26
+ end
27
+
28
+ def rake_tasks
29
+ @templates.each { |_, t| t.rake_tasks }
30
+ end
31
+
32
+
33
+ protected
34
+
35
+ def load_config(config)
36
+ if config.class == String
37
+ return YAML.load_file(config)
38
+ elsif config.class == Hash
39
+ return config
40
+ end
41
+ end
42
+
43
+ end
@@ -0,0 +1,35 @@
1
+ require 'bora/cfn/status'
2
+
3
+ class Bora
4
+ module Cfn
5
+
6
+ class Event
7
+ def initialize(event)
8
+ @event = event
9
+ @status = Status.new(@event.resource_status)
10
+ end
11
+
12
+ def method_missing(sym, *args, &block)
13
+ @event.send(sym, *args, &block)
14
+ end
15
+
16
+ def status_success?
17
+ @status.success?
18
+ end
19
+
20
+ def status_failure?
21
+ @status.failure?
22
+ end
23
+
24
+ def status_complete?
25
+ status_success? || status_failure?
26
+ end
27
+
28
+ def to_s
29
+ status_reason = @event.resource_status_reason ? " - #{@event.resource_status_reason}" : ""
30
+ "#{@event.timestamp.getlocal} - #{@event.resource_type} - #{@event.logical_resource_id} - #{@status}#{status_reason}"
31
+ end
32
+ end
33
+
34
+ end
35
+ end
@@ -0,0 +1,24 @@
1
+ class Bora
2
+ module Cfn
3
+
4
+ class Output
5
+ def initialize(output)
6
+ @output = output
7
+ end
8
+
9
+ def key
10
+ @output.output_key
11
+ end
12
+
13
+ def value
14
+ @output.output_value
15
+ end
16
+
17
+ def to_s
18
+ desc = @output.description ? " (#{@output.description})" : ""
19
+ "#{@output.output_key} - #{@output.output_value} #{desc}"
20
+ end
21
+ end
22
+
23
+ end
24
+ end