netguru-safe 0.2.9

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.
Files changed (54) hide show
  1. data/.autotest +42 -0
  2. data/.document +5 -0
  3. data/.gitignore +11 -0
  4. data/CHANGELOG +25 -0
  5. data/Gemfile +4 -0
  6. data/Gemfile.lock +44 -0
  7. data/LICENSE +20 -0
  8. data/README.markdown +237 -0
  9. data/Rakefile +32 -0
  10. data/TODO +11 -0
  11. data/VERSION +1 -0
  12. data/astrails-safe.gemspec +37 -0
  13. data/bin/astrails-safe +53 -0
  14. data/examples/example_helper.rb +19 -0
  15. data/lib/astrails/safe.rb +73 -0
  16. data/lib/astrails/safe/archive.rb +24 -0
  17. data/lib/astrails/safe/backup.rb +20 -0
  18. data/lib/astrails/safe/cloudfiles.rb +77 -0
  19. data/lib/astrails/safe/config/builder.rb +60 -0
  20. data/lib/astrails/safe/config/node.rb +76 -0
  21. data/lib/astrails/safe/gpg.rb +46 -0
  22. data/lib/astrails/safe/gzip.rb +25 -0
  23. data/lib/astrails/safe/local.rb +51 -0
  24. data/lib/astrails/safe/mongodump.rb +23 -0
  25. data/lib/astrails/safe/mysqldump.rb +32 -0
  26. data/lib/astrails/safe/pgdump.rb +36 -0
  27. data/lib/astrails/safe/pipe.rb +17 -0
  28. data/lib/astrails/safe/s3.rb +75 -0
  29. data/lib/astrails/safe/sftp.rb +88 -0
  30. data/lib/astrails/safe/sink.rb +35 -0
  31. data/lib/astrails/safe/source.rb +47 -0
  32. data/lib/astrails/safe/stream.rb +20 -0
  33. data/lib/astrails/safe/svndump.rb +13 -0
  34. data/lib/astrails/safe/tmp_file.rb +48 -0
  35. data/lib/astrails/safe/version.rb +5 -0
  36. data/lib/extensions/mktmpdir.rb +45 -0
  37. data/spec/integration/airbrake_integration_spec.rb +76 -0
  38. data/spec/integration/archive_integration_spec.rb +88 -0
  39. data/spec/integration/cleanup_spec.rb +61 -0
  40. data/spec/spec.opts +5 -0
  41. data/spec/spec_helper.rb +13 -0
  42. data/spec/unit/archive_spec.rb +67 -0
  43. data/spec/unit/cloudfiles_spec.rb +177 -0
  44. data/spec/unit/config_spec.rb +234 -0
  45. data/spec/unit/gpg_spec.rb +148 -0
  46. data/spec/unit/gzip_spec.rb +64 -0
  47. data/spec/unit/local_spec.rb +110 -0
  48. data/spec/unit/mongodump_spec.rb +54 -0
  49. data/spec/unit/mysqldump_spec.rb +83 -0
  50. data/spec/unit/pgdump_spec.rb +45 -0
  51. data/spec/unit/s3_spec.rb +163 -0
  52. data/spec/unit/svndump_spec.rb +39 -0
  53. data/templates/script.rb +160 -0
  54. metadata +222 -0
@@ -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({}, :extension => ".foo", :command => "foobar", :compressed => true) }
47
+
48
+ it "should not touch extension" do
49
+ @gzip.process
50
+ @backup.extension.should == ".foo"
51
+ end
52
+
53
+ it "should not touch command" do
54
+ @gzip.process
55
+ @backup.command.should == "foobar"
56
+ end
57
+
58
+ it "should not touch compressed" do
59
+ @gzip.process
60
+ @backup.compressed.should == true
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].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,54 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ describe Astrails::Safe::Mongodump do
4
+ def def_config
5
+ {
6
+ :host => 'prod.example.com',
7
+ :user => 'testuser',
8
+ :password => 'p4ssw0rd',
9
+ }
10
+ end
11
+
12
+ def mongodump(id = :foo, config = def_config)
13
+ Astrails::Safe::Mongodump.new(id, Astrails::Safe::Config::Node.new(nil, config))
14
+ end
15
+
16
+ before(:each) do
17
+ stub(Time).now.stub!.strftime {"NOW"}
18
+ @output_folder = File.join(Astrails::Safe::TmpFile.tmproot, 'mongodump')
19
+ end
20
+
21
+ after(:each) { Astrails::Safe::TmpFile.cleanup }
22
+
23
+ describe :backup do
24
+ before(:each) do
25
+ @mongo = mongodump
26
+ end
27
+
28
+ {
29
+ :id => "foo",
30
+ :kind => "mongodump",
31
+ :extension => ".tar",
32
+ :filename => "mongodump-foo.NOW"
33
+ }.each do |k, v|
34
+ it "should set #{k} to #{v}" do
35
+ @mongo.backup.send(k).should == v
36
+ end
37
+ end
38
+
39
+ it "should set the command" do
40
+ @mongo.backup.send(:command).should == "mongodump -q \"{xxxx : { \\$ne : 0 } }\" --db foo --host prod.example.com -u testuser -p p4ssw0rd --out #{@output_folder} && cd #{@output_folder} && tar cf - ."
41
+ end
42
+
43
+ {
44
+ :host => "--host ",
45
+ :user => "-u ",
46
+ :password => "-p "
47
+ }.each do |key, v|
48
+ it "should not add #{key} to command if it is not present" do
49
+ @mongo = mongodump(:foo, def_config.reject! {|k,v| k == key})
50
+ @mongo.backup.send(:command).should_not =~ /#{v}/
51
+ end
52
+ end
53
+ end
54
+ 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
@@ -0,0 +1,163 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ describe Astrails::Safe::S3 do
4
+
5
+ def def_config
6
+ {
7
+ :s3 => {
8
+ :bucket => "bucket_name",
9
+ :key => "_key",
10
+ :secret => "_secret",
11
+ },
12
+ :keep => {
13
+ :s3 => 2
14
+ }
15
+ }
16
+ end
17
+
18
+ def def_backup(extra = {})
19
+ {
20
+ :kind => "_kind",
21
+ :filename => "/backup/somewhere/_kind-_id.NOW.bar",
22
+ :extension => ".bar",
23
+ :id => "_id",
24
+ :timestamp => "NOW"
25
+ }.merge(extra)
26
+ end
27
+
28
+ def s3(config = def_config, backup = def_backup)
29
+ Astrails::Safe::S3.new(
30
+ Astrails::Safe::Config::Node.new(nil, config),
31
+ Astrails::Safe::Backup.new(backup)
32
+ )
33
+ end
34
+
35
+ describe :cleanup do
36
+
37
+ before(:each) do
38
+ @s3 = s3
39
+
40
+ @files = [4,1,3,2].map do |i|
41
+ stub(object = Object.new).key { "aaaaa#{i}" }
42
+ object
43
+ end
44
+
45
+ stub(@s3).remote_bucket.stub!.objects.stub!.with_prefix(:prefix => '_kind/_id/_kind-_id.') { @files }
46
+ end
47
+
48
+ it "should check [:keep, :s3]" do
49
+ @s3.config[:keep].data["s3"] = nil
50
+ dont_allow(@s3.backup).filename
51
+ @s3.send :cleanup
52
+ end
53
+
54
+ it "should delete extra files" do
55
+ dont_allow(@files[0]).delete
56
+ mock(@files[1]).delete
57
+ dont_allow(@files[2]).delete
58
+ mock(@files[3]).delete
59
+ @s3.send :cleanup
60
+ end
61
+
62
+ end
63
+
64
+ describe :active do
65
+ before(:each) do
66
+ @s3 = s3
67
+ end
68
+
69
+ it "should be true when all params are set" do
70
+ @s3.should be_active
71
+ end
72
+
73
+ it "should be false if bucket is missing" do
74
+ @s3.config[:s3].data["bucket"] = nil
75
+ @s3.should_not be_active
76
+ end
77
+
78
+ it "should be false if key is missing" do
79
+ @s3.config[:s3].data["key"] = nil
80
+ @s3.should_not be_active
81
+ end
82
+
83
+ it "should be false if secret is missing" do
84
+ @s3.config[:s3].data["secret"] = nil
85
+ @s3.should_not be_active
86
+ end
87
+ end
88
+
89
+ describe :path do
90
+ before(:each) do
91
+ @s3 = s3
92
+ end
93
+ it "should use s3/path 1st" do
94
+ @s3.config[:s3].data["path"] = "s3_path"
95
+ @s3.config[:local] = {:path => "local_path"}
96
+ @s3.send(:path).should == "s3_path"
97
+ end
98
+
99
+ it "should use local/path 2nd" do
100
+ @s3.config[:local] = {:path => "local_path"}
101
+ @s3.send(:path).should == "local_path"
102
+ end
103
+
104
+ it "should use constant 3rd" do
105
+ @s3.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 :stat
115
+ stub(File).stat("foo").stub!.size {123}
116
+ when :create_bucket
117
+ stub.instance_of(AWS::S3).buckets.stub!.create.stub!
118
+ when :file_open
119
+ stub(File).open("foo") {|f, block| block.call(:opened_file)}
120
+ when :s3_store
121
+ stub.instance_of(AWS::S3).buckets.stub!.create.stub!.objects.stub!.create(@full_path, :data => :opened_file)
122
+ end
123
+ end
124
+ end
125
+
126
+ before(:each) do
127
+ @s3 = s3(def_config, def_backup(:path => "foo"))
128
+ @full_path = "_kind/_id/backup/somewhere/_kind-_id.NOW.bar.bar"
129
+ end
130
+
131
+ it "should fail if no backup.file is set" do
132
+ @s3.backup.path = nil
133
+ proc {@s3.send(:save)}.should raise_error(RuntimeError)
134
+ end
135
+
136
+ it "should establish s3 connection" do
137
+ connection = AWS::S3.new(:access_key_id => "_key", :secret_access_key => "_secret")
138
+ mock(AWS::S3).new(:access_key_id => "_key", :secret_access_key => "_secret") { connection }
139
+ add_stubs(:stat, :create_bucket, :file_open, :s3_store)
140
+
141
+ @s3.send(:save)
142
+ end
143
+
144
+ it "should open local file" do
145
+ add_stubs(:stat, :create_bucket)
146
+ mock(File).open("foo")
147
+ @s3.send(:save)
148
+ end
149
+
150
+ it "should upload file" do
151
+ add_stubs(:stat, :create_bucket, :file_open)
152
+ mock(@s3).remote_bucket.mock!.objects.mock!.create(@full_path, :data => :opened_file)
153
+ @s3.send(:save)
154
+ end
155
+
156
+ it "should fail on files bigger then 5G" do
157
+ mock(File).stat("foo").stub!.size {5*1024*1024*1024+1}
158
+ mock(STDERR).puts(anything)
159
+ dont_allow(Benchmark).realtime
160
+ @s3.send(:save)
161
+ end
162
+ end
163
+ end