backupgem 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
|