heroku_cloud_backup 0.0.7 → 0.1.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.
- data/.travis.yml +10 -0
- data/LICENSE +1 -1
- data/README.md +3 -1
- data/Rakefile +14 -0
- data/heroku_cloud_backup.gemspec +8 -4
- data/lib/heroku_cloud_backup/errors.rb +2 -1
- data/lib/heroku_cloud_backup/railtie.rb +2 -0
- data/lib/heroku_cloud_backup/tasks.rb +2 -0
- data/lib/heroku_cloud_backup/version.rb +3 -1
- data/lib/heroku_cloud_backup.rb +72 -67
- data/spec/heroku_cloud_backup_spec.rb +55 -0
- metadata +34 -9
data/.travis.yml
ADDED
data/LICENSE
CHANGED
data/README.md
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
# Heroku Cloud Backup
|
2
2
|
|
3
|
+
[](http://travis-ci.org/kamui/heroku_cloud_backup)
|
4
|
+
|
3
5
|
The `heroku_cloud_backup` gem adds a rake task to your project that will take backups stored with Heroku's [PGBackup addon](http://devcenter.heroku.com/articles/pgbackups) and upload them to the cloud.
|
4
6
|
|
5
7
|
## Installation
|
@@ -93,4 +95,4 @@ I would recommend you create a temporarily public url from your cloud storage. I
|
|
93
95
|
|
94
96
|
### Copyright
|
95
97
|
|
96
|
-
Copyright (c) 2011 Jack Chu. See LICENSE for details.
|
98
|
+
Copyright (c) 2011-2012 Jack Chu. See LICENSE for details.
|
data/Rakefile
CHANGED
@@ -1,2 +1,16 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
1
3
|
require 'bundler'
|
2
4
|
Bundler::GemHelper.install_tasks
|
5
|
+
|
6
|
+
require 'rake/testtask'
|
7
|
+
task :default => :test
|
8
|
+
|
9
|
+
desc "Run tests"
|
10
|
+
task :test do
|
11
|
+
Rake::TestTask.new do |t|
|
12
|
+
t.libs << 'lib' << 'spec'
|
13
|
+
t.pattern = 'spec/**/*_spec.rb'
|
14
|
+
t.verbose = true
|
15
|
+
end
|
16
|
+
end
|
data/heroku_cloud_backup.gemspec
CHANGED
@@ -1,4 +1,5 @@
|
|
1
|
-
#
|
1
|
+
# encoding: utf-8
|
2
|
+
|
2
3
|
$:.push File.expand_path("../lib", __FILE__)
|
3
4
|
require "heroku_cloud_backup/version"
|
4
5
|
|
@@ -8,7 +9,7 @@ Gem::Specification.new do |s|
|
|
8
9
|
s.platform = Gem::Platform::RUBY
|
9
10
|
s.authors = ["Jack Chu"]
|
10
11
|
s.email = ["jack@jackchu.com"]
|
11
|
-
s.homepage = "http://jackchu.com/automated-heroku-database-backups-to-s3"
|
12
|
+
s.homepage = "http://jackchu.com/blog/2011/06/10/automated-heroku-database-backups-to-amazon-s3/"
|
12
13
|
s.summary = %q{Backup pg dumps to the cloud}
|
13
14
|
s.description = %q{PG backups into the cloud with fog}
|
14
15
|
|
@@ -18,6 +19,9 @@ Gem::Specification.new do |s|
|
|
18
19
|
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
19
20
|
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
20
21
|
s.require_paths = ["lib"]
|
21
|
-
|
22
|
-
s.add_runtime_dependency
|
22
|
+
|
23
|
+
s.add_runtime_dependency 'fog', '>= 0.7.2'
|
24
|
+
s.add_runtime_dependency 'heroku', '>= 2.1.4'
|
25
|
+
s.add_development_dependency 'rake'
|
26
|
+
s.add_development_dependency 'minitest'
|
23
27
|
end
|
@@ -1,8 +1,9 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
1
3
|
module HerokuCloudBackup
|
2
4
|
module Errors
|
3
5
|
class Error < StandardError; end
|
4
6
|
class NotFound < HerokuCloudBackup::Errors::Error; end
|
5
|
-
class InvalidProvider < HerokuCloudBackup::Errors::Error; end
|
6
7
|
class ConnectionError < HerokuCloudBackup::Errors::Error; end
|
7
8
|
class UploadError < HerokuCloudBackup::Errors::Error; end
|
8
9
|
class NoBackups < HerokuCloudBackup::Errors::Error; end
|
data/lib/heroku_cloud_backup.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
1
3
|
require 'fog'
|
2
4
|
require 'open-uri'
|
3
5
|
require "heroku"
|
@@ -8,80 +10,20 @@ require 'heroku_cloud_backup/version'
|
|
8
10
|
|
9
11
|
module HerokuCloudBackup
|
10
12
|
class << self
|
11
|
-
|
12
|
-
def log(message)
|
13
|
-
puts "[#{Time.now}] #{message}"
|
14
|
-
end
|
15
|
-
|
16
|
-
def backups_url
|
17
|
-
ENV["PGBACKUPS_URL"]
|
18
|
-
end
|
19
|
-
|
20
|
-
def client
|
21
|
-
@client ||= PGBackups::Client.new(ENV["PGBACKUPS_URL"])
|
22
|
-
end
|
23
|
-
|
24
|
-
def databases
|
25
|
-
if db = ENV["HEROKU_BACKUP_DATABASES"]
|
26
|
-
db.split(",").map(&:strip)
|
27
|
-
else
|
28
|
-
["DATABASE_URL"]
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
def backup_name(to_url)
|
33
|
-
# translate s3://bucket/email/foo/bar.dump => foo/bar
|
34
|
-
parts = to_url.split('/')
|
35
|
-
parts.slice(4..-1).join('/').gsub(/\.dump$/, '')
|
36
|
-
end
|
37
|
-
|
38
13
|
def execute
|
39
14
|
log "heroku:backup started"
|
40
15
|
|
41
|
-
@bucket_name = ENV['HCB_BUCKET'] || raise(HerokuCloudBackup::Errors::NotFound.new("Please provide a 'HCB_BUCKET' config variable."))
|
42
|
-
@backup_path = ENV['HCB_PREFIX'] || "db"
|
43
|
-
@provider = ENV['HCB_PROVIDER'] || raise(HerokuCloudBackup::Errors::NotFound.new("Please provide a 'HCB_PROVIDER' config variable."))
|
44
|
-
@key1 = ENV['HCB_KEY1'] || raise(HerokuCloudBackup::Errors::NotFound.new("Please provide a 'HCB_KEY1' config variable."))
|
45
|
-
@key2 = ENV['HCB_KEY2'] || raise(HerokuCloudBackup::Errors::NotFound.new("Please provide a 'HCB_KEY2' config variable."))
|
46
|
-
|
47
16
|
b = client.get_backups.last
|
48
|
-
raise HerokuCloudBackup::Errors::NoBackups.new("You don't have any pgbackups. Please run heroku pgbackups:capture first.")
|
17
|
+
raise HerokuCloudBackup::Errors::NoBackups.new("You don't have any pgbackups. Please run heroku pgbackups:capture first.") if b.empty?
|
49
18
|
|
50
19
|
begin
|
51
|
-
|
52
|
-
when 'aws'
|
53
|
-
@connection = Fog::Storage.new(
|
54
|
-
:provider => 'AWS',
|
55
|
-
:aws_access_key_id => @key1,
|
56
|
-
:aws_secret_access_key => @key2
|
57
|
-
)
|
58
|
-
when 'rackspace'
|
59
|
-
@connection = Fog::Storage.new(
|
60
|
-
:provider => 'Rackspace',
|
61
|
-
:rackspace_username => @key1,
|
62
|
-
:rackspace_api_key => @key2
|
63
|
-
)
|
64
|
-
when 'google'
|
65
|
-
@connection = Fog::Storage.new(
|
66
|
-
:provider => 'Google',
|
67
|
-
:google_storage_secret_access_key => @key1,
|
68
|
-
:google_storage_access_key_id => @key2
|
69
|
-
)
|
70
|
-
else
|
71
|
-
raise HerokuCloudBackup::Errors::InvalidProvider.new("Your provider was invalid. Valid values are 'aws', 'rackspace', or 'google'")
|
72
|
-
end
|
73
|
-
rescue
|
74
|
-
raise HerokuCloudBackup::Errors::ConnectionError.new("There was an error connecting to your provider.")
|
75
|
-
end
|
76
|
-
|
77
|
-
begin
|
78
|
-
directory = @connection.directories.get(@bucket_name)
|
20
|
+
directory = connection.directories.get(bucket_name)
|
79
21
|
rescue Excon::Errors::Forbidden
|
80
22
|
raise HerokuCloudBackup::Errors::Forbidden.new("You do not have access to this bucket name. It's possible this bucket name is already owned by another user. Please check your credentials (access keys) or select a different bucket name.")
|
81
23
|
end
|
82
24
|
|
83
25
|
if !directory
|
84
|
-
directory =
|
26
|
+
directory = connection.directories.create(:key => bucket_name)
|
85
27
|
end
|
86
28
|
|
87
29
|
public_url = b["public_url"]
|
@@ -90,7 +32,7 @@ module HerokuCloudBackup
|
|
90
32
|
name = "#{created_at.strftime('%Y-%m-%d-%H%M%S')}.dump"
|
91
33
|
begin
|
92
34
|
log "creating #{@backup_path}/#{b["from_name"]}/#{name}"
|
93
|
-
directory.files.create(:key => "#{
|
35
|
+
directory.files.create(:key => "#{backup_path}/#{b["from_name"]}/#{name}", :body => open(public_url))
|
94
36
|
rescue Exception => e
|
95
37
|
raise HerokuCloudBackup::Errors::UploadError.new(e.message)
|
96
38
|
end
|
@@ -100,16 +42,79 @@ module HerokuCloudBackup
|
|
100
42
|
log "heroku:backup complete"
|
101
43
|
end
|
102
44
|
|
45
|
+
def connection=(connection)
|
46
|
+
@connection = connection
|
47
|
+
end
|
48
|
+
|
49
|
+
def connection
|
50
|
+
return @connection if @connection
|
51
|
+
self.connection =
|
52
|
+
begin
|
53
|
+
case provider
|
54
|
+
when 'aws'
|
55
|
+
Fog::Storage.new(:provider => 'AWS',
|
56
|
+
:aws_access_key_id => key1,
|
57
|
+
:aws_secret_access_key => key2
|
58
|
+
)
|
59
|
+
when 'rackspace'
|
60
|
+
Fog::Storage.new(:provider => 'Rackspace',
|
61
|
+
:rackspace_username => key1,
|
62
|
+
:rackspace_api_key => key2
|
63
|
+
)
|
64
|
+
when 'google'
|
65
|
+
Fog::Storage.new(:provider => 'Google',
|
66
|
+
:google_storage_secret_access_key => key1,
|
67
|
+
:google_storage_access_key_id => key2
|
68
|
+
)
|
69
|
+
else
|
70
|
+
raise "Your provider was invalid. Valid values are 'aws', 'rackspace', or 'google'"
|
71
|
+
end
|
72
|
+
rescue => error
|
73
|
+
raise HerokuCloudBackup::Errors::ConnectionError.new("There was an error connecting to your provider. #{error}")
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def client
|
78
|
+
@client ||= PGBackups::Client.new(backups_url)
|
79
|
+
end
|
80
|
+
|
103
81
|
private
|
82
|
+
def backups_url
|
83
|
+
ENV["PGBACKUPS_URL"] || raise(HerokuCloudBackup::Errors::NotFound.new("'PGBACKUPS_URL' environment variable not found."))
|
84
|
+
end
|
85
|
+
|
86
|
+
def bucket_name
|
87
|
+
ENV['HCB_BUCKET'] || raise(HerokuCloudBackup::Errors::NotFound.new("Please provide a 'HCB_BUCKET' config variable."))
|
88
|
+
end
|
89
|
+
|
90
|
+
def backup_path
|
91
|
+
ENV['HCB_PREFIX'] || "db"
|
92
|
+
end
|
93
|
+
|
94
|
+
def provider
|
95
|
+
ENV['HCB_PROVIDER'] || raise(HerokuCloudBackup::Errors::NotFound.new("Please provide a 'HCB_PROVIDER' config variable."))
|
96
|
+
end
|
97
|
+
|
98
|
+
def key1
|
99
|
+
ENV['HCB_KEY1'] || raise(HerokuCloudBackup::Errors::NotFound.new("Please provide a 'HCB_KEY1' config variable."))
|
100
|
+
end
|
101
|
+
|
102
|
+
def key2
|
103
|
+
ENV['HCB_KEY2'] || raise(HerokuCloudBackup::Errors::NotFound.new("Please provide a 'HCB_KEY2' config variable."))
|
104
|
+
end
|
105
|
+
|
106
|
+
def log(message)
|
107
|
+
puts "[#{Time.now}] #{message}"
|
108
|
+
end
|
104
109
|
|
105
110
|
def prune
|
106
111
|
number_of_files = ENV['HCB_MAX']
|
107
112
|
if number_of_files && number_of_files.to_i > 0
|
108
|
-
directory =
|
109
|
-
files = directory.files.all(:prefix =>
|
113
|
+
directory = connection.directories.get(bucket_name)
|
114
|
+
files = directory.files.all(:prefix => backup_path)
|
110
115
|
file_count = 0
|
111
116
|
files.reverse.each do |file|
|
112
|
-
if file.key =~ Regexp.new("/#{
|
117
|
+
if file.key =~ Regexp.new("/#{backup_path}\/\d{4}-\d{2}-\d{2}-\d{6}\.sql\.gz$/i")
|
113
118
|
file_count += 1
|
114
119
|
else
|
115
120
|
next
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'minitest/autorun'
|
4
|
+
require "heroku_cloud_backup"
|
5
|
+
|
6
|
+
class MiniTest::Spec
|
7
|
+
class << self
|
8
|
+
alias :context :describe
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
describe HerokuCloudBackup do
|
13
|
+
before(:each) do
|
14
|
+
Fog.mock!
|
15
|
+
end
|
16
|
+
|
17
|
+
context ".client" do
|
18
|
+
it "should return an instance of PGBackups::Client" do
|
19
|
+
ENV['PGBACKUPS_URL'] = "http://example.com"
|
20
|
+
HerokuCloudBackup.client.must_be_instance_of PGBackups::Client
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
context ".connection" do
|
25
|
+
before(:each) do
|
26
|
+
ENV['HCB_KEY1'] = ENV['HCB_KEY2'] = 'testcredentials'
|
27
|
+
end
|
28
|
+
|
29
|
+
after(:each) do
|
30
|
+
HerokuCloudBackup.connection = nil
|
31
|
+
end
|
32
|
+
|
33
|
+
%w(aws rackspace google).each do |provider|
|
34
|
+
context "with HCB_PROVIDER=#{provider}" do
|
35
|
+
let(:connection) do
|
36
|
+
ENV['HCB_PROVIDER'] = provider
|
37
|
+
HerokuCloudBackup.connection
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should return a valid storage object if provider is #{provider}" do
|
41
|
+
connection.wont_be_nil
|
42
|
+
end
|
43
|
+
|
44
|
+
it "should return a storage object matching the provider" do
|
45
|
+
connection.class.name.must_match(/^Fog::Storage::#{provider}::/i)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
it "should raise HerokuCloudBackup::Errors::ConnectionError exception if provider is invalid" do
|
51
|
+
ENV['HCB_PROVIDER'] = "notsupported"
|
52
|
+
lambda { HerokuCloudBackup.connection }.must_raise HerokuCloudBackup::Errors::ConnectionError
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: heroku_cloud_backup
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2012-02-29 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: fog
|
16
|
-
requirement: &
|
16
|
+
requirement: &70160610629320 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ! '>='
|
@@ -21,10 +21,10 @@ dependencies:
|
|
21
21
|
version: 0.7.2
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *70160610629320
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: heroku
|
27
|
-
requirement: &
|
27
|
+
requirement: &70160610628780 !ruby/object:Gem::Requirement
|
28
28
|
none: false
|
29
29
|
requirements:
|
30
30
|
- - ! '>='
|
@@ -32,7 +32,29 @@ dependencies:
|
|
32
32
|
version: 2.1.4
|
33
33
|
type: :runtime
|
34
34
|
prerelease: false
|
35
|
-
version_requirements: *
|
35
|
+
version_requirements: *70160610628780
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: rake
|
38
|
+
requirement: &70160610628380 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
type: :development
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *70160610628380
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: minitest
|
49
|
+
requirement: &70160610627780 !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ! '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
type: :development
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: *70160610627780
|
36
58
|
description: PG backups into the cloud with fog
|
37
59
|
email:
|
38
60
|
- jack@jackchu.com
|
@@ -41,6 +63,7 @@ extensions: []
|
|
41
63
|
extra_rdoc_files: []
|
42
64
|
files:
|
43
65
|
- .gitignore
|
66
|
+
- .travis.yml
|
44
67
|
- Gemfile
|
45
68
|
- LICENSE
|
46
69
|
- README.md
|
@@ -51,7 +74,8 @@ files:
|
|
51
74
|
- lib/heroku_cloud_backup/railtie.rb
|
52
75
|
- lib/heroku_cloud_backup/tasks.rb
|
53
76
|
- lib/heroku_cloud_backup/version.rb
|
54
|
-
|
77
|
+
- spec/heroku_cloud_backup_spec.rb
|
78
|
+
homepage: http://jackchu.com/blog/2011/06/10/automated-heroku-database-backups-to-amazon-s3/
|
55
79
|
licenses: []
|
56
80
|
post_install_message:
|
57
81
|
rdoc_options: []
|
@@ -71,8 +95,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
71
95
|
version: '0'
|
72
96
|
requirements: []
|
73
97
|
rubyforge_project: heroku_cloud_backup
|
74
|
-
rubygems_version: 1.8.
|
98
|
+
rubygems_version: 1.8.15
|
75
99
|
signing_key:
|
76
100
|
specification_version: 3
|
77
101
|
summary: Backup pg dumps to the cloud
|
78
|
-
test_files:
|
102
|
+
test_files:
|
103
|
+
- spec/heroku_cloud_backup_spec.rb
|