Skip to content

Commit

Permalink
Adds new SCP feature
Browse files Browse the repository at this point in the history
* Fixes #120
  • Loading branch information
petems committed Nov 22, 2017
1 parent 208acd4 commit 89385b7
Show file tree
Hide file tree
Showing 4 changed files with 164 additions and 0 deletions.
42 changes: 42 additions & 0 deletions lib/tugboat/cli.rb
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,48 @@ def ssh(name = nil)
'user_quiet' => options[:quiet])
end

desc 'scp FUZZY_NAME FROM_PATH TO_PATH', 'scp files to a droplet'
method_option 'id',
type: :string,
aliases: '-i',
desc: 'The ID of the droplet.'
method_option 'from_path',
type: :string,
aliases: '-from',
desc: 'The path of the local file'
method_option 'to_path',
type: :string,
aliases: '-to',
desc: 'The path to copy to on the remote droplet'
method_option 'name',
type: :string,
aliases: '-n',
desc: 'The exact name of the droplet'
method_option 'ssh_user',
type: :string,
aliases: '-u',
desc: 'Specifies which user to log in as'
method_option 'scp_command',
type: :string,
aliases: ['-c'],
desc: 'Command to run to copy the file, eg scp, rsync (defaults to scp)'
method_option 'wait',
type: :boolean,
aliases: '-w',
desc: 'Wait for droplet to become active before trying to SSH'
def scp(name = nil, from_path, to_path)
Middleware.sequence_scp_droplet.call('tugboat_action' => __method__,
'user_droplet_id' => options[:id],
'user_droplet_name' => options[:name],
'user_droplet_fuzzy_name' => name,
'user_from_file' => from_path,
'user_to_file' => to_path,
'user_droplet_ssh_user' => options[:ssh_user],
'user_scp_command' => options[:scp_command],
'user_droplet_ssh_wait' => options[:wait],
'user_quiet' => options[:quiet])
end

desc 'create NAME', 'Create a droplet.'
method_option 'size',
type: :string,
Expand Down
13 changes: 13 additions & 0 deletions lib/tugboat/middleware.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ module Middleware
autoload :RestartDroplet, 'tugboat/middleware/restart_droplet'
autoload :SnapshotDroplet, 'tugboat/middleware/snapshot_droplet'
autoload :SSHDroplet, 'tugboat/middleware/ssh_droplet'
autoload :SCPDroplet, 'tugboat/middleware/scp_droplet'
autoload :StartDroplet, 'tugboat/middleware/start_droplet'
autoload :WaitForState, 'tugboat/middleware/wait_for_state'

Expand Down Expand Up @@ -147,6 +148,18 @@ def self.sequence_ssh_droplet
end
end

# SSH into a droplet
def self.sequence_scp_droplet
::Middleware::Builder.new do
use InjectConfiguration
use CheckConfiguration
use InjectClient
use FindDroplet
use CheckDropletActive
use SCPDroplet
end
end

# Create a droplet
def self.sequence_create_droplet
::Middleware::Builder.new do
Expand Down
34 changes: 34 additions & 0 deletions lib/tugboat/middleware/scp_droplet.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
module Tugboat
module Middleware
class SCPDroplet < Base
def call(env)
say "Executing SCP on Droplet #{env['droplet_name']}..."

identity = File.expand_path(env['config'].ssh_key_path.to_s).strip

ssh_user = env['user_droplet_ssh_user'] || env['config'].ssh_user

scp_command = env['user_scp_command'] || 'scp'

host_ip = env['droplet_ip']

host_string = "#{ssh_user}@#{host_ip}"

if env['user_droplet_ssh_wait']
say 'Wait flag given, waiting for droplet to become active'
wait_for_state(env['droplet_id'], 'active', env['barge'])
end

identity_string = "-i #{identity}"

scp_command_string = [scp_command, identity_string, env['user_from_file'], "#{host_string}:#{env['user_to_file']}"].join(' ')

say "Attempting SCP with `#{scp_command_string}`"

Kernel.exec(scp_command_string)

@app.call(env)
end
end
end
end
75 changes: 75 additions & 0 deletions spec/cli/scp_cli_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
require 'spec_helper'

describe Tugboat::CLI do
include_context 'spec'

describe 'scp' do
it "tries to fetch the droplet's IP from the API" do
stub_request(:get, 'https://api.digitalocean.com/v2/droplets?page=1&per_page=1').
with(headers: { 'Accept' => '*/*', 'Accept-Encoding' => 'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'Authorization' => 'Bearer foo', 'Content-Type' => 'application/json', 'User-Agent' => 'Faraday v0.9.2' }).
to_return(status: 200, body: fixture('show_droplets'), headers: {})

stub_request(:get, 'https://api.digitalocean.com/v2/droplets?page=1&per_page=200').
to_return(headers: { 'Content-Type' => 'application/json' }, status: 200, body: fixture('show_droplets'))
allow(Kernel).to receive(:exec).with("scp -i #{Dir.home}/.ssh/id_rsa2 /tmp/foo baz@104.236.32.182:/tmp/bar")

expected_string = <<-eos
Droplet fuzzy name provided. Finding droplet ID...done\e[0m, 6918990 (example.com)
Executing SCP on Droplet (example.com)...
Attempting SCP with `scp -i #{Dir.home}/.ssh/id_rsa2 /tmp/foo baz@104.236.32.182:/tmp/bar`
eos

expect { cli.scp('example.com', '/tmp/foo', '/tmp/bar') }.to output(expected_string).to_stdout
end

it "runs with rsync if given at the command line" do
stub_request(:get, 'https://api.digitalocean.com/v2/droplets?page=1&per_page=1').
with(headers: { 'Accept' => '*/*', 'Accept-Encoding' => 'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'Authorization' => 'Bearer foo', 'Content-Type' => 'application/json', 'User-Agent' => 'Faraday v0.9.2' }).
to_return(status: 200, body: fixture('show_droplets'), headers: {})

stub_request(:get, 'https://api.digitalocean.com/v2/droplets?page=1&per_page=200').
to_return(headers: { 'Content-Type' => 'application/json' }, status: 200, body: fixture('show_droplets'))
allow(Kernel).to receive(:exec).with("rsync -i #{Dir.home}/.ssh/id_rsa2 /tmp/foo baz@104.236.32.182:/tmp/bar")

expected_string = <<-eos
Droplet fuzzy name provided. Finding droplet ID...done\e[0m, 6918990 (example.com)
Executing SCP on Droplet (example.com)...
Attempting SCP with `rsync -i #{Dir.home}/.ssh/id_rsa2 /tmp/foo baz@104.236.32.182:/tmp/bar`
eos

cli.options = cli.options.merge(scp_command: 'rsync')

expect { cli.scp('example.com', '/tmp/foo', '/tmp/bar') }.to output(expected_string).to_stdout
end

it "wait's until droplet active if -w command is given and droplet eventually active" do
stub_request(:get, 'https://api.digitalocean.com/v2/droplets?page=1&per_page=1').
with(headers: { 'Accept' => '*/*', 'Accept-Encoding' => 'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'Authorization' => 'Bearer foo', 'Content-Type' => 'application/json', 'User-Agent' => 'Faraday v0.9.2' }).
to_return(status: 200, body: '', headers: {})

stub_request(:get, 'https://api.digitalocean.com/v2/droplets/6918990?per_page=200').
with(headers: { 'Accept' => '*/*', 'Accept-Encoding' => 'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'Authorization' => 'Bearer foo', 'Content-Type' => 'application/json', 'User-Agent' => 'Faraday v0.9.2' }).
to_return(
{ status: 200, body: fixture('show_droplet_inactive'), headers: {} },
status: 200, body: fixture('show_droplet'), headers: {}
)

stub_request(:get, 'https://api.digitalocean.com/v2/droplets?page=1&per_page=200').
to_return(headers: { 'Content-Type' => 'application/json' }, status: 200, body: fixture('show_droplets'))
allow(Kernel).to receive(:exec).with("scp -i #{Dir.home}/.ssh/id_rsa2 /tmp/foo baz@104.236.32.182:/tmp/bar")

cli.options = cli.options.merge(wait: true)

expected_string = <<-eos
Droplet fuzzy name provided. Finding droplet ID...done\e[0m, 6918990 (example.com)
Executing SCP on Droplet (example.com)...
Wait flag given, waiting for droplet to become active
..done\e[0m (2s)
Attempting SCP with `scp -i #{Dir.home}/.ssh/id_rsa2 /tmp/foo baz@104.236.32.182:/tmp/bar`
eos

expect { cli.scp('example.com', '/tmp/foo', '/tmp/bar') }.to output(expected_string).to_stdout
end

end
end

0 comments on commit 89385b7

Please sign in to comment.