backupgem 0.0.9 → 0.0.11

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG CHANGED
@@ -1,4 +1,8 @@
1
1
  = Backup Changelog
2
+ == Version 0.0.11
3
+ * Removed the tmpdir directive because it was screwing up the meta-code
4
+ * Moved the graffle and keynote files to an "extras" directory because it was breaking windows machines
5
+
2
6
  == Version 0.0.8
3
7
  * The attached patch will add the 'backup' executable to your bin path using
4
8
  the built-in RubyGems method for adding things to bin. Thanks to Anthony
data/Rakefile CHANGED
@@ -6,13 +6,13 @@ require 'rake/testtask'
6
6
 
7
7
  spec = Gem::Specification.new do |s|
8
8
  s.name = 'backupgem'
9
- s.version = '0.0.9'
9
+ s.version = '0.0.11'
10
10
  s.author = "Nate Murray"
11
11
  s.email = "nate@natemurray.com"
12
12
  s.homepage = "http://tech.natemurray.com/backup"
13
13
  s.platform = Gem::Platform::RUBY
14
14
  s.summary = "Beginning-to-end solution for backups and rotation."
15
- s.files = FileList["{bin,lib,tests,examples,doc}/**/*.{txt,html,css}"].to_a
15
+ s.files = FileList["{bin,lib,tests,examples,doc}/**/*"].to_a
16
16
  s.require_path = "lib"
17
17
  s.autorequire = "backupgem"
18
18
  s.test_files = FileList["{tests}/**/*test.rb"].to_a
@@ -0,0 +1,2 @@
1
+ #!/bin/bash
2
+ ./bin/backup --recipe examples/mediawiki.rb
@@ -0,0 +1,28 @@
1
+ #------------------------------------------------------------------------------
2
+ # Global Settings for Backup
3
+ # This file sets the global settings for all recipes in the same directory
4
+ # @author: Nate Murray <nate@natemurray.com>
5
+ #------------------------------------------------------------------------------
6
+
7
+ # This file specifies many, but not all, of the setting you can change for Backup.
8
+ # Any setting that is set to the default is commented out. Uncomment and
9
+ # change any to your liking.
10
+
11
+ # A single or array of servers to execute the backup tasks on
12
+ # set :servers, %w{ localhost someotherhost } # default: localhost
13
+
14
+ # The directory to place the backup on the backup server
15
+ set :backup_path, "/var/local/backups/mediawiki"
16
+
17
+ # Name of the SSH user
18
+ # set :ssh_user, ENV['USER']
19
+
20
+ # Path to your SSH key
21
+ # set :identity_key, ENV['HOME'] + "/.ssh/id_rsa"
22
+
23
+ # Set global actions
24
+ # action :compress, :method => :tar_bz2 # tar_bz2 is the default
25
+ # action :deliver, :method => :mv # mv is the default
26
+ # action :encrypt, :method => :gpg # gpg is the default
27
+
28
+ set :tmp_dir, File.dirname(__FILE__) + "/../tmp"
@@ -0,0 +1,24 @@
1
+ #------------------------------------------------------------------------------
2
+ # Mediawiki Backup script
3
+ # @author: Nate Murray <nate@natemurray.com>
4
+ #------------------------------------------------------------------------------
5
+ action(:content) do
6
+ dump = c[:tmp_dir] + "/test.sql"
7
+ sh "mysqldump -uroot test > #{dump}"
8
+ # should something happen here to cleanup the last task? maybe something
9
+ # should happen to the stack. like if the next task goes through then you
10
+ # remove he last file from the stack. Ah. if everything goes well you clean
11
+ # up everything from the stack.
12
+ dump
13
+ end
14
+
15
+ # action :compress, :method => :tar_bz2 # could be set in global
16
+ # action :deliver, :method => :scp # could be set in global
17
+
18
+ # settings for backup servers are global unless specified otherwise
19
+ # rotate settings are global unless specified herer
20
+
21
+
22
+
23
+
24
+
@@ -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
+
@@ -0,0 +1,35 @@
1
+ #------------------------------------------------------------------------------
2
+ # Example S3 Backup script
3
+ # @author: Jason L. Perry <jasper@ambethia.com>
4
+ #------------------------------------------------------------------------------
5
+
6
+ # Set the name of the s3 bucket you want to store your backups in.
7
+ # Your Access ID is prepended to this to avoid naming conflicts.
8
+ set :backup_path, "database_backup"
9
+
10
+ # You can specify your keys here, or set them as environment variables:
11
+ # AMAZON_ACCESS_KEY_ID
12
+ # AMAZON_SECRET_ACCESS_KEY
13
+ set :aws_access, '123'
14
+ set :aws_secret, 'ABC'
15
+
16
+ # S3 does not support renaming objects, so rotation data is stored in an
17
+ # index. You can specify a different key for index here, if you need to.
18
+ #
19
+ # set :rotation_object_key, 'backup_rotation_index.yml'
20
+
21
+ action(:content) do
22
+ dump = c[:tmp_dir] + "/databases.sql"
23
+ sh "mysqldump -uroot --all-databases > #{dump}"
24
+ dump
25
+ end
26
+
27
+ action :deliver, :method => :s3
28
+ action :rotate, :method => :via_s3
29
+
30
+ set :son_promoted_on, :fri
31
+ set :father_promoted_on, :last_fri_of_the_month
32
+
33
+ set :sons_to_keep, 7
34
+ set :fathers_to_keep, 5
35
+ set :grandfathers_to_keep, 12
@@ -0,0 +1,24 @@
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'
8
+ require 'backup/state_recorder'
9
+
10
+ begin
11
+ require 'aws/s3'
12
+ require 'backup/s3_helpers'
13
+ rescue LoadError
14
+ # If AWS::S3 is not installed, no worries, we just
15
+ # wont have access to s3 methods. It's worth noting
16
+ # at least version 1.8.4 of ruby is required for s3.
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
@@ -0,0 +1,208 @@
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
+ 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}"
173
+ dirty_file new_orig
174
+ return new_orig
175
+ end
176
+ raise "Unknown option in :content. Try :is_file, :is_folder " +
177
+ ":is_contents_of or :is_hg_repository"
178
+ end
179
+
180
+ # Given name of a file in +string+ adds that file to @dirty_files. These
181
+ # files will be removed when the +cleanup+ task is called.
182
+ def dirty_file(string)
183
+ @dirty_files << string
184
+ end
185
+
186
+ # +cleanup+ takes every element from @dirty_files and performs an +rm -rf+ on the value
187
+ def cleanup(opts={})
188
+ dirty_files.each do |f|
189
+ rm_rf f
190
+ end
191
+ end
192
+
193
+ private
194
+ def define_method(name, &block)
195
+ metaclass.send(:define_method, name, &block)
196
+ end
197
+
198
+ def store_result(result)
199
+ @result_history.push result
200
+ end
201
+
202
+ def last_result
203
+ @result_history.last
204
+ end
205
+
206
+ end
207
+
208
+ end
@@ -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