Permalink
Browse files

Added new SCM Syncers: SVN and Git

  • Loading branch information...
1 parent 1d923a9 commit ba62e989a2168abfd5c7ef0fb4e57eb3e28d46dc @kikito kikito committed Feb 16, 2012
View
@@ -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)
View
@@ -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
##
@@ -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
View
@@ -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']
]
@@ -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
@@ -0,0 +1,12 @@
+# encoding: utf-8
+
+module Backup
+ module Configuration
+ module Syncer
+ module SCM
+ class Git < Base
+ end
+ end
+ end
+ end
+end
@@ -0,0 +1,12 @@
+# encoding: utf-8
+
+module Backup
+ module Configuration
+ module Syncer
+ module SCM
+ class SVN < Base
+ end
+ end
+ end
+ end
+end
View
@@ -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
@@ -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
@@ -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
@@ -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
@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.

@kikito

kikito Sep 29, 2012

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.

@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.

@kikito

kikito via email Oct 1, 2012

+
+
+ def backup_repository!(repository)
+ initialize_repository!(repository) unless local_repository_exists?(repository)
+ update_repository!(repository)
+ end
+ end
+ end
+ end
+end
View
@@ -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)
@@ -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
@@ -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
@@ -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
Oops, something went wrong.

0 comments on commit ba62e98

Please sign in to comment.