Skip to content

Commit

Permalink
Merge branch 'sftp'
Browse files Browse the repository at this point in the history
  • Loading branch information
alessandro-fazzi committed Aug 1, 2018
2 parents 4262156 + a21c902 commit b347451
Show file tree
Hide file tree
Showing 8 changed files with 207 additions and 32 deletions.
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ To move directories to/from the remote server, it wraps efficient tools like lft

[![Build Status](https://travis-ci.org/welaika/photocopier.svg?branch=master)](https://travis-ci.org/welaika/photocopier)

## Prerequisites

If you need to use FTP protocol you need to install LFTP on your machine.

## Installation

Add this line to your application's Gemfile:
Expand Down Expand Up @@ -64,7 +68,7 @@ The very same commands are valid for the `Photocopier::FTP` adapter.
host: '', #mandatory
user: '', #mandatory
password: '', #mandatory
scheme: 'ftp' #default, could be any scheme compatibile with your lftp version
scheme: 'ftp' #default, other options are sftp and ftps
}
```
For performance reasons, the `.get_directory` and `.put_directory` commands make
Expand Down Expand Up @@ -130,4 +134,4 @@ made with ❤️ and ☕️ by [weLaika](http://dev.welaika.com)

(The MIT License)

Copyright © 2012-2018 [weLaika](https://dev.welaika.com)
Copyright © 2012-2018 [weLaika](https://dev.welaika.com)
2 changes: 2 additions & 0 deletions lib/photocopier.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
require 'logger'
require 'fileutils'
require 'net/ftp'
require 'net/sftp'
require 'net/ssh'
require 'net/ssh/gateway'
require 'net/scp'
Expand All @@ -13,3 +14,4 @@
require 'photocopier/adapter'
require "photocopier/ssh"
require "photocopier/ftp"
require "photocopier/ftp/session"
41 changes: 24 additions & 17 deletions lib/photocopier/ftp.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ def get(remote_path, file_path = nil)
end

def put_file(file_path, remote_path)
session.put file_path, remote_path
session.put_file file_path, remote_path
end

def delete(remote_path)
Expand All @@ -22,34 +22,40 @@ def delete(remote_path)

def get_directory(remote_path, local_path, exclude = [])
FileUtils.mkdir_p(local_path)
lftp(local_path, remote_path, false, exclude)
lftp(local_path, remote_path, false, exclude, options[:port])
end

def put_directory(local_path, remote_path, exclude = [])
lftp(local_path, remote_path, true, exclude)
lftp(local_path, remote_path, true, exclude, options[:port])
end

def inferred_port
if options[:port].nil? && options[:scheme] == 'sftp'
22
elsif options[:port].nil?
21
else
options[:port]
end
end

private

def session
if @session.nil?
@session = Net::FTP.open(options[:host], options[:user], options[:password])
@session.passive = options[:passive] if options.has_key?(:passive)
end
@session
@session ||= Session.new(options)
end

def lftp(local, remote, reverse, exclude)
def lftp(local, remote, reverse, exclude, port = nil)
remote = Shellwords.escape(remote)
local = Shellwords.escape(local)
command = [
"set ftp:list-options -a",
"set cmd:fail-exit true",
"open #{remote_ftp_url}",
"find -d 1 #{remote} || mkdir -p #{remote}",
"lcd #{local}",
"cd #{remote}",
lftp_mirror_arguments(reverse, exclude)
"set ftp:list-options -a",
"set cmd:fail-exit true",
"open -p #{port || inferred_port} #{remote_ftp_url}",
"find -d 1 #{remote} || mkdir -p #{remote}",
"lcd #{local}",
"cd #{remote}",
lftp_mirror_arguments(reverse, exclude)
].join("; ")

run "lftp -c '#{command}'"
Expand All @@ -68,7 +74,8 @@ def remote_ftp_url
end

def lftp_mirror_arguments(reverse, exclude = [])
mirror = "mirror --delete --use-cache --verbose --no-perms --allow-suid --no-umask --parallel=5"
mirror = "mirror --delete --use-cache --verbose" \
" --no-perms --allow-suid --no-umask --parallel=5"
mirror << " --reverse --dereference" if reverse
exclude.each do |glob|
mirror << " --exclude-glob #{glob}" # NOTE do not use Shellwords.escape here
Expand Down
56 changes: 56 additions & 0 deletions lib/photocopier/ftp/session.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
module Photocopier
class FTP
class Session
def initialize(options)
@scheme = options[:scheme]

if sftp?
@session = Net::SFTP.start(
options[:host],
options[:user],
password: options[:password],
port: options[:port] || 22
)
else
@session = Net::FTP.open(
options[:host],
username: options[:user],
password: options[:password],
port: options[:port] || 21
)
@session.passive = options[:passive] if options.key?(:passive)
end
end

def get(remote, local)
if sftp?
@session.download!(remote, local)
else
@session.get(remote, local)
end
end

def put_file(local, remote)
if sftp?
@session.upload!(local, remote)
else
@session.put(local, remote)
end
end

def delete(remote)
if sftp?
@session.remove!(remote)
else
@session.delete(remote)
end
end

private

def sftp?
@scheme == 'sftp'
end
end
end
end
2 changes: 1 addition & 1 deletion lib/photocopier/version.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# frozen_string_litterla: true

module Photocopier
VERSION = "1.2.0".freeze
VERSION = "1.3.0".freeze
end
1 change: 1 addition & 0 deletions photocopier.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ Gem::Specification.new do |spec|

spec.add_dependency "activesupport", '~> 5.1', '>= 5.1.1'
spec.add_dependency "net-scp", "~> 1.2", ">= 1.2.1"
spec.add_dependency "net-sftp", "~> 2.1.2"
spec.add_dependency "net-ssh", "~> 4.1"
spec.add_dependency "net-ssh-gateway", "~> 2.0"

Expand Down
49 changes: 49 additions & 0 deletions spec/ftp/session_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
RSpec.describe Photocopier::FTP::Session do
let(:options) do
{
host: 'host',
user: 'user',
password: 'password',
scheme: 'ftp'
}
end
let(:ftp) { spy('ftp') }
let(:sftp) { spy('sftp') }

before do
allow(Net::SFTP).to receive(:start).and_return(sftp)
allow(Net::FTP).to receive(:open).and_return(ftp)
end

context "with ftp scheme" do
let(:session) { described_class.new(options) }

it "calls ftp methods" do
expect(ftp).to receive(:get).once
expect(ftp).to receive(:put).once
expect(ftp).to receive(:delete).once

session.get(:remote, :local)
session.put_file(:local, :remote)
session.delete(:remote)
end
end

context "with sftp scheme" do
before do
options[:scheme] = 'sftp'
end

let(:session) { described_class.new(options) }

it "calls sftp methods" do
expect(sftp).to receive(:download!).once
expect(sftp).to receive(:upload!).once
expect(sftp).to receive(:remove!).once

session.get(:remote, :local)
session.put_file(:local, :remote)
session.delete(:remote)
end
end
end
80 changes: 68 additions & 12 deletions spec/ftp_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,30 @@
it_behaves_like "a Photocopier adapter"

let(:ftp) { Photocopier::FTP.new(options) }
let(:options) do { host: "host", user: "user", password: "password" } end
let(:options) do
{
host: "host",
user: "user",
password: "password",
port: 2121
}
end

context "#session" do
it "retrieves an FTP session" do
expect(Net::FTP).to receive(:open).with("host", "user", "password")
expect(Net::FTP).to receive(:open).with(
"host",
username: "user",
password: "password",
port: 2121
)
ftp.send(:session)
end

context "passive mode" do
let(:options) do { host: "host", passive: true } end
let(:options) do
{ host: "host", passive: true }
end
let(:ftp) { double('ftp').as_null_object }

it "should enable passive mode" do
Expand All @@ -22,7 +36,7 @@
end

context "#remote_ftp_url" do
let(:options) do { host: "host" } end
let(:options) { { host: "host" } }

it "should build an ftp url" do
expect(ftp.send(:remote_ftp_url)).to eq("ftp://host")
Expand Down Expand Up @@ -96,22 +110,64 @@
{
host: 'example.com',
user: 'user',
password: "pass!\"',;$u&V^s"
password: "pass!\"',;$u&V^s",
port: 2121
}
end

it "should build a lftp command with the right escaping" do
lftp_commands = [
let(:lftp_commands) do
[
'set ftp:list-options -a',
'set cmd:fail-exit true',
'open ftp://user:pass%21%22%27%2C%3B%24u%26V%[email protected]',
"open -p #{ftp.inferred_port} #{options[:scheme] || 'ftp'}://user:pass%21%22%27%2C%3B%24u%26V%[email protected]",
'find -d 1 remote\\ dir || mkdir -p remote\\ dir',
'lcd local\\ dir',
'cd remote\\ dir',
'mirror --delete --use-cache --verbose --no-perms --allow-suid --no-umask --parallel=5 --reverse --dereference --exclude-glob .git --exclude-glob *.sql --exclude-glob bin/'
].join("; ")
end

it "should build a lftp command with the right escaping" do
expect(lftp_commands).to match('-p 2121')
expect(ftp).to receive(:system).with("lftp -c '#{lftp_commands}'")
ftp.send(:lftp, "local dir", "remote dir", true, [".git", "*.sql", "bin/"])
ftp.send(:lftp, "local dir", "remote dir", true, [".git", "*.sql", "bin/"], options[:port])
end

context "without a port expressed" do
before do
options.delete :port
end
let(:ftp) { Photocopier::FTP.new(options) }

context "if schema is sftp" do
it "uses default port 22" do
options[:scheme] = 'sftp'

expect(lftp_commands).to match('-p 22')
expect(ftp).to receive(:system).with("lftp -c '#{lftp_commands}'")
ftp.send(:lftp, "local dir", "remote dir", true, [".git", "*.sql", "bin/"])
end
end

context "if schema is ftp" do
it "uses default port 21" do
options[:scheme] = 'ftp'

expect(lftp_commands).to match('-p 21')
expect(ftp).to receive(:system).with("lftp -c '#{lftp_commands}'")
ftp.send(:lftp, "local dir", "remote dir", true, [".git", "*.sql", "bin/"])
end
end

context "if schema is ftps" do
it "uses default port 21" do
options[:scheme] = 'ftps'

expect(lftp_commands).to match('-p 21')
expect(ftp).to receive(:system).with("lftp -c '#{lftp_commands}'")
ftp.send(:lftp, "local dir", "remote dir", true, [".git", "*.sql", "bin/"])
end
end
end
end

Expand All @@ -135,7 +191,7 @@

context "#put_file" do
it "should send a file to remote" do
expect(session).to receive(:put).with(file_path, remote_path)
expect(session).to receive(:put_file).with(file_path, remote_path)
ftp.put_file(file_path, remote_path)
end
end
Expand All @@ -154,14 +210,14 @@
context "#get_directory" do
it "should get a remote directory" do
expect(FileUtils).to receive(:mkdir_p).with(local_path)
expect(ftp).to receive(:lftp).with(local_path, remote_path, false, exclude_list)
expect(ftp).to receive(:lftp).with(local_path, remote_path, false, exclude_list, 2121)
ftp.get_directory(remote_path, local_path, exclude_list)
end
end

context "#put_directory" do
it "should send a directory to remote" do
expect(ftp).to receive(:lftp).with(local_path, remote_path, true, exclude_list)
expect(ftp).to receive(:lftp).with(local_path, remote_path, true, exclude_list, 2121)
ftp.put_directory(local_path, remote_path, exclude_list)
end
end
Expand Down

0 comments on commit b347451

Please sign in to comment.