backupgem 0.0.7 → 0.0.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. data/CHANGELOG +8 -0
  2. data/Rakefile +25 -2
  3. data/bin/backup +1 -1
  4. data/doc/Backup of backupgem.key/Contents/PkgInfo +1 -0
  5. data/doc/Backup of backupgem.key/droppedImage-2.pdf +0 -0
  6. data/doc/Backup of backupgem.key/droppedImage-3.pdf +0 -0
  7. data/doc/Backup of backupgem.key/droppedImage-5.pdf +0 -0
  8. data/doc/Backup of backupgem.key/droppedImage-6.pdf +0 -0
  9. data/doc/Backup of backupgem.key/index.apxl.gz +0 -0
  10. data/doc/Backup of backupgem.key/thumbs/st0.tiff +0 -0
  11. data/doc/Backup of backupgem.key/thumbs/st1.tiff +0 -0
  12. data/doc/Backup of backupgem.key/thumbs/st15.tiff +0 -0
  13. data/doc/Backup of backupgem.key/thumbs/st2.tiff +0 -0
  14. data/doc/Backup of backupgem.key/thumbs/st3-1.tiff +0 -0
  15. data/doc/Backup of backupgem.key/thumbs/st3-2.tiff +0 -0
  16. data/doc/Backup of backupgem.key/thumbs/st3-3.tiff +0 -0
  17. data/doc/Backup of backupgem.key/thumbs/st3-4.tiff +0 -0
  18. data/doc/Backup of backupgem.key/thumbs/st3-5.tiff +0 -0
  19. data/doc/Backup of backupgem.key/thumbs/st3-6.tiff +0 -0
  20. data/doc/Backup of backupgem.key/thumbs/st3-7.tiff +0 -0
  21. data/doc/Backup of backupgem.key/thumbs/st3.tiff +0 -0
  22. data/doc/Backup of backupgem.key/thumbs/st4.tiff +0 -0
  23. data/doc/Backup of backupgem.key/thumbs/st5.tiff +0 -0
  24. data/doc/Backup of backupgem.key/thumbs/st6-1.tiff +0 -0
  25. data/doc/Backup of backupgem.key/thumbs/st6.tiff +0 -0
  26. data/doc/backup_flow.graffle/Icon/r +0 -0
  27. data/doc/backup_flow.graffle/data.plist +320 -0
  28. data/doc/backup_flow.graffle/image1.ai +602 -2
  29. data/doc/backup_flow.graffle/image2.pict +0 -0
  30. data/doc/backupgem.key/Contents/PkgInfo +1 -0
  31. data/doc/backupgem.key/droppedImage-2.pdf +0 -0
  32. data/doc/backupgem.key/droppedImage-3.pdf +0 -0
  33. data/doc/backupgem.key/droppedImage-5.pdf +0 -0
  34. data/doc/backupgem.key/droppedImage-6.pdf +0 -0
  35. data/doc/backupgem.key/index.apxl.gz +0 -0
  36. data/doc/backupgem.key/thumbs/st0.tiff +0 -0
  37. data/doc/backupgem.key/thumbs/st1.tiff +0 -0
  38. data/doc/backupgem.key/thumbs/st15.tiff +0 -0
  39. data/doc/backupgem.key/thumbs/st2.tiff +0 -0
  40. data/doc/backupgem.key/thumbs/st3-1.tiff +0 -0
  41. data/doc/backupgem.key/thumbs/st3-2.tiff +0 -0
  42. data/doc/backupgem.key/thumbs/st3-3.tiff +0 -0
  43. data/doc/backupgem.key/thumbs/st3-4.tiff +0 -0
  44. data/doc/backupgem.key/thumbs/st3-5.tiff +0 -0
  45. data/doc/backupgem.key/thumbs/st3-6.tiff +0 -0
  46. data/doc/backupgem.key/thumbs/st3-7.tiff +0 -0
  47. data/doc/backupgem.key/thumbs/st3.tiff +0 -0
  48. data/doc/backupgem.key/thumbs/st4.tiff +0 -0
  49. data/doc/backupgem.key/thumbs/st5.tiff +0 -0
  50. data/doc/backupgem.key/thumbs/st6-1.tiff +0 -0
  51. data/doc/backupgem.key/thumbs/st6.tiff +0 -0
  52. data/examples/mediawiki_numeric.rb +19 -0
  53. data/lib/backup.rb +8 -0
  54. data/lib/backup/actor.rb +17 -6
  55. data/lib/backup/actor.rb.orig +200 -0
  56. data/lib/backup/cli.rb +45 -6
  57. data/lib/backup/recipes/standard.rb +16 -2
  58. data/lib/backup/rotator.rb +62 -10
  59. data/lib/backup/state_recorder.rb +21 -0
  60. data/tests/s3_test.rb +40 -0
  61. metadata +80 -11
Binary file
@@ -0,0 +1 @@
1
+ ????????
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1,19 @@
1
+ #------------------------------------------------------------------------------
2
+ # Mediawiki Backup script
3
+ # Uses numeric rotation instead of temporal
4
+ # @author: Nate Murray <nate@natemurray.com>
5
+ #------------------------------------------------------------------------------
6
+ action(:content) do
7
+ dump = c[:tmp_dir] + "/test.sql"
8
+ sh "mysqldump -uroot test > #{dump}"
9
+ dump
10
+ end
11
+
12
+ set :rotation_mode, :numeric # base our rotation on numbers not dates
13
+ set :sons_promoted_after, 3 # upgrade to father after 3 sons
14
+ set :fathers_promoted_after, 1
15
+
16
+
17
+
18
+
19
+
data/lib/backup.rb CHANGED
@@ -5,6 +5,7 @@ require 'backup/configuration'
5
5
  require 'backup/extensions'
6
6
  require 'backup/ssh_helpers'
7
7
  require 'backup/date_parser'
8
+ require 'backup/state_recorder'
8
9
 
9
10
  begin
10
11
  require 'aws/s3'
@@ -14,3 +15,10 @@ rescue LoadError
14
15
  # wont have access to s3 methods. It's worth noting
15
16
  # at least version 1.8.4 of ruby is required for s3.
16
17
  end
18
+
19
+ begin
20
+ require 'madeleine'
21
+ rescue LoadError
22
+ # If you don't have madeleine then you just cant use numeric rotation mode
23
+ ::NO_NUMERIC_ROTATION = true
24
+ end
data/lib/backup/actor.rb CHANGED
@@ -155,15 +155,26 @@ module Backup
155
155
  orig = opts[:is_contents_of]
156
156
  tmpdir = c[:tmp_dir] + "/tmp_" + Time.now.strftime("%Y%m%d%H%M%S") +"_#{rand}"
157
157
  new_orig = tmpdir + "/" + File.basename(orig)
158
- sh "mkdir #{tmpdir}"
159
- sh "mkdir #{new_orig}"
160
- cmd = opts[:copy] ? "cp -r" : "mv"
161
- sh "#{cmd} #{orig}/* #{new_orig}/"
158
+ mkdir_p tmpdir
159
+ mkdir_p new_orig
160
+ if opts[:copy]
161
+ cp_r orig + '/.', new_orig
162
+ else
163
+ mv orig + '/.', new_orig
164
+ end
165
+ dirty_file new_orig
166
+ return new_orig
167
+ end
168
+ if opts[:is_hg_repository]
169
+ orig = opts[:is_hg_repository]
170
+ name = opts[:as] || File.basename(orig)
171
+ new_orig = c[:tmp_dir] + '/' + name
172
+ sh "hg clone #{orig} #{new_orig}"
162
173
  dirty_file new_orig
163
174
  return new_orig
164
175
  end
165
176
  raise "Unknown option in :content. Try :is_file, :is_folder " +
166
- "or :is_contents_of"
177
+ ":is_contents_of or :is_hg_repository"
167
178
  end
168
179
 
169
180
  # Given name of a file in +string+ adds that file to @dirty_files. These
@@ -175,7 +186,7 @@ module Backup
175
186
  # +cleanup+ takes every element from @dirty_files and performs an +rm -rf+ on the value
176
187
  def cleanup(opts={})
177
188
  dirty_files.each do |f|
178
- sh "rm -rf #{f}"
189
+ rm_rf f
179
190
  end
180
191
  end
181
192
 
@@ -0,0 +1,200 @@
1
+ require 'rake'
2
+
3
+ module Backup
4
+ include FileUtils
5
+ # An Actor is the entity that actually does the work of determining which
6
+ # servers should be the target of a particular task, and of executing the
7
+ # task on each of them in parallel. An Actor is never instantiated
8
+ # directly--rather, you create a new Configuration instance, and access the
9
+ # new actor via Configuration#actor.
10
+ class Actor
11
+ # The configuration instance associated with this actor.
12
+ attr_reader :configuration
13
+
14
+ # Alias for #configuration
15
+ alias_method :c, :configuration
16
+
17
+ # A hash of the tasks known to this actor, keyed by name. The values are
18
+ # instances of Actor::Action.
19
+ attr_reader :action
20
+
21
+ # A stack of the results of the actions called
22
+ attr_reader :result_history
23
+
24
+ # the rotator instance
25
+ attr_reader :rotator
26
+
27
+ # Returns an Array of Strings specifying dirty files. These files will be
28
+ # +rm -rf+ when the +cleanup+ task is called.
29
+ attr_reader :dirty_files
30
+
31
+ class Action #:nodoc:
32
+ attr_reader :name, :actor, :options
33
+
34
+ def initialize(name, actor, options)
35
+ @name, @actor, @options = name, actor, options
36
+ end
37
+ end
38
+
39
+ def initialize(config) #:nodoc:
40
+ @configuration = config
41
+ @action = {}
42
+ @result_history = []
43
+ @dirty_files = []
44
+ @rotator = Backup::Rotator.new(self)
45
+ end
46
+
47
+ # each action in the action_order is part of the chain. so you start by
48
+ # setting the output as 'nil' then you try to call before_ action, then
49
+ # store the output, then cal action with the args if action takes the args
50
+ # you are sending. if it doesnt give an intelligent error message. do this
51
+ # for all actions. then call after_action with the output if it exists.
52
+ # each time out are calilng the method with the arguemtns f the method
53
+ # exists and the method takes the arguments.
54
+ def start_process!
55
+ configuration[:action_order].each do |a|
56
+ self.send_and_store("before_" + a)
57
+ self.send_and_store(a)
58
+ self.send_and_store("after_" + a)
59
+ end
60
+ last_result
61
+ end
62
+
63
+ def send_and_store(name)
64
+ store_result self.send(name) if self.respond_to? name
65
+ end
66
+
67
+ # Define a new task for this actor. The block will be invoked when this
68
+ # task is called.
69
+ # todo, this might be more complex if the before and after tasks are going
70
+ # to be part of the input and output chain
71
+ def define_action(name, options={}, &block)
72
+ @action[name] = (options[:action_class] || Action).new(name, self, options)
73
+
74
+ if self.respond_to?(name) && !( block_given? || options[:method] )
75
+ # if it was already defined and we aren't trying to re-define it then
76
+ # what we are trying to do is define it the same way it is defined now
77
+ # only with options being sent to it.
78
+ metaclass.send(:alias_method, "old_#{name}".intern, name)
79
+ #self.class.send(:alias_method, "old_#{name}".intern, name)
80
+ #define_method("#{name.to_s}_new".intern) do
81
+ define_method(name) do
82
+ begin
83
+ result = self.send("old_#{name}", options)
84
+ end
85
+ result
86
+ end
87
+ return
88
+ end
89
+
90
+ define_method(name) do
91
+ #logger.debug "executing task #{name}"
92
+ begin
93
+ if block_given?
94
+ result = instance_eval( &block )
95
+ elsif options[:method]
96
+ #result = self.send(options[:method], options[:args])
97
+ result = self.send(options[:method])
98
+ # here we need to have a thing where we can send the arguments
99
+ # define the method 'content' so that would take the other options
100
+ # if there are options (any hash) just send along that hash. this needs more work
101
+ end
102
+ end
103
+ result
104
+ end
105
+
106
+ end
107
+
108
+ def metaclass
109
+ class << self; self; end
110
+ end
111
+
112
+ # rotate Actions
113
+ def via_mv; rotator.rotate_via_mv(last_result); end
114
+ def via_ssh; rotator.rotate_via_ssh(last_result); end
115
+ def via_ftp; rotator.rotate_via_ftp(last_result); end
116
+ def via_s3; rotator.rotate_via_s3(last_result); end
117
+
118
+ # By default, +:content+ can perform one of three actions
119
+ # * +:is_file+
120
+ # * +:is_folder+
121
+ # + +:is_contents_of+
122
+ #
123
+ # Examples:
124
+ # action :content, :is_file => "/path/to/file" # content is a single file
125
+ # action :content, :is_folder => "/path/to/folder" # content is the folder itself
126
+ # action :content, :is_contents_of => "/path/to/other/folder" # files in folder/
127
+ #
128
+ # +:is_file+ and +:is_folder+ are basically the same thing in that they
129
+ # backup the whole file/folder whereas +:is_contents_of+ backs up the
130
+ # <em>contents</em> of the given folder.
131
+ #
132
+ # Note that +:is_contents_of+ performs a very spcific action:
133
+ # * a temporary directory is created
134
+ # * all of the files are moved (including subdirectories) into the temporary directory
135
+ # * the archive is created from the temporary directory
136
+ #
137
+ # If you wish to copy the files out of the original directory instead of
138
+ # moving them. Then you may specify the +copy+ option passing a +true+
139
+ # value like so:
140
+ #
141
+ # action :content, :is_contents_of => "/path/to/other/folder",
142
+ # :copy => true
143
+ #
144
+ # This will copy recursively.
145
+ #
146
+ # If this is not your desired behavior then you can easily write your own.
147
+ # Also, these options only work for local files. If you are getting the
148
+ # files from a foreign server you will have to write a custom +:content+
149
+ # method.
150
+ #
151
+ def content(opts={})
152
+ return opts[:is_file] if opts[:is_file]
153
+ return opts[:is_folder] if opts[:is_folder]
154
+ if opts[:is_contents_of]
155
+ orig = opts[:is_contents_of]
156
+ tmpdir = c[:tmp_dir] + "/tmp_" + Time.now.strftime("%Y%m%d%H%M%S") +"_#{rand}"
157
+ new_orig = tmpdir + "/" + File.basename(orig)
158
+ mkdir_p tmpdir
159
+ mkdir_p new_orig
160
+ if opts[:copy]
161
+ cp_r orig + '/.', new_orig
162
+ else
163
+ mv orig + '/.', new_orig
164
+ end
165
+ dirty_file new_orig
166
+ return new_orig
167
+ end
168
+ raise "Unknown option in :content. Try :is_file, :is_folder " +
169
+ "or :is_contents_of"
170
+ end
171
+
172
+ # Given name of a file in +string+ adds that file to @dirty_files. These
173
+ # files will be removed when the +cleanup+ task is called.
174
+ def dirty_file(string)
175
+ @dirty_files << string
176
+ end
177
+
178
+ # +cleanup+ takes every element from @dirty_files and performs an +rm -rf+ on the value
179
+ def cleanup(opts={})
180
+ dirty_files.each do |f|
181
+ rm_rf f
182
+ end
183
+ end
184
+
185
+ private
186
+ def define_method(name, &block)
187
+ metaclass.send(:define_method, name, &block)
188
+ end
189
+
190
+ def store_result(result)
191
+ @result_history.push result
192
+ end
193
+
194
+ def last_result
195
+ @result_history.last
196
+ end
197
+
198
+ end
199
+
200
+ end
data/lib/backup/cli.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  require 'optparse'
2
2
  require 'backup'
3
+ require 'yaml'
3
4
 
4
5
  module Backup
5
6
  # The CLI class encapsulates the behavior of backup when it is invoked
@@ -24,15 +25,14 @@ module Backup
24
25
  :global => nil }
25
26
 
26
27
  OptionParser.new do |opts|
27
- opts.banner = "Usage: #{$0} [options] [args]"
28
+ opts.banner = "Usage: #{$0} [options]"
28
29
 
29
30
  opts.separator ""
30
31
  opts.separator "Recipe Options -----------------------"
31
32
  opts.separator ""
32
33
 
33
- opts.on("-r", "--recipe RECIPE",
34
- "A recipe file to load. Multiple recipes may",
35
- "be specified, and are loaded in the given order."
34
+ opts.on("-r", "--recipe RECIPE ",
35
+ "A recipe file to load. Multiple recipes is DEPRECATED and not fully functional."
36
36
  ) { |value| @options[:recipes] << value }
37
37
 
38
38
  opts.on("-s", "--set NAME=VALUE",
@@ -49,6 +49,10 @@ module Backup
49
49
  "file +global.rb+ in the same directory."
50
50
  ) { |value| @options[:recipes] << value }
51
51
 
52
+ opts.on("-q", "--quiet",
53
+ "suppresses much of the output of backup, except",
54
+ "for error messages") { verbose(false) }
55
+
52
56
  if args.empty?
53
57
  puts opts
54
58
  exit
@@ -74,7 +78,7 @@ module Backup
74
78
 
75
79
  private
76
80
  def check_options!
77
- # performa sanity check
81
+ # perform a sanity check
78
82
  end
79
83
 
80
84
  # Load the recipes specified by the options, and execute the actions
@@ -91,7 +95,14 @@ module Backup
91
95
  global = options[:global] || File.dirname(recipe) + "/global.rb"
92
96
  config.load global if File.exists? global # cache this?
93
97
  end
94
- options[:recipes].each { |recipe| config.load(recipe) }
98
+
99
+ options[:recipes].each_with_index do |recipe,i|
100
+ config.load(recipe)
101
+ $state = setup_saved_state(recipe, config)
102
+ warn "DEPRICATED: Using multiple recipes with one command is deprecated for the time being. Just run a different command if you want to do two recipes at the same time" if i > 0
103
+
104
+ end
105
+
95
106
  #options[:vars].each { |name, value| config.set(name, value) }
96
107
 
97
108
  actor = config.actor
@@ -100,6 +111,34 @@ module Backup
100
111
  #options[:actions].each { |action| actor.send action }
101
112
  end
102
113
 
114
+
115
+ # Setup the persistant state using madeline
116
+ def setup_saved_state(recipe, config)
117
+ if defined? ::NO_NUMERIC_ROTATION
118
+ if :numeric == config[:rotation_mode]
119
+ puts "Missing Gem: :numeric :rotation mode is not valid unless you have madeleine installed. try: 'gem install madeleine'"
120
+ exit 1
121
+ end
122
+ return nil
123
+ end
124
+
125
+ saved_state_folder = config[:saved_state_folder] || File.join(config[:backup_path], ".backupgem_#{File.basename(recipe)}_state")
126
+
127
+ state = SnapshotMadeleine.new(saved_state_folder, YAML) do
128
+ StateRecorder.new
129
+ end
130
+ state.system.saved_state_folder = saved_state_folder
131
+ state
132
+ end
133
+
103
134
  end
104
135
  end
105
136
 
137
+ at_exit {
138
+
139
+ if !defined?(::NO_NUMERIC_ROTATION) && defined?($state)
140
+ $state.take_snapshot
141
+ $state.system.cleanup_snapshots
142
+ end
143
+ }
144
+
@@ -38,6 +38,13 @@ set :gpg_encrypt_options, ""
38
38
  # Read more about that below
39
39
  set :rotation_method, :gfs
40
40
 
41
+ # rotation mode - temporal or numeric. For instance
42
+ # temporal mode would continue to be the default and work with
43
+ # :son_promoted_on. The promotions are based on days. This works well for 1 backup per day.
44
+ # numeric works by promoting after every number of creations. This is better for multiple backups per day.
45
+ # numeric mode uses :sons_promoted_after
46
+ set :rotation_mode, :temporal
47
+
41
48
  # :mon-sun
42
49
  # :last_day_of_the_month # whatever son_promoted on son was, but the last of the month
43
50
  # everything else you can define with a Runt object
@@ -57,13 +64,19 @@ set :sons_to_keep, 14
57
64
  set :fathers_to_keep, 6
58
65
  set :grandfathers_to_keep, 6 # 6 months, by default
59
66
 
67
+ # These options are only used if :rotation_mode is :numeric.
68
+ # This is better if you are doing multiple backups per day.
69
+ # This setting says that every 14th son will be promoted to a father.
70
+ set :sons_promoted_after, 14
71
+ set :fathers_promoted_after, 6
60
72
 
61
73
  # -------------------------
62
74
  # Standard Actions
63
75
  # -------------------------
64
76
  action(:tar_bz2) do
65
77
  name = c[:tmp_dir] + "/" + File.basename(last_result) + ".tar.bz2"
66
- sh "tar -cvjf #{name} #{last_result}"
78
+ v = "v" if verbose
79
+ sh "tar -c#{v}jf #{name} #{last_result}"
67
80
  name
68
81
  end
69
82
 
@@ -78,7 +91,8 @@ action(:scp) do
78
91
  end
79
92
 
80
93
  action(:mv) do
81
- sh "mv #{last_result} #{c[:backup_path]}/"
94
+ move last_result, c[:backup_path] # has to be move (not mv) to avoid infinite
95
+ # recursion
82
96
  c[:backup_path] + "/" + File.basename(last_result)
83
97
  end
84
98