-
Notifications
You must be signed in to change notification settings - Fork 676
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Michael van Rooijen
committed
Mar 4, 2011
1 parent
1535fee
commit a54cf21
Showing
7 changed files
with
301 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
# encoding: utf-8 | ||
|
||
module Backup | ||
module Configuration | ||
module Storage | ||
class SFTP < Base | ||
class << self | ||
|
||
## | ||
# Server credentials | ||
attr_accessor :username, :password | ||
|
||
## | ||
# Server IP Address and SFTP port | ||
attr_accessor :ip, :port | ||
|
||
## | ||
# Path to store backups to | ||
attr_accessor :path | ||
|
||
end | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
# encoding: utf-8 | ||
|
||
## | ||
# Only load the Net::SFTP library/gem when the Backup::Storage::SFTP class is loaded | ||
require 'net/sftp' | ||
|
||
module Backup | ||
module Storage | ||
class SFTP < Base | ||
|
||
## | ||
# Server credentials | ||
attr_accessor :username, :password | ||
|
||
## | ||
# Server IP Address and SFTP port | ||
attr_accessor :ip, :port | ||
|
||
## | ||
# Path to store backups to | ||
attr_accessor :path | ||
|
||
## | ||
# Creates a new instance of the SFTP storage object | ||
# First it sets the defaults (if any exist) and then evaluates | ||
# the configuration block which may overwrite these defaults | ||
def initialize(&block) | ||
load_defaults! | ||
instance_eval(&block) if block_given? | ||
@time = TIME | ||
@path = path.sub(/^\~\//, '') | ||
end | ||
|
||
## | ||
# This is the remote path to where the backup files will be stored | ||
def remote_path | ||
File.join(path, TRIGGER) | ||
end | ||
|
||
## | ||
# Performs the backup transfer | ||
def perform! | ||
transfer! | ||
cycle! | ||
end | ||
|
||
private | ||
|
||
## | ||
# Establishes a connection to the remote server and returns the Net::SFTP object. | ||
# Not doing any instance variable caching because this object gets persisted in YAML | ||
# format to a file and will issues. This, however has no impact on performance since it only | ||
# gets invoked once per object for a #transfer! and once for a remove! Backups run in the | ||
# background anyway so even if it were a bit slower it shouldn't matter. | ||
def connection | ||
Net::SFTP.start(ip, username, :password => password, :port => port) | ||
end | ||
|
||
## | ||
# Transfers the archived file to the specified remote server | ||
def transfer! | ||
Logger.message("#{ self.class } started transferring \"#{ remote_file }\".") | ||
create_remote_directories! | ||
connection.upload!( | ||
File.join(local_path, local_file), | ||
File.join(remote_path, remote_file) | ||
) | ||
end | ||
|
||
## | ||
# Removes the transferred archive file from the server | ||
def remove! | ||
begin | ||
connection.remove!( | ||
File.join(remote_path, remote_file) | ||
) | ||
rescue Net::SFTP::StatusException | ||
Logger.warn "Could not remove file \"#{ File.join(remote_path, remote_file) }\"." | ||
end | ||
end | ||
|
||
## | ||
# Creates (if they don't exist yet) all the directories on the remote | ||
# server in order to upload the backup file. Net::SFTP does not support | ||
# paths to directories that don't yet exist when creating new directories. | ||
# Instead, we split the parts up in to an array (for each '/') and loop through | ||
# that to create the directories one by one. Net::SFTP raises an exception when | ||
# the directory it's trying ot create already exists, so we have rescue it | ||
def create_remote_directories! | ||
path_parts = Array.new | ||
remote_path.split('/').each do |path_part| | ||
path_parts << path_part | ||
begin | ||
connection.mkdir!(path_parts.join('/')) | ||
rescue Net::SFTP::StatusException | ||
end | ||
end | ||
end | ||
|
||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
# encoding: utf-8 | ||
|
||
require File.dirname(__FILE__) + '/../../spec_helper' | ||
|
||
describe Backup::Configuration::Storage::SFTP do | ||
before do | ||
Backup::Configuration::Storage::SFTP.defaults do |sftp| | ||
sftp.username = 'my_username' | ||
sftp.password = 'my_password' | ||
sftp.ip = '123.45.678.90' | ||
sftp.port = 22 | ||
sftp.path = '~/backups/' | ||
sftp.keep = 20 | ||
end | ||
end | ||
|
||
it 'should set the default sftp configuration' do | ||
sftp = Backup::Configuration::Storage::SFTP | ||
sftp.username.should == 'my_username' | ||
sftp.password.should == 'my_password' | ||
sftp.ip.should == '123.45.678.90' | ||
sftp.port.should == 22 | ||
sftp.path.should == '~/backups/' | ||
sftp.keep.should == 20 | ||
end | ||
|
||
describe '#clear_defaults!' do | ||
it 'should clear all the defaults, resetting them to nil' do | ||
Backup::Configuration::Storage::SFTP.clear_defaults! | ||
|
||
sftp = Backup::Configuration::Storage::SFTP | ||
sftp.username.should == nil | ||
sftp.password.should == nil | ||
sftp.ip.should == nil | ||
sftp.port.should == nil | ||
sftp.path.should == nil | ||
sftp.keep.should == nil | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
# encoding: utf-8 | ||
|
||
require File.dirname(__FILE__) + '/../spec_helper' | ||
|
||
describe Backup::Storage::SFTP do | ||
|
||
let(:sftp) do | ||
Backup::Storage::SFTP.new do |sftp| | ||
sftp.username = 'my_username' | ||
sftp.password = 'my_password' | ||
sftp.ip = '123.45.678.90' | ||
sftp.port = 22 | ||
sftp.path = '~/backups/' | ||
sftp.keep = 20 | ||
end | ||
end | ||
|
||
before do | ||
Backup::Configuration::Storage::SFTP.clear_defaults! | ||
end | ||
|
||
it 'should have defined the configuration properly' do | ||
sftp.username.should == 'my_username' | ||
sftp.password.should == 'my_password' | ||
sftp.ip.should == '123.45.678.90' | ||
sftp.port.should == 22 | ||
sftp.path.should == 'backups/' | ||
sftp.keep.should == 20 | ||
end | ||
|
||
it 'should use the defaults if a particular attribute has not been defined' do | ||
Backup::Configuration::Storage::SFTP.defaults do |sftp| | ||
sftp.username = 'my_default_username' | ||
sftp.password = 'my_default_password' | ||
sftp.path = '~/backups' | ||
end | ||
|
||
sftp = Backup::Storage::SFTP.new do |sftp| | ||
sftp.password = 'my_password' | ||
sftp.ip = '123.45.678.90' | ||
end | ||
|
||
sftp.username.should == 'my_default_username' | ||
sftp.password.should == 'my_password' | ||
sftp.ip.should == '123.45.678.90' | ||
sftp.port.should == nil | ||
end | ||
|
||
describe '#connection' do | ||
it 'should establish a connection to the remote server using the provided ip address and credentials' do | ||
Net::SFTP.expects(:start).with('123.45.678.90', 'my_username', :password => 'my_password', :port => 22) | ||
sftp.send(:connection) | ||
end | ||
end | ||
|
||
describe '#transfer!' do | ||
let(:connection) { mock('Fog::Storage') } | ||
|
||
before do | ||
Net::SFTP.stubs(:start).returns(connection) | ||
sftp.stubs(:create_remote_directories!) | ||
Backup::Logger.stubs(:message) | ||
end | ||
|
||
it 'should transfer the provided file to the path' do | ||
Backup::Model.new('blah', 'blah') {} | ||
file = mock("Backup::Storage::SFTP::File") | ||
|
||
sftp.expects(:create_remote_directories!) | ||
connection.expects(:upload!).with( | ||
File.join(Backup::TMP_PATH, "#{ Backup::TIME }.#{ Backup::TRIGGER }.tar"), | ||
File.join('backups/myapp', "#{ Backup::TIME }.#{ Backup::TRIGGER }.tar") | ||
) | ||
|
||
sftp.send(:transfer!) | ||
end | ||
end | ||
|
||
describe '#remove!' do | ||
let(:connection) { mock('Net::SFTP') } | ||
|
||
before do | ||
Net::SFTP.stubs(:start).returns(connection) | ||
end | ||
|
||
it 'should remove the file from the remote server path' do | ||
connection.expects(:remove!).with("backups/myapp/#{ Backup::TIME }.#{ Backup::TRIGGER }.tar") | ||
sftp.send(:remove!) | ||
end | ||
end | ||
|
||
describe '#create_remote_directories!' do | ||
let(:connection) { mock('Net::SFTP') } | ||
|
||
before do | ||
Net::SFTP.stubs(:start).returns(connection) | ||
end | ||
|
||
it 'should properly create remote directories one by one' do | ||
sftp.path = 'backups/some_other_folder/another_folder' | ||
|
||
connection.expects(:mkdir!).with('backups') | ||
connection.expects(:mkdir!).with('backups/some_other_folder') | ||
connection.expects(:mkdir!).with('backups/some_other_folder/another_folder') | ||
connection.expects(:mkdir!).with('backups/some_other_folder/another_folder/myapp') | ||
|
||
sftp.send(:create_remote_directories!) | ||
end | ||
end | ||
|
||
describe '#perform' do | ||
it 'should invoke transfer! and cycle!' do | ||
sftp.expects(:transfer!) | ||
sftp.expects(:cycle!) | ||
sftp.perform! | ||
end | ||
end | ||
|
||
end |