netguru-safe 0.2.9

Sign up to get free protection for your applications and to get access to all the features.
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,177 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../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 => {
13
+ :cloudfiles => 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 cloudfiles(config = def_config, backup = def_backup)
29
+ Astrails::Safe::Cloudfiles.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
+ @cloudfiles = cloudfiles
39
+
40
+ @files = [4,1,3,2].map { |i| "aaaaa#{i}" }
41
+
42
+ @container = "container"
43
+
44
+ stub(@container).objects(:prefix => "_kind/_id/_kind-_id.") { @files }
45
+ stub(@container).delete_object(anything)
46
+
47
+ stub(CloudFiles::Connection).
48
+ new('_user', '_api_key', true, false).stub!.
49
+ container('_container') {@container}
50
+ end
51
+
52
+ it "should check [:keep, :cloudfiles]" do
53
+ @cloudfiles.config[:keep].data["cloudfiles"] = nil
54
+ dont_allow(@cloudfiles.backup).filename
55
+ @cloudfiles.send :cleanup
56
+ end
57
+
58
+ it "should delete extra files" do
59
+ mock(@container).delete_object('aaaaa1')
60
+ mock(@container).delete_object('aaaaa2')
61
+ @cloudfiles.send :cleanup
62
+ end
63
+
64
+ end
65
+
66
+ describe :active do
67
+ before(:each) do
68
+ @cloudfiles = cloudfiles
69
+ end
70
+
71
+ it "should be true when all params are set" do
72
+ @cloudfiles.should be_active
73
+ end
74
+
75
+ it "should be false if container is missing" do
76
+ @cloudfiles.config[:cloudfiles].data["container"] = nil
77
+ @cloudfiles.should_not be_active
78
+ end
79
+
80
+ it "should be false if user is missing" do
81
+ @cloudfiles.config[:cloudfiles].data["user"] = nil
82
+ @cloudfiles.should_not be_active
83
+ end
84
+
85
+ it "should be false if api_key is missing" do
86
+ @cloudfiles.config[:cloudfiles].data["api_key"] = nil
87
+ @cloudfiles.should_not be_active
88
+ end
89
+ end
90
+
91
+ describe :path do
92
+ before(:each) do
93
+ @cloudfiles = cloudfiles
94
+ end
95
+ it "should use cloudfiles/path 1st" do
96
+ @cloudfiles.config[:cloudfiles].data["path"] = "cloudfiles_path"
97
+ @cloudfiles.config[:local] = {:path => "local_path"}
98
+ @cloudfiles.send(:path).should == "cloudfiles_path"
99
+ end
100
+
101
+ it "should use local/path 2nd" do
102
+ @cloudfiles.config[:local] = {:path => "local_path"}
103
+ @cloudfiles.send(:path).should == "local_path"
104
+ end
105
+
106
+ it "should use constant 3rd" do
107
+ @cloudfiles.send(:path).should == "_kind/_id"
108
+ end
109
+
110
+ end
111
+
112
+ describe :save do
113
+ def add_stubs(*stubs)
114
+ stubs.each do |s|
115
+ case s
116
+ when :connection
117
+ @connection = "connection"
118
+ stub(CloudFiles::Authentication).new
119
+ stub(CloudFiles::Connection).
120
+ new('_user', '_api_key', true, false) {@connection}
121
+ when :file_size
122
+ stub(@cloudfiles).get_file_size("foo") {123}
123
+ when :create_container
124
+ @container = "container"
125
+ stub(@container).create_object("_kind/_id/backup/somewhere/_kind-_id.NOW.bar.bar", true) {@object}
126
+ stub(@connection).create_container {@container}
127
+ when :file_open
128
+ stub(File).open("foo")
129
+ when :cloudfiles_store
130
+ @object = "object"
131
+ stub(@object).write(nil) {true}
132
+ end
133
+ end
134
+ end
135
+
136
+ before(:each) do
137
+ @cloudfiles = cloudfiles(def_config, def_backup(:path => "foo"))
138
+ @full_path = "_kind/_id/backup/somewhere/_kind-_id.NOW.bar.bar"
139
+ end
140
+
141
+ it "should fail if no backup.file is set" do
142
+ @cloudfiles.backup.path = nil
143
+ proc {@cloudfiles.send(:save)}.should raise_error(RuntimeError)
144
+ end
145
+
146
+ it "should establish Cloud Files connection" do
147
+ add_stubs(:connection, :file_size, :create_container, :file_open, :cloudfiles_store)
148
+ @cloudfiles.send(:save)
149
+ end
150
+
151
+ it "should open local file" do
152
+ add_stubs(:connection, :file_size, :create_container, :cloudfiles_store)
153
+ mock(File).open("foo")
154
+ @cloudfiles.send(:save)
155
+ end
156
+
157
+ it "should call write on the cloudfile object with files' descriptor" do
158
+ add_stubs(:connection, :file_size, :create_container, :cloudfiles_store)
159
+ stub(File).open("foo") {"qqq"}
160
+ mock(@object).write("qqq") {true}
161
+ @cloudfiles.send(:save)
162
+ end
163
+
164
+ it "should upload file" do
165
+ add_stubs(:connection, :file_size, :create_container, :file_open, :cloudfiles_store)
166
+ @cloudfiles.send(:save)
167
+ end
168
+
169
+ it "should fail on files bigger then 5G" do
170
+ add_stubs(:connection)
171
+ mock(File).stat("foo").stub!.size {5*1024*1024*1024+1}
172
+ mock(STDERR).puts(anything)
173
+ dont_allow(Benchmark).realtime
174
+ @cloudfiles.send(:save)
175
+ end
176
+ end
177
+ end
@@ -0,0 +1,234 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../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
+ airbrake do
7
+ api_key "test_airbrake_api_key"
8
+ end
9
+
10
+ local do
11
+ path "path"
12
+ end
13
+
14
+ s3 do
15
+ key "s3 key"
16
+ secret "secret"
17
+ bucket "bucket"
18
+ path "path1"
19
+ end
20
+
21
+ sftp do
22
+ user "sftp user"
23
+ password "sftp password"
24
+ host "sftp host"
25
+ end
26
+
27
+ gpg do
28
+ key "gpg-key"
29
+ password "astrails"
30
+ end
31
+
32
+ keep do
33
+ local 4
34
+ s3 20
35
+ end
36
+
37
+ mysqldump do
38
+ options "-ceKq --single-transaction --create-options"
39
+
40
+ user "astrails"
41
+ password ""
42
+ host "localhost"
43
+ port 3306
44
+ socket "/var/run/mysqld/mysqld.sock"
45
+
46
+ database :blog
47
+
48
+ database :production do
49
+ keep :local => 3
50
+
51
+ gpg do
52
+ password "custom-production-pass"
53
+ end
54
+
55
+ skip_tables [:logger_exceptions, :request_logs]
56
+ end
57
+
58
+ end
59
+
60
+ pgdump do
61
+ options "-i -x -O"
62
+
63
+ user "astrails"
64
+ password ""
65
+ host "localhost"
66
+ port 5432
67
+
68
+ database :blog
69
+
70
+ database :production do
71
+ keep :local => 3
72
+
73
+ skip_tables [:logger_exceptions, :request_logs]
74
+ end
75
+
76
+ end
77
+
78
+ svndump do
79
+ repo :my_repo do
80
+ repo_path "/home/svn/my_repo"
81
+ end
82
+ end
83
+
84
+ tar do
85
+ archive "git-repositories" do
86
+ files "/home/git/repositories"
87
+ end
88
+
89
+ archive "etc-files" do
90
+ files "/etc"
91
+ exclude "/etc/puppet/other"
92
+ end
93
+
94
+ archive "dot-configs" do
95
+ files "/home/*/.[^.]*"
96
+ end
97
+
98
+ archive "blog" do
99
+ files "/var/www/blog.astrails.com/"
100
+ exclude ["/var/www/blog.astrails.com/log", "/var/www/blog.astrails.com/tmp"]
101
+ end
102
+
103
+ archive :misc do
104
+ files [ "/backup/*.rb" ]
105
+ end
106
+ end
107
+
108
+ mongodump do
109
+ host "host"
110
+ database "database"
111
+ user "user"
112
+ password "password"
113
+ end
114
+ end
115
+
116
+ expected = {
117
+ "airbrake" => {"api_key" => "test_airbrake_api_key"},
118
+
119
+ "local" => {"path" => "path"},
120
+
121
+ "s3" => {
122
+ "key" => "s3 key",
123
+ "secret" => "secret",
124
+ "bucket" => "bucket",
125
+ "path" => "path1",
126
+ },
127
+
128
+ "sftp" => {
129
+ "user" => "sftp user",
130
+ "password" => "sftp password",
131
+ "host" => "sftp host",
132
+ },
133
+
134
+ "gpg" => {"password" => "astrails", "key" => "gpg-key"},
135
+
136
+ "keep" => {"s3" => 20, "local" => 4},
137
+
138
+ "mysqldump" => {
139
+ "options" => "-ceKq --single-transaction --create-options",
140
+ "user" => "astrails",
141
+ "password" => "",
142
+ "host" => "localhost",
143
+ "port" => 3306,
144
+ "socket" => "/var/run/mysqld/mysqld.sock",
145
+
146
+ "databases" => {
147
+ "blog" => {},
148
+ "production" => {
149
+ "keep" => {"local" => 3},
150
+ "gpg" => {"password" => "custom-production-pass"},
151
+ "skip_tables" => ["logger_exceptions", "request_logs"],
152
+ },
153
+ },
154
+ },
155
+
156
+ "pgdump" => {
157
+ "options" => "-i -x -O",
158
+ "user" => "astrails",
159
+ "password" => "",
160
+ "host" => "localhost",
161
+ "port" => 5432,
162
+
163
+ "databases" => {
164
+ "blog" => {},
165
+ "production" => {
166
+ "keep" => {"local" => 3},
167
+ "skip_tables" => ["logger_exceptions", "request_logs"],
168
+ },
169
+ },
170
+ },
171
+
172
+ "svndump" => {
173
+ "repos" => {
174
+ "my_repo"=> {
175
+ "repo_path" => "/home/svn/my_repo"
176
+ }
177
+ }
178
+ },
179
+
180
+ "tar" => {
181
+ "archives" => {
182
+ "git-repositories" => {"files" => "/home/git/repositories"},
183
+ "etc-files" => {"files" => "/etc", "exclude" => "/etc/puppet/other"},
184
+ "dot-configs" => {"files" => "/home/*/.[^.]*"},
185
+ "blog" => {
186
+ "files" => "/var/www/blog.astrails.com/",
187
+ "exclude" => ["/var/www/blog.astrails.com/log", "/var/www/blog.astrails.com/tmp"],
188
+ },
189
+ "misc" => { "files" => ["/backup/*.rb"] },
190
+ },
191
+ },
192
+
193
+ "mongodump" => {
194
+ "host" => "host",
195
+ "databases" => {
196
+ "database" => {}
197
+ },
198
+ "user" => "user",
199
+ "password" => "password"
200
+ }
201
+ }
202
+
203
+ config.to_hash.should == expected
204
+ end
205
+
206
+ it "should make an array from multivalues" do
207
+ config = Astrails::Safe::Config::Node.new do
208
+ skip_tables "a"
209
+ skip_tables "b"
210
+ files "/foo"
211
+ files "/bar"
212
+ exclude "/foo/bar"
213
+ exclude "/foo/bar/baz"
214
+ end
215
+
216
+ expected = {
217
+ "skip_tables" => ["a", "b"],
218
+ "files" => ["/foo", "/bar"],
219
+ "exclude" => ["/foo/bar", "/foo/bar/baz"],
220
+ }
221
+
222
+ config.to_hash.should == expected
223
+ end
224
+
225
+ it "should raise error on key duplication" do
226
+ proc do
227
+ Astrails::Safe::Config::Node.new do
228
+ path "foo"
229
+ path "bar"
230
+ end
231
+ end.should raise_error(ArgumentError, "duplicate value for 'path'")
232
+ end
233
+
234
+ end
@@ -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