cloud_runner 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --color
2
+ --order random
3
+ --format progress
data/.rvmrc ADDED
@@ -0,0 +1 @@
1
+ rvm use --create ruby-1.9.3-p327@cloud_runner
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in cloud_runner.gemspec
4
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,44 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ cloud_runner (0.0.1)
5
+ digital_ocean
6
+ net-scp
7
+ net-ssh
8
+
9
+ GEM
10
+ remote: http://rubygems.org/
11
+ specs:
12
+ diff-lcs (1.2.1)
13
+ digital_ocean (1.0.0)
14
+ faraday (~> 0.8.4)
15
+ faraday_middleware
16
+ json
17
+ rash
18
+ faraday (0.8.6)
19
+ multipart-post (~> 1.1)
20
+ faraday_middleware (0.9.0)
21
+ faraday (>= 0.7.4, < 0.9)
22
+ hashie (2.0.2)
23
+ json (1.7.7)
24
+ multipart-post (1.2.0)
25
+ net-scp (1.1.0)
26
+ net-ssh (>= 2.6.5)
27
+ net-ssh (2.6.6)
28
+ rash (0.4.0)
29
+ hashie (~> 2.0.0)
30
+ rspec (2.13.0)
31
+ rspec-core (~> 2.13.0)
32
+ rspec-expectations (~> 2.13.0)
33
+ rspec-mocks (~> 2.13.0)
34
+ rspec-core (2.13.1)
35
+ rspec-expectations (2.13.0)
36
+ diff-lcs (>= 1.1.3, < 2.0)
37
+ rspec-mocks (2.13.0)
38
+
39
+ PLATFORMS
40
+ ruby
41
+
42
+ DEPENDENCIES
43
+ cloud_runner!
44
+ rspec
data/README.md ADDED
@@ -0,0 +1,33 @@
1
+ # Cloud Runner
2
+
3
+ Cloud Runner creates a new VM somewhere in the cloud and
4
+ runs a single script against it.
5
+
6
+ Exit code correctly propagates.
7
+
8
+
9
+ ## Installation
10
+
11
+ gem install cloud_runner
12
+
13
+
14
+ ## DigitalOcean Cloud
15
+
16
+ Uses `https://www.digitalocean.com/api` for creating new
17
+ droplets (VM) and associating ssh keys.
18
+
19
+ Run script against new droplet:
20
+
21
+ cr-new \
22
+ --client-id CID \
23
+ --app-key APP_KEY \
24
+ --script something.sh
25
+
26
+ Run script against existing droplet:
27
+
28
+ cr-over \
29
+ --client-id CID \
30
+ --app-key APP_KEY \
31
+ --droplet-id DROPLET_ID \
32
+ --ssh-key SSH_KEY \
33
+ --script something.sh
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require "bundler"
2
+ Bundler::GemHelper.install_tasks
data/bin/cr-new ADDED
@@ -0,0 +1,48 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "optparse"
4
+ require "bundler"
5
+ Bundler.setup
6
+
7
+ $:.unshift(File.expand_path("../lib", __FILE__))
8
+ require "cloud_runner/digital_ocean/cli/new"
9
+
10
+ options = {
11
+ :client_id => ENV["CR_CLIENT_ID"],
12
+ :api_key => ENV["CR_API_KEY"],
13
+ :host_image => "ubuntu-10-04",
14
+ }
15
+
16
+ OptionParser.new do |p|
17
+ p.on("-c", "--client-id CLIENT_ID", String,
18
+ "DigitalOcean Client id / ENV['CR_CLIENT_ID']") do |v|
19
+ options[:client_id] = v
20
+ end
21
+
22
+ p.on("-a", "--api-key API_KEY", String,
23
+ "DigitalOcean API key / ENV['CR_API_KEY']") do |v|
24
+ options[:api_key] = v
25
+ end
26
+
27
+ p.on("-s", "--script SCRIPT", String,
28
+ "Path to script to run on specified droplet") do |v|
29
+ options[:script] = v
30
+ end
31
+
32
+ p.on("-h", "--host-image [HOST_IMAGE]", String,
33
+ "Host image to use for building droplet") do |v|
34
+ options[:host_image] = v
35
+ end
36
+
37
+ p.on("--keep-droplet",
38
+ "Keep droplet after script finishes") do |v|
39
+ options[:keep_droplet] = true
40
+ end
41
+
42
+ p.on("-h", "--help", "Display this screen") do
43
+ puts(p)
44
+ exit
45
+ end
46
+ end.parse!
47
+
48
+ exit CloudRunner::DigitalOcean::Cli::New.new(options).run_script($stdout, $stderr)
data/bin/cr-over ADDED
@@ -0,0 +1,47 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "optparse"
4
+ require "bundler"
5
+ Bundler.setup
6
+
7
+ $:.unshift(File.expand_path("../lib", __FILE__))
8
+ require "cloud_runner/digital_ocean/cli/over"
9
+
10
+ options = {
11
+ :client_id => ENV["CR_CLIENT_ID"],
12
+ :api_key => ENV["CR_API_KEY"],
13
+ }
14
+
15
+ OptionParser.new do |p|
16
+ p.on("-c", "--client-id CLIENT_ID", String,
17
+ "DigitalOcean Client id / ENV['CR_CLIENT_ID']") do |v|
18
+ options[:client_id] = v
19
+ end
20
+
21
+ p.on("-a", "--api-key API_KEY", String,
22
+ "DigitalOcean API key / ENV['CR_API_KEY']") do |v|
23
+ options[:api_key] = v
24
+ end
25
+
26
+ p.on("-d", "--droplet-id DROPLET_ID", String,
27
+ "Droplet to find") do |v|
28
+ options[:droplet_id] = v
29
+ end
30
+
31
+ p.on("-k", "--ssh-key SSH_KEY", String,
32
+ "Path to SSH key for specified droplet") do |v|
33
+ options[:ssh_key] = v
34
+ end
35
+
36
+ p.on("-s", "--script SCRIPT", String,
37
+ "Path to script to run on specified droplet") do |v|
38
+ options[:script] = v
39
+ end
40
+
41
+ p.on("-h", "--help", "Display this screen") do
42
+ puts(p)
43
+ exit
44
+ end
45
+ end.parse!
46
+
47
+ exit CloudRunner::DigitalOcean::Cli::Over.new(options).run_script($stdout, $stderr)
@@ -0,0 +1,27 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "cloud_runner/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "cloud_runner"
7
+ s.version = CloudRunner::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Dmitriy Kalinin", "Alex Suraci"]
10
+ s.email = ["cppforlife@gmail.com"]
11
+ s.homepage = "http://github.com/cppforlife/cloud_runner"
12
+ s.summary = %q{Quickly spin up new VM in the cloud to run a script.}
13
+ s.description = %q{Currently only supports DigitalOcean.}
14
+
15
+ s.rubyforge_project = "cloud_runner"
16
+
17
+ s.add_dependency "digital_ocean"
18
+ s.add_dependency "net-ssh"
19
+ s.add_dependency "net-scp"
20
+
21
+ s.add_development_dependency "rspec"
22
+
23
+ s.files = `git ls-files`.split("\n")
24
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
25
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
26
+ s.require_paths = ["lib"]
27
+ end
@@ -0,0 +1,44 @@
1
+ require "digital_ocean"
2
+ require "cloud_runner/digital_ocean/base"
3
+
4
+ module CloudRunner::DigitalOcean
5
+ class Api
6
+ def initialize(client_id, api_key)
7
+ @client_id = client_id
8
+ @api_key = api_key
9
+ end
10
+
11
+ def self.find(object_type, field)
12
+ define_method("all_#{object_type}s") do
13
+ method = "#{object_type}s"
14
+ api.send(method).list.send(method)
15
+ end
16
+
17
+ define_method("find_#{object_type}_by_#{field}") do |value|
18
+ send("all_#{object_type}s").detect do |object|
19
+ object.send(field) == value
20
+ end
21
+ end
22
+ end
23
+
24
+ find :region, :name
25
+ find :size, :name
26
+ find :image, :name
27
+
28
+ def self.delegate(method_name)
29
+ define_method(method_name) { api.send(method_name) }
30
+ end
31
+
32
+ delegate :ssh_keys
33
+ delegate :droplets
34
+
35
+ private
36
+
37
+ def api
38
+ @api ||= ::DigitalOcean::API.new(
39
+ :client_id => @client_id,
40
+ :api_key => @api_key,
41
+ )
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,3 @@
1
+ module CloudRunner
2
+ module DigitalOcean; end
3
+ end
@@ -0,0 +1,79 @@
1
+ require "cloud_runner/ssh_key"
2
+ require "cloud_runner/one_line_logger"
3
+ require "cloud_runner/digital_ocean/base"
4
+ require "cloud_runner/digital_ocean/api"
5
+ require "cloud_runner/digital_ocean/run"
6
+
7
+ module CloudRunner::DigitalOcean
8
+ module Cli
9
+ class Base
10
+ attr_reader :options
11
+
12
+ def initialize(opts={})
13
+ @options = opts.clone.freeze
14
+
15
+ raise "Client id must be specified" \
16
+ unless @client_id = options[:client_id]
17
+
18
+ raise "Api key must be specified" \
19
+ unless @api_key = options[:api_key]
20
+
21
+ raise "Script must be specified" \
22
+ unless @script_path = options[:script]
23
+
24
+ @script_path = File.realpath(@script_path)
25
+ end
26
+
27
+ def run_script(out, err)
28
+ @out, @err = out, err
29
+
30
+ set_up
31
+ execute_script
32
+ ensure
33
+ clean_up
34
+ end
35
+
36
+ protected
37
+
38
+ def set_up
39
+ raise NotImplementedError
40
+ end
41
+
42
+ def execute_script
43
+ step("Executing script '#{@script_path}'") do
44
+ run.run_script(@script_path, @out, @err, {
45
+ :ssh_logger => OneLineLogger.new(@err),
46
+ })
47
+ end
48
+ end
49
+
50
+ def clean_up
51
+ raise NotImplementedError
52
+ end
53
+
54
+ def run
55
+ @ci_run ||= Run.new(api)
56
+ end
57
+
58
+ def ssh_key
59
+ raise NotImplementedError
60
+ end
61
+
62
+ def step(description, &action)
63
+ @out.puts("-----> #{description}...")
64
+
65
+ action.call.tap do |result|
66
+ @out.puts(result)
67
+ end if action
68
+ end
69
+
70
+ private
71
+
72
+ def api
73
+ @api ||= Api.new(@client_id, @api_key)
74
+ end
75
+ end
76
+
77
+ #~
78
+ end
79
+ end
@@ -0,0 +1,49 @@
1
+ require "cloud_runner/ssh_key"
2
+ require "cloud_runner/digital_ocean/cli/base"
3
+
4
+ module CloudRunner::DigitalOcean::Cli
5
+ class New < Base
6
+ SHORT_HOST_IMAGE_NAMES = {
7
+ "ubuntu-10-04" => "Ubuntu 10.04 x64 Server",
8
+ "ubuntu-12-04" => "Ubuntu 12.04 x64 Server",
9
+ "centos-6-3" => "CentOS 6.3 x64",
10
+ }.freeze
11
+
12
+ def initialize(opts={})
13
+ super
14
+
15
+ raise "Host image must be specified" \
16
+ unless @host_image = options[:host_image]
17
+
18
+ raise "Host image is not available" \
19
+ unless @image_name = SHORT_HOST_IMAGE_NAMES[@host_image]
20
+ end
21
+
22
+ protected
23
+
24
+ def set_up
25
+ step("Creating ssh key '#{ssh_key.private_path}'") do
26
+ run.create_ssh_key(ssh_key)
27
+ end
28
+
29
+ step("Creating droplet '#{run.name}'") do
30
+ run.create_droplet(:image_name => @image_name)
31
+ end
32
+ end
33
+
34
+ # Since we created ssh key/droplet
35
+ # assume that's it is safe to delete.
36
+ def clean_up
37
+ if options[:keep_droplet]
38
+ step("Skipping deletion of droplet and ssh key")
39
+ else
40
+ step("Deleting droplet") { run.delete_droplet }
41
+ step("Deleting ssh key") { run.delete_ssh_key }
42
+ end
43
+ end
44
+
45
+ def ssh_key
46
+ @ssh_key ||= CloudRunner::SshKey.new
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,39 @@
1
+ require "cloud_runner/ssh_key"
2
+ require "cloud_runner/digital_ocean/cli/base"
3
+
4
+ module CloudRunner::DigitalOcean::Cli
5
+ class Over < Base
6
+ def initialize(opts={})
7
+ super
8
+
9
+ raise "Droplet id must be specified" \
10
+ unless @droplet_id = options[:droplet_id]
11
+
12
+ raise "Ssh key must be specified" \
13
+ unless @ssh_key_path = options[:ssh_key]
14
+
15
+ @ssh_key_path = File.realpath(@ssh_key_path)
16
+ end
17
+
18
+ protected
19
+
20
+ def set_up
21
+ step("Using ssh key '#{ssh_key.private_path}'") do
22
+ run.use_ssh_key(ssh_key)
23
+ end
24
+
25
+ step("Finding droplet '#{@droplet_id}'") do
26
+ run.find_dropley_by_id(@droplet_id)
27
+ end
28
+ end
29
+
30
+ # Nothing to clean up since we did not
31
+ # create a new ssh/droplet.
32
+ def clean_up
33
+ end
34
+
35
+ def ssh_key
36
+ @ssh_key ||= CloudRunner::SshKey.new("rsa", @ssh_key_path)
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,105 @@
1
+ require "securerandom"
2
+ require "cloud_runner/ssh"
3
+ require "cloud_runner/digital_ocean/base"
4
+
5
+ module CloudRunner::DigitalOcean
6
+ class Run
7
+ DROPLET_DEFAULT_NAMES = {
8
+ :region => "New York 1",
9
+ :image => "Ubuntu 12.04 x64 Server",
10
+ :size => "512MB",
11
+ }.freeze
12
+
13
+ def initialize(api)
14
+ @api = api
15
+ end
16
+
17
+ def name
18
+ @name ||= "run-#{SecureRandom.hex}"
19
+ end
20
+
21
+ def create_ssh_key(ssh_key)
22
+ use_ssh_key(ssh_key)
23
+
24
+ @public_ssh_key ||= @api.ssh_keys.add(
25
+ :name => "#{name}-ssh-key",
26
+ :ssh_pub_key => ssh_key.public,
27
+ ).ssh_key
28
+ end
29
+
30
+ def use_ssh_key(ssh_key)
31
+ raise "Ssh key must specified" unless @ssh_key = ssh_key
32
+ end
33
+
34
+ def delete_ssh_key
35
+ @api.ssh_keys.delete(@public_ssh_key.id) if @public_ssh_key
36
+ end
37
+
38
+ def create_droplet(opts={})
39
+ raise "Ssh key must be created" unless @public_ssh_key
40
+
41
+ attrs = extract_droplet_attrs(opts)
42
+
43
+ @droplet ||= @api.droplets.create(
44
+ :name => "#{name}-droplet",
45
+ :region_id => attrs[:region].id,
46
+ :image_id => attrs[:image].id,
47
+ :size_id => attrs[:size].id,
48
+ :ssh_key_ids => "#{@public_ssh_key.id}",
49
+ ).droplet
50
+
51
+ wait_for_droplet_to_be_alive
52
+ end
53
+
54
+ def find_droplet_by_id(droplet_id)
55
+ raise "Droplet id must be specified" unless droplet_id
56
+
57
+ @droplet ||= @api.droplets.show(droplet_id).droplet
58
+
59
+ wait_for_droplet_to_be_alive
60
+ end
61
+
62
+ def delete_droplet
63
+ @api.droplets.delete(@droplet.id) if @droplet
64
+ end
65
+
66
+ def run_script(local_path, out, err, opts={})
67
+ raise "Droplet must be created" unless @droplet
68
+ raise "Local path must be specified" unless local_path
69
+
70
+ ssh = CloudRunner::Ssh.new(@droplet.ip_address, "root", @ssh_key)
71
+ ssh.run_script(local_path, out, err, opts)
72
+ end
73
+
74
+ private
75
+
76
+ def extract_droplet_attrs(opts)
77
+ {}.tap do |attrs|
78
+ DROPLET_DEFAULT_NAMES.each do |field, name|
79
+ find_method_name = "find_#{field}_by_name"
80
+ find_name = opts[:"#{field}_name"] || name
81
+
82
+ record = @api.send(find_method_name, find_name)
83
+ raise "#{field} was not found" \
84
+ unless attrs[field] = record
85
+ end
86
+ end
87
+ end
88
+
89
+ def wait_for_droplet_to_be_alive
90
+ wait_for_droplet { |d| d.ip_address && d.status == "active" }
91
+ end
92
+
93
+ def wait_for_droplet(&blk)
94
+ raise "Droplet must be created" unless @droplet
95
+
96
+ Timeout.timeout(180) do
97
+ while !blk.call(@droplet) && sleep(5)
98
+ @droplet = @api.droplets.show(@droplet.id).droplet
99
+ end
100
+ end
101
+
102
+ @droplet
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,18 @@
1
+ require "logger"
2
+
3
+ class OneLineLogger < Logger
4
+ def initialize(*args)
5
+ super
6
+ customize_formatter
7
+ end
8
+
9
+ def customize_formatter
10
+ last_line_len = 0
11
+ self.formatter = proc do |severity, datetime, progname, msg|
12
+ line = msg.split("\n").last
13
+ "\r#{" " * last_line_len}\r#{line}".tap do
14
+ last_line_len = line.size
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,90 @@
1
+ require "securerandom"
2
+ require "stringio"
3
+ require "net/ssh"
4
+ require "net/scp"
5
+
6
+ module CloudRunner
7
+ class Ssh
8
+ DEFAULT_OPTIONS = {
9
+ :auth_methods => ["publickey"],
10
+ :paranoid => false,
11
+ }.freeze
12
+
13
+ def initialize(host, user, ssh_key)
14
+ raise "Host must be specified" unless @host = host
15
+ raise "User must be specified" unless @user = user
16
+ raise "Key must be specified" unless @ssh_key = ssh_key
17
+ end
18
+
19
+ def run_script(local_path, out, err, opts={})
20
+ ssh_opts = DEFAULT_OPTIONS.clone.merge(
21
+ :keys => [@ssh_key.private_path],
22
+ :host_key => "ssh-#{@ssh_key.type}",
23
+ :logger => opts[:ssh_logger] || StringIO.new,
24
+ :verbose => :debug,
25
+ )
26
+
27
+ # Assume the worst
28
+ @exit_code = 1
29
+
30
+ Net::SSH.start(@host, @user, ssh_opts) do |ssh|
31
+ ssh.scp.upload!(local_path, remote_path)
32
+ ssh.exec!("chmod +x #{remote_path}")
33
+ full_exec(ssh, remote_path, out, err)
34
+ end
35
+
36
+ @exit_code
37
+ end
38
+
39
+ private
40
+
41
+ def remote_path
42
+ @remote_path ||= "/tmp/run-#{SecureRandom.hex}.sh"
43
+ end
44
+
45
+ def full_exec(ssh, command, out, err)
46
+ channel_exec(ssh, command) do |ch|
47
+ stream_stdout(ch, out)
48
+ stream_stderr(ch, err)
49
+ handle_exit(ch)
50
+ handle_signal(ch)
51
+ end
52
+ end
53
+
54
+ def channel_exec(ssh, command, &blk)
55
+ ssh.open_channel do |channel|
56
+ channel.exec(command) do |ch, success|
57
+ return @exit_code = 1 unless success
58
+ blk.call(ch)
59
+ end
60
+ end
61
+ ssh.loop
62
+ end
63
+
64
+ def stream_stdout(channel, stream)
65
+ channel.on_data do |ch, data|
66
+ stream.print(data)
67
+ end
68
+ end
69
+
70
+ def stream_stderr(channel, stream)
71
+ channel.on_extended_data do |ch, type, data|
72
+ stream.print(data)
73
+ end
74
+ end
75
+
76
+ def handle_exit(channel)
77
+ channel.on_request("exit-status") do |ch, data|
78
+ @exit_code = data.read_long
79
+ end
80
+ end
81
+
82
+ def handle_signal(channel)
83
+ channel.on_request("exit-signal") do |ch, data|
84
+ raise "Received signal: #{data.read_long}"
85
+ end
86
+ end
87
+
88
+ #~
89
+ end
90
+ end
@@ -0,0 +1,35 @@
1
+ require "securerandom"
2
+
3
+ module CloudRunner
4
+ class SshKey
5
+ attr_reader :type
6
+
7
+ def initialize(type="rsa", private_path=nil)
8
+ raise "Type must be specified" unless @type = type
9
+ @private_path = private_path
10
+ end
11
+
12
+ def public
13
+ File.read("#{private_path}.pub")
14
+ end
15
+
16
+ def private
17
+ File.read(private_path)
18
+ end
19
+
20
+ def private_path
21
+ unless @private_path
22
+ @private_path = "/tmp/ssh-key-#{SecureRandom.hex}"
23
+ generate
24
+ end
25
+ @private_path
26
+ end
27
+
28
+ private
29
+
30
+ def generate
31
+ `ssh-keygen -t '#{@type}' -f '#{@private_path}' -N ''`
32
+ raise "Failed to generate ssh key" unless $? == 0
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,3 @@
1
+ module CloudRunner
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,3 @@
1
+ module CloudRunner
2
+ # Your code goes here...
3
+ end
@@ -0,0 +1,33 @@
1
+ require "spec_helper"
2
+ require "stringio"
3
+ require "cloud_runner/digital_ocean/cli/new"
4
+
5
+ describe "cr-new" do
6
+ let(:client_id) { ENV["SPEC_CR_CLIENT_ID"] || raise }
7
+ let(:api_key) { ENV["SPEC_CR_API_KEY"] || raise }
8
+
9
+ def self.it_runs_script(script, expected_exit_code)
10
+ describe "for '#{script}' script" do
11
+ let(:out) { StringIO.new }
12
+ let(:err) { StringIO.new }
13
+
14
+ it "runs script and returns exit code" do
15
+ CloudRunner::DigitalOcean::Cli::New.new(
16
+ :client_id => client_id,
17
+ :api_key => api_key,
18
+ :script => "./spec/fixtures/#{script}.sh",
19
+ :host_image => "ubuntu-10-04",
20
+ ).run_script(
21
+ Multiplexer.new(out, $stdout),
22
+ Multiplexer.new(err, $stderr),
23
+ ).should == expected_exit_code
24
+
25
+ out.string.should include("Echo to stdout\n")
26
+ err.string.should include("Echo to stderr\n")
27
+ end
28
+ end
29
+ end
30
+
31
+ it_runs_script "success", 0
32
+ it_runs_script "fail", 128
33
+ end
@@ -0,0 +1,6 @@
1
+ #!/bin/bash
2
+
3
+ echo "Echo to stdout" > /dev/stdout
4
+ echo "Echo to stderr" > /dev/stderr
5
+
6
+ exit 128
@@ -0,0 +1,6 @@
1
+ #!/bin/bash
2
+
3
+ echo "Echo to stdout" > /dev/stdout
4
+ echo "Echo to stderr" > /dev/stderr
5
+
6
+ exit 0
@@ -0,0 +1,6 @@
1
+ require "bundler"
2
+ Bundler.setup
3
+
4
+ $:.unshift(File.expand_path("../lib", __FILE__))
5
+
6
+ Dir[File.expand_path("../support/**/*.rb", __FILE__)].each { |f| require(f) }
@@ -0,0 +1,9 @@
1
+ class Multiplexer < BasicObject
2
+ def initialize(*objs)
3
+ @objs = objs
4
+ end
5
+
6
+ def method_missing(*args, &blk)
7
+ @objs.map { |o| o.send(*args, &blk) }.first
8
+ end
9
+ end
metadata ADDED
@@ -0,0 +1,143 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: cloud_runner
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Dmitriy Kalinin
9
+ - Alex Suraci
10
+ autorequire:
11
+ bindir: bin
12
+ cert_chain: []
13
+ date: 2013-03-19 00:00:00.000000000 Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: digital_ocean
17
+ requirement: !ruby/object:Gem::Requirement
18
+ none: false
19
+ requirements:
20
+ - - ! '>='
21
+ - !ruby/object:Gem::Version
22
+ version: '0'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ none: false
27
+ requirements:
28
+ - - ! '>='
29
+ - !ruby/object:Gem::Version
30
+ version: '0'
31
+ - !ruby/object:Gem::Dependency
32
+ name: net-ssh
33
+ requirement: !ruby/object:Gem::Requirement
34
+ none: false
35
+ requirements:
36
+ - - ! '>='
37
+ - !ruby/object:Gem::Version
38
+ version: '0'
39
+ type: :runtime
40
+ prerelease: false
41
+ version_requirements: !ruby/object:Gem::Requirement
42
+ none: false
43
+ requirements:
44
+ - - ! '>='
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ - !ruby/object:Gem::Dependency
48
+ name: net-scp
49
+ requirement: !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ type: :runtime
56
+ prerelease: false
57
+ version_requirements: !ruby/object:Gem::Requirement
58
+ none: false
59
+ requirements:
60
+ - - ! '>='
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
63
+ - !ruby/object:Gem::Dependency
64
+ name: rspec
65
+ requirement: !ruby/object:Gem::Requirement
66
+ none: false
67
+ requirements:
68
+ - - ! '>='
69
+ - !ruby/object:Gem::Version
70
+ version: '0'
71
+ type: :development
72
+ prerelease: false
73
+ version_requirements: !ruby/object:Gem::Requirement
74
+ none: false
75
+ requirements:
76
+ - - ! '>='
77
+ - !ruby/object:Gem::Version
78
+ version: '0'
79
+ description: Currently only supports DigitalOcean.
80
+ email:
81
+ - cppforlife@gmail.com
82
+ executables:
83
+ - cr-new
84
+ - cr-over
85
+ extensions: []
86
+ extra_rdoc_files: []
87
+ files:
88
+ - .gitignore
89
+ - .rspec
90
+ - .rvmrc
91
+ - Gemfile
92
+ - Gemfile.lock
93
+ - README.md
94
+ - Rakefile
95
+ - bin/cr-new
96
+ - bin/cr-over
97
+ - cloud_runner.gemspec
98
+ - lib/cloud_runner.rb
99
+ - lib/cloud_runner/digital_ocean/api.rb
100
+ - lib/cloud_runner/digital_ocean/base.rb
101
+ - lib/cloud_runner/digital_ocean/cli/base.rb
102
+ - lib/cloud_runner/digital_ocean/cli/new.rb
103
+ - lib/cloud_runner/digital_ocean/cli/over.rb
104
+ - lib/cloud_runner/digital_ocean/run.rb
105
+ - lib/cloud_runner/one_line_logger.rb
106
+ - lib/cloud_runner/ssh.rb
107
+ - lib/cloud_runner/ssh_key.rb
108
+ - lib/cloud_runner/version.rb
109
+ - spec/cr_new_spec.rb
110
+ - spec/fixtures/fail.sh
111
+ - spec/fixtures/success.sh
112
+ - spec/spec_helper.rb
113
+ - spec/support/multiplexer.rb
114
+ homepage: http://github.com/cppforlife/cloud_runner
115
+ licenses: []
116
+ post_install_message:
117
+ rdoc_options: []
118
+ require_paths:
119
+ - lib
120
+ required_ruby_version: !ruby/object:Gem::Requirement
121
+ none: false
122
+ requirements:
123
+ - - ! '>='
124
+ - !ruby/object:Gem::Version
125
+ version: '0'
126
+ required_rubygems_version: !ruby/object:Gem::Requirement
127
+ none: false
128
+ requirements:
129
+ - - ! '>='
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ requirements: []
133
+ rubyforge_project: cloud_runner
134
+ rubygems_version: 1.8.24
135
+ signing_key:
136
+ specification_version: 3
137
+ summary: Quickly spin up new VM in the cloud to run a script.
138
+ test_files:
139
+ - spec/cr_new_spec.rb
140
+ - spec/fixtures/fail.sh
141
+ - spec/fixtures/success.sh
142
+ - spec/spec_helper.rb
143
+ - spec/support/multiplexer.rb