Compare commits

...

No commits in common. "master" and "gh-pages" have entirely different histories.

63 changed files with 789 additions and 3087 deletions

22
.gitignore vendored
View File

@ -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
View File

@ -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
View File

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

View File

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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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"
"""

View File

@ -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.
"""

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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:
"""

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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

BIN
images/code.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
images/pattern.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
images/tar.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
images/top.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
images/zip.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

87
index.html Normal file
View File

@ -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>&lt;a&gt;</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 well 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&amp;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>

52
javascripts/script.js Normal file
View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -1,5 +0,0 @@
module Blimpy
module Boxes
end
end

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -1,3 +0,0 @@
module Blimpy
VERSION = "0.7.0"
end

1
params.json Normal file
View File

@ -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 well help you sort it out."}

View File

@ -1,6 +0,0 @@
#!/bin/sh
export PATH=/var/lib/gems/1.8/bin:$PATH
exec $@

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -1,7 +0,0 @@
require 'spec_helper'
require 'blimpy/livery/cwd'
describe Blimpy::Livery::CWD do
end

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 }

580
stylesheets/stylesheet.css Normal file
View File

@ -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 */