darkofabijan-astrails-safe 0.2.8
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/LICENSE +20 -0
- data/README.markdown +237 -0
- data/Rakefile +61 -0
- data/bin/astrails-safe +53 -0
- data/examples/example_helper.rb +19 -0
- data/lib/astrails/safe.rb +61 -0
- data/lib/astrails/safe/archive.rb +24 -0
- data/lib/astrails/safe/backup.rb +20 -0
- data/lib/astrails/safe/cloudfiles.rb +70 -0
- data/lib/astrails/safe/config/builder.rb +60 -0
- data/lib/astrails/safe/config/node.rb +76 -0
- data/lib/astrails/safe/gpg.rb +46 -0
- data/lib/astrails/safe/gzip.rb +25 -0
- data/lib/astrails/safe/local.rb +70 -0
- data/lib/astrails/safe/mysqldump.rb +32 -0
- data/lib/astrails/safe/pgdump.rb +36 -0
- data/lib/astrails/safe/pipe.rb +17 -0
- data/lib/astrails/safe/s3.rb +86 -0
- data/lib/astrails/safe/sftp.rb +88 -0
- data/lib/astrails/safe/sink.rb +35 -0
- data/lib/astrails/safe/source.rb +47 -0
- data/lib/astrails/safe/stream.rb +20 -0
- data/lib/astrails/safe/svndump.rb +13 -0
- data/lib/astrails/safe/tmp_file.rb +48 -0
- data/lib/extensions/mktmpdir.rb +45 -0
- data/spec/integration/archive_integration_spec.rb +88 -0
- data/spec/integration/cleanup_spec.rb +61 -0
- data/spec/spec.opts +5 -0
- data/spec/spec_helper.rb +16 -0
- data/spec/unit/archive_spec.rb +67 -0
- data/spec/unit/cloudfiles_spec.rb +170 -0
- data/spec/unit/config_spec.rb +213 -0
- data/spec/unit/gpg_spec.rb +148 -0
- data/spec/unit/gzip_spec.rb +64 -0
- data/spec/unit/local_spec.rb +110 -0
- data/spec/unit/mysqldump_spec.rb +83 -0
- data/spec/unit/pgdump_spec.rb +45 -0
- data/spec/unit/s3_spec.rb +160 -0
- data/spec/unit/svndump_spec.rb +39 -0
- data/templates/script.rb +165 -0
- metadata +179 -0
@@ -0,0 +1,148 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe Astrails::Safe::Gpg do
|
4
|
+
def def_backup
|
5
|
+
{
|
6
|
+
:compressed => false,
|
7
|
+
:command => "command",
|
8
|
+
:extension => ".foo",
|
9
|
+
:filename => "qweqwe"
|
10
|
+
}
|
11
|
+
end
|
12
|
+
|
13
|
+
def gpg(config = {}, backup = def_backup)
|
14
|
+
Astrails::Safe::Gpg.new(
|
15
|
+
Astrails::Safe::Config::Node.new(nil, config),
|
16
|
+
Astrails::Safe::Backup.new(backup)
|
17
|
+
)
|
18
|
+
end
|
19
|
+
|
20
|
+
after(:each) { Astrails::Safe::TmpFile.cleanup }
|
21
|
+
|
22
|
+
describe :process do
|
23
|
+
|
24
|
+
before(:each) do
|
25
|
+
@gpg = gpg()
|
26
|
+
stub(@gpg).gpg_password_file {"pwd-file"}
|
27
|
+
stub(@gpg).pipe {"|gpg -BLAH"}
|
28
|
+
end
|
29
|
+
|
30
|
+
describe "when active" do
|
31
|
+
before(:each) do
|
32
|
+
stub(@gpg).active? {true}
|
33
|
+
end
|
34
|
+
|
35
|
+
it "should add .gpg extension" do
|
36
|
+
mock(@gpg.backup.extension) << '.gpg'
|
37
|
+
@gpg.process
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should add command pipe" do
|
41
|
+
mock(@gpg.backup.command) << (/\|gpg -BLAH/)
|
42
|
+
@gpg.process
|
43
|
+
end
|
44
|
+
|
45
|
+
it "should set compressed" do
|
46
|
+
mock(@gpg.backup).compressed = true
|
47
|
+
@gpg.process
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
describe "when inactive" do
|
52
|
+
before(:each) do
|
53
|
+
stub(@gpg).active? {false}
|
54
|
+
end
|
55
|
+
|
56
|
+
it "should not touch extension" do
|
57
|
+
dont_allow(@gpg.backup.extension) << anything
|
58
|
+
@gpg.process
|
59
|
+
end
|
60
|
+
|
61
|
+
it "should not touch command" do
|
62
|
+
dont_allow(@gpg.backup.command) << anything
|
63
|
+
@gpg.process
|
64
|
+
end
|
65
|
+
|
66
|
+
it "should not touch compressed" do
|
67
|
+
dont_allow(@gpg.backup).compressed = anything
|
68
|
+
@gpg.process
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
describe :active? do
|
74
|
+
|
75
|
+
describe "with key" do
|
76
|
+
it "should be true" do
|
77
|
+
gpg(:gpg => {:key => :foo}).should be_active
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
describe "with password" do
|
82
|
+
it "should be true" do
|
83
|
+
gpg(:gpg => {:password => :foo}).should be_active
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
describe "without key & password" do
|
88
|
+
it "should be false" do
|
89
|
+
gpg.should_not be_active
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
describe "with key & password" do
|
94
|
+
it "should raise RuntimeError" do
|
95
|
+
lambda {
|
96
|
+
gpg(:gpg => {:key => "foo", :password => "bar"}).send :active?
|
97
|
+
}.should raise_error(RuntimeError, "can't use both gpg password and pubkey")
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
describe :pipe do
|
103
|
+
|
104
|
+
describe "with key" do
|
105
|
+
def kgpg(extra={})
|
106
|
+
gpg({:gpg => {:key => "foo", :options => "GPG-OPT"}.merge(extra), :options => "OPT"})
|
107
|
+
end
|
108
|
+
|
109
|
+
it "should not call gpg_password_file" do
|
110
|
+
g = kgpg
|
111
|
+
dont_allow(g).gpg_password_file(anything)
|
112
|
+
g.send(:pipe)
|
113
|
+
end
|
114
|
+
|
115
|
+
it "should use '-r' and :options" do
|
116
|
+
kgpg.send(:pipe).should == "|gpg GPG-OPT -e -r foo"
|
117
|
+
end
|
118
|
+
|
119
|
+
it "should use the 'command' options" do
|
120
|
+
kgpg(:command => 'other-gpg').send(:pipe).should == "|other-gpg GPG-OPT -e -r foo"
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
describe "with password" do
|
125
|
+
def pgpg(extra = {})
|
126
|
+
returning(gpg({:gpg => {:password => "bar", :options => "GPG-OPT"}.merge(extra), :options => "OPT"})) do |g|
|
127
|
+
stub(g).gpg_password_file(anything) {"pass-file"}
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
it "should use '--passphrase-file' and :options" do
|
132
|
+
pgpg.send(:pipe).should == "|gpg GPG-OPT -c --passphrase-file pass-file"
|
133
|
+
end
|
134
|
+
|
135
|
+
it "should use the 'command' options" do
|
136
|
+
pgpg(:command => 'other-gpg').send(:pipe).should == "|other-gpg GPG-OPT -c --passphrase-file pass-file"
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
describe :gpg_password_file do
|
142
|
+
it "should create password file" do
|
143
|
+
file = gpg.send(:gpg_password_file, "foo")
|
144
|
+
File.exists?(file).should be_true
|
145
|
+
File.read(file).should == "foo"
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe Astrails::Safe::Gzip do
|
4
|
+
|
5
|
+
def def_backup
|
6
|
+
{
|
7
|
+
:compressed => false,
|
8
|
+
:command => "command",
|
9
|
+
:extension => ".foo",
|
10
|
+
:filename => "qweqwe"
|
11
|
+
}
|
12
|
+
end
|
13
|
+
|
14
|
+
after(:each) { Astrails::Safe::TmpFile.cleanup }
|
15
|
+
|
16
|
+
def gzip(config = {}, backup = def_backup)
|
17
|
+
Astrails::Safe::Gzip.new(
|
18
|
+
@config = Astrails::Safe::Config::Node.new(nil, config),
|
19
|
+
@backup = Astrails::Safe::Backup.new(backup)
|
20
|
+
)
|
21
|
+
end
|
22
|
+
|
23
|
+
describe :preocess do
|
24
|
+
|
25
|
+
describe "when not yet compressed" do
|
26
|
+
before(:each) { @gzip = gzip }
|
27
|
+
|
28
|
+
it "should add .gz extension" do
|
29
|
+
mock(@backup.extension) << '.gz'
|
30
|
+
@gzip.process
|
31
|
+
end
|
32
|
+
|
33
|
+
it "should add |gzip pipe" do
|
34
|
+
mock(@backup.command) << '|gzip'
|
35
|
+
@gzip.process
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should set compressed" do
|
39
|
+
mock(@backup).compressed = true
|
40
|
+
@gzip.process
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
describe "when already compressed" do
|
45
|
+
|
46
|
+
before(:each) { @gzip = gzip({}, :compressed => true) }
|
47
|
+
|
48
|
+
it "should not touch extension" do
|
49
|
+
dont_allow(@backup.extension).<< anything
|
50
|
+
@gzip.process
|
51
|
+
end
|
52
|
+
|
53
|
+
it "should not touch command" do
|
54
|
+
dont_allow(@backup.command).<< anything
|
55
|
+
@gzip.process
|
56
|
+
end
|
57
|
+
|
58
|
+
it "should not touch compressed" do
|
59
|
+
dont_allow(@backup).compressed = anything
|
60
|
+
@gzip.process
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe Astrails::Safe::Local do
|
4
|
+
def def_config
|
5
|
+
{
|
6
|
+
:local => {
|
7
|
+
:path => "/:kind~:id~:timestamp"
|
8
|
+
},
|
9
|
+
:keep => {
|
10
|
+
:local => 2
|
11
|
+
}
|
12
|
+
}
|
13
|
+
end
|
14
|
+
|
15
|
+
def def_backup
|
16
|
+
{
|
17
|
+
:kind => "mysqldump",
|
18
|
+
:id => "blog",
|
19
|
+
:timestamp => "NoW",
|
20
|
+
:compressed => true,
|
21
|
+
:command => "command",
|
22
|
+
:extension => ".foo.gz",
|
23
|
+
:filename => "qweqwe"
|
24
|
+
}
|
25
|
+
end
|
26
|
+
|
27
|
+
def local(config = def_config, backup = def_backup)
|
28
|
+
Astrails::Safe::Local.new(
|
29
|
+
@config = Astrails::Safe::Config::Node.new(nil, config),
|
30
|
+
@backup = Astrails::Safe::Backup.new(backup)
|
31
|
+
)
|
32
|
+
end
|
33
|
+
|
34
|
+
describe :active? do
|
35
|
+
it "should be true" do
|
36
|
+
local.should be_active
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
describe :path do
|
41
|
+
it "should raise RuntimeError when no path" do
|
42
|
+
lambda {
|
43
|
+
local({}).send :path
|
44
|
+
}.should raise_error(RuntimeError, "missing :local/:path")
|
45
|
+
end
|
46
|
+
|
47
|
+
it "should use local/path" do
|
48
|
+
local.send(:path).should == "/mysqldump~blog~NoW"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
describe :save do
|
53
|
+
before(:each) do
|
54
|
+
@local = local
|
55
|
+
stub(@local).system
|
56
|
+
stub(@local).full_path {"file-path"}
|
57
|
+
stub(FileUtils).mkdir_p
|
58
|
+
end
|
59
|
+
|
60
|
+
it "should call system to save the file" do
|
61
|
+
mock(@local).system("command>file-path")
|
62
|
+
@local.send(:save)
|
63
|
+
end
|
64
|
+
|
65
|
+
it "should create directory" do
|
66
|
+
mock(FileUtils).mkdir_p("/mysqldump~blog~NoW")
|
67
|
+
@local.send(:save)
|
68
|
+
end
|
69
|
+
|
70
|
+
it "should set backup.path" do
|
71
|
+
mock(@backup).path = "file-path"
|
72
|
+
@local.send(:save)
|
73
|
+
end
|
74
|
+
|
75
|
+
describe "dry run" do
|
76
|
+
before(:all) {$DRY_RUN = true}
|
77
|
+
after(:all) {$DRY_RUN = false}
|
78
|
+
|
79
|
+
it "should not create directory"
|
80
|
+
it "should not call system"
|
81
|
+
it "should set backup.path" do
|
82
|
+
mock(@backup).path = "file-path"
|
83
|
+
@local.send(:save)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
describe :cleanup do
|
89
|
+
before(:each) do
|
90
|
+
@files = [4,1,3,2].to_a.map { |i| "/mysqldump~blog~NoW/qweqwe.#{i}" }
|
91
|
+
stub(File).file?(anything) {true}
|
92
|
+
stub(File).size(anything) {1}
|
93
|
+
stub(File).unlink
|
94
|
+
end
|
95
|
+
|
96
|
+
it "should check [:keep, :local]" do
|
97
|
+
@local = local(def_config.merge(:keep => {}))
|
98
|
+
dont_allow(Dir).[]
|
99
|
+
@local.send :cleanup
|
100
|
+
end
|
101
|
+
|
102
|
+
it "should delete extra files" do
|
103
|
+
@local = local
|
104
|
+
mock(Dir).[]("/mysqldump~blog~NoW/qweqwe.*") {@files}
|
105
|
+
mock(File).unlink("/mysqldump~blog~NoW/qweqwe.1")
|
106
|
+
mock(File).unlink("/mysqldump~blog~NoW/qweqwe.2")
|
107
|
+
@local.send :cleanup
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe Astrails::Safe::Mysqldump do
|
4
|
+
|
5
|
+
def def_config(extra = {})
|
6
|
+
{
|
7
|
+
:options => "OPTS",
|
8
|
+
:user => "User",
|
9
|
+
:password => "pwd",
|
10
|
+
:host => "localhost",
|
11
|
+
:port => 7777,
|
12
|
+
:socket => "socket",
|
13
|
+
:skip_tables => [:bar, :baz]
|
14
|
+
}.merge(extra)
|
15
|
+
end
|
16
|
+
|
17
|
+
def mysqldump(id = :foo, config = def_config)
|
18
|
+
Astrails::Safe::Mysqldump.new(id, Astrails::Safe::Config::Node.new(nil, config))
|
19
|
+
end
|
20
|
+
|
21
|
+
before(:each) do
|
22
|
+
stub(Time).now.stub!.strftime {"NOW"}
|
23
|
+
end
|
24
|
+
|
25
|
+
after(:each) { Astrails::Safe::TmpFile.cleanup }
|
26
|
+
|
27
|
+
describe :backup do
|
28
|
+
before(:each) do
|
29
|
+
@mysql = mysqldump
|
30
|
+
stub(@mysql).mysql_password_file {"/tmp/pwd"}
|
31
|
+
end
|
32
|
+
|
33
|
+
{
|
34
|
+
:id => "foo",
|
35
|
+
:kind => "mysqldump",
|
36
|
+
:extension => ".sql",
|
37
|
+
:filename => "mysqldump-foo.NOW",
|
38
|
+
:command => "mysqldump --defaults-extra-file=/tmp/pwd OPTS --ignore-table=foo.bar --ignore-table=foo.baz foo",
|
39
|
+
}.each do |k, v|
|
40
|
+
it "should set #{k} to #{v}" do
|
41
|
+
@mysql.backup.send(k).should == v
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
describe :mysql_skip_tables do
|
48
|
+
it "should return nil if no skip_tables" do
|
49
|
+
config = def_config.dup
|
50
|
+
config.delete(:skip_tables)
|
51
|
+
m = mysqldump(:foo, Astrails::Safe::Config::Node.new(nil, config))
|
52
|
+
stub(m).timestamp {"NOW"}
|
53
|
+
m.send(:mysql_skip_tables).should be_nil
|
54
|
+
m.backup.command.should_not match(/ignore-table/)
|
55
|
+
end
|
56
|
+
|
57
|
+
it "should return '' if skip_tables empty" do
|
58
|
+
config = def_config.dup
|
59
|
+
config[:skip_tables] = []
|
60
|
+
m = mysqldump(:foo, Astrails::Safe::Config::Node.new(nil, config))
|
61
|
+
stub(m).timestamp {"NOW"}
|
62
|
+
m.send(:mysql_skip_tables).should == ""
|
63
|
+
m.backup.command.should_not match(/ignore-table/)
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
describe :mysql_password_file do
|
69
|
+
it "should create passwords file with quoted values" do
|
70
|
+
m = mysqldump(:foo, def_config(:password => '#qwe"asd\'zxc'))
|
71
|
+
file = m.send(:mysql_password_file)
|
72
|
+
File.exists?(file).should == true
|
73
|
+
File.read(file).should == <<-PWD
|
74
|
+
[mysqldump]
|
75
|
+
user = "User"
|
76
|
+
password = "#qwe\\"asd'zxc"
|
77
|
+
socket = "socket"
|
78
|
+
host = "localhost"
|
79
|
+
port = 7777
|
80
|
+
PWD
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe Astrails::Safe::Pgdump do
|
4
|
+
|
5
|
+
def def_config
|
6
|
+
{
|
7
|
+
:options => "OPTS",
|
8
|
+
:user => "User",
|
9
|
+
:password => "pwd",
|
10
|
+
:host => "localhost",
|
11
|
+
:port => 7777,
|
12
|
+
:skip_tables => [:bar, :baz]
|
13
|
+
}
|
14
|
+
end
|
15
|
+
|
16
|
+
def pgdump(id = :foo, config = def_config)
|
17
|
+
Astrails::Safe::Pgdump.new(id, Astrails::Safe::Config::Node.new(nil, config))
|
18
|
+
end
|
19
|
+
|
20
|
+
before(:each) do
|
21
|
+
stub(Time).now.stub!.strftime {"NOW"}
|
22
|
+
end
|
23
|
+
|
24
|
+
after(:each) { Astrails::Safe::TmpFile.cleanup }
|
25
|
+
|
26
|
+
describe :backup do
|
27
|
+
before(:each) do
|
28
|
+
@pg = pgdump
|
29
|
+
end
|
30
|
+
|
31
|
+
{
|
32
|
+
:id => "foo",
|
33
|
+
:kind => "pgdump",
|
34
|
+
:extension => ".sql",
|
35
|
+
:filename => "pgdump-foo.NOW",
|
36
|
+
:command => "pg_dump OPTS --username='User' --host='localhost' --port='7777' foo",
|
37
|
+
}.each do |k, v|
|
38
|
+
it "should set #{k} to #{v}" do
|
39
|
+
@pg.backup.send(k).should == v
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|