bora 0.1.0

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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 9d8bdb0f5c4ecf2bd52b21f4be37383916b33f11
4
+ data.tar.gz: e0185553a1d9600dd9b4ff546e720b78fe006003
5
+ SHA512:
6
+ metadata.gz: 64a03d50e228425644d439891c6f1272d12deae7d34cd0a43d6a8ef72ab2207ff5cee387413765ee24f347957851f7dc50bf8e86429c5bfcc6f7885351155292
7
+ data.tar.gz: b992783319a0389b1bc7a57d6cdd862349d4b032072285a045e3d3aace2edb4253ad739833c9cfc577e5b11d8b44aaaf3288502da4fb7eafd126c6a7b8a84fcc
@@ -0,0 +1,11 @@
1
+ /.bundle/
2
+ /.direnv/
3
+ /.envrc
4
+ /.yardoc
5
+ /Gemfile.lock
6
+ /_yardoc/
7
+ /coverage/
8
+ /doc/
9
+ /pkg/
10
+ /spec/reports/
11
+ /tmp/
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.0.0
4
+ before_install: gem install bundler -v 1.11.2
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in bora.gemspec
4
+ gemspec
@@ -0,0 +1,36 @@
1
+ # Bora
2
+
3
+ Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/bora`. To experiment with that code, run `bin/console` for an interactive prompt.
4
+
5
+ TODO: Delete this and the text above, and describe your gem
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem 'bora'
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ $ bundle
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install bora
22
+
23
+ ## Usage
24
+
25
+ TODO: Write usage instructions here
26
+
27
+ ## Development
28
+
29
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
30
+
31
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
32
+
33
+ ## Contributing
34
+
35
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/bora.
36
+
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "bora"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,26 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'bora/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "bora"
8
+ spec.version = Bora::VERSION
9
+ spec.authors = ["Charles Blaxland"]
10
+ spec.email = ["charles.blaxland@gmail.com"]
11
+
12
+ spec.summary = %q{A tool (including rake tasks) for working with cloudformation stacks}
13
+ spec.homepage = "https://github.com/ampedandwired/bora"
14
+
15
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
16
+ spec.bindir = "exe"
17
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
18
+ spec.require_paths = ["lib"]
19
+
20
+ spec.add_dependency "aws-sdk", "~> 2.0"
21
+ spec.add_dependency "colorize", "~> 0.7"
22
+ spec.add_dependency "rake", "~> 10.0"
23
+
24
+ spec.add_development_dependency "bundler", "~> 1.11"
25
+ spec.add_development_dependency "rspec", "~> 3.0"
26
+ end
@@ -0,0 +1,4 @@
1
+ require "bora/version"
2
+ require "bora/event"
3
+ require "bora/stack"
4
+ require "bora/tasks"
@@ -0,0 +1,36 @@
1
+ require 'colorize'
2
+
3
+ module Bora
4
+ class Event
5
+ def initialize(event)
6
+ @event = event
7
+ end
8
+
9
+ def method_missing(sym, *args, &block)
10
+ @event.send(sym, *args, &block)
11
+ end
12
+
13
+ def status_success?
14
+ @event.resource_status.end_with?("_COMPLETE")
15
+ end
16
+
17
+ def status_failure?
18
+ @event.resource_status.end_with?("_FAILED")
19
+ end
20
+
21
+ def status_complete?
22
+ status_success? || status_failure?
23
+ end
24
+
25
+ def to_s(colorize = true)
26
+ color = case
27
+ when status_success?; :green
28
+ when status_failure?; :red
29
+ else; :yellow;
30
+ end
31
+ status = colorize ? @event.resource_status.colorize(color) : @event.resource_status
32
+ status_reason = @event.resource_status_reason ? " - #{@event.resource_status_reason}" : ""
33
+ "#{@event.timestamp} - #{@event.resource_type} - #{@event.logical_resource_id} - #{status}#{status_reason}"
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,90 @@
1
+ require 'set'
2
+ require 'aws-sdk'
3
+
4
+ module Bora
5
+ class Stack
6
+ def initialize(stack_name)
7
+ @stack_name = stack_name
8
+ @cfn = Aws::CloudFormation::Client.new
9
+ @processed_events = Set.new
10
+ end
11
+
12
+ def create(params, &block)
13
+ call_cfn_action(:create, params, &block)
14
+ end
15
+
16
+ def update(params, &block)
17
+ call_cfn_action(:update, params, &block)
18
+ end
19
+
20
+ def create_or_update(params, &block)
21
+ exists? ? update(params, &block) : create(params, &block)
22
+ end
23
+
24
+ def delete(&block)
25
+ call_cfn_action(:delete, &block)
26
+ end
27
+
28
+ def exists?
29
+ underlying_stack && underlying_stack.stack_status != 'DELETE_COMPLETE'
30
+ end
31
+
32
+
33
+ private
34
+
35
+ def call_cfn_action(action, params = {}, &block)
36
+ underlying_stack(refresh: true)
37
+ return true if action == :delete && !exists?
38
+ @previous_event_time = last_event_time
39
+ params[:stack_name] = @stack_name
40
+ begin
41
+ @cfn.method("#{action.to_s.downcase}_stack").call(params)
42
+ wait_for_completion(&block)
43
+ rescue Aws::CloudFormation::Errors::ValidationError => e
44
+ raise e unless e.message.include?("No updates are to be performed")
45
+ end
46
+ (action == :delete && !underlying_stack) || underlying_stack.stack_status.end_with?('_COMPLETE')
47
+ end
48
+
49
+ def wait_for_completion
50
+ begin
51
+ events = unprocessed_events
52
+ events.each { |e| yield e } if block_given?
53
+ finished = events.find do |e|
54
+ e.resource_type == 'AWS::CloudFormation::Stack' && e.logical_resource_id == @stack_name && e.status_complete?
55
+ end
56
+ sleep 10 unless finished
57
+ end until finished
58
+ underlying_stack(refresh: true)
59
+ end
60
+
61
+ def underlying_stack(refresh: false)
62
+ if !@_stack || refresh
63
+ begin
64
+ response = @cfn.describe_stacks({stack_name: @stack_name})
65
+ @_stack = response.stacks[0]
66
+ rescue Aws::CloudFormation::Errors::ValidationError
67
+ @_stack = nil
68
+ end
69
+ end
70
+ @_stack
71
+ end
72
+
73
+ def unprocessed_events
74
+ return [] if !underlying_stack
75
+ events = @cfn.describe_stack_events({stack_name: underlying_stack.stack_id}).stack_events
76
+ unprocessed_events = events.select do |event|
77
+ !@processed_events.include?(event.event_id) && @previous_event_time < event.timestamp
78
+ end
79
+ @processed_events.merge(unprocessed_events.map(&:event_id))
80
+ unprocessed_events.reverse.map { |e| Event.new(e) }
81
+ end
82
+
83
+ def last_event_time
84
+ return Time.at(0) if !underlying_stack
85
+ events = @cfn.describe_stack_events({stack_name: @stack_name}).stack_events
86
+ events.length > 0 ? events[0].timestamp : Time.at(0)
87
+ end
88
+
89
+ end
90
+ end
@@ -0,0 +1,60 @@
1
+ require 'rake/tasklib'
2
+ require 'bora/stack'
3
+
4
+ module Bora
5
+ class Tasks < Rake::TaskLib
6
+ def initialize(stack_name)
7
+ @stack_name = stack_name
8
+ @stack = Stack.new(stack_name)
9
+ @colorize = true
10
+ yield self if block_given?
11
+ define_tasks
12
+ end
13
+
14
+ attr_accessor :stack_params, :colorize
15
+
16
+ private
17
+
18
+ def define_tasks
19
+ define_apply_task
20
+ define_delete_task
21
+ end
22
+
23
+ def define_apply_task
24
+ within_namespace do
25
+ desc "Creates (or updates) the #{@stack_name} stack"
26
+ task :apply do
27
+ invoke_action(@stack.exists? ? "update" : "create", stack_params)
28
+ end
29
+ end
30
+ end
31
+
32
+ def define_delete_task
33
+ within_namespace do
34
+ desc "Deletes the #{@stack_name} stack"
35
+ task :delete do
36
+ invoke_action("delete")
37
+ end
38
+ end
39
+ end
40
+
41
+ def invoke_action(action, *args)
42
+ puts "#{action.capitalize} stack #{@stack_name}"
43
+ success = @stack.send(action, *args) { |event| puts event.to_s(colorize) }
44
+ if success
45
+ puts "#{action.capitalize} stack #{@stack_name} completed successfully"
46
+ else
47
+ fail("#{action.capitalize} stack #{@stack_name} failed")
48
+ end
49
+ end
50
+
51
+ def within_namespace
52
+ namespace :stack do
53
+ namespace @stack_name do
54
+ yield
55
+ end
56
+ end
57
+ end
58
+
59
+ end
60
+ end
@@ -0,0 +1,3 @@
1
+ module Bora
2
+ VERSION = "0.1.0"
3
+ end
metadata ADDED
@@ -0,0 +1,127 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: bora
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Charles Blaxland
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2016-01-14 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: aws-sdk
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: colorize
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0.7'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0.7'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '10.0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '10.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: bundler
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.11'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.11'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rspec
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '3.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '3.0'
83
+ description:
84
+ email:
85
+ - charles.blaxland@gmail.com
86
+ executables: []
87
+ extensions: []
88
+ extra_rdoc_files: []
89
+ files:
90
+ - ".gitignore"
91
+ - ".rspec"
92
+ - ".travis.yml"
93
+ - Gemfile
94
+ - README.md
95
+ - Rakefile
96
+ - bin/console
97
+ - bin/setup
98
+ - bora.gemspec
99
+ - lib/bora.rb
100
+ - lib/bora/event.rb
101
+ - lib/bora/stack.rb
102
+ - lib/bora/tasks.rb
103
+ - lib/bora/version.rb
104
+ homepage: https://github.com/ampedandwired/bora
105
+ licenses: []
106
+ metadata: {}
107
+ post_install_message:
108
+ rdoc_options: []
109
+ require_paths:
110
+ - lib
111
+ required_ruby_version: !ruby/object:Gem::Requirement
112
+ requirements:
113
+ - - ">="
114
+ - !ruby/object:Gem::Version
115
+ version: '0'
116
+ required_rubygems_version: !ruby/object:Gem::Requirement
117
+ requirements:
118
+ - - ">="
119
+ - !ruby/object:Gem::Version
120
+ version: '0'
121
+ requirements: []
122
+ rubyforge_project:
123
+ rubygems_version: 2.4.5.1
124
+ signing_key:
125
+ specification_version: 4
126
+ summary: A tool (including rake tasks) for working with cloudformation stacks
127
+ test_files: []