pgbackups-archive 0.1.0 → 0.2.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/README.md
CHANGED
@@ -29,9 +29,9 @@ Apply environment variables:
|
|
29
29
|
heroku config:add PGBACKUPS_BUCKET="myapp-backups"
|
30
30
|
heroku config:add PGBACKUPS_REGION="us-west-2"
|
31
31
|
|
32
|
-
Optionally set
|
32
|
+
By default backups work of your primary database or the value of ENV['DATABASE_URL'], but database backups from your primary can impact the performance of your application. Optionally set an alternate database to perform backups on with:
|
33
33
|
|
34
|
-
heroku config:add
|
34
|
+
heroku config:add PGBACKUPS_DATABASE_URL="your_follower_database_url_here"
|
35
35
|
|
36
36
|
|
37
37
|
Note: A good security measure would be to use a dedicated set of AWS credentials with a security policy only allowing access to the bucket you're specifying. See this Pro Tip on [Assigning an AWS IAM user access to a single S3 bucket](http://coderwall.com/p/dwhlma).
|
@@ -1,42 +1,77 @@
|
|
1
1
|
require "heroku/client"
|
2
|
+
require "tmpdir"
|
2
3
|
|
3
4
|
class Heroku::Client::PgbackupsArchive
|
4
5
|
|
5
|
-
attr_reader :client, :
|
6
|
+
attr_reader :client, :pgbackup
|
7
|
+
|
8
|
+
def self.perform
|
9
|
+
backup = new
|
10
|
+
backup.capture
|
11
|
+
backup.download
|
12
|
+
backup.archive
|
13
|
+
backup.delete
|
14
|
+
end
|
6
15
|
|
7
16
|
def initialize(attrs={})
|
8
17
|
Heroku::Command.load
|
9
|
-
@client
|
10
|
-
@
|
11
|
-
|
18
|
+
@client = Heroku::Client::Pgbackups.new pgbackups_url
|
19
|
+
@pgbackup = nil
|
20
|
+
end
|
21
|
+
|
22
|
+
def archive
|
23
|
+
PgbackupsArchive::Storage.new(key, file).store
|
12
24
|
end
|
13
25
|
|
14
26
|
def capture
|
15
|
-
@
|
27
|
+
@pgbackup = @client.create_transfer(database_url, database_url, nil,
|
28
|
+
"BACKUP", :expire => true)
|
16
29
|
|
17
|
-
until @
|
30
|
+
until @pgbackup["finished_at"]
|
18
31
|
print "."
|
19
32
|
sleep 1
|
20
|
-
@
|
33
|
+
@pgbackup = @client.get_transfer @pgbackup["id"]
|
21
34
|
end
|
35
|
+
end
|
22
36
|
|
23
|
-
|
37
|
+
def delete
|
38
|
+
File.delete temp_file
|
24
39
|
end
|
25
40
|
|
41
|
+
def download
|
42
|
+
File.open(temp_file, "wb") do |output|
|
43
|
+
streamer = lambda do |chunk, remaining_bytes, total_bytes|
|
44
|
+
output.write chunk
|
45
|
+
end
|
46
|
+
Excon.get(@pgbackup["public_url"], :response_block => streamer)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
26
52
|
def database_url
|
27
53
|
ENV["PGBACKUPS_DATABASE_URL"] || ENV["DATABASE_URL"]
|
28
54
|
end
|
29
55
|
|
56
|
+
def environment
|
57
|
+
defined?(Rails) ? Rails.env : nil
|
58
|
+
end
|
59
|
+
|
30
60
|
def file
|
31
|
-
open
|
61
|
+
File.open temp_file, "r"
|
32
62
|
end
|
33
63
|
|
34
64
|
def key
|
35
|
-
["pgbackups",
|
65
|
+
["pgbackups", environment, @pgbackup["finished_at"]
|
66
|
+
.gsub(/\/|\:|\.|\s/, "-").concat(".dump")].compact.join("/")
|
36
67
|
end
|
37
68
|
|
38
|
-
def
|
39
|
-
|
69
|
+
def pgbackups_url
|
70
|
+
ENV["PGBACKUPS_URL"]
|
71
|
+
end
|
72
|
+
|
73
|
+
def temp_file
|
74
|
+
"#{Dir.tmpdir}/#{URI(@pgbackup['public_url']).path.split('/').last}"
|
40
75
|
end
|
41
76
|
|
42
77
|
end
|
@@ -1,9 +1,8 @@
|
|
1
1
|
namespace :pgbackups do
|
2
2
|
|
3
|
+
desc "Perform a pgbackups backup then archive to S3."
|
3
4
|
task :archive do
|
4
|
-
|
5
|
-
archive.capture
|
6
|
-
archive.store
|
5
|
+
Heroku::Client::PgbackupsArchive.perform
|
7
6
|
end
|
8
7
|
|
9
8
|
end
|
@@ -2,60 +2,198 @@ require "minitest_helper"
|
|
2
2
|
require "heroku/client"
|
3
3
|
|
4
4
|
describe Heroku::Client::PgbackupsArchive do
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
5
|
+
|
6
|
+
describe "#self.perform" do
|
7
|
+
before do
|
8
|
+
Heroku::Client::PgbackupsArchive.expects(:new).returns(
|
9
|
+
mock(
|
10
|
+
:capture => stub,
|
11
|
+
:download => stub,
|
12
|
+
:archive => stub,
|
13
|
+
:delete => stub
|
14
|
+
)
|
15
|
+
)
|
16
|
+
end
|
17
|
+
|
18
|
+
it { Heroku::Client::PgbackupsArchive.perform }
|
14
19
|
end
|
15
20
|
|
16
|
-
describe "
|
17
|
-
|
21
|
+
describe "An instance" do
|
22
|
+
let(:database_url) { "db_url" }
|
23
|
+
let(:pgbackups_url) { "https://ip:password@pgbackups.heroku.com/client" }
|
24
|
+
let(:backup) { Heroku::Client::PgbackupsArchive.new }
|
18
25
|
|
19
|
-
|
20
|
-
|
26
|
+
before do
|
27
|
+
ENV["PGBACKUPS_URL"] = pgbackups_url
|
21
28
|
end
|
22
29
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
30
|
+
describe "#initialize" do
|
31
|
+
it "should set client to a Heroku::Client::Pgbackups instance" do
|
32
|
+
backup.client.class.must_equal Heroku::Client::Pgbackups
|
33
|
+
end
|
27
34
|
end
|
28
|
-
end
|
29
35
|
|
30
|
-
|
36
|
+
describe "#archive" do
|
37
|
+
let(:key) { "some-key" }
|
38
|
+
let(:file) { "some-file" }
|
31
39
|
|
32
|
-
describe "backup database is not configured" do
|
33
40
|
before do
|
34
|
-
|
35
|
-
|
41
|
+
backup.stubs(:key).returns(key)
|
42
|
+
backup.stubs(:file).returns(file)
|
43
|
+
|
44
|
+
PgbackupsArchive::Storage.expects(:new).with(key, file)
|
45
|
+
.returns(mock(:store => stub))
|
36
46
|
end
|
37
47
|
|
38
|
-
it "
|
39
|
-
archive
|
40
|
-
|
41
|
-
|
48
|
+
it "should use a storage instance to store the archive" do
|
49
|
+
backup.archive
|
50
|
+
end
|
51
|
+
end
|
42
52
|
|
43
|
-
|
53
|
+
describe "#capture" do
|
54
|
+
let(:pgbackup) { { "finished_at" => "some-timestamp" } }
|
55
|
+
|
56
|
+
before do
|
57
|
+
backup.stubs(:database_url).returns(database_url)
|
58
|
+
|
59
|
+
backup.client.expects(:create_transfer)
|
60
|
+
.with(database_url, database_url, nil, "BACKUP", :expire => true)
|
61
|
+
.returns(pgbackup)
|
62
|
+
end
|
63
|
+
|
64
|
+
it "uses the client to create a pgbackup" do
|
65
|
+
backup.capture
|
44
66
|
end
|
45
67
|
end
|
46
68
|
|
47
|
-
describe "
|
69
|
+
describe "#delete" do
|
70
|
+
let(:temp_file) { "temp-file" }
|
71
|
+
|
48
72
|
before do
|
49
|
-
|
50
|
-
|
73
|
+
backup.stubs(:temp_file).returns(temp_file)
|
74
|
+
File.expects(:delete).with(temp_file).returns(true)
|
51
75
|
end
|
52
76
|
|
53
|
-
it "
|
54
|
-
|
55
|
-
|
56
|
-
|
77
|
+
it "should delete the temp file" do
|
78
|
+
backup.delete
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
describe "#download" do
|
83
|
+
let(:pgbackup) do
|
84
|
+
{
|
85
|
+
"public_url" => "https://raw.github.com/kjohnston/" +
|
86
|
+
"pgbackups-archive/master/pgbackups-archive.gemspec"
|
87
|
+
}
|
88
|
+
end
|
89
|
+
|
90
|
+
before do
|
91
|
+
backup.instance_eval do
|
92
|
+
@pgbackup = {
|
93
|
+
"public_url" => "https://raw.github.com/kjohnston/pgbackups-archive/master/pgbackups-archive.gemspec"
|
94
|
+
}
|
95
|
+
end
|
96
|
+
|
97
|
+
backup.download
|
98
|
+
end
|
99
|
+
|
100
|
+
it "downloads the backup file" do
|
101
|
+
backup.send(:file).read.must_match /Gem::Specification/
|
102
|
+
end
|
103
|
+
|
104
|
+
after do
|
105
|
+
backup.delete
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
describe "#database_url" do
|
110
|
+
describe "when an alternate database to backup is not set" do
|
111
|
+
before do
|
112
|
+
ENV["PGBACKUPS_DATABASE_URL"] = nil
|
113
|
+
ENV["DATABASE_URL"] = "default_url"
|
114
|
+
end
|
115
|
+
|
116
|
+
it "defaults to using the DATABASE_URL" do
|
117
|
+
backup.send(:database_url).must_equal "default_url"
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
describe "an alternate database to backup is set" do
|
122
|
+
before do
|
123
|
+
ENV["PGBACKUPS_DATABASE_URL"] = "alternate_url"
|
124
|
+
ENV["DATABASE_URL"] = "default_url"
|
125
|
+
end
|
126
|
+
|
127
|
+
it "uses the PGBACKUPS_DATABASE_URL" do
|
128
|
+
backup.send(:database_url).must_equal "alternate_url"
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
describe "#environment" do
|
134
|
+
describe "when Rails is present" do
|
135
|
+
before do
|
136
|
+
class Rails
|
137
|
+
def self.env
|
138
|
+
"test"
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
it "should use Rails.env" do
|
144
|
+
backup.send(:environment).must_equal "test"
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
describe "when Rails is not present" do
|
149
|
+
it "should default to nil" do
|
150
|
+
backup.send(:environment).must_equal nil
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
describe "#file" do
|
156
|
+
let(:temp_file) { "temp-file" }
|
157
|
+
|
158
|
+
before do
|
159
|
+
backup.stubs(:temp_file).returns(temp_file)
|
160
|
+
File.expects(:open).with(temp_file, "r").returns("")
|
161
|
+
end
|
162
|
+
|
163
|
+
it { backup.send(:file) }
|
164
|
+
end
|
165
|
+
|
166
|
+
describe "#key" do
|
167
|
+
before do
|
168
|
+
backup.instance_eval do
|
169
|
+
@pgbackup = {
|
170
|
+
"finished_at" => "timestamp"
|
171
|
+
}
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
it "should be composed properly" do
|
176
|
+
backup.send(:key).must_equal "pgbackups/test/timestamp.dump"
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
describe "#pgbackups_url" do
|
181
|
+
it { backup.send(:pgbackups_url).must_equal pgbackups_url }
|
182
|
+
end
|
183
|
+
|
184
|
+
describe "#temp_file" do
|
185
|
+
before do
|
186
|
+
backup.instance_eval do
|
187
|
+
@pgbackup = {
|
188
|
+
"public_url" => "https://raw.github.com/kjohnston/pgbackups-archive/master/pgbackups-archive.gemspec"
|
189
|
+
}
|
190
|
+
end
|
191
|
+
end
|
57
192
|
|
58
|
-
|
193
|
+
it "should be composed properly" do
|
194
|
+
temp_file = backup.send(:temp_file)
|
195
|
+
temp_file.must_match /^\/var\//
|
196
|
+
temp_file.must_match /\/pgbackups-archive.gemspec$/
|
59
197
|
end
|
60
198
|
end
|
61
199
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pgbackups-archive
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-02-
|
12
|
+
date: 2013-02-11 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: bundler
|