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 the database to perform backups on. Defaults to DATABASE_URL.
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 PGBACKUPS_DATABASE="HEROKU_POSTGRESQL_GOLD_URL"
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, :backup
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 = Heroku::Client::Pgbackups.new attrs[:pgbackups_url]
10
- @backup = nil
11
- @environment = attrs[:env] || (defined?(Rails) ? Rails.env : nil)
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
- @backup = @client.create_transfer database_url, database_url, nil, "BACKUP", :expire => true
27
+ @pgbackup = @client.create_transfer(database_url, database_url, nil,
28
+ "BACKUP", :expire => true)
16
29
 
17
- until @backup["finished_at"]
30
+ until @pgbackup["finished_at"]
18
31
  print "."
19
32
  sleep 1
20
- @backup = @client.get_transfer @backup["id"]
33
+ @pgbackup = @client.get_transfer @pgbackup["id"]
21
34
  end
35
+ end
22
36
 
23
- @backup
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 @backup["public_url"]
61
+ File.open temp_file, "r"
32
62
  end
33
63
 
34
64
  def key
35
- ["pgbackups", @environment, @backup["finished_at"].gsub(/\/|\:|\.|\s/, "-").concat(".dump")].join("/")
65
+ ["pgbackups", environment, @pgbackup["finished_at"]
66
+ .gsub(/\/|\:|\.|\s/, "-").concat(".dump")].compact.join("/")
36
67
  end
37
68
 
38
- def store
39
- PgbackupsArchive::Storage.new(key, file).store
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,3 +1,3 @@
1
1
  module PgbackupsArchive
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.0"
3
3
  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
- archive = Heroku::Client::PgbackupsArchive.new(:pgbackups_url => ENV["PGBACKUPS_URL"])
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
- let(:archive) {
6
- Heroku::Client::PgbackupsArchive.new(
7
- :pgbackups_url => "https://ip:password@pgbackups.heroku.com/client"
8
- )
9
- }
10
- let(:backup) { { "finished_at" => "some timestamp" } }
11
-
12
- it "should use a pgbackup client" do
13
- archive.client.class.must_equal Heroku::Client::Pgbackups
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 "given a finished_at timestamp" do
17
- before { archive.client.stubs(:create_transfer).returns(backup) }
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
- it "should capture the backup" do
20
- archive.capture.must_equal backup
26
+ before do
27
+ ENV["PGBACKUPS_URL"] = pgbackups_url
21
28
  end
22
29
 
23
- it "should store the backup" do
24
- archive.stubs(:key).returns("key")
25
- archive.stubs(:file).returns("file")
26
- archive.store.class.must_equal Fog::Storage::AWS::File
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
- describe "configure the backup database" do
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
- ENV["PGBACKUPS_DATABASE_URL"] = nil
35
- ENV["DATABASE_URL"] = "db_url"
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 "defaults to using the DATABASE_URL" do
39
- archive.client.expects(:create_transfer)
40
- .with("db_url", "db_url", nil, "BACKUP", :expire => true)
41
- .returns(backup)
48
+ it "should use a storage instance to store the archive" do
49
+ backup.archive
50
+ end
51
+ end
42
52
 
43
- archive.capture
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 "backup database is configured" do
69
+ describe "#delete" do
70
+ let(:temp_file) { "temp-file" }
71
+
48
72
  before do
49
- ENV["PGBACKUPS_DATABASE_URL"] = "backup_db"
50
- ENV["DATABASE_URL"] = "db_url"
73
+ backup.stubs(:temp_file).returns(temp_file)
74
+ File.expects(:delete).with(temp_file).returns(true)
51
75
  end
52
76
 
53
- it "defaults to using the DATABASE_URL" do
54
- archive.client.expects(:create_transfer)
55
- .with("backup_db", "backup_db", nil, "BACKUP", :expire => true)
56
- .returns(backup)
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
- archive.capture
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.1.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-05 00:00:00.000000000 Z
12
+ date: 2013-02-11 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bundler