webtranslateit-safe 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.autotest +3 -0
- data/.document +5 -0
- data/.github/dependabot.yml +26 -0
- data/.github/release-drafter.yml +36 -0
- data/.github/workflows/ci.yml +51 -0
- data/.github/workflows/release-drafter.yml +29 -0
- data/.gitignore +18 -0
- data/.rspec +3 -0
- data/.rubocop.yml +8 -0
- data/.rubocop_todo.yml +552 -0
- data/CHANGELOG +42 -0
- data/Gemfile +11 -0
- data/Gemfile.lock +89 -0
- data/LICENSE.txt +22 -0
- data/README.markdown +237 -0
- data/Rakefile +8 -0
- data/TODO +31 -0
- data/bin/webtranslateit-safe +64 -0
- data/lib/extensions/mktmpdir.rb +45 -0
- data/lib/webtranslateit/safe/archive.rb +29 -0
- data/lib/webtranslateit/safe/backup.rb +27 -0
- data/lib/webtranslateit/safe/cloudfiles.rb +77 -0
- data/lib/webtranslateit/safe/config/builder.rb +100 -0
- data/lib/webtranslateit/safe/config/node.rb +79 -0
- data/lib/webtranslateit/safe/ftp.rb +85 -0
- data/lib/webtranslateit/safe/gpg.rb +52 -0
- data/lib/webtranslateit/safe/gzip.rb +29 -0
- data/lib/webtranslateit/safe/local.rb +55 -0
- data/lib/webtranslateit/safe/mongodump.rb +30 -0
- data/lib/webtranslateit/safe/mysqldump.rb +36 -0
- data/lib/webtranslateit/safe/pgdump.rb +36 -0
- data/lib/webtranslateit/safe/pipe.rb +23 -0
- data/lib/webtranslateit/safe/s3.rb +80 -0
- data/lib/webtranslateit/safe/sftp.rb +96 -0
- data/lib/webtranslateit/safe/sink.rb +40 -0
- data/lib/webtranslateit/safe/source.rb +51 -0
- data/lib/webtranslateit/safe/stream.rb +40 -0
- data/lib/webtranslateit/safe/svndump.rb +17 -0
- data/lib/webtranslateit/safe/tmp_file.rb +53 -0
- data/lib/webtranslateit/safe/version.rb +9 -0
- data/lib/webtranslateit/safe.rb +70 -0
- data/spec/integration/archive_integration_spec.rb +89 -0
- data/spec/integration/cleanup_spec.rb +62 -0
- data/spec/spec_helper.rb +7 -0
- data/spec/webtranslateit/safe/archive_spec.rb +67 -0
- data/spec/webtranslateit/safe/cloudfiles_spec.rb +175 -0
- data/spec/webtranslateit/safe/config_spec.rb +307 -0
- data/spec/webtranslateit/safe/gpg_spec.rb +148 -0
- data/spec/webtranslateit/safe/gzip_spec.rb +64 -0
- data/spec/webtranslateit/safe/local_spec.rb +109 -0
- data/spec/webtranslateit/safe/mongodump_spec.rb +54 -0
- data/spec/webtranslateit/safe/mysqldump_spec.rb +83 -0
- data/spec/webtranslateit/safe/pgdump_spec.rb +45 -0
- data/spec/webtranslateit/safe/s3_spec.rb +168 -0
- data/spec/webtranslateit/safe/svndump_spec.rb +39 -0
- data/templates/script.rb +183 -0
- data/webtranslateit-safe.gemspec +32 -0
- metadata +149 -0
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2010-2013 Astrails Ltd.
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.markdown
ADDED
@@ -0,0 +1,237 @@
|
|
1
|
+
# webtranslateit-safe
|
2
|
+
|
3
|
+
Simple database and filesystem backups with S3 and Rackspace Cloud Files support (with optional encryption)
|
4
|
+
|
5
|
+
* Code: [http://github.com/webtranslateit/safe](http://github.com/webtranslateit/safe)
|
6
|
+
|
7
|
+
## Motivation
|
8
|
+
|
9
|
+
We needed a backup solution that will satisfy the following requirements:
|
10
|
+
|
11
|
+
* opensource
|
12
|
+
* simple to install and configure
|
13
|
+
* support for simple ‘tar’ backups of directories (with includes/excludes)
|
14
|
+
* support for simple mysqldump of mysql databases
|
15
|
+
* support for symmetric or public key encryption
|
16
|
+
* support for local filesystem, Amazon S3, and Rackspace Cloud Files for storage
|
17
|
+
* support for backup rotation. we don’t want backups filling all the diskspace or cost a fortune on S3 or Cloud Files
|
18
|
+
|
19
|
+
And since we didn't find any, we wrote our own :)
|
20
|
+
|
21
|
+
## Contributions
|
22
|
+
|
23
|
+
The following functionality was contributed by webtranslateit-safe users:
|
24
|
+
|
25
|
+
* PostgreSQL dump using `pg_dump` (by Mark Mansour <mark@stateofflux.com>)
|
26
|
+
* Subversion dump using svndump (by Richard Luther <richard.luther@gmail.com>)
|
27
|
+
* SFTP remote storage (by Adam <adam@mediadrive.ca>)
|
28
|
+
* benchmarking output (By Neer)
|
29
|
+
* README fixes (by Bobby Wilson)
|
30
|
+
* improved config file parsing (by Fedor Kocherga <fkocherga@gmail.com>)
|
31
|
+
* mysql password file quoting (by Jonathan Sutherland <jonathan.sutherland@gmail.com>)
|
32
|
+
* Rackspace Cloud Files support (by H. Wade Minter <minter@lunenburg.org>)
|
33
|
+
* Plan FTP support (by seroy <seroy@bk.ru>)
|
34
|
+
* mongodump support (by Matt Berther <matt@mattberther.com>)
|
35
|
+
|
36
|
+
Thanks to all :)
|
37
|
+
|
38
|
+
## Installation
|
39
|
+
|
40
|
+
gem install webtranslateit-safe
|
41
|
+
|
42
|
+
## Reporting problems
|
43
|
+
|
44
|
+
Please report problems at the [Issues tracker](http://github.com/webtranslateit/safe/issues)
|
45
|
+
|
46
|
+
## Usage
|
47
|
+
|
48
|
+
Usage:
|
49
|
+
webtranslateit-safe [OPTIONS] CONFIG_FILE
|
50
|
+
Options:
|
51
|
+
-h, --help This help screen
|
52
|
+
-v, --verbose be verbose, duh!
|
53
|
+
-n, --dry-run just pretend, don't do anything.
|
54
|
+
-L, --local skip remote storage, only do local backups
|
55
|
+
|
56
|
+
Note: CONFIG\_FILE will be created from template if missing
|
57
|
+
|
58
|
+
## Encryption
|
59
|
+
|
60
|
+
If you want to encrypt your backups you have 2 options:
|
61
|
+
* use simple password encryption
|
62
|
+
* use GPG public key encryption
|
63
|
+
|
64
|
+
> IMPORTANT: some gpg installations automatically set 'use-agent' option in the default
|
65
|
+
> configuration file that is created when you run gpg for the first time. This will cause
|
66
|
+
> gpg to fail on the 2nd run if you don't have the agent running. The result is that
|
67
|
+
> 'webtranslateit-safe' will work ONCE when you manually test it and then fail on any subsequent run.
|
68
|
+
> The solution is to remove the 'use-agent' from the config file (usually /root/.gnupg/gpg.conf)
|
69
|
+
> To mitigate this problem for the gpg 1.x series '--no-use-agent' option is added by defaults
|
70
|
+
> to the autogenerated config file, but for gpg2 is doesn't work. as the manpage says it:
|
71
|
+
> "This is dummy option. gpg2 always requires the agent." :(
|
72
|
+
|
73
|
+
For simple password, just add password entry in gpg section.
|
74
|
+
For public key encryption you will need to create a public/secret keypair.
|
75
|
+
|
76
|
+
We recommend to create your GPG keys only on your local machine and then
|
77
|
+
transfer your public key to the server that will do the backups.
|
78
|
+
|
79
|
+
This way the server will only know how to encrypt the backups but only you
|
80
|
+
will be able to decrypt them using the secret key you have locally. Of course
|
81
|
+
you MUST backup your backup encryption key :)
|
82
|
+
We recommend also pringing the hard paper copy of your GPG key 'just in case'.
|
83
|
+
|
84
|
+
The procedure to create and transfer the key is as follows:
|
85
|
+
|
86
|
+
1. run 'gpg --gen-key' on your local machine and follow onscreen instructions to create the key
|
87
|
+
(you can accept all the defaults).
|
88
|
+
|
89
|
+
2. extract your public key into a file (assuming you used test@example.com as your key email):
|
90
|
+
`gpg -a --export test@example.com > test@example.com.pub`
|
91
|
+
|
92
|
+
3. transfer public key to the server
|
93
|
+
`scp test@example.com.pub root@example.com:`
|
94
|
+
|
95
|
+
4. import public key on the remote system:
|
96
|
+
|
97
|
+
$ gpg --import test@example.com.pub
|
98
|
+
gpg: key 45CA9403: public key "Test Backup <test@example.com>" imported
|
99
|
+
gpg: Total number processed: 1
|
100
|
+
gpg: imported: 1
|
101
|
+
|
102
|
+
5. since we don't keep the secret part of the key on the remote server, gpg has
|
103
|
+
no way to know its yours and can be trusted.
|
104
|
+
To fix that we can sign it with other trusted key, or just directly modify its
|
105
|
+
trust level in gpg (use level 5):
|
106
|
+
|
107
|
+
$ gpg --edit-key test@example.com
|
108
|
+
...
|
109
|
+
Command> trust
|
110
|
+
...
|
111
|
+
1 = I don't know or won't say
|
112
|
+
2 = I do NOT trust
|
113
|
+
3 = I trust marginally
|
114
|
+
4 = I trust fully
|
115
|
+
5 = I trust ultimately
|
116
|
+
m = back to the main menu
|
117
|
+
|
118
|
+
Your decision? 5
|
119
|
+
...
|
120
|
+
Command> quit
|
121
|
+
|
122
|
+
6. export your secret key for backup
|
123
|
+
(we recommend to print it on paper and burn to a CD/DVD and store in a safe place):
|
124
|
+
|
125
|
+
$ gpg -a --export-secret-key test@example.com > test@example.com.key
|
126
|
+
|
127
|
+
|
128
|
+
|
129
|
+
## Example configuration
|
130
|
+
|
131
|
+
safe do
|
132
|
+
verbose true
|
133
|
+
|
134
|
+
local :path => "/backup/:kind/:id"
|
135
|
+
|
136
|
+
s3 do
|
137
|
+
key "...................."
|
138
|
+
secret "........................................"
|
139
|
+
bucket "backup.astrails.com"
|
140
|
+
path "servers/alpha/:kind/:id"
|
141
|
+
end
|
142
|
+
|
143
|
+
cloudfiles do
|
144
|
+
user "..........."
|
145
|
+
api_key "................................."
|
146
|
+
container "safe_backup"
|
147
|
+
path ":kind/" # this is default
|
148
|
+
service_net false
|
149
|
+
end
|
150
|
+
|
151
|
+
sftp do
|
152
|
+
host "sftp.astrails.com"
|
153
|
+
user "astrails"
|
154
|
+
# port 8023
|
155
|
+
password "ssh password for sftp"
|
156
|
+
end
|
157
|
+
|
158
|
+
gpg do
|
159
|
+
command "/usr/local/bin/gpg"
|
160
|
+
options "--no-use-agent"
|
161
|
+
# symmetric encryption key
|
162
|
+
# password "qwe"
|
163
|
+
|
164
|
+
# public GPG key (must be known to GPG, i.e. be on the keyring)
|
165
|
+
key "backup@astrails.com"
|
166
|
+
end
|
167
|
+
|
168
|
+
keep do
|
169
|
+
local 20
|
170
|
+
s3 100
|
171
|
+
cloudfiles 100
|
172
|
+
sftp 100
|
173
|
+
end
|
174
|
+
|
175
|
+
mysqldump do
|
176
|
+
options "-ceKq --single-transaction --create-options"
|
177
|
+
|
178
|
+
user "root"
|
179
|
+
password "............"
|
180
|
+
socket "/var/run/mysqld/mysqld.sock"
|
181
|
+
|
182
|
+
database :blog
|
183
|
+
database :servershape
|
184
|
+
database :astrails_com
|
185
|
+
database :secret_project_com do
|
186
|
+
skip_tables "foo"
|
187
|
+
skip_tables ["bar", "baz"]
|
188
|
+
end
|
189
|
+
|
190
|
+
end
|
191
|
+
|
192
|
+
svndump do
|
193
|
+
repo :my_repo do
|
194
|
+
repo_path "/home/svn/my_repo"
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
pgdump do
|
199
|
+
options "-i -x -O" # -i => ignore version, -x => do not dump privileges (grant/revoke), -O => skip restoration of object ownership in plain text format
|
200
|
+
|
201
|
+
user "username"
|
202
|
+
password "............" # shouldn't be used, instead setup ident. Current functionality exports a password env to the shell which pg_dump uses - untested!
|
203
|
+
|
204
|
+
database :blog
|
205
|
+
database :stateofflux_com
|
206
|
+
end
|
207
|
+
|
208
|
+
tar do
|
209
|
+
options "-h" # dereference symlinks
|
210
|
+
archive "git-repositories", :files => "/home/git/repositories"
|
211
|
+
archive "dot-configs", :files => "/home/*/.[^.]*"
|
212
|
+
archive "etc", :files => "/etc", :exclude => "/etc/puppet/other"
|
213
|
+
|
214
|
+
archive "blog-astrails-com" do
|
215
|
+
files "/var/www/blog.astrails.com/"
|
216
|
+
exclude "/var/www/blog.astrails.com/log"
|
217
|
+
exclude "/var/www/blog.astrails.com/tmp"
|
218
|
+
end
|
219
|
+
|
220
|
+
archive "astrails-com" do
|
221
|
+
files "/var/www/astrails.com/"
|
222
|
+
exclude ["/var/www/astrails.com/log", "/var/www/astrails.com/tmp"]
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
## Contributing
|
228
|
+
|
229
|
+
1. Fork it
|
230
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
231
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
232
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
233
|
+
5. Create new Pull Request
|
234
|
+
|
235
|
+
## Copyright
|
236
|
+
|
237
|
+
Copyright (c) 2010-2023 WebTranslateIt Software SL. See LICENSE.txt for details.
|
data/Rakefile
ADDED
data/TODO
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
- refactor
|
2
|
+
- refactor out global variables. pass a config object around instead
|
3
|
+
- common logging
|
4
|
+
- remove 1.8.6 support
|
5
|
+
- module registry
|
6
|
+
- base => prefix ?
|
7
|
+
- move requires into specific modules
|
8
|
+
- config.foo instead of config[:foo]
|
9
|
+
|
10
|
+
- features
|
11
|
+
- remote-only s3 support
|
12
|
+
- generic notifier support
|
13
|
+
- email notifier
|
14
|
+
- hipchat
|
15
|
+
- generic error notifier support
|
16
|
+
- email
|
17
|
+
- hipchat
|
18
|
+
|
19
|
+
|
20
|
+
|
21
|
+
- add 'silent'
|
22
|
+
- handle errors from mysqldump
|
23
|
+
- check that gpg is installed
|
24
|
+
- support percona XtraBackup as an option instead of mysqldump [patches anyone :) ?]
|
25
|
+
- backup validation:
|
26
|
+
- support for 'minsize' opition in backup that will check that produced backup is at least the expected size
|
27
|
+
this should catch many backup failure scenarious (like broken mysql connection, insufficient disk space etc.
|
28
|
+
- support differencial backups
|
29
|
+
- it should be fairly easy for filesystem backups using tar's built in incremental functionality.
|
30
|
+
- for mysql need to use XtraBackup
|
31
|
+
- or we can keep the previous dump locally and store only diff with the latest dump
|
@@ -0,0 +1,64 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
|
5
|
+
require 'webtranslateit/safe'
|
6
|
+
|
7
|
+
include WebTranslateIt::Safe
|
8
|
+
|
9
|
+
def die(msg)
|
10
|
+
puts "ERROR: #{msg}"
|
11
|
+
exit 1
|
12
|
+
end
|
13
|
+
|
14
|
+
def usage
|
15
|
+
puts <<~END
|
16
|
+
Usage: webtranslateit-safe [OPTIONS] CONFIG_FILE
|
17
|
+
Options:
|
18
|
+
-h, --help This help screen
|
19
|
+
-v, --verbose be verbose, duh!
|
20
|
+
-n, --dry-run just pretend, don't do anything.
|
21
|
+
-L, --local skip S3 and Cloud Files
|
22
|
+
|
23
|
+
Note: config file will be created from template if missing
|
24
|
+
END
|
25
|
+
exit 1
|
26
|
+
end
|
27
|
+
|
28
|
+
OPTS = [
|
29
|
+
'-h', '--help',
|
30
|
+
'-v', '--verbose', '--not-verbose',
|
31
|
+
'-n', '--dry-run', '--not-dry-run',
|
32
|
+
'-L', '--local', '--not-local'
|
33
|
+
].freeze
|
34
|
+
def main
|
35
|
+
opts = ARGV & OPTS
|
36
|
+
args = ARGV - OPTS
|
37
|
+
|
38
|
+
usage unless args.first
|
39
|
+
usage if opts.delete('-h') || opts.delete('--help')
|
40
|
+
|
41
|
+
config_file = File.expand_path(args.first)
|
42
|
+
|
43
|
+
is_dry = (opts.delete('-n') || opts.delete('--dry-run')) && !opts.delete('--not-dry-run')
|
44
|
+
is_verbose = (opts.delete('-v') || opts.delete('--verbose')) && !opts.delete('--not-verbose')
|
45
|
+
is_local_only = (opts.delete('-L') || opts.delete('--local')) && !opts.delete('--not-local')
|
46
|
+
|
47
|
+
unless File.exist?(config_file)
|
48
|
+
die 'Missing configuration file. NOT CREATED! Rerun w/o the -n argument to create a template configuration file.' if is_dry
|
49
|
+
|
50
|
+
FileUtils.cp File.join(WebTranslateIt::Safe::ROOT, 'templates', 'script.rb'), config_file
|
51
|
+
|
52
|
+
die "Created default #{config_file}. Please edit and run again."
|
53
|
+
end
|
54
|
+
|
55
|
+
config = eval(File.read(config_file))
|
56
|
+
|
57
|
+
config[:verbose] = is_verbose
|
58
|
+
config[:dry_run] = is_dry
|
59
|
+
config[:local_only] = is_local_only
|
60
|
+
|
61
|
+
process config
|
62
|
+
end
|
63
|
+
|
64
|
+
main
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'tmpdir'
|
2
|
+
|
3
|
+
unless Dir.respond_to?(:mktmpdir)
|
4
|
+
# backward compat for 1.8.6
|
5
|
+
class Dir
|
6
|
+
def Dir.mktmpdir(prefix_suffix=nil, tmpdir=nil)
|
7
|
+
case prefix_suffix
|
8
|
+
when nil
|
9
|
+
prefix = 'd'
|
10
|
+
suffix = ''
|
11
|
+
when String
|
12
|
+
prefix = prefix_suffix
|
13
|
+
suffix = ''
|
14
|
+
when Array
|
15
|
+
prefix = prefix_suffix[0]
|
16
|
+
suffix = prefix_suffix[1]
|
17
|
+
else
|
18
|
+
raise ArgumentError, "unexpected prefix_suffix: #{prefix_suffix.inspect}"
|
19
|
+
end
|
20
|
+
tmpdir ||= Dir.tmpdir
|
21
|
+
t = Time.now.strftime('%Y%m%d')
|
22
|
+
n = nil
|
23
|
+
begin
|
24
|
+
path = "#{tmpdir}/#{prefix}#{t}-#{$$}-#{rand(0x100000000).to_s(36)}"
|
25
|
+
path << "-#{n}" if n
|
26
|
+
path << suffix
|
27
|
+
Dir.mkdir(path, 0700)
|
28
|
+
rescue Errno::EEXIST
|
29
|
+
n ||= 0
|
30
|
+
n += 1
|
31
|
+
retry
|
32
|
+
end
|
33
|
+
|
34
|
+
if block_given?
|
35
|
+
begin
|
36
|
+
yield path
|
37
|
+
ensure
|
38
|
+
FileUtils.remove_entry_secure path
|
39
|
+
end
|
40
|
+
else
|
41
|
+
path
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module WebTranslateIt
|
2
|
+
|
3
|
+
module Safe
|
4
|
+
|
5
|
+
class Archive < Source
|
6
|
+
|
7
|
+
def command
|
8
|
+
"tar -cf - #{config[:options]} #{tar_exclude_files} #{tar_files}"
|
9
|
+
end
|
10
|
+
|
11
|
+
def extension = '.tar'
|
12
|
+
|
13
|
+
protected
|
14
|
+
|
15
|
+
def tar_exclude_files
|
16
|
+
[*config[:exclude]].compact.map { |x| "--exclude=#{x}" }.join(' ')
|
17
|
+
end
|
18
|
+
|
19
|
+
def tar_files
|
20
|
+
raise 'missing files for tar' unless config[:files]
|
21
|
+
|
22
|
+
[*config[:files]].map(&:strip).join(' ')
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module WebTranslateIt
|
2
|
+
|
3
|
+
module Safe
|
4
|
+
|
5
|
+
class Backup
|
6
|
+
|
7
|
+
attr_accessor :id, :kind, :filename, :extension, :command, :compressed, :timestamp, :path
|
8
|
+
|
9
|
+
def initialize(opts = {})
|
10
|
+
opts.each do |k, v|
|
11
|
+
send("#{k}=", v)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def run(config, *mods)
|
16
|
+
mods.each do |mod|
|
17
|
+
mod = mod.to_s
|
18
|
+
mod[0] = mod[0..0].upcase
|
19
|
+
WebTranslateIt::Safe.const_get(mod).new(config, self).process
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
module WebTranslateIt
|
2
|
+
module Safe
|
3
|
+
class Cloudfiles < Sink
|
4
|
+
MAX_CLOUDFILES_FILE_SIZE = 5368709120
|
5
|
+
|
6
|
+
def active?
|
7
|
+
container && user && api_key
|
8
|
+
end
|
9
|
+
|
10
|
+
protected
|
11
|
+
|
12
|
+
def path
|
13
|
+
@path ||= expand(config[:cloudfiles, :path] || config[:local, :path] || ':kind/:id')
|
14
|
+
end
|
15
|
+
|
16
|
+
# UGLY: we need this function for the reason that
|
17
|
+
# we can't double mock on ruby 1.9.2, duh!
|
18
|
+
# so we created this func to mock it all together
|
19
|
+
def get_file_size(path)
|
20
|
+
File.stat(path).size
|
21
|
+
end
|
22
|
+
|
23
|
+
def save
|
24
|
+
raise RuntimeError, 'pipe-streaming not supported for S3.' unless @backup.path
|
25
|
+
|
26
|
+
# needed in cleanup even on dry run
|
27
|
+
cf = CloudFiles::Connection.new(user, api_key, true, service_net) unless local_only?
|
28
|
+
puts "Uploading #{container}:#{full_path} from #{@backup.path}" if verbose? || dry_run?
|
29
|
+
unless dry_run? || local_only?
|
30
|
+
if get_file_size(@backup.path) > MAX_CLOUDFILES_FILE_SIZE
|
31
|
+
STDERR.puts "ERROR: File size exceeds maximum allowed for upload to Cloud Files (#{MAX_CLOUDFILES_FILE_SIZE}): #{@backup.path}"
|
32
|
+
return
|
33
|
+
end
|
34
|
+
benchmark = Benchmark.realtime do
|
35
|
+
cf_container = cf.create_container(container)
|
36
|
+
o = cf_container.create_object(full_path,true)
|
37
|
+
o.write(File.open(@backup.path))
|
38
|
+
end
|
39
|
+
puts '...done' if verbose?
|
40
|
+
puts('Upload took ' + sprintf('%.2f', benchmark) + ' second(s).') if verbose?
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def cleanup
|
45
|
+
return if local_only?
|
46
|
+
|
47
|
+
return unless keep = config[:keep, :cloudfiles]
|
48
|
+
|
49
|
+
puts "listing files: #{container}:#{base}*" if verbose?
|
50
|
+
cf = CloudFiles::Connection.new(user, api_key, true, service_net) unless local_only?
|
51
|
+
cf_container = cf.container(container)
|
52
|
+
files = cf_container.objects(:prefix => base).sort
|
53
|
+
|
54
|
+
cleanup_with_limit(files, keep) do |f|
|
55
|
+
puts "removing Cloud File #{container}:#{f}" if dry_run? || verbose?
|
56
|
+
cf_container.delete_object(f) unless dry_run? || local_only?
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def container
|
61
|
+
config[:cloudfiles, :container]
|
62
|
+
end
|
63
|
+
|
64
|
+
def user
|
65
|
+
config[:cloudfiles, :user]
|
66
|
+
end
|
67
|
+
|
68
|
+
def api_key
|
69
|
+
config[:cloudfiles, :api_key]
|
70
|
+
end
|
71
|
+
|
72
|
+
def service_net
|
73
|
+
config[:cloudfiles, :service_net] || false
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
module WebTranslateIt
|
2
|
+
|
3
|
+
module Safe
|
4
|
+
|
5
|
+
module Config
|
6
|
+
|
7
|
+
class Builder
|
8
|
+
|
9
|
+
def initialize(node, data = {})
|
10
|
+
@node = node
|
11
|
+
data.each { |k, v| send k, v }
|
12
|
+
end
|
13
|
+
|
14
|
+
|
15
|
+
class << self
|
16
|
+
|
17
|
+
def simple_value(*names)
|
18
|
+
names.each do |m|
|
19
|
+
define_method(m) do |value|
|
20
|
+
ensure_uniq(m)
|
21
|
+
@node.set m, value
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def multi_value(*names)
|
27
|
+
names.each do |m|
|
28
|
+
define_method(m) do |value|
|
29
|
+
value = value.map(&:to_s) if value.is_a?(Array)
|
30
|
+
@node.set_multi m, value
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def hash_value(*names)
|
36
|
+
names.each do |m|
|
37
|
+
define_method(m) do |data = {}, &block|
|
38
|
+
ensure_uniq(m)
|
39
|
+
ensure_hash(m, data)
|
40
|
+
@node.set m, Node.new(@node, data || {}, &block)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def mixed_value(*names)
|
46
|
+
names.each do |m|
|
47
|
+
define_method(m) do |data = {}, &block|
|
48
|
+
ensure_uniq(m)
|
49
|
+
if data.is_a?(Hash) || block
|
50
|
+
ensure_hash(m, data) if block
|
51
|
+
@node.set m, Node.new(@node, data, &block)
|
52
|
+
else
|
53
|
+
@node.set m, data
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def collection(*names)
|
60
|
+
names.each do |m|
|
61
|
+
define_method(m) do |id, data = {}, &block|
|
62
|
+
raise "bad collection id: #{id.inspect}" unless id
|
63
|
+
|
64
|
+
ensure_hash(m, data)
|
65
|
+
|
66
|
+
name = "#{m}s"
|
67
|
+
collection = @node.get(name) || @node.set(name, Node.new(@node, {}))
|
68
|
+
collection.set id, Node.new(collection, data, &block)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
|
75
|
+
simple_value :verbose, :dry_run, :local_only, :path, :command,
|
76
|
+
:options, :user, :host, :port, :password, :key, :secret, :bucket,
|
77
|
+
:api_key, :container, :socket, :service_net, :repo_path
|
78
|
+
multi_value :skip_tables, :exclude, :files
|
79
|
+
hash_value :mysqldump, :tar, :gpg, :keep, :pgdump, :tar, :svndump,
|
80
|
+
:sftp, :ftp, :mongodump
|
81
|
+
mixed_value :s3, :local, :cloudfiles
|
82
|
+
collection :database, :archive, :repo
|
83
|
+
|
84
|
+
private
|
85
|
+
|
86
|
+
def ensure_uniq(m)
|
87
|
+
raise(ArgumentError, "duplicate value for '#{m}'") if @node.get(m)
|
88
|
+
end
|
89
|
+
|
90
|
+
def ensure_hash(k, v)
|
91
|
+
raise "#{k}: hash expected: #{v.inspect}" unless v.is_a?(Hash)
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|