blimpy 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.
- data/.gitignore +17 -0
- data/Gemfile +13 -0
- data/LICENSE +22 -0
- data/README.md +45 -0
- data/Rakefile +2 -0
- data/bin/blimpy +88 -0
- data/blimpy.gemspec +20 -0
- data/features/cli/init.feature +10 -0
- data/features/cli/list.feature +19 -0
- data/features/cli/start.feature +27 -0
- data/features/step_definitions/cli_steps.rb +32 -0
- data/features/support/env.rb +19 -0
- data/features/support/hooks.rb +18 -0
- data/lib/blimpy.rb +22 -0
- data/lib/blimpy/box.rb +92 -0
- data/lib/blimpy/engine.rb +29 -0
- data/lib/blimpy/fleet.rb +67 -0
- data/lib/blimpy/version.rb +3 -0
- data/spec/blimpy_spec.rb +28 -0
- data/spec/box_spec.rb +163 -0
- data/spec/engine_spec.rb +58 -0
- data/spec/fleet_spec.rb +56 -0
- data/spec/spec_helper.rb +15 -0
- metadata +102 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 R. Tyler Croy
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
# Blimpy
|
2
|
+
|
3
|
+

|
4
|
+
|
5
|
+
|
6
|
+
### About
|
7
|
+
Notes and other bits are being stored in [this public Evernote
|
8
|
+
notebook](https://www.evernote.com/pub/agentdero/blimpy).
|
9
|
+
|
10
|
+
The current concept/design document is captured in [this
|
11
|
+
note](https://www.evernote.com/pub/agentdero/blimpy#b=58a228bb-8910-4cd1-a7f5-995d775b81a2&n=06def701-7e25-425b-81d4-5811e7987c7e)
|
12
|
+
|
13
|
+
|
14
|
+
### The Blimpfile
|
15
|
+
|
16
|
+
Here's an example Blimpfile:
|
17
|
+
|
18
|
+
```ruby
|
19
|
+
Blimpy.fleet do |fleet|
|
20
|
+
fleet.add do |ship|
|
21
|
+
ship.image_id = 'ami-349b495d'
|
22
|
+
ship.livery = 'rails'
|
23
|
+
ship.group = 'Simple' # [Required] The name of the desired Security Group
|
24
|
+
ship.region = 'us-west-1'
|
25
|
+
ship.name = 'Rails App Server'
|
26
|
+
end
|
27
|
+
end
|
28
|
+
```
|
29
|
+
|
30
|
+
|
31
|
+
### What is Livery?
|
32
|
+
|
33
|
+
In aviation, livery is the insignia or "look" an aircraft typically has. For
|
34
|
+
example, Alaskan Airlines has a distinctive "[creepy mountain
|
35
|
+
man](http://farm1.static.flickr.com/135/333644732_4f797d3c22.jpg)" livery on
|
36
|
+
every plane.
|
37
|
+
|
38
|
+
With Blimpy, "livery" is a similar concept, a means of describing the "look" of
|
39
|
+
a specific machine in the cloud. Currently the concept is still on the drawing
|
40
|
+
board, but if you would imagine a tarball containing a `bootstrap.sh` script
|
41
|
+
and Chef cookbooks or Puppet manifests to provision the entirety of the machine
|
42
|
+
from start-to-finish.
|
43
|
+
|
44
|
+
When the machine comes online, the specified livery would be downloaded from S3
|
45
|
+
(for example) and bootstrap.sh would be invoked as root.
|
data/Rakefile
ADDED
data/bin/blimpy
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'thor'
|
5
|
+
|
6
|
+
begin
|
7
|
+
require 'blimpy'
|
8
|
+
rescue LoadError
|
9
|
+
$:.unshift File.expand_path(File.dirname(__FILE__) + '/../lib/')
|
10
|
+
require 'blimpy'
|
11
|
+
end
|
12
|
+
|
13
|
+
module Blimpy
|
14
|
+
class CLI < Thor
|
15
|
+
BLIMPFILE = File.join(Dir.pwd, 'Blimpfile')
|
16
|
+
|
17
|
+
no_tasks do
|
18
|
+
def ensure_blimpfile
|
19
|
+
unless File.exists? Blimpy::CLI::BLIMPFILE
|
20
|
+
puts 'Please create a Blimpfile in your current directory'
|
21
|
+
exit 1
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
desc 'start', 'Start up a fleet of blimps'
|
27
|
+
method_options :"dry-run" => :boolean
|
28
|
+
def start
|
29
|
+
ensure_blimpfile
|
30
|
+
engine = Blimpy::Engine.new
|
31
|
+
engine.load_file(File.open(BLIMPFILE).read)
|
32
|
+
puts 'Up, up and away!'
|
33
|
+
|
34
|
+
if options[:'dry-run']
|
35
|
+
puts 'skipping actually starting the fleet'
|
36
|
+
exit 0
|
37
|
+
end
|
38
|
+
|
39
|
+
engine.fleet.start
|
40
|
+
end
|
41
|
+
|
42
|
+
desc 'list', 'List running blimps'
|
43
|
+
def list
|
44
|
+
ensure_blimpfile
|
45
|
+
blimps = Dir["#{Dir.pwd}/.blimpy.d/*.blimp"]
|
46
|
+
if blimps.empty?
|
47
|
+
puts 'No currently running VMs'
|
48
|
+
exit 0
|
49
|
+
end
|
50
|
+
|
51
|
+
blimps.each do |blimp|
|
52
|
+
data = YAML.load_file(blimp)
|
53
|
+
instance_id = File.basename(blimp)
|
54
|
+
instance_id = instance_id.split('.blimp').first
|
55
|
+
puts "#{data['name']} (#{instance_id}) is: online at #{data['dns']}"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
desc 'destroy', 'Destroy all running blimps'
|
60
|
+
def destroy
|
61
|
+
ensure_blimpfile
|
62
|
+
fleet = Blimpy::Fleet.new
|
63
|
+
fleet.destroy
|
64
|
+
end
|
65
|
+
|
66
|
+
|
67
|
+
desc 'init', 'Create a skeleton Blimpfile in the current directory'
|
68
|
+
def init
|
69
|
+
File.open(File.join(Dir.pwd, 'Blimpfile'), 'w') do |f|
|
70
|
+
f.write(
|
71
|
+
"""# vim: ft=ruby
|
72
|
+
# Blimpfile created on #{Time.now}
|
73
|
+
|
74
|
+
Blimpy.fleet do |fleet|
|
75
|
+
fleet.add do |ship|
|
76
|
+
ship.name = 'Excelsior'
|
77
|
+
ship.group = 'default'
|
78
|
+
end
|
79
|
+
end
|
80
|
+
""")
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
Blimpy::CLI.start
|
87
|
+
|
88
|
+
exit 0
|
data/blimpy.gemspec
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/blimpy/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = ["R. Tyler Croy"]
|
6
|
+
gem.email = ["tyler@monkeypox.org"]
|
7
|
+
gem.description = %q{Blimpy is a tool for managing a fleet of machines in the CLOUD!}
|
8
|
+
gem.summary = %q{Ruby + CLOUD = Blimpy}
|
9
|
+
gem.homepage = "https://github.com/rtyler/blimpy"
|
10
|
+
|
11
|
+
gem.files = `git ls-files`.split($\)
|
12
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
13
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
14
|
+
gem.name = "blimpy"
|
15
|
+
gem.require_paths = ["lib"]
|
16
|
+
gem.version = Blimpy::VERSION
|
17
|
+
|
18
|
+
gem.add_dependency 'fog'
|
19
|
+
gem.add_dependency 'thor'
|
20
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
Feature: Create a Blimpfile in the current working directory
|
2
|
+
As a Blimpy user
|
3
|
+
In order to get started as quickly as possible
|
4
|
+
The 'init' command should create a skeleton Blimpfile in the current directroy
|
5
|
+
|
6
|
+
|
7
|
+
Scenario: Clean working directory
|
8
|
+
Given I have no Blimpfile in my current directory
|
9
|
+
When I run `blimpy init`
|
10
|
+
Then a file named "Blimpfile" should exist
|
@@ -0,0 +1,19 @@
|
|
1
|
+
Feature: List running VMs
|
2
|
+
|
3
|
+
Scenario: With no running VMs
|
4
|
+
Given I have the Blimpfile:
|
5
|
+
"""
|
6
|
+
# Empty!
|
7
|
+
"""
|
8
|
+
When I run `blimpy list`
|
9
|
+
Then the exit status should be 0
|
10
|
+
And the output should contain:
|
11
|
+
"""
|
12
|
+
No currently running VMs
|
13
|
+
"""
|
14
|
+
|
15
|
+
Scenario: With a running VM
|
16
|
+
Given I have a single VM running
|
17
|
+
When I run `blimpy list`
|
18
|
+
Then the exit status should be 0
|
19
|
+
And the output should list the VM
|
@@ -0,0 +1,27 @@
|
|
1
|
+
Feature: Start a VM or cluster of VMs in the cloud
|
2
|
+
|
3
|
+
Scenario: Without a Blimpfile
|
4
|
+
Given I have no Blimpfile in my current directory
|
5
|
+
When I run `blimpy start`
|
6
|
+
Then the output should contain:
|
7
|
+
"""
|
8
|
+
Please create a Blimpfile in your current directory
|
9
|
+
"""
|
10
|
+
And the exit status should be 1
|
11
|
+
|
12
|
+
Scenario: With a simple Blimpfile
|
13
|
+
Given I have the Blimpfile:
|
14
|
+
"""
|
15
|
+
Blimpy.fleet do |f|
|
16
|
+
f.add do |host|
|
17
|
+
host.group = 'Simple'
|
18
|
+
host.name = 'Cucumber Host'
|
19
|
+
end
|
20
|
+
end
|
21
|
+
"""
|
22
|
+
When I run `blimpy start --dry-run`
|
23
|
+
Then the exit status should be 0
|
24
|
+
And the output should contain:
|
25
|
+
"""
|
26
|
+
Up, up and away!
|
27
|
+
"""
|
@@ -0,0 +1,32 @@
|
|
1
|
+
|
2
|
+
Given /^I have no Blimpfile in my current directory$/ do
|
3
|
+
# no-op: default state of the world is to not have a Blimpfile!
|
4
|
+
end
|
5
|
+
|
6
|
+
Given /^I have the Blimpfile:$/ do |string|
|
7
|
+
create_blimpfile(string)
|
8
|
+
end
|
9
|
+
|
10
|
+
Given /^I have a single VM running$/ do
|
11
|
+
create_blimpfile(
|
12
|
+
"""
|
13
|
+
Blimpy.fleet do |f|
|
14
|
+
f.add do |host|
|
15
|
+
host.name = 'Failboat'
|
16
|
+
end
|
17
|
+
end
|
18
|
+
""")
|
19
|
+
d = File.join(@tempdir, '.blimpy.d')
|
20
|
+
Dir.mkdir(d)
|
21
|
+
@name = 'Cucumber host'
|
22
|
+
@server_id = '0xdeadbeef'
|
23
|
+
File.open(File.join(d, "#{@server_id}.blimp"), 'w') do |f|
|
24
|
+
f.write("name: #{@name}\n")
|
25
|
+
f.write("dns: foo.bar\n")
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
Then /^the output should list the VM$/ do
|
30
|
+
expected = 'Cucumber host (0xdeadbeef) is: online at foo.bar'
|
31
|
+
assert_partial_output(expected, all_output)
|
32
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'aruba/cucumber'
|
2
|
+
require 'fileutils'
|
3
|
+
require 'temp_dir'
|
4
|
+
|
5
|
+
|
6
|
+
# Pull in my gem working directory bin directory
|
7
|
+
ENV['PATH'] = "#{File.expand_path(File.dirname(__FILE__) + '/../../bin')}#{File::PATH_SEPARATOR}#{ENV['PATH']}"
|
8
|
+
|
9
|
+
|
10
|
+
module BlimpyWorld
|
11
|
+
def create_blimpfile(string)
|
12
|
+
path = File.join(@tempdir, 'Blimpfile')
|
13
|
+
File.open(path, 'w') do |f|
|
14
|
+
f.write(string)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
World(BlimpyWorld)
|
@@ -0,0 +1,18 @@
|
|
1
|
+
Before do
|
2
|
+
@cwd = Dir.pwd
|
3
|
+
@tempdir = TempDir.create(:basename => 'blimpy_test')
|
4
|
+
puts "Using tempdir: #{@tempdir}"
|
5
|
+
Dir.chdir(@tempdir)
|
6
|
+
@dirs = [@tempdir]
|
7
|
+
end
|
8
|
+
|
9
|
+
After do |scenario|
|
10
|
+
Dir.chdir(@cwd)
|
11
|
+
|
12
|
+
unless scenario.failed?
|
13
|
+
# NOTE: just having this line here makes me apprehensive
|
14
|
+
#FileUtils.rm_rf(@tempdir) unless @tempdir.nil?
|
15
|
+
else
|
16
|
+
puts "Leaving the tempdir in tact: #{@tempdir}"
|
17
|
+
end
|
18
|
+
end
|
data/lib/blimpy.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'blimpy/box'
|
2
|
+
require 'blimpy/engine'
|
3
|
+
require 'blimpy/fleet'
|
4
|
+
require 'blimpy/version'
|
5
|
+
|
6
|
+
module Blimpy
|
7
|
+
def self.fleet(&block)
|
8
|
+
if block.nil?
|
9
|
+
return false
|
10
|
+
end
|
11
|
+
fleet = Blimpy::Fleet.new
|
12
|
+
block.call fleet
|
13
|
+
fleet
|
14
|
+
end
|
15
|
+
|
16
|
+
class InvalidBlimpFileError < Exception
|
17
|
+
end
|
18
|
+
class InvalidRegionError < Exception
|
19
|
+
end
|
20
|
+
class BoxValidationError < Exception
|
21
|
+
end
|
22
|
+
end
|
data/lib/blimpy/box.rb
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
|
2
|
+
module Blimpy
|
3
|
+
class Box
|
4
|
+
attr_reader :allowed_regions, :region, :server
|
5
|
+
attr_accessor :image_id, :livery, :group, :name, :tags, :fleet_id
|
6
|
+
|
7
|
+
def self.from_instance_id(an_id)
|
8
|
+
server = Fog::Compute[:aws].servers.get(an_id)
|
9
|
+
if server.nil?
|
10
|
+
return nil
|
11
|
+
end
|
12
|
+
self.new(server)
|
13
|
+
end
|
14
|
+
|
15
|
+
def initialize(server=nil)
|
16
|
+
@allowed_regions = ['us-west-1', 'us-west-2', 'us-east-1']
|
17
|
+
@region = 'us-west-2' # Default to US West (Oregon) for now
|
18
|
+
@image_id = 'ami-349b495d' # Default to Ubuntu 10.04 LTS (64bit)
|
19
|
+
@livery = nil
|
20
|
+
@group = nil
|
21
|
+
@name = 'Unnamed Box'
|
22
|
+
@tags = {}
|
23
|
+
@server = server
|
24
|
+
@fleet_id = 0
|
25
|
+
end
|
26
|
+
|
27
|
+
def region=(newRegion)
|
28
|
+
unless @allowed_regions.include? newRegion
|
29
|
+
raise InvalidRegionError
|
30
|
+
end
|
31
|
+
@region = newRegion
|
32
|
+
end
|
33
|
+
|
34
|
+
def validate!
|
35
|
+
if Fog::Compute[:aws].security_groups.get(@group).nil?
|
36
|
+
raise BoxValidationError
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def online!
|
41
|
+
File.open(File.join(state_dir, state_file), 'a') do |f|
|
42
|
+
f.write("dns: #{@server.dns_name}\n")
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def start
|
47
|
+
ensure_state_dir
|
48
|
+
@server = create_host
|
49
|
+
|
50
|
+
File.open(File.join(state_dir, state_file), 'w') do |f|
|
51
|
+
f.write("name: #{@name}\n")
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def stop
|
56
|
+
unless @server.nil?
|
57
|
+
@server.stop
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def destroy
|
62
|
+
unless @server.nil?
|
63
|
+
@server.destroy
|
64
|
+
File.unlink(File.join(state_dir, state_file))
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def state_dir
|
69
|
+
File.join(Dir.pwd, '.blimpy.d')
|
70
|
+
end
|
71
|
+
|
72
|
+
def state_file
|
73
|
+
if @server.nil?
|
74
|
+
raise Exception, "I can't make a state file without a @server!"
|
75
|
+
end
|
76
|
+
"#{@server.id}.blimp"
|
77
|
+
end
|
78
|
+
|
79
|
+
def ensure_state_dir
|
80
|
+
unless File.exist? state_dir
|
81
|
+
Dir.mkdir(state_dir)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
private
|
86
|
+
|
87
|
+
def create_host
|
88
|
+
tags = @tags.merge({:Name => @name, :CreatedBy => 'Blimpy', :BlimpyFleetId => @fleet_id})
|
89
|
+
Fog::Compute[:aws].servers.create(:image_id => @image_id, :region => @region, :tags => tags)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
|
2
|
+
require 'rubygems'
|
3
|
+
require 'fog'
|
4
|
+
|
5
|
+
module Blimpy
|
6
|
+
class Engine
|
7
|
+
attr_reader :fleet
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@fleet = nil
|
11
|
+
end
|
12
|
+
|
13
|
+
def load_file(file_content)
|
14
|
+
if file_content.nil? || file_content.empty?
|
15
|
+
raise InvalidBlimpFileError, 'File appears empty'
|
16
|
+
end
|
17
|
+
|
18
|
+
begin
|
19
|
+
@fleet = eval(file_content)
|
20
|
+
if @fleet and !(@fleet.instance_of? Blimpy::Fleet)
|
21
|
+
raise Exception, 'File does not create a Fleet'
|
22
|
+
end
|
23
|
+
rescue Exception => e
|
24
|
+
raise InvalidBlimpFileError, e.to_s
|
25
|
+
end
|
26
|
+
@fleet
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/lib/blimpy/fleet.rb
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
|
2
|
+
module Blimpy
|
3
|
+
class Fleet
|
4
|
+
attr_reader :hosts
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
@hosts = []
|
8
|
+
@servers = []
|
9
|
+
@id = Time.now.utc.to_i
|
10
|
+
end
|
11
|
+
|
12
|
+
def add(&block)
|
13
|
+
if block.nil?
|
14
|
+
return false
|
15
|
+
end
|
16
|
+
box = Blimpy::Box.new
|
17
|
+
box.fleet_id = @id
|
18
|
+
@hosts << box
|
19
|
+
block.call(box)
|
20
|
+
end
|
21
|
+
|
22
|
+
def start
|
23
|
+
# Make sure all our hosts are valid first!
|
24
|
+
@hosts.each do |host|
|
25
|
+
host.validate!
|
26
|
+
end
|
27
|
+
|
28
|
+
@hosts.each do |host|
|
29
|
+
@servers << host.start
|
30
|
+
end
|
31
|
+
|
32
|
+
@hosts.each do |host|
|
33
|
+
print ">> #{host.name} "
|
34
|
+
host.server.wait_for do
|
35
|
+
print '.'
|
36
|
+
ready?
|
37
|
+
end
|
38
|
+
print ".. online at: #{host.server.dns_name}"
|
39
|
+
host.online!
|
40
|
+
puts
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def members
|
45
|
+
instance_ids = []
|
46
|
+
Dir["#{Dir.pwd}/.blimpy.d/*.blimp"].each do |d|
|
47
|
+
filename = File.basename(d)
|
48
|
+
instance_ids << filename.split('.blimp').first
|
49
|
+
end
|
50
|
+
instance_ids
|
51
|
+
end
|
52
|
+
|
53
|
+
def stop
|
54
|
+
members.each do |instance_id|
|
55
|
+
box = Blimpy::Box.from_instance_id(instance_id)
|
56
|
+
box.stop
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def destroy
|
61
|
+
members.each do |instance_id|
|
62
|
+
box = Blimpy::Box.from_instance_id(instance_id)
|
63
|
+
box.destroy
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
data/spec/blimpy_spec.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Blimpy do
|
4
|
+
describe '#fleet' do
|
5
|
+
context 'without a block' do
|
6
|
+
it 'should not create a new Fleet' do
|
7
|
+
Blimpy::Fleet.should_receive(:new).never
|
8
|
+
subject.fleet
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
context 'with a block' do
|
13
|
+
it 'should create a new Fleet' do
|
14
|
+
result = subject.fleet do |f|
|
15
|
+
end
|
16
|
+
result.should be_instance_of Blimpy::Fleet
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'should invoke the block with a Fleet' do
|
20
|
+
invoked_block = false
|
21
|
+
subject.fleet do |f|
|
22
|
+
invoked_block = true
|
23
|
+
end
|
24
|
+
invoked_block.should be true
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
data/spec/box_spec.rb
ADDED
@@ -0,0 +1,163 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Blimpy::Box do
|
4
|
+
describe '#image_id' do
|
5
|
+
it 'should be the Ubuntu 10.04 AMI ID by default' do
|
6
|
+
subject.image_id.should == 'ami-349b495d'
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
describe '#livery' do
|
11
|
+
it 'should be unset by default' do
|
12
|
+
subject.livery.should be nil
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
describe '#group' do
|
17
|
+
it 'should be unset by default' do
|
18
|
+
subject.group.should be nil
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe '#name' do
|
23
|
+
it 'should be "Unnamed Box" by default' do
|
24
|
+
subject.name.should == 'Unnamed Box'
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
describe '#allowed_regions' do
|
29
|
+
it 'should be an Array' do
|
30
|
+
subject.allowed_regions.should be_instance_of Array
|
31
|
+
end
|
32
|
+
it 'should not be empty' do
|
33
|
+
subject.allowed_regions.should_not be_empty
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
describe '#region' do
|
38
|
+
it 'should return the default region' do
|
39
|
+
subject.region.should == 'us-west-2'
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
describe '#region=' do
|
44
|
+
it 'should raise an InvalidRegionError if the region is not allowed' do
|
45
|
+
expect {
|
46
|
+
subject.region = :elbonia
|
47
|
+
}.to raise_error(Blimpy::InvalidRegionError)
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'should change the value of @region' do
|
51
|
+
subject.region = 'us-east-1'
|
52
|
+
subject.region.should == 'us-east-1'
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
describe '#validate!' do
|
57
|
+
let(:security_group) do
|
58
|
+
group = double('Fog::Compute::AWS::SecurityGroup')
|
59
|
+
group.stub(:name).and_return('MockedGroup')
|
60
|
+
group
|
61
|
+
end
|
62
|
+
let(:groups) { double('Fog::Compute::AWS::SecurityGroups') }
|
63
|
+
|
64
|
+
before :each do
|
65
|
+
# Fog::Compute[:aws] will return a *new* instance of
|
66
|
+
# Fog::Compute::Aws::Real every time (apparently) it is invoked
|
67
|
+
Fog::Compute::AWS::Real.any_instance.should_receive(:security_groups).and_return(groups)
|
68
|
+
end
|
69
|
+
|
70
|
+
context 'with invalid settings' do
|
71
|
+
it 'should raise with a bad security group' do
|
72
|
+
groups.should_receive(:get).and_return(nil)
|
73
|
+
expect {
|
74
|
+
subject.validate!
|
75
|
+
}.to raise_error(Blimpy::BoxValidationError)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
context 'with valid settings' do
|
80
|
+
it 'should validate with a good security group' do
|
81
|
+
groups.should_receive(:get).with('MockedGroup').and_return(security_group)
|
82
|
+
expect {
|
83
|
+
subject.group = 'MockedGroup'
|
84
|
+
subject.validate!
|
85
|
+
}.not_to raise_error(Blimpy::BoxValidationError)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
context 'with a mocked server' do
|
91
|
+
let(:server_id) { 'id-0xdeadbeef' }
|
92
|
+
let(:server) do
|
93
|
+
server = double('Fog::Compute::AWS::Server')
|
94
|
+
server.stub(:id).and_return(server_id)
|
95
|
+
server
|
96
|
+
end
|
97
|
+
|
98
|
+
describe '#start' do
|
99
|
+
before :each do
|
100
|
+
# Mocking out #create_host so we don't actually create EC2 instance
|
101
|
+
Blimpy::Box.any_instance.should_receive(:create_host).and_return(server)
|
102
|
+
Blimpy::Box.any_instance.should_receive(:ensure_state_dir).and_return(true)
|
103
|
+
end
|
104
|
+
|
105
|
+
it 'should create a state file' do
|
106
|
+
path = File.join(subject.state_dir, "#{server_id}.blimp")
|
107
|
+
File.should_receive(:open).with(path, 'w')
|
108
|
+
subject.start
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
|
113
|
+
describe '#stop' do
|
114
|
+
before :each do
|
115
|
+
server.should_receive(:stop)
|
116
|
+
end
|
117
|
+
|
118
|
+
subject { Blimpy::Box.new(server) }
|
119
|
+
|
120
|
+
it 'should stop the Box' do
|
121
|
+
subject.stop
|
122
|
+
end
|
123
|
+
end
|
124
|
+
describe '#destroy' do
|
125
|
+
before :each do
|
126
|
+
server.should_receive(:destroy)
|
127
|
+
end
|
128
|
+
subject { Blimpy::Box.new(server) }
|
129
|
+
|
130
|
+
it 'should remove its state file' do
|
131
|
+
subject.should_receive(:state_file).and_return('foo')
|
132
|
+
File.should_receive(:unlink).with(File.join(subject.state_dir, 'foo'))
|
133
|
+
subject.destroy
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
describe '#ensure_state_dir' do
|
139
|
+
let(:path) { File.join(Dir.pwd, '.blimpy.d') }
|
140
|
+
|
141
|
+
context 'if ./.blimpy.d does not exist' do
|
142
|
+
before :each do
|
143
|
+
File.should_receive(:exist?).with(path).and_return(false)
|
144
|
+
end
|
145
|
+
|
146
|
+
it 'should create directory' do
|
147
|
+
Dir.should_receive(:mkdir).with(path)
|
148
|
+
subject.ensure_state_dir
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
context 'if ./blimpy.d does exist' do
|
153
|
+
before :each do
|
154
|
+
File.should_receive(:exist?).with(path).and_return(true)
|
155
|
+
end
|
156
|
+
|
157
|
+
it 'should create directory' do
|
158
|
+
Dir.should_receive(:mkdir).with(path).never
|
159
|
+
subject.ensure_state_dir
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
data/spec/engine_spec.rb
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Blimpy::Engine do
|
4
|
+
|
5
|
+
describe '#load_file' do
|
6
|
+
context 'no contents' do
|
7
|
+
let(:content) { '' }
|
8
|
+
|
9
|
+
it 'should raise InvalidBlimpFileError' do
|
10
|
+
expect {
|
11
|
+
subject.load_file(content)
|
12
|
+
}.to raise_error(Blimpy::InvalidBlimpFileError)
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
|
17
|
+
context 'invalid content' do
|
18
|
+
let(:content) do
|
19
|
+
"""
|
20
|
+
this is totally invalid Ruby
|
21
|
+
"""
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'should raise InvalidBlimpFileError' do
|
25
|
+
expect {
|
26
|
+
subject.load_file(content)
|
27
|
+
}.to raise_error(Blimpy::InvalidBlimpFileError)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
context 'valid content' do
|
32
|
+
let(:content) do
|
33
|
+
"""
|
34
|
+
Blimpy.fleet do |fleet|
|
35
|
+
fleet.add do |host|
|
36
|
+
host.image_id = 'ami-349b495d'
|
37
|
+
host.livery = 'rails'
|
38
|
+
host.group = 'Simple'
|
39
|
+
host.region = 'us-west-1'
|
40
|
+
host.name = 'Rails App Server'
|
41
|
+
end
|
42
|
+
end
|
43
|
+
"""
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'should create the appropriate Fleet object' do
|
47
|
+
result = subject.load_file(content)
|
48
|
+
result.should be_instance_of Blimpy::Fleet
|
49
|
+
result.hosts.should be_instance_of Array
|
50
|
+
result.hosts.size.should == 1
|
51
|
+
|
52
|
+
host = result.hosts.first
|
53
|
+
host.group.should == 'Simple'
|
54
|
+
host.name.should == 'Rails App Server'
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
data/spec/fleet_spec.rb
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Blimpy::Fleet do
|
4
|
+
describe '#hosts' do
|
5
|
+
it 'should be an Array' do
|
6
|
+
subject.hosts.should be_instance_of Array
|
7
|
+
subject.hosts.size.should == 0
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
describe '#add' do
|
12
|
+
it 'should return false if no Box was properly added' do
|
13
|
+
subject.add.should == false
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'should pass a Box instance to the block' do
|
17
|
+
invoked_block = false
|
18
|
+
subject.add do |box|
|
19
|
+
invoked_block = true
|
20
|
+
box.should be_instance_of Blimpy::Box
|
21
|
+
end
|
22
|
+
invoked_block.should be true
|
23
|
+
end
|
24
|
+
|
25
|
+
context 'with a block' do
|
26
|
+
before :each do
|
27
|
+
subject.add do |b|
|
28
|
+
@box = b
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'should add the box the fleet' do
|
33
|
+
@box.should_not be nil
|
34
|
+
subject.hosts.should include(@box)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
|
40
|
+
context 'group operations' do
|
41
|
+
before :each do
|
42
|
+
subject.should_receive(:members).and_return([])
|
43
|
+
end
|
44
|
+
describe '#stop' do
|
45
|
+
it 'should run stop' do
|
46
|
+
subject.stop
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
describe '#destroy' do
|
51
|
+
it 'should run destroy' do
|
52
|
+
subject.destroy
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
$:.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
|
2
|
+
|
3
|
+
require 'blimpy'
|
4
|
+
|
5
|
+
RSpec.configure do |config|
|
6
|
+
# RSpec automatically cleans stuff out of backtraces;
|
7
|
+
# sometimes this is annoying when trying to debug something e.g. a gem
|
8
|
+
config.backtrace_clean_patterns = [
|
9
|
+
/\/lib\d*\/ruby\//,
|
10
|
+
/bin\//,
|
11
|
+
/gems/,
|
12
|
+
/spec\/spec_helper\.rb/,
|
13
|
+
/lib\/rspec\/(core|expectations|matchers|mocks)/
|
14
|
+
]
|
15
|
+
end
|
metadata
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: blimpy
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- R. Tyler Croy
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-04-23 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: fog
|
16
|
+
requirement: &6750880 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *6750880
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: thor
|
27
|
+
requirement: &6747240 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *6747240
|
36
|
+
description: Blimpy is a tool for managing a fleet of machines in the CLOUD!
|
37
|
+
email:
|
38
|
+
- tyler@monkeypox.org
|
39
|
+
executables:
|
40
|
+
- blimpy
|
41
|
+
extensions: []
|
42
|
+
extra_rdoc_files: []
|
43
|
+
files:
|
44
|
+
- .gitignore
|
45
|
+
- Gemfile
|
46
|
+
- LICENSE
|
47
|
+
- README.md
|
48
|
+
- Rakefile
|
49
|
+
- bin/blimpy
|
50
|
+
- blimpy.gemspec
|
51
|
+
- features/cli/init.feature
|
52
|
+
- features/cli/list.feature
|
53
|
+
- features/cli/start.feature
|
54
|
+
- features/step_definitions/cli_steps.rb
|
55
|
+
- features/support/env.rb
|
56
|
+
- features/support/hooks.rb
|
57
|
+
- lib/blimpy.rb
|
58
|
+
- lib/blimpy/box.rb
|
59
|
+
- lib/blimpy/engine.rb
|
60
|
+
- lib/blimpy/fleet.rb
|
61
|
+
- lib/blimpy/version.rb
|
62
|
+
- spec/blimpy_spec.rb
|
63
|
+
- spec/box_spec.rb
|
64
|
+
- spec/engine_spec.rb
|
65
|
+
- spec/fleet_spec.rb
|
66
|
+
- spec/spec_helper.rb
|
67
|
+
homepage: https://github.com/rtyler/blimpy
|
68
|
+
licenses: []
|
69
|
+
post_install_message:
|
70
|
+
rdoc_options: []
|
71
|
+
require_paths:
|
72
|
+
- lib
|
73
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
74
|
+
none: false
|
75
|
+
requirements:
|
76
|
+
- - ! '>='
|
77
|
+
- !ruby/object:Gem::Version
|
78
|
+
version: '0'
|
79
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
80
|
+
none: false
|
81
|
+
requirements:
|
82
|
+
- - ! '>='
|
83
|
+
- !ruby/object:Gem::Version
|
84
|
+
version: '0'
|
85
|
+
requirements: []
|
86
|
+
rubyforge_project:
|
87
|
+
rubygems_version: 1.8.10
|
88
|
+
signing_key:
|
89
|
+
specification_version: 3
|
90
|
+
summary: Ruby + CLOUD = Blimpy
|
91
|
+
test_files:
|
92
|
+
- features/cli/init.feature
|
93
|
+
- features/cli/list.feature
|
94
|
+
- features/cli/start.feature
|
95
|
+
- features/step_definitions/cli_steps.rb
|
96
|
+
- features/support/env.rb
|
97
|
+
- features/support/hooks.rb
|
98
|
+
- spec/blimpy_spec.rb
|
99
|
+
- spec/box_spec.rb
|
100
|
+
- spec/engine_spec.rb
|
101
|
+
- spec/fleet_spec.rb
|
102
|
+
- spec/spec_helper.rb
|