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,20 @@
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
+
10
+ # FIXME: move to Backup
11
+ def expand(path)
12
+ path .
13
+ gsub(/:kind\b/, @backup.kind.to_s) .
14
+ gsub(/:id\b/, @backup.id.to_s) .
15
+ gsub(/:timestamp\b/, @backup.timestamp)
16
+ end
17
+
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,13 @@
1
+ module Astrails
2
+ module Safe
3
+ class Svndump < Source
4
+
5
+ def command
6
+ "svnadmin dump #{@config[:options]} #{@config[:repo_path]}"
7
+ end
8
+
9
+ def extension; '.svn'; end
10
+
11
+ end
12
+ end
13
+ 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,5 @@
1
+ module Astrails
2
+ module Safe
3
+ VERSION = '0.2.9'
4
+ end
5
+ 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,76 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+ require 'toadhopper'
3
+
4
+ describe "a failing backup" do
5
+ before :each do
6
+ @exception = RuntimeError.new("BOOM")
7
+ stub.instance_of(Astrails::Safe::Source).backup { raise @exception }
8
+
9
+ # need both local and instance vars
10
+ # instance variables are used in tests
11
+ # local variables are used in the backup definition (instance vars can't be seen)
12
+ @root = root = "tmp/airbrake_backup_example"
13
+
14
+ # clean state
15
+ rm_rf @root
16
+ mkdir_p @root
17
+
18
+ # create source tree
19
+ @src = src = "#{@root}/src"
20
+ @dst = dst = "#{@root}/backup"
21
+ mkdir_p @dst
22
+
23
+ @now = Time.now
24
+ @timestamp = @now.strftime("%y%m%d-%H%M")
25
+
26
+ stub(Time).now {@now} # Freeze
27
+ end
28
+
29
+ describe "when not configured to send airbrake notifications" do
30
+ it "should not notify the toadhopper" do
31
+ dont_allow(Toadhopper).post!
32
+
33
+ Astrails::Safe.safe do
34
+ local :path => "#{@dst}/:kind"
35
+ tar do
36
+ archive :test1, :files => @src
37
+ end
38
+ end
39
+ end
40
+ end
41
+
42
+ describe "when configured to send airbrake notifications" do
43
+ def perform_backup
44
+ Astrails::Safe.safe do
45
+ airbrake do
46
+ api_key "test_api_key"
47
+ end
48
+
49
+ local :path => "#{@dst}/:kind"
50
+ tar do
51
+ archive :test1, :files => @src
52
+ end
53
+ end
54
+ end
55
+
56
+ it "should assign the specified api key" do
57
+ mock(Toadhopper).new('test_api_key')
58
+ perform_backup
59
+ end
60
+
61
+ it "should notifiy the toadhopper" do
62
+ mock.instance_of(Toadhopper).post!(@exception)
63
+ perform_backup
64
+ end
65
+
66
+ it "should clean up the tmp files" do
67
+ mock(Astrails::Safe::TmpFile).cleanup
68
+ perform_backup
69
+ end
70
+
71
+ it "should not raise exceptions from the toadhopper" do
72
+ mock.instance_of(Toadhopper).post!(@exception) { raise RuntimeError.new("From the toad") }
73
+ lambda { perform_backup }.should_not raise_exception("From the toad")
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,88 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_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/archive_backup_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}/q/w/e"
20
+ mkdir_p "#{@src}/a/s/d"
21
+
22
+ File.open("#{@src}/qwe1", "w") {|f| f.write("qwe") }
23
+ File.open("#{@src}/q/qwe2", "w") {|f| f.write("qwe"*2) }
24
+ File.open("#{@src}/q/w/qwe3", "w") {|f| f.write("qwe"*3) }
25
+ File.open("#{@src}/q/w/e/qwe4", "w") {|f| f.write("qwe"*4) }
26
+
27
+ File.open("#{@src}/asd1", "w") {|f| f.write("asd") }
28
+ File.open("#{@src}/a/asd2", "w") {|f| f.write("asd" * 2) }
29
+ File.open("#{@src}/a/s/asd3", "w") {|f| f.write("asd" * 3) }
30
+
31
+ @dst = dst = "#{@root}/backup"
32
+ mkdir_p @dst
33
+
34
+ @now = Time.now
35
+ @timestamp = @now.strftime("%y%m%d-%H%M")
36
+
37
+ stub(Time).now {@now} # Freeze
38
+
39
+ Astrails::Safe.safe do
40
+ local :path => "#{dst}/:kind"
41
+ tar do
42
+ archive :test1 do
43
+ files src
44
+ exclude "#{src}/q/w"
45
+ exclude "#{src}/q/w/e"
46
+ end
47
+ end
48
+ end
49
+
50
+ @backup = "#{dst}/archive/archive-test1.#{@timestamp}.tar.gz"
51
+ end
52
+
53
+ it "should create backup file" do
54
+ File.exists?(@backup).should be_true
55
+ end
56
+
57
+ describe "after extracting" do
58
+ before(:all) do
59
+ # prepare target dir
60
+ @target = "#{@root}/test"
61
+ mkdir_p @target
62
+ system "tar -zxvf #{@backup} -C #{@target}"
63
+
64
+ @test = "#{@target}/#{@root}/src"
65
+ puts @test
66
+ end
67
+
68
+ it "should include asd1/2/3" do
69
+ File.exists?("#{@test}/asd1").should be_true
70
+ File.exists?("#{@test}/a/asd2").should be_true
71
+ File.exists?("#{@test}/a/s/asd3").should be_true
72
+ end
73
+
74
+ it "should only include qwe 1 and 2 (no 3)" do
75
+ File.exists?("#{@test}/qwe1").should be_true
76
+ File.exists?("#{@test}/q/qwe2").should be_true
77
+ File.exists?("#{@test}/q/w/qwe3").should be_false
78
+ File.exists?("#{@test}/q/w/e/qwe4").should be_false
79
+ end
80
+
81
+ it "should preserve file content" do
82
+ File.read("#{@test}/qwe1").should == "qwe"
83
+ File.read("#{@test}/q/qwe2").should == "qweqwe"
84
+ File.read("#{@test}/a/s/asd3").should == "asdasdasd"
85
+ end
86
+ end
87
+
88
+ end
@@ -0,0 +1,61 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_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
+ File.exists?(@backup).should be_true
51
+ end
52
+
53
+ it "should remove old backups" do
54
+ Dir["#{@dst}/archive/archive-foo.*"].should == [@backup]
55
+ end
56
+
57
+ it "should NOT remove backups with base having same prefix" do
58
+ Dir["#{@dst}/archive/archive-foobar.*"].should == ["#{@dst}/archive/archive-foobar.000001.tar.gz", "#{@dst}/archive/archive-foobar.000002.tar.gz"]
59
+ end
60
+
61
+ end
data/spec/spec.opts ADDED
@@ -0,0 +1,5 @@
1
+ --colour
2
+ --format specdoc
3
+ --format profile:log/spec-benchmark.log
4
+ --loadby mtime
5
+ --reverse
@@ -0,0 +1,13 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
2
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
3
+ require 'spec'
4
+ require 'spec/autorun'
5
+
6
+ require 'rubygems'
7
+ #require 'ruby-debug'
8
+
9
+ require 'astrails/safe'
10
+
11
+ Spec::Runner.configure do |config|
12
+ config.mock_with :rr
13
+ end
@@ -0,0 +1,67 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../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