Skip to content

Commit

Permalink
Added new SCM Syncers: SVN and Git
Browse files Browse the repository at this point in the history
  • Loading branch information
kikito committed Feb 16, 2012
1 parent 1d923a9 commit ba62e98
Show file tree
Hide file tree
Showing 20 changed files with 804 additions and 7 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ Below you find a list of components that Backup currently supports. If you'd lik
- RSync (Push, Pull and Local)
- Amazon S3
- Rackspce Cloud Files
- SCM support (SVN and Git)

[Syncer Wiki Page](https://github.com/meskyanichi/backup/wiki/Syncers)

Expand Down
18 changes: 14 additions & 4 deletions lib/backup.rb
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,11 @@ module RSync
autoload :Push, File.join(SYNCER_PATH, 'rsync', 'push')
autoload :Pull, File.join(SYNCER_PATH, 'rsync', 'pull')
end
module SCM
autoload :Base, File.join(SYNCER_PATH, 'scm', 'base')
autoload :Git, File.join(SYNCER_PATH, 'scm', 'git')
autoload :SVN, File.join(SYNCER_PATH, 'scm', 'svn')
end
end

##
Expand Down Expand Up @@ -164,11 +169,16 @@ module Syncer
autoload :Cloud, File.join(CONFIGURATION_PATH, 'syncer', 'cloud')
autoload :CloudFiles, File.join(CONFIGURATION_PATH, 'syncer', 'cloud_files')
autoload :S3, File.join(CONFIGURATION_PATH, 'syncer', 's3')
module SCM
autoload :Base, File.join(CONFIGURATION_PATH, 'syncer', 'scm', 'base')
autoload :Git, File.join(CONFIGURATION_PATH, 'syncer', 'scm', 'git')
autoload :SVN, File.join(CONFIGURATION_PATH, 'syncer', 'scm', 'svn')
end
module RSync
autoload :Base, File.join(CONFIGURATION_PATH, 'syncer', 'rsync', 'base')
autoload :Local, File.join(CONFIGURATION_PATH, 'syncer', 'rsync', 'local')
autoload :Push, File.join(CONFIGURATION_PATH, 'syncer', 'rsync', 'push')
autoload :Pull, File.join(CONFIGURATION_PATH, 'syncer', 'rsync', 'pull')
autoload :Base, File.join(CONFIGURATION_PATH, 'syncer', 'rsync', 'base')
autoload :Local, File.join(CONFIGURATION_PATH, 'syncer', 'rsync', 'local')
autoload :Push, File.join(CONFIGURATION_PATH, 'syncer', 'rsync', 'push')
autoload :Pull, File.join(CONFIGURATION_PATH, 'syncer', 'rsync', 'pull')
end
end

Expand Down
6 changes: 5 additions & 1 deletion lib/backup/config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,11 @@ def add_dsl_constants!
# Encryptors
['OpenSSL', 'GPG'],
# Syncers
['Rackspace', 'S3', { 'RSync' => ['Push', 'Pull', 'Local'] }],
[ 'Rackspace',
'S3',
{ 'SCM' => ['SVN', 'Git'] },
{ 'RSync' => ['Push', 'Pull', 'Local'] }
],
# Notifiers
['Mail', 'Twitter', 'Campfire', 'Presently', 'Prowl', 'Hipchat']
]
Expand Down
17 changes: 17 additions & 0 deletions lib/backup/configuration/syncer/scm/base.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# encoding: utf-8

module Backup
module Configuration
module Syncer
module SCM
class Base < Syncer::Base
class << self

attr_accessor :protocol, :username, :password, :ip, :port, :path, :additional_options

end
end
end
end
end
end
12 changes: 12 additions & 0 deletions lib/backup/configuration/syncer/scm/git.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# encoding: utf-8

module Backup
module Configuration
module Syncer
module SCM
class Git < Base
end
end
end
end
end
12 changes: 12 additions & 0 deletions lib/backup/configuration/syncer/scm/svn.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# encoding: utf-8

module Backup
module Configuration
module Syncer
module SCM
class SVN < Base
end
end
end
end
end
2 changes: 1 addition & 1 deletion lib/backup/model.rb
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ def sync_with(name, &block)
# Warn user of DSL change from 'RSync' to 'RSync::Local'
if name.to_s == 'Backup::Config::RSync'
Logger.warn Errors::ConfigError.new(<<-EOS)
Configuration Update Needed for Syncer::RSync
Config Update Needed for Syncer::RSync
The RSync Syncer has been split into three separate modules:
RSync::Local, RSync::Push and RSync::Pull
Please update your configuration for your local RSync Syncer
Expand Down
80 changes: 80 additions & 0 deletions lib/backup/syncer/scm/base.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# encoding: utf-8

module Backup
module Syncer
module SCM
class Base < Syncer::Base

##
# In a server url such as:
# https://jimmy:password@example.com:80
# -> [protocol]://[username]:[password][ip]:[port]
attr_accessor :protocol, :username, :password, :ip, :port

##
# repositories is an alias to directories; provided just for clarity
alias :repositories :directories
alias :repositories= :directories=

##
# Instantiates a new Repository Syncer object
# and sets the default configuration
def initialize
load_defaults!

@path ||= 'backups'
@directories = Array.new
end

def perform!
Logger.message("#{ self.class } started syncing '#{ path }'.")
repositories.each do |repository|
backup_repository! repository
end
end

def authority
"#{protocol}://#{credentials}#{ip}#{prefixed_port}"
end

def repository_urls
repositories.collect{ |r| repository_url(r) }
end

def repository_url(repository)
"#{authority}#{repository}"
end

def repository_local_path(repository)
File.join(path, repository)
end

def repository_absolute_local_path(repository)
File.absolute_path(repository_local_path(repository))
end

def repository_local_container_path(repository)
File.dirname(repository_local_path(repository))
end

def create_repository_local_container!(repository)
FileUtils.mkdir_p(repository_local_container_path(repository))
end

private

def credentials
"#{username}#{prefixed_password}@" if username
end

def prefixed_password
":#{password}" if password
end

def prefixed_port
":#{port}" if port
end
end
end
end
end
49 changes: 49 additions & 0 deletions lib/backup/syncer/scm/git.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# encoding: utf-8

module Backup
module Syncer
module SCM
class Git < Base

attr_accessor :username, :password, :host, :protocol, :port, :repo_path, :path

def initialize(&block)
super

@protocol ||= 'git'

instance_eval(&block) if block_given?
end

def local_repository_exists?(repository)
local_path = repository_local_path(repository)
run "cd #{local_path} && git rev-parse --git-dir > /dev/null 2>&1"
return true
rescue Errors::CLI::SystemCallError
Logger.message("#{local_path} is not a repository")
return false
end

def clone_repository!(repository)
Logger.message("Cloning repository in '#{repository_local_path(repository)}'.")
create_repository_local_container!(repository)
run "cd #{repository_local_container_path(repository)} && git clone --bare #{repository_url(repository)}"
end

def update_repository!(repository)
local_path = repository_local_path(repository)
Logger.message("Updating repository in '#{local_path}'.")
run "cd #{local_path} && git fetch --all"
end

def backup_repository!(repository)
if local_repository_exists?(repository)
update_repository!(repository)
else
clone_repository!(repository)
end
end
end
end
end
end
56 changes: 56 additions & 0 deletions lib/backup/syncer/scm/svn.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# encoding: utf-8

module Backup
module Syncer
module SCM
class SVN < Base

def initialize(&block)

super

@protocol ||= "http"

instance_eval(&block) if block_given?
end

def local_repository_exists?(repository)
run "svnadmin verify #{repository_local_path(repository)}"
return true
rescue Errors::CLI::SystemCallError
return false
end

def initialize_repository!(repository)
local_path = repository_local_path(repository)
absolute_path = repository_absolute_local_path(repository)
url = repository_url(repository)
hook_path = File.join(local_path, 'hooks', 'pre-revprop-change')

Logger.message("Initializing empty svn repository in '#{local_path}'.")

create_repository_local_container!(repository)

run "svnadmin create '#{local_path}'"
run "echo '#!/bin/sh' > '#{hook_path}'"
run "chmod +x '#{hook_path}'"
run "svnsync init file://#{absolute_path} #{url}"
end

def update_repository!(repository)
absolute_path = repository_absolute_local_path(repository)
local_path = repository_local_path(repository)

Logger.message("Updating svn repository in '#{local_path}'.")
run("svnsync sync file://#{absolute_path} --non-interactive")
end

This comment has been minimized.

Copy link
@petemounce

petemounce Sep 29, 2012

Why svnsync instead of svnadmin hotcopy? I don't know the semantics of the former, but the latter allows the repository to be active during the backup; that is, it won't lose transactions in-flight. But, it's been a long time since I looked at backing up svn repositories; I'm probably out of date.

This comment has been minimized.

Copy link
@kikito

kikito Sep 29, 2012

Author

I'm not certain why we used svnsync, but I'm sure we knew svnadmin existed and yet we used svnsync. But it has been years since I have had to use these, I'm afraid I have forgotten the details. I'd venture it has to do with the permissions required for each action. But that's a guess.

This comment has been minimized.

Copy link
@petemounce

petemounce Sep 30, 2012

I've skimmed some docs; maybe svnsync because it can operate remotely, whereas svnadmin hotcopy needs to operate locally? Flexibility I guess...? Except there are file:// URLs there, note remote ones. Shrug.

This comment has been minimized.

Copy link
@kikito

kikito via email Oct 1, 2012

Author


def backup_repository!(repository)
initialize_repository!(repository) unless local_repository_exists?(repository)
update_repository!(repository)
end
end
end
end
end
2 changes: 1 addition & 1 deletion spec/cli/utility_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@
[--config-path=CONFIG_PATH] # Path to your Backup configuration directory
[--databases=DATABASES] # (mongodb, mysql, postgresql, redis, riak)
[--storages=STORAGES] # (cloud_files, dropbox, ftp, local, ninefold, rsync, s3, scp, sftp)
[--syncers=SYNCERS] # (cloud_files, rsync_local, rsync_pull, rsync_push, s3)
[--syncers=SYNCERS] # (cloud_files, rsync_local, rsync_pull, rsync_push, s3, scm_git, scm_svn)
[--encryptors=ENCRYPTORS] # (gpg, openssl)
[--compressors=COMPRESSORS] # (bzip2, gzip, lzma, pbzip2)
[--notifiers=NOTIFIERS] # (campfire, hipchat, mail, presently, prowl, twitter)
Expand Down
48 changes: 48 additions & 0 deletions spec/configuration/syncer/scm/base_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# encoding: utf-8

require File.expand_path('../../../../spec_helper.rb', __FILE__)

describe Backup::Configuration::Syncer::SCM do
before do
Backup::Configuration::Syncer::SCM::Base.defaults do |default|
default.protocol = 'http'
default.username = 'my_user_name'
default.password = 'secret'
default.ip = 'example.com'
default.port = 1234
default.path = '~/backups/'
default.additional_options = 'some_additional_options'
# base.directories/repositories # can not have a default value

end
end

after { Backup::Configuration::Syncer::SCM::Base.clear_defaults! }

it 'should set the default base configuration' do
base = Backup::Configuration::Syncer::SCM::Base
base.protocol.should == 'http'
base.username.should == 'my_user_name'
base.password.should == 'secret'
base.ip.should == 'example.com'
base.port.should == 1234
base.path.should == '~/backups/'
base.additional_options == 'some_additional_options'
end

describe '#clear_defaults!' do
it 'should clear all the defaults, resetting them to nil' do
Backup::Configuration::Syncer::SCM::Base.clear_defaults!

base = Backup::Configuration::Syncer::SCM::Base
base.protocol.should == nil
base.username.should == nil
base.password.should == nil
base.ip.should == nil
base.port.should == nil
base.path.should == nil
base.additional_options == nil
end
end

end
10 changes: 10 additions & 0 deletions spec/configuration/syncer/scm/git_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# encoding: utf-8

require File.expand_path('../../../../spec_helper.rb', __FILE__)

describe Backup::Configuration::Syncer::SCM::Git do
it 'should be a subclass of SCM::Base' do
git = Backup::Configuration::Syncer::SCM::Git
git.superclass.should == Backup::Configuration::Syncer::SCM::Base
end
end
10 changes: 10 additions & 0 deletions spec/configuration/syncer/scm/svn_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# encoding: utf-8

require File.expand_path('../../../../spec_helper.rb', __FILE__)

describe Backup::Configuration::Syncer::SCM::SVN do
it 'should be a subclass of SCM::Base' do
svn = Backup::Configuration::Syncer::SCM::SVN
svn.superclass.should == Backup::Configuration::Syncer::SCM::Base
end
end
Loading

0 comments on commit ba62e98

Please sign in to comment.