astrails-safe 0.2.0 → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|