bostonlogic-safe 0.3.0

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 (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