backupgem 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,89 @@
1
+ #------------------------------------------------------------------------------
2
+ # Backup Global Settings
3
+ # @author: Nate Murray <nate@natemurray.com>
4
+ # @date: Mon Aug 28 07:28:22 PDT 2006
5
+ #
6
+ # The settings contained in this file will be global for all tasks and can be
7
+ # overridden locally.
8
+ #------------------------------------------------------------------------------
9
+
10
+ # Sepcify sever settings
11
+ set :servers, %w{ localhost }
12
+ set :action_order, %w{ content compress encrypt deliver rotate cleanup }
13
+
14
+ # Name of the SSH user
15
+ set :ssh_user, ENV['USER']
16
+
17
+ # Path to your SSH key
18
+ set :identity_key, ENV['HOME'] + "/.ssh/id_rsa"
19
+
20
+ # Set global actions
21
+ action :compress, :method => :tar_bz2
22
+ action :deliver, :method => :mv # action :deliver, :method => :scp
23
+ action :rotate, :method => :via_mv # action :rotate, :method => :via_ssh
24
+ # action :encrypt, :method => :gpg
25
+
26
+ # Specify a directory that backup can use as a temporary directory
27
+ set :tmp_dir, "/tmp"
28
+
29
+ # Options to be passed to gpg when encrypting
30
+ set :encrypt, false
31
+ set :gpg_encrypt_options, ""
32
+
33
+ # These settings specify the rotation variables
34
+ # Rotation method. Currently the only method is gfs, grandfather-father-son.
35
+ # Read more about that below
36
+ set :rotation_method, :gfs
37
+
38
+ # :mon-sun
39
+ # :last_day_of_the_month # whatever son_promoted on son was, but the last of the month
40
+ # everything else you can define with a Runt object
41
+ # set :son_created_on, :every_day - if you dont want a son created dont run the program
42
+ # a backup is created every time the program is run
43
+
44
+ set :son_promoted_on, :fri
45
+ set :father_promoted_on, :last_fri_of_the_month
46
+
47
+ # more complex
48
+ # mon_wed_fri = Runt::DIWeek.new(Runt::Mon) |
49
+ # Runt::DIWeek.new(Runt::Wed) |
50
+ # Runt::DIWeek.new(Runt::Fri)
51
+ # set :son_promoted_on, mon_wed_fri
52
+
53
+ set :sons_to_keep, 14
54
+ set :fathers_to_keep, 6
55
+ set :grandfathers_to_keep, 6 # 6 months, by default
56
+
57
+
58
+ # -------------------------
59
+ # Standard Actions
60
+ # -------------------------
61
+ action(:tar_bz2) do
62
+ name = c[:tmp_dir] + "/" + File.basename(last_result) + ".tar.bz2"
63
+ sh "tar -cvjf #{name} #{last_result}"
64
+ name
65
+ end
66
+
67
+ action(:scp) do
68
+ # what should the default scp task be?
69
+ # scp the local file to the foreign directory. same name.
70
+ c[:servers].each do |server|
71
+ host = server =~ /localhost/ ? "" : "#{server}:"
72
+ sh "scp #{last_result} #{host}#{c[:backup_path]}/"
73
+ end
74
+ c[:backup_path] + "/" + File.basename(last_result)
75
+ end
76
+
77
+ action(:mv) do
78
+ sh "mv #{last_result} #{c[:backup_path]}/"
79
+ c[:backup_path] + "/" + File.basename(last_result)
80
+ end
81
+
82
+ action(:encrypt) do
83
+ result = last_result
84
+ if c[:encrypt]
85
+ sh "gpg #{c[:gpg_encrypt_options]} --encrypt #{last_result}"
86
+ result = last_result + ".gpg" # ?
87
+ end
88
+ result
89
+ end
@@ -0,0 +1,155 @@
1
+ require "rake"
2
+
3
+ module Backup
4
+ class Rotator
5
+ include FileUtils
6
+
7
+ attr_reader :actor
8
+ attr_reader :configuration
9
+ alias_method :c, :configuration
10
+
11
+ def initialize(actor)
12
+ @actor = actor
13
+ @configuration = actor.configuration
14
+ end
15
+
16
+ # Take the last result and rotate it via mv on the local machine
17
+ def rotate_via_mv(last_result)
18
+ # verify that each of the directories exist, grandfathers, fathers, sons
19
+ hierarchy.each { |m| verify_local_backup_directory_exists(m) }
20
+
21
+ # place todays backup into the specified directory with a timestamp.
22
+ newname = timestamped_prefix(last_result)
23
+ sh "mv #{last_result} #{place_in}/#{newname}"
24
+
25
+ cleanup_via_mv(place_in, how_many_to_keep_today)
26
+ end
27
+
28
+ def rotate_via_ssh(last_result)
29
+ ssh = Backup::SshActor.new(c)
30
+ ssh.connect
31
+ ssh.run "echo \"#{last_result}\""
32
+
33
+ hierarchy.each do |m|
34
+ dir = c[:backup_path] + "/" + m
35
+ ssh.verify_directory_exists(dir)
36
+ end
37
+
38
+ newname = timestamped_prefix(last_result)
39
+ ssh.run "mv #{last_result} #{place_in}/#{newname}"
40
+
41
+ ssh.cleanup_directory(place_in, how_many_to_keep_today)
42
+ ssh.close
43
+ end
44
+
45
+ # TODO
46
+ def rotate_via_ftp(last_result)
47
+ # ftp = Backup::FtpActor.new(c)
48
+ # ftp.connect
49
+ #
50
+ # hierarchy.each do |m|
51
+ # dir = c[:backup_path] + "/" + m
52
+ # ftp.verify_directory_exists(dir)
53
+ # end
54
+ #
55
+ # newname = timestamped_prefix(last_result)
56
+ # ftp.run "mv #{last_result} #{place_in}/#{newname}"
57
+ #
58
+ # ftp.cleanup_directory(place_in, how_many_to_keep_today)
59
+ # ftp.close
60
+ end
61
+
62
+ def create_sons_today?; is_today_a? :son_created_on; end
63
+ def promote_sons_today?; is_today_a? :son_promoted_on; end
64
+ def promote_fathers_today?; is_today_a? :father_promoted_on; end
65
+
66
+ # old ( offset_days % c[:son_promoted_on] ) == 0 ? true : false
67
+ # old ( offset_days % (c[:son_promoted_on] * c[:father_promoted_on]) ) == 0 ? true : false
68
+
69
+ private
70
+ def is_today_a?(symbol)
71
+ t = $test_time || Date.today
72
+ day = DateParser.date_from( c[symbol] )
73
+ day.include?( t )
74
+ end
75
+
76
+ def verify_local_backup_directory_exists(dir)
77
+ path = c[:backup_path]
78
+ full = path + "/" + dir
79
+ unless File.exists?(full)
80
+ sh "mkdir -p #{full}"
81
+ end
82
+ end
83
+
84
+ #def offset_days
85
+ # t = $test_time || Time.now # todo, write a test to use this
86
+ # num_from_day = Time.num_from_day( c[:promote_on] )
87
+ # offset = ( t.days_since_epoch + num_from_day + 0)
88
+ # offset
89
+ #end
90
+
91
+ def cleanup_via_mv(where, num_keep)
92
+
93
+ files = Dir[where + "/*"].sort
94
+ diff = files.size - num_keep
95
+
96
+ 1.upto( diff ) do
97
+ extra = files.shift
98
+ sh "rm #{extra}"
99
+ end
100
+ end
101
+
102
+ def hierarchy
103
+ %w{grandfathers fathers sons}
104
+ end
105
+
106
+ # figure out which generation today's backup is
107
+ public
108
+ def todays_generation
109
+ goes_in = promote_fathers_today? ? "grandfathers" : \
110
+ promote_sons_today? ? "fathers" : "sons"
111
+ end
112
+
113
+ private
114
+ def place_in
115
+ goes_in = todays_generation
116
+ place_in = c[:backup_path] + "/" + goes_in
117
+ end
118
+
119
+
120
+ # Given +name+ returns a timestamped version of name.
121
+ def timestamped_prefix(name)
122
+ newname = Time.now.strftime("%Y-%m-%d-%H-%M-%S_") + File.basename(name)
123
+ end
124
+
125
+ # Returns the number of sons to keep. Looks for config values +:sons_to_keep+,
126
+ # +:son_promoted_on+. Default +14+.
127
+ def sons_to_keep
128
+ c[:sons_to_keep] || c[:son_promoted_on] || 14
129
+ end
130
+
131
+ # Returns the number of fathers to keep. Looks for config values +:fathers_to_keep+,
132
+ # +:fathers_promoted_on+. Default +6+.
133
+ def fathers_to_keep
134
+ c[:fathers_to_keep] || c[:father_promoted_on] || 6
135
+ end
136
+
137
+ # Returns the number of grandfathers to keep. Looks for config values
138
+ # +:grandfathers_to_keep+. Default +6+.
139
+ def gfathers_to_keep
140
+ c[:grandfathers_to_keep] || 6
141
+ end
142
+
143
+ # This method returns the number of how many to keep in whatever today
144
+ # goes in. Example: if today is a day to create a +son+ then this
145
+ # function returns the value of +sons_to_keep+. If today is a +father+
146
+ # then +fathers_to_keep+ etc.
147
+ def how_many_to_keep_today
148
+ goes_in = todays_generation
149
+ keep = goes_in =~ /^sons$/ ? sons_to_keep :
150
+ goes_in =~ /^fathers$/ ? fathers_to_keep :
151
+ goes_in =~ /^grandfathers$/ ? gfathers_to_keep : 14
152
+ end
153
+
154
+ end
155
+ end
@@ -0,0 +1,138 @@
1
+ # large portions borrowed from capistrano
2
+ require 'rubygems'
3
+ require 'net/ssh'
4
+
5
+ # TODO - add in a way to extend the belay script to work with this. thats a
6
+ # good idea for the belay script over all. write the kernal extensions, and the
7
+ # rake extensions. form there write an example third party extension.
8
+ module Backup
9
+ class Command
10
+ attr_reader :command, :options
11
+ attr_reader :actor
12
+
13
+ def initialize(server_name, command, callback, options, actor) #:nodoc:
14
+ @command = command.strip.gsub(/\r?\n/, "\\\n")
15
+ @callback = callback
16
+ @options = options
17
+ @actor = actor
18
+ @channels = open_channels
19
+ end
20
+
21
+ def process!
22
+ since = Time.now
23
+ loop do
24
+ active = 0
25
+ @channels.each do |ch|
26
+ next if ch[:closed]
27
+ active += 1
28
+ ch.connection.process(true)
29
+ end
30
+
31
+ break if active == 0
32
+ if Time.now - since >= 1
33
+ since = Time.now
34
+ @channels.each { |ch| ch.connection.ping! }
35
+ end
36
+ sleep 0.01 # a brief respite, to keep the CPU from going crazy
37
+ end
38
+
39
+ #logger.trace "command finished"
40
+
41
+ if failed = @channels.detect { |ch| ch[:status] != 0 }
42
+ raise "command #{@command.inspect} failed"
43
+ end
44
+
45
+ self
46
+ end
47
+
48
+ def open_channels
49
+ channel = actor.session.open_channel do |channel|
50
+ channel.request_pty( :want_reply => true )
51
+ channel[:actor] = @actor
52
+
53
+ channel.on_success do |ch|
54
+ #logger.trace "executing command", ch[:host]
55
+ ch.exec command
56
+ ch.send_data options[:data] if options[:data]
57
+ end
58
+
59
+ channel.on_data do |ch, data|
60
+ puts data
61
+ @callback[ch, :out, data] if @callback
62
+ end
63
+
64
+ channel.on_failure do |ch|
65
+ #logger.important "could not open channel", ch[:host]
66
+ # puts "we got a faulure"
67
+ ch.close
68
+ end
69
+
70
+ channel.on_request do |ch, request, reply, data|
71
+ ch[:status] = data.read_long if request == "exit-status"
72
+ end
73
+
74
+ channel.on_close do |ch|
75
+ ch[:closed] = true
76
+ end
77
+ end
78
+ [channel]
79
+ end
80
+ end # end Class Command
81
+
82
+ class SshActor
83
+
84
+ #def self.new_for_ssh(server_name)
85
+ # a = new(server_name)
86
+ #end
87
+
88
+ attr_reader :session
89
+ attr_reader :config
90
+ alias_method :c, :config
91
+
92
+ def initialize(config)
93
+ @config = config
94
+ end
95
+
96
+ def connect
97
+ c[:servers].each do |server| # todo, make this actually work
98
+ @session = Net::SSH.start(
99
+ server,
100
+ c[:ssh_user],
101
+ :host_key => "ssh-rsa",
102
+ :keys => [ c[:identity_key] ],
103
+ :auth_methods => %w{ publickey } )
104
+ end
105
+ end
106
+
107
+ def run(cmd, options={}, &block)
108
+ #logger.debug "executing #{cmd.strip.inspect}"
109
+ puts "executing #{cmd.strip.inspect}"
110
+ command = Command.new(@server_name, cmd, block, options, self)
111
+ command.process! # raises an exception if command fails on any server
112
+ end
113
+
114
+ def on_remote(&block)
115
+ connect
116
+ self.instance_eval(&block)
117
+ close
118
+ end
119
+
120
+ def close
121
+ @session.close
122
+ end
123
+
124
+ def verify_directory_exists(dir)
125
+ run "if [ -d '#{dir}' ]; then true; else mkdir -p '#{dir}'; fi"
126
+ end
127
+
128
+ def cleanup_directory(dir, keep)
129
+ puts "Cleaning up"
130
+ cleanup = <<-END
131
+ LOOK_IN="#{dir}"; COUNT=`ls -1 $LOOK_IN | wc -l`; MAX=#{keep}; if (( $COUNT > $MAX )); then let "OFFSET=$COUNT-$MAX"; i=1; for f in `ls -1 $LOOK_IN | sort`; do if (( $i <= $OFFSET )); then CMD="rm $LOOK_IN/$f"; echo $CMD; $CMD; fi; let "i=$i + 1"; done; else true; fi
132
+ END
133
+ run cleanup # todo make it so that even this can be overridden
134
+ end
135
+
136
+ end # end class SshActor
137
+
138
+ end # end Module Backup
data/lib/backup.rb ADDED
@@ -0,0 +1,7 @@
1
+ require 'rubygems'
2
+ require 'runt'
3
+ require 'date'
4
+ require 'backup/configuration'
5
+ require 'backup/extensions'
6
+ require 'backup/ssh_helpers'
7
+ require 'backup/date_parser'
@@ -0,0 +1,70 @@
1
+ #!/usr/bin/ruby
2
+ require File.dirname(__FILE__) + "/tests_helper"
3
+
4
+ class ActorTest < Test::Unit::TestCase
5
+ def setup
6
+ @config = Backup::Configuration.new
7
+ @config.load "standard"
8
+ @actor = @config.actor
9
+ setup_tmp_backup_dir
10
+ end
11
+
12
+ def dont_test_exists
13
+ assert @actor
14
+ end
15
+
16
+ def dont_test_is_file
17
+ dir = create_tmp_files
18
+ config = <<-END
19
+ action :content, :is_file => "#{dir}/1"
20
+ END
21
+ @config.load :string => config
22
+ assert result = @actor.content
23
+ assert File.exists?(result)
24
+ puts "content result is: #{result}"
25
+ @actor.start_process!
26
+ end
27
+
28
+ def dont_test_is_folder
29
+ dir = create_tmp_files
30
+ config = <<-END
31
+ action :content, :is_folder => "#{dir}"
32
+ END
33
+ @config.load :string => config
34
+ assert result = @actor.content
35
+ assert File.exists?(result)
36
+ assert File.directory?(result)
37
+ puts "content result is: #{result}"
38
+ @actor.start_process!
39
+ end
40
+
41
+ def test_is_contents_of
42
+ dir = create_tmp_files
43
+ config = <<-END
44
+ action :content, :is_contents_of => "#{dir}", :copy => true
45
+ END
46
+ @config.load :string => config
47
+ #@actor.content
48
+ #@actor.cleanup
49
+ @actor.start_process!
50
+ end
51
+
52
+ private
53
+ def setup_tmp_backup_dir
54
+ newtmp = @config[:tmp_dir] + "/backup_#{rand}_" + Time.now.strftime("%Y%m%d%H%M%S")
55
+ sh "mkdir #{newtmp}"
56
+ config = <<-END
57
+ set :backup_path, "#{newtmp}"
58
+ END
59
+ @config.load :string => config
60
+ end
61
+
62
+ def create_tmp_files
63
+ newtmp = @config[:tmp_dir] + "/test_#{rand}_" + Time.now.strftime("%Y%m%d%H%M%S")
64
+ sh "mkdir #{newtmp}"
65
+ 0.upto(5) { |i| sh "touch #{newtmp}/#{i}" }
66
+ newtmp
67
+ end
68
+
69
+ end
70
+
data/tests/cleanup.sh ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/bash
2
+ LOOK_IN="/var/local/backups/mediawiki/sons"; COUNT=`ls -1 $LOOK_IN | wc -l`; MAX=14; if (( $COUNT > $MAX )); then let "OFFSET=$COUNT-$MAX"; i=1; for f in `ls -1 $LOOK_IN | sort`; do if (( $i <= $OFFSET )); then CMD="rm $LOOK_IN/$f"; echo $CMD; $CMD; fi; echo "$i $f"; let "i=$i + 1"; done; else true; fi
@@ -0,0 +1,32 @@
1
+ #!/usr/bin/ruby
2
+ require File.dirname(__FILE__) + "/tests_helper"
3
+ require 'runt'
4
+ require 'date'
5
+
6
+ class RotationTest < Test::Unit::TestCase
7
+ def setup
8
+ # setup our config to test the objects
9
+ @config = Backup::Configuration.new
10
+ @config.load "standard"
11
+ @rotator = Backup::Rotator.new(@config.actor)
12
+ end
13
+
14
+ def test_rotator
15
+ t = Date.today
16
+ r = @rotator
17
+ 0.upto(14) do |i|
18
+ $test_time = t
19
+ print t.to_s + t.strftime(" #{i} %a ").to_s
20
+ puts r.todays_generation
21
+ t += 1
22
+ end
23
+ end
24
+
25
+ def test_date_parsing
26
+ dp = Backup::DateParser.new
27
+ assert fri = dp.date_from(:fri)
28
+ assert daily = dp.date_from(:daily)
29
+ assert last = dp.date_from(:last_mon_of_the_month)
30
+ assert_raise(RuntimeError) { dp.date_from(:asdfasdf) }
31
+ end
32
+ end
data/tests/ssh_test.rb ADDED
@@ -0,0 +1,21 @@
1
+ require File.dirname(__FILE__) + "/tests_helper"
2
+
3
+ class SSHTest < Test::Unit::TestCase
4
+ def setup
5
+ @config = Backup::Configuration.new
6
+ @config.load "standard"
7
+ @config.action :deliver, :method => :via_ssh
8
+ @actor = Backup::SshActor.new(@config)
9
+ end
10
+
11
+ def test_exists
12
+ assert @config
13
+ assert @actor
14
+ end
15
+
16
+ def test_on_remote
17
+ @actor.on_remote do
18
+ run "echo \"hello $HOSTNAME\""
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,5 @@
1
+ $:.unshift File.dirname(__FILE__) + "/../lib"
2
+
3
+ require 'test/unit'
4
+ require 'backup'
5
+ require 'backup/extensions'
metadata ADDED
@@ -0,0 +1,99 @@
1
+ --- !ruby/object:Gem::Specification
2
+ rubygems_version: 0.9.0
3
+ specification_version: 1
4
+ name: backupgem
5
+ version: !ruby/object:Gem::Version
6
+ version: 0.0.2
7
+ date: 2006-10-06 00:00:00 -07:00
8
+ summary: Beginning-to-end solution for backups and rotation.
9
+ require_paths:
10
+ - lib
11
+ email: nate@natemurray.com
12
+ homepage: http://tech.natemurray.com/backup
13
+ rubyforge_project:
14
+ description:
15
+ autorequire: backupgem
16
+ default_executable:
17
+ bindir: bin
18
+ has_rdoc: true
19
+ required_ruby_version: !ruby/object:Gem::Version::Requirement
20
+ requirements:
21
+ - - ">"
22
+ - !ruby/object:Gem::Version
23
+ version: 0.0.0
24
+ version:
25
+ platform: ruby
26
+ signing_key:
27
+ cert_chain:
28
+ post_install_message:
29
+ authors:
30
+ - Nate Murray
31
+ files:
32
+ - bin/backup
33
+ - bin/commands.sh
34
+ - lib/backup
35
+ - lib/backup.rb
36
+ - lib/backup/actor.rb
37
+ - lib/backup/cli.rb
38
+ - lib/backup/configuration.rb
39
+ - lib/backup/date_parser.rb
40
+ - lib/backup/extensions.rb
41
+ - lib/backup/recipes
42
+ - lib/backup/rotator.rb
43
+ - lib/backup/ssh_helpers.rb
44
+ - lib/backup/recipes/standard.rb
45
+ - tests/actor_test.rb
46
+ - tests/cleanup.sh
47
+ - tests/rotation_test.rb
48
+ - tests/ssh_test.rb
49
+ - tests/tests_helper.rb
50
+ - examples/global.rb
51
+ - examples/mediawiki.rb
52
+ - doc/index.html
53
+ - doc/LICENSE-GPL
54
+ - doc/styles.css
55
+ - README
56
+ - CHANGELOG
57
+ test_files:
58
+ - tests/actor_test.rb
59
+ - tests/rotation_test.rb
60
+ - tests/ssh_test.rb
61
+ rdoc_options: []
62
+
63
+ extra_rdoc_files:
64
+ - README
65
+ - CHANGELOG
66
+ executables: []
67
+
68
+ extensions: []
69
+
70
+ requirements: []
71
+
72
+ dependencies:
73
+ - !ruby/object:Gem::Dependency
74
+ name: rake
75
+ version_requirement:
76
+ version_requirements: !ruby/object:Gem::Version::Requirement
77
+ requirements:
78
+ - - ">="
79
+ - !ruby/object:Gem::Version
80
+ version: 0.7.1
81
+ version:
82
+ - !ruby/object:Gem::Dependency
83
+ name: runt
84
+ version_requirement:
85
+ version_requirements: !ruby/object:Gem::Version::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: 0.3.0
90
+ version:
91
+ - !ruby/object:Gem::Dependency
92
+ name: net-ssh
93
+ version_requirement:
94
+ version_requirements: !ruby/object:Gem::Version::Requirement
95
+ requirements:
96
+ - - ">="
97
+ - !ruby/object:Gem::Version
98
+ version: 1.0.9
99
+ version: