pogo 2.31.2
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 +73 -0
- data/bin/pogo +22 -0
- data/data/cacert.pem +3988 -0
- data/lib/heroku.rb +22 -0
- data/lib/heroku/auth.rb +320 -0
- data/lib/heroku/cli.rb +38 -0
- data/lib/heroku/client.rb +764 -0
- data/lib/heroku/client/heroku_postgresql.rb +111 -0
- data/lib/heroku/client/pgbackups.rb +113 -0
- data/lib/heroku/client/rendezvous.rb +105 -0
- data/lib/heroku/client/ssl_endpoint.rb +25 -0
- data/lib/heroku/command.rb +273 -0
- data/lib/heroku/command/account.rb +23 -0
- data/lib/heroku/command/accounts.rb +34 -0
- data/lib/heroku/command/addons.rb +305 -0
- data/lib/heroku/command/apps.rb +311 -0
- data/lib/heroku/command/auth.rb +86 -0
- data/lib/heroku/command/base.rb +230 -0
- data/lib/heroku/command/certs.rb +148 -0
- data/lib/heroku/command/config.rb +137 -0
- data/lib/heroku/command/db.rb +218 -0
- data/lib/heroku/command/domains.rb +85 -0
- data/lib/heroku/command/drains.rb +46 -0
- data/lib/heroku/command/git.rb +65 -0
- data/lib/heroku/command/help.rb +163 -0
- data/lib/heroku/command/keys.rb +115 -0
- data/lib/heroku/command/labs.rb +161 -0
- data/lib/heroku/command/logs.rb +98 -0
- data/lib/heroku/command/maintenance.rb +61 -0
- data/lib/heroku/command/pg.rb +277 -0
- data/lib/heroku/command/pgbackups.rb +289 -0
- data/lib/heroku/command/plugins.rb +110 -0
- data/lib/heroku/command/ps.rb +232 -0
- data/lib/heroku/command/releases.rb +124 -0
- data/lib/heroku/command/run.rb +179 -0
- data/lib/heroku/command/sharing.rb +89 -0
- data/lib/heroku/command/ssl.rb +61 -0
- data/lib/heroku/command/stack.rb +62 -0
- data/lib/heroku/command/status.rb +51 -0
- data/lib/heroku/command/update.rb +47 -0
- data/lib/heroku/command/version.rb +23 -0
- data/lib/heroku/deprecated.rb +5 -0
- data/lib/heroku/deprecated/help.rb +38 -0
- data/lib/heroku/distribution.rb +9 -0
- data/lib/heroku/helpers.rb +517 -0
- data/lib/heroku/helpers/heroku_postgresql.rb +104 -0
- data/lib/heroku/plugin.rb +161 -0
- data/lib/heroku/updater.rb +158 -0
- data/lib/heroku/version.rb +3 -0
- data/lib/vendor/heroku/okjson.rb +598 -0
- data/spec/helper/legacy_help.rb +16 -0
- data/spec/heroku/auth_spec.rb +246 -0
- data/spec/heroku/client/heroku_postgresql_spec.rb +34 -0
- data/spec/heroku/client/pgbackups_spec.rb +43 -0
- data/spec/heroku/client/rendezvous_spec.rb +62 -0
- data/spec/heroku/client/ssl_endpoint_spec.rb +48 -0
- data/spec/heroku/client_spec.rb +564 -0
- data/spec/heroku/command/addons_spec.rb +585 -0
- data/spec/heroku/command/apps_spec.rb +351 -0
- data/spec/heroku/command/auth_spec.rb +38 -0
- data/spec/heroku/command/base_spec.rb +109 -0
- data/spec/heroku/command/certs_spec.rb +178 -0
- data/spec/heroku/command/config_spec.rb +144 -0
- data/spec/heroku/command/db_spec.rb +110 -0
- data/spec/heroku/command/domains_spec.rb +87 -0
- data/spec/heroku/command/drains_spec.rb +34 -0
- data/spec/heroku/command/git_spec.rb +116 -0
- data/spec/heroku/command/help_spec.rb +93 -0
- data/spec/heroku/command/keys_spec.rb +120 -0
- data/spec/heroku/command/labs_spec.rb +99 -0
- data/spec/heroku/command/logs_spec.rb +60 -0
- data/spec/heroku/command/maintenance_spec.rb +51 -0
- data/spec/heroku/command/pg_spec.rb +223 -0
- data/spec/heroku/command/pgbackups_spec.rb +280 -0
- data/spec/heroku/command/plugins_spec.rb +104 -0
- data/spec/heroku/command/ps_spec.rb +195 -0
- data/spec/heroku/command/releases_spec.rb +130 -0
- data/spec/heroku/command/run_spec.rb +86 -0
- data/spec/heroku/command/sharing_spec.rb +59 -0
- data/spec/heroku/command/ssl_spec.rb +32 -0
- data/spec/heroku/command/stack_spec.rb +46 -0
- data/spec/heroku/command/status_spec.rb +48 -0
- data/spec/heroku/command/version_spec.rb +16 -0
- data/spec/heroku/command_spec.rb +211 -0
- data/spec/heroku/helpers/heroku_postgresql_spec.rb +109 -0
- data/spec/heroku/helpers_spec.rb +48 -0
- data/spec/heroku/plugin_spec.rb +172 -0
- data/spec/heroku/updater_spec.rb +44 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +209 -0
- data/spec/support/display_message_matcher.rb +49 -0
- data/spec/support/openssl_mock_helper.rb +8 -0
- metadata +220 -0
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
require "spec_helper"
|
|
2
|
+
require "heroku/command/logs"
|
|
3
|
+
|
|
4
|
+
describe Heroku::Command::Logs do
|
|
5
|
+
describe "logs" do
|
|
6
|
+
it "runs with no options" do
|
|
7
|
+
stub_core.read_logs("myapp", [])
|
|
8
|
+
execute "logs"
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
it "runs with options" do
|
|
12
|
+
stub_core.read_logs("myapp", [
|
|
13
|
+
"tail=1",
|
|
14
|
+
"num=2",
|
|
15
|
+
"ps=ps.3",
|
|
16
|
+
"source=source.4"
|
|
17
|
+
])
|
|
18
|
+
execute "logs --tail --num 2 --ps ps.3 --source source.4"
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
describe "with log output" do
|
|
22
|
+
before(:each) do
|
|
23
|
+
stub_core.read_logs("myapp", []).yields("2011-01-01T00:00:00+00:00 app[web.1]: test")
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
it "prettifies tty output" do
|
|
27
|
+
old_stdout_isatty = $stdout.isatty
|
|
28
|
+
stub($stdout).isatty.returns(true)
|
|
29
|
+
stderr, stdout = execute("logs")
|
|
30
|
+
stderr.should == ""
|
|
31
|
+
stdout.should == <<-STDOUT
|
|
32
|
+
\e[36m2011-01-01T00:00:00+00:00 app[web.1]:\e[0m test
|
|
33
|
+
STDOUT
|
|
34
|
+
stub($stdout).isatty.returns(old_stdout_isatty)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
it "does not use ansi if stdout is not a tty" do
|
|
38
|
+
old_stdout_isatty = $stdout.isatty
|
|
39
|
+
stub($stdout).isatty.returns(false)
|
|
40
|
+
stderr, stdout = execute("logs")
|
|
41
|
+
stderr.should == ""
|
|
42
|
+
stdout.should == <<-STDOUT
|
|
43
|
+
2011-01-01T00:00:00+00:00 app[web.1]: test
|
|
44
|
+
STDOUT
|
|
45
|
+
stub($stdout).isatty.returns(old_stdout_isatty)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
it "does not use ansi if TERM is not set" do
|
|
49
|
+
term = ENV.delete("TERM")
|
|
50
|
+
stderr, stdout = execute("logs")
|
|
51
|
+
stderr.should == ""
|
|
52
|
+
stdout.should == <<-STDOUT
|
|
53
|
+
2011-01-01T00:00:00+00:00 app[web.1]: test
|
|
54
|
+
STDOUT
|
|
55
|
+
ENV["TERM"] = term
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
require "spec_helper"
|
|
2
|
+
require "heroku/command/maintenance"
|
|
3
|
+
|
|
4
|
+
module Heroku::Command
|
|
5
|
+
describe Maintenance do
|
|
6
|
+
|
|
7
|
+
before(:each) do
|
|
8
|
+
stub_core
|
|
9
|
+
api.post_app("name" => "myapp", "stack" => "cedar")
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
after(:each) do
|
|
13
|
+
api.delete_app("myapp")
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
it "displays off for maintenance mode of an app" do
|
|
17
|
+
stderr, stdout = execute("maintenance")
|
|
18
|
+
stderr.should == ""
|
|
19
|
+
stdout.should == <<-STDOUT
|
|
20
|
+
off
|
|
21
|
+
STDOUT
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
it "displays on for maintenance mode of an app" do
|
|
25
|
+
api.post_app_maintenance('myapp', '1')
|
|
26
|
+
|
|
27
|
+
stderr, stdout = execute("maintenance")
|
|
28
|
+
stderr.should == ""
|
|
29
|
+
stdout.should == <<-STDOUT
|
|
30
|
+
on
|
|
31
|
+
STDOUT
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
it "turns on maintenance mode for the app" do
|
|
35
|
+
stderr, stdout = execute("maintenance:on")
|
|
36
|
+
stderr.should == ""
|
|
37
|
+
stdout.should == <<-STDOUT
|
|
38
|
+
Enabling maintenance mode for myapp... done
|
|
39
|
+
STDOUT
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
it "turns off maintenance mode for the app" do
|
|
43
|
+
stderr, stdout = execute("maintenance:off")
|
|
44
|
+
stderr.should == ""
|
|
45
|
+
stdout.should == <<-STDOUT
|
|
46
|
+
Disabling maintenance mode for myapp... done
|
|
47
|
+
STDOUT
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
require "spec_helper"
|
|
2
|
+
require "heroku/command/pg"
|
|
3
|
+
|
|
4
|
+
module Heroku::Command
|
|
5
|
+
describe Pg do
|
|
6
|
+
before do
|
|
7
|
+
stub_core
|
|
8
|
+
|
|
9
|
+
api.post_app "name" => "myapp"
|
|
10
|
+
api.put_config_vars "myapp", {
|
|
11
|
+
"DATABASE_URL" => "postgres://database_url",
|
|
12
|
+
"SHARED_DATABASE_URL" => "postgres://other_database_url",
|
|
13
|
+
"HEROKU_POSTGRESQL_RONIN_URL" => "postgres://ronin_database_url"
|
|
14
|
+
}
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
after do
|
|
18
|
+
api.delete_app "myapp"
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
it "resets the app's database if user confirms" do
|
|
22
|
+
stub_pg.reset
|
|
23
|
+
|
|
24
|
+
stderr, stdout = execute("pg:reset RONIN --confirm myapp")
|
|
25
|
+
stderr.should == ""
|
|
26
|
+
stdout.should == <<-STDOUT
|
|
27
|
+
Resetting HEROKU_POSTGRESQL_RONIN... done
|
|
28
|
+
STDOUT
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
it "resets shared databases" do
|
|
32
|
+
Heroku::Client.any_instance.should_receive(:database_reset).with('myapp')
|
|
33
|
+
|
|
34
|
+
stderr, stdout = execute("pg:reset SHARED_DATABASE --confirm myapp")
|
|
35
|
+
stderr.should == ''
|
|
36
|
+
stdout.should == <<-STDOUT
|
|
37
|
+
Resetting SHARED_DATABASE... done
|
|
38
|
+
STDOUT
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
it "doesn't reset the app's database if the user doesn't confirm" do
|
|
42
|
+
stub_pg.reset
|
|
43
|
+
|
|
44
|
+
stderr, stdout = execute("pg:reset RONIN")
|
|
45
|
+
stderr.should == <<-STDERR
|
|
46
|
+
! Confirmation did not match myapp. Aborted.
|
|
47
|
+
STDERR
|
|
48
|
+
stdout.should == "
|
|
49
|
+
! WARNING: Destructive Action
|
|
50
|
+
! This command will affect the app: myapp
|
|
51
|
+
! To proceed, type \"myapp\" or re-run this command with --confirm myapp
|
|
52
|
+
|
|
53
|
+
> "
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
context "index" do
|
|
57
|
+
it "requests the info from the server" do
|
|
58
|
+
stub_pg.get_database.returns(:info => [
|
|
59
|
+
{"name"=>"Plan", "values"=>["Ronin"]},
|
|
60
|
+
{"name"=>"Status", "values"=>["available"]},
|
|
61
|
+
{"name"=>"Data Size", "values"=>["1 MB"]},
|
|
62
|
+
{"name"=>"Tables", "values"=>[1]},
|
|
63
|
+
{"name"=>"PG Version", "values"=>["9.1.4"]},
|
|
64
|
+
{"name"=>"Fork/Follow", "values"=>["Available"]},
|
|
65
|
+
{"name"=>"Created", "values"=>["2011-12-13 00:00 UTC"]},
|
|
66
|
+
{"name"=>"Conn Info", "values"=>["[Deprecated] Please use `heroku pg:credentials HEROKU_POSTGRESQL_RONIN` to view connection info"]},
|
|
67
|
+
{"name"=>"Maintenance", "values"=>["not required"]}
|
|
68
|
+
])
|
|
69
|
+
|
|
70
|
+
stderr, stdout = execute("pg")
|
|
71
|
+
stderr.should == ""
|
|
72
|
+
stdout.should == <<-STDOUT
|
|
73
|
+
=== HEROKU_POSTGRESQL_RONIN
|
|
74
|
+
Plan: Ronin
|
|
75
|
+
Status: available
|
|
76
|
+
Data Size: 1 MB
|
|
77
|
+
Tables: 1
|
|
78
|
+
PG Version: 9.1.4
|
|
79
|
+
Fork/Follow: Available
|
|
80
|
+
Created: 2011-12-13 00:00 UTC
|
|
81
|
+
Conn Info: [Deprecated] Please use `heroku pg:credentials HEROKU_POSTGRESQL_RONIN` to view connection info
|
|
82
|
+
Maintenance: not required
|
|
83
|
+
|
|
84
|
+
=== SHARED_DATABASE
|
|
85
|
+
Data Size: (empty)
|
|
86
|
+
|
|
87
|
+
STDOUT
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
context "info" do
|
|
92
|
+
it "requests the info from the server" do
|
|
93
|
+
stub_pg.get_database.returns(:info => [
|
|
94
|
+
{"name"=>"Plan", "values"=>["Ronin"]},
|
|
95
|
+
{"name"=>"Status", "values"=>["available"]},
|
|
96
|
+
{"name"=>"Data Size", "values"=>["1 MB"]},
|
|
97
|
+
{"name"=>"Tables", "values"=>[1]},
|
|
98
|
+
{"name"=>"PG Version", "values"=>["9.1.4"]},
|
|
99
|
+
{"name"=>"Fork/Follow", "values"=>["Available"]},
|
|
100
|
+
{"name"=>"Forked From", "values"=>["postgres://username:password@postgreshost.com:5432/database_name"], "resolve_db_name" => "true"},
|
|
101
|
+
{"name"=>"Created", "values"=>["2011-12-13 00:00 UTC"]},
|
|
102
|
+
{"name"=>"Conn Info", "values"=>["[Deprecated] Please use `heroku pg:credentials HEROKU_POSTGRESQL_RONIN` to view connection info"]},
|
|
103
|
+
{"name"=>"Maintenance", "values"=>["not required"]}
|
|
104
|
+
])
|
|
105
|
+
|
|
106
|
+
stderr, stdout = execute("pg:info RONIN")
|
|
107
|
+
stderr.should == ""
|
|
108
|
+
stdout.should == <<-STDOUT
|
|
109
|
+
=== HEROKU_POSTGRESQL_RONIN
|
|
110
|
+
Plan: Ronin
|
|
111
|
+
Status: available
|
|
112
|
+
Data Size: 1 MB
|
|
113
|
+
Tables: 1
|
|
114
|
+
PG Version: 9.1.4
|
|
115
|
+
Fork/Follow: Available
|
|
116
|
+
Forked From: Database on postgreshost.com:5432/database_name
|
|
117
|
+
Created: 2011-12-13 00:00 UTC
|
|
118
|
+
Conn Info: [Deprecated] Please use `heroku pg:credentials HEROKU_POSTGRESQL_RONIN` to view connection info
|
|
119
|
+
Maintenance: not required
|
|
120
|
+
|
|
121
|
+
STDOUT
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
context "promotion" do
|
|
126
|
+
it "promotes the specified database" do
|
|
127
|
+
stderr, stdout = execute("pg:promote RONIN --confirm myapp")
|
|
128
|
+
stderr.should == ""
|
|
129
|
+
stdout.should == <<-STDOUT
|
|
130
|
+
Promoting HEROKU_POSTGRESQL_RONIN to DATABASE_URL... done
|
|
131
|
+
STDOUT
|
|
132
|
+
api.get_config_vars("myapp").body["DATABASE_URL"].should == "postgres://ronin_database_url"
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
it "promotes the specified database url case-sensitively" do
|
|
136
|
+
stderr, stdout = execute("pg:promote postgres://john:S3nsit1ve@my.example.com/db_name --confirm=myapp")
|
|
137
|
+
stderr.should == ""
|
|
138
|
+
stdout.should == <<-STDOUT
|
|
139
|
+
Promoting Custom URL to DATABASE_URL... done
|
|
140
|
+
STDOUT
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
it "fails if no database is specified" do
|
|
144
|
+
stderr, stdout = execute("pg:promote")
|
|
145
|
+
stderr.should == <<-STDERR
|
|
146
|
+
! Usage: heroku pg:promote DATABASE
|
|
147
|
+
! Must specify DATABASE to promote.
|
|
148
|
+
STDERR
|
|
149
|
+
stdout.should == ""
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
context "credential resets" do
|
|
154
|
+
before do
|
|
155
|
+
api.put_config_vars "myapp", {
|
|
156
|
+
"DATABASE_URL" => "postgres:///to_reset_credentials",
|
|
157
|
+
"HEROKU_POSTGRESQL_RESETME_URL" => "postgres:///to_reset_credentials"
|
|
158
|
+
}
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
it "resets credentials and promotes to DATABASE_URL if it's the main DB" do
|
|
162
|
+
stub_pg.rotate_credentials
|
|
163
|
+
stderr, stdout = execute("pg:credentials resetme --reset")
|
|
164
|
+
stderr.should be_empty
|
|
165
|
+
stdout.should == <<-STDOUT
|
|
166
|
+
Resetting credentials for HEROKU_POSTGRESQL_RESETME (DATABASE_URL)... done
|
|
167
|
+
Promoting HEROKU_POSTGRESQL_RESETME (DATABASE_URL)... done
|
|
168
|
+
STDOUT
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
it "does not update DATABASE_URL if it's not the main db" do
|
|
172
|
+
stub_pg.rotate_credentials
|
|
173
|
+
api.put_config_vars "myapp", {
|
|
174
|
+
"DATABASE_URL" => "postgres://to_reset_credentials",
|
|
175
|
+
"HEROKU_POSTGRESQL_RESETME_URL" => "postgres://something_else"
|
|
176
|
+
}
|
|
177
|
+
stderr, stdout = execute("pg:credentials resetme --reset")
|
|
178
|
+
stderr.should be_empty
|
|
179
|
+
stdout.should_not include("Promoting")
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
context "unfollow" do
|
|
185
|
+
before do
|
|
186
|
+
api.put_config_vars "myapp", {
|
|
187
|
+
"DATABASE_URL" => "postgres://database_url",
|
|
188
|
+
"SHARED_DATABASE_URL" => "postgres://other_database_url",
|
|
189
|
+
"HEROKU_POSTGRESQL_RONIN_URL" => "postgres://ronin_database_url",
|
|
190
|
+
"HEROKU_POSTGRESQL_OTHER_URL" => "postgres://other_database_url"
|
|
191
|
+
}
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
it "sends request to unfollow" do
|
|
195
|
+
hpg_client = double('Heroku::Client::HerokuPostgresql')
|
|
196
|
+
Heroku::Client::HerokuPostgresql.should_receive(:new).twice.with('postgres://other_database_url').and_return(hpg_client)
|
|
197
|
+
hpg_client.should_receive(:unfollow)
|
|
198
|
+
hpg_client.should_receive(:get_database).and_return(
|
|
199
|
+
:following => 'postgresql://user:pass@roninhost/database',
|
|
200
|
+
:info => [
|
|
201
|
+
{"name"=>"Plan", "values"=>["Ronin"]},
|
|
202
|
+
{"name"=>"Status", "values"=>["available"]},
|
|
203
|
+
{"name"=>"Data Size", "values"=>["1 MB"]},
|
|
204
|
+
{"name"=>"Tables", "values"=>[1]},
|
|
205
|
+
{"name"=>"PG Version", "values"=>["9.1.4"]},
|
|
206
|
+
{"name"=>"Fork/Follow", "values"=>["Available"]},
|
|
207
|
+
{"name"=>"Created", "values"=>["2011-12-13 00:00 UTC"]},
|
|
208
|
+
{"name"=>"Conn Info", "values"=>["[Deprecated] Please use `heroku pg:credentials HEROKU_POSTGRESQL_RONIN` to view connection info"]},
|
|
209
|
+
{"name"=>"Maintenance", "values"=>["not required"]}
|
|
210
|
+
]
|
|
211
|
+
)
|
|
212
|
+
stderr, stdout = execute("pg:unfollow HEROKU_POSTGRESQL_OTHER --confirm myapp")
|
|
213
|
+
stderr.should == ""
|
|
214
|
+
stdout.should == <<-STDOUT
|
|
215
|
+
! HEROKU_POSTGRESQL_OTHER will become writable and no longer
|
|
216
|
+
! follow Database on roninhost:5432/database. This cannot be undone.
|
|
217
|
+
Unfollowing HEROKU_POSTGRESQL_OTHER... done
|
|
218
|
+
STDOUT
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
end
|
|
223
|
+
end
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
require "spec_helper"
|
|
2
|
+
require "heroku/command/pgbackups"
|
|
3
|
+
|
|
4
|
+
module Heroku::Command
|
|
5
|
+
describe Pgbackups do
|
|
6
|
+
before do
|
|
7
|
+
@pgbackups = prepare_command(Pgbackups)
|
|
8
|
+
@pgbackups.heroku.stub!(:info).and_return({})
|
|
9
|
+
|
|
10
|
+
api.post_app("name" => "myapp")
|
|
11
|
+
api.put_config_vars(
|
|
12
|
+
"myapp",
|
|
13
|
+
{
|
|
14
|
+
"DATABASE_URL" => "postgres://database",
|
|
15
|
+
"HEROKU_POSTGRESQL_IVORY" => "postgres://database",
|
|
16
|
+
"PGBACKUPS_URL" => "https://ip:password@pgbackups.heroku.com/client"
|
|
17
|
+
}
|
|
18
|
+
)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
after do
|
|
22
|
+
api.delete_app("myapp")
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
it "requests a pgbackups transfer list for the index command" do
|
|
26
|
+
stub_core
|
|
27
|
+
stub_pgbackups.get_transfers.returns([{
|
|
28
|
+
"created_at" => "2012-01-01 12:00:00 +0000",
|
|
29
|
+
"from_name" => "DATABASE",
|
|
30
|
+
"size" => "1024",
|
|
31
|
+
"to_name" => "BACKUP",
|
|
32
|
+
"to_url" => "s3://bucket/userid/b001.dump"
|
|
33
|
+
}])
|
|
34
|
+
|
|
35
|
+
stderr, stdout = execute("pgbackups")
|
|
36
|
+
stderr.should == ""
|
|
37
|
+
stdout.should == <<-STDOUT
|
|
38
|
+
ID Backup Time Size Database
|
|
39
|
+
---- ------------------------- ---- --------
|
|
40
|
+
b001 2012-01-01 12:00:00 +0000 1024 DATABASE
|
|
41
|
+
STDOUT
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
describe "single backup" do
|
|
45
|
+
it "gets the url for the latest backup if nothing is specified" do
|
|
46
|
+
stub_core
|
|
47
|
+
stub_pgbackups.get_latest_backup.returns({"public_url" => "http://latest/backup.dump"})
|
|
48
|
+
|
|
49
|
+
old_stdout_isatty = STDOUT.isatty
|
|
50
|
+
$stdout.stub!(:isatty).and_return(true)
|
|
51
|
+
stderr, stdout = execute("pgbackups:url")
|
|
52
|
+
stderr.should == ""
|
|
53
|
+
stdout.should == <<-STDOUT
|
|
54
|
+
http://latest/backup.dump
|
|
55
|
+
STDOUT
|
|
56
|
+
$stdout.stub!(:isatty).and_return(old_stdout_isatty)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
it "gets the url for the named backup if a name is specified" do
|
|
60
|
+
stub_pgbackups.get_backup.with("b001").returns({"public_url" => "http://latest/backup.dump" })
|
|
61
|
+
|
|
62
|
+
old_stdout_isatty = STDOUT.isatty
|
|
63
|
+
$stdout.stub!(:isatty).and_return(true)
|
|
64
|
+
stderr, stdout = execute("pgbackups:url b001")
|
|
65
|
+
stderr.should == ""
|
|
66
|
+
stdout.should == <<-STDOUT
|
|
67
|
+
http://latest/backup.dump
|
|
68
|
+
STDOUT
|
|
69
|
+
$stdout.stub!(:isatty).and_return(old_stdout_isatty)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
it "should capture a backup when requested" do
|
|
73
|
+
from_name, from_url = "FROM_NAME", "postgres://from/bar"
|
|
74
|
+
backup_obj = {'to_url' => "s3://bucket/userid/b001.dump"}
|
|
75
|
+
|
|
76
|
+
@pgbackups.stub!(:args).and_return([])
|
|
77
|
+
@pgbackups.stub!(:hpg_resolve).and_return([from_name, from_url])
|
|
78
|
+
@pgbackups.stub!(:transfer!).with(from_url, from_name, nil, "BACKUP", {:expire => nil}).and_return(backup_obj)
|
|
79
|
+
@pgbackups.stub!(:poll_transfer!).with(backup_obj).and_return(backup_obj)
|
|
80
|
+
|
|
81
|
+
@pgbackups.capture
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
it "should send expiration flag to client if specified on args" do
|
|
85
|
+
from_name, from_url = "FROM_NAME", "postgres://from/bar"
|
|
86
|
+
backup_obj = {'to_url' => "s3://bucket/userid/b001.dump"}
|
|
87
|
+
|
|
88
|
+
@pgbackups.stub!(:options).and_return({:expire => true})
|
|
89
|
+
@pgbackups.stub!(:hpg_resolve).and_return([from_name, from_url])
|
|
90
|
+
@pgbackups.stub!(:transfer!).with(from_url, from_name, nil, "BACKUP", {:expire => true}).and_return(backup_obj)
|
|
91
|
+
@pgbackups.stub!(:poll_transfer!).with(backup_obj).and_return(backup_obj)
|
|
92
|
+
|
|
93
|
+
@pgbackups.capture
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
it "destroys no backup without a name" do
|
|
97
|
+
stub_core
|
|
98
|
+
stderr, stdout = execute("pgbackups:destroy")
|
|
99
|
+
stderr.should == <<-STDERR
|
|
100
|
+
! Usage: heroku pgbackups:destroy BACKUP_ID
|
|
101
|
+
! Must specify BACKUP_ID to destroy.
|
|
102
|
+
STDERR
|
|
103
|
+
stdout.should == ""
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
it "destroys a backup" do
|
|
107
|
+
stub_core
|
|
108
|
+
stub_pgbackups.get_backup("b001").returns({})
|
|
109
|
+
stub_pgbackups.delete_backup("b001").returns({})
|
|
110
|
+
|
|
111
|
+
stderr, stdout = execute("pgbackups:destroy b001")
|
|
112
|
+
stderr.should == ""
|
|
113
|
+
stdout.should == <<-STDOUT
|
|
114
|
+
Destroying b001... done
|
|
115
|
+
STDOUT
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
it "aborts if no database addon is present" do
|
|
119
|
+
api.delete_config_var("myapp", "DATABASE_URL")
|
|
120
|
+
stub_core
|
|
121
|
+
stderr, stdout = execute("pgbackups:capture")
|
|
122
|
+
stderr.should == <<-STDERR
|
|
123
|
+
! Your app has no databases.
|
|
124
|
+
STDERR
|
|
125
|
+
stdout.should == ""
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
context "on errors" do
|
|
129
|
+
def stub_failed_capture(log)
|
|
130
|
+
@backup_obj = {
|
|
131
|
+
"error_at" => Time.now.to_s,
|
|
132
|
+
"finished_at" => Time.now.to_s,
|
|
133
|
+
"log" => log,
|
|
134
|
+
'to_url' => 'postgres://from/bar'
|
|
135
|
+
}
|
|
136
|
+
stub_core
|
|
137
|
+
stub_pgbackups.create_transfer.returns(@backup_obj)
|
|
138
|
+
stub_pgbackups.get_transfer.returns(@backup_obj)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
it 'aborts on a generic error' do
|
|
142
|
+
stub_failed_capture "something generic"
|
|
143
|
+
stderr, stdout = execute("pgbackups:capture")
|
|
144
|
+
stderr.should == <<-STDERR
|
|
145
|
+
! An error occurred and your backup did not finish.
|
|
146
|
+
STDERR
|
|
147
|
+
stdout.should == <<-STDOUT
|
|
148
|
+
|
|
149
|
+
HEROKU_POSTGRESQL_IVORY (DATABASE_URL) ----backup---> bar
|
|
150
|
+
|
|
151
|
+
\r\e[0K... 0 -
|
|
152
|
+
STDOUT
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
it 'aborts and informs when the database isnt up yet' do
|
|
156
|
+
stub_failed_capture 'could not translate host name "ec2-42-42-42-42.compute-1.amazonaws.com" to address: Name or service not known'
|
|
157
|
+
stderr, stdout = execute("pgbackups:capture")
|
|
158
|
+
stderr.should == <<-STDERR
|
|
159
|
+
! An error occurred and your backup did not finish.
|
|
160
|
+
! The database is not yet online. Please try again.
|
|
161
|
+
STDERR
|
|
162
|
+
stdout.should == <<-STDOUT
|
|
163
|
+
|
|
164
|
+
HEROKU_POSTGRESQL_IVORY (DATABASE_URL) ----backup---> bar
|
|
165
|
+
|
|
166
|
+
\r\e[0K... 0 -
|
|
167
|
+
STDOUT
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
it 'aborts and informs when the credentials are incorrect' do
|
|
171
|
+
stub_failed_capture 'psql: FATAL: database "randomname" does not exist'
|
|
172
|
+
stderr, stdout = execute("pgbackups:capture")
|
|
173
|
+
stderr.should == <<-STDERR
|
|
174
|
+
! An error occurred and your backup did not finish.
|
|
175
|
+
! The database credentials are incorrect.
|
|
176
|
+
STDERR
|
|
177
|
+
stdout.should == <<-STDOUT
|
|
178
|
+
|
|
179
|
+
HEROKU_POSTGRESQL_IVORY (DATABASE_URL) ----backup---> bar
|
|
180
|
+
|
|
181
|
+
\r\e[0K... 0 -
|
|
182
|
+
STDOUT
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
context "restore" do
|
|
188
|
+
before do
|
|
189
|
+
from_name, from_url = "FROM_NAME", "postgres://fromhost/database"
|
|
190
|
+
|
|
191
|
+
@pgbackups_client = mock("pgbackups_client")
|
|
192
|
+
@pgbackups.stub!(:pgbackup_client).and_return(@pgbackups_client)
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
it "should receive a confirm_command on restore" do
|
|
196
|
+
@pgbackups_client.stub!(:get_latest_backup).and_return({"to_url" => "s3://bucket/user/bXXX.dump"})
|
|
197
|
+
|
|
198
|
+
@pgbackups.should_receive(:confirm_command).and_return(false)
|
|
199
|
+
@pgbackups_client.should_not_receive(:transfer!)
|
|
200
|
+
|
|
201
|
+
@pgbackups.restore
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
it "aborts if no database addon is present" do
|
|
205
|
+
@pgbackups.should_receive(:hpg_resolve).and_raise(SystemExit)
|
|
206
|
+
lambda { @pgbackups.restore }.should raise_error(SystemExit)
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
context "for commands which perform restores" do
|
|
210
|
+
before do
|
|
211
|
+
@backup_obj = {
|
|
212
|
+
"to_name" => "TO_NAME",
|
|
213
|
+
"to_url" => "s3://bucket/userid/bXXX.dump",
|
|
214
|
+
"from_url" => "FROM_NAME",
|
|
215
|
+
"from_name" => "postgres://databasehost/dbname"
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
@pgbackups.stub!(:confirm_command).and_return(true)
|
|
219
|
+
@pgbackups_client.should_receive(:create_transfer).and_return(@backup_obj)
|
|
220
|
+
@pgbackups.stub!(:poll_transfer!).and_return(@backup_obj)
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
it "should default to the latest backup" do
|
|
224
|
+
@pgbackups.stub(:args).and_return([])
|
|
225
|
+
@pgbackups_client.should_receive(:get_latest_backup).and_return(@backup_obj)
|
|
226
|
+
@pgbackups.restore
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
it "should restore the named backup" do
|
|
230
|
+
name = "backupname"
|
|
231
|
+
args = ['DATABASE', name]
|
|
232
|
+
@pgbackups.stub(:args).and_return(args)
|
|
233
|
+
@pgbackups.stub(:shift_argument).and_return(*args)
|
|
234
|
+
@pgbackups.stub(:hpg_resolve).and_return([name])
|
|
235
|
+
@pgbackups_client.should_receive(:get_backup).with(name).and_return(@backup_obj)
|
|
236
|
+
@pgbackups.restore
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
it "should handle external restores" do
|
|
240
|
+
args = ['db_name_gets_shifted_out_in_resolve_db', 'http://external/file.dump']
|
|
241
|
+
@pgbackups.stub(:args).and_return(args)
|
|
242
|
+
@pgbackups.stub(:shift_argument).and_return(*args)
|
|
243
|
+
@pgbackups.stub(:hpg_resolve).and_return(["name", "url"])
|
|
244
|
+
@pgbackups_client.should_not_receive(:get_backup)
|
|
245
|
+
@pgbackups_client.should_not_receive(:get_latest_backup)
|
|
246
|
+
@pgbackups.restore
|
|
247
|
+
end
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
context "on errors" do
|
|
251
|
+
before(:each) do
|
|
252
|
+
@pgbackups_client.stub!(:get_latest_backup).and_return({"to_url" => "s3://bucket/user/bXXX.dump"})
|
|
253
|
+
@pgbackups.stub!(:confirm_command).and_return(true)
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
def stub_error_backup_with_log(log)
|
|
257
|
+
@backup_obj = {
|
|
258
|
+
"error_at" => Time.now.to_s,
|
|
259
|
+
"log" => log
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
@pgbackups_client.should_receive(:create_transfer).and_return(@backup_obj)
|
|
263
|
+
@pgbackups.stub!(:poll_transfer!).and_return(@backup_obj)
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
it 'aborts for a generic error' do
|
|
267
|
+
stub_error_backup_with_log 'something generic'
|
|
268
|
+
@pgbackups.should_receive(:error).with("An error occurred and your restore did not finish.")
|
|
269
|
+
@pgbackups.restore
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
it 'aborts and informs for expired s3 urls' do
|
|
273
|
+
stub_error_backup_with_log 'Invalid dump format: /tmp/aDMyoXPrAX/b031.dump: XML document text'
|
|
274
|
+
@pgbackups.should_receive(:error).with { |message| message.should =~ /backup url is invalid/ }
|
|
275
|
+
@pgbackups.restore
|
|
276
|
+
end
|
|
277
|
+
end
|
|
278
|
+
end
|
|
279
|
+
end
|
|
280
|
+
end
|