astrails-safe 0.2.0 → 0.2.1
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/README.markdown +22 -5
- data/VERSION.yml +1 -1
- data/examples/integration/cleanup_example.rb +62 -0
- data/examples/unit/config_example.rb +12 -0
- data/examples/unit/local_example.rb +4 -4
- data/examples/unit/s3_example.rb +1 -1
- data/lib/astrails/safe.rb +4 -2
- data/lib/astrails/safe/config/builder.rb +1 -1
- data/lib/astrails/safe/local.rb +4 -1
- data/lib/astrails/safe/s3.rb +6 -3
- data/lib/astrails/safe/sftp.rb +77 -0
- data/lib/astrails/safe/sink.rb +1 -1
- data/templates/script.rb +8 -0
- metadata +11 -8
data/README.markdown
CHANGED
|
@@ -20,12 +20,23 @@ We needed a backup solution that will satisfy the following requirements:
|
|
|
20
20
|
|
|
21
21
|
And since we didn't find any, we wrote our own :)
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
Contributions
|
|
24
|
+
-------------
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
The following functionality was contributed by astrails-safe users:
|
|
27
|
+
|
|
28
|
+
* PostgreSQL dump using pg_dump (by Mark Mansour <mark@stateofflux.com>)
|
|
29
|
+
* Subversion dump using svndump (by Richard Luther <richard.luther@gmail.com>)
|
|
30
|
+
* SFTP remote storage (by Adam <adam@mediadrive.ca>)
|
|
31
|
+
* benchmarking output (By Neer)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
Thanks to all :)
|
|
35
|
+
|
|
36
|
+
Reporting problems
|
|
37
|
+
------------------
|
|
38
|
+
|
|
39
|
+
Please report problems at the [Issues tracker](http://github.com/astrails/safe/issues)
|
|
29
40
|
|
|
30
41
|
Usage
|
|
31
42
|
-----
|
|
@@ -118,6 +129,12 @@ Example configuration
|
|
|
118
129
|
path "servers/alpha/:kind/:id"
|
|
119
130
|
end
|
|
120
131
|
|
|
132
|
+
sftp do
|
|
133
|
+
host "sftp.astrails.com"
|
|
134
|
+
user "astrails"
|
|
135
|
+
password "ssh password for sftp"
|
|
136
|
+
end
|
|
137
|
+
|
|
121
138
|
gpg do
|
|
122
139
|
# symmetric encryption key
|
|
123
140
|
# password "qwe"
|
data/VERSION.yml
CHANGED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../example_helper')
|
|
2
|
+
|
|
3
|
+
require "fileutils"
|
|
4
|
+
include FileUtils
|
|
5
|
+
|
|
6
|
+
describe "tar backup" do
|
|
7
|
+
before(:all) do
|
|
8
|
+
# need both local and instance vars
|
|
9
|
+
# instance variables are used in tests
|
|
10
|
+
# local variables are used in the backup definition (instance vars can't be seen)
|
|
11
|
+
@root = root = "tmp/cleanup_example"
|
|
12
|
+
|
|
13
|
+
# clean state
|
|
14
|
+
rm_rf @root
|
|
15
|
+
mkdir_p @root
|
|
16
|
+
|
|
17
|
+
# create source tree
|
|
18
|
+
@src = src = "#{@root}/src"
|
|
19
|
+
mkdir_p src
|
|
20
|
+
|
|
21
|
+
File.open(qwe = "#{@src}/qwe", "w") {|f| f.write("qwe") }
|
|
22
|
+
|
|
23
|
+
@dst = dst = "#{@root}/backup"
|
|
24
|
+
mkdir_p "#{@dst}/archive"
|
|
25
|
+
|
|
26
|
+
@now = Time.now
|
|
27
|
+
@timestamp = @now.strftime("%y%m%d-%H%M")
|
|
28
|
+
|
|
29
|
+
stub(Time).now {@now} # Freeze
|
|
30
|
+
|
|
31
|
+
cp qwe, "#{dst}/archive/archive-foo.000001.tar.gz"
|
|
32
|
+
cp qwe, "#{dst}/archive/archive-foo.000002.tar.gz"
|
|
33
|
+
cp qwe, "#{dst}/archive/archive-foobar.000001.tar.gz"
|
|
34
|
+
cp qwe, "#{dst}/archive/archive-foobar.000002.tar.gz"
|
|
35
|
+
|
|
36
|
+
Astrails::Safe.safe do
|
|
37
|
+
local :path => "#{dst}/:kind"
|
|
38
|
+
tar do
|
|
39
|
+
keep :local => 1 # only leave the latest
|
|
40
|
+
archive :foo do
|
|
41
|
+
files src
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
@backup = "#{dst}/archive/archive-foo.#{@timestamp}.tar.gz"
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
it "should create backup file" do
|
|
50
|
+
puts "Expecting: #{@backup}"
|
|
51
|
+
File.exists?(@backup).should be_true
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
it "should remove old backups" do
|
|
55
|
+
Dir["#{@dst}/archive/archive-foo.*"].should == [@backup]
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
it "should NOT remove backups with base having same prefix" do
|
|
59
|
+
Dir["#{@dst}/archive/archive-foobar.*"].should == ["#{@dst}/archive/archive-foobar.000001.tar.gz", "#{@dst}/archive/archive-foobar.000002.tar.gz"]
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
end
|
|
@@ -14,6 +14,12 @@ describe Astrails::Safe::Config do
|
|
|
14
14
|
path "path1"
|
|
15
15
|
end
|
|
16
16
|
|
|
17
|
+
sftp do
|
|
18
|
+
user "sftp user"
|
|
19
|
+
password "sftp password"
|
|
20
|
+
host "sftp host"
|
|
21
|
+
end
|
|
22
|
+
|
|
17
23
|
gpg do
|
|
18
24
|
key "gpg-key"
|
|
19
25
|
password "astrails"
|
|
@@ -107,6 +113,12 @@ describe Astrails::Safe::Config do
|
|
|
107
113
|
"path" => "path1",
|
|
108
114
|
},
|
|
109
115
|
|
|
116
|
+
"sftp" => {
|
|
117
|
+
"user" => "sftp user",
|
|
118
|
+
"password" => "sftp password",
|
|
119
|
+
"host" => "sftp host",
|
|
120
|
+
},
|
|
121
|
+
|
|
110
122
|
"gpg" => {"password" => "astrails", "key" => "gpg-key"},
|
|
111
123
|
|
|
112
124
|
"keep" => {"s3" => 20, "local" => 4},
|
|
@@ -82,8 +82,8 @@ describe Astrails::Safe::Local do
|
|
|
82
82
|
describe :cleanup do
|
|
83
83
|
before(:each) do
|
|
84
84
|
@local = local
|
|
85
|
-
@files = [4,1,3,2].to_a.map { |i| "
|
|
86
|
-
stub(Dir).[](
|
|
85
|
+
@files = [4,1,3,2].to_a.map { |i| "/mysqldump~blog~NoW/qweqwe.#{i}" }
|
|
86
|
+
stub(Dir).[]("/mysqldump~blog~NoW/qweqwe.*") {@files}
|
|
87
87
|
stub(File).file?(anything) {true}
|
|
88
88
|
stub(File).size(anything) {1}
|
|
89
89
|
stub(File).unlink
|
|
@@ -96,8 +96,8 @@ describe Astrails::Safe::Local do
|
|
|
96
96
|
end
|
|
97
97
|
|
|
98
98
|
it "should delete extra files" do
|
|
99
|
-
mock(File).unlink("
|
|
100
|
-
mock(File).unlink("
|
|
99
|
+
mock(File).unlink("/mysqldump~blog~NoW/qweqwe.1")
|
|
100
|
+
mock(File).unlink("/mysqldump~blog~NoW/qweqwe.2")
|
|
101
101
|
@local.send :cleanup
|
|
102
102
|
end
|
|
103
103
|
end
|
data/examples/unit/s3_example.rb
CHANGED
|
@@ -39,7 +39,7 @@ describe Astrails::Safe::S3 do
|
|
|
39
39
|
|
|
40
40
|
@files = [4,1,3,2].to_a.map { |i| stub(o = {}).key {"aaaaa#{i}"}; o }
|
|
41
41
|
|
|
42
|
-
stub(AWS::S3::Bucket).objects("_bucket", :prefix => "_kind/_id/_kind-_id", :max_keys => 4) {@files}
|
|
42
|
+
stub(AWS::S3::Bucket).objects("_bucket", :prefix => "_kind/_id/_kind-_id.", :max_keys => 4) {@files}
|
|
43
43
|
stub(AWS::S3::Bucket).find("_bucket").stub![anything].stub!.delete
|
|
44
44
|
end
|
|
45
45
|
|
data/lib/astrails/safe.rb
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
require "aws/s3"
|
|
2
|
+
require 'net/sftp'
|
|
2
3
|
require 'fileutils'
|
|
4
|
+
require 'benchmark'
|
|
3
5
|
|
|
4
6
|
require 'tempfile'
|
|
5
7
|
require 'extensions/mktmpdir'
|
|
@@ -28,7 +30,7 @@ require 'astrails/safe/gzip'
|
|
|
28
30
|
require 'astrails/safe/sink'
|
|
29
31
|
require 'astrails/safe/local'
|
|
30
32
|
require 'astrails/safe/s3'
|
|
31
|
-
|
|
33
|
+
require 'astrails/safe/sftp'
|
|
32
34
|
|
|
33
35
|
module Astrails
|
|
34
36
|
module Safe
|
|
@@ -46,7 +48,7 @@ module Astrails
|
|
|
46
48
|
].each do |klass, path|
|
|
47
49
|
if collection = config[*path]
|
|
48
50
|
collection.each do |name, config|
|
|
49
|
-
klass.new(name, config).backup.run(config, :gpg, :gzip, :local, :s3)
|
|
51
|
+
klass.new(name, config).backup.run(config, :gpg, :gzip, :local, :s3, :sftp)
|
|
50
52
|
end
|
|
51
53
|
end
|
|
52
54
|
end
|
|
@@ -4,7 +4,7 @@ module Astrails
|
|
|
4
4
|
class Builder
|
|
5
5
|
COLLECTIONS = %w/database archive repo/
|
|
6
6
|
ITEMS = %w/s3 key secret bucket path gpg password keep local mysqldump pgdump options
|
|
7
|
-
user host port socket skip_tables tar files exclude filename svndump repo_path/
|
|
7
|
+
user host port socket skip_tables tar files exclude filename svndump repo_path sftp/
|
|
8
8
|
NAMES = COLLECTIONS + ITEMS
|
|
9
9
|
def initialize(node)
|
|
10
10
|
@node = node
|
data/lib/astrails/safe/local.rb
CHANGED
|
@@ -19,7 +19,10 @@ module Astrails
|
|
|
19
19
|
|
|
20
20
|
unless $DRY_RUN
|
|
21
21
|
FileUtils.mkdir_p(path) unless File.directory?(path)
|
|
22
|
-
|
|
22
|
+
benchmark = Benchmark.realtime do
|
|
23
|
+
system "#{@backup.command}>#{@backup.path = full_path}"
|
|
24
|
+
end
|
|
25
|
+
puts("command took " + sprintf("%.2f", benchmark) + " second(s).") if $_VERBOSE
|
|
23
26
|
end
|
|
24
27
|
|
|
25
28
|
end
|
data/lib/astrails/safe/s3.rb
CHANGED
|
@@ -20,11 +20,14 @@ module Astrails
|
|
|
20
20
|
|
|
21
21
|
puts "Uploading #{bucket}:#{full_path}" if $_VERBOSE || $DRY_RUN
|
|
22
22
|
unless $DRY_RUN || $LOCAL
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
benchmark = Benchmark.realtime do
|
|
24
|
+
AWS::S3::Bucket.create(bucket)
|
|
25
|
+
File.open(@backup.path) do |file|
|
|
26
|
+
AWS::S3::S3Object.store(full_path, file, bucket)
|
|
27
|
+
end
|
|
26
28
|
end
|
|
27
29
|
puts "...done" if $_VERBOSE
|
|
30
|
+
puts("Upload took " + sprintf("%.2f", benchmark) + " second(s).") if $_VERBOSE
|
|
28
31
|
end
|
|
29
32
|
end
|
|
30
33
|
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
module Astrails
|
|
2
|
+
module Safe
|
|
3
|
+
class Sftp < Sink
|
|
4
|
+
|
|
5
|
+
protected
|
|
6
|
+
|
|
7
|
+
def active?
|
|
8
|
+
host && user
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def path
|
|
12
|
+
@path ||= expand(config[:sftp, :path] || config[:local, :path] || ":kind/:id")
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def save
|
|
16
|
+
puts "Uploading #{host}:#{full_path} via SFTP" if $_VERBOSE || $DRY_RUN
|
|
17
|
+
|
|
18
|
+
unless $DRY_RUN || $LOCAL
|
|
19
|
+
opts = {}
|
|
20
|
+
opts[:password] = password if password
|
|
21
|
+
Net::SFTP.start(host, user, opts) do |sftp|
|
|
22
|
+
puts "Sending #{@backup.path} to #{full_path}" if $_VERBOSE
|
|
23
|
+
begin
|
|
24
|
+
sftp.upload! @backup.path, full_path
|
|
25
|
+
rescue Net::SFTP::StatusException
|
|
26
|
+
puts "Ensuring remote path (#{path}) exists" if $_VERBOSE
|
|
27
|
+
# mkdir -p
|
|
28
|
+
folders = path.split('/')
|
|
29
|
+
folders.each_index do |i|
|
|
30
|
+
folder = folders[0..i].join('/')
|
|
31
|
+
puts "Creating #{folder} on remote" if $_VERBOSE
|
|
32
|
+
sftp.mkdir!(folder) rescue Net::SFTP::StatusException
|
|
33
|
+
end
|
|
34
|
+
retry
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
puts "...done" if $_VERBOSE
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def cleanup
|
|
42
|
+
return if $LOCAL || $DRY_RUN
|
|
43
|
+
|
|
44
|
+
return unless keep = @config[:keep, :sftp]
|
|
45
|
+
|
|
46
|
+
puts "listing files in #{host}:#{path}" if $_VERBOSE
|
|
47
|
+
Net::SFTP.start(host, user, :password => password) do |sftp|
|
|
48
|
+
files = sftp.dir.glob(path, '*')
|
|
49
|
+
|
|
50
|
+
puts files.collect {|x| x.name } if $_VERBOSE
|
|
51
|
+
|
|
52
|
+
files = files.
|
|
53
|
+
collect {|x| x.name }.
|
|
54
|
+
sort
|
|
55
|
+
|
|
56
|
+
cleanup_with_limit(files, keep) do |f|
|
|
57
|
+
file = File.join(path, f)
|
|
58
|
+
puts "removing sftp file #{host}:#{file}" if $DRY_RUN || $_VERBOSE
|
|
59
|
+
sftp.remove!(file) unless $DRY_RUN || $LOCAL
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def host
|
|
65
|
+
@config[:sftp, :host]
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def user
|
|
69
|
+
@config[:sftp, :user]
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def password
|
|
73
|
+
@config[:sftp, :password]
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
data/lib/astrails/safe/sink.rb
CHANGED
data/templates/script.rb
CHANGED
|
@@ -24,6 +24,14 @@ safe do
|
|
|
24
24
|
## alternative style:
|
|
25
25
|
# s3 :key => YOUR_S3_KEY, :secret => YOUR_S3_SECRET, :bucket => S3_BUCKET
|
|
26
26
|
|
|
27
|
+
## uncomment to enable uploads via SFTP
|
|
28
|
+
# sftp do
|
|
29
|
+
# host "YOUR_REMOTE_HOSTNAME"
|
|
30
|
+
# user "YOUR_REMOTE_USERNAME"
|
|
31
|
+
# password "YOUR_REMOTE_PASSWORD"
|
|
32
|
+
# path ":kind/:id" # this is the default
|
|
33
|
+
# end
|
|
34
|
+
|
|
27
35
|
## uncomment to enable GPG encryption.
|
|
28
36
|
## Note: you can use public 'key' or symmetric password but not both!
|
|
29
37
|
# gpg do
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: astrails-safe
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.2.
|
|
4
|
+
version: 0.2.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Astrails Ltd.
|
|
@@ -10,7 +10,7 @@ autorequire:
|
|
|
10
10
|
bindir: bin
|
|
11
11
|
cert_chain: []
|
|
12
12
|
|
|
13
|
-
date: 2009-
|
|
13
|
+
date: 2009-07-05 00:00:00 -07:00
|
|
14
14
|
default_executable: astrails-safe
|
|
15
15
|
dependencies:
|
|
16
16
|
- !ruby/object:Gem::Dependency
|
|
@@ -40,6 +40,7 @@ files:
|
|
|
40
40
|
- bin/astrails-safe
|
|
41
41
|
- examples/example_helper.rb
|
|
42
42
|
- examples/integration/archive_integration_example.rb
|
|
43
|
+
- examples/integration/cleanup_example.rb
|
|
43
44
|
- examples/unit/archive_example.rb
|
|
44
45
|
- examples/unit/config_example.rb
|
|
45
46
|
- examples/unit/gpg_example.rb
|
|
@@ -61,6 +62,7 @@ files:
|
|
|
61
62
|
- lib/astrails/safe/pgdump.rb
|
|
62
63
|
- lib/astrails/safe/pipe.rb
|
|
63
64
|
- lib/astrails/safe/s3.rb
|
|
65
|
+
- lib/astrails/safe/sftp.rb
|
|
64
66
|
- lib/astrails/safe/sink.rb
|
|
65
67
|
- lib/astrails/safe/source.rb
|
|
66
68
|
- lib/astrails/safe/stream.rb
|
|
@@ -95,14 +97,15 @@ signing_key:
|
|
|
95
97
|
specification_version: 2
|
|
96
98
|
summary: Backup filesystem and databases (MySQL and PostgreSQL) to Amazon S3 (with encryption)
|
|
97
99
|
test_files:
|
|
98
|
-
- examples/integration/archive_integration_example.rb
|
|
99
100
|
- examples/example_helper.rb
|
|
100
|
-
- examples/
|
|
101
|
-
- examples/
|
|
102
|
-
- examples/unit/mysqldump_example.rb
|
|
103
|
-
- examples/unit/gpg_example.rb
|
|
101
|
+
- examples/integration/archive_integration_example.rb
|
|
102
|
+
- examples/integration/cleanup_example.rb
|
|
104
103
|
- examples/unit/archive_example.rb
|
|
105
|
-
- examples/unit/svndump_example.rb
|
|
106
104
|
- examples/unit/config_example.rb
|
|
105
|
+
- examples/unit/gpg_example.rb
|
|
106
|
+
- examples/unit/gzip_example.rb
|
|
107
107
|
- examples/unit/local_example.rb
|
|
108
|
+
- examples/unit/mysqldump_example.rb
|
|
108
109
|
- examples/unit/pgdump_example.rb
|
|
110
|
+
- examples/unit/s3_example.rb
|
|
111
|
+
- examples/unit/svndump_example.rb
|