From 80851a887ff5d55e832539d7ad605a0356ed0b7e Mon Sep 17 00:00:00 2001 From: Seth Vargo Date: Tue, 11 Nov 2014 18:36:30 -0500 Subject: [PATCH] Create an Adapter to bridge the APIs between SFTP and FTP libraries --- plugins/pushes/ftp/adapter.rb | 107 ++++++++++++++++++ test/unit/plugins/pushes/ftp/adapter_test.rb | 111 +++++++++++++++++++ 2 files changed, 218 insertions(+) create mode 100644 plugins/pushes/ftp/adapter.rb create mode 100644 test/unit/plugins/pushes/ftp/adapter_test.rb diff --git a/plugins/pushes/ftp/adapter.rb b/plugins/pushes/ftp/adapter.rb new file mode 100644 index 000000000..f370d411f --- /dev/null +++ b/plugins/pushes/ftp/adapter.rb @@ -0,0 +1,107 @@ +module VagrantPlugins + module FTPPush + class Adapter + attr_reader :host + attr_reader :port + attr_reader :username + attr_reader :password + attr_reader :options + attr_reader :server + + def initialize(host, username, password, options = {}) + @host, @port = parse_host(host) + @username = username + @password = password + @options = options + @server = nil + end + + # Parse the host into it's url and port parts. + # @return [Array] + def parse_host(host) + if host.include?(":") + split = host.split(":", 2) + [split[0], split[1].to_i] + else + [host, default_port] + end + end + + def default_port + raise NotImplementedError + end + + def connect(&block) + raise NotImplementedError + end + + def upload(local, remote) + raise NotImplementedError + end + end + + # + # The FTP Adapter + # + class FTPAdapter < Adapter + def initialize(*) + require "net/ftp" + super + end + + def default_port + 20 + end + + def connect(&block) + @server = Net::FTP.new + @server.passive = options.fetch(:passive, true) + @server.connect(host, port) + @server.login(username, password) + + begin + yield self + ensure + @server.close + end + end + + def upload(local, remote) + parent = File.dirname(remote) + + # Create the parent directory if it does not exist + if !@server.list("/").any? { |f| f.start_with?(parent) } + @server.mkdir(parent) + end + + # Upload the file + @server.putbinaryfile(local, remote) + end + end + + # + # The SFTP Adapter + # + class SFTPAdapter < Adapter + def initialize(*) + require "net/sftp" + super + end + + def default_port + 22 + end + + def connect(&block) + Net::SFTP.start(@host, @username, password: @password, port: @port) do |server| + @server = server + yield self + end + end + + def upload(local, remote) + @server.upload!(local, remote, mkdir: true) + end + end + end +end diff --git a/test/unit/plugins/pushes/ftp/adapter_test.rb b/test/unit/plugins/pushes/ftp/adapter_test.rb new file mode 100644 index 000000000..54bacc1d1 --- /dev/null +++ b/test/unit/plugins/pushes/ftp/adapter_test.rb @@ -0,0 +1,111 @@ +require_relative "../../../base" +require "fake_ftp" + +require Vagrant.source_root.join("plugins/pushes/ftp/adapter") + +describe VagrantPlugins::FTPPush::Adapter do + include_context "unit" + + subject do + described_class.new("127.0.0.1:2345", "sethvargo", "bacon", + foo: "bar", + ) + end + + describe "#initialize" do + it "sets the instance variables" do + expect(subject.host).to eq("127.0.0.1") + expect(subject.port).to eq(2345) + expect(subject.username).to eq("sethvargo") + expect(subject.password).to eq("bacon") + expect(subject.options).to eq(foo: "bar") + expect(subject.server).to be(nil) + end + end + + describe "#parse_host" do + it "has a default value" do + allow(subject).to receive(:default_port) + .and_return(5555) + + result = subject.parse_host("127.0.0.1") + expect(result[0]).to eq("127.0.0.1") + expect(result[1]).to eq(5555) + end + end +end + +describe VagrantPlugins::FTPPush::FTPAdapter do + include_context "unit" + + before(:all) do + @server = FakeFtp::Server.new(21212, 21213) + @server.start + end + + after(:all) { @server.stop } + + let(:server) { @server } + + before { server.reset } + + subject do + described_class.new("127.0.0.1:#{server.port}", "sethvargo", "bacon") + end + + describe "#default_port" do + it "is 20" do + expect(subject.default_port).to eq(20) + end + end + + describe "#upload" do + before do + @dir = Dir.mktmpdir + FileUtils.touch("#{@dir}/file") + end + + after do + FileUtils.rm_rf(@dir) + end + + it "uploads the file" do + subject.connect do |ftp| + ftp.upload("#{@dir}/file", "/file") + end + + expect(server.files).to include("file") + end + + it "uploads in passive mode" do + subject.options[:passive] = true + subject.connect do |ftp| + ftp.upload("#{@dir}/file", "/file") + end + + expect(server.file("file")).to be_passive + end + end +end + +describe VagrantPlugins::FTPPush::SFTPAdapter do + include_context "unit" + + subject do + described_class.new("127.0.0.1:2345", "sethvargo", "bacon", + foo: "bar", + ) + end + + describe "#default_port" do + it "is 22" do + expect(subject.default_port).to eq(22) + end + end + + describe "#upload" do + it "uploads the file" do + pending "a way to mock an SFTP server" + end + end +end