astrails-safe 0.2.0 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -20,12 +20,23 @@ We needed a backup solution that will satisfy the following requirements:
20
20
 
21
21
  And since we didn't find any, we wrote our own :)
22
22
 
23
- Note
24
- ----
23
+ Contributions
24
+ -------------
25
25
 
26
- Support for pg_dump and svndump was contributed but since I don't personally use them i don't have an easy
27
- way of testing it. So if you use Subversion or PostgreSQL please test the new functionality and report if
28
- there are any problems. Issues tracker is [here](http://github.com/astrails/safe/issues)
26
+ The following functionality was contributed by astrails-safe users:
27
+
28
+ * PostgreSQL dump using pg_dump (by Mark Mansour <mark@stateofflux.com>)
29
+ * Subversion dump using svndump (by Richard Luther <richard.luther@gmail.com>)
30
+ * SFTP remote storage (by Adam <adam@mediadrive.ca>)
31
+ * benchmarking output (By Neer)
32
+
33
+
34
+ Thanks to all :)
35
+
36
+ Reporting problems
37
+ ------------------
38
+
39
+ Please report problems at the [Issues tracker](http://github.com/astrails/safe/issues)
29
40
 
30
41
  Usage
31
42
  -----
@@ -118,6 +129,12 @@ Example configuration
118
129
  path "servers/alpha/:kind/:id"
119
130
  end
120
131
 
132
+ sftp do
133
+ host "sftp.astrails.com"
134
+ user "astrails"
135
+ password "ssh password for sftp"
136
+ end
137
+
121
138
  gpg do
122
139
  # symmetric encryption key
123
140
  # password "qwe"
@@ -1,4 +1,4 @@
1
1
  ---
2
2
  :major: 0
3
3
  :minor: 2
4
- :patch: 0
4
+ :patch: 1
@@ -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
@@ -14,6 +14,12 @@ describe Astrails::Safe::Config do
14
14
  path "path1"
15
15
  end
16
16
 
17
+ sftp do
18
+ user "sftp user"
19
+ password "sftp password"
20
+ host "sftp host"
21
+ end
22
+
17
23
  gpg do
18
24
  key "gpg-key"
19
25
  password "astrails"
@@ -107,6 +113,12 @@ describe Astrails::Safe::Config do
107
113
  "path" => "path1",
108
114
  },
109
115
 
116
+ "sftp" => {
117
+ "user" => "sftp user",
118
+ "password" => "sftp password",
119
+ "host" => "sftp host",
120
+ },
121
+
110
122
  "gpg" => {"password" => "astrails", "key" => "gpg-key"},
111
123
 
112
124
  "keep" => {"s3" => 20, "local" => 4},
@@ -82,8 +82,8 @@ describe Astrails::Safe::Local do
82
82
  describe :cleanup do
83
83
  before(:each) do
84
84
  @local = local
85
- @files = [4,1,3,2].to_a.map { |i| "aaaaa#{i}" }
86
- stub(Dir).[](anything) {@files}
85
+ @files = [4,1,3,2].to_a.map { |i| "/mysqldump~blog~NoW/qweqwe.#{i}" }
86
+ stub(Dir).[]("/mysqldump~blog~NoW/qweqwe.*") {@files}
87
87
  stub(File).file?(anything) {true}
88
88
  stub(File).size(anything) {1}
89
89
  stub(File).unlink
@@ -96,8 +96,8 @@ describe Astrails::Safe::Local do
96
96
  end
97
97
 
98
98
  it "should delete extra files" do
99
- mock(File).unlink("aaaaa1")
100
- mock(File).unlink("aaaaa2")
99
+ mock(File).unlink("/mysqldump~blog~NoW/qweqwe.1")
100
+ mock(File).unlink("/mysqldump~blog~NoW/qweqwe.2")
101
101
  @local.send :cleanup
102
102
  end
103
103
  end
@@ -39,7 +39,7 @@ describe Astrails::Safe::S3 do
39
39
 
40
40
  @files = [4,1,3,2].to_a.map { |i| stub(o = {}).key {"aaaaa#{i}"}; o }
41
41
 
42
- stub(AWS::S3::Bucket).objects("_bucket", :prefix => "_kind/_id/_kind-_id", :max_keys => 4) {@files}
42
+ stub(AWS::S3::Bucket).objects("_bucket", :prefix => "_kind/_id/_kind-_id.", :max_keys => 4) {@files}
43
43
  stub(AWS::S3::Bucket).find("_bucket").stub![anything].stub!.delete
44
44
  end
45
45
 
@@ -1,5 +1,7 @@
1
1
  require "aws/s3"
2
+ require 'net/sftp'
2
3
  require 'fileutils'
4
+ require 'benchmark'
3
5
 
4
6
  require 'tempfile'
5
7
  require 'extensions/mktmpdir'
@@ -28,7 +30,7 @@ require 'astrails/safe/gzip'
28
30
  require 'astrails/safe/sink'
29
31
  require 'astrails/safe/local'
30
32
  require 'astrails/safe/s3'
31
-
33
+ require 'astrails/safe/sftp'
32
34
 
33
35
  module Astrails
34
36
  module Safe
@@ -46,7 +48,7 @@ module Astrails
46
48
  ].each do |klass, path|
47
49
  if collection = config[*path]
48
50
  collection.each do |name, config|
49
- klass.new(name, config).backup.run(config, :gpg, :gzip, :local, :s3)
51
+ klass.new(name, config).backup.run(config, :gpg, :gzip, :local, :s3, :sftp)
50
52
  end
51
53
  end
52
54
  end
@@ -4,7 +4,7 @@ module Astrails
4
4
  class Builder
5
5
  COLLECTIONS = %w/database archive repo/
6
6
  ITEMS = %w/s3 key secret bucket path gpg password keep local mysqldump pgdump options
7
- user host port socket skip_tables tar files exclude filename svndump repo_path/
7
+ user host port socket skip_tables tar files exclude filename svndump repo_path sftp/
8
8
  NAMES = COLLECTIONS + ITEMS
9
9
  def initialize(node)
10
10
  @node = node
@@ -19,7 +19,10 @@ module Astrails
19
19
 
20
20
  unless $DRY_RUN
21
21
  FileUtils.mkdir_p(path) unless File.directory?(path)
22
- system "#{@backup.command}>#{@backup.path = full_path}"
22
+ benchmark = Benchmark.realtime do
23
+ system "#{@backup.command}>#{@backup.path = full_path}"
24
+ end
25
+ puts("command took " + sprintf("%.2f", benchmark) + " second(s).") if $_VERBOSE
23
26
  end
24
27
 
25
28
  end
@@ -20,11 +20,14 @@ module Astrails
20
20
 
21
21
  puts "Uploading #{bucket}:#{full_path}" if $_VERBOSE || $DRY_RUN
22
22
  unless $DRY_RUN || $LOCAL
23
- AWS::S3::Bucket.create(bucket)
24
- File.open(@backup.path) do |file|
25
- AWS::S3::S3Object.store(full_path, file, bucket)
23
+ benchmark = Benchmark.realtime do
24
+ AWS::S3::Bucket.create(bucket)
25
+ File.open(@backup.path) do |file|
26
+ AWS::S3::S3Object.store(full_path, file, bucket)
27
+ end
26
28
  end
27
29
  puts "...done" if $_VERBOSE
30
+ puts("Upload took " + sprintf("%.2f", benchmark) + " second(s).") if $_VERBOSE
28
31
  end
29
32
  end
30
33
 
@@ -0,0 +1,77 @@
1
+ module Astrails
2
+ module Safe
3
+ class Sftp < Sink
4
+
5
+ protected
6
+
7
+ def active?
8
+ host && user
9
+ end
10
+
11
+ def path
12
+ @path ||= expand(config[:sftp, :path] || config[:local, :path] || ":kind/:id")
13
+ end
14
+
15
+ def save
16
+ puts "Uploading #{host}:#{full_path} via SFTP" if $_VERBOSE || $DRY_RUN
17
+
18
+ unless $DRY_RUN || $LOCAL
19
+ opts = {}
20
+ opts[:password] = password if password
21
+ Net::SFTP.start(host, user, opts) do |sftp|
22
+ puts "Sending #{@backup.path} to #{full_path}" if $_VERBOSE
23
+ begin
24
+ sftp.upload! @backup.path, full_path
25
+ rescue Net::SFTP::StatusException
26
+ puts "Ensuring remote path (#{path}) exists" if $_VERBOSE
27
+ # mkdir -p
28
+ folders = path.split('/')
29
+ folders.each_index do |i|
30
+ folder = folders[0..i].join('/')
31
+ puts "Creating #{folder} on remote" if $_VERBOSE
32
+ sftp.mkdir!(folder) rescue Net::SFTP::StatusException
33
+ end
34
+ retry
35
+ end
36
+ end
37
+ puts "...done" if $_VERBOSE
38
+ end
39
+ end
40
+
41
+ def cleanup
42
+ return if $LOCAL || $DRY_RUN
43
+
44
+ return unless keep = @config[:keep, :sftp]
45
+
46
+ puts "listing files in #{host}:#{path}" if $_VERBOSE
47
+ Net::SFTP.start(host, user, :password => password) do |sftp|
48
+ files = sftp.dir.glob(path, '*')
49
+
50
+ puts files.collect {|x| x.name } if $_VERBOSE
51
+
52
+ files = files.
53
+ collect {|x| x.name }.
54
+ sort
55
+
56
+ cleanup_with_limit(files, keep) do |f|
57
+ file = File.join(path, f)
58
+ puts "removing sftp file #{host}:#{file}" if $DRY_RUN || $_VERBOSE
59
+ sftp.remove!(file) unless $DRY_RUN || $LOCAL
60
+ end
61
+ end
62
+ end
63
+
64
+ def host
65
+ @config[:sftp, :host]
66
+ end
67
+
68
+ def user
69
+ @config[:sftp, :user]
70
+ end
71
+
72
+ def password
73
+ @config[:sftp, :password]
74
+ end
75
+ end
76
+ end
77
+ end
@@ -13,7 +13,7 @@ module Astrails
13
13
 
14
14
  # path is defined in subclass
15
15
  def base
16
- @base ||= File.join(path, File.basename(@backup.filename).split(".").first)
16
+ @base ||= File.join(path, File.basename(@backup.filename).split(".").first + '.')
17
17
  end
18
18
 
19
19
  def full_path
@@ -24,6 +24,14 @@ safe do
24
24
  ## alternative style:
25
25
  # s3 :key => YOUR_S3_KEY, :secret => YOUR_S3_SECRET, :bucket => S3_BUCKET
26
26
 
27
+ ## uncomment to enable uploads via SFTP
28
+ # sftp do
29
+ # host "YOUR_REMOTE_HOSTNAME"
30
+ # user "YOUR_REMOTE_USERNAME"
31
+ # password "YOUR_REMOTE_PASSWORD"
32
+ # path ":kind/:id" # this is the default
33
+ # end
34
+
27
35
  ## uncomment to enable GPG encryption.
28
36
  ## Note: you can use public 'key' or symmetric password but not both!
29
37
  # gpg do
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: astrails-safe
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Astrails Ltd.
@@ -10,7 +10,7 @@ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
12
 
13
- date: 2009-06-04 00:00:00 -07:00
13
+ date: 2009-07-05 00:00:00 -07:00
14
14
  default_executable: astrails-safe
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
@@ -40,6 +40,7 @@ files:
40
40
  - bin/astrails-safe
41
41
  - examples/example_helper.rb
42
42
  - examples/integration/archive_integration_example.rb
43
+ - examples/integration/cleanup_example.rb
43
44
  - examples/unit/archive_example.rb
44
45
  - examples/unit/config_example.rb
45
46
  - examples/unit/gpg_example.rb
@@ -61,6 +62,7 @@ files:
61
62
  - lib/astrails/safe/pgdump.rb
62
63
  - lib/astrails/safe/pipe.rb
63
64
  - lib/astrails/safe/s3.rb
65
+ - lib/astrails/safe/sftp.rb
64
66
  - lib/astrails/safe/sink.rb
65
67
  - lib/astrails/safe/source.rb
66
68
  - lib/astrails/safe/stream.rb
@@ -95,14 +97,15 @@ signing_key:
95
97
  specification_version: 2
96
98
  summary: Backup filesystem and databases (MySQL and PostgreSQL) to Amazon S3 (with encryption)
97
99
  test_files:
98
- - examples/integration/archive_integration_example.rb
99
100
  - examples/example_helper.rb
100
- - examples/unit/gzip_example.rb
101
- - examples/unit/s3_example.rb
102
- - examples/unit/mysqldump_example.rb
103
- - examples/unit/gpg_example.rb
101
+ - examples/integration/archive_integration_example.rb
102
+ - examples/integration/cleanup_example.rb
104
103
  - examples/unit/archive_example.rb
105
- - examples/unit/svndump_example.rb
106
104
  - examples/unit/config_example.rb
105
+ - examples/unit/gpg_example.rb
106
+ - examples/unit/gzip_example.rb
107
107
  - examples/unit/local_example.rb
108
+ - examples/unit/mysqldump_example.rb
108
109
  - examples/unit/pgdump_example.rb
110
+ - examples/unit/s3_example.rb
111
+ - examples/unit/svndump_example.rb