Compare commits
No commits in common. "master" and "gh-pages" have entirely different histories.
|
@ -1,22 +0,0 @@
|
|||
*.gem
|
||||
*.rbc
|
||||
.bundle
|
||||
.config
|
||||
.yardoc
|
||||
Gemfile.lock
|
||||
InstalledFiles
|
||||
_yardoc
|
||||
coverage
|
||||
doc/
|
||||
lib/bundler/man
|
||||
pkg
|
||||
rdoc
|
||||
spec/reports
|
||||
test/tmp
|
||||
test/version_tmp
|
||||
tmp
|
||||
.blimpy.d
|
||||
.rvmrc
|
||||
Blimpyfile
|
||||
features/report.html
|
||||
features/reports
|
15
Gemfile
15
Gemfile
|
@ -1,15 +0,0 @@
|
|||
source 'https://rubygems.org'
|
||||
|
||||
# Specify your gem's dependencies in blimpy.gemspec
|
||||
gemspec
|
||||
|
||||
group :development do
|
||||
gem 'rake'
|
||||
gem 'rspec', '~> 2.11.0'
|
||||
gem 'cucumber'
|
||||
gem 'ci_reporter'
|
||||
gem 'aruba'
|
||||
gem 'tempdir'
|
||||
gem 'pry'
|
||||
gem 'debugger' unless RUBY_VERSION =~ /1.8.+/
|
||||
end
|
22
LICENSE
22
LICENSE
|
@ -1,22 +0,0 @@
|
|||
Copyright (c) 2012 R. Tyler Croy
|
||||
|
||||
MIT License
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
80
README.md
80
README.md
|
@ -1,80 +0,0 @@
|
|||
# Blimpy
|
||||
[![Build Status](https://buildhive.cloudbees.com/job/rtyler/job/blimpy/badge/icon)](https://buildhive.cloudbees.com/job/rtyler/job/blimpy/)
|
||||
|
||||
![Excelsior!](http://strongspace.com/rtyler/public/excelsior.png)
|
||||
|
||||
|
||||
## About
|
||||
|
||||
Blimpy is a tool to help developers spin up and utilize machines "in the
|
||||
cloud."
|
||||
|
||||
Once a developer has a Blimpfile, they can execute a few simple commands to
|
||||
manage the newly created "fleet" in the specified cloud provider:
|
||||
|
||||
```
|
||||
% blimpy start
|
||||
[snip]
|
||||
>> excelsior ..... online at: ec2-50-112-3-57.us-west-2.compute.amazonaws.com..
|
||||
>> goodyear ..... online at: ec2-50-112-27-89.us-west-2.compute.amazonaws.com
|
||||
%
|
||||
```
|
||||
|
||||
Once machines are online, they're easy to access by name with:
|
||||
|
||||
```
|
||||
% blimpy scp goodyear secrets.tar.gz
|
||||
% blimpy ssh goodyear
|
||||
```
|
||||
|
||||
Then once you're finished working with the machines a simple `blimpy destroy`
|
||||
will terminate the machines.
|
||||
|
||||
|
||||
---
|
||||
|
||||
## The Blimpfile
|
||||
|
||||
Here's an example Blimpfile:
|
||||
|
||||
```ruby
|
||||
Blimpy.fleet do |fleet|
|
||||
fleet.add(:aws) do |ship|
|
||||
ship.name = 'rails-app'
|
||||
ship.ports = [22, 80, 8080] # [Optional] Create a security group with these ports open
|
||||
ship.image_id = 'ami-4438b474' # [Optional] defaults to Ubuntu 12.04 64-bit
|
||||
ship.livery = Blimpy::Livery::CWD # [Optional]
|
||||
ship.group = 'Simple' # [Optional] The name of the desired Security Group
|
||||
ship.region = 'us-west-1' # [Optional] defaults to us-west-2
|
||||
ship.username = 'ubuntu' # [Optional] SSH username, defaults to "ubuntu" for AWS machines
|
||||
ship.flavor = 'm1.small' # [Optional] defaults to t1.micro
|
||||
ship.tags = {:mytag => 'somevalue'} # [Optional]
|
||||
ship.provision_on_start = false # [Optional] defaults to true
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
### Supported Clouds
|
||||
|
||||
Currently Blimpy supports creating machines on:
|
||||
|
||||
* [Amazon Web Services](https://github.com/rtyler/blimpy/wiki/AWS) - using the `:aws` argument passed into `fleet.add`
|
||||
* [OpenStack](https://github.com/rtyler/blimpy/wiki/OpenStack) - using the `:openstack` argument passed into `fleet.add`
|
||||
|
||||
---
|
||||
|
||||
### What is Livery?
|
||||
|
||||
In aviation, livery is the insignia or "look" an aircraft typically has. For
|
||||
example, Alaskan Airlines has a distinctive "[creepy mountain
|
||||
man](http://farm1.static.flickr.com/135/333644732_4f797d3c22.jpg)" livery on
|
||||
every plane.
|
||||
|
||||
With Blimpy, "livery" is a similar concept, a means of describing the "look" of
|
||||
a specific machine in the cloud. Currently the concept is still on the drawing
|
||||
board, but if you would imagine a tarball containing a `bootstrap.sh` script
|
||||
and Chef cookbooks or Puppet manifests to provision the entirety of the machine
|
||||
from start-to-finish.
|
||||
|
||||
When the machine comes online, the specified livery would be downloaded from S3
|
||||
(for example) and bootstrap.sh would be invoked as root.
|
37
Rakefile
37
Rakefile
|
@ -1,37 +0,0 @@
|
|||
#!/usr/bin/env rake
|
||||
require "bundler/gem_tasks"
|
||||
require 'rspec/core/rake_task'
|
||||
require 'cucumber/rake/task'
|
||||
require 'ci/reporter/rake/rspec'
|
||||
|
||||
RSpec::Core::RakeTask.new('spec') do |t|
|
||||
t.rspec_opts = '--color --fail-fast'
|
||||
end
|
||||
|
||||
|
||||
Cucumber::Rake::Task.new('cucumber')
|
||||
|
||||
namespace :cucumber do
|
||||
Cucumber::Rake::Task.new('aws') do |t|
|
||||
t.cucumber_opts = '-p aws'
|
||||
end
|
||||
|
||||
Cucumber::Rake::Task.new('openstack') do |t|
|
||||
t.cucumber_opts = '-p openstack'
|
||||
end
|
||||
|
||||
Cucumber::Rake::Task.new('wip') do |t|
|
||||
t.cucumber_opts = '-p wip'
|
||||
end
|
||||
end
|
||||
|
||||
desc 'Run the basic test suite'
|
||||
task :test => ['spec', 'cucumber']
|
||||
|
||||
namespace :test do
|
||||
desc 'Run all the tests, including the slow (AWS-based) integration tests'
|
||||
task :all => ['spec', 'cucumber', 'cucumber:aws']
|
||||
end
|
||||
|
||||
|
||||
task :default => :test
|
21
bin/blimpy
21
bin/blimpy
|
@ -1,21 +0,0 @@
|
|||
#!/usr/bin/env ruby
|
||||
|
||||
# Need to force the psych engine since syck doesn't seem to serialize the
|
||||
# OpenStack Floating IPs
|
||||
begin
|
||||
require 'rubygems'
|
||||
require 'yaml'
|
||||
YAML::ENGINE.yamler = 'psych'
|
||||
rescue NameError
|
||||
# I don't think we have YAML::ENGINE until Ruby 1.9
|
||||
end
|
||||
|
||||
$:.unshift File.expand_path(File.dirname(__FILE__) + '/../lib/')
|
||||
require 'blimpy/cli'
|
||||
|
||||
# allow monkey-patching of Blimpy by the project (mainly to add more commands)
|
||||
blimprc = File.join(Dir.pwd,"Blimprc")
|
||||
Blimpy.load_file File.open(blimprc).read if File.exists? blimprc
|
||||
|
||||
Blimpy::CLI.start
|
||||
exit 0
|
|
@ -1,21 +0,0 @@
|
|||
# -*- encoding: utf-8 -*-
|
||||
require File.expand_path('../lib/blimpy/version', __FILE__)
|
||||
|
||||
Gem::Specification.new do |gem|
|
||||
gem.authors = ["R. Tyler Croy"]
|
||||
gem.email = ["tyler@monkeypox.org"]
|
||||
gem.description = %q{Blimpy is a tool for managing a fleet of machines in the CLOUD!}
|
||||
gem.summary = %q{Ruby + CLOUD = Blimpy}
|
||||
gem.homepage = "https://github.com/rtyler/blimpy"
|
||||
|
||||
gem.files = `git ls-files`.split($\)
|
||||
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
||||
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
||||
gem.name = "blimpy"
|
||||
gem.require_paths = ["lib"]
|
||||
gem.version = Blimpy::VERSION
|
||||
|
||||
gem.add_dependency 'fog'
|
||||
gem.add_dependency 'thor'
|
||||
gem.add_dependency 'minitar'
|
||||
end
|
|
@ -1,7 +0,0 @@
|
|||
<%
|
||||
opts = "--color --format progress --format junit --out features/reports --format html --out features/report.html"
|
||||
%>
|
||||
default: <%= opts %> --tags ~@wip --tags ~@slow
|
||||
aws: <%= opts %> --tags ~@wip --tags @slow --tags ~@openstack
|
||||
openstack: <%= opts %> --tags ~@wip --tags @openstack
|
||||
wip: <%= opts %> --tags @wip
|
|
@ -1,10 +0,0 @@
|
|||
Feature: Create a Blimpfile in the current working directory
|
||||
As a Blimpy user
|
||||
In order to get started as quickly as possible
|
||||
The 'init' command should create a skeleton Blimpfile in the current directroy
|
||||
|
||||
|
||||
Scenario: Clean working directory
|
||||
Given I have no Blimpfile in my current directory
|
||||
When I run `blimpy init`
|
||||
Then a file named "Blimpfile" should exist
|
|
@ -1,31 +0,0 @@
|
|||
Feature: Run provisioning against running VMs
|
||||
In order to update the currently running VMs with new code/etc
|
||||
As a blimpy user
|
||||
I should be able to incrementally send updates to VMs and have the
|
||||
provisioning code run again
|
||||
|
||||
Background: Ensure a simple Blimpfile
|
||||
Given I have the Blimpfile:
|
||||
"""
|
||||
Blimpy.fleet do |fleet|
|
||||
fleet.add(:aws) do |blimp|
|
||||
blimp.name = 'provision-blimp'
|
||||
end
|
||||
end
|
||||
"""
|
||||
|
||||
Scenario: No arguments and no VMs
|
||||
When I run `blimpy provision`
|
||||
Then the exit status should be 1
|
||||
And the output should contain:
|
||||
"""
|
||||
No Blimps running!
|
||||
"""
|
||||
|
||||
Scenario: Naming a blimp without running Blimps
|
||||
When I run `blimpy provision provision-blimp`
|
||||
Then the exit status should be 1
|
||||
And the output should contain:
|
||||
"""
|
||||
Could not find a blimp named "provision-blimp"
|
||||
"""
|
|
@ -1,17 +0,0 @@
|
|||
Feature: Resume a VM or VMs that were previously stopped
|
||||
|
||||
Scenario: Resume without stopped Blimps
|
||||
Given I have the Blimpfile:
|
||||
"""
|
||||
Blimpy.fleet do |f|
|
||||
f.add(:aws) do |host|
|
||||
host.name = 'Cucumber Host'
|
||||
end
|
||||
end
|
||||
"""
|
||||
When I run `blimpy resume`
|
||||
Then the exit status should be 1
|
||||
And the output should contain:
|
||||
"""
|
||||
No fleet running right now, perhaps you should `start` one.
|
||||
"""
|
|
@ -1,38 +0,0 @@
|
|||
Feature: SCP a file into a named VM
|
||||
In order to easily copy files from my local host to the named VM
|
||||
As a Blimpy user
|
||||
I should be able to run `blimpy scp <name> <file> [dest]`
|
||||
|
||||
Scenario: SCPing with an invalid blimp name
|
||||
Given I have the Blimpfile:
|
||||
"""
|
||||
Blimpy.fleet do |f|
|
||||
f.add(:aws) do |host|
|
||||
host.name = 'Cucumber Host'
|
||||
end
|
||||
end
|
||||
"""
|
||||
And I have a file named "hello.txt"
|
||||
When I run `blimpy scp Gherkins hello.txt`
|
||||
Then the exit status should be 1
|
||||
And the output should contain:
|
||||
"""
|
||||
Could not find a blimp named "Gherkins"
|
||||
"""
|
||||
|
||||
# This test is in the same boat that the complimentary test in ssh.feature is
|
||||
# in.
|
||||
@slow @destroy
|
||||
Scenario: SCPing a valid file
|
||||
Given I have the Blimpfile:
|
||||
"""
|
||||
Blimpy.fleet do |f|
|
||||
f.add(:aws) do |host|
|
||||
host.name = 'Cucumber Host'
|
||||
end
|
||||
end
|
||||
"""
|
||||
And I have a file named "hello.txt"
|
||||
And I run `blimpy start`
|
||||
When I run `blimpy scp "Cucumber Host" hello.txt`
|
||||
Then the exit status should be 0
|
|
@ -1,39 +0,0 @@
|
|||
Feature: SSH into a named VM
|
||||
In order to directly access running VMs
|
||||
As a Blimpy user
|
||||
I should be able to run `blimpy ssh <name>` and be logged in
|
||||
|
||||
Scenario: SSHing with an invalid name
|
||||
Given I have the Blimpfile:
|
||||
"""
|
||||
Blimpy.fleet do |f|
|
||||
f.add(:aws) do |host|
|
||||
host.name = 'Cucumber Host'
|
||||
end
|
||||
end
|
||||
"""
|
||||
When I run `blimpy ssh Gherkins`
|
||||
Then the exit status should be 1
|
||||
And the output should contain:
|
||||
"""
|
||||
Could not find a blimp named "Gherkins"
|
||||
"""
|
||||
|
||||
|
||||
# This test is really frustrating and I can't get aruba and the ssh code
|
||||
# to work together here :-/
|
||||
@slow @destroy @wip
|
||||
Scenario: SSHing into a remote host should work
|
||||
Given I have the Blimpfile:
|
||||
"""
|
||||
Blimpy.fleet do |f|
|
||||
f.add(:aws) do |host|
|
||||
host.group = 'Simple'
|
||||
host.name = 'Cucumber Host'
|
||||
end
|
||||
end
|
||||
"""
|
||||
When I ssh into the machine
|
||||
And I type "hostname -f"
|
||||
And I type "exit"
|
||||
Then the output should contain the right DNS info
|
|
@ -1,72 +0,0 @@
|
|||
Feature: Start a VM or cluster of VMs in the cloud
|
||||
|
||||
Scenario: Without a Blimpfile
|
||||
Given I have no Blimpfile in my current directory
|
||||
When I run `blimpy start`
|
||||
Then the output should contain:
|
||||
"""
|
||||
Please create a Blimpfile in your current directory
|
||||
"""
|
||||
And the exit status should be 1
|
||||
|
||||
Scenario: dry-run start with a simple Blimpfile
|
||||
Given I have the Blimpfile:
|
||||
"""
|
||||
Blimpy.fleet do |f|
|
||||
f.add(:aws) do |host|
|
||||
host.name = 'Cucumber Host'
|
||||
end
|
||||
end
|
||||
"""
|
||||
When I run `blimpy start --dry-run`
|
||||
Then the exit status should be 0
|
||||
And the output should contain:
|
||||
"""
|
||||
skipping actually starting the fleet
|
||||
"""
|
||||
|
||||
Scenario: Start with an invalid Blimpfile
|
||||
Given I have the Blimpfile:
|
||||
"""
|
||||
Blimpy.fleet do |f|
|
||||
f.bork
|
||||
end
|
||||
"""
|
||||
When I run `blimpy start`
|
||||
Then the exit status should be 1
|
||||
And the output should contain:
|
||||
"""
|
||||
The Blimpfile is invalid!
|
||||
"""
|
||||
|
||||
@slow @destroy
|
||||
Scenario: start with a functional Blimpfile
|
||||
Given I have the Blimpfile:
|
||||
"""
|
||||
Blimpy.fleet do |f|
|
||||
f.add(:aws) do |host|
|
||||
host.name = 'Cucumber Host'
|
||||
end
|
||||
end
|
||||
"""
|
||||
When I run `blimpy start`
|
||||
Then the exit status should be 0
|
||||
And the output should contain:
|
||||
"""
|
||||
online at:
|
||||
"""
|
||||
|
||||
@slow @destroy
|
||||
Scenario: Start a bigger instance
|
||||
Given I have the Blimpfile:
|
||||
"""
|
||||
Blimpy.fleet do |f|
|
||||
f.add(:aws) do |host|
|
||||
host.name = 'Cucumber Host'
|
||||
host.flavor = 'm1.large'
|
||||
end
|
||||
end
|
||||
"""
|
||||
When I run `blimpy start`
|
||||
Then the exit status should be 0
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
Feature: Show running VMs
|
||||
|
||||
Scenario: With no running VMs
|
||||
Given I have the Blimpfile:
|
||||
"""
|
||||
# Empty!
|
||||
"""
|
||||
When I run `blimpy status`
|
||||
Then the exit status should be 0
|
||||
And the output should contain:
|
||||
"""
|
||||
No currently running VMs
|
||||
"""
|
||||
|
||||
Scenario: With a running VM
|
||||
Given I have a single VM running
|
||||
When I run `blimpy status`
|
||||
Then the exit status should be 0
|
||||
And the output should list the VM
|
|
@ -1,8 +0,0 @@
|
|||
Feature: Expose the current version to the user
|
||||
As a user
|
||||
I want to be able to check the blimpy version
|
||||
So that I can tell if I should update or not
|
||||
|
||||
Scenario: Simple version check
|
||||
When I run `blimpy version`
|
||||
Then the output should contain the current Blimpy version
|
|
@ -1,35 +0,0 @@
|
|||
Feature: Craft machines based on a livery
|
||||
In order to bootstrap a machine that looks how I expect it to
|
||||
As a Blimpy user
|
||||
When I specify a specific livery then that livery should provision
|
||||
the host the way I would expect it to.
|
||||
|
||||
Scenario: Using a configuration-less livery
|
||||
Given the following Blimpfile contents:
|
||||
"""
|
||||
Blimpy.fleet do |f|
|
||||
f.add(:aws) do |ship|
|
||||
ship.name = 'cucumber-livery'
|
||||
ship.livery = Blimpy::Livery::CWD
|
||||
end
|
||||
end
|
||||
"""
|
||||
When I evaluate the Blimpfile
|
||||
Then the CWD livery should be set up
|
||||
|
||||
Scenario: Configuration-less liveries
|
||||
Given the following Blimpfile contents:
|
||||
"""
|
||||
Blimpy.fleet do |f|
|
||||
f.add(:aws) do |ship|
|
||||
ship.name = 'cucumber-livery'
|
||||
ship.livery = Blimpy::Livery::Puppet.configure do |p|
|
||||
p.module_path = './modules'
|
||||
p.manifest_path = './test/site.pp'
|
||||
p.options = '--verbose'
|
||||
end
|
||||
end
|
||||
end
|
||||
"""
|
||||
When I evaluate the Blimpfile
|
||||
Then the Puppet livery should be correctly configured
|
|
@ -1,26 +0,0 @@
|
|||
Feature: Provision machines on an OpenStack cluster
|
||||
In order to use a private cloud, powered by OpenStack
|
||||
As a Blimpy user
|
||||
I should be able to tspin up machines on OpenStack the same way I am able to
|
||||
on AWS
|
||||
|
||||
|
||||
@slow @destroy @openstack
|
||||
Scenario: Start with a functional Blimpfile
|
||||
Given I have the Blimpfile:
|
||||
"""
|
||||
Blimpy.fleet do |f|
|
||||
f.add(:openstack) do |host|
|
||||
host.name = 'Cucumber Host'
|
||||
host.image_id = '5e624061-65cc-4e67-b6c5-8e7ac6e38ea7' # Maps to our intenral 'lucid-server' image
|
||||
host.region = 'test' # This is the "test" tenant
|
||||
host.flavor = 'm1.tiny'
|
||||
end
|
||||
end
|
||||
"""
|
||||
When I run `blimpy start`
|
||||
Then the exit status should be 0
|
||||
And the output should contain:
|
||||
"""
|
||||
online at:
|
||||
"""
|
|
@ -1,58 +0,0 @@
|
|||
|
||||
Given /^I have no Blimpfile in my current directory$/ do
|
||||
# no-op: default state of the world is to not have a Blimpfile!
|
||||
end
|
||||
|
||||
Given /^I have the Blimpfile:$/ do |string|
|
||||
create_blimpfile(string)
|
||||
end
|
||||
|
||||
Given /^I have a single VM running$/ do
|
||||
create_blimpfile(
|
||||
"""
|
||||
Blimpy.fleet do |f|
|
||||
f.add do |host|
|
||||
host.name = 'Failboat'
|
||||
end
|
||||
end
|
||||
""")
|
||||
d = File.join(@tempdir, '.blimpy.d')
|
||||
Dir.mkdir(d)
|
||||
@name = 'Cucumber host'
|
||||
@server_id = '0xdeadbeef'
|
||||
File.open(File.join(d, "#{@server_id}.blimp"), 'w') do |f|
|
||||
f.write(":name: #{@name}\n")
|
||||
f.write(":dns: foo.bar\n")
|
||||
end
|
||||
end
|
||||
|
||||
Given /^I have a file named "([^"]*)"$/ do |filename|
|
||||
File.open(filename, 'w') do |fd|
|
||||
fd.write("I am #{filename}\n")
|
||||
end
|
||||
end
|
||||
|
||||
When /^I ssh into the machine$/ do
|
||||
step %{I run `blimpy start`}
|
||||
step %{I run `blimpy ssh "Cucumber Host" -o StrictHostKeyChecking=no` interactively}
|
||||
end
|
||||
|
||||
Then /^the output should list the VM$/ do
|
||||
expected = 'Cucumber host (0xdeadbeef) is: online at foo.bar'
|
||||
assert_partial_output(expected, all_output)
|
||||
end
|
||||
|
||||
Then /^the output should contain the right DNS info$/ do
|
||||
terminate_processes!
|
||||
internal_name = nil
|
||||
Dir["#{@tempdir}/.blimpy.d/*.blimp"].each do |filename|
|
||||
data = YAML.load_file(filename)
|
||||
internal_name = data['internal_dns']
|
||||
break unless internal_name.nil?
|
||||
end
|
||||
step %{the output should contain "#{internal_name}"}
|
||||
end
|
||||
|
||||
Then /^the output should contain the current Blimpy version$/ do
|
||||
assert_partial_output(Blimpy::VERSION, all_output)
|
||||
end
|
|
@ -1,19 +0,0 @@
|
|||
Given /^the following Blimpfile contents:$/ do |buffer|
|
||||
@blimpfile = buffer
|
||||
end
|
||||
|
||||
When /^I evaluate the Blimpfile$/ do
|
||||
@blimpfile.should_not be_nil
|
||||
@fleet = eval(@blimpfile)
|
||||
end
|
||||
|
||||
Then /^the CWD livery should be set up$/ do
|
||||
@fleet.should_not be_nil
|
||||
@fleet.ships.first.livery.should == Blimpy::Livery::CWD
|
||||
end
|
||||
|
||||
Then /^the Puppet livery should be correctly configured$/ do
|
||||
@fleet.should_not be_nil
|
||||
@fleet.ships.first.livery.should be_instance_of(Blimpy::Livery::Puppet)
|
||||
end
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
require 'rubygems'
|
||||
require 'aruba/cucumber'
|
||||
require 'fileutils'
|
||||
require 'ruby-debug' unless RUBY_VERSION =~ /1.8.+/
|
||||
require 'temp_dir'
|
||||
|
||||
|
||||
# Pull in my gem working directory bin directory
|
||||
ENV['PATH'] = "#{File.expand_path(File.dirname(__FILE__) + '/../../bin')}#{File::PATH_SEPARATOR}#{ENV['PATH']}"
|
||||
|
||||
$:.unshift(File.expand_path(File.dirname(__FILE__) + "/../../lib"))
|
||||
require 'blimpy'
|
||||
require 'blimpy/livery'
|
||||
|
||||
module Blimpy
|
||||
module Cucumber
|
||||
def create_blimpfile(string)
|
||||
path = File.join(@tempdir, 'Blimpfile')
|
||||
File.open(path, 'w') do |f|
|
||||
f.write(string)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
World(Blimpy::Cucumber)
|
|
@ -1,27 +0,0 @@
|
|||
Before do
|
||||
@cwd = Dir.pwd
|
||||
@tempdir = TempDir.create(:basename => 'blimpy_test')
|
||||
Dir.chdir(@tempdir)
|
||||
@dirs = [@tempdir]
|
||||
end
|
||||
|
||||
Before '@slow' do
|
||||
@aruba_timeout_seconds = 90
|
||||
end
|
||||
|
||||
After '@destroy' do |scenario|
|
||||
Dir.chdir(@tempdir) do
|
||||
`blimpy destroy`
|
||||
end
|
||||
end
|
||||
|
||||
After do |scenario|
|
||||
Dir.chdir(@cwd)
|
||||
|
||||
unless scenario.failed?
|
||||
# NOTE: just having this line here makes me apprehensive
|
||||
#FileUtils.rm_rf(@tempdir) unless @tempdir.nil?
|
||||
else
|
||||
puts "Leaving the tempdir in tact: #{@tempdir}"
|
||||
end
|
||||
end
|
Binary file not shown.
After Width: | Height: | Size: 1.1 KiB |
Binary file not shown.
After Width: | Height: | Size: 13 KiB |
Binary file not shown.
After Width: | Height: | Size: 1.3 KiB |
Binary file not shown.
After Width: | Height: | Size: 1.1 KiB |
Binary file not shown.
After Width: | Height: | Size: 1.3 KiB |
|
@ -0,0 +1,87 @@
|
|||
<!doctype html>
|
||||
<!-- The Time Machine GitHub pages theme was designed and developed by Jon Rohan, on Feb 7, 2012. -->
|
||||
<!-- Follow him for fun. http://twitter.com/jonrohan. Tail his code on http://github.com/jonrohan -->
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
||||
|
||||
<link rel="stylesheet" href="stylesheets/stylesheet.css" media="screen"/>
|
||||
<link rel="stylesheet" href="stylesheets/pygment_trac.css"/>
|
||||
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
|
||||
<script type="text/javascript" src="javascripts/script.js"></script>
|
||||
|
||||
<title>Blimpy</title>
|
||||
<meta name="description" content="A Vagrant-inspired tool for managing multiple machines in the cloud">
|
||||
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div class="wrapper">
|
||||
<header>
|
||||
<h1 class="title">Blimpy</h1>
|
||||
</header>
|
||||
<div id="container">
|
||||
<p class="tagline">A Vagrant-inspired tool for managing multiple machines in the cloud</p>
|
||||
<div id="main" role="main">
|
||||
<div class="download-bar">
|
||||
<div class="inner">
|
||||
<a href="https://github.com/rtyler/blimpy/tarball/master" class="download-button tar"><span>Download</span></a>
|
||||
<a href="https://github.com/rtyler/blimpy/zipball/master" class="download-button zip"><span>Download</span></a>
|
||||
<a href="https://github.com/rtyler/blimpy" class="code">View Blimpy on GitHub</a>
|
||||
</div>
|
||||
<span class="blc"></span><span class="trc"></span>
|
||||
</div>
|
||||
<article class="markdown-body">
|
||||
<h3>Welcome to GitHub Pages.</h3>
|
||||
|
||||
<p>This automatic page generator is the easiest way to create beautiful pages for all of your projects. Author your page content here using GitHub Flavored Markdown, select a template crafted by a designer, and publish. After your page is generated, you can check out the new branch:</p>
|
||||
|
||||
<pre><code>$ cd your_repo_root/repo_name
|
||||
$ git fetch origin
|
||||
$ git checkout gh-pages
|
||||
</code></pre>
|
||||
|
||||
<p>If you're using the GitHub for Mac, simply sync your repository and you'll see the new branch.</p>
|
||||
|
||||
<h3>Designer Templates</h3>
|
||||
|
||||
<p>We've crafted some handsome templates for you to use. Go ahead and continue to layouts to browse through them. You can easily go back to edit your page before publishing. After publishing your page, you can revisit the page generator and switch to another theme. Your Page content will be preserved if it remained markdown format.</p>
|
||||
|
||||
<h3>Rather Drive Stick?</h3>
|
||||
|
||||
<p>If you prefer to not use the automatic generator, push a branch named <code>gh-pages</code> to your repository to create a page manually. In addition to supporting regular HTML content, GitHub Pages support Jekyll, a simple, blog aware static site generator written by our own Tom Preston-Werner. Jekyll makes it easy to create site-wide headers and footers without having to copy them across every page. It also offers intelligent blog support and other advanced templating features.</p>
|
||||
|
||||
<h3>Authors and Contributors</h3>
|
||||
|
||||
<p>You can <a href="https://github.com/blog/821" class="user-mention">@mention</a> a GitHub username to generate a link to their profile. The resulting <code><a></code> element will link to the contributor's GitHub Profile. For example: In 2007, Chris Wanstrath (<a href="https://github.com/defunkt" class="user-mention">@defunkt</a>), PJ Hyett (<a href="https://github.com/pjhyett" class="user-mention">@pjhyett</a>), and Tom Preston-Werner (<a href="https://github.com/mojombo" class="user-mention">@mojombo</a>) founded GitHub.</p>
|
||||
|
||||
<h3>Support or Contact</h3>
|
||||
|
||||
<p>Having trouble with Pages? Check out the documentation at <a href="http://help.github.com/pages">http://help.github.com/pages</a> or contact <a href="mailto:support@github.com">support@github.com</a> and we’ll help you sort it out.</p>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
<footer>
|
||||
<div class="owner">
|
||||
<p><a href="https://github.com/rtyler" class="avatar"><img src="https://secure.gravatar.com/avatar/d565139dbbafc06e7daf4826ca0f0228?s=30&d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png" width="48" height="48"/></a> <a href="https://github.com/rtyler">rtyler</a> maintains <a href="https://github.com/rtyler/blimpy">Blimpy</a></p>
|
||||
|
||||
|
||||
</div>
|
||||
<div class="creds">
|
||||
<small>This page generated using <a href="https://pages.github.com/">GitHub Pages</a><br/>theme by <a href="http://twitter.com/jonrohan/">Jon Rohan</a></small>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
<div class="current-section">
|
||||
<a href="#top">Scroll to top</a>
|
||||
<a href="https://github.com/rtyler/blimpy/tarball/master" class="tar">tar</a><a href="https://github.com/rtyler/blimpy/zipball/master" class="zip">zip</a><a href="" class="code">source code</a>
|
||||
<p class="name"></p>
|
||||
</div>
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,52 @@
|
|||
(function($) {
|
||||
$(document).ready(function(){
|
||||
|
||||
// putting lines by the pre blocks
|
||||
$("pre").each(function(){
|
||||
var pre = $(this).text().split("\n");
|
||||
var lines = new Array(pre.length+1);
|
||||
for(var i = 0; i < pre.length; i++) {
|
||||
var wrap = Math.floor(pre[i].split("").length / 70)
|
||||
if (pre[i]==""&&i==pre.length-1) {
|
||||
lines.splice(i, 1);
|
||||
} else {
|
||||
lines[i] = i+1;
|
||||
for(var j = 0; j < wrap; j++) {
|
||||
lines[i] += "\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
$(this).before("<pre class='lines'>" + lines.join("\n") + "</pre>");
|
||||
});
|
||||
|
||||
var headings = [];
|
||||
|
||||
var collectHeaders = function(){
|
||||
headings.push({"top":$(this).offset().top - 15,"text":$(this).text()});
|
||||
}
|
||||
|
||||
if($(".markdown-body h1").length > 1) $(".markdown-body h1").each(collectHeaders)
|
||||
else if($(".markdown-body h2").length > 1) $(".markdown-body h2").each(collectHeaders)
|
||||
else if($(".markdown-body h3").length > 1) $(".markdown-body h3").each(collectHeaders)
|
||||
|
||||
$(window).scroll(function(){
|
||||
if(headings.length==0) return true;
|
||||
var scrolltop = $(window).scrollTop() || 0;
|
||||
if(headings[0] && scrolltop < headings[0].top) {
|
||||
$(".current-section").css({"opacity":0,"visibility":"hidden"});
|
||||
return false;
|
||||
}
|
||||
$(".current-section").css({"opacity":1,"visibility":"visible"});
|
||||
for(var i in headings) {
|
||||
if(scrolltop >= headings[i].top) {
|
||||
$(".current-section .name").text(headings[i].text);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$(".current-section a").click(function(){
|
||||
$(window).scrollTop(0);
|
||||
return false;
|
||||
})
|
||||
});
|
||||
})(jQuery)
|
|
@ -1,50 +0,0 @@
|
|||
require 'rubygems'
|
||||
require 'fog/core'
|
||||
require 'fog/compute'
|
||||
|
||||
require 'blimpy/box'
|
||||
require 'blimpy/fleet'
|
||||
require 'blimpy/version'
|
||||
|
||||
module Blimpy
|
||||
def self.fleet(&block)
|
||||
if block.nil?
|
||||
return false
|
||||
end
|
||||
fleet = Blimpy::Fleet.new
|
||||
block.call fleet
|
||||
fleet
|
||||
end
|
||||
|
||||
def self.load_file(file_content)
|
||||
if file_content.nil? || file_content.empty?
|
||||
raise InvalidBlimpFileError, 'File appears empty'
|
||||
end
|
||||
|
||||
begin
|
||||
fleet = eval(file_content)
|
||||
if fleet and !(fleet.instance_of? Blimpy::Fleet)
|
||||
raise Exception, 'File does not create a Fleet'
|
||||
end
|
||||
rescue Exception => e
|
||||
raise InvalidBlimpFileError, e.to_s
|
||||
end
|
||||
fleet
|
||||
end
|
||||
|
||||
class UnknownError < Exception
|
||||
end
|
||||
class InvalidBlimpFileError < Exception
|
||||
end
|
||||
class InvalidRegionError < Exception
|
||||
end
|
||||
class BoxValidationError < Exception
|
||||
end
|
||||
class SSHKeyNotFoundError < Exception
|
||||
end
|
||||
class InvalidShipException < Exception
|
||||
end
|
||||
class UnsupportedFeatureException < Exception
|
||||
end
|
||||
class InvalidLiveryException < Exception; end;
|
||||
end
|
|
@ -1,285 +0,0 @@
|
|||
require 'rubygems'
|
||||
require 'yaml'
|
||||
require 'blimpy/helpers/state'
|
||||
require 'blimpy/livery'
|
||||
require 'blimpy/keys'
|
||||
require 'blimpy/securitygroups'
|
||||
require 'blimpy/boxes'
|
||||
|
||||
module Blimpy
|
||||
class Box
|
||||
include Blimpy::Helpers::State
|
||||
|
||||
attr_reader :allowed_regions, :region
|
||||
attr_accessor :image_id, :flavor, :group, :ports
|
||||
attr_accessor :dns, :internal_dns
|
||||
attr_accessor :name, :tags, :fleet_id, :username, :ssh_port, :livery
|
||||
attr_accessor :provision_on_start
|
||||
|
||||
def self.from_instance_id(an_id, data)
|
||||
return if data[:type].nil?
|
||||
|
||||
name = data[:type].to_sym
|
||||
return unless Blimpy::Boxes.const_defined? name
|
||||
|
||||
klass = Blimpy::Boxes.const_get(name)
|
||||
|
||||
server = klass.fog_server_for_instance(an_id, data)
|
||||
return if server.nil?
|
||||
|
||||
box = klass.new(server)
|
||||
box.with_data(an_id, data)
|
||||
box
|
||||
end
|
||||
|
||||
def initialize(server=nil)
|
||||
@provision_on_start = true
|
||||
@livery = nil
|
||||
@group = nil
|
||||
@name = 'Unnamed Box'
|
||||
@tags = {}
|
||||
@ports = []
|
||||
@server = server
|
||||
@fleet_id = 0
|
||||
@ssh_connected = false
|
||||
@exec_commands = true
|
||||
end
|
||||
|
||||
def region=(newRegion)
|
||||
unless (@allowed_regions.nil?) || (@allowed_regions.include?(newRegion))
|
||||
raise InvalidRegionError
|
||||
end
|
||||
@region = newRegion
|
||||
end
|
||||
|
||||
def online!
|
||||
write_state_file
|
||||
end
|
||||
|
||||
def validate!
|
||||
raise NotImplementedError, '#validate! should be defined in a subclass of Blimpy::Box'
|
||||
end
|
||||
|
||||
def prestart
|
||||
end
|
||||
|
||||
def start
|
||||
ensure_state_folder
|
||||
prestart
|
||||
@server = create_host
|
||||
poststart
|
||||
write_state_file
|
||||
end
|
||||
|
||||
def poststart
|
||||
end
|
||||
|
||||
def bootstrap
|
||||
@exec_commands = false
|
||||
if @livery.nil?
|
||||
return
|
||||
end
|
||||
|
||||
if @livery.respond_to? :new
|
||||
@livery = @livery.new
|
||||
end
|
||||
|
||||
wait_for_sshd
|
||||
bootstrap_livery
|
||||
end
|
||||
|
||||
# This is just here to make things more consistent from an API perspective
|
||||
def provision
|
||||
bootstrap
|
||||
end
|
||||
|
||||
def stop
|
||||
unless @server.nil?
|
||||
@server.stop
|
||||
end
|
||||
end
|
||||
|
||||
def resume
|
||||
unless @server.nil?
|
||||
@server.start
|
||||
end
|
||||
end
|
||||
|
||||
def predestroy
|
||||
end
|
||||
|
||||
def destroy
|
||||
unless @server.nil?
|
||||
predestroy
|
||||
@server.destroy
|
||||
postdestroy
|
||||
File.unlink(state_file)
|
||||
end
|
||||
end
|
||||
|
||||
def postdestroy
|
||||
end
|
||||
|
||||
def type
|
||||
# We only really care about the class name as part of the Blimpy::Boxes
|
||||
# module
|
||||
self.class.to_s.split('::').last
|
||||
end
|
||||
|
||||
def serializable_attributes
|
||||
[:type, :name, :region, :dns, :internal_dns, :flavor, :tags]
|
||||
end
|
||||
|
||||
def immutable_attributes
|
||||
[:type]
|
||||
end
|
||||
|
||||
def write_state_file
|
||||
data = {}
|
||||
serializable_attributes.each do |attr|
|
||||
data[attr] = self.send(attr)
|
||||
end
|
||||
File.open(state_file, 'w') do |f|
|
||||
f.write(data.to_yaml)
|
||||
end
|
||||
end
|
||||
|
||||
def state_file
|
||||
if @server.nil?
|
||||
raise Exception, "I can't make a state file without a @server!"
|
||||
end
|
||||
File.join(state_folder, "#{@server.id}.blimp")
|
||||
end
|
||||
|
||||
def wait_for_state(until_state, &block)
|
||||
if @server.nil?
|
||||
return
|
||||
end
|
||||
|
||||
@server.wait_for do
|
||||
block.call
|
||||
state == until_state
|
||||
end
|
||||
end
|
||||
|
||||
def with_data(ship_id, data)
|
||||
data.each do |key, value|
|
||||
next if immutable_attributes.include? key.to_sym
|
||||
self.send("#{key}=", value)
|
||||
end
|
||||
end
|
||||
|
||||
def dns
|
||||
@dns ||= begin
|
||||
if @server.nil?
|
||||
'no name'
|
||||
else
|
||||
@server.dns_name
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def internal_dns
|
||||
@internal_dns ||= begin
|
||||
if @server.nil?
|
||||
'no name'
|
||||
else
|
||||
@server.private_dns_name
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def run_command(*args)
|
||||
if @exec_commands
|
||||
::Kernel.exec(*args)
|
||||
else
|
||||
::Kernel.system(*args)
|
||||
end
|
||||
end
|
||||
|
||||
def ssh_commands(*args)
|
||||
cmds = ['ssh', '-o', 'PasswordAuthentication=no',
|
||||
'-o', 'StrictHostKeyChecking=no' ]
|
||||
if (ssh_port)
|
||||
cmds += [-p, ssh_port.to_s]
|
||||
end
|
||||
cmds += ['-l', username, dns, *args]
|
||||
end
|
||||
|
||||
def ssh_into(*args)
|
||||
run_command(*ssh_commands(*args))
|
||||
end
|
||||
|
||||
def scp_file(filename, directory='', *args)
|
||||
filename = File.expand_path(filename)
|
||||
run_command('scp', '-o', 'StrictHostKeyChecking=no',
|
||||
filename, "#{username}@#{dns}:#{directory}", *args)
|
||||
end
|
||||
|
||||
def scp_files(directory, files)
|
||||
filename = File.expand_path(filename)
|
||||
run_command(*['scp', '-o', 'StrictHostKeyChecking=no']+files+["#{username}@#{dns}:#{directory}"])
|
||||
end
|
||||
|
||||
def bootstrap_livery
|
||||
if @livery.kind_of? Symbol
|
||||
raise Blimpy::InvalidLiveryException, 'Symbol liveries are unsupported!'
|
||||
end
|
||||
|
||||
@livery.setup_on(self)
|
||||
@livery.preflight(self)
|
||||
@livery.flight(self)
|
||||
@livery.postflight(self)
|
||||
end
|
||||
|
||||
def wait_for_sshd
|
||||
return if @ssh_connected
|
||||
start = Time.now.to_i
|
||||
use_exec = @exec_commands
|
||||
# Even if we are supposed to use #exec here, we wait to disable it until
|
||||
# after sshd(8) comes online
|
||||
@exec_commands = false
|
||||
|
||||
$stdout.sync = true
|
||||
need_nl = false
|
||||
|
||||
until @ssh_connected
|
||||
# Run the `true` command and exit
|
||||
@ssh_connected = ssh_into('-q', 'true')
|
||||
# if SSH is killed, don't repeat
|
||||
if $?.signaled?
|
||||
if $?.termsig==2
|
||||
# if Ctrl+C, report what we were doing
|
||||
puts "Failed to connect. To try it yourself:\n#{ssh_commands('-v','true').join(' ')}"
|
||||
end
|
||||
raise Exception, "ssh was killed: #{$?}"
|
||||
end
|
||||
|
||||
unless @ssh_connected
|
||||
if !need_nl
|
||||
p = ssh_port.nil? ? "" : ":#{ssh_port}"
|
||||
print ">> Connecting #{username}@#{name}#{p}"
|
||||
end
|
||||
if (Time.now.to_i - start) < 60
|
||||
print '.'
|
||||
need_nl = true
|
||||
sleep 1
|
||||
end
|
||||
end
|
||||
end
|
||||
puts if need_nl
|
||||
@exec_commands = use_exec
|
||||
end
|
||||
|
||||
def fog
|
||||
raise NotImplementedError, '#fog should be implemented by cloud-specific subclasses'
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
def create_host
|
||||
raise NotImplementedError, '#create_host should be implemented by a cloud-specific subclass'
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,5 +0,0 @@
|
|||
|
||||
module Blimpy
|
||||
module Boxes
|
||||
end
|
||||
end
|
|
@ -1,69 +0,0 @@
|
|||
require 'blimpy/box'
|
||||
require 'blimpy/boxes'
|
||||
require 'fog/aws'
|
||||
|
||||
module Blimpy::Boxes
|
||||
class AWS < Blimpy::Box
|
||||
# Default to US West (Oregon)
|
||||
DEFAULT_REGION = 'us-west-2'
|
||||
# Default to 12.04 64-bit
|
||||
DEFAULT_IMAGE_ID = 'ami-4438b474'
|
||||
|
||||
def self.fog_server_for_instance(id, blimpdata)
|
||||
region = blimpdata[:region] || DEFAULT_REGION
|
||||
fog = Fog::Compute.new(:provider => 'AWS', :region => region)
|
||||
fog.servers.get(id)
|
||||
end
|
||||
|
||||
def initialize(server=nil)
|
||||
super(server)
|
||||
@allowed_regions = ['us-west-1', 'us-west-2', 'us-east-1']
|
||||
@region = DEFAULT_REGION
|
||||
@image_id = DEFAULT_IMAGE_ID
|
||||
@username = 'ubuntu'
|
||||
@flavor = 't1.micro'
|
||||
@group = 'default'
|
||||
end
|
||||
|
||||
def validate!
|
||||
if @region.nil?
|
||||
raise Blimpy::BoxValidationError, "Cannot spin up machine without a set region"
|
||||
end
|
||||
|
||||
if fog.security_groups.get(@group).nil?
|
||||
raise Blimpy::BoxValidationError, "The security group '#{@group}' does not exist in #{@region}"
|
||||
end
|
||||
end
|
||||
|
||||
def fog
|
||||
@fog ||= begin
|
||||
Fog::Compute.new(:provider => 'AWS', :region => @region)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def import_key
|
||||
material = Blimpy::Keys.public_key
|
||||
begin
|
||||
if (fog.key_pairs.get(Blimpy::Keys.key_name).nil?)
|
||||
fog.import_key_pair(Blimpy::Keys.key_name, material)
|
||||
end
|
||||
rescue Fog::Compute::AWS::Error => e
|
||||
end
|
||||
end
|
||||
|
||||
def create_host
|
||||
tags = @tags.merge({:Name => @name, :CreatedBy => 'Blimpy', :BlimpyFleetId => @fleet_id})
|
||||
|
||||
import_key
|
||||
generated_group = Blimpy::SecurityGroups.ensure_group(fog, @ports + [22])
|
||||
groups = [@group, generated_group].compact
|
||||
fog.servers.create(:image_id => @image_id,
|
||||
:flavor_id => @flavor,
|
||||
:key_name => Blimpy::Keys.key_name,
|
||||
:groups => groups,
|
||||
:tags => tags)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,64 +0,0 @@
|
|||
require 'ostruct'
|
||||
require 'fog/core/timeout'
|
||||
require 'fog/core/wait_for'
|
||||
require 'etc'
|
||||
|
||||
module Blimpy::Boxes
|
||||
# Fake box type for physical computer accessible through SSH
|
||||
class Existing < Blimpy::Box
|
||||
attr_accessor :host
|
||||
|
||||
def self.fog_server_for_instance(id, blimpdata)
|
||||
ExistingServer.new(id,blimpdata[:host])
|
||||
end
|
||||
|
||||
def initialize(server=nil)
|
||||
super(server)
|
||||
@username = Etc.getlogin
|
||||
end
|
||||
|
||||
def validate!
|
||||
if @host.nil?
|
||||
raise Blimpy::BoxValidationError, "Don't know which box to log into --- the host property is not set."
|
||||
end
|
||||
end
|
||||
|
||||
def wait_for_state(until_state, &block)
|
||||
# this magical box type becomes any state instantly
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def create_host
|
||||
ExistingServer.new(@name,@host)
|
||||
end
|
||||
end
|
||||
|
||||
class ExistingServer
|
||||
def initialize(name,host)
|
||||
@name = name
|
||||
@host = host
|
||||
end
|
||||
def dns_name
|
||||
@host
|
||||
end
|
||||
def private_dns_name
|
||||
@host
|
||||
end
|
||||
def id
|
||||
@name
|
||||
end
|
||||
|
||||
def wait_for(timeout=Fog.timeout, interval=1, &block)
|
||||
Fog.wait_for(timeout, interval, &block)
|
||||
end
|
||||
|
||||
# no-ops
|
||||
def stop
|
||||
end
|
||||
def start
|
||||
end
|
||||
def destroy
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,159 +0,0 @@
|
|||
require 'blimpy/box'
|
||||
require 'blimpy/boxes'
|
||||
require 'fog/openstack'
|
||||
|
||||
module Blimpy::Boxes
|
||||
class OpenStack < Blimpy::Box
|
||||
def self.fog_server_for_instance(id, blimpdata)
|
||||
region = blimpdata[:region]
|
||||
fog = Fog::Compute.new(:provider => 'OpenStack', :openstack_tenant => region)
|
||||
fog.servers.get(id)
|
||||
end
|
||||
|
||||
class FloatingIp
|
||||
attr_accessor :address, :id
|
||||
|
||||
def initialize(address, id)
|
||||
@address = address
|
||||
@id = id
|
||||
end
|
||||
|
||||
def to_yaml(*args)
|
||||
{:address => address, :id => id}.to_yaml
|
||||
end
|
||||
end
|
||||
|
||||
attr_accessor :key_name, :floating_ip
|
||||
|
||||
def initialize(server=nil)
|
||||
super(server)
|
||||
@username = 'ubuntu'
|
||||
@flavor = 'm1.tiny'
|
||||
@group = 'default'
|
||||
@key_name = nil
|
||||
@floating_ip = nil
|
||||
end
|
||||
|
||||
def ports=(new_pors)
|
||||
raise Blimpy::UnsupportedFeatureException, 'Opening arbitrary ports in OpenStack is currently not supported'
|
||||
end
|
||||
|
||||
def wait_for_state(until_state, &block)
|
||||
until @server.ready?
|
||||
sleep 1
|
||||
@server.reload
|
||||
end
|
||||
# OpenStack doesn't seem to like it if you try to associate the IP
|
||||
# address too early, but will properly associate it after the machine is
|
||||
# ready
|
||||
associate_ip
|
||||
end
|
||||
|
||||
def serializable_attributes
|
||||
super + [:floating_ip]
|
||||
end
|
||||
|
||||
def dns
|
||||
if floating_ip.nil?
|
||||
'unavailable'
|
||||
else
|
||||
floating_ip.address
|
||||
end
|
||||
end
|
||||
|
||||
def internal_dns
|
||||
'unavailable'
|
||||
end
|
||||
|
||||
def validate!
|
||||
if @region.nil?
|
||||
raise Blimpy::BoxValidationError, "Cannot spin up machine without a set region"
|
||||
end
|
||||
|
||||
if flavor_id(@flavor).nil?
|
||||
raise Blimpy::BoxValidationError, "'#{@flavor}' is not a valid OpenStack tenant name"
|
||||
end
|
||||
end
|
||||
|
||||
def fog
|
||||
@fog ||= begin
|
||||
Fog::Compute.new(:provider => 'openstack', :openstack_tenant => @region)
|
||||
end
|
||||
end
|
||||
|
||||
def flavors
|
||||
@flavors ||= fog.flavors
|
||||
end
|
||||
|
||||
def flavor_id(name)
|
||||
flavors.each do |flavor|
|
||||
return flavor.id if flavor.name == name
|
||||
end
|
||||
nil
|
||||
end
|
||||
|
||||
def prestart
|
||||
allocate_ip
|
||||
end
|
||||
|
||||
def allocate_ip
|
||||
response = fog.allocate_address
|
||||
unless response.status == 200
|
||||
raise Blimpy::UnknownError, "Blimpy was unable to allocate a floating IP address; #{response.inspect}"
|
||||
end
|
||||
|
||||
details = response.body['floating_ip']
|
||||
@floating_ip = FloatingIp.new(details['ip'], details['id'])
|
||||
end
|
||||
|
||||
def associate_ip
|
||||
if floating_ip.nil?
|
||||
raise Blimpy::UnknownError, "Blimpy cannot associate a floating IP until it's been allocated properly!"
|
||||
end
|
||||
response = fog.associate_address(@server.id, floating_ip.address)
|
||||
|
||||
unless response.status == 202
|
||||
raise Blimpy::UnknownError, "Blimpy failed to associate the IP somehow #{response.inspect}"
|
||||
end
|
||||
end
|
||||
|
||||
def predestroy
|
||||
disassociate_ip unless floating_ip.nil?
|
||||
end
|
||||
|
||||
def postdestroy
|
||||
deallocate_ip unless floating_ip.nil?
|
||||
end
|
||||
|
||||
def disassociate_ip
|
||||
fog.disassociate_address(@server.id, floating_ip.address)
|
||||
end
|
||||
|
||||
def deallocate_ip
|
||||
fog.release_address(floating_ip.id)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def import_key
|
||||
material = Blimpy::Keys.public_key
|
||||
begin
|
||||
fog.create_key_pair(Blimpy::Keys.key_name, material)
|
||||
rescue Excon::Errors::Conflict => e
|
||||
end
|
||||
end
|
||||
|
||||
def create_host
|
||||
tags = @tags.merge({:Name => @name, :CreatedBy => 'Blimpy', :BlimpyFleetId => @fleet_id})
|
||||
|
||||
groups = [@group]
|
||||
import_key
|
||||
fog.servers.create(:image_ref => image_id,
|
||||
:flavor_ref => flavor_id(@flavor),
|
||||
:key_name => Blimpy::Keys.key_name,
|
||||
:groups => groups,
|
||||
:name => @name,
|
||||
:tags => tags)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,243 +0,0 @@
|
|||
require 'rubygems'
|
||||
require 'thor'
|
||||
|
||||
require 'blimpy'
|
||||
|
||||
module Blimpy
|
||||
class CLI < Thor
|
||||
BLIMPFILE = File.join(Dir.pwd, 'Blimpfile')
|
||||
|
||||
no_tasks do
|
||||
def ensure_blimpfile
|
||||
unless File.exists? Blimpy::CLI::BLIMPFILE
|
||||
puts 'Please create a Blimpfile in your current directory'
|
||||
exit 1
|
||||
end
|
||||
end
|
||||
|
||||
def load_blimpfile
|
||||
Blimpy.load_file(File.open(BLIMPFILE).read)
|
||||
end
|
||||
|
||||
def box_by_name(name)
|
||||
fleet = load_blimpfile
|
||||
box = nil
|
||||
ship_id = nil
|
||||
data = nil
|
||||
fleet.members.each do |instance_id, instance_data|
|
||||
next unless instance_data[:name] == name
|
||||
ship_id = instance_id
|
||||
data = instance_data
|
||||
break
|
||||
end
|
||||
|
||||
if ship_id.nil?
|
||||
return nil
|
||||
end
|
||||
|
||||
fleet.ships.each do |ship|
|
||||
next unless ship.name == name
|
||||
ship.with_data(ship_id, data)
|
||||
return ship
|
||||
end
|
||||
end
|
||||
|
||||
def current_blimps
|
||||
blimps = Dir["#{Dir.pwd}/.blimpy.d/*.blimp"]
|
||||
return false if blimps.empty?
|
||||
|
||||
data = []
|
||||
blimps.each do |blimp|
|
||||
data << [blimp, YAML.load_file(blimp)]
|
||||
end
|
||||
data
|
||||
end
|
||||
|
||||
def load_fleet
|
||||
ensure_blimpfile
|
||||
begin
|
||||
return load_blimpfile
|
||||
rescue Blimpy::InvalidBlimpFileError => e
|
||||
puts "The Blimpfile is invalid!"
|
||||
puts e.to_s
|
||||
return nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
desc 'start', 'Start up a fleet of blimps'
|
||||
method_options :"dry-run" => :boolean
|
||||
def start
|
||||
fleet = load_fleet
|
||||
|
||||
if fleet.nil?
|
||||
exit 1
|
||||
end
|
||||
|
||||
if options[:'dry-run']
|
||||
puts 'skipping actually starting the fleet'
|
||||
exit 0
|
||||
end
|
||||
|
||||
fleet.start
|
||||
end
|
||||
|
||||
desc 'resume', 'Resume an existing fleet of instances'
|
||||
def resume
|
||||
fleet = load_fleet
|
||||
|
||||
if fleet.nil?
|
||||
exit 1
|
||||
end
|
||||
|
||||
if fleet.members.empty?
|
||||
puts "No fleet running right now, perhaps you should `start` one."
|
||||
exit 1
|
||||
else
|
||||
fleet.resume(fleet.members)
|
||||
end
|
||||
end
|
||||
|
||||
desc 'show', 'Show blimp details for running blimps'
|
||||
method_options :tags => :boolean
|
||||
def show
|
||||
ensure_blimpfile
|
||||
blimps = current_blimps
|
||||
unless blimps
|
||||
puts 'No currently running VMs'
|
||||
exit 0
|
||||
end
|
||||
|
||||
tags_option = options[:tags]
|
||||
blimps.each do |blimp, data|
|
||||
if tags_option
|
||||
tags = nil
|
||||
data[:tags].each do |k,v|
|
||||
if tags.nil?
|
||||
tags = "#{k}=#{v}"
|
||||
elsif
|
||||
tags = "#{tags},#{k}=#{v}"
|
||||
end
|
||||
end
|
||||
puts "#{data[:name]} #{data[:internal_dns]} #{tags}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
desc 'status', 'Show running blimps'
|
||||
def status
|
||||
ensure_blimpfile
|
||||
blimps = current_blimps
|
||||
unless blimps
|
||||
puts 'No currently running VMs'
|
||||
exit 0
|
||||
end
|
||||
|
||||
blimps.each do |blimp, data|
|
||||
instance_id = File.basename(blimp)
|
||||
instance_id = instance_id.split('.blimp').first
|
||||
puts "#{data[:name]} (#{instance_id}) is: online at #{data[:dns]} (#{data[:internal_dns]} internally)"
|
||||
end
|
||||
end
|
||||
|
||||
desc 'destroy', 'Destroy all running blimps'
|
||||
def destroy
|
||||
ensure_blimpfile
|
||||
fleet = Blimpy::Fleet.new
|
||||
fleet.destroy
|
||||
end
|
||||
|
||||
desc 'stop', 'Stop the running blimps'
|
||||
def stop
|
||||
ensure_blimpfile
|
||||
fleet = Blimpy::Fleet.new
|
||||
fleet.stop
|
||||
end
|
||||
|
||||
desc 'init', 'Create a skeleton Blimpfile in the current directory'
|
||||
def init
|
||||
File.open(File.join(Dir.pwd, 'Blimpfile'), 'w') do |f|
|
||||
f.write(
|
||||
"""# vim: ft=ruby
|
||||
# Blimpfile created on #{Time.now}
|
||||
|
||||
Blimpy.fleet do |fleet|
|
||||
fleet.add(:aws) do |ship|
|
||||
ship.name = 'Excelsior'
|
||||
ship.ports = [22, 8080]
|
||||
end
|
||||
end
|
||||
""")
|
||||
end
|
||||
end
|
||||
|
||||
desc 'ssh BLIMP_NAME', 'Log into a running blimp'
|
||||
def ssh(name=nil, *args)
|
||||
ensure_blimpfile
|
||||
unless name.nil?
|
||||
box = box_by_name(name)
|
||||
if box.nil?
|
||||
puts "Could not find a blimp named \"#{name}\""
|
||||
exit 1
|
||||
end
|
||||
else
|
||||
blimps = current_blimps
|
||||
unless blimps
|
||||
puts "No Blimps running!"
|
||||
exit 1
|
||||
end
|
||||
|
||||
blimps.each do |blimp, data|
|
||||
next unless data[:name]
|
||||
box = box_by_name(data[:name])
|
||||
end
|
||||
end
|
||||
|
||||
box.wait_for_sshd
|
||||
box.ssh_into *args
|
||||
end
|
||||
|
||||
desc 'scp BLIMP_NAME FILE_NAME', 'Securely copy FILE_NAME into the blimp'
|
||||
def scp(name, filename, *args)
|
||||
ensure_blimpfile
|
||||
box = box_by_name(name)
|
||||
if box.nil?
|
||||
puts "Could not find a blimp named \"#{name}\""
|
||||
exit 1
|
||||
end
|
||||
box.wait_for_sshd
|
||||
# Pass any extra commands along to the `scp` invocation
|
||||
box.scp_file(filename, '', *ARGV[3..-1])
|
||||
end
|
||||
|
||||
desc 'provision BLIMP_NAME', 'Run the livery again'
|
||||
def provision(name=nil)
|
||||
ensure_blimpfile
|
||||
unless name.nil?
|
||||
box = box_by_name(name)
|
||||
if box.nil?
|
||||
puts "Could not find a blimp named \"#{name}\""
|
||||
exit 1
|
||||
end
|
||||
box.bootstrap
|
||||
else
|
||||
blimps = current_blimps
|
||||
unless blimps
|
||||
puts "No Blimps running!"
|
||||
exit 1
|
||||
end
|
||||
|
||||
blimps.each do |blimp, data|
|
||||
next unless data[:name]
|
||||
box = box_by_name(data[:name])
|
||||
box.bootstrap
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
desc 'version', 'Print the current Blimpy gem version'
|
||||
def version
|
||||
puts Blimpy::VERSION
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,174 +0,0 @@
|
|||
require 'blimpy/helpers/state'
|
||||
require 'blimpy/boxes/aws'
|
||||
require 'blimpy/boxes/openstack'
|
||||
require 'blimpy/boxes/existing'
|
||||
|
||||
module Blimpy
|
||||
class Fleet
|
||||
include Blimpy::Helpers::State
|
||||
|
||||
attr_reader :ships, :id
|
||||
|
||||
def initialize
|
||||
@ships = []
|
||||
@id = Time.now.utc.to_i
|
||||
@airborn = false
|
||||
end
|
||||
|
||||
BOXES = { :aws => Blimpy::Boxes::AWS, :openstack => Blimpy::Boxes::OpenStack, :existing => Blimpy::Boxes::Existing }
|
||||
|
||||
def valid_types
|
||||
BOXES.keys
|
||||
end
|
||||
|
||||
def add(box_type, &block)
|
||||
unless valid_types.include? box_type
|
||||
raise Blimpy::InvalidShipException
|
||||
end
|
||||
if block.nil?
|
||||
return false
|
||||
end
|
||||
|
||||
box = BOXES[box_type]
|
||||
|
||||
if box.nil?
|
||||
return false
|
||||
else
|
||||
box = box.new()
|
||||
end
|
||||
box.fleet_id = @id
|
||||
@ships << box
|
||||
block.call(box)
|
||||
end
|
||||
|
||||
def state_file
|
||||
File.join(state_folder, 'manifest')
|
||||
end
|
||||
|
||||
def save!
|
||||
File.open(state_file, 'w') do |f|
|
||||
f.write("id=#{id}\n")
|
||||
end
|
||||
end
|
||||
|
||||
def resume(instances)
|
||||
boxes = []
|
||||
print '>> Resuming: '
|
||||
instances.each do |instance_id, instance_data|
|
||||
print "#{instance_data[:name]},"
|
||||
box = Blimpy::Box.from_instance_id(instance_id, instance_data)
|
||||
box.resume
|
||||
boxes << box
|
||||
end
|
||||
|
||||
boxes.each do |box|
|
||||
box.wait_for_state('running') { print '.' }
|
||||
end
|
||||
puts
|
||||
end
|
||||
|
||||
def animate
|
||||
buffer ="""
|
||||
_..--=--..._
|
||||
.-' '-. .-.
|
||||
/.' '.\\/ /
|
||||
|=- B L I M P Y -=| (
|
||||
\\'. .'/\\ \\
|
||||
'-.,_____ _____.-' '-'
|
||||
[_____]=+ ~ ~"""
|
||||
frames = [
|
||||
'x~ ',
|
||||
'x ~ ',
|
||||
'+~ ~ ',
|
||||
'+ ~ ~',
|
||||
'+ ~ ',
|
||||
'x ~',
|
||||
]
|
||||
|
||||
print buffer
|
||||
$stdout.flush
|
||||
until @airborn do
|
||||
frames.each do |frame|
|
||||
# Reset every frame
|
||||
5.times { print "\b" }
|
||||
print frame
|
||||
$stdout.flush
|
||||
sleep 0.2
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def start
|
||||
instances = members
|
||||
unless instances.empty?
|
||||
return resume(instances)
|
||||
end
|
||||
|
||||
# Make sure all our ships are valid first!
|
||||
@ships.each do |host|
|
||||
host.validate!
|
||||
end
|
||||
|
||||
Thread.new do
|
||||
animate
|
||||
end
|
||||
|
||||
@ships.each do |host|
|
||||
host.start
|
||||
end
|
||||
|
||||
@ships.each do |host|
|
||||
host.wait_for_state('running') { }
|
||||
@airborn = true
|
||||
print "\n"
|
||||
puts ">> #{host.name} online at: #{host.dns}"
|
||||
host.online!
|
||||
if host.provision_on_start
|
||||
host.bootstrap
|
||||
puts
|
||||
end
|
||||
end
|
||||
|
||||
save!
|
||||
end
|
||||
|
||||
def members
|
||||
instances = []
|
||||
Dir["#{Dir.pwd}/.blimpy.d/*.blimp"].each do |d|
|
||||
filename = File.basename(d)
|
||||
instance_id = filename.split('.blimp').first
|
||||
instance_data = YAML.load_file(d)
|
||||
instances << [instance_id, instance_data]
|
||||
end
|
||||
instances
|
||||
end
|
||||
|
||||
def stop
|
||||
print '>> Stopping: '
|
||||
boxes = []
|
||||
|
||||
members.each do |instance_id, instance_data|
|
||||
box = Blimpy::Box.from_instance_id(instance_id, instance_data)
|
||||
print "#{instance_data[:name]},"
|
||||
box.stop
|
||||
boxes << box
|
||||
end
|
||||
|
||||
boxes.each do |box|
|
||||
box.wait_for_state('stopped') { print '.' }
|
||||
end
|
||||
puts
|
||||
end
|
||||
|
||||
def destroy
|
||||
members.each do |instance_id, instance_data|
|
||||
box = Blimpy::Box.from_instance_id(instance_id, instance_data)
|
||||
box.destroy
|
||||
end
|
||||
|
||||
if File.exists? state_file
|
||||
File.unlink(state_file)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,20 +0,0 @@
|
|||
module Blimpy
|
||||
module Helpers
|
||||
module State
|
||||
def state_folder
|
||||
File.join(Dir.pwd, '.blimpy.d')
|
||||
end
|
||||
|
||||
def ensure_state_folder
|
||||
unless File.exist? state_folder
|
||||
Dir.mkdir(state_folder)
|
||||
end
|
||||
end
|
||||
|
||||
def state_file
|
||||
raise NotImplementedError, '#state_file should be implemented in a consumer'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
require 'socket'
|
||||
|
||||
module Blimpy
|
||||
module Keys
|
||||
def self.public_key
|
||||
filename = File.expand_path('~/.ssh/id_rsa.pub')
|
||||
unless File.exists? filename
|
||||
filename = File.expand_path('~/.ssh/id_dsa.pub')
|
||||
unless File.exists? filename
|
||||
raise Blimpy::SSHKeyNotFoundError, 'Expected either ~/.ssh/id_rsa.pub or ~/.ssh/id_dsa.pub but found neither'
|
||||
end
|
||||
end
|
||||
|
||||
File.open(filename, 'r').read
|
||||
end
|
||||
|
||||
def self.key_name
|
||||
safe_hostname = Socket.gethostname.gsub('.', '-')
|
||||
"Blimpy-#{ENV['USER']}-#{safe_hostname}"
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,36 +0,0 @@
|
|||
require 'rubygems'
|
||||
require 'zlib'
|
||||
require 'archive/tar/minitar'
|
||||
|
||||
require 'blimpy/livery/cwd'
|
||||
require 'blimpy/livery/puppet'
|
||||
|
||||
module Blimpy
|
||||
module Livery
|
||||
def self.tarball_directory(directory)
|
||||
if directory.nil? || !(File.directory? directory)
|
||||
raise ArgumentError, "The argument '#{directory}' doesn't appear to be a directory"
|
||||
end
|
||||
|
||||
directory = File.expand_path(directory)
|
||||
short_name = File.basename(directory)
|
||||
|
||||
tarball = nil
|
||||
Dir.chdir(File.expand_path(directory + '/../')) do
|
||||
tarball = self.gzip_for_directory(short_name, '/tmp') do |tgz|
|
||||
Archive::Tar::Minitar.pack(short_name, tgz)
|
||||
end
|
||||
end
|
||||
tarball
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def self.gzip_for_directory(directory, root)
|
||||
filename = File.join(root, "#{directory}.tar.gz")
|
||||
yield Zlib::GzipWriter.new(File.open(filename, 'wb'))
|
||||
filename
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -1,64 +0,0 @@
|
|||
|
||||
module Blimpy
|
||||
module Livery
|
||||
class Base
|
||||
def preflight(*args)
|
||||
end
|
||||
|
||||
def flight(*args)
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def postflight(*args)
|
||||
end
|
||||
|
||||
def rsync_excludes
|
||||
['.git', '.svn', '.blimpy.d']
|
||||
end
|
||||
|
||||
def rsync_command
|
||||
excludes = rsync_excludes.map { |x| "--exclude=#{x}" }
|
||||
|
||||
if File.exists? '.blimpignore'
|
||||
excludes << '--exclude-from=.blimpignore'
|
||||
end
|
||||
|
||||
['rsync',
|
||||
'-avL',
|
||||
'-e',
|
||||
'ssh -o StrictHostKeyChecking=no'] + excludes
|
||||
end
|
||||
|
||||
def can_rsync?(box)
|
||||
@can_rsync ||= box.ssh_into('-q', 'which rsync > /dev/null')
|
||||
end
|
||||
|
||||
def sync_to(box)
|
||||
if can_rsync? box
|
||||
command = rsync_command + ['.', "#{box.username}@#{box.dns}:#{dir_name}/"]
|
||||
box.run_command(*command)
|
||||
else
|
||||
puts "Remote host has no rsync(1), falling back to copying a full tarball over"
|
||||
tarball = Blimpy::Livery.tarball_directory(livery_root)
|
||||
box.scp_file(tarball)
|
||||
# HAXX
|
||||
basename = File.basename(tarball)
|
||||
box.ssh_into("tar -zxf #{basename} && cd #{dir_name}")
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
def livery_root
|
||||
Dir.pwd
|
||||
end
|
||||
|
||||
def dir_name
|
||||
File.basename(livery_root)
|
||||
end
|
||||
|
||||
def setup_on(box)
|
||||
sync_to(box)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,33 +0,0 @@
|
|||
require 'blimpy/livery/base'
|
||||
|
||||
module Blimpy
|
||||
module Livery
|
||||
class CWD < Base
|
||||
def script
|
||||
'bootstrap.sh'
|
||||
end
|
||||
|
||||
def preflight(box)
|
||||
box.scp_file(bootstrap_script, dir_name)
|
||||
end
|
||||
|
||||
def use_sudo?(box)
|
||||
box.username != 'root'
|
||||
end
|
||||
|
||||
def flight(box)
|
||||
run_sudo = 'sudo'
|
||||
|
||||
unless use_sudo?(box)
|
||||
run_sudo = ''
|
||||
end
|
||||
|
||||
box.ssh_into("cd #{dir_name} && #{run_sudo} BLIMPY_SHIPNAME=#{box.name} ./#{script}")
|
||||
end
|
||||
|
||||
def bootstrap_script
|
||||
File.join(livery_root, script)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,69 +0,0 @@
|
|||
require 'blimpy/livery/base'
|
||||
require 'blimpy/livery/cwd'
|
||||
|
||||
module Blimpy
|
||||
module Livery
|
||||
class Puppet < CWD
|
||||
attr_accessor :module_path, :manifest_path, :options
|
||||
|
||||
def initialize(*args)
|
||||
super
|
||||
@module_path = './modules'
|
||||
@manifest_path = 'manifests/site.pp'
|
||||
@options = '--verbose'
|
||||
@puppet_exists = false
|
||||
end
|
||||
|
||||
def script
|
||||
'puppet.sh'
|
||||
end
|
||||
|
||||
def preflight(box)
|
||||
# If we find Puppet in our default path, we don't really need to send
|
||||
# the bootstrap script again
|
||||
@puppet_exists = box.ssh_into('which puppet > /dev/null')
|
||||
unless @puppet_exists
|
||||
super(box)
|
||||
end
|
||||
|
||||
unless box.ssh_into("test -f #{dir_name}/gempath.sh")
|
||||
gemhelper = File.expand_path(File.dirname(__FILE__) + "/../../../scripts/gempath.sh")
|
||||
box.scp_file(gemhelper, dir_name)
|
||||
end
|
||||
end
|
||||
|
||||
def flight(box)
|
||||
unless @puppet_exists
|
||||
# This should get our puppet.sh bootstrap script run
|
||||
super(box)
|
||||
end
|
||||
|
||||
# At this point we should be safe to actually invoke Puppet
|
||||
command = "puppet apply --modulepath=#{module_path} #{options} #{manifest_path}"
|
||||
|
||||
run_sudo = ''
|
||||
run_sudo = 'sudo' if use_sudo?(box)
|
||||
|
||||
box.ssh_into("cd #{dir_name} && chmod 755 ./gempath.sh && #{run_sudo} ./gempath.sh #{command}")
|
||||
end
|
||||
|
||||
def postflight(box)
|
||||
end
|
||||
|
||||
def bootstrap_script
|
||||
File.expand_path(File.dirname(__FILE__) + "/../../../scripts/#{script}")
|
||||
end
|
||||
|
||||
def self.configure(&block)
|
||||
if block.nil?
|
||||
raise Blimpy::InvalidLiveryException, "Puppet livery must be given a block in order to configure itself"
|
||||
end
|
||||
instance = self.new
|
||||
yield instance
|
||||
instance
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,45 +0,0 @@
|
|||
require 'set'
|
||||
require 'zlib'
|
||||
|
||||
module Blimpy
|
||||
module SecurityGroups
|
||||
def self.group_id(ports)
|
||||
if ports.nil? or ports.empty?
|
||||
return nil
|
||||
end
|
||||
|
||||
unless ports.is_a? Set
|
||||
ports = Set.new(ports)
|
||||
end
|
||||
|
||||
# Lolwut, #hash is inconsistent between ruby processes
|
||||
"Blimpy-#{Zlib.crc32(ports.inspect)}"
|
||||
end
|
||||
|
||||
def self.ensure_group(fog, ports)
|
||||
name = group_id(ports)
|
||||
|
||||
exists = fog.security_groups.get(name)
|
||||
|
||||
if exists.nil?
|
||||
name = create_group(fog, ports)
|
||||
end
|
||||
name
|
||||
end
|
||||
|
||||
def self.create_group(fog, ports)
|
||||
name = group_id(ports)
|
||||
group = fog.security_groups.create(:name => name,
|
||||
:description => "Custom Blimpy security group for #{ports.to_a}")
|
||||
|
||||
unless ports.is_a? Set
|
||||
ports = Set.new(ports)
|
||||
end
|
||||
|
||||
ports.each do |port|
|
||||
group.authorize_port_range(port .. port)
|
||||
end
|
||||
name
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,3 +0,0 @@
|
|||
module Blimpy
|
||||
VERSION = "0.7.0"
|
||||
end
|
|
@ -0,0 +1 @@
|
|||
{"note":"Don't delete this file! It's used internally to help with page regeneration.","name":"Blimpy","google":"","tagline":"A Vagrant-inspired tool for managing multiple machines in the cloud","body":"### Welcome to GitHub Pages.\r\nThis automatic page generator is the easiest way to create beautiful pages for all of your projects. Author your page content here using GitHub Flavored Markdown, select a template crafted by a designer, and publish. After your page is generated, you can check out the new branch:\r\n\r\n```\r\n$ cd your_repo_root/repo_name\r\n$ git fetch origin\r\n$ git checkout gh-pages\r\n```\r\n\r\nIf you're using the GitHub for Mac, simply sync your repository and you'll see the new branch.\r\n\r\n### Designer Templates\r\nWe've crafted some handsome templates for you to use. Go ahead and continue to layouts to browse through them. You can easily go back to edit your page before publishing. After publishing your page, you can revisit the page generator and switch to another theme. Your Page content will be preserved if it remained markdown format.\r\n\r\n### Rather Drive Stick?\r\nIf you prefer to not use the automatic generator, push a branch named `gh-pages` to your repository to create a page manually. In addition to supporting regular HTML content, GitHub Pages support Jekyll, a simple, blog aware static site generator written by our own Tom Preston-Werner. Jekyll makes it easy to create site-wide headers and footers without having to copy them across every page. It also offers intelligent blog support and other advanced templating features.\r\n\r\n### Authors and Contributors\r\nYou can @mention a GitHub username to generate a link to their profile. The resulting `<a>` element will link to the contributor's GitHub Profile. For example: In 2007, Chris Wanstrath (@defunkt), PJ Hyett (@pjhyett), and Tom Preston-Werner (@mojombo) founded GitHub.\r\n\r\n### Support or Contact\r\nHaving trouble with Pages? Check out the documentation at http://help.github.com/pages or contact support@github.com and we’ll help you sort it out."}
|
|
@ -1,6 +0,0 @@
|
|||
#!/bin/sh
|
||||
|
||||
export PATH=/var/lib/gems/1.8/bin:$PATH
|
||||
|
||||
exec $@
|
||||
|
|
@ -1,67 +0,0 @@
|
|||
#!/bin/sh -x
|
||||
|
||||
OSNAME=`uname`
|
||||
LINUXFLAVOR=`lsb_release -is`
|
||||
PROG=`basename $0`
|
||||
|
||||
Info () {
|
||||
echo "$PROG : $*"
|
||||
}
|
||||
|
||||
Fatal () {
|
||||
echo "$PROG : FATAL: $*"
|
||||
exit 1
|
||||
}
|
||||
|
||||
Info "Begin"
|
||||
if [ "${OSNAME}" = "FreeBSD" ]; then
|
||||
which pkg > /dev/null 2>&1
|
||||
if [ $? -ne 0 ]; then
|
||||
Info "pkgng is not installed!"
|
||||
ftp -V ftp://anonymous:ec2@ftp.freebsd.org/pub/FreeBSD/ports/amd64/packages-9-stable/Latest/pkg.tbz
|
||||
pkg_add ./pkg.tbz
|
||||
echo "PACKAGESITE : http://pkgbeta.freebsd.org/freebsd-9-amd64/latest/" > /usr/local/etc/pkg.conf
|
||||
pkg update -q
|
||||
# Update pkgng itself
|
||||
pkg install -y pkg
|
||||
# Install rsync(1) so we don't have to fall back to tar(1)+scp(1) ever again
|
||||
pkg install -y rsync
|
||||
# Install puppet so we can get that up and running
|
||||
pkg install -y puppet
|
||||
fi
|
||||
else
|
||||
case "$LINUXFLAVOR" in
|
||||
RedHat*) # Extract the major version number
|
||||
VERSION=`lsb_release -ids | sed 's/.*release \([0-9]\).*/\1/'`
|
||||
if [ $VERSION -eq 5 ]; then
|
||||
rpm -ivh http://yum.puppetlabs.com/el/5/products/i386/puppetlabs-release-5-6.noarch.rpm
|
||||
elif [ $VERSION -eq 6 ]; then
|
||||
rpm -ivh http://yum.puppetlabs.com/el/6/products/i386/puppetlabs-release-6-6.noarch.rpm
|
||||
else
|
||||
Fatal "Unsupported Red Hat release: $VERSION"
|
||||
fi
|
||||
# Disable EPEL if for some reason it is configured on this host
|
||||
# as we don't want to get some old Puppet from there.
|
||||
if [ -f /etc/yum.repos.d/epel.repo ]; then
|
||||
yum install -y puppet --disablerepo=epel
|
||||
else
|
||||
yum install -y puppet
|
||||
fi
|
||||
;;
|
||||
Ubuntu) export PATH=/var/lib/gems/1.8/bin:/usr/local/bin:$PATH
|
||||
which puppet > /dev/null 2>&1
|
||||
if [ $? -ne 0 ]; then
|
||||
# CODENAME is 'precise', 'oneiric', etc.
|
||||
CODENAME=`lsb_release -c | awk '{print $2}'`
|
||||
REPO_DEB="puppetlabs-release-${CODENAME}.deb"
|
||||
wget --quiet http://apt.puppetlabs.com/${REPO_DEB} || Fatal "Could not retrieve http://apt.puppetlabs.com/${REPO_DEB}"
|
||||
dpkg -i ${REPO_DEB} || Fatal "Could not install Puppet repo source '${REPO_DEB}'"
|
||||
rm -f ${REPO_DEB}
|
||||
apt-get update
|
||||
apt-get -qqy install puppet-common=2.7.* puppet=2.7.* hiera=1.1.* hiera-puppet=1.0* || Fatal "Could not install Puppet"
|
||||
fi
|
||||
;;
|
||||
*) Fatal "Unsupported Linux flavor: $LINUXFLAVOR"
|
||||
;;
|
||||
esac
|
||||
fi
|
|
@ -1,198 +0,0 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Blimpy::Box do
|
||||
|
||||
describe '#livery' do
|
||||
it 'should be unset by default' do
|
||||
subject.livery.should be nil
|
||||
end
|
||||
end
|
||||
|
||||
describe '#group' do
|
||||
it 'should be unset by default' do
|
||||
subject.group.should be nil
|
||||
end
|
||||
end
|
||||
|
||||
describe '#name' do
|
||||
it 'should be "Unnamed Box" by default' do
|
||||
subject.name.should == 'Unnamed Box'
|
||||
end
|
||||
end
|
||||
|
||||
describe '#validate!' do
|
||||
it 'should raise a NotImplementedError, since this should be defined by subclasses' do
|
||||
expect {
|
||||
subject.validate!
|
||||
}.to raise_error(NotImplementedError)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#create_host' do
|
||||
it 'should raise a NotImplementedError' do
|
||||
# lol private
|
||||
expect {
|
||||
subject.send(:create_host)
|
||||
}.to raise_error(NotImplementedError)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#fog' do
|
||||
it 'should raise NotImplementedError' do
|
||||
expect {
|
||||
subject.fog.should be_nil
|
||||
}.to raise_error(NotImplementedError)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#with_data' do
|
||||
context 'with valid data' do
|
||||
let(:data) do
|
||||
{'type' => 'AWS',
|
||||
'name' => 'Fakey',
|
||||
'region' => 'us-west-2',
|
||||
'dns' => 'ec2-50-112-24-134.us-west-2.compute.amazonaws.com',
|
||||
'internal_dns' => 'ip-10-252-73-124.us-west-2.compute.internal'}
|
||||
end
|
||||
let(:ship_id) { 'i-deadbeef' }
|
||||
|
||||
before :each do
|
||||
subject.with_data(ship_id, data)
|
||||
end
|
||||
|
||||
it 'should set the dns_name' do
|
||||
subject.dns.should == data['dns']
|
||||
end
|
||||
|
||||
it 'should set the region' do
|
||||
subject.region.should == data['region']
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a mocked server' do
|
||||
let(:server_id) { 'id-0xdeadbeef' }
|
||||
let(:server) do
|
||||
server = double('Fog::Compute::AWS::Server')
|
||||
server.stub(:id).and_return(server_id)
|
||||
server.stub(:dns_name).and_return('test')
|
||||
server.stub(:private_dns_name).and_return('test')
|
||||
server
|
||||
end
|
||||
|
||||
describe '#start' do
|
||||
before :each do
|
||||
# Mocking out #create_host so we don't actually create EC2 instance
|
||||
Blimpy::Box.any_instance.should_receive(:create_host).and_return(server)
|
||||
Blimpy::Box.any_instance.should_receive(:ensure_state_folder).and_return(true)
|
||||
end
|
||||
|
||||
it 'should create a state file' do
|
||||
subject.stub(:state_file).and_return('fake-state-file')
|
||||
File.should_receive(:open).with('fake-state-file', 'w')
|
||||
subject.start
|
||||
end
|
||||
end
|
||||
|
||||
describe '#stop' do
|
||||
before :each do
|
||||
server.should_receive(:stop)
|
||||
end
|
||||
|
||||
subject { Blimpy::Box.new(server) }
|
||||
|
||||
it 'should stop the Box' do
|
||||
subject.stop
|
||||
end
|
||||
end
|
||||
|
||||
describe '#destroy' do
|
||||
before :each do
|
||||
server.should_receive(:destroy)
|
||||
end
|
||||
subject { Blimpy::Box.new(server) }
|
||||
|
||||
it 'should remove its state file' do
|
||||
subject.should_receive(:state_file).and_return('fake-state-file')
|
||||
File.should_receive(:unlink).with('fake-state-file')
|
||||
subject.destroy
|
||||
end
|
||||
end
|
||||
|
||||
describe '#from_instance_id' do
|
||||
let(:fog) { double('Fog::Compute') }
|
||||
|
||||
it 'should fail if no "type" exists' do
|
||||
result = Blimpy::Box.from_instance_id('someid', {})
|
||||
result.should be_nil
|
||||
end
|
||||
|
||||
it 'should fail if the "type" is not a defined Box class' do
|
||||
result = Blimpy::Box.from_instance_id('someid', {:type => 'MAGIC'})
|
||||
result.should be_nil
|
||||
end
|
||||
|
||||
context 'with an AWS box type' do
|
||||
before :each do
|
||||
fog.stub_chain(:servers, :get).and_return(server)
|
||||
Fog::Compute.should_receive(:new).and_return(fog)
|
||||
end
|
||||
|
||||
it 'should create a new AWS Box instance' do
|
||||
result = Blimpy::Box.from_instance_id('someid', {:type => 'AWS'})
|
||||
result.should be_instance_of Blimpy::Boxes::AWS
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#bootstrap_livery' do
|
||||
let(:livery) { double('Mock-livery') }
|
||||
|
||||
it 'should raise an error if the livery is a symbol (old style)' do
|
||||
subject.livery = :deprecated
|
||||
expect {
|
||||
subject.bootstrap_livery
|
||||
}.to raise_error(Blimpy::InvalidLiveryException)
|
||||
end
|
||||
|
||||
it 'should invoke the correct livery methods' do
|
||||
subject.livery = livery
|
||||
livery.should_receive(:setup_on).with(subject)
|
||||
livery.should_receive(:preflight).with(subject)
|
||||
livery.should_receive(:flight).with(subject)
|
||||
livery.should_receive(:postflight).with(subject)
|
||||
|
||||
subject.bootstrap_livery
|
||||
end
|
||||
end
|
||||
|
||||
describe '#bootstrap' do
|
||||
context 'when livery is not defined' do
|
||||
before :each do
|
||||
subject.livery = nil
|
||||
end
|
||||
|
||||
it 'should not call any of the bootstrap methods' do
|
||||
subject.should_receive(:wait_for_sshd).never
|
||||
subject.should_receive(:bootstrap_livery).never
|
||||
subject.bootstrap
|
||||
end
|
||||
end
|
||||
|
||||
context 'when a livery is defined' do
|
||||
before :each do
|
||||
subject.should_receive(:wait_for_sshd)
|
||||
subject.should_receive(:bootstrap_livery)
|
||||
end
|
||||
|
||||
context 'as a class object' do
|
||||
it 'should instantiate the livery' do
|
||||
subject.livery = Blimpy::Livery::CWD
|
||||
subject.livery.should_receive(:new)
|
||||
subject.bootstrap
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,78 +0,0 @@
|
|||
require 'spec_helper'
|
||||
require 'blimpy/boxes/aws'
|
||||
|
||||
describe Blimpy::Boxes::AWS do
|
||||
describe '#image_id' do
|
||||
it 'should be the Ubuntu 10.04 AMI ID by default' do
|
||||
subject.image_id.should == Blimpy::Boxes::AWS::DEFAULT_IMAGE_ID
|
||||
end
|
||||
end
|
||||
|
||||
describe '#allowed_regions' do
|
||||
it 'should be an Array' do
|
||||
subject.allowed_regions.should be_instance_of Array
|
||||
end
|
||||
it 'should not be empty' do
|
||||
subject.allowed_regions.should_not be_empty
|
||||
end
|
||||
end
|
||||
|
||||
describe '#region' do
|
||||
it 'should return the default region' do
|
||||
subject.region.should == 'us-west-2'
|
||||
end
|
||||
end
|
||||
|
||||
describe '#region=' do
|
||||
it 'should raise an InvalidRegionError if the region is not allowed' do
|
||||
expect {
|
||||
subject.region = :elbonia
|
||||
}.to raise_error(Blimpy::InvalidRegionError)
|
||||
end
|
||||
|
||||
it 'should change the value of @region' do
|
||||
subject.region = 'us-east-1'
|
||||
subject.region.should == 'us-east-1'
|
||||
end
|
||||
end
|
||||
|
||||
describe '#validate!' do
|
||||
let(:security_group) do
|
||||
group = double('Fog::Compute::AWS::SecurityGroup')
|
||||
group.stub(:name).and_return('MockedGroup')
|
||||
group
|
||||
end
|
||||
let (:fog) { mock('Fog::Compute::AWS') }
|
||||
|
||||
before :each do
|
||||
subject.stub(:fog).and_return(fog)
|
||||
end
|
||||
|
||||
it 'should raise if no region has been set' do
|
||||
expect {
|
||||
# This may be a silly test
|
||||
subject.instance_variable_set(:@region, nil)
|
||||
subject.validate!
|
||||
}.to raise_error(Blimpy::BoxValidationError)
|
||||
end
|
||||
|
||||
context 'with invalid settings' do
|
||||
it 'should raise with a bad security group' do
|
||||
fog.stub_chain(:security_groups, :get).and_return(nil)
|
||||
expect {
|
||||
subject.validate!
|
||||
}.to raise_error(Blimpy::BoxValidationError)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with valid settings' do
|
||||
it 'should validate with a good security group' do
|
||||
fog.stub_chain(:security_groups, :get).with('MockedGroup').and_return(security_group)
|
||||
expect {
|
||||
subject.group = 'MockedGroup'
|
||||
subject.validate!
|
||||
}.not_to raise_error(Blimpy::BoxValidationError)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,307 +0,0 @@
|
|||
require 'spec_helper'
|
||||
require 'blimpy/boxes/openstack'
|
||||
|
||||
shared_context 'valid floating_ip' do
|
||||
let(:floating) do
|
||||
floating = double('FloatingIp')
|
||||
floating.stub(:address).and_return('127.0.0.1')
|
||||
floating.stub(:id).and_return(7)
|
||||
floating
|
||||
end
|
||||
before :each do
|
||||
subject.stub(:floating_ip).and_return(floating)
|
||||
end
|
||||
end
|
||||
|
||||
describe Blimpy::Boxes::OpenStack do
|
||||
describe '#image_id' do
|
||||
it 'should be nil by default' do
|
||||
subject.image_id.should be_nil
|
||||
end
|
||||
end
|
||||
|
||||
describe '#username' do
|
||||
it 'should be "ubuntu" by default' do
|
||||
subject.username.should == 'ubuntu'
|
||||
end
|
||||
end
|
||||
|
||||
describe '#flavor' do
|
||||
it 'should be "m1.tiny" by default' do
|
||||
subject.flavor.should == 'm1.tiny'
|
||||
end
|
||||
end
|
||||
|
||||
describe '#group' do
|
||||
it 'should be "default" by default' do
|
||||
subject.group.should == 'default'
|
||||
end
|
||||
end
|
||||
|
||||
describe '#allowed_regions' do
|
||||
it 'should be nil by default' do
|
||||
subject.allowed_regions.should be_nil
|
||||
end
|
||||
end
|
||||
|
||||
describe '#region=' do
|
||||
it 'should not raise an InvalidRegionError if no allowed_regions exist' do
|
||||
subject.stub(:allowed_regions).and_return(nil)
|
||||
subject.region = :elbonia
|
||||
subject.region.should == :elbonia
|
||||
end
|
||||
end
|
||||
|
||||
describe '#ports=' do
|
||||
it 'should be disabled currently' do
|
||||
expect {
|
||||
subject.ports = [22, 8080]
|
||||
}.to raise_error(Blimpy::UnsupportedFeatureException)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#validate!' do
|
||||
it 'should raise a validation error if there isn\'t a region' do
|
||||
subject.region = nil
|
||||
expect {
|
||||
subject.validate!
|
||||
}.to raise_error(Blimpy::BoxValidationError)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with mocked flavors' do
|
||||
let(:fog) { double('Fog') }
|
||||
let(:flavors) do
|
||||
flavor = double('Fog::Compute::OpenStack::Flavor')
|
||||
flavor.stub(:id).and_return('1')
|
||||
flavor.stub(:name).and_return('m1.tiny')
|
||||
[flavor]
|
||||
end
|
||||
|
||||
before :each do
|
||||
fog.should_receive(:flavors).and_return(flavors)
|
||||
subject.should_receive(:fog).and_return(fog)
|
||||
end
|
||||
|
||||
describe '#flavors' do
|
||||
it 'should pull the list of flavors from Fog' do
|
||||
subject.flavors.should == flavors
|
||||
end
|
||||
end
|
||||
|
||||
describe '#flavor_id' do
|
||||
it 'should filter out the right flavor' do
|
||||
subject.flavor_id('m1.tiny').should == '1'
|
||||
end
|
||||
|
||||
it 'should return nil if the flavor doesn\'t exist' do
|
||||
subject.flavor_id('invalid').should be_nil
|
||||
end
|
||||
end
|
||||
|
||||
describe '#validate!' do
|
||||
it 'should raise a validation error with an invalid flavor' do
|
||||
subject.flavor = 'invalid'
|
||||
subject.region = 'test'
|
||||
expect {
|
||||
subject.validate!
|
||||
}.to raise_error(Blimpy::BoxValidationError)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe '#prestart' do
|
||||
it 'should allocate an IP' do
|
||||
subject.should_receive(:allocate_ip)
|
||||
subject.prestart
|
||||
end
|
||||
end
|
||||
|
||||
describe '#allocate_ip' do
|
||||
let(:fog) { double('Fog') }
|
||||
|
||||
before :each do
|
||||
subject.stub(:fog).and_return(fog)
|
||||
end
|
||||
|
||||
context 'with a bad response' do
|
||||
let(:response) do
|
||||
response = double('Excon::Response')
|
||||
response.stub(:status).and_return(500)
|
||||
response
|
||||
end
|
||||
|
||||
it 'should raise an error if we cannot allocate the IP' do
|
||||
fog.should_receive(:allocate_address).and_return(response)
|
||||
expect {
|
||||
subject.allocate_ip
|
||||
}.to raise_error(Blimpy::UnknownError)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a good response' do
|
||||
let(:response) do
|
||||
response = double('Excon::Response')
|
||||
response.stub(:status).and_return(200)
|
||||
response.stub(:body).and_return({"floating_ip"=>
|
||||
{"instance_id"=>nil,
|
||||
"ip"=>"10.38.12.109",
|
||||
"fixed_ip"=>nil,
|
||||
"id"=>109,
|
||||
"pool"=>"nova"}})
|
||||
response
|
||||
end
|
||||
|
||||
it 'should allocate and store the floating IP info' do
|
||||
fog.should_receive(:allocate_address).and_return(response)
|
||||
|
||||
subject.allocate_ip
|
||||
subject.floating_ip.address.should == '10.38.12.109'
|
||||
subject.floating_ip.id.should == 109
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#associate_ip' do
|
||||
let(:fog) { double('Fog') }
|
||||
include_context 'valid floating_ip'
|
||||
let(:server) do
|
||||
server = double('Fog::Compute::Server')
|
||||
server.stub(:id).and_return(server_id)
|
||||
server
|
||||
end
|
||||
let(:server_id) { 'fake-id' }
|
||||
|
||||
subject { described_class.new(server) }
|
||||
|
||||
before :each do
|
||||
subject.stub(:fog).and_return(fog)
|
||||
end
|
||||
|
||||
it 'should raise an exception if a floating IP hasn\'t been created' do
|
||||
subject.stub(:floating_ip).and_return(nil)
|
||||
expect {
|
||||
subject.associate_ip
|
||||
}.to raise_error(Blimpy::UnknownError)
|
||||
end
|
||||
|
||||
context 'with a good response' do
|
||||
let(:response) do
|
||||
response = double('Excon::Response')
|
||||
response.stub(:status).and_return(202)
|
||||
response
|
||||
end
|
||||
|
||||
it 'should associate the right IP to the right instance ID' do
|
||||
fog.should_receive(:associate_address).with(server_id, floating.address).and_return(response)
|
||||
subject.associate_ip
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a bad response' do
|
||||
let(:response) do
|
||||
response = double('Excon::Response')
|
||||
response.stub(:status).and_return(500)
|
||||
response
|
||||
end
|
||||
|
||||
it 'should raise an error' do
|
||||
subject.stub(:floating_ip).and_return(floating)
|
||||
fog.should_receive(:associate_address).with(server_id, floating.address).and_return(response)
|
||||
expect {
|
||||
subject.associate_ip
|
||||
}.to raise_error(Blimpy::UnknownError)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#predestroy' do
|
||||
context 'if the server has a floating IP' do
|
||||
include_context 'valid floating_ip'
|
||||
|
||||
it 'should disasscoaite it' do
|
||||
subject.should_receive(:disassociate_ip)
|
||||
subject.predestroy
|
||||
end
|
||||
end
|
||||
|
||||
context 'if the server has no floating IP' do
|
||||
it 'should not try to disassociate it' do
|
||||
subject.should_receive(:disassociate_ip).never
|
||||
subject.predestroy
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#postdestroy' do
|
||||
context 'if the server has a floating IP' do
|
||||
include_context 'valid floating_ip'
|
||||
|
||||
it 'should deallocate the IP' do
|
||||
subject.should_receive(:deallocate_ip)
|
||||
subject.postdestroy
|
||||
end
|
||||
end
|
||||
|
||||
context 'if the server has no floating IP' do
|
||||
it 'should not try to deallocate the IP' do
|
||||
subject.should_receive(:deallocate_ip).never
|
||||
subject.postdestroy
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#disassociate_ip' do
|
||||
let(:fog) { double('Fog') }
|
||||
include_context 'valid floating_ip'
|
||||
let(:server) do
|
||||
server = double('Fog::Compute::Server')
|
||||
server.stub(:id).and_return(server_id)
|
||||
server
|
||||
end
|
||||
let(:server_id) { 'fake-id' }
|
||||
|
||||
subject { described_class.new(server) }
|
||||
|
||||
before :each do
|
||||
subject.stub(:fog).and_return(fog)
|
||||
end
|
||||
|
||||
it 'should disassociate the right IP to the right instance ID' do
|
||||
fog.should_receive(:disassociate_address).with(server_id, floating.address)
|
||||
subject.disassociate_ip
|
||||
end
|
||||
end
|
||||
|
||||
describe '#deallocate_ip' do
|
||||
let(:fog) { double('Fog') }
|
||||
include_context 'valid floating_ip'
|
||||
|
||||
before :each do
|
||||
subject.stub(:fog).and_return(fog)
|
||||
end
|
||||
|
||||
context 'with a good response' do
|
||||
let(:response) do
|
||||
response = double('Excon::Response')
|
||||
response.stub(:status).and_return(202)
|
||||
response
|
||||
end
|
||||
|
||||
it 'should release the right IP by floating_ip ID' do
|
||||
fog.should_receive(:release_address).with(floating.id).and_return(response)
|
||||
subject.deallocate_ip
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe Blimpy::Boxes::OpenStack::FloatingIp do
|
||||
subject do
|
||||
described_class.new('127.0.0.1', 1)
|
||||
end
|
||||
|
||||
it { should respond_to(:to_yaml) }
|
||||
end
|
|
@ -1,117 +0,0 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Blimpy::Fleet do
|
||||
describe '#ships' do
|
||||
it 'should be an Array' do
|
||||
subject.ships.should be_instance_of Array
|
||||
subject.ships.size.should == 0
|
||||
end
|
||||
end
|
||||
|
||||
describe '#add' do
|
||||
context 'with invalid parameters' do
|
||||
it 'should raise an InvalidShipException' do
|
||||
expect {
|
||||
subject.add(:submarine)
|
||||
}.to raise_error(Blimpy::InvalidShipException)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with valid parameters' do
|
||||
it 'should return false if no Box was properly added' do
|
||||
subject.add(:aws).should == false
|
||||
end
|
||||
|
||||
it 'should pass a Box instance to the block' do
|
||||
invoked_block = false
|
||||
subject.add(:aws) do |box|
|
||||
invoked_block = true
|
||||
box.should be_a Blimpy::Box
|
||||
end
|
||||
invoked_block.should be true
|
||||
end
|
||||
|
||||
context 'with a block' do
|
||||
before :each do
|
||||
subject.add(:aws) do |b|
|
||||
@box = b
|
||||
end
|
||||
end
|
||||
|
||||
it 'should add the box the fleet' do
|
||||
@box.should_not be nil
|
||||
subject.ships.should include(@box)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#save!' do
|
||||
let(:manifest) { 'fake-manifest' }
|
||||
let(:manifest_file) do
|
||||
fd = mock('Manifest File Descriptor')
|
||||
fd
|
||||
end
|
||||
|
||||
before :each do
|
||||
subject.should_receive(:state_file).and_return(manifest)
|
||||
end
|
||||
|
||||
it 'should save the fleet id' do
|
||||
fleet_id = 1337
|
||||
subject.should_receive(:id).and_return(fleet_id)
|
||||
File.should_receive(:open).with(manifest, 'w').and_yield(manifest_file)
|
||||
manifest_file.should_receive(:write).with("id=#{fleet_id}\n")
|
||||
subject.save!
|
||||
end
|
||||
end
|
||||
|
||||
describe '#state_file' do
|
||||
it 'should return a file named manifest' do
|
||||
subject.should_receive(:state_folder).and_return('fake-state-folder')
|
||||
subject.state_file.should == 'fake-state-folder/manifest'
|
||||
end
|
||||
end
|
||||
|
||||
context 'group operations' do
|
||||
let(:members) do
|
||||
members = []
|
||||
members << [0xdeadbeef, {}]
|
||||
members
|
||||
end
|
||||
let(:box) do
|
||||
box = double('Blimpy::Box')
|
||||
box.stub(:stop)
|
||||
box.stub(:destroy)
|
||||
box.stub(:wait_for_state)
|
||||
box
|
||||
end
|
||||
|
||||
before :each do
|
||||
Blimpy::Box.should_receive(:from_instance_id).with(0xdeadbeef, {}).and_return(box)
|
||||
subject.should_receive(:members).and_return(members)
|
||||
# Stub out output methods, this will keep our output clean in RSpec
|
||||
subject.stub(:print)
|
||||
subject.stub(:puts)
|
||||
end
|
||||
|
||||
describe '#stop' do
|
||||
it 'should run stop' do
|
||||
subject.stop
|
||||
end
|
||||
end
|
||||
|
||||
describe '#destroy' do
|
||||
it 'should run destroy' do
|
||||
subject.destroy
|
||||
end
|
||||
end
|
||||
|
||||
describe '#start' do
|
||||
it 'should invoke resume' do
|
||||
box.should_receive(:resume)
|
||||
subject.start
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,38 +0,0 @@
|
|||
require 'spec_helper'
|
||||
require 'blimpy/helpers/state'
|
||||
|
||||
describe Blimpy::Helpers::State do
|
||||
include Blimpy::Helpers::State
|
||||
|
||||
describe '#state_folder' do
|
||||
it 'should be .blimpy.d in the working directory' do
|
||||
pwd = '/fake-pwd'
|
||||
Dir.should_receive(:pwd).and_return(pwd)
|
||||
state_folder.should == "#{pwd}/.blimpy.d"
|
||||
end
|
||||
end
|
||||
|
||||
describe '#state_file' do
|
||||
it 'should raise an error since it must be defined by consumer classes' do
|
||||
expect {
|
||||
state_file
|
||||
}.to raise_error(NotImplementedError)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#ensure_state_folder' do
|
||||
it 'should make the dir if it doesn\'t exist' do
|
||||
File.should_receive(:exist?).and_return(false)
|
||||
Dir.should_receive(:mkdir)
|
||||
ensure_state_folder
|
||||
end
|
||||
|
||||
it 'should not make the dir if it exists' do
|
||||
File.should_receive(:exist?).and_return(true)
|
||||
Dir.should_receive(:mkdir).never
|
||||
ensure_state_folder
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -1,41 +0,0 @@
|
|||
require 'spec_helper'
|
||||
require 'blimpy/keys'
|
||||
|
||||
describe Blimpy::Keys do
|
||||
subject(:keys) { described_class }
|
||||
|
||||
describe '#public_key' do
|
||||
context 'with no SSH keys' do
|
||||
it 'should raise a SSHKeyNotFoundError' do
|
||||
File.stub(:exists?).and_return(false)
|
||||
expect {
|
||||
keys.public_key
|
||||
}.to raise_error(Blimpy::SSHKeyNotFoundError)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#key_name' do
|
||||
let(:username) { 'rspecguy' }
|
||||
before :each do
|
||||
ENV['USER'] = username
|
||||
Socket.should_receive(:gethostname).and_return(hostname)
|
||||
end
|
||||
|
||||
context 'with a simple hostname' do
|
||||
let(:hostname) { 'rspec' }
|
||||
|
||||
it 'should create the right key name' do
|
||||
expect(keys.key_name).to eql("Blimpy-#{username}-#{hostname}")
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a FQDN' do
|
||||
let(:hostname) { 'rspec.github.io' }
|
||||
|
||||
it 'should create the right key name' do
|
||||
expect(keys.key_name).to eql("Blimpy-#{username}-rspec-github-io")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,21 +0,0 @@
|
|||
require 'spec_helper'
|
||||
require 'blimpy/livery/base'
|
||||
|
||||
|
||||
describe Blimpy::Livery::Base do
|
||||
describe '#rsync_excludes' do
|
||||
it { subject.rsync_excludes.should be_instance_of Array }
|
||||
end
|
||||
|
||||
describe '#rsync_command' do
|
||||
subject { described_class.new.rsync_command }
|
||||
|
||||
it { expect(subject.first).to eql('rsync') }
|
||||
it { should include('--exclude=.git') }
|
||||
end
|
||||
|
||||
describe '#livery_root' do
|
||||
it { expect(subject.livery_root).to eql(Dir.pwd) }
|
||||
end
|
||||
|
||||
end
|
|
@ -1,7 +0,0 @@
|
|||
require 'spec_helper'
|
||||
|
||||
require 'blimpy/livery/cwd'
|
||||
|
||||
describe Blimpy::Livery::CWD do
|
||||
|
||||
end
|
|
@ -1,33 +0,0 @@
|
|||
require 'spec_helper'
|
||||
|
||||
require 'blimpy/livery/puppet'
|
||||
|
||||
describe Blimpy::Livery::Puppet do
|
||||
context 'class methods' do
|
||||
subject { described_class }
|
||||
|
||||
it { should respond_to :configure }
|
||||
describe '#configure' do
|
||||
it 'should return an instance of the Puppet livery' do
|
||||
result = subject.configure { |p| }
|
||||
expect(result).to be_instance_of described_class
|
||||
end
|
||||
|
||||
it 'should raise a nice error if no configuration specified' do
|
||||
expect {
|
||||
subject.configure
|
||||
}.to raise_error(Blimpy::InvalidLiveryException)
|
||||
end
|
||||
|
||||
it 'should yield an instance of the Puppet livery' do
|
||||
yielded = nil
|
||||
subject.configure { |p| yielded = p }
|
||||
expect(yielded).to be_instance_of described_class
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it { should respond_to :module_path= }
|
||||
it { should respond_to :manifest_path= }
|
||||
it { should respond_to :options= }
|
||||
end
|
|
@ -1,19 +0,0 @@
|
|||
require 'spec_helper'
|
||||
require 'blimpy/livery'
|
||||
|
||||
describe Blimpy::Livery do
|
||||
context 'class methods' do
|
||||
describe '#tarball_directory' do
|
||||
subject { Blimpy::Livery } # No instantiating!
|
||||
it 'should raise an exception if the directory doesn\'t exist' do
|
||||
expect {
|
||||
subject.tarball_directory(nil)
|
||||
}.to raise_error(ArgumentError)
|
||||
|
||||
expect {
|
||||
subject.tarball_directory('/tmp/never-gonna-give-you-up.lolz')
|
||||
}.to raise_error(ArgumentError)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,64 +0,0 @@
|
|||
require 'spec_helper'
|
||||
require 'blimpy/securitygroups'
|
||||
|
||||
describe Blimpy::SecurityGroups do
|
||||
let(:fog) { mock('Fog object') }
|
||||
# Due to the implementation of the group_id method, [22,8140] can trigger
|
||||
# a failure case that is not triggered by [22,8080].
|
||||
# Zlib.crc32(Set.new(Set.new([22,8140])).inspect) != Zlib.crc32(Set.new([22,8140]).inspect), at least in ruby 1.8.7
|
||||
let(:ports) { [22, 8140 ] }
|
||||
let(:expected_group_name) { subject.group_id(ports) }
|
||||
|
||||
describe '#group_id' do
|
||||
it 'should return nil for an empty port Array' do
|
||||
subject.group_id([]).should be_nil
|
||||
end
|
||||
|
||||
context 'with a known ID' do
|
||||
let(:known_id) { 3548764514 }
|
||||
|
||||
it 'should generate the right string for [1, 2]' do
|
||||
subject.group_id([1, 2]).should == "Blimpy-#{known_id}"
|
||||
end
|
||||
|
||||
it 'should generate the identical string for [1, 2, 1]' do
|
||||
subject.group_id([1, 2, 1]).should == "Blimpy-#{known_id}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#ensure_group' do
|
||||
context 'for a group that exists' do
|
||||
it 'should bail and not try to create the group' do
|
||||
fog.stub_chain(:security_groups, :get).and_return(true)
|
||||
subject.should_receive(:create_group).never
|
||||
name = subject.ensure_group(fog, ports)
|
||||
name.should == expected_group_name
|
||||
end
|
||||
end
|
||||
|
||||
context "for a group that doesn't exist" do
|
||||
let(:sec_groups) { mock('Fog Security Groups object') }
|
||||
let(:group) { mock('Fog SecurityGroup') }
|
||||
|
||||
it 'should create the group' do
|
||||
fog.stub(:security_groups).and_return(sec_groups)
|
||||
sec_groups.should_receive(:get).with(expected_group_name).and_return(nil)
|
||||
sec_groups.should_receive(:create).and_return(group)
|
||||
group.stub(:authorize_port_range)
|
||||
name = subject.ensure_group(fog, ports)
|
||||
name.should == expected_group_name
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#create_group' do
|
||||
let(:group) { mock('Fog SecurityGroup') }
|
||||
it 'should authorize the port ranges for every port' do
|
||||
fog.stub_chain(:security_groups, :create).and_return(group)
|
||||
group.should_receive(:authorize_port_range).with(22..22)
|
||||
group.should_receive(:authorize_port_range).with(8140..8140)
|
||||
subject.create_group(fog, ports)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,82 +0,0 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Blimpy do
|
||||
describe '#fleet' do
|
||||
context 'without a block' do
|
||||
it 'should not create a new Fleet' do
|
||||
Blimpy::Fleet.should_receive(:new).never
|
||||
subject.fleet
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a block' do
|
||||
it 'should create a new Fleet' do
|
||||
result = subject.fleet do |f|
|
||||
end
|
||||
result.should be_instance_of Blimpy::Fleet
|
||||
end
|
||||
|
||||
it 'should invoke the block with a Fleet' do
|
||||
invoked_block = false
|
||||
subject.fleet do |f|
|
||||
invoked_block = true
|
||||
end
|
||||
invoked_block.should be true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#load_file' do
|
||||
context 'no contents' do
|
||||
let(:content) { '' }
|
||||
|
||||
it 'should raise InvalidBlimpFileError' do
|
||||
expect {
|
||||
subject.load_file(content)
|
||||
}.to raise_error(Blimpy::InvalidBlimpFileError)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
context 'invalid content' do
|
||||
let(:content) do
|
||||
"""
|
||||
this is totally invalid Ruby
|
||||
"""
|
||||
end
|
||||
|
||||
it 'should raise InvalidBlimpFileError' do
|
||||
expect {
|
||||
subject.load_file(content)
|
||||
}.to raise_error(Blimpy::InvalidBlimpFileError)
|
||||
end
|
||||
end
|
||||
|
||||
context 'valid content' do
|
||||
let(:content) do
|
||||
"""
|
||||
Blimpy.fleet do |fleet|
|
||||
fleet.add(:aws) do |ship|
|
||||
ship.image_id = 'ami-349b495d'
|
||||
ship.livery = 'rails'
|
||||
ship.group = 'Simple'
|
||||
ship.region = 'us-west-1'
|
||||
ship.name = 'Rails App Server'
|
||||
end
|
||||
end
|
||||
"""
|
||||
end
|
||||
|
||||
it 'should create the appropriate Fleet object' do
|
||||
result = subject.load_file(content)
|
||||
result.should be_instance_of Blimpy::Fleet
|
||||
result.ships.should be_instance_of Array
|
||||
result.ships.size.should == 1
|
||||
|
||||
ship = result.ships.first
|
||||
ship.group.should == 'Simple'
|
||||
ship.name.should == 'Rails App Server'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,18 +0,0 @@
|
|||
require 'rubygems'
|
||||
require 'ruby-debug'
|
||||
|
||||
$:.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
|
||||
|
||||
require 'blimpy'
|
||||
|
||||
RSpec.configure do |config|
|
||||
# RSpec automatically cleans stuff out of backtraces;
|
||||
# sometimes this is annoying when trying to debug something e.g. a gem
|
||||
config.backtrace_clean_patterns = [
|
||||
/\/lib\d*\/ruby\//,
|
||||
/bin\//,
|
||||
/gems/,
|
||||
/spec\/spec_helper\.rb/,
|
||||
/lib\/rspec\/(core|expectations|matchers|mocks)/
|
||||
]
|
||||
end
|
|
@ -0,0 +1,69 @@
|
|||
.highlight { background: #ffffff; }
|
||||
.highlight .c { color: #999988; font-style: italic } /* Comment */
|
||||
.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */
|
||||
.highlight .k { font-weight: bold } /* Keyword */
|
||||
.highlight .o { font-weight: bold } /* Operator */
|
||||
.highlight .cm { color: #999988; font-style: italic } /* Comment.Multiline */
|
||||
.highlight .cp { color: #999999; font-weight: bold } /* Comment.Preproc */
|
||||
.highlight .c1 { color: #999988; font-style: italic } /* Comment.Single */
|
||||
.highlight .cs { color: #999999; font-weight: bold; font-style: italic } /* Comment.Special */
|
||||
.highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */
|
||||
.highlight .gd .x { color: #000000; background-color: #ffaaaa } /* Generic.Deleted.Specific */
|
||||
.highlight .ge { font-style: italic } /* Generic.Emph */
|
||||
.highlight .gr { color: #aa0000 } /* Generic.Error */
|
||||
.highlight .gh { color: #999999 } /* Generic.Heading */
|
||||
.highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */
|
||||
.highlight .gi .x { color: #000000; background-color: #aaffaa } /* Generic.Inserted.Specific */
|
||||
.highlight .go { color: #888888 } /* Generic.Output */
|
||||
.highlight .gp { color: #555555 } /* Generic.Prompt */
|
||||
.highlight .gs { font-weight: bold } /* Generic.Strong */
|
||||
.highlight .gu { color: #800080; font-weight: bold; } /* Generic.Subheading */
|
||||
.highlight .gt { color: #aa0000 } /* Generic.Traceback */
|
||||
.highlight .kc { font-weight: bold } /* Keyword.Constant */
|
||||
.highlight .kd { font-weight: bold } /* Keyword.Declaration */
|
||||
.highlight .kn { font-weight: bold } /* Keyword.Namespace */
|
||||
.highlight .kp { font-weight: bold } /* Keyword.Pseudo */
|
||||
.highlight .kr { font-weight: bold } /* Keyword.Reserved */
|
||||
.highlight .kt { color: #445588; font-weight: bold } /* Keyword.Type */
|
||||
.highlight .m { color: #009999 } /* Literal.Number */
|
||||
.highlight .s { color: #d14 } /* Literal.String */
|
||||
.highlight .na { color: #008080 } /* Name.Attribute */
|
||||
.highlight .nb { color: #0086B3 } /* Name.Builtin */
|
||||
.highlight .nc { color: #445588; font-weight: bold } /* Name.Class */
|
||||
.highlight .no { color: #008080 } /* Name.Constant */
|
||||
.highlight .ni { color: #800080 } /* Name.Entity */
|
||||
.highlight .ne { color: #990000; font-weight: bold } /* Name.Exception */
|
||||
.highlight .nf { color: #990000; font-weight: bold } /* Name.Function */
|
||||
.highlight .nn { color: #555555 } /* Name.Namespace */
|
||||
.highlight .nt { color: #000080 } /* Name.Tag */
|
||||
.highlight .nv { color: #008080 } /* Name.Variable */
|
||||
.highlight .ow { font-weight: bold } /* Operator.Word */
|
||||
.highlight .w { color: #bbbbbb } /* Text.Whitespace */
|
||||
.highlight .mf { color: #009999 } /* Literal.Number.Float */
|
||||
.highlight .mh { color: #009999 } /* Literal.Number.Hex */
|
||||
.highlight .mi { color: #009999 } /* Literal.Number.Integer */
|
||||
.highlight .mo { color: #009999 } /* Literal.Number.Oct */
|
||||
.highlight .sb { color: #d14 } /* Literal.String.Backtick */
|
||||
.highlight .sc { color: #d14 } /* Literal.String.Char */
|
||||
.highlight .sd { color: #d14 } /* Literal.String.Doc */
|
||||
.highlight .s2 { color: #d14 } /* Literal.String.Double */
|
||||
.highlight .se { color: #d14 } /* Literal.String.Escape */
|
||||
.highlight .sh { color: #d14 } /* Literal.String.Heredoc */
|
||||
.highlight .si { color: #d14 } /* Literal.String.Interpol */
|
||||
.highlight .sx { color: #d14 } /* Literal.String.Other */
|
||||
.highlight .sr { color: #009926 } /* Literal.String.Regex */
|
||||
.highlight .s1 { color: #d14 } /* Literal.String.Single */
|
||||
.highlight .ss { color: #990073 } /* Literal.String.Symbol */
|
||||
.highlight .bp { color: #999999 } /* Name.Builtin.Pseudo */
|
||||
.highlight .vc { color: #008080 } /* Name.Variable.Class */
|
||||
.highlight .vg { color: #008080 } /* Name.Variable.Global */
|
||||
.highlight .vi { color: #008080 } /* Name.Variable.Instance */
|
||||
.highlight .il { color: #009999 } /* Literal.Number.Integer.Long */
|
||||
|
||||
.type-csharp .highlight .k { color: #0000FF }
|
||||
.type-csharp .highlight .kt { color: #0000FF }
|
||||
.type-csharp .highlight .nf { color: #000000; font-weight: normal }
|
||||
.type-csharp .highlight .nc { color: #2B91AF }
|
||||
.type-csharp .highlight .nn { color: #000000 }
|
||||
.type-csharp .highlight .s { color: #A31515 }
|
||||
.type-csharp .highlight .sc { color: #A31515 }
|
|
@ -0,0 +1,580 @@
|
|||
/* http://meyerweb.com/eric/tools/css/reset/
|
||||
v2.0 | 20110126
|
||||
License: none (public domain)
|
||||
*/
|
||||
|
||||
html, body, div, span, applet, object, iframe,
|
||||
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
|
||||
a, abbr, acronym, address, big, cite, code,
|
||||
del, dfn, em, img, ins, kbd, q, s, samp,
|
||||
small, strike, strong, sub, sup, tt, var,
|
||||
b, u, i, center,
|
||||
dl, dt, dd, ol, ul, li,
|
||||
fieldset, form, label, legend,
|
||||
table, caption, tbody, tfoot, thead, tr, th, td,
|
||||
article, aside, canvas, details, embed,
|
||||
figure, figcaption, footer, header, hgroup,
|
||||
menu, nav, output, ruby, section, summary,
|
||||
time, mark, audio, video {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
font-size: 100%;
|
||||
font: inherit;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
/* HTML5 display-role reset for older browsers */
|
||||
article, aside, details, figcaption, figure,
|
||||
footer, header, hgroup, menu, nav, section {
|
||||
display: block;
|
||||
}
|
||||
body {
|
||||
line-height: 1;
|
||||
}
|
||||
ol, ul {
|
||||
list-style: none;
|
||||
}
|
||||
blockquote, q {
|
||||
quotes: none;
|
||||
}
|
||||
blockquote:before, blockquote:after,
|
||||
q:before, q:after {
|
||||
content: '';
|
||||
content: none;
|
||||
}
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
}
|
||||
|
||||
/* Style */
|
||||
|
||||
body {
|
||||
font-size: 15px;
|
||||
font-family: Arial, Arial, Helvetica, sans-serif;
|
||||
line-height: 1.5;
|
||||
background: #D1D1D1;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #63a52a;
|
||||
text-decoration: none;
|
||||
-webkit-transition: color ease-in-out 0.3s;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
color: #90D355;
|
||||
}
|
||||
|
||||
h1.title {
|
||||
margin: 30px 20px 10px;
|
||||
font-size: 60px;
|
||||
font-weight: bold;
|
||||
font-style: italic;
|
||||
font-family:Georgia, serif;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
width: 675px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
#container {
|
||||
border: 1px solid #2a2a2a;
|
||||
background: #ddd url(../images/pattern.png);
|
||||
box-shadow: 0 0 5px #b1b1b1;
|
||||
}
|
||||
|
||||
p.tagline {
|
||||
padding: 20px 20px 0;
|
||||
color: #fff;
|
||||
font-size: 17px;
|
||||
}
|
||||
|
||||
#main {
|
||||
margin-top: 20px;
|
||||
padding: 0 20px 90px;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.download-bar {
|
||||
background: #222;
|
||||
border: 5px solid #444;
|
||||
padding: 10px;
|
||||
margin: 0 -35px 20px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.download-bar .inner {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.download-bar .watch-fork iframe {
|
||||
display: block;
|
||||
float: left;
|
||||
border-right: 1px solid #ddd;
|
||||
padding-right: 5px;
|
||||
}
|
||||
.download-bar .watch-fork iframe.last {
|
||||
border-right: 0 none;
|
||||
padding-right: 0;
|
||||
padding-left: 5px;
|
||||
border-left: 1px solid #fff;
|
||||
}
|
||||
.download-bar .watch-fork {
|
||||
overflow: hidden;
|
||||
float: right;
|
||||
background-color: #eee;
|
||||
padding: 5px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.download-bar .blc {
|
||||
border: 10px solid black;
|
||||
border-color: transparent transparent black;
|
||||
width: 0;
|
||||
height: 0;
|
||||
display: block;
|
||||
position: absolute;
|
||||
bottom: -15px;
|
||||
left: 0;
|
||||
-moz-transform: rotate(45deg);
|
||||
-webkit-transform: rotate(45deg);
|
||||
}
|
||||
|
||||
.download-bar .trc {
|
||||
border: 10px solid black;
|
||||
border-color: black transparent transparent;
|
||||
width: 0;
|
||||
height: 0;
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: -15px;
|
||||
right: 0;
|
||||
-moz-transform: rotate(45deg);
|
||||
-webkit-transform: rotate(45deg);
|
||||
}
|
||||
|
||||
.download-bar .avatar {
|
||||
border: 1px solid black;
|
||||
display: block;
|
||||
padding: 4px;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.download-bar .avatar img {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.download-bar a.code {
|
||||
background: transparent url(../images/code.png) no-repeat 0 2px;
|
||||
padding-left: 35px;
|
||||
margin-top: 8px;
|
||||
display: block;
|
||||
float: left;
|
||||
text-indent: 0;
|
||||
width: auto;
|
||||
height: auto;
|
||||
opacity: 1;
|
||||
-moz-opacity: 1;
|
||||
filter:alpha(opacity=1);
|
||||
}
|
||||
|
||||
.current-section {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 50%;
|
||||
width: 693px;
|
||||
margin-left: -352px;
|
||||
background: #222;
|
||||
border: 5px solid #444;
|
||||
color: #fff;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
-webkit-transition: opacity ease-in-out 0.3s;
|
||||
}
|
||||
|
||||
.current-section p {
|
||||
padding: 5px 27px;
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.current-section a {
|
||||
float: right;
|
||||
text-indent: -10000px;
|
||||
background: transparent url(../images/top.png) no-repeat 0 0;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
opacity: 0.8;
|
||||
margin-right: 12px;
|
||||
margin-top: 12px;
|
||||
-moz-opacity: 0.8;
|
||||
filter:alpha(opacity=8);
|
||||
-webkit-transition: opacity ease-in-out 0.3s;
|
||||
}
|
||||
|
||||
.current-section a:hover {
|
||||
opacity: 1;
|
||||
-moz-opacity: 1;
|
||||
filter:alpha(opacity=1);
|
||||
}
|
||||
|
||||
.current-section a.zip {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
a.zip,
|
||||
a.zip span {
|
||||
background: transparent url(../images/zip.png) no-repeat 0 0;
|
||||
width: 30px;
|
||||
height: 21px;
|
||||
opacity: 0.8;
|
||||
display: inline-block;
|
||||
text-indent: -10000px;
|
||||
-moz-opacity: 0.8;
|
||||
filter:alpha(opacity=8);
|
||||
-webkit-transition: opacity ease-in-out 0.3s;
|
||||
}
|
||||
|
||||
a.tar,
|
||||
a.tar span {
|
||||
background: transparent url(../images/tar.png) no-repeat 0 0;
|
||||
width: 30px;
|
||||
height: 21px;
|
||||
opacity: 0.8;
|
||||
display: inline-block;
|
||||
text-indent: -10000px;
|
||||
-moz-opacity: 0.8;
|
||||
filter:alpha(opacity=8);
|
||||
-webkit-transition: opacity ease-in-out 0.3s;
|
||||
}
|
||||
|
||||
a.code {
|
||||
background: transparent url(../images/code.png) no-repeat 0 2px;
|
||||
width: 30px;
|
||||
height: 21px;
|
||||
display: block;
|
||||
opacity: 0.8;
|
||||
display: inline-block;
|
||||
text-indent: -10000px;
|
||||
-moz-opacity: 0.8;
|
||||
filter:alpha(opacity=8);
|
||||
-webkit-transition: opacity ease-in-out 0.3s;
|
||||
}
|
||||
|
||||
a.zip:hover,
|
||||
a.tar:hover,
|
||||
a.code:hover {
|
||||
opacity: 1;
|
||||
-moz-opacity: 1;
|
||||
filter:alpha(opacity=1);
|
||||
}
|
||||
|
||||
a.download-button {
|
||||
border: 1px solid black;
|
||||
border-radius: 3px;
|
||||
display: inline-block;
|
||||
text-indent: 0!important;
|
||||
width: auto;
|
||||
float: right;
|
||||
background: #999; /* for non-css3 browsers */
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#37ADD4', endColorstr='#1B657E'); /* for IE */
|
||||
background: -webkit-gradient(linear, left top, left bottom, from(#37ADD4), to(#1B657E)); /* for webkit browsers */
|
||||
background: -moz-linear-gradient(top, #37ADD4, #1B657E); /* for firefox 3.6+ */
|
||||
height: auto;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
a.download-button span {
|
||||
background-position: 10px 5px;
|
||||
width: auto;
|
||||
height: auto;
|
||||
padding: 5px 10px;
|
||||
padding-left: 45px;
|
||||
display: inline-block;
|
||||
text-indent: 0!important;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
footer {
|
||||
margin-bottom: 60px;
|
||||
padding-bottom: 60px;
|
||||
}
|
||||
|
||||
footer .owner {
|
||||
background: #222;
|
||||
border: 5px solid #444;
|
||||
padding: 5px 15px;
|
||||
margin: -67px -10px 35px;
|
||||
color: #d6d6d6;
|
||||
}
|
||||
|
||||
footer .creds small {
|
||||
float: right;
|
||||
font-size: 10px;
|
||||
text-align: right;
|
||||
margin-left: 15px;
|
||||
}
|
||||
|
||||
footer .owner .avatar {
|
||||
background-color: #666;
|
||||
display: block;
|
||||
margin: -19px 10px 0 0;
|
||||
width: 60px;
|
||||
float: left;
|
||||
}
|
||||
|
||||
footer .owner img {
|
||||
display: block;
|
||||
border: 1px solid #2a2a2a;
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
footer .owner p {
|
||||
font-family:Georgia, serif;
|
||||
}
|
||||
|
||||
footer .owner p a {
|
||||
font-size: 16px;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/* Markdown */
|
||||
.markdown-body h1,
|
||||
.markdown-body h2,
|
||||
.markdown-body h3,
|
||||
.markdown-body h4,
|
||||
.markdown-body h5,
|
||||
.markdown-body h6,
|
||||
.markdown-body p,
|
||||
.markdown-body pre,
|
||||
.markdown-body ul,
|
||||
.markdown-body ol,
|
||||
.markdown-body dl,
|
||||
.markdown-body table,
|
||||
.markdown-body blockquote {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.markdown-body h1,
|
||||
.markdown-body h2,
|
||||
.markdown-body h3,
|
||||
.markdown-body h4,
|
||||
.markdown-body h5,
|
||||
.markdown-body h6 {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.markdown-body h1 {
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
.markdown-body h2 {
|
||||
font-size: 24px;
|
||||
color: #557398;
|
||||
}
|
||||
|
||||
.markdown-body h3 {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.markdown-body h4 {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.markdown-body h5 {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.markdown-body pre {
|
||||
padding: 10px 70px 10px 0;
|
||||
margin-left: -20px;
|
||||
margin-right: -20px;
|
||||
font-family: 'Monaco', 'Lucida Console', monospace;
|
||||
font-size: 13px;
|
||||
line-height: 20px;
|
||||
box-shadow: inset 0 0 5px #000;
|
||||
word-wrap: break-word;
|
||||
background-color:#3b3b3b;
|
||||
color: #d6d6d6;
|
||||
}
|
||||
|
||||
.markdown-body pre.lines {
|
||||
font-size: 12px;
|
||||
margin:0 10px 0 -20px;
|
||||
padding: 10px;
|
||||
float: left;
|
||||
display: block;
|
||||
text-align: right;
|
||||
box-shadow: none;
|
||||
background-color:#2a2a2a;
|
||||
color: #d6d6d6;
|
||||
}
|
||||
|
||||
.markdown-body ul,
|
||||
.markdown-body ol {
|
||||
padding-left: 30px;
|
||||
}
|
||||
|
||||
.markdown-body ul {
|
||||
list-style-type: disc;
|
||||
}
|
||||
|
||||
.markdown-body li,
|
||||
.markdown-body li p,
|
||||
.markdown-body dd,
|
||||
.markdown-body dd p {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.markdown-body li pre,
|
||||
.markdown-body li pre.lines,
|
||||
.markdown-body dd pre,
|
||||
.markdown-body dd pre.lines {
|
||||
margin-left: -35px;
|
||||
}
|
||||
|
||||
.markdown-body dt {
|
||||
font-weight: bold;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.markdown-body dd {
|
||||
margin-left: 15px;
|
||||
}
|
||||
|
||||
.markdown-body table {
|
||||
width: 673px;
|
||||
margin-left: -20px;
|
||||
margin-right: -20px;
|
||||
}
|
||||
|
||||
.markdown-body tbody {
|
||||
border-top: 2px solid #557398;
|
||||
border-bottom: 2px solid #557398;
|
||||
background-color: #EBEFF4;
|
||||
}
|
||||
|
||||
.markdown-body table td * {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.markdown-body td {
|
||||
border-right: 1px solid #557398;
|
||||
border-bottom: 1px solid #557398;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.markdown-body td:first-child,
|
||||
.markdown-body th:first-child {
|
||||
width: 30%;
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
.markdown-body td:last-child {
|
||||
border-right: 0 none;
|
||||
}
|
||||
|
||||
.markdown-body th {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
text-align: left;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.markdown-body tt {
|
||||
background-color:#3b3b3b;
|
||||
color: #d6d6d6;
|
||||
padding: 2px 3px;
|
||||
}
|
||||
|
||||
.markdown-body blockquote {
|
||||
font-style: italic;
|
||||
font-family:Georgia, serif;
|
||||
font-size: 17px;
|
||||
border-top: 3px solid #333;
|
||||
border-bottom: 3px solid #333;
|
||||
padding: 10px 20px;
|
||||
padding-left: 50px;
|
||||
}
|
||||
|
||||
.markdown-body blockquote:before {
|
||||
font-style: italic;
|
||||
font-family: Georgia, serif;
|
||||
font-size: 90px;
|
||||
height: 90px;
|
||||
margin-left: -60px;
|
||||
margin-top: -25px;
|
||||
content: "‟";
|
||||
display: block;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.markdown-body img {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.highlight { background: #ffffff; }
|
||||
.highlight .c { color: #999988; font-style: italic } /* Comment */
|
||||
.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */
|
||||
.highlight .k { font-weight: bold } /* Keyword */
|
||||
.highlight .o { font-weight: bold } /* Operator */
|
||||
.highlight .cm { color: #999988; font-style: italic } /* Comment.Multiline */
|
||||
.highlight .cp { color: #999999; font-weight: bold } /* Comment.Preproc */
|
||||
.highlight .c1 { color: #999988; font-style: italic } /* Comment.Single */
|
||||
.highlight .cs { color: #999999; font-weight: bold; font-style: italic } /* Comment.Special */
|
||||
.highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */
|
||||
.highlight .gd .x { color: #000000; background-color: #ffaaaa } /* Generic.Deleted.Specific */
|
||||
.highlight .ge { font-style: italic } /* Generic.Emph */
|
||||
.highlight .gr { color: #aa0000 } /* Generic.Error */
|
||||
.highlight .gh { color: #999999 } /* Generic.Heading */
|
||||
.highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */
|
||||
.highlight .gi .x { color: #000000; background-color: #aaffaa } /* Generic.Inserted.Specific */
|
||||
.highlight .go { color: #888888 } /* Generic.Output */
|
||||
.highlight .gp { color: #555555 } /* Generic.Prompt */
|
||||
.highlight .gs { font-weight: bold } /* Generic.Strong */
|
||||
.highlight .gu { color: #800080; font-weight: bold; } /* Generic.Subheading */
|
||||
.highlight .gt { color: #aa0000 } /* Generic.Traceback */
|
||||
.highlight .kc { font-weight: bold } /* Keyword.Constant */
|
||||
.highlight .kd { font-weight: bold } /* Keyword.Declaration */
|
||||
.highlight .kn { font-weight: bold } /* Keyword.Namespace */
|
||||
.highlight .kp { font-weight: bold } /* Keyword.Pseudo */
|
||||
.highlight .kr { font-weight: bold } /* Keyword.Reserved */
|
||||
.highlight .kt { color: #445588; font-weight: bold } /* Keyword.Type */
|
||||
.highlight .m { color: #009999 } /* Literal.Number */
|
||||
.highlight .s { color: #d14 } /* Literal.String */
|
||||
.highlight .na { color: #008080 } /* Name.Attribute */
|
||||
.highlight .nb { color: #0086B3 } /* Name.Builtin */
|
||||
.highlight .nc { color: #445588; font-weight: bold } /* Name.Class */
|
||||
.highlight .no { color: #008080 } /* Name.Constant */
|
||||
.highlight .ni { color: #800080 } /* Name.Entity */
|
||||
.highlight .ne { color: #990000; font-weight: bold } /* Name.Exception */
|
||||
.highlight .nf { color: #990000; font-weight: bold } /* Name.Function */
|
||||
.highlight .nn { color: #555555 } /* Name.Namespace */
|
||||
.highlight .nt { color: #000080 } /* Name.Tag */
|
||||
.highlight .nv { color: #008080 } /* Name.Variable */
|
||||
.highlight .ow { font-weight: bold } /* Operator.Word */
|
||||
.highlight .w { color: #bbbbbb } /* Text.Whitespace */
|
||||
.highlight .mf { color: #009999 } /* Literal.Number.Float */
|
||||
.highlight .mh { color: #009999 } /* Literal.Number.Hex */
|
||||
.highlight .mi { color: #009999 } /* Literal.Number.Integer */
|
||||
.highlight .mo { color: #009999 } /* Literal.Number.Oct */
|
||||
.highlight .sb { color: #d14 } /* Literal.String.Backtick */
|
||||
.highlight .sc { color: #d14 } /* Literal.String.Char */
|
||||
.highlight .sd { color: #d14 } /* Literal.String.Doc */
|
||||
.highlight .s2 { color: #d14 } /* Literal.String.Double */
|
||||
.highlight .se { color: #d14 } /* Literal.String.Escape */
|
||||
.highlight .sh { color: #d14 } /* Literal.String.Heredoc */
|
||||
.highlight .si { color: #d14 } /* Literal.String.Interpol */
|
||||
.highlight .sx { color: #d14 } /* Literal.String.Other */
|
||||
.highlight .sr { color: #009926 } /* Literal.String.Regex */
|
||||
.highlight .s1 { color: #d14 } /* Literal.String.Single */
|
||||
.highlight .ss { color: #990073 } /* Literal.String.Symbol */
|
||||
.highlight .bp { color: #999999 } /* Name.Builtin.Pseudo */
|
||||
.highlight .vc { color: #008080 } /* Name.Variable.Class */
|
||||
.highlight .vg { color: #008080 } /* Name.Variable.Global */
|
||||
.highlight .vi { color: #008080 } /* Name.Variable.Instance */
|
||||
.highlight .il { color: #009999 } /* Literal.Number.Integer.Long */
|
Loading…
Reference in New Issue