astrails-safe 0.2.0 → 0.2.1

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.
@@ -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