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