akupchanko-astrails-safe 0.3.1
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/.gitignore +18 -0
- data/.rspec +3 -0
- data/CHANGELOG +35 -0
- data/Gemfile +7 -0
- data/LICENSE.txt +22 -0
- data/README.markdown +250 -0
- data/Rakefile +8 -0
- data/TODO +31 -0
- data/akupchanko-astrails-safe.gemspec +35 -0
- data/bin/astrails-safe +64 -0
- data/lib/astrails/safe.rb +68 -0
- data/lib/astrails/safe/archive.rb +24 -0
- data/lib/astrails/safe/backup.rb +20 -0
- data/lib/astrails/safe/cloudfiles.rb +77 -0
- data/lib/astrails/safe/config/builder.rb +90 -0
- data/lib/astrails/safe/config/node.rb +72 -0
- data/lib/astrails/safe/ftp.rb +104 -0
- data/lib/astrails/safe/gpg.rb +46 -0
- data/lib/astrails/safe/gzip.rb +25 -0
- data/lib/astrails/safe/local.rb +51 -0
- data/lib/astrails/safe/mongodump.rb +23 -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 +80 -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 +32 -0
- data/lib/astrails/safe/svndump.rb +13 -0
- data/lib/astrails/safe/tmp_file.rb +48 -0
- data/lib/astrails/safe/version.rb +5 -0
- data/lib/extensions/mktmpdir.rb +45 -0
- data/spec/astrails/safe/archive_spec.rb +67 -0
- data/spec/astrails/safe/cloudfiles_spec.rb +175 -0
- data/spec/astrails/safe/config_spec.rb +307 -0
- data/spec/astrails/safe/gpg_spec.rb +148 -0
- data/spec/astrails/safe/gzip_spec.rb +64 -0
- data/spec/astrails/safe/local_spec.rb +109 -0
- data/spec/astrails/safe/mongodump_spec.rb +54 -0
- data/spec/astrails/safe/mysqldump_spec.rb +83 -0
- data/spec/astrails/safe/pgdump_spec.rb +45 -0
- data/spec/astrails/safe/s3_spec.rb +168 -0
- data/spec/astrails/safe/svndump_spec.rb +39 -0
- data/spec/integration/archive_integration_spec.rb +89 -0
- data/spec/integration/cleanup_spec.rb +62 -0
- data/spec/spec_helper.rb +8 -0
- data/templates/script.rb +183 -0
- metadata +178 -0
@@ -0,0 +1,32 @@
|
|
1
|
+
module Astrails
|
2
|
+
module Safe
|
3
|
+
class Stream
|
4
|
+
|
5
|
+
attr_accessor :config, :backup
|
6
|
+
def initialize(config, backup)
|
7
|
+
@config, @backup = config, backup
|
8
|
+
end
|
9
|
+
# FIXME: move to Backup
|
10
|
+
def expand(path)
|
11
|
+
path .
|
12
|
+
gsub(/:kind\b/, @backup.kind.to_s) .
|
13
|
+
gsub(/:id\b/, @backup.id.to_s) .
|
14
|
+
gsub(/:timestamp\b/, @backup.timestamp)
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def verbose?
|
20
|
+
config[:verbose]
|
21
|
+
end
|
22
|
+
|
23
|
+
def local_only?
|
24
|
+
config[:local_only]
|
25
|
+
end
|
26
|
+
|
27
|
+
def dry_run?
|
28
|
+
config[:dry_run]
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'tmpdir'
|
2
|
+
module Astrails
|
3
|
+
module Safe
|
4
|
+
module TmpFile
|
5
|
+
@keep_files = []
|
6
|
+
|
7
|
+
def self.tmproot
|
8
|
+
@tmproot ||= Dir.mktmpdir
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.cleanup
|
12
|
+
begin
|
13
|
+
FileUtils.remove_entry_secure tmproot
|
14
|
+
rescue ArgumentError => e
|
15
|
+
if e.message =~ /parent directory is world writable/
|
16
|
+
puts <<-ERR
|
17
|
+
|
18
|
+
|
19
|
+
********************************************************************************
|
20
|
+
It looks like you have wrong permissions on your TEMP directory. The usual
|
21
|
+
case is when you have world writable TEMP directory withOUT the sticky bit.
|
22
|
+
|
23
|
+
Try "chmod +t" on it.
|
24
|
+
|
25
|
+
********************************************************************************
|
26
|
+
|
27
|
+
ERR
|
28
|
+
else
|
29
|
+
raise
|
30
|
+
end
|
31
|
+
end
|
32
|
+
@tmproot = nil
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.create(name)
|
36
|
+
# create temp directory
|
37
|
+
|
38
|
+
file = Tempfile.new(name, tmproot)
|
39
|
+
|
40
|
+
yield file
|
41
|
+
|
42
|
+
file.close
|
43
|
+
@keep_files << file # so that it will not get gcollected and removed from filesystem until the end
|
44
|
+
file.path
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -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,67 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Astrails::Safe::Archive do
|
4
|
+
|
5
|
+
def def_config
|
6
|
+
{
|
7
|
+
:options => "OPTS",
|
8
|
+
:files => "apples",
|
9
|
+
:exclude => "oranges"
|
10
|
+
}
|
11
|
+
end
|
12
|
+
|
13
|
+
def archive(id = :foo, config = def_config)
|
14
|
+
Astrails::Safe::Archive.new(id, Astrails::Safe::Config::Node.new(nil, config))
|
15
|
+
end
|
16
|
+
|
17
|
+
after(:each) { Astrails::Safe::TmpFile.cleanup }
|
18
|
+
|
19
|
+
describe :backup do
|
20
|
+
before(:each) do
|
21
|
+
@archive = archive
|
22
|
+
stub(@archive).timestamp {"NOW"}
|
23
|
+
end
|
24
|
+
|
25
|
+
{
|
26
|
+
:id => "foo",
|
27
|
+
:kind => "archive",
|
28
|
+
:extension => ".tar",
|
29
|
+
:filename => "archive-foo.NOW",
|
30
|
+
:command => "tar -cf - OPTS --exclude=oranges apples",
|
31
|
+
}.each do |k, v|
|
32
|
+
it "should set #{k} to #{v}" do
|
33
|
+
@archive.backup.send(k).should == v
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
describe :tar_exclude_files do
|
39
|
+
it "should return '' when no excludes" do
|
40
|
+
archive(:foo, {}).send(:tar_exclude_files).should == ''
|
41
|
+
end
|
42
|
+
|
43
|
+
it "should accept single exclude as string" do
|
44
|
+
archive(:foo, {:exclude => "bar"}).send(:tar_exclude_files).should == '--exclude=bar'
|
45
|
+
end
|
46
|
+
|
47
|
+
it "should accept multiple exclude as array" do
|
48
|
+
archive(:foo, {:exclude => ["foo", "bar"]}).send(:tar_exclude_files).should == '--exclude=foo --exclude=bar'
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
describe :tar_files do
|
53
|
+
it "should raise RuntimeError when no files" do
|
54
|
+
lambda {
|
55
|
+
archive(:foo, {}).send(:tar_files)
|
56
|
+
}.should raise_error(RuntimeError, "missing files for tar")
|
57
|
+
end
|
58
|
+
|
59
|
+
it "should accept single file as string" do
|
60
|
+
archive(:foo, {:files => "foo"}).send(:tar_files).should == "foo"
|
61
|
+
end
|
62
|
+
|
63
|
+
it "should accept multiple files as array" do
|
64
|
+
archive(:foo, {:files => ["foo", "bar"]}).send(:tar_files).should == "foo bar"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,175 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Astrails::Safe::Cloudfiles do
|
4
|
+
|
5
|
+
def def_config
|
6
|
+
{
|
7
|
+
cloudfiles: {
|
8
|
+
container: '_container',
|
9
|
+
user: '_user',
|
10
|
+
api_key: '_api_key',
|
11
|
+
},
|
12
|
+
keep: { cloudfiles: 2 }
|
13
|
+
}
|
14
|
+
end
|
15
|
+
|
16
|
+
def def_backup(extra = {})
|
17
|
+
{
|
18
|
+
kind: '_kind',
|
19
|
+
filename: '/backup/somewhere/_kind-_id.NOW.bar',
|
20
|
+
extension: '.bar',
|
21
|
+
id: '_id',
|
22
|
+
timestamp: 'NOW'
|
23
|
+
}.merge(extra)
|
24
|
+
end
|
25
|
+
|
26
|
+
def cloudfiles(config = def_config, backup = def_backup)
|
27
|
+
Astrails::Safe::Cloudfiles.new(
|
28
|
+
Astrails::Safe::Config::Node.new.merge(config),
|
29
|
+
Astrails::Safe::Backup.new(backup)
|
30
|
+
)
|
31
|
+
end
|
32
|
+
|
33
|
+
describe :cleanup do
|
34
|
+
|
35
|
+
before(:each) do
|
36
|
+
@cloudfiles = cloudfiles
|
37
|
+
|
38
|
+
@files = [4,1,3,2].map { |i| "aaaaa#{i}" }
|
39
|
+
|
40
|
+
@container = "container"
|
41
|
+
|
42
|
+
stub(@container).objects(prefix: "_kind/_id/_kind-_id.") { @files }
|
43
|
+
stub(@container).delete_object(anything)
|
44
|
+
|
45
|
+
stub(CloudFiles::Connection).
|
46
|
+
new('_user', '_api_key', true, false).stub!.
|
47
|
+
container('_container') {@container}
|
48
|
+
end
|
49
|
+
|
50
|
+
it "should check [:keep, :cloudfiles]" do
|
51
|
+
@cloudfiles.config[:keep].data["cloudfiles"] = nil
|
52
|
+
dont_allow(@cloudfiles.backup).filename
|
53
|
+
@cloudfiles.send :cleanup
|
54
|
+
end
|
55
|
+
|
56
|
+
it "should delete extra files" do
|
57
|
+
mock(@container).delete_object('aaaaa1')
|
58
|
+
mock(@container).delete_object('aaaaa2')
|
59
|
+
@cloudfiles.send :cleanup
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
|
64
|
+
describe :active do
|
65
|
+
before(:each) do
|
66
|
+
@cloudfiles = cloudfiles
|
67
|
+
end
|
68
|
+
|
69
|
+
it "should be true when all params are set" do
|
70
|
+
@cloudfiles.should be_active
|
71
|
+
end
|
72
|
+
|
73
|
+
it "should be false if container is missing" do
|
74
|
+
@cloudfiles.config[:cloudfiles].data["container"] = nil
|
75
|
+
@cloudfiles.should_not be_active
|
76
|
+
end
|
77
|
+
|
78
|
+
it "should be false if user is missing" do
|
79
|
+
@cloudfiles.config[:cloudfiles].data["user"] = nil
|
80
|
+
@cloudfiles.should_not be_active
|
81
|
+
end
|
82
|
+
|
83
|
+
it "should be false if api_key is missing" do
|
84
|
+
@cloudfiles.config[:cloudfiles].data["api_key"] = nil
|
85
|
+
@cloudfiles.should_not be_active
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
describe :path do
|
90
|
+
before(:each) do
|
91
|
+
@cloudfiles = cloudfiles
|
92
|
+
end
|
93
|
+
it "should use cloudfiles/path 1st" do
|
94
|
+
@cloudfiles.config[:cloudfiles].data["path"] = "cloudfiles_path"
|
95
|
+
@cloudfiles.config[:local] = {path: "local_path"}
|
96
|
+
@cloudfiles.send(:path).should == "cloudfiles_path"
|
97
|
+
end
|
98
|
+
|
99
|
+
it "should use local/path 2nd" do
|
100
|
+
@cloudfiles.config.merge local: {path: 'local_path'}
|
101
|
+
@cloudfiles.send(:path).should == 'local_path'
|
102
|
+
end
|
103
|
+
|
104
|
+
it "should use constant 3rd" do
|
105
|
+
@cloudfiles.send(:path).should == "_kind/_id"
|
106
|
+
end
|
107
|
+
|
108
|
+
end
|
109
|
+
|
110
|
+
describe :save do
|
111
|
+
def add_stubs(*stubs)
|
112
|
+
stubs.each do |s|
|
113
|
+
case s
|
114
|
+
when :connection
|
115
|
+
@connection = "connection"
|
116
|
+
stub(CloudFiles::Authentication).new
|
117
|
+
stub(CloudFiles::Connection).
|
118
|
+
new('_user', '_api_key', true, false) {@connection}
|
119
|
+
when :file_size
|
120
|
+
stub(@cloudfiles).get_file_size("foo") {123}
|
121
|
+
when :create_container
|
122
|
+
@container = "container"
|
123
|
+
stub(@container).create_object("_kind/_id/backup/somewhere/_kind-_id.NOW.bar.bar", true) {@object}
|
124
|
+
stub(@connection).create_container {@container}
|
125
|
+
when :file_open
|
126
|
+
stub(File).open("foo")
|
127
|
+
when :cloudfiles_store
|
128
|
+
@object = "object"
|
129
|
+
stub(@object).write(nil) {true}
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
before(:each) do
|
135
|
+
@cloudfiles = cloudfiles(def_config, def_backup(path: 'foo'))
|
136
|
+
@full_path = "_kind/_id/backup/somewhere/_kind-_id.NOW.bar.bar"
|
137
|
+
end
|
138
|
+
|
139
|
+
it "should fail if no backup.file is set" do
|
140
|
+
@cloudfiles.backup.path = nil
|
141
|
+
proc {@cloudfiles.send(:save)}.should raise_error(RuntimeError)
|
142
|
+
end
|
143
|
+
|
144
|
+
it "should establish Cloud Files connection" do
|
145
|
+
add_stubs(:connection, :file_size, :create_container, :file_open, :cloudfiles_store)
|
146
|
+
@cloudfiles.send(:save)
|
147
|
+
end
|
148
|
+
|
149
|
+
it "should open local file" do
|
150
|
+
add_stubs(:connection, :file_size, :create_container, :cloudfiles_store)
|
151
|
+
mock(File).open("foo")
|
152
|
+
@cloudfiles.send(:save)
|
153
|
+
end
|
154
|
+
|
155
|
+
it "should call write on the cloudfile object with files' descriptor" do
|
156
|
+
add_stubs(:connection, :file_size, :create_container, :cloudfiles_store)
|
157
|
+
stub(File).open("foo") {"qqq"}
|
158
|
+
mock(@object).write("qqq") {true}
|
159
|
+
@cloudfiles.send(:save)
|
160
|
+
end
|
161
|
+
|
162
|
+
it "should upload file" do
|
163
|
+
add_stubs(:connection, :file_size, :create_container, :file_open, :cloudfiles_store)
|
164
|
+
@cloudfiles.send(:save)
|
165
|
+
end
|
166
|
+
|
167
|
+
it "should fail on files bigger then 5G" do
|
168
|
+
add_stubs(:connection)
|
169
|
+
mock(File).stat("foo").stub!.size {5*1024*1024*1024+1}
|
170
|
+
mock(STDERR).puts(anything)
|
171
|
+
dont_allow(Benchmark).realtime
|
172
|
+
@cloudfiles.send(:save)
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
@@ -0,0 +1,307 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Astrails::Safe::Config do
|
4
|
+
it "should parse example config" do
|
5
|
+
config = Astrails::Safe::Config::Node.new do
|
6
|
+
|
7
|
+
dry_run false
|
8
|
+
local_only true
|
9
|
+
verbose true
|
10
|
+
|
11
|
+
local do
|
12
|
+
path "path"
|
13
|
+
end
|
14
|
+
|
15
|
+
s3 do
|
16
|
+
key "s3 key"
|
17
|
+
secret "secret"
|
18
|
+
bucket "bucket"
|
19
|
+
path "path1"
|
20
|
+
end
|
21
|
+
|
22
|
+
sftp do
|
23
|
+
user "sftp user"
|
24
|
+
password "sftp password"
|
25
|
+
host "sftp host"
|
26
|
+
end
|
27
|
+
|
28
|
+
gpg do
|
29
|
+
password "astrails"
|
30
|
+
key "gpg-key"
|
31
|
+
end
|
32
|
+
|
33
|
+
keep do
|
34
|
+
s3 20
|
35
|
+
local 4
|
36
|
+
end
|
37
|
+
|
38
|
+
mysqldump do
|
39
|
+
options "-ceKq --single-transaction --create-options"
|
40
|
+
|
41
|
+
user "astrails"
|
42
|
+
password ""
|
43
|
+
host "localhost"
|
44
|
+
port 3306
|
45
|
+
socket "/var/run/mysqld/mysqld.sock"
|
46
|
+
|
47
|
+
database :blog
|
48
|
+
|
49
|
+
database :production do
|
50
|
+
keep :local => 3
|
51
|
+
|
52
|
+
gpg do
|
53
|
+
password "custom-production-pass"
|
54
|
+
end
|
55
|
+
|
56
|
+
skip_tables [:logger_exceptions, :request_logs]
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
|
61
|
+
pgdump do
|
62
|
+
options "-i -x -O"
|
63
|
+
|
64
|
+
user "astrails"
|
65
|
+
password ""
|
66
|
+
host "localhost"
|
67
|
+
port 5432
|
68
|
+
|
69
|
+
database :blog
|
70
|
+
|
71
|
+
database :production do
|
72
|
+
keep :local => 3
|
73
|
+
|
74
|
+
skip_tables [:logger_exceptions, :request_logs]
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
|
79
|
+
svndump do
|
80
|
+
repo :my_repo do
|
81
|
+
repo_path "/home/svn/my_repo"
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
tar do
|
86
|
+
archive "git-repositories" do
|
87
|
+
files "/home/git/repositories"
|
88
|
+
end
|
89
|
+
|
90
|
+
archive "etc-files" do
|
91
|
+
files "/etc"
|
92
|
+
exclude "/etc/puppet/other"
|
93
|
+
end
|
94
|
+
|
95
|
+
archive "dot-configs" do
|
96
|
+
files "/home/*/.[^.]*"
|
97
|
+
end
|
98
|
+
|
99
|
+
archive "blog" do
|
100
|
+
files "/var/www/blog.astrails.com/"
|
101
|
+
exclude ["/var/www/blog.astrails.com/log", "/var/www/blog.astrails.com/tmp"]
|
102
|
+
end
|
103
|
+
|
104
|
+
archive :misc do
|
105
|
+
files [ "/backup/*.rb" ]
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
mongodump do
|
110
|
+
host "host"
|
111
|
+
database "database"
|
112
|
+
user "user"
|
113
|
+
password "password"
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
expected = {
|
118
|
+
"dry_run" => false,
|
119
|
+
"local_only" => true,
|
120
|
+
"verbose" => true,
|
121
|
+
|
122
|
+
"local" => {"path" => "path"},
|
123
|
+
|
124
|
+
"s3" => {
|
125
|
+
"key" => "s3 key",
|
126
|
+
"secret" => "secret",
|
127
|
+
"bucket" => "bucket",
|
128
|
+
"path" => "path1",
|
129
|
+
},
|
130
|
+
|
131
|
+
"sftp" => {
|
132
|
+
"user" => "sftp user",
|
133
|
+
"password" => "sftp password",
|
134
|
+
"host" => "sftp host",
|
135
|
+
},
|
136
|
+
|
137
|
+
"gpg" => {"password" => "astrails", "key" => "gpg-key"},
|
138
|
+
|
139
|
+
"keep" => {"s3" => 20, "local" => 4},
|
140
|
+
|
141
|
+
"mysqldump" => {
|
142
|
+
"options" => "-ceKq --single-transaction --create-options",
|
143
|
+
"user" => "astrails",
|
144
|
+
"password" => "",
|
145
|
+
"host" => "localhost",
|
146
|
+
"port" => 3306,
|
147
|
+
"socket" => "/var/run/mysqld/mysqld.sock",
|
148
|
+
|
149
|
+
"databases" => {
|
150
|
+
"blog" => {},
|
151
|
+
"production" => {
|
152
|
+
"keep" => {"local" => 3},
|
153
|
+
"gpg" => {"password" => "custom-production-pass"},
|
154
|
+
"skip_tables" => ["logger_exceptions", "request_logs"],
|
155
|
+
},
|
156
|
+
},
|
157
|
+
},
|
158
|
+
|
159
|
+
"pgdump" => {
|
160
|
+
"options" => "-i -x -O",
|
161
|
+
"user" => "astrails",
|
162
|
+
"password" => "",
|
163
|
+
"host" => "localhost",
|
164
|
+
"port" => 5432,
|
165
|
+
|
166
|
+
"databases" => {
|
167
|
+
"blog" => {},
|
168
|
+
"production" => {
|
169
|
+
"keep" => {"local" => 3},
|
170
|
+
"skip_tables" => ["logger_exceptions", "request_logs"],
|
171
|
+
},
|
172
|
+
},
|
173
|
+
},
|
174
|
+
|
175
|
+
"svndump" => {
|
176
|
+
"repos" => {
|
177
|
+
"my_repo"=> {
|
178
|
+
"repo_path" => "/home/svn/my_repo"
|
179
|
+
}
|
180
|
+
}
|
181
|
+
},
|
182
|
+
|
183
|
+
"tar" => {
|
184
|
+
"archives" => {
|
185
|
+
"git-repositories" => {"files" => ["/home/git/repositories"]},
|
186
|
+
"etc-files" => {"files" => ["/etc"], "exclude" => ["/etc/puppet/other"]},
|
187
|
+
"dot-configs" => {"files" => ["/home/*/.[^.]*"]},
|
188
|
+
"blog" => {
|
189
|
+
"files" => ["/var/www/blog.astrails.com/"],
|
190
|
+
"exclude" => ["/var/www/blog.astrails.com/log", "/var/www/blog.astrails.com/tmp"],
|
191
|
+
},
|
192
|
+
"misc" => { "files" => ["/backup/*.rb"] },
|
193
|
+
},
|
194
|
+
},
|
195
|
+
|
196
|
+
"mongodump" => {
|
197
|
+
"host" => "host",
|
198
|
+
"databases" => {
|
199
|
+
"database" => {}
|
200
|
+
},
|
201
|
+
"user" => "user",
|
202
|
+
"password" => "password"
|
203
|
+
}
|
204
|
+
}
|
205
|
+
|
206
|
+
config.to_hash.should == expected
|
207
|
+
end
|
208
|
+
|
209
|
+
it "should make an array from multivalues" do
|
210
|
+
config = Astrails::Safe::Config::Node.new do
|
211
|
+
skip_tables "a"
|
212
|
+
skip_tables "b"
|
213
|
+
files "/foo"
|
214
|
+
files "/bar"
|
215
|
+
exclude "/foo/bar"
|
216
|
+
exclude "/foo/bar/baz"
|
217
|
+
end
|
218
|
+
|
219
|
+
expected = {
|
220
|
+
"skip_tables" => ["a", "b"],
|
221
|
+
"files" => ["/foo", "/bar"],
|
222
|
+
"exclude" => ["/foo/bar", "/foo/bar/baz"],
|
223
|
+
}
|
224
|
+
|
225
|
+
config.to_hash.should == expected
|
226
|
+
end
|
227
|
+
|
228
|
+
it "should raise error on key duplication" do
|
229
|
+
proc do
|
230
|
+
Astrails::Safe::Config::Node.new do
|
231
|
+
path "foo"
|
232
|
+
path "bar"
|
233
|
+
end
|
234
|
+
end.should raise_error(ArgumentError, "duplicate value for 'path'")
|
235
|
+
end
|
236
|
+
|
237
|
+
it "should accept hash as data" do
|
238
|
+
Astrails::Safe::Config::Node.new do
|
239
|
+
tar do
|
240
|
+
archive 'blog', files: 'foo', exclude: ['aaa', 'bbb']
|
241
|
+
end
|
242
|
+
end.to_hash.should == {
|
243
|
+
'tar' => {
|
244
|
+
'archives' => {
|
245
|
+
'blog' => {
|
246
|
+
'files' => ['foo'],
|
247
|
+
'exclude' => ['aaa', 'bbb']
|
248
|
+
}
|
249
|
+
}
|
250
|
+
}
|
251
|
+
}
|
252
|
+
end
|
253
|
+
|
254
|
+
it "should accept hash as data and a block" do
|
255
|
+
Astrails::Safe::Config::Node.new do
|
256
|
+
tar do
|
257
|
+
archive 'blog', files: 'foo' do
|
258
|
+
exclude ['aaa', 'bbb']
|
259
|
+
end
|
260
|
+
end
|
261
|
+
end.to_hash.should == {
|
262
|
+
'tar' => {
|
263
|
+
'archives' => {
|
264
|
+
'blog' => {
|
265
|
+
'files' => ['foo'],
|
266
|
+
'exclude' => ['aaa', 'bbb']
|
267
|
+
}
|
268
|
+
}
|
269
|
+
}
|
270
|
+
}
|
271
|
+
end
|
272
|
+
|
273
|
+
it 'should accept multiple levels of data hash' do
|
274
|
+
config = Astrails::Safe::Config::Node.new nil, tar: {
|
275
|
+
s3: { bucket: '_bucket', key: '_key', secret: '_secret', },
|
276
|
+
keep: { s3: 2 }
|
277
|
+
}
|
278
|
+
|
279
|
+
config.to_hash.should == {
|
280
|
+
'tar' => {
|
281
|
+
's3' => { 'bucket' => '_bucket', 'key' => '_key', 'secret' => '_secret', },
|
282
|
+
'keep' => { 's3' => 2 }
|
283
|
+
}
|
284
|
+
}
|
285
|
+
end
|
286
|
+
|
287
|
+
it 'should set multi value as array' do
|
288
|
+
config = Astrails::Safe::Config::Node.new do
|
289
|
+
tar do
|
290
|
+
archive 'foo' do
|
291
|
+
files 'bar'
|
292
|
+
end
|
293
|
+
end
|
294
|
+
end
|
295
|
+
|
296
|
+
config.to_hash.should == {
|
297
|
+
'tar' => {
|
298
|
+
'archives' => {
|
299
|
+
'foo' => {
|
300
|
+
'files' => ['bar']
|
301
|
+
}
|
302
|
+
}
|
303
|
+
}
|
304
|
+
}
|
305
|
+
end
|
306
|
+
|
307
|
+
end
|