bostonlogic-safe 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. data/LICENSE +20 -0
  2. data/README.markdown +227 -0
  3. data/Rakefile +54 -0
  4. data/VERSION.yml +4 -0
  5. data/bin/astrails-safe +53 -0
  6. data/examples/example_helper.rb +19 -0
  7. data/examples/integration/archive_integration_example.rb +86 -0
  8. data/examples/integration/cleanup_example.rb +62 -0
  9. data/examples/unit/archive_example.rb +67 -0
  10. data/examples/unit/config_example.rb +184 -0
  11. data/examples/unit/gpg_example.rb +138 -0
  12. data/examples/unit/gzip_example.rb +64 -0
  13. data/examples/unit/local_example.rb +110 -0
  14. data/examples/unit/mysqldump_example.rb +83 -0
  15. data/examples/unit/pgdump_example.rb +45 -0
  16. data/examples/unit/rcloud_example.rb +110 -0
  17. data/examples/unit/s3_example.rb +112 -0
  18. data/examples/unit/svndump_example.rb +39 -0
  19. data/lib/astrails/safe.rb +71 -0
  20. data/lib/astrails/safe/archive.rb +24 -0
  21. data/lib/astrails/safe/backup.rb +20 -0
  22. data/lib/astrails/safe/config/builder.rb +62 -0
  23. data/lib/astrails/safe/config/node.rb +66 -0
  24. data/lib/astrails/safe/gpg.rb +45 -0
  25. data/lib/astrails/safe/gzip.rb +25 -0
  26. data/lib/astrails/safe/local.rb +48 -0
  27. data/lib/astrails/safe/mysqldump.rb +31 -0
  28. data/lib/astrails/safe/notification.rb +66 -0
  29. data/lib/astrails/safe/pgdump.rb +36 -0
  30. data/lib/astrails/safe/pipe.rb +13 -0
  31. data/lib/astrails/safe/rcloud.rb +73 -0
  32. data/lib/astrails/safe/s3.rb +68 -0
  33. data/lib/astrails/safe/sftp.rb +79 -0
  34. data/lib/astrails/safe/sink.rb +33 -0
  35. data/lib/astrails/safe/source.rb +46 -0
  36. data/lib/astrails/safe/stream.rb +19 -0
  37. data/lib/astrails/safe/svndump.rb +13 -0
  38. data/lib/astrails/safe/tmp_file.rb +48 -0
  39. data/lib/extensions/mktmpdir.rb +45 -0
  40. data/templates/script.rb +155 -0
  41. metadata +135 -0
@@ -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
@@ -0,0 +1,67 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../example_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,184 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../example_helper')
2
+
3
+ describe Astrails::Safe::Config do
4
+ it "should parse example config" do
5
+ config = Astrails::Safe::Config::Node.new do
6
+ local do
7
+ path "path"
8
+ end
9
+
10
+ s3 do
11
+ key "s3 key"
12
+ secret "secret"
13
+ bucket "bucket"
14
+ path "path1"
15
+ end
16
+
17
+ sftp do
18
+ user "sftp user"
19
+ password "sftp password"
20
+ host "sftp host"
21
+ end
22
+
23
+ gpg do
24
+ key "gpg-key"
25
+ password "astrails"
26
+ end
27
+
28
+ keep do
29
+ local 4
30
+ s3 20
31
+ end
32
+
33
+ mysqldump do
34
+ options "-ceKq --single-transaction --create-options"
35
+
36
+ user "astrails"
37
+ password ""
38
+ host "localhost"
39
+ port 3306
40
+ socket "/var/run/mysqld/mysqld.sock"
41
+
42
+ database :blog
43
+
44
+ database :production do
45
+ keep :local => 3
46
+
47
+ gpg do
48
+ password "custom-production-pass"
49
+ end
50
+
51
+ skip_tables [:logger_exceptions, :request_logs]
52
+ end
53
+
54
+ end
55
+
56
+ pgdump do
57
+ options "-i -x -O"
58
+
59
+ user "astrails"
60
+ password ""
61
+ host "localhost"
62
+ port 5432
63
+
64
+ database :blog
65
+
66
+ database :production do
67
+ keep :local => 3
68
+
69
+ skip_tables [:logger_exceptions, :request_logs]
70
+ end
71
+
72
+ end
73
+
74
+ svndump do
75
+ repo :my_repo do
76
+ repo_path "/home/svn/my_repo"
77
+ end
78
+ end
79
+
80
+ tar do
81
+ archive "git-repositories" do
82
+ files "/home/git/repositories"
83
+ end
84
+
85
+ archive "etc-files" do
86
+ files "/etc"
87
+ exclude "/etc/puppet/other"
88
+ end
89
+
90
+ archive "dot-configs" do
91
+ files "/home/*/.[^.]*"
92
+ end
93
+
94
+ archive "blog" do
95
+ files "/var/www/blog.astrails.com/"
96
+ exclude ["/var/www/blog.astrails.com/log", "/var/www/blog.astrails.com/tmp"]
97
+ end
98
+
99
+ archive :misc do
100
+ files [ "/backup/*.rb" ]
101
+ end
102
+ end
103
+
104
+ end
105
+
106
+ expected = {
107
+ "local" => {"path" => "path"},
108
+
109
+ "s3" => {
110
+ "key" => "s3 key",
111
+ "secret" => "secret",
112
+ "bucket" => "bucket",
113
+ "path" => "path1",
114
+ },
115
+
116
+ "sftp" => {
117
+ "user" => "sftp user",
118
+ "password" => "sftp password",
119
+ "host" => "sftp host",
120
+ },
121
+
122
+ "gpg" => {"password" => "astrails", "key" => "gpg-key"},
123
+
124
+ "keep" => {"s3" => 20, "local" => 4},
125
+
126
+ "mysqldump" => {
127
+ "options" => "-ceKq --single-transaction --create-options",
128
+ "user" => "astrails",
129
+ "password" => "",
130
+ "host" => "localhost",
131
+ "port" => 3306,
132
+ "socket" => "/var/run/mysqld/mysqld.sock",
133
+
134
+ "databases" => {
135
+ "blog" => {},
136
+ "production" => {
137
+ "keep" => {"local" => 3},
138
+ "gpg" => {"password" => "custom-production-pass"},
139
+ "skip_tables" => ["logger_exceptions", "request_logs"],
140
+ },
141
+ },
142
+ },
143
+
144
+ "pgdump" => {
145
+ "options" => "-i -x -O",
146
+ "user" => "astrails",
147
+ "password" => "",
148
+ "host" => "localhost",
149
+ "port" => 5432,
150
+
151
+ "databases" => {
152
+ "blog" => {},
153
+ "production" => {
154
+ "keep" => {"local" => 3},
155
+ "skip_tables" => ["logger_exceptions", "request_logs"],
156
+ },
157
+ },
158
+ },
159
+
160
+ "svndump" => {
161
+ "repos" => {
162
+ "my_repo"=> {
163
+ "repo_path" => "/home/svn/my_repo"
164
+ }
165
+ }
166
+ },
167
+
168
+ "tar" => {
169
+ "archives" => {
170
+ "git-repositories" => {"files" => "/home/git/repositories"},
171
+ "etc-files" => {"files" => "/etc", "exclude" => "/etc/puppet/other"},
172
+ "dot-configs" => {"files" => "/home/*/.[^.]*"},
173
+ "blog" => {
174
+ "files" => "/var/www/blog.astrails.com/",
175
+ "exclude" => ["/var/www/blog.astrails.com/log", "/var/www/blog.astrails.com/tmp"],
176
+ },
177
+ "misc" => { "files" => ["/backup/*.rb"] },
178
+ },
179
+ },
180
+ }
181
+
182
+ config.to_hash.should == expected
183
+ end
184
+ end
@@ -0,0 +1,138 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../example_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
+ before(:each) do
106
+ @gpg = gpg(:gpg => {:key => "foo", :options => "GPG-OPT"}, :options => "OPT")
107
+ end
108
+
109
+ it "should not call gpg_password_file" do
110
+ dont_allow(@gpg).gpg_password_file(anything)
111
+ @gpg.send(:pipe)
112
+ end
113
+
114
+ it "should use '-r' and :options" do
115
+ @gpg.send(:pipe).should == "|gpg GPG-OPT -e -r foo"
116
+ end
117
+ end
118
+
119
+ describe "with password" do
120
+ before(:each) do
121
+ @gpg = gpg(:gpg => {:password => "bar", :options => "GPG-OPT"}, :options => "OPT")
122
+ stub(@gpg).gpg_password_file(anything) {"pass-file"}
123
+ end
124
+
125
+ it "should use '--passphrase-file' and :options" do
126
+ @gpg.send(:pipe).should == "|gpg GPG-OPT -c --passphrase-file pass-file"
127
+ end
128
+ end
129
+ end
130
+
131
+ describe :gpg_password_file do
132
+ it "should create password file" do
133
+ file = gpg.send(:gpg_password_file, "foo")
134
+ File.exists?(file).should be_true
135
+ File.read(file).should == "foo"
136
+ end
137
+ end
138
+ end