yore 0.0.3 → 0.0.4
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +6 -0
- data/Manifest.txt +4 -0
- data/README.rdoc +33 -6
- data/bin/yore +22 -7
- data/lib/ihl_ruby/config.rb +95 -0
- data/lib/ihl_ruby/database_utils.rb +89 -0
- data/lib/ihl_ruby/extend_base_classes.rb +6 -0
- data/lib/ihl_ruby/misc_utils.rb +2 -1
- data/lib/ihl_ruby/xml_utils.rb +19 -5
- data/lib/yore.orig.rb +1 -1
- data/lib/yore/yore_core.rb +265 -141
- data/test/S3_test.rb +141 -0
- data/test/loadsave_yore_test.rb +182 -0
- data/test/test_job_b.xml +6 -13
- data/test/yore_test.rb +10 -139
- metadata +7 -4
data/History.txt
CHANGED
@@ -13,4 +13,10 @@
|
|
13
13
|
|
14
14
|
* much better logging, reporting and console output
|
15
15
|
|
16
|
+
== 0.0.4 2009-07-30
|
17
|
+
|
18
|
+
* now a more general tool for archiving web application data.
|
19
|
+
* supports specific web applications via the kind option eg. rails and spree initially
|
20
|
+
* refactored config functionality
|
21
|
+
|
16
22
|
|
data/Manifest.txt
CHANGED
@@ -8,6 +8,7 @@ lib/ihl_ruby/logging.rb
|
|
8
8
|
lib/ihl_ruby/string_utils.rb
|
9
9
|
lib/ihl_ruby/xml_utils.rb
|
10
10
|
lib/ihl_ruby/shell_extras.rb
|
11
|
+
lib/ihl_ruby/database_utils.rb
|
11
12
|
lib/yore.orig.rb
|
12
13
|
lib/yore/yore_core.rb
|
13
14
|
Manifest.txt
|
@@ -20,3 +21,6 @@ script/generate
|
|
20
21
|
test/test_job_a.xml
|
21
22
|
test/test_job_b.xml
|
22
23
|
test/yore_test.rb
|
24
|
+
test/loadsave_yore_test.rb
|
25
|
+
test/S3_test.rb
|
26
|
+
|
data/README.rdoc
CHANGED
@@ -5,19 +5,46 @@
|
|
5
5
|
|
6
6
|
== DESCRIPTION:
|
7
7
|
|
8
|
-
yore (as in "days of yore") is
|
9
|
-
It
|
8
|
+
yore (as in "days of yore") is a data management utility for web applications.
|
9
|
+
It provides hands-off scheduled backup functions, combines file and database
|
10
|
+
data into a single file, knows certain applications, particularly Rails related ones,
|
11
|
+
and can use Amazon S3 for storage.
|
10
12
|
|
11
13
|
== FEATURES/PROBLEMS:
|
12
14
|
|
13
15
|
* Compressed, encrypted, single file backups of folders and mysql databases
|
14
|
-
*
|
15
|
-
* Backups
|
16
|
-
* will later remove old files that don't match the configurable scheme for backup history
|
16
|
+
* Can be called regularly eg. by cron
|
17
|
+
* Backups can be uploaded to Amazon S3
|
18
|
+
* will later remove old files that don't match the configurable scheme for backup history,
|
19
|
+
but keeping a useful history eg.
|
20
|
+
- backup every day
|
21
|
+
- keep 2 weeks of daily backups
|
22
|
+
- keep 12 weeks of friday backups
|
23
|
+
- keep the first friday backup of each month forever
|
24
|
+
* Can automatically collect and compress database all user data from particular applications
|
25
|
+
to a single file, and restore to another server. Known applications are Rails-centric but others
|
26
|
+
can be manually configured.
|
27
|
+
* Rails database credentials are read from database.yml
|
28
|
+
|
29
|
+
Amazon S3
|
30
|
+
|
31
|
+
* Yore relies on s3cmd for S3 access which is contained in the s3sync gem.
|
32
|
+
s3cmd must be configured with keys for a registered Amazon S3 account.
|
33
|
+
Use something like "s3cmd listbuckets" to check that you have that setup
|
34
|
+
correctly before launching yore.
|
35
|
+
|
36
|
+
* You can and probably should create a seperate Amazon account for 1 or more web servers,
|
37
|
+
and then grant write access to a single bucket for their use, rather than using your main
|
38
|
+
S3 keys. You don't need to supply your credit card details for this account - your main
|
39
|
+
account will be charged. This avoids your main Amazon AWS keys getting stolen by someone
|
40
|
+
hacking into the webserver, or even other legitimate users using them.
|
17
41
|
|
18
42
|
== SYNOPSIS:
|
19
43
|
|
20
|
-
yore
|
44
|
+
yore [global options] command [command options] [arguments]
|
45
|
+
eg.
|
46
|
+
* yore backup yore_config.xml
|
47
|
+
* cd my_rails_app; yore save --kind=spree data.tgz
|
21
48
|
|
22
49
|
== REQUIREMENTS:
|
23
50
|
|
data/bin/yore
CHANGED
@@ -4,7 +4,7 @@ require 'fileutils'
|
|
4
4
|
|
5
5
|
require 'rubygems'
|
6
6
|
gem 'RequirePaths'; require 'require_paths'
|
7
|
-
require_paths '.','../../..'
|
7
|
+
require_paths '.','../../..'
|
8
8
|
|
9
9
|
gem 'cmdparse'; require 'cmdparse'
|
10
10
|
|
@@ -23,18 +23,17 @@ def command(aParser,aController,aAction,aShortDescription=nil,aOptionParser=nil,
|
|
23
23
|
c.description = aOther[:description] if aOther[:description]
|
24
24
|
c.options = aOptionParser if aOptionParser
|
25
25
|
c.set_execution_block do |args|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
aController.
|
30
|
-
aController.do_action(aAction,args)
|
26
|
+
if job = CMD_OPTIONS[:config]
|
27
|
+
aController.configure(job,CMD_OPTIONS)
|
28
|
+
end
|
29
|
+
aController.do_action(aAction,args,CMD_OPTIONS)
|
31
30
|
end
|
32
31
|
aParser.add_command(c)
|
33
32
|
end
|
34
33
|
|
35
34
|
cmd = CmdParse::CommandParser.new( true )
|
36
35
|
cmd.program_name = "yore"
|
37
|
-
cmd.program_version = [0, 0,
|
36
|
+
cmd.program_version = [0, 0, 4]
|
38
37
|
# Options are given after a command and before arguments on the command line
|
39
38
|
# so global options are given first, before the first command
|
40
39
|
# ie ruby yore.rb --global_option command --command_option argument1 argument2 argumentn
|
@@ -43,6 +42,9 @@ cmd.options = CmdParse::OptionParserWrapper.new do |opt|
|
|
43
42
|
opt.on("--verbose", "Be verbose when outputting info") do |t|
|
44
43
|
CMD_OPTIONS[:verbose] = t
|
45
44
|
end
|
45
|
+
opt.on("--config", "Configuration XML File") do |t|
|
46
|
+
CMD_OPTIONS[:config] = t
|
47
|
+
end
|
46
48
|
end
|
47
49
|
cmd.add_command( CmdParse::HelpCommand.new )
|
48
50
|
cmd.add_command( CmdParse::VersionCommand.new )
|
@@ -56,8 +58,21 @@ option_parser = CmdParse::OptionParserWrapper.new do |opt|
|
|
56
58
|
end
|
57
59
|
end
|
58
60
|
|
61
|
+
load_save_option_parser = CmdParse::OptionParserWrapper.new do |opt|
|
62
|
+
opt.on( '--kind=rails|spree', String, 'Specify application to configure for' ) do |value|
|
63
|
+
CMD_OPTIONS[:kind] = value
|
64
|
+
end
|
65
|
+
opt.on( '--RAILS_ENV=development|test|production', String, 'Specify Rails environment to use database credentials for' ) do |value|
|
66
|
+
CMD_OPTIONS[:RAILS_ENV] = value
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
59
70
|
command(cmd,yore,:backup,"Backup filelist to S3",option_parser)
|
60
71
|
|
72
|
+
command(cmd,yore,:save,"Save application data to local file",load_save_option_parser)
|
73
|
+
|
74
|
+
command(cmd,yore,:load,"Load application data from local file",load_save_option_parser)
|
75
|
+
|
61
76
|
command(cmd,yore,:test_email,"Test email sending\n")
|
62
77
|
|
63
78
|
command(cmd,yore,:db_dump,"Dump database by name in job\n")
|
data/lib/ihl_ruby/config.rb
CHANGED
@@ -1,3 +1,8 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
gem 'RequirePaths'; require 'require_paths'
|
3
|
+
require_paths '..'
|
4
|
+
require 'ihl_ruby/xml_utils'
|
5
|
+
|
1
6
|
class ConfigClass < Hash
|
2
7
|
|
3
8
|
attr_reader :default_values
|
@@ -105,3 +110,93 @@ class ConfigClass < Hash
|
|
105
110
|
|
106
111
|
end
|
107
112
|
|
113
|
+
class ConfigXmlClass < ConfigClass
|
114
|
+
attr_accessor :xmlRoot
|
115
|
+
def initialize(aDefaultValues,aConfig)
|
116
|
+
return super(aDefaultValues,aConfig) unless aConfig.is_a?(REXML::Element)
|
117
|
+
@xmlRoot = aConfig.deep_clone
|
118
|
+
super(aDefaultValues,XmlUtils.read_simple_items(@xmlRoot,'/Yore/SimpleItems'))
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
# credentials files look like :
|
123
|
+
#<?xml version="1.0" encoding="UTF-8"?>
|
124
|
+
#<Credentials>
|
125
|
+
# <SimpleItems namespace="global">
|
126
|
+
# <Item name=""></Item>
|
127
|
+
# <Item name=""></Item>
|
128
|
+
# <Item name=""></Item>
|
129
|
+
# </SimpleItems>
|
130
|
+
# <SimpleItems namespace="yore_test">
|
131
|
+
# <Item name=""></Item>
|
132
|
+
# <Item name=""></Item>
|
133
|
+
# <Item name=""></Item>
|
134
|
+
# </SimpleItems>
|
135
|
+
#</Credentials>
|
136
|
+
#
|
137
|
+
# global .credentials.xml file
|
138
|
+
# local .credentials.xml file
|
139
|
+
# cred = Credentials.new() # optionally specify filename or path or hash. if nil then use Dir.pwd
|
140
|
+
#
|
141
|
+
# def initialize(aSource)
|
142
|
+
# # load global namespace from ~/.credentials.xml
|
143
|
+
# # load global namespace from local .credentials.xml
|
144
|
+
# # load given namespace from ~/.credentials.xml
|
145
|
+
# # load given namespace from local .credentials.xml
|
146
|
+
# # merge all top to bottom
|
147
|
+
class Credentials < Hash
|
148
|
+
|
149
|
+
CRED_FILENAME = ".credentials.xml"
|
150
|
+
|
151
|
+
def find_file_upwards(aFilename,aStartPath=nil)
|
152
|
+
aStartPath ||= Dir.pwd
|
153
|
+
return nil if aFilename.nil? || aFilename.empty?
|
154
|
+
arrPath = aStartPath.split(File::SEPARATOR)
|
155
|
+
while arrPath.length > 0
|
156
|
+
path = File.join(arrPath.join(File::SEPARATOR),aFilename)
|
157
|
+
return path if File.exists?(path)
|
158
|
+
arrPath.pop
|
159
|
+
end
|
160
|
+
return nil
|
161
|
+
end
|
162
|
+
|
163
|
+
def get_all_credentials(aXmlRoot)
|
164
|
+
return nil unless aXmlRoot
|
165
|
+
result = {}
|
166
|
+
REXML::XPath.each(aXmlRoot, '/Credentials/SimpleItems') do |si|
|
167
|
+
ns = si.attributes['Namespace']
|
168
|
+
values = XmlUtils.read_simple_items(si)
|
169
|
+
result[ns.to_sym] = values.symbolize_keys if ns && values
|
170
|
+
end
|
171
|
+
return result
|
172
|
+
end
|
173
|
+
|
174
|
+
#XmlUtils.read_simple_items(@xmlRoot,'/Yore/SimpleItems')
|
175
|
+
def get_user_credentials
|
176
|
+
return get_all_credentials(XmlUtils.get_file_root(File.join(HOME_PATH,CRED_FILENAME)))
|
177
|
+
end
|
178
|
+
|
179
|
+
def get_local_credentials(aSource=nil)
|
180
|
+
aSource ||= Dir.pwd
|
181
|
+
# assume source is a directory path, but other types could be supported later
|
182
|
+
return nil unless file=find_file_upwards(CRED_FILENAME,aSource)
|
183
|
+
return get_all_credentials(XmlUtils.get_file_root(file))
|
184
|
+
end
|
185
|
+
|
186
|
+
def initialize(aNamespace=nil,aSource=nil)
|
187
|
+
#HOME_PATH can be preset by tests eg. ::Credentials.const_set('HOME_PATH',@user_dir)
|
188
|
+
Credentials.const_set("HOME_PATH", ENV['HOME']) unless Credentials.const_defined? "HOME_PATH"
|
189
|
+
arrCredentials = []
|
190
|
+
user_credentials = get_user_credentials()
|
191
|
+
local_credentials = get_local_credentials(aSource)
|
192
|
+
arrCredentials << user_credentials[:global] if user_credentials
|
193
|
+
arrCredentials << local_credentials[:global] if local_credentials
|
194
|
+
arrCredentials << user_credentials[aNamespace.to_sym] if aNamespace && user_credentials
|
195
|
+
arrCredentials << local_credentials[aNamespace.to_sym] if aNamespace && local_credentials
|
196
|
+
arrCredentials.compact!
|
197
|
+
arrCredentials.each do |c|
|
198
|
+
self.merge!(c)
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
@@ -0,0 +1,89 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
gem 'RequirePaths'; require 'require_paths'
|
3
|
+
require_paths '..'
|
4
|
+
require 'ihl_ruby/shell_extras'
|
5
|
+
|
6
|
+
module DatabaseUtils
|
7
|
+
def self.execute_sql_file(filename,aUser=nil,aPassword=nil)
|
8
|
+
conf = ActiveRecord::Base.configurations[RAILS_ENV]
|
9
|
+
pw = aPassword || conf['password'].to_s || ''
|
10
|
+
user = aUser || conf['username'].to_s || ''
|
11
|
+
cmd_line = "mysql -h #{conf['host']} -D #{conf['database']} #{user.empty? ? '' : '-u '+user} #{pw.empty? ? '' : '-p'+pw} <#{filename}"
|
12
|
+
if !system(cmd_line)
|
13
|
+
raise Exception, "Error executing "+cmd_line
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
## http://www.cyberciti.biz/faq/how-do-i-empty-mysql-database/
|
18
|
+
#
|
19
|
+
#
|
20
|
+
## drop all tables :
|
21
|
+
## mysqldump -uusername -ppassword -hhost \
|
22
|
+
##--add-drop-table --no-data database | grep ^DROP | \
|
23
|
+
##mysql -uusername -ppassword -hhost database
|
24
|
+
#
|
25
|
+
|
26
|
+
def self.database_exists(aDbDetails,aDatabase=nil)
|
27
|
+
aDbDetails[:database] = aDatabase if aDatabase
|
28
|
+
return false if !aDbDetails[:database]
|
29
|
+
response = POpen4::shell("mysql -u #{aDbDetails[:username]} -p#{aDbDetails[:password]} -e 'use #{aDbDetails[:database]}'") do |r|
|
30
|
+
if r[:stderr] && r[:stderr].index("ERROR 1049 ")==0 # Unknown database
|
31
|
+
r[:exitcode] = 0
|
32
|
+
return false
|
33
|
+
end
|
34
|
+
end
|
35
|
+
return (response && response[:exitcode]==0)
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.clear_database(aDbDetails)
|
39
|
+
response = POpen4::shell("mysqldump -u #{aDbDetails[:username]} -p#{aDbDetails[:password]} --add-drop-table --no-data #{aDbDetails[:database]} | grep ^DROP | mysql -u #{aDbDetails[:username]} -p#{aDbDetails[:password]} #{aDbDetails[:database]}")
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.create_database(aDbDetails,aDatabase=nil)
|
43
|
+
aDbDetails[:database] = aDatabase if aDatabase
|
44
|
+
return false if !aDbDetails[:database]
|
45
|
+
response = POpen4::shell("mysqladmin -u #{aDbDetails[:username]} -p#{aDbDetails[:password]} create #{aDbDetails[:database]}")
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.ensure_empty_database(aDbDetails,aDatabase=nil)
|
49
|
+
aDbDetails[:database] = aDatabase if aDatabase
|
50
|
+
if database_exists(aDbDetails)
|
51
|
+
clear_database(aDbDetails)
|
52
|
+
else
|
53
|
+
create_database(aDbDetails)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.load_database(aDbDetails,aSqlFile)
|
58
|
+
ensure_empty_database(aDbDetails)
|
59
|
+
response = POpen4::shell("mysql -u #{aDbDetails[:username]} -p#{aDbDetails[:password]} #{aDbDetails[:database]} < #{aSqlFile}")
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.save_database(aDbDetails,aSqlFile)
|
63
|
+
response = POpen4::shell("mysqldump --user=#{aDbDetails[:username]} --password=#{aDbDetails[:password]} --skip-extended-insert #{aDbDetails[:database]} > #{aSqlFile}")
|
64
|
+
end
|
65
|
+
|
66
|
+
#
|
67
|
+
## eg. rake metas:spree:data:load from=/tmp/spree_data.tgz to=mysql:fresco_server_d:root:password
|
68
|
+
#desc 'load spree data from a file'
|
69
|
+
#task :load do
|
70
|
+
# from = ENV['from']
|
71
|
+
# to=ENV['to']
|
72
|
+
# db_server,db,user,password = to.split(':')
|
73
|
+
# tmpdir = make_temp_dir('metas')
|
74
|
+
# cmd = "tar -xvzf #{from} -C #{tmpdir}"
|
75
|
+
# puts CapUtilsClass.shell(cmd)
|
76
|
+
#
|
77
|
+
# ensure_empty_database(db_server,db,user,password)
|
78
|
+
#
|
79
|
+
# puts CapUtilsClass.shell("mysql -u #{user} -p#{password} #{db} < #{File.join(tmpdir,'db/dumps/db.sql')}")
|
80
|
+
# FileUtils.mkdir_p('public/assets')
|
81
|
+
# puts CapUtilsClass.shell("cp -rf #{File.join(tmpdir,'public/assets/products')} public/assets/products")
|
82
|
+
#end
|
83
|
+
|
84
|
+
|
85
|
+
|
86
|
+
|
87
|
+
end
|
88
|
+
|
89
|
+
|
data/lib/ihl_ruby/misc_utils.rb
CHANGED
@@ -138,7 +138,7 @@ module MiscUtils
|
|
138
138
|
def self.path_debase(aPath,aBase)
|
139
139
|
aBase = MiscUtils::append_slash(aBase)
|
140
140
|
aPath = MiscUtils::remove_slash(aPath) unless aPath=='/'
|
141
|
-
aPath[aBase.length,aPath.length-aBase.length]
|
141
|
+
aPath[0,aBase.length]==aBase ? aPath[aBase.length,aPath.length-aBase.length] : aPath
|
142
142
|
end
|
143
143
|
|
144
144
|
def self.path_rebase(aPath,aOldBase,aNewBase)
|
@@ -230,6 +230,7 @@ module MiscUtils
|
|
230
230
|
abssrcpath = aRootPath = aPath
|
231
231
|
aPath = nil
|
232
232
|
end
|
233
|
+
return aArray if !File.exists?(abssrcpath)
|
233
234
|
#abssrcpath is real path to query
|
234
235
|
#aRootPath is highest level path
|
235
236
|
#aPath is current path relative to aRootPath
|
data/lib/ihl_ruby/xml_utils.rb
CHANGED
@@ -20,6 +20,7 @@ module XmlUtils
|
|
20
20
|
end
|
21
21
|
|
22
22
|
def self.get_file_root(aFilename)
|
23
|
+
return nil unless File.exists?(aFilename)
|
23
24
|
get_xml_root(MiscUtils.string_from_file(aFilename))
|
24
25
|
end
|
25
26
|
|
@@ -35,8 +36,10 @@ module XmlUtils
|
|
35
36
|
return val.nil? ? default : val.to_s
|
36
37
|
end
|
37
38
|
|
38
|
-
def self.peek_node_value(
|
39
|
-
|
39
|
+
def self.peek_node_value(aNode,aXPath,aDefault=nil)
|
40
|
+
node = single_node(aNode,aXPath)
|
41
|
+
return node.to_s if node.is_a?(REXML::Attribute)
|
42
|
+
return node.nil? ? aDefault : node.text()
|
40
43
|
end
|
41
44
|
|
42
45
|
# convert <root><a>one</a><b>two</b></root> to {'a' => 'one', 'b' => 'two'}
|
@@ -66,6 +69,15 @@ module XmlUtils
|
|
66
69
|
result += aText ? " >#{aText}</#{aName}>" : " />"
|
67
70
|
end
|
68
71
|
|
72
|
+
def self.add_xml_from_string(aString,aNode)
|
73
|
+
return nil unless xdoc = REXML::Document.new('<?xml version="1.0" encoding="UTF-8"?>'+aString)
|
74
|
+
r = xdoc.root
|
75
|
+
r.remove()
|
76
|
+
r.parent = nil
|
77
|
+
aNode.add_element(r)
|
78
|
+
return r
|
79
|
+
end
|
80
|
+
|
69
81
|
def self.hash_to_xml(aHash,aRootName,aDocHeader=true)
|
70
82
|
xdoc = REXML::Document.new(BASIC_HEADER)
|
71
83
|
root = xdoc.add_element(aRootName)
|
@@ -75,9 +87,10 @@ module XmlUtils
|
|
75
87
|
return xdoc
|
76
88
|
end
|
77
89
|
|
78
|
-
def self.read_simple_items(aRoot,aParentXPath)
|
90
|
+
def self.read_simple_items(aRoot,aParentXPath=nil)
|
79
91
|
result = {}
|
80
|
-
|
92
|
+
xp = aParentXPath ? File.join(aParentXPath,'Item') : 'Item'
|
93
|
+
REXML::XPath.each(aRoot, xp) do |item|
|
81
94
|
result[item.attribute('Name').to_s] = item.text
|
82
95
|
end
|
83
96
|
return result
|
@@ -91,8 +104,9 @@ module XmlUtils
|
|
91
104
|
return result
|
92
105
|
end
|
93
106
|
|
107
|
+
# reads the simple items format given either a filename or xml node
|
94
108
|
def self.read_config_values(aXmlConfig)
|
95
|
-
xmlRoot = get_file_root(aXmlConfig)
|
109
|
+
xmlRoot = aXmlConfig.is_a?(REXML::Element) ? aXmlConfig : get_file_root(aXmlConfig)
|
96
110
|
return read_simple_items(xmlRoot,'/Config/SimpleItems')
|
97
111
|
end
|
98
112
|
|
data/lib/yore.orig.rb
CHANGED
data/lib/yore/yore_core.rb
CHANGED
@@ -13,12 +13,11 @@ require 'ihl_ruby/xml_utils'
|
|
13
13
|
require 'ihl_ruby/extend_base_classes'
|
14
14
|
require 'ihl_ruby/shell_extras'
|
15
15
|
require 'ihl_ruby/config'
|
16
|
+
require 'ihl_ruby/database_utils'
|
16
17
|
|
17
|
-
THIS_FILE = __FILE__
|
18
|
+
THIS_FILE = File.expand_path(__FILE__)
|
18
19
|
THIS_DIR = File.dirname(THIS_FILE)
|
19
20
|
|
20
|
-
|
21
|
-
|
22
21
|
module YoreCore
|
23
22
|
|
24
23
|
class KeepDaily
|
@@ -79,11 +78,11 @@ module YoreCore
|
|
79
78
|
end
|
80
79
|
end
|
81
80
|
|
82
|
-
|
83
|
-
|
84
81
|
class Yore
|
85
82
|
|
86
83
|
DEFAULT_CONFIG = {
|
84
|
+
:kind => '',
|
85
|
+
:basepath => '',
|
87
86
|
:keep_daily => 14,
|
88
87
|
:keep_weekly => 12,
|
89
88
|
:keep_monthly => 12,
|
@@ -104,18 +103,17 @@ module YoreCore
|
|
104
103
|
:mail_to => '',
|
105
104
|
:mail_to_alias => '',
|
106
105
|
:mail_auth => :plain,
|
107
|
-
:mysqldump => 'mysqldump'
|
106
|
+
:mysqldump => 'mysqldump',
|
107
|
+
:RAILS_ENV => ''
|
108
108
|
}
|
109
109
|
|
110
110
|
attr_reader :config
|
111
111
|
attr_reader :logger
|
112
112
|
attr_reader :reporter
|
113
113
|
attr_reader :keepers
|
114
|
-
attr_reader :basepath
|
115
114
|
|
116
115
|
def initialize(aConfig=nil)
|
117
116
|
DEFAULT_CONFIG[:email_report] = false # fixes some bug where this was nil
|
118
|
-
@config = ConfigClass.new(DEFAULT_CONFIG,aConfig)
|
119
117
|
|
120
118
|
cons = ConsoleLogger.new()
|
121
119
|
cons.level = Logger::Severity.const_get(config[:log_level]) rescue Logger::Severity::INFO
|
@@ -126,7 +124,6 @@ module YoreCore
|
|
126
124
|
@reporter.level = cons.level
|
127
125
|
|
128
126
|
@logger = MultiLogger.new([cons,@reporter])
|
129
|
-
#require 'ruby-debug'; debugger
|
130
127
|
@logger.info "Yore file and database backup tool for Amazon S3 "
|
131
128
|
@logger.info "(c) 2009 Buzzware Solutions (www.buzzware.com.au)"
|
132
129
|
@logger.info "-------------------------------------------------"
|
@@ -134,44 +131,126 @@ module YoreCore
|
|
134
131
|
|
135
132
|
@logger.info "report file: #{report_file}"
|
136
133
|
|
137
|
-
configure(
|
134
|
+
configure(aConfig)
|
138
135
|
end
|
136
|
+
|
137
|
+
#aOptions may require {:basepath => File.dirname(File.expand_path(job))}
|
138
|
+
def self.launch(aConfigXml,aCmdOptions=nil,aOptions=nil)
|
139
|
+
result = Yore.new()
|
140
|
+
result.configure(aConfigXml,aCmdOptions,aOptions)
|
141
|
+
return result
|
142
|
+
end
|
143
|
+
|
144
|
+
def create_empty_config_xml()
|
145
|
+
s = <<-EOS
|
146
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
147
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
148
|
+
<Yore>
|
149
|
+
<SimpleItems>
|
150
|
+
</SimpleItems>
|
151
|
+
<Sources>
|
152
|
+
</Sources>
|
153
|
+
</Yore>
|
154
|
+
EOS
|
155
|
+
xdoc = REXML::Document.new(s)
|
156
|
+
return xdoc.root
|
157
|
+
end
|
158
|
+
|
159
|
+
def get_rails_db_details(aRailsPath,aRailsEnv)
|
160
|
+
return nil unless aRailsPath && aRailsEnv && aRailsEnv!=''
|
161
|
+
return nil unless dbyml = (YAML::load(File.open(File.expand_path('config/database.yml',aRailsPath))) rescue nil)
|
162
|
+
return dbyml[aRailsEnv] && dbyml[aRailsEnv].symbolize_keys
|
163
|
+
end
|
164
|
+
|
165
|
+
def expand_app_option(kind=nil)
|
166
|
+
kind = config[:kind] unless kind && !kind.empty?
|
167
|
+
return nil unless kind && !kind.empty?
|
168
|
+
config.xmlRoot = create_empty_config_xml() if !config.xmlRoot
|
169
|
+
case kind
|
170
|
+
when 'spree'
|
171
|
+
# add file source
|
172
|
+
xmlSources = XmlUtils.single_node(config.xmlRoot,'/Yore/Sources')
|
173
|
+
if xmlSources
|
174
|
+
strSource = <<-EOS
|
175
|
+
<Source Type="File">
|
176
|
+
<IncludePath>public/assets/products</IncludePath>
|
177
|
+
</Source>
|
178
|
+
EOS
|
179
|
+
XmlUtils.add_xml_from_string(strSource,xmlSources)
|
180
|
+
end
|
181
|
+
expand_app_option('rails') # do again
|
182
|
+
when 'rails'
|
183
|
+
# * add db source from database.yml
|
184
|
+
# load database from config[:basepath],'config/database.yml'
|
185
|
+
#if (dbyml = YAML::load(File.open(File.expand_path('config/database.yml',config[:basepath]))) rescue nil)
|
186
|
+
# if env = (config[:RAILS_ENV] && config[:RAILS_ENV]!='' && config[:RAILS_ENV])
|
187
|
+
# if (db_details = dbyml[env]) &&
|
188
|
+
db_details = get_rails_db_details(config[:basepath],config[:RAILS_ENV])
|
189
|
+
xmlSources = XmlUtils.single_node(config.xmlRoot,'/Yore/Sources')
|
190
|
+
if db_details && xmlSources
|
191
|
+
strSource = <<-EOS
|
192
|
+
<Source Type="MySql" >
|
193
|
+
<Database Name="#{db_details[:database]}" Host="#{db_details[:host]}" User="#{db_details[:username]}" Password="#{db_details[:password]}">
|
194
|
+
<ArchiveFile>rails_app.sql</ArchiveFile>
|
195
|
+
</Database>
|
196
|
+
</Source>
|
197
|
+
EOS
|
198
|
+
XmlUtils.add_xml_from_string(strSource,xmlSources)
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
139
202
|
|
140
203
|
# read the config however its given and return a hash with values in their correct type, and either valid or nil
|
141
204
|
# keys must be :symbols for aOptions. aConfig and aCmdOptions can be strings
|
142
205
|
def configure(aConfig,aCmdOptions = nil,aOptions = nil)
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
206
|
+
config_to_read = {}
|
207
|
+
if aConfig.is_a?(String)
|
208
|
+
aConfig = File.expand_path(aConfig)
|
209
|
+
logger.info "Job file: #{aConfig}"
|
210
|
+
op = {:basepath => File.dirname(aConfig)}
|
211
|
+
xml = XmlUtils.get_file_root(aConfig)
|
212
|
+
return configure(xml,aCmdOptions,op)
|
213
|
+
end
|
214
|
+
|
215
|
+
if @config
|
216
|
+
config_as_hash = nil
|
217
|
+
case aConfig
|
218
|
+
when nil then ; # do nothing
|
219
|
+
when Hash,::ConfigClass then config_as_hash = aConfig
|
220
|
+
when REXML::Element then
|
221
|
+
config_as_hash = XmlUtils.read_simple_items(aConfig,'/Yore/SimpleItems')
|
222
|
+
config.xmlRoot = aConfig # overwriting previous! perhaps should merge
|
223
|
+
else
|
224
|
+
raise StandardError.new('unsupported type')
|
225
|
+
end
|
226
|
+
config_as_hash.each{|n,v| config_to_read[n.to_sym] = v} if config_as_hash # merge given new values
|
227
|
+
else
|
228
|
+
@config = ConfigXmlClass.new(DEFAULT_CONFIG,aConfig)
|
229
|
+
end
|
230
|
+
aCmdOptions.each{|k,v| config_to_read[k.to_sym] = v} if aCmdOptions # merge command options
|
231
|
+
config_to_read.merge!(aOptions) if aOptions # merge options
|
232
|
+
config.read(config_to_read)
|
233
|
+
config[:basepath] = File.expand_path(Dir.pwd) if !config[:basepath] || config[:basepath]==''
|
234
|
+
|
235
|
+
expand_app_option()
|
155
236
|
|
156
237
|
@keepers = Array.new
|
157
238
|
@keepers << KeepDaily.new(config[:keep_daily])
|
158
239
|
@keepers << KeepWeekly.new(config[:keep_weekly])
|
159
240
|
@keepers << KeepMonthly.new(config[:keep_monthly])
|
160
|
-
|
161
|
-
@basepath = config_h[:basepath]
|
162
241
|
end
|
163
242
|
|
164
|
-
def do_action(aAction,aArgs)
|
243
|
+
def do_action(aAction,aArgs,aCmdOptions)
|
165
244
|
logger.info "Executing command: #{aAction} ...\n"
|
166
245
|
begin
|
167
|
-
send(aAction,aArgs)
|
246
|
+
send(aAction,aArgs,aCmdOptions)
|
168
247
|
rescue Exception => e
|
169
|
-
logger.
|
248
|
+
logger.info {e.backtrace.join("\n")}
|
249
|
+
logger.warn "#{e.class.to_s}: during #{aAction.to_s}(#{(aArgs && aArgs.inspect).to_s}): #{e.message.to_s}"
|
170
250
|
end
|
171
251
|
end
|
172
252
|
|
173
253
|
def shell(aCommandline,&aBlock)
|
174
|
-
#require 'ruby-debug'; debugger
|
175
254
|
logger.debug "To shell: " + aCommandline
|
176
255
|
result = block_given? ? POpen4::shell(aCommandline,nil,nil,&aBlock) : POpen4::shell(aCommandline)
|
177
256
|
logger.debug "From shell: '#{result.inspect}'"
|
@@ -192,6 +271,11 @@ module YoreCore
|
|
192
271
|
def get_report
|
193
272
|
MiscUtils::string_from_file(@reporter.logdev.filename)
|
194
273
|
end
|
274
|
+
|
275
|
+
def temp_path
|
276
|
+
@temp_path = MiscUtils.make_temp_dir('yore') unless @temp_path
|
277
|
+
return @temp_path
|
278
|
+
end
|
195
279
|
|
196
280
|
def self.filemap_from_filelist(aFiles)
|
197
281
|
ancestor_path = MiscUtils.file_list_ancestor(aFiles)
|
@@ -206,45 +290,52 @@ module YoreCore
|
|
206
290
|
|
207
291
|
end
|
208
292
|
|
209
|
-
#def self.nice_format(aNumber)
|
210
|
-
# if aNumber >= 100
|
211
|
-
# sprintf('%.0f', aNumber)
|
212
|
-
# else
|
213
|
-
# sprintf('%.3f', aNumber).sub(/\.0{1,3}$/, '')
|
214
|
-
# end
|
215
|
-
#end
|
216
293
|
|
217
294
|
# By default, GNU tar suppresses a leading slash on absolute pathnames while creating or reading a tar archive. (You can suppress this with the -p option.)
|
218
295
|
# tar : http://my.safaribooksonline.com/0596102461/I_0596102461_CHP_3_SECT_9#snippet
|
219
296
|
|
220
297
|
# get files from wherever they are into a single file
|
221
|
-
def
|
298
|
+
def compress(aSourceFiles,aDestFile,aParentDir=nil)
|
222
299
|
logger.info "Collecting files ..."
|
223
|
-
logger.info aSourceFiles.join("\n")
|
224
|
-
filelist = filemap = nil
|
225
|
-
if aSourceFiles.is_a?(Hash)
|
226
|
-
|
227
|
-
|
228
|
-
else # assume array
|
229
|
-
|
230
|
-
|
231
|
-
end
|
232
|
-
aParentDir ||= MiscUtils.file_list_ancestor(filelist)
|
233
|
-
listfile =
|
300
|
+
#logger.info aSourceFiles.join("\n")
|
301
|
+
#filelist = filemap = nil
|
302
|
+
#if aSourceFiles.is_a?(Hash)
|
303
|
+
# filelist = aSourceFiles.keys
|
304
|
+
# filemap = aSourceFiles
|
305
|
+
#else # assume array
|
306
|
+
# filelist = aSourceFiles
|
307
|
+
# filemap = Yore.filemap_from_filelist(aSourceFiles)
|
308
|
+
#end
|
309
|
+
#aParentDir ||= MiscUtils.file_list_ancestor(filelist)
|
310
|
+
listfile = MiscUtils.temp_file
|
234
311
|
MiscUtils.string_to_file(
|
235
|
-
filelist.sort.map{|p| MiscUtils.path_debase(p, aParentDir)}.join("\n"),
|
312
|
+
aSourceFiles.join("\n"), #filelist.sort.map{|p| MiscUtils.path_debase(p, aParentDir)}.join("\n"),
|
236
313
|
listfile
|
237
314
|
)
|
238
315
|
tarfile = MiscUtils.file_change_ext(aDestFile, 'tar')
|
239
316
|
|
240
|
-
shell("tar cv --directory
|
241
|
-
shell("tar --append --directory=#{aParentDir} --file=#{tarfile} .contents")
|
317
|
+
shell("tar cv #{aParentDir ? '--directory='+aParentDir.to_s : ''} --file=#{tarfile} --files-from=#{listfile}")
|
242
318
|
logger.info "Compressing ..."
|
243
319
|
tarfile_size = File.size(tarfile)
|
244
320
|
shell("bzip2 #{tarfile}; mv #{tarfile}.bz2 #{aDestFile}")
|
245
321
|
logger.info "Compressed #{'%.1f' % (tarfile_size*1.0/2**10)} KB to #{'%.1f' % (File.size(aDestFile)*1.0/2**10)} KB"
|
246
322
|
end
|
247
323
|
|
324
|
+
def uncompress(aArchive,aDestination=nil,aArchiveContent=nil)
|
325
|
+
#tarfile = File.expand_path(MiscUtils.file_change_ext(File.basename(aArchive),'tar'),temp_dir)
|
326
|
+
#shell("bunzip2 #{tarfile}; mv #{tarfile}.bz2 #{aDestFile}")
|
327
|
+
#
|
328
|
+
#shell("tar cv #{aParentDir ? '--directory='+aParentDir.to_s : ''} --file=#{tarfile} --files-from=#{listfile}")
|
329
|
+
#logger.info "Compressing ..."
|
330
|
+
#tarfile_size = File.size(tarfile)
|
331
|
+
#shell("bzip2 #{tarfile}; mv #{tarfile}.bz2 #{aDestFile}")
|
332
|
+
#logger.info "Compressed #{'%.1f' % (tarfile_size*1.0/2**10)} KB to #{'%.1f' % (File.size(aDestFile)*1.0/2**10)} KB"
|
333
|
+
aDestination ||= MiscUtils.make_temp_dir('uncompress')
|
334
|
+
FileUtils.mkdir_p(aDestination)
|
335
|
+
shell("tar xvf #{aArchive} #{aArchiveContent.to_s} --directory=#{aDestination} --bzip2")
|
336
|
+
end
|
337
|
+
|
338
|
+
|
248
339
|
def pack(aFileIn,aFileOut)
|
249
340
|
logger.info "Encrypting ..."
|
250
341
|
shell "openssl enc -aes-256-cbc -K #{config[:crypto_key]} -iv #{config[:crypto_iv]} -in #{aFileIn} -out #{aFileOut}"
|
@@ -262,7 +353,7 @@ module YoreCore
|
|
262
353
|
|
263
354
|
# uploads the given file to the current bucket as its basename
|
264
355
|
def upload(aFile)
|
265
|
-
ensure_bucket()
|
356
|
+
#ensure_bucket()
|
266
357
|
logger.info "Uploading #{File.basename(aFile)} to S3 bucket #{config[:bucket]} ..."
|
267
358
|
s3shell "s3cmd put #{config[:bucket]}:#{File.basename(aFile)} #{aFile}"
|
268
359
|
end
|
@@ -291,36 +382,6 @@ module YoreCore
|
|
291
382
|
return Time.from_date_numeric(date)
|
292
383
|
end
|
293
384
|
|
294
|
-
#def set_file_name(aFile,aNewName)#
|
295
|
-
#
|
296
|
-
#end
|
297
|
-
|
298
|
-
def backup_process(aSourceFiles,aTimeNow=Time.now,aTempDir=nil)
|
299
|
-
aTempDir ||= MiscUtils.make_temp_dir('yore_')
|
300
|
-
temp_file = File.expand_path('backup.tar',aTempDir)
|
301
|
-
collect(aSourceFiles,temp_file)
|
302
|
-
backup_file = File.expand_path(encode_file_name(aTimeNow),aTempDir)
|
303
|
-
pack(temp_file,backup_file)
|
304
|
-
upload(backup_file)
|
305
|
-
end
|
306
|
-
|
307
|
-
# aDb : Hash containing :db_host,db_user,db_password,db_name,
|
308
|
-
def db_to_file(aDb,aFile)
|
309
|
-
logger.info "Dumping database #{aDb[:db_name]} ..."
|
310
|
-
shell "#{config[:mysqldump]} --host=#{aDb[:db_host]} --user=#{aDb[:db_user]} --password=#{aDb[:db_password]} --databases --skip-extended-insert --add-drop-database #{aDb[:db_name]} > #{aFile}"
|
311
|
-
end
|
312
|
-
|
313
|
-
def file_to_db(aFile,aDatabase)
|
314
|
-
#run "mysql --user=root --password=prot123ection </root/joomla_db_snapshot/joomla_db.sql"
|
315
|
-
end
|
316
|
-
|
317
|
-
#
|
318
|
-
#
|
319
|
-
# COMMANDLINE COMMANDS
|
320
|
-
#
|
321
|
-
#
|
322
|
-
|
323
|
-
|
324
385
|
|
325
386
|
def clean
|
326
387
|
|
@@ -357,59 +418,142 @@ module YoreCore
|
|
357
418
|
|
358
419
|
def self.database_from_xml(aDatabaseNode)
|
359
420
|
return {
|
360
|
-
:
|
361
|
-
:
|
362
|
-
:
|
363
|
-
:
|
364
|
-
:file => XmlUtils::peek_node_value(aDatabaseNode, "ToFile")
|
421
|
+
:host => aDatabaseNode.attributes['Host'],
|
422
|
+
:username => aDatabaseNode.attributes['User'],
|
423
|
+
:password => aDatabaseNode.attributes['Password'],
|
424
|
+
:database => aDatabaseNode.attributes['Name'],
|
425
|
+
:file => XmlUtils::peek_node_value(aDatabaseNode, "ToFile"),
|
426
|
+
:archive_file => XmlUtils::peek_node_value(aDatabaseNode, "ArchiveFile")
|
365
427
|
}
|
366
428
|
end
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
xmlRoot = XmlUtils.get_file_root(job)
|
372
|
-
|
429
|
+
|
430
|
+
|
431
|
+
def collect_file_list(aSourcesXml,aTempFolder)
|
373
432
|
filelist = []
|
374
433
|
sourceFound = false
|
375
434
|
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
#</Source>
|
389
|
-
REXML::XPath.each(xmlSource, 'Database') do |xmlDb|
|
390
|
-
args = Yore::database_from_xml(xmlDb)
|
391
|
-
file = args.delete(:file)
|
392
|
-
unless args[:db_host] && args[:db_user] && args[:db_password] && args[:db_name] && file
|
393
|
-
raise StandardError.new("Invalid or missing parameter")
|
435
|
+
if aSourcesXml
|
436
|
+
REXML::XPath.each(aSourcesXml,'Source') do |xmlSource|
|
437
|
+
case xmlSource.attributes['Type']
|
438
|
+
when 'File' then
|
439
|
+
# BasePath tag provides base path for IncludePaths to be relative to. Also indicates root folder for archive
|
440
|
+
bp = MiscUtils.path_combine(config[:basepath],XmlUtils::peek_node_value(xmlSource, "@BasePath"))
|
441
|
+
filelist << '-C'+bp
|
442
|
+
REXML::XPath.each(xmlSource, 'IncludePath') do |xmlPath|
|
443
|
+
files = MiscUtils::recursive_file_list(MiscUtils::path_combine(bp,xmlPath.text))
|
444
|
+
files.map!{|f| MiscUtils.path_debase(f,bp)}
|
445
|
+
filelist += files
|
446
|
+
sourceFound = true
|
394
447
|
end
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
448
|
+
when 'MySql' then
|
449
|
+
#<Source Type="MySql" >
|
450
|
+
# <Database Host="" Name="" User="" Password="">
|
451
|
+
# <ToFile>~/dbdump.sql</ToFile>
|
452
|
+
# </Database>
|
453
|
+
#</Source>
|
454
|
+
REXML::XPath.each(xmlSource, 'Database') do |xmlDb|
|
455
|
+
args = Yore::database_from_xml(xmlDb)
|
456
|
+
file = args.delete(:file) #legacy, absolute path
|
457
|
+
arc_file = args.delete(:archive_file) #path in archive
|
458
|
+
unless args[:host] && args[:username] && args[:password] && args[:database] && (file||arc_file)
|
459
|
+
raise StandardError.new("Invalid or missing parameter")
|
460
|
+
end
|
461
|
+
if arc_file
|
462
|
+
arc_file = MiscUtils.path_debase(arc_file,'/')
|
463
|
+
sql_file = File.expand_path(arc_file,aTempFolder)
|
464
|
+
FileUtils.mkdir_p(File.dirname(sql_file)) # create folders as necessry
|
465
|
+
DatabaseUtils::save_database(args,sql_file) #db_to_file(args,sql_file)
|
466
|
+
filelist << '-C'+aTempFolder
|
467
|
+
filelist << arc_file
|
468
|
+
sourceFound = true
|
469
|
+
else
|
470
|
+
DatabaseUtils::save_database(args,sql_file)
|
471
|
+
filelist << file
|
472
|
+
sourceFound = true
|
473
|
+
end
|
474
|
+
end
|
475
|
+
end
|
399
476
|
end
|
400
|
-
|
401
|
-
|
477
|
+
end
|
402
478
|
raise StandardError.new("Backup source found but file list empty") if sourceFound && filelist.empty?
|
479
|
+
return filelist
|
480
|
+
end
|
481
|
+
|
482
|
+
def rails_tmp_path
|
483
|
+
return @rails_tmp_path if @rails_tmp_path
|
484
|
+
@rails_tmp_path = File.join(config[:basepath],'tmp/yore',Time.now.strftime('%Y%m%d-%H%M%S'))
|
485
|
+
end
|
486
|
+
|
487
|
+
def self.move_folder(aPath1,aPath2)
|
488
|
+
path2Parent = MiscUtils.path_parent(aPath2)
|
489
|
+
FileUtils.mkdir_p(path2Parent)
|
490
|
+
FileUtils.mv(aPath1, path2Parent, :force => true)
|
491
|
+
end
|
492
|
+
|
493
|
+
def self.copy_folder(aPath1,aPath2)
|
494
|
+
path2Parent = MiscUtils.path_parent(aPath2)
|
495
|
+
FileUtils.mkdir_p(path2Parent)
|
496
|
+
FileUtils.cp_r(aPath1, path2Parent)
|
497
|
+
end
|
498
|
+
|
499
|
+
def save_internal(aFilename)
|
500
|
+
FileUtils.mkdir_p(files_path = File.join(temp_path,'files'))
|
501
|
+
filelist = collect_file_list(XmlUtils.single_node(config.xmlRoot,'/Yore/Sources'),files_path)
|
502
|
+
compress(filelist,aFilename)
|
503
|
+
end
|
403
504
|
|
404
|
-
|
405
|
-
|
505
|
+
#
|
506
|
+
# ACTIONS
|
507
|
+
#
|
406
508
|
|
407
|
-
|
408
|
-
|
509
|
+
def save(aArgs,aCmdOptions=nil)
|
510
|
+
fnArchive = aArgs.is_a?(Array) ? aArgs.first : aArgs #only supported argument
|
511
|
+
configure(nil,aCmdOptions)
|
512
|
+
save_internal(fnArchive)
|
513
|
+
end
|
409
514
|
|
410
|
-
|
515
|
+
def backup(aArgs,aCmdOptions=nil) # was aJobFiles
|
516
|
+
unless aCmdOptions && aCmdOptions[:config] # assume already configured if config option specified, but back supports first arg being config file
|
517
|
+
job = aArgs.first
|
518
|
+
configure(job,aCmdOptions || {})
|
519
|
+
end
|
520
|
+
temp_file = File.expand_path('backup.tar',temp_path)
|
521
|
+
save_internal(temp_file)
|
522
|
+
backup_file = File.expand_path(encode_file_name(),temp_path)
|
523
|
+
pack(temp_file,backup_file)
|
524
|
+
upload(backup_file)
|
411
525
|
#clean
|
412
526
|
end
|
527
|
+
|
528
|
+
def load(aArgs,aCmdOptions=nil)
|
529
|
+
fnArchive = aArgs.is_a?(Array) ? aArgs.first : aArgs #only supported argument
|
530
|
+
configure(nil,aCmdOptions)
|
531
|
+
|
532
|
+
FileUtils.mkdir_p(archive_path = File.join(temp_path,'archive'))
|
533
|
+
uncompress(fnArchive,archive_path)
|
534
|
+
xmlSources = XmlUtils.single_node(config.xmlRoot,'/Yore/Sources')
|
535
|
+
REXML::XPath.each(xmlSources,'Source') do |xmlSource|
|
536
|
+
case xmlSource.attributes['Type']
|
537
|
+
when 'File' then
|
538
|
+
#<Source Type="File">
|
539
|
+
# <IncludePath>public/assets/products</IncludePath>
|
540
|
+
#</Source>
|
541
|
+
REXML::XPath.each(xmlSource,'IncludePath') do |xmlIncludePath|
|
542
|
+
pathArchive = xmlIncludePath.text()
|
543
|
+
pathUncompressed = File.join(archive_path,pathArchive)
|
544
|
+
pathTmp = File.join(rails_tmp_path,pathArchive)
|
545
|
+
pathDest = File.join(config[:basepath],pathArchive)
|
546
|
+
# move basepath/relativepath to tmp/yore/090807-010203/relativepath
|
547
|
+
Yore::move_folder(pathDest,pathTmp) if File.exists?(pathDest)
|
548
|
+
# get <IncludeFiles> and copy to basepath/relativepath
|
549
|
+
Yore::copy_folder(pathUncompressed,pathDest) if File.exists?(pathUncompressed)
|
550
|
+
end
|
551
|
+
when 'MySql' then
|
552
|
+
db_details = Yore::database_from_xml(XmlUtils.single_node(xmlSource,'Database'))
|
553
|
+
DatabaseUtils.load_database(db_details,File.join(archive_path,db_details[:archive_file]))
|
554
|
+
end
|
555
|
+
end
|
556
|
+
end
|
413
557
|
|
414
558
|
def test_email(*aDb)
|
415
559
|
args = {
|
@@ -430,26 +574,6 @@ module YoreCore
|
|
430
574
|
MiscUtils::send_email(args)
|
431
575
|
end
|
432
576
|
|
433
|
-
def db_dump(aArgs)
|
434
|
-
return nil unless aArgs
|
435
|
-
return nil unless job = aArgs[0]
|
436
|
-
|
437
|
-
xmlRoot = XmlUtils.get_file_root(job)
|
438
|
-
xmlDb = nil
|
439
|
-
if db_name = aArgs[1]
|
440
|
-
xmlDb = XmlUtils::single_node(xmlRoot,"/Yore/Sources/Source[@Type='MySql']/Database[@Name='#{db_name}']")
|
441
|
-
else
|
442
|
-
xmlDb = XmlUtils::single_node(xmlRoot,"/Yore/Sources/Source[@Type='MySql']/Database")
|
443
|
-
end
|
444
|
-
raise StandardError.new("No database") unless xmlDb
|
445
|
-
args = Yore.database_from_xml(xmlDb)
|
446
|
-
file = args.delete(:file)
|
447
|
-
unless args[:db_host] && args[:db_user] && args[:db_password] && args[:db_name] && file
|
448
|
-
raise StandardError.new("Invalid or missing parameter")
|
449
|
-
end
|
450
|
-
db_to_file(args,file)
|
451
|
-
end
|
452
|
-
|
453
577
|
end
|
454
578
|
|
455
579
|
end
|