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.
- data/CHANGELOG +6 -0
- data/README +387 -0
- data/bin/backup +12 -0
- data/bin/commands.sh +2 -0
- data/doc/LICENSE-GPL +280 -0
- data/doc/index.html +716 -0
- data/doc/styles.css +157 -0
- data/examples/global.rb +28 -0
- data/examples/mediawiki.rb +24 -0
- data/lib/backup/actor.rb +196 -0
- data/lib/backup/cli.rb +105 -0
- data/lib/backup/configuration.rb +138 -0
- data/lib/backup/date_parser.rb +37 -0
- data/lib/backup/extensions.rb +17 -0
- data/lib/backup/recipes/standard.rb +89 -0
- data/lib/backup/rotator.rb +155 -0
- data/lib/backup/ssh_helpers.rb +138 -0
- data/lib/backup.rb +7 -0
- data/tests/actor_test.rb +70 -0
- data/tests/cleanup.sh +2 -0
- data/tests/rotation_test.rb +32 -0
- data/tests/ssh_test.rb +21 -0
- data/tests/tests_helper.rb +5 -0
- metadata +99 -0
data/doc/styles.css
ADDED
@@ -0,0 +1,157 @@
|
|
1
|
+
/* Layout */
|
2
|
+
html,body{margin:0;padding:0}
|
3
|
+
body{font: 100% arial,sans-serif}
|
4
|
+
p{margin:0 10px 10px}
|
5
|
+
a{color: #981793;}
|
6
|
+
div#header h1{height:80px;line-height:80px;margin:0;
|
7
|
+
padding-left:10px;background: #EEE;color: #79B30B}
|
8
|
+
div#content p{line-height:1.4}
|
9
|
+
div#navigation{background:#B9CAFF}
|
10
|
+
div#extra{background:#FF8539}
|
11
|
+
div#footer{background: #333;color: #FFF}
|
12
|
+
div#footer p{margin:0;padding:5px 10px}
|
13
|
+
div#wrapper{float:right;width:100%;margin-left:-205px}
|
14
|
+
div#content{margin-left:205px}
|
15
|
+
|
16
|
+
div#navigation {
|
17
|
+
float:left;
|
18
|
+
width:200px;
|
19
|
+
margin-right: 5px;
|
20
|
+
}
|
21
|
+
|
22
|
+
div#extra{float:left;clear:left;width:200px}
|
23
|
+
div#footer{clear:both;width:100%}
|
24
|
+
|
25
|
+
|
26
|
+
/* Styles */
|
27
|
+
body {
|
28
|
+
background-color: white;
|
29
|
+
color: #222;
|
30
|
+
font-family: Georgia, Times, serif;
|
31
|
+
line-height: 110%;
|
32
|
+
}
|
33
|
+
|
34
|
+
/* Code styling */
|
35
|
+
.example {
|
36
|
+
border: solid 1px #C9B;
|
37
|
+
background-color: #FFF5F9;
|
38
|
+
padding: 3px;
|
39
|
+
margin: 5px 40px 5px 22px;
|
40
|
+
}
|
41
|
+
|
42
|
+
pre {
|
43
|
+
background-color: #FFFEF9;
|
44
|
+
color: #936;
|
45
|
+
padding: 5px;
|
46
|
+
margin: 0px;
|
47
|
+
line-height: 170%;
|
48
|
+
}
|
49
|
+
|
50
|
+
blockquote code, p code, li code {
|
51
|
+
color: #936;
|
52
|
+
font-style: normal;
|
53
|
+
background-color: #FFFEF9;
|
54
|
+
padding: 2px;
|
55
|
+
line-height: 170%;
|
56
|
+
font-size: 12pt;
|
57
|
+
}
|
58
|
+
|
59
|
+
p code, li code {
|
60
|
+
line-height: 100%;
|
61
|
+
padding: 0px;
|
62
|
+
font-size: 130%;
|
63
|
+
}
|
64
|
+
|
65
|
+
blockquote code {
|
66
|
+
padding: 5px;
|
67
|
+
font-size: .9em;
|
68
|
+
}
|
69
|
+
|
70
|
+
div#content li {
|
71
|
+
margin-bottom: 5px;
|
72
|
+
}
|
73
|
+
|
74
|
+
div.longlist li {
|
75
|
+
padding-bottom: 8px;
|
76
|
+
}
|
77
|
+
|
78
|
+
.last_updated {
|
79
|
+
margin-left: 60%;
|
80
|
+
text-align: right;
|
81
|
+
font-size:80%;
|
82
|
+
}
|
83
|
+
|
84
|
+
|
85
|
+
/* syntax highlighting */
|
86
|
+
.keyword {
|
87
|
+
font-weight: bold;
|
88
|
+
}
|
89
|
+
.comment {
|
90
|
+
color: #555;
|
91
|
+
}
|
92
|
+
.string, .number {
|
93
|
+
color: #396;
|
94
|
+
}
|
95
|
+
.string {
|
96
|
+
background-color: #E9F5F5;
|
97
|
+
}
|
98
|
+
.regex {
|
99
|
+
background-color: #E9F1F5;
|
100
|
+
color: #435;
|
101
|
+
}
|
102
|
+
.ident {
|
103
|
+
color: #369;
|
104
|
+
}
|
105
|
+
.symbol {
|
106
|
+
color: #000;
|
107
|
+
}
|
108
|
+
.constant, .class {
|
109
|
+
color: #630;
|
110
|
+
font-weight: bold;
|
111
|
+
}
|
112
|
+
|
113
|
+
/* tables */
|
114
|
+
table {
|
115
|
+
margin: 1em 1em 1em 0;
|
116
|
+
background: #f9f9f9;
|
117
|
+
border: 1px #aaaaaa solid;
|
118
|
+
border-collapse: collapse;
|
119
|
+
}
|
120
|
+
|
121
|
+
th, td {
|
122
|
+
border: 1px #aaaaaa solid;
|
123
|
+
padding: 0.2em;
|
124
|
+
}
|
125
|
+
|
126
|
+
th {
|
127
|
+
background: #f2f2f2;
|
128
|
+
text-align: center;
|
129
|
+
}
|
130
|
+
|
131
|
+
/* navigation */
|
132
|
+
div#navigation a, div#extra a {
|
133
|
+
color: #08052B;
|
134
|
+
text-decoration: none;
|
135
|
+
}
|
136
|
+
|
137
|
+
div#navigation a:hover, div#extra a:hover {
|
138
|
+
text-decoration: underline;
|
139
|
+
}
|
140
|
+
|
141
|
+
div#navigation ol, div#extra ul {
|
142
|
+
margin-left: 6px;
|
143
|
+
padding-left: 20px;
|
144
|
+
}
|
145
|
+
|
146
|
+
div#navigation ol ol {
|
147
|
+
padding-left: 20px;
|
148
|
+
}
|
149
|
+
|
150
|
+
div#navigation ol ol li a, ol ol li {
|
151
|
+
color: #424E75;
|
152
|
+
font-size: 90%;
|
153
|
+
}
|
154
|
+
|
155
|
+
.rightad {
|
156
|
+
float: right;
|
157
|
+
}
|
data/examples/global.rb
ADDED
@@ -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 :delivery, :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
|
+
|
data/lib/backup/actor.rb
ADDED
@@ -0,0 +1,196 @@
|
|
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?
|
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
|
+
|
117
|
+
# By default, +:content+ can perform one of three actions
|
118
|
+
# * +:is_file+
|
119
|
+
# * +:is_folder+
|
120
|
+
# + +:is_contents_of+
|
121
|
+
#
|
122
|
+
# Examples:
|
123
|
+
# action :content, :is_file => "/path/to/file" # content is a single file
|
124
|
+
# action :content, :is_folder => "/path/to/folder" # content is the folder itself
|
125
|
+
# action :content, :is_contents_of => "/path/to/other/folder" # files in folder/
|
126
|
+
#
|
127
|
+
# +:is_file+ and +:is_folder+ are basically the same thing in that they
|
128
|
+
# backup the whole file/folder whereas +:is_contents_of+ backs up the
|
129
|
+
# <em>contents</em> of the given folder.
|
130
|
+
#
|
131
|
+
# Note that +:is_contents_of+ performs a very spcific action:
|
132
|
+
# * a temporary directory is created
|
133
|
+
# * all of the files are moved (including subdirectories) into the temporary directory
|
134
|
+
# * the archive is created from the temporary directory
|
135
|
+
#
|
136
|
+
# If you wish to copy the files out of the original directory instead of
|
137
|
+
# moving them. Then you may specify the +copy+ option passing a +true+
|
138
|
+
# value like so:
|
139
|
+
#
|
140
|
+
# action :content, :is_contents_of => "/path/to/other/folder",
|
141
|
+
# :copy => true
|
142
|
+
#
|
143
|
+
# This will copy recursively.
|
144
|
+
#
|
145
|
+
# If this is not your desired behavior then you can easily write your own.
|
146
|
+
# Also, these options only work for local files. If you are getting the
|
147
|
+
# files from a foreign server you will have to write a custom +:content+
|
148
|
+
# method.
|
149
|
+
#
|
150
|
+
def content(opts={})
|
151
|
+
return opts[:is_file] if opts[:is_file]
|
152
|
+
return opts[:is_folder] if opts[:is_folder]
|
153
|
+
if opts[:is_contents_of]
|
154
|
+
orig = opts[:is_contents_of]
|
155
|
+
tmpdir = c[:tmp_dir] + "/tmp_" + Time.now.strftime("%Y%m%d%H%M%S") +"_#{rand}"
|
156
|
+
new_orig = tmpdir + "/" + File.basename(orig)
|
157
|
+
sh "mkdir #{tmpdir}"
|
158
|
+
sh "mkdir #{new_orig}"
|
159
|
+
cmd = opts[:copy] ? "cp -r" : "mv"
|
160
|
+
sh "#{cmd} #{orig}/* #{new_orig}/"
|
161
|
+
dirty_file new_orig
|
162
|
+
return new_orig
|
163
|
+
end
|
164
|
+
raise "Unknown option in :content. Try :is_file, :is_folder " +
|
165
|
+
"or :is_contents_of"
|
166
|
+
end
|
167
|
+
|
168
|
+
# Given name of a file in +string+ adds that file to @dirty_files. These
|
169
|
+
# files will be removed when the +cleanup+ task is called.
|
170
|
+
def dirty_file(string)
|
171
|
+
@dirty_files << string
|
172
|
+
end
|
173
|
+
|
174
|
+
# +cleanup+ takes every element from @dirty_files and performs an +rm -rf+ on the value
|
175
|
+
def cleanup(opts={})
|
176
|
+
dirty_files.each do |f|
|
177
|
+
sh "rm -rf #{f}"
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
private
|
182
|
+
def define_method(name, &block)
|
183
|
+
metaclass.send(:define_method, name, &block)
|
184
|
+
end
|
185
|
+
|
186
|
+
def store_result(result)
|
187
|
+
@result_history.push result
|
188
|
+
end
|
189
|
+
|
190
|
+
def last_result
|
191
|
+
@result_history.last
|
192
|
+
end
|
193
|
+
|
194
|
+
end
|
195
|
+
|
196
|
+
end
|
data/lib/backup/cli.rb
ADDED
@@ -0,0 +1,105 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
require 'backup'
|
3
|
+
|
4
|
+
module Backup
|
5
|
+
# The CLI class encapsulates the behavior of backup when it is invoked
|
6
|
+
# as a command-line utility.
|
7
|
+
class CLI
|
8
|
+
# Invoke capistrano using the ARGV array as the option parameters.
|
9
|
+
def self.execute!
|
10
|
+
new.execute!
|
11
|
+
end
|
12
|
+
|
13
|
+
# The array of (unparsed) command-line options
|
14
|
+
attr_reader :args
|
15
|
+
|
16
|
+
# The hash of (parsed) command-line options
|
17
|
+
attr_reader :options
|
18
|
+
|
19
|
+
# Docs for creating a new instance go here
|
20
|
+
def initialize(args = ARGV)
|
21
|
+
@args = args
|
22
|
+
@options = { :recipes => [], :actions => [],
|
23
|
+
:vars => {}, # :pre_vars => {},
|
24
|
+
:global => nil }
|
25
|
+
|
26
|
+
OptionParser.new do |opts|
|
27
|
+
opts.banner = "Usage: #{$0} [options] [args]"
|
28
|
+
|
29
|
+
opts.separator ""
|
30
|
+
opts.separator "Recipe Options -----------------------"
|
31
|
+
opts.separator ""
|
32
|
+
|
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."
|
36
|
+
) { |value| @options[:recipes] << value }
|
37
|
+
|
38
|
+
opts.on("-s", "--set NAME=VALUE",
|
39
|
+
"Specify a variable and it's value to set. This",
|
40
|
+
"will be set after loading all recipe files."
|
41
|
+
) do |pair|
|
42
|
+
name, value = pair.split(/=/, 2)
|
43
|
+
@options[:vars][name.to_sym] = value
|
44
|
+
end
|
45
|
+
|
46
|
+
opts.on("-g", "--global RECIPE",
|
47
|
+
"Specify a specific file to load as the global file",
|
48
|
+
"for the recipes. By default the recipes load the",
|
49
|
+
"file +global.rb+ in the same directory."
|
50
|
+
) { |value| @options[:recipes] << value }
|
51
|
+
|
52
|
+
if args.empty?
|
53
|
+
puts opts
|
54
|
+
exit
|
55
|
+
else
|
56
|
+
opts.parse!(args)
|
57
|
+
end
|
58
|
+
|
59
|
+
check_options!
|
60
|
+
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
|
65
|
+
# Begin running Backup based on the configured options.
|
66
|
+
def execute!
|
67
|
+
#if !@options[:recipes].empty? # put backk
|
68
|
+
execute_recipes!
|
69
|
+
# elsif @options[:apply_to]
|
70
|
+
# execute_apply_to!
|
71
|
+
#end
|
72
|
+
end
|
73
|
+
|
74
|
+
|
75
|
+
private
|
76
|
+
def check_options!
|
77
|
+
# performa sanity check
|
78
|
+
end
|
79
|
+
|
80
|
+
# Load the recipes specified by the options, and execute the actions
|
81
|
+
# specified.
|
82
|
+
def execute_recipes!
|
83
|
+
config = Backup::Configuration.new
|
84
|
+
#config.logger.level = options[:verbose]
|
85
|
+
#options[:pre_vars].each { |name, value| config.set(name, value) }
|
86
|
+
options[:vars].each { |name, value| config.set(name, value) }
|
87
|
+
|
88
|
+
# load the standard recipe definition
|
89
|
+
config.load "standard"
|
90
|
+
options[:recipes].each do |recipe|
|
91
|
+
global = options[:global] || File.dirname(recipe) + "/global.rb"
|
92
|
+
config.load global if File.exists? global # cache this?
|
93
|
+
end
|
94
|
+
options[:recipes].each { |recipe| config.load(recipe) }
|
95
|
+
#options[:vars].each { |name, value| config.set(name, value) }
|
96
|
+
|
97
|
+
actor = config.actor
|
98
|
+
actor.start_process! # eventually make more options, like the ability
|
99
|
+
# to run each action individually
|
100
|
+
#options[:actions].each { |action| actor.send action }
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
@@ -0,0 +1,138 @@
|
|
1
|
+
require 'backup/actor'
|
2
|
+
require 'backup/rotator'
|
3
|
+
|
4
|
+
module Backup
|
5
|
+
# Represents a specific Backup configuration. A Configuration instance
|
6
|
+
# may be used to load multiple recipe files, define and describe tasks,
|
7
|
+
# define roles, create an actor, and set configuration variables.
|
8
|
+
class Configuration
|
9
|
+
# The actor created for this configuration instance.
|
10
|
+
attr_reader :actor
|
11
|
+
|
12
|
+
# The logger instance defined for this configuration.
|
13
|
+
attr_reader :logger
|
14
|
+
|
15
|
+
# The load paths used for locating recipe files.
|
16
|
+
attr_reader :load_paths
|
17
|
+
|
18
|
+
# The hash of variables currently known by the configuration
|
19
|
+
attr_reader :variables
|
20
|
+
|
21
|
+
def initialize(actor_class=Actor) #:nodoc:
|
22
|
+
@actor = actor_class.new(self)
|
23
|
+
#@logger = Logger.new
|
24
|
+
@load_paths = [".", File.join(File.dirname(__FILE__), "recipes")]
|
25
|
+
@variables = {}
|
26
|
+
end
|
27
|
+
|
28
|
+
# Set a variable to the given value.
|
29
|
+
def set(variable, value=nil, &block)
|
30
|
+
# if the variable is uppercase, then we add it as a constant to the
|
31
|
+
# actor. This is to allow uppercase "variables" to be set and referenced
|
32
|
+
# in recipes.
|
33
|
+
if variable.to_s[0].between?(?A, ?Z)
|
34
|
+
klass = @actor.metaclass
|
35
|
+
klass.send(:remove_const, variable) if klass.const_defined?(variable)
|
36
|
+
klass.const_set(variable, value)
|
37
|
+
end
|
38
|
+
|
39
|
+
value = block if value.nil? && block_given?
|
40
|
+
@variables[variable] = value
|
41
|
+
end
|
42
|
+
|
43
|
+
alias :[]= :set
|
44
|
+
|
45
|
+
def [](variable)
|
46
|
+
# TODO have it raise if it doesn exist
|
47
|
+
@variables[variable]
|
48
|
+
end
|
49
|
+
|
50
|
+
# Require another file. This is identical to the standard require method,
|
51
|
+
# with the exception that it sets the reciever as the "current" configuration
|
52
|
+
# so that third-party task bundles can include themselves relative to
|
53
|
+
# that configuration.
|
54
|
+
def require(*args) #:nodoc:
|
55
|
+
original, Backup.configuration = Backup.configuration, self
|
56
|
+
super
|
57
|
+
ensure
|
58
|
+
# restore the original, so that require's can be nested
|
59
|
+
Backup.configuration = original
|
60
|
+
end
|
61
|
+
|
62
|
+
# Disclaimer: This method written by Jamis Buck. Taken directly from his
|
63
|
+
# excellent code Capistrano.
|
64
|
+
#
|
65
|
+
# Load a configuration file or string into this configuration.
|
66
|
+
#
|
67
|
+
# Usage:
|
68
|
+
#
|
69
|
+
# load("recipe"):
|
70
|
+
# Look for and load the contents of 'recipe.rb' into this
|
71
|
+
# configuration.
|
72
|
+
#
|
73
|
+
# load(:file => "recipe"):
|
74
|
+
# same as above
|
75
|
+
#
|
76
|
+
# load(:string => "set :scm, :subversion"):
|
77
|
+
# Load the given string as a configuration specification.
|
78
|
+
#
|
79
|
+
# load { ... }
|
80
|
+
# Load the block in the context of the configuration.
|
81
|
+
def load(*args, &block)
|
82
|
+
options = args.last.is_a?(Hash) ? args.pop : {}
|
83
|
+
args.each { |arg| load options.merge(:file => arg) }
|
84
|
+
return unless args.empty?
|
85
|
+
|
86
|
+
if block
|
87
|
+
raise "loading a block requires 0 parameters" unless args.empty?
|
88
|
+
load(options.merge(:proc => block))
|
89
|
+
|
90
|
+
elsif options[:file]
|
91
|
+
file = options[:file]
|
92
|
+
unless file[0] == ?/
|
93
|
+
load_paths.each do |path|
|
94
|
+
if File.file?(File.join(path, file))
|
95
|
+
file = File.join(path, file)
|
96
|
+
break
|
97
|
+
elsif File.file?(File.join(path, file) + ".rb")
|
98
|
+
file = File.join(path, file + ".rb")
|
99
|
+
break
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
load :string => File.read(file), :name => options[:name] || file
|
104
|
+
|
105
|
+
elsif options[:string]
|
106
|
+
#logger.trace "loading configuration #{options[:name] || "<eval>"}"
|
107
|
+
instance_eval(options[:string], options[:name] || "<eval>")
|
108
|
+
|
109
|
+
elsif options[:proc]
|
110
|
+
#logger.trace "loading configuration #{options[:proc].inspect}"
|
111
|
+
instance_eval(&options[:proc])
|
112
|
+
|
113
|
+
else
|
114
|
+
raise ArgumentError, "don't know how to load #{options.inspect}"
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
# Describe the next task to be defined. The given text will be attached to
|
119
|
+
# the next task that is defined and used as its description.
|
120
|
+
def desc(text)
|
121
|
+
@next_description = text
|
122
|
+
end
|
123
|
+
|
124
|
+
# Define a new task. If a description is active (see #desc), it is added to
|
125
|
+
# the options under the <tt>:desc</tt> key. This method ultimately
|
126
|
+
# delegates to Actor#define_task.
|
127
|
+
def action(name, options={}, &block)
|
128
|
+
# raise ArgumentError, "expected a block or method" unless block or options[:method] # ??
|
129
|
+
if @next_description
|
130
|
+
options = options.merge(:desc => @next_description)
|
131
|
+
@next_description = nil
|
132
|
+
end
|
133
|
+
|
134
|
+
actor.define_action(name, options, &block)
|
135
|
+
end
|
136
|
+
|
137
|
+
end
|
138
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Backup
|
2
|
+
class DateParser
|
3
|
+
|
4
|
+
def self.date_from(what)
|
5
|
+
DateParser.new.date_from(what)
|
6
|
+
end
|
7
|
+
|
8
|
+
# the test is going to be whatever is returned here .include? the day of
|
9
|
+
# today. so if we want to do something every day than this needs to return
|
10
|
+
# something that will lincde the righ daY:W
|
11
|
+
def date_from(what)
|
12
|
+
if what.kind_of?(Symbol)
|
13
|
+
return Runt::DIWeek.new( Time.num_from_day(what) ) if day_of_week?(what)
|
14
|
+
return Runt::REDay.new(0,0,24,01) if what == :daily
|
15
|
+
if what.to_s =~ /^last_/
|
16
|
+
what.to_s =~ /^last_(\w+)_of_the_month$/
|
17
|
+
day = $1
|
18
|
+
return Runt::DIMonth.new(Runt::Last, Time.num_from_day(day.intern))
|
19
|
+
end
|
20
|
+
end
|
21
|
+
raise "#{what} is not a valid time" unless what.respond_to?(:include?)
|
22
|
+
what
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
def day_of_week?(word)
|
27
|
+
days_of_week.include?(word.to_s.downcase[0..2])
|
28
|
+
end
|
29
|
+
|
30
|
+
def days_of_week
|
31
|
+
%w{mon tue wed thu fri sat sun}
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
|