pogo 2.32.14 → 2.39.2
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +0 -2
- data/lib/heroku/auth.rb +3 -1
- data/lib/heroku/client.rb +8 -5
- data/lib/heroku/client/cisaurus.rb +25 -0
- data/lib/heroku/client/heroku_postgresql.rb +0 -3
- data/lib/heroku/client/rendezvous.rb +2 -1
- data/lib/heroku/command.rb +1 -1
- data/lib/heroku/command/addons.rb +11 -2
- data/lib/heroku/command/apps.rb +105 -20
- data/lib/heroku/command/base.rb +1 -1
- data/lib/heroku/command/certs.rb +95 -34
- data/lib/heroku/command/config.rb +5 -5
- data/lib/heroku/command/domains.rb +4 -4
- data/lib/heroku/command/fork.rb +160 -0
- data/lib/heroku/command/git.rb +19 -20
- data/lib/heroku/command/help.rb +18 -2
- data/lib/heroku/command/keys.rb +1 -1
- data/lib/heroku/command/labs.rb +1 -1
- data/lib/heroku/command/logs.rb +3 -56
- data/lib/heroku/command/maintenance.rb +2 -2
- data/lib/heroku/command/pg.rb +7 -16
- data/lib/heroku/command/pgbackups.rb +37 -17
- data/lib/heroku/command/ps.rb +82 -35
- data/lib/heroku/command/regions.rb +23 -0
- data/lib/heroku/command/releases.rb +3 -3
- data/lib/heroku/command/run.rb +40 -27
- data/lib/heroku/command/sharing.rb +4 -4
- data/lib/heroku/command/ssl.rb +7 -25
- data/lib/heroku/command/stack.rb +1 -1
- data/lib/heroku/helpers/heroku_postgresql.rb +13 -17
- data/lib/heroku/helpers/log_displayer.rb +70 -0
- data/lib/heroku/plugin.rb +3 -0
- data/lib/heroku/updater.rb +11 -2
- data/lib/heroku/version.rb +1 -1
- data/spec/heroku/auth_spec.rb +10 -0
- data/spec/heroku/client/ssl_endpoint_spec.rb +12 -12
- data/spec/heroku/client_spec.rb +100 -100
- data/spec/heroku/command/addons_spec.rb +63 -59
- data/spec/heroku/command/apps_spec.rb +68 -65
- data/spec/heroku/command/base_spec.rb +21 -21
- data/spec/heroku/command/certs_spec.rb +31 -31
- data/spec/heroku/command/config_spec.rb +18 -18
- data/spec/heroku/command/db_spec.rb +3 -3
- data/spec/heroku/command/domains_spec.rb +13 -13
- data/spec/heroku/command/drains_spec.rb +3 -3
- data/spec/heroku/command/fork_spec.rb +56 -0
- data/spec/heroku/command/git_spec.rb +57 -29
- data/spec/heroku/command/labs_spec.rb +8 -8
- data/spec/heroku/command/logs_spec.rb +3 -3
- data/spec/heroku/command/maintenance_spec.rb +5 -5
- data/spec/heroku/command/pg_spec.rb +11 -25
- data/spec/heroku/command/pgbackups_spec.rb +13 -8
- data/spec/heroku/command/ps_spec.rb +23 -23
- data/spec/heroku/command/releases_spec.rb +22 -24
- data/spec/heroku/command/run_spec.rb +12 -15
- data/spec/heroku/command/sharing_spec.rb +9 -9
- data/spec/heroku/command/stack_spec.rb +4 -4
- data/spec/heroku/command_spec.rb +12 -12
- data/spec/heroku/helpers/heroku_postgresql_spec.rb +35 -14
- data/spec/spec_helper.rb +17 -2
- data/spec/support/openssl_mock_helper.rb +1 -1
- metadata +11 -6
- data/spec/heroku/command/ssl_spec.rb +0 -32
@@ -13,7 +13,7 @@ module Heroku::Command
|
|
13
13
|
#Example:
|
14
14
|
#
|
15
15
|
# $ heroku sharing
|
16
|
-
# ===
|
16
|
+
# === example Collaborators
|
17
17
|
# collaborator@example.com
|
18
18
|
# email@example.com
|
19
19
|
#
|
@@ -33,7 +33,7 @@ module Heroku::Command
|
|
33
33
|
#Example:
|
34
34
|
#
|
35
35
|
# $ heroku sharing:add collaborator@example.com
|
36
|
-
# Adding collaborator@example.com to
|
36
|
+
# Adding collaborator@example.com to example collaborators... done
|
37
37
|
#
|
38
38
|
def add
|
39
39
|
unless email = shift_argument
|
@@ -53,7 +53,7 @@ module Heroku::Command
|
|
53
53
|
#Example:
|
54
54
|
#
|
55
55
|
# $ heroku sharing:remove collaborator@example.com
|
56
|
-
# Removing collaborator@example.com to
|
56
|
+
# Removing collaborator@example.com to example collaborators... done
|
57
57
|
#
|
58
58
|
def remove
|
59
59
|
unless email = shift_argument
|
@@ -73,7 +73,7 @@ module Heroku::Command
|
|
73
73
|
#Example:
|
74
74
|
#
|
75
75
|
# $ heroku sharing:transfer collaborator@example.com
|
76
|
-
# Transferring
|
76
|
+
# Transferring example to collaborator@example.com... done
|
77
77
|
#
|
78
78
|
def transfer
|
79
79
|
unless email = shift_argument
|
data/lib/heroku/command/ssl.rb
CHANGED
@@ -2,13 +2,15 @@ require "heroku/command/base"
|
|
2
2
|
|
3
3
|
module Heroku::Command
|
4
4
|
|
5
|
+
# DEPRECATED: see `heroku certs` instead
|
6
|
+
#
|
5
7
|
# manage ssl certificates for an app
|
6
8
|
#
|
7
9
|
class Ssl < Base
|
8
10
|
|
9
11
|
# ssl
|
10
12
|
#
|
11
|
-
# list certificates for an app
|
13
|
+
# list legacy certificates for an app
|
12
14
|
#
|
13
15
|
def index
|
14
16
|
api.get_domains(app).body.each do |domain|
|
@@ -22,36 +24,16 @@ module Heroku::Command
|
|
22
24
|
|
23
25
|
# ssl:add PEM KEY
|
24
26
|
#
|
25
|
-
#
|
27
|
+
# DEPRECATED: see `heroku certs:add` instead
|
26
28
|
#
|
27
29
|
def add
|
28
|
-
|
29
|
-
|
30
|
-
fail "Usage: heroku ssl:add PEM KEY" unless pem_file && key_file
|
31
|
-
raise CommandFailed, "Missing pem file." unless pem_file
|
32
|
-
raise CommandFailed, "Missing key file." unless key_file
|
33
|
-
raise CommandFailed, "Could not find pem in #{pem_file}" unless File.exists?(pem_file)
|
34
|
-
raise CommandFailed, "Could not find key in #{key_file}" unless File.exists?(key_file)
|
35
|
-
|
36
|
-
pem = File.read(pem_file)
|
37
|
-
key = File.read(key_file)
|
38
|
-
info = heroku.add_ssl(app, pem, key)
|
39
|
-
display "Added certificate to #{info['domain']}, expiring at #{info['expires_at']}"
|
40
|
-
end
|
41
|
-
|
42
|
-
# ssl:remove DOMAIN
|
43
|
-
#
|
44
|
-
# remove an ssl certificate from an app
|
45
|
-
#
|
46
|
-
def remove
|
47
|
-
raise CommandFailed, "Missing domain. Usage:\nheroku ssl:remove <domain>" unless domain = args.shift
|
48
|
-
heroku.remove_ssl(app, domain)
|
49
|
-
display "Removed certificate from #{domain}"
|
30
|
+
$stderr.puts " ! `heroku ssl:add` has been deprecated. Please use the SSL Endpoint add-on and the `heroku certs` commands instead."
|
31
|
+
$stderr.puts " ! SSL Endpoint documentation is available at: https://devcenter.heroku.com/articles/ssl-endpoint"
|
50
32
|
end
|
51
33
|
|
52
34
|
# ssl:clear
|
53
35
|
#
|
54
|
-
# remove
|
36
|
+
# remove legacy ssl certificates from an app
|
55
37
|
#
|
56
38
|
def clear
|
57
39
|
heroku.clear_ssl(app)
|
data/lib/heroku/command/stack.rb
CHANGED
@@ -61,17 +61,6 @@ module Heroku::Helpers::HerokuPostgresql
|
|
61
61
|
@hpg_databases['DATABASE_URL'] = find_database_url_real_attachment
|
62
62
|
end
|
63
63
|
|
64
|
-
if app_config_vars['SHARED_DATABASE_URL']
|
65
|
-
@hpg_databases['SHARED_DATABASE'] = Attachment.new({
|
66
|
-
'config_var' => 'SHARED_DATABASE',
|
67
|
-
'resource' => {
|
68
|
-
'name' => 'SHARED_DATABASE',
|
69
|
-
'value' => app_config_vars['SHARED_DATABASE_URL'],
|
70
|
-
'type' => 'shared:database'
|
71
|
-
}
|
72
|
-
})
|
73
|
-
end
|
74
|
-
|
75
64
|
return @hpg_databases
|
76
65
|
end
|
77
66
|
|
@@ -82,11 +71,15 @@ module Heroku::Helpers::HerokuPostgresql
|
|
82
71
|
def forget_config!
|
83
72
|
@hpg_databases = nil
|
84
73
|
@app_config_vars = nil
|
74
|
+
@app_attachments = nil
|
85
75
|
end
|
86
76
|
|
87
77
|
def find_database_url_real_attachment
|
88
|
-
|
89
|
-
return unless
|
78
|
+
raw_primary_db_url = app_config_vars['DATABASE_URL']
|
79
|
+
return unless raw_primary_db_url
|
80
|
+
|
81
|
+
primary_db_url = raw_primary_db_url.split("?").first
|
82
|
+
return unless primary_db_url && !primary_db_url.empty?
|
90
83
|
|
91
84
|
real_config = app_config_vars.detect {|k,v| k != 'DATABASE_URL' && v == primary_db_url }
|
92
85
|
if real_config
|
@@ -113,11 +106,11 @@ module Heroku::Helpers::HerokuPostgresql
|
|
113
106
|
end
|
114
107
|
|
115
108
|
found_attachment = nil
|
116
|
-
|
109
|
+
candidates = match_attachments_by_name(name)
|
117
110
|
if default && name.empty? && app_config_vars[default]
|
118
111
|
found_attachment = hpg_databases[default]
|
119
|
-
elsif
|
120
|
-
found_attachment = hpg_databases[
|
112
|
+
elsif candidates.size == 1
|
113
|
+
found_attachment = hpg_databases[candidates.first]
|
121
114
|
end
|
122
115
|
|
123
116
|
if found_attachment.nil?
|
@@ -132,7 +125,7 @@ module Heroku::Helpers::HerokuPostgresql
|
|
132
125
|
%w[fork follow].each do |opt|
|
133
126
|
if val = config[opt]
|
134
127
|
unless val.is_a?(String)
|
135
|
-
error("--#{opt} requires a database argument")
|
128
|
+
error("--#{opt} requires a database argument.")
|
136
129
|
end
|
137
130
|
|
138
131
|
uri = URI.parse(val) rescue nil
|
@@ -140,6 +133,9 @@ module Heroku::Helpers::HerokuPostgresql
|
|
140
133
|
argument_url = uri.to_s
|
141
134
|
else
|
142
135
|
attachment = hpg_resolve(val)
|
136
|
+
if attachment.starter_plan?
|
137
|
+
error("#{opt.tr 'f', 'F'} is only available on production databases.")
|
138
|
+
end
|
143
139
|
argument_url = attachment.url
|
144
140
|
end
|
145
141
|
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require "heroku/helpers"
|
2
|
+
|
3
|
+
module Heroku::Helpers
|
4
|
+
class LogDisplayer
|
5
|
+
|
6
|
+
include Heroku::Helpers
|
7
|
+
|
8
|
+
attr_reader :heroku, :app, :opts
|
9
|
+
|
10
|
+
def initialize(heroku, app, opts)
|
11
|
+
@heroku, @app, @opts = heroku, app, opts
|
12
|
+
end
|
13
|
+
|
14
|
+
def display_logs
|
15
|
+
@assigned_colors = {}
|
16
|
+
@line_start = true
|
17
|
+
@token = nil
|
18
|
+
|
19
|
+
heroku.read_logs(app, opts) do |chunk|
|
20
|
+
unless chunk.empty?
|
21
|
+
if STDOUT.isatty && ENV.has_key?("TERM")
|
22
|
+
display(colorize(chunk))
|
23
|
+
else
|
24
|
+
display(chunk)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
rescue Errno::EPIPE
|
29
|
+
rescue Interrupt => interrupt
|
30
|
+
if STDOUT.isatty && ENV.has_key?("TERM")
|
31
|
+
display("\e[0m")
|
32
|
+
end
|
33
|
+
raise(interrupt)
|
34
|
+
end
|
35
|
+
|
36
|
+
COLORS = %w( cyan yellow green magenta red )
|
37
|
+
COLOR_CODES = {
|
38
|
+
"red" => 31,
|
39
|
+
"green" => 32,
|
40
|
+
"yellow" => 33,
|
41
|
+
"magenta" => 35,
|
42
|
+
"cyan" => 36,
|
43
|
+
}
|
44
|
+
|
45
|
+
def colorize(chunk)
|
46
|
+
lines = []
|
47
|
+
chunk.split("\n").map do |line|
|
48
|
+
if parsed_line = parse_log(line)
|
49
|
+
header, identifier, body = parsed_line
|
50
|
+
@assigned_colors[identifier] ||= COLORS[@assigned_colors.size % COLORS.size]
|
51
|
+
lines << [
|
52
|
+
"\e[#{COLOR_CODES[@assigned_colors[identifier]]}m",
|
53
|
+
header,
|
54
|
+
"\e[0m",
|
55
|
+
body,
|
56
|
+
].join("")
|
57
|
+
elsif not line.empty?
|
58
|
+
lines << line
|
59
|
+
end
|
60
|
+
end
|
61
|
+
lines.join("\n")
|
62
|
+
end
|
63
|
+
|
64
|
+
def parse_log(log)
|
65
|
+
return unless parsed = log.match(/^(.*?\[(\w+)([\d\.]+)?\]:)(.*)?$/)
|
66
|
+
[1, 2, 4].map { |i| parsed[i] }
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
end
|
data/lib/heroku/plugin.rb
CHANGED
@@ -11,6 +11,7 @@ module Heroku
|
|
11
11
|
heroku-cedar
|
12
12
|
heroku-certs
|
13
13
|
heroku-credentials
|
14
|
+
heroku-dyno-size
|
14
15
|
heroku-kill
|
15
16
|
heroku-labs
|
16
17
|
heroku-logging
|
@@ -19,11 +20,13 @@ module Heroku
|
|
19
20
|
heroku-postgresql
|
20
21
|
heroku-releases
|
21
22
|
heroku-shared-postgresql
|
23
|
+
heroku-sql-console
|
22
24
|
heroku-status
|
23
25
|
heroku-stop
|
24
26
|
heroku-suggest
|
25
27
|
pgbackups-automate
|
26
28
|
pgcmd
|
29
|
+
heroku-fork
|
27
30
|
)
|
28
31
|
|
29
32
|
attr_reader :name, :uri
|
data/lib/heroku/updater.rb
CHANGED
@@ -1,10 +1,14 @@
|
|
1
|
+
require "digest"
|
1
2
|
require "fileutils"
|
2
|
-
|
3
|
-
require 'heroku/helpers'
|
3
|
+
require "heroku/helpers"
|
4
4
|
|
5
5
|
module Heroku
|
6
6
|
module Updater
|
7
7
|
|
8
|
+
def self.error(message)
|
9
|
+
raise Heroku::Command::CommandFailed.new(message)
|
10
|
+
end
|
11
|
+
|
8
12
|
def self.updating_lock_path
|
9
13
|
File.join(Heroku::Helpers.home_directory, ".heroku", "updating")
|
10
14
|
end
|
@@ -84,6 +88,11 @@ module Heroku
|
|
84
88
|
file.print Excon.get_with_redirect(url, :nonblock => false).body
|
85
89
|
end
|
86
90
|
|
91
|
+
hash = Digest::SHA256.file("#{download_dir}/heroku.zip").hexdigest
|
92
|
+
official_hash = Excon.get_with_redirect("https://toolbelt.heroku.com/update/hash", :nonblock => false).body.chomp
|
93
|
+
|
94
|
+
error "Update hash signature mismatch" unless hash == official_hash
|
95
|
+
|
87
96
|
Zip::ZipFile.open("#{download_dir}/heroku.zip") do |zip|
|
88
97
|
zip.each do |entry|
|
89
98
|
target = File.join(download_dir, entry.to_s)
|
data/lib/heroku/version.rb
CHANGED
data/spec/heroku/auth_spec.rb
CHANGED
@@ -109,6 +109,16 @@ module Heroku
|
|
109
109
|
end
|
110
110
|
end
|
111
111
|
|
112
|
+
describe "#base_host" do
|
113
|
+
it "returns the host without the first part" do
|
114
|
+
@cli.base_host("http://foo.bar.com").should == "bar.com"
|
115
|
+
end
|
116
|
+
|
117
|
+
it "works with localhost" do
|
118
|
+
@cli.base_host("http://localhost:3000").should == "localhost"
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
112
122
|
it "asks for credentials when the file doesn't exist" do
|
113
123
|
@cli.delete_credentials
|
114
124
|
@cli.should_receive(:ask_for_credentials).and_return(["u", "p"])
|
@@ -7,42 +7,42 @@ describe Heroku::Client, "ssl endpoints" do
|
|
7
7
|
end
|
8
8
|
|
9
9
|
it "adds an ssl endpoint" do
|
10
|
-
stub_request(:post, "https://api.heroku.com/apps/
|
10
|
+
stub_request(:post, "https://api.heroku.com/apps/example/ssl-endpoints").
|
11
11
|
with(:body => { :accept => "json", :pem => "pem content", :key => "key content" }).
|
12
12
|
to_return(:body => %{ {"cname": "tokyo-1050" } })
|
13
|
-
@client.ssl_endpoint_add("
|
13
|
+
@client.ssl_endpoint_add("example", "pem content", "key content").should == { "cname" => "tokyo-1050" }
|
14
14
|
end
|
15
15
|
|
16
16
|
it "gets info on an ssl endpoint" do
|
17
|
-
stub_request(:get, "https://api.heroku.com/apps/
|
17
|
+
stub_request(:get, "https://api.heroku.com/apps/example/ssl-endpoints/tokyo-1050").
|
18
18
|
to_return(:body => %{ {"cname": "tokyo-1050" } })
|
19
|
-
@client.ssl_endpoint_info("
|
19
|
+
@client.ssl_endpoint_info("example", "tokyo-1050").should == { "cname" => "tokyo-1050" }
|
20
20
|
end
|
21
21
|
|
22
22
|
it "lists ssl endpoints for an app" do
|
23
|
-
stub_request(:get, "https://api.heroku.com/apps/
|
23
|
+
stub_request(:get, "https://api.heroku.com/apps/example/ssl-endpoints").
|
24
24
|
to_return(:body => %{ [{"cname": "tokyo-1050" }, {"cname": "tokyo-1051" }] })
|
25
|
-
@client.ssl_endpoint_list("
|
25
|
+
@client.ssl_endpoint_list("example").should == [
|
26
26
|
{ "cname" => "tokyo-1050" },
|
27
27
|
{ "cname" => "tokyo-1051" },
|
28
28
|
]
|
29
29
|
end
|
30
30
|
|
31
31
|
it "removes an ssl endpoint" do
|
32
|
-
stub_request(:delete, "https://api.heroku.com/apps/
|
33
|
-
@client.ssl_endpoint_remove("
|
32
|
+
stub_request(:delete, "https://api.heroku.com/apps/example/ssl-endpoints/tokyo-1050")
|
33
|
+
@client.ssl_endpoint_remove("example", "tokyo-1050")
|
34
34
|
end
|
35
35
|
|
36
36
|
it "rolls back an ssl endpoint" do
|
37
|
-
stub_request(:post, "https://api.heroku.com/apps/
|
37
|
+
stub_request(:post, "https://api.heroku.com/apps/example/ssl-endpoints/tokyo-1050/rollback").
|
38
38
|
to_return(:body => %{ {"cname": "tokyo-1050" } })
|
39
|
-
@client.ssl_endpoint_rollback("
|
39
|
+
@client.ssl_endpoint_rollback("example", "tokyo-1050").should == { "cname" => "tokyo-1050" }
|
40
40
|
end
|
41
41
|
|
42
42
|
it "updates an ssl endpoint" do
|
43
|
-
stub_request(:put, "https://api.heroku.com/apps/
|
43
|
+
stub_request(:put, "https://api.heroku.com/apps/example/ssl-endpoints/tokyo-1050").
|
44
44
|
with(:body => { :accept => "json", :pem => "pem content", :key => "key content" }).
|
45
45
|
to_return(:body => %{ {"cname": "tokyo-1050" } })
|
46
|
-
@client.ssl_endpoint_update("
|
46
|
+
@client.ssl_endpoint_update("example", "tokyo-1050", "pem content", "key content").should == { "cname" => "tokyo-1050" }
|
47
47
|
end
|
48
48
|
end
|
data/spec/heroku/client_spec.rb
CHANGED
@@ -24,26 +24,26 @@ describe Heroku::Client do
|
|
24
24
|
stub_api_request(:get, "/apps").to_return(:body => <<-EOXML)
|
25
25
|
<?xml version='1.0' encoding='UTF-8'?>
|
26
26
|
<apps type="array">
|
27
|
-
<app><name>
|
28
|
-
<app><name>
|
27
|
+
<app><name>example</name><owner>test@heroku.com</owner></app>
|
28
|
+
<app><name>example2</name><owner>test@heroku.com</owner></app>
|
29
29
|
</apps>
|
30
30
|
EOXML
|
31
31
|
capture_stderr do # capture deprecation message
|
32
32
|
@client.list.should == [
|
33
|
-
["
|
34
|
-
["
|
33
|
+
["example", "test@heroku.com"],
|
34
|
+
["example2", "test@heroku.com"]
|
35
35
|
]
|
36
36
|
end
|
37
37
|
end
|
38
38
|
|
39
39
|
it "info -> get app attributes" do
|
40
|
-
stub_api_request(:get, "/apps/
|
40
|
+
stub_api_request(:get, "/apps/example").to_return(:body => <<-EOXML)
|
41
41
|
<?xml version='1.0' encoding='UTF-8'?>
|
42
42
|
<app>
|
43
43
|
<blessed type='boolean'>true</blessed>
|
44
44
|
<created-at type='datetime'>2008-07-08T17:21:50-07:00</created-at>
|
45
45
|
<id type='integer'>49134</id>
|
46
|
-
<name>
|
46
|
+
<name>example</name>
|
47
47
|
<production type='boolean'>true</production>
|
48
48
|
<share-public type='boolean'>true</share-public>
|
49
49
|
<domain_name/>
|
@@ -52,7 +52,7 @@ describe Heroku::Client do
|
|
52
52
|
@client.stub!(:list_collaborators).and_return([:jon, :mike])
|
53
53
|
@client.stub!(:installed_addons).and_return([:addon1])
|
54
54
|
capture_stderr do # capture deprecation message
|
55
|
-
@client.info('
|
55
|
+
@client.info('example').should == { :blessed => 'true', :created_at => '2008-07-08T17:21:50-07:00', :id => '49134', :name => 'example', :production => 'true', :share_public => 'true', :domain_name => nil, :collaborators => [:jon, :mike], :addons => [:addon1] }
|
56
56
|
end
|
57
57
|
end
|
58
58
|
|
@@ -82,14 +82,14 @@ describe Heroku::Client do
|
|
82
82
|
@client.should_receive(:resource).and_return(@resource)
|
83
83
|
@resource.should_receive(:put).with({}, @client.heroku_headers).and_return(@response)
|
84
84
|
capture_stderr do # capture deprecation message
|
85
|
-
@client.create_complete?('
|
85
|
+
@client.create_complete?('example').should be_false
|
86
86
|
end
|
87
87
|
end
|
88
88
|
|
89
89
|
it "update(name, attributes) -> updates existing apps" do
|
90
|
-
stub_api_request(:put, "/apps/
|
90
|
+
stub_api_request(:put, "/apps/example").with(:body => "app[mode]=production")
|
91
91
|
capture_stderr do # capture deprecation message
|
92
|
-
@client.update("
|
92
|
+
@client.update("example", :mode => 'production')
|
93
93
|
end
|
94
94
|
end
|
95
95
|
|
@@ -101,63 +101,63 @@ describe Heroku::Client do
|
|
101
101
|
end
|
102
102
|
|
103
103
|
it "rake(app_name, cmd) -> run a rake command on the app" do
|
104
|
-
stub_api_request(:post, "/apps/
|
104
|
+
stub_api_request(:post, "/apps/example/services").with(:body => "rake db:migrate").to_return(:body => "foo")
|
105
105
|
stub_api_request(:get, "/foo").to_return(:body => "output")
|
106
106
|
capture_stderr do # capture deprecation message
|
107
|
-
@client.rake('
|
107
|
+
@client.rake('example', 'db:migrate')
|
108
108
|
end
|
109
109
|
end
|
110
110
|
|
111
111
|
it "console(app_name, cmd) -> run a console command on the app" do
|
112
|
-
stub_api_request(:post, "/apps/
|
113
|
-
@client.console('
|
112
|
+
stub_api_request(:post, "/apps/example/console").with(:body => "command=2%2B2")
|
113
|
+
@client.console('example', '2+2')
|
114
114
|
end
|
115
115
|
|
116
116
|
it "console(app_name) { |c| } -> opens a console session, yields one accessor and closes it after the block" do
|
117
|
-
stub_api_request(:post, "/apps/
|
118
|
-
stub_api_request(:post, "/apps/
|
119
|
-
stub_api_request(:delete, "/apps/
|
117
|
+
stub_api_request(:post, "/apps/example/consoles").to_return(:body => "consolename")
|
118
|
+
stub_api_request(:post, "/apps/example/consoles/consolename/command").with(:body => "command=1%2B1").to_return(:body => "2")
|
119
|
+
stub_api_request(:delete, "/apps/example/consoles/consolename")
|
120
120
|
|
121
|
-
@client.console('
|
121
|
+
@client.console('example') do |c|
|
122
122
|
c.run("1+1").should == '=> 2'
|
123
123
|
end
|
124
124
|
end
|
125
125
|
|
126
126
|
it "shows an error message when a console request fails" do
|
127
|
-
stub_request(:post, %r{.*/apps/
|
127
|
+
stub_request(:post, %r{.*/apps/example/console}).to_return({
|
128
128
|
:body => "ERRMSG", :status => 502
|
129
129
|
})
|
130
|
-
lambda { @client.console('
|
130
|
+
lambda { @client.console('example') }.should raise_error(Heroku::Client::AppCrashed, /Your application may have crashed/)
|
131
131
|
end
|
132
132
|
|
133
133
|
it "restart(app_name) -> restarts the app servers" do
|
134
|
-
stub_api_request(:delete, "/apps/
|
134
|
+
stub_api_request(:delete, "/apps/example/server")
|
135
135
|
capture_stderr do # capture deprecation message
|
136
|
-
@client.restart('
|
136
|
+
@client.restart('example')
|
137
137
|
end
|
138
138
|
end
|
139
139
|
|
140
140
|
describe "read_logs" do
|
141
141
|
describe "old style" do
|
142
142
|
before(:each) do
|
143
|
-
stub_api_request(:get, "/apps/
|
144
|
-
stub_api_request(:get, "/apps/
|
143
|
+
stub_api_request(:get, "/apps/example/logs?logplex=true").to_return(:body => "Use old logs")
|
144
|
+
stub_api_request(:get, "/apps/example/logs").to_return(:body => "oldlogs")
|
145
145
|
end
|
146
146
|
|
147
147
|
it "can read old style logs" do
|
148
148
|
@client.should_receive(:puts).with("oldlogs")
|
149
|
-
@client.read_logs("
|
149
|
+
@client.read_logs("example")
|
150
150
|
end
|
151
151
|
end
|
152
152
|
|
153
153
|
describe "new style" do
|
154
154
|
before(:each) do
|
155
|
-
stub_api_request(:get, "/apps/
|
155
|
+
stub_api_request(:get, "/apps/example/logs?logplex=true").to_return(:body => "https://logplex.heroku.com/identifier")
|
156
156
|
stub_request(:get, "https://logplex.heroku.com/identifier").to_return(:body => "newlogs")
|
157
157
|
end
|
158
158
|
|
159
159
|
it "can read new style logs" do
|
160
|
-
@client.read_logs("
|
160
|
+
@client.read_logs("example") do |logs|
|
161
161
|
logs.should == "newlogs"
|
162
162
|
end
|
163
163
|
end
|
@@ -165,40 +165,40 @@ describe Heroku::Client do
|
|
165
165
|
end
|
166
166
|
|
167
167
|
it "logs(app_name) -> returns recent output of the app logs" do
|
168
|
-
stub_api_request(:get, "/apps/
|
168
|
+
stub_api_request(:get, "/apps/example/logs").to_return(:body => "log")
|
169
169
|
capture_stderr do # capture deprecation message
|
170
|
-
@client.logs('
|
170
|
+
@client.logs('example').should == 'log'
|
171
171
|
end
|
172
172
|
end
|
173
173
|
|
174
174
|
it "can get the number of dynos" do
|
175
|
-
stub_api_request(:get, "/apps/
|
175
|
+
stub_api_request(:get, "/apps/example").to_return(:body => <<-EOXML)
|
176
176
|
<?xml version='1.0' encoding='UTF-8'?>
|
177
177
|
<app>
|
178
178
|
<dynos type='integer'>5</dynos>
|
179
179
|
</app>
|
180
180
|
EOXML
|
181
181
|
capture_stderr do # capture deprecation message
|
182
|
-
@client.dynos('
|
182
|
+
@client.dynos('example').should == 5
|
183
183
|
end
|
184
184
|
end
|
185
185
|
|
186
186
|
it "can get the number of workers" do
|
187
|
-
stub_api_request(:get, "/apps/
|
187
|
+
stub_api_request(:get, "/apps/example").to_return(:body => <<-EOXML)
|
188
188
|
<?xml version='1.0' encoding='UTF-8'?>
|
189
189
|
<app>
|
190
190
|
<workers type='integer'>5</workers>
|
191
191
|
</app>
|
192
192
|
EOXML
|
193
193
|
capture_stderr do # capture deprecation message
|
194
|
-
@client.workers('
|
194
|
+
@client.workers('example').should == 5
|
195
195
|
end
|
196
196
|
end
|
197
197
|
|
198
198
|
it "set_dynos(app_name, qty) -> scales the app" do
|
199
|
-
stub_api_request(:put, "/apps/
|
199
|
+
stub_api_request(:put, "/apps/example/dynos").with(:body => "dynos=3")
|
200
200
|
capture_stderr do # capture deprecation message
|
201
|
-
@client.set_dynos('
|
201
|
+
@client.set_dynos('example', 3)
|
202
202
|
end
|
203
203
|
end
|
204
204
|
|
@@ -208,7 +208,7 @@ describe Heroku::Client do
|
|
208
208
|
e.stub!(:http_body).and_return('the crashlog')
|
209
209
|
@client.should_receive(:post).and_raise(e)
|
210
210
|
capture_stderr do # capture deprecation message
|
211
|
-
lambda { @client.rake('
|
211
|
+
lambda { @client.rake('example', '') }.should raise_error(Heroku::Client::AppCrashed)
|
212
212
|
end
|
213
213
|
end
|
214
214
|
|
@@ -218,22 +218,22 @@ describe Heroku::Client do
|
|
218
218
|
e.stub!(:http_body).and_return('not a crashlog')
|
219
219
|
@client.should_receive(:post).and_raise(e)
|
220
220
|
capture_stderr do # capture deprecation message
|
221
|
-
lambda { @client.rake('
|
221
|
+
lambda { @client.rake('example', '') }.should raise_error(RestClient::RequestFailed)
|
222
222
|
end
|
223
223
|
end
|
224
224
|
|
225
225
|
describe "ps_scale" do
|
226
226
|
it "scales a process and returns the new count" do
|
227
|
-
stub_api_request(:post, "/apps/
|
227
|
+
stub_api_request(:post, "/apps/example/ps/scale").with(:body => { :type => "web", :qty => "5" }).to_return(:body => "5")
|
228
228
|
capture_stderr do # capture deprecation message
|
229
|
-
@client.ps_scale("
|
229
|
+
@client.ps_scale("example", :type => "web", :qty => "5").should == 5
|
230
230
|
end
|
231
231
|
end
|
232
232
|
end
|
233
233
|
|
234
234
|
describe "collaborators" do
|
235
235
|
it "list(app_name) -> list app collaborators" do
|
236
|
-
stub_api_request(:get, "/apps/
|
236
|
+
stub_api_request(:get, "/apps/example/collaborators").to_return(:body => <<-EOXML)
|
237
237
|
<?xml version="1.0" encoding="UTF-8"?>
|
238
238
|
<collaborators type="array">
|
239
239
|
<collaborator><email>joe@example.com</email></collaborator>
|
@@ -241,7 +241,7 @@ describe Heroku::Client do
|
|
241
241
|
</collaborators>
|
242
242
|
EOXML
|
243
243
|
capture_stderr do # capture deprecation message
|
244
|
-
@client.list_collaborators('
|
244
|
+
@client.list_collaborators('example').should == [
|
245
245
|
{ :email => 'joe@example.com' },
|
246
246
|
{ :email => 'jon@example.com' }
|
247
247
|
]
|
@@ -249,23 +249,23 @@ describe Heroku::Client do
|
|
249
249
|
end
|
250
250
|
|
251
251
|
it "add_collaborator(app_name, email) -> adds collaborator to app" do
|
252
|
-
stub_api_request(:post, "/apps/
|
252
|
+
stub_api_request(:post, "/apps/example/collaborators").with(:body => "collaborator%5Bemail%5D=joe%40example.com")
|
253
253
|
capture_stderr do # capture deprecation message
|
254
|
-
@client.add_collaborator('
|
254
|
+
@client.add_collaborator('example', 'joe@example.com')
|
255
255
|
end
|
256
256
|
end
|
257
257
|
|
258
258
|
it "remove_collaborator(app_name, email) -> removes collaborator from app" do
|
259
|
-
stub_api_request(:delete, "/apps/
|
259
|
+
stub_api_request(:delete, "/apps/example/collaborators/joe%40example%2Ecom")
|
260
260
|
capture_stderr do # capture deprecation message
|
261
|
-
@client.remove_collaborator('
|
261
|
+
@client.remove_collaborator('example', 'joe@example.com')
|
262
262
|
end
|
263
263
|
end
|
264
264
|
end
|
265
265
|
|
266
266
|
describe "domain names" do
|
267
267
|
it "list(app_name) -> list app domain names" do
|
268
|
-
stub_api_request(:get, "/apps/
|
268
|
+
stub_api_request(:get, "/apps/example/domains").to_return(:body => <<-EOXML)
|
269
269
|
<?xml version="1.0" encoding="UTF-8"?>
|
270
270
|
<domain-names type="array">
|
271
271
|
<domain-name><domain>example1.com</domain></domain-name>
|
@@ -273,51 +273,51 @@ describe Heroku::Client do
|
|
273
273
|
</domain-names>
|
274
274
|
EOXML
|
275
275
|
capture_stderr do # capture deprecation message
|
276
|
-
@client.list_domains('
|
276
|
+
@client.list_domains('example').should == [{:domain => 'example1.com'}, {:domain => 'example2.com'}]
|
277
277
|
end
|
278
278
|
end
|
279
279
|
|
280
280
|
it "add_domain(app_name, domain) -> adds domain name to app" do
|
281
|
-
stub_api_request(:post, "/apps/
|
281
|
+
stub_api_request(:post, "/apps/example/domains").with(:body => "example.com")
|
282
282
|
capture_stderr do # capture deprecation message
|
283
|
-
@client.add_domain('
|
283
|
+
@client.add_domain('example', 'example.com')
|
284
284
|
end
|
285
285
|
end
|
286
286
|
|
287
287
|
it "remove_domain(app_name, domain) -> removes domain name from app" do
|
288
|
-
stub_api_request(:delete, "/apps/
|
288
|
+
stub_api_request(:delete, "/apps/example/domains/example.com")
|
289
289
|
capture_stderr do # capture deprecation message
|
290
|
-
@client.remove_domain('
|
290
|
+
@client.remove_domain('example', 'example.com')
|
291
291
|
end
|
292
292
|
end
|
293
293
|
|
294
294
|
it "remove_domain(app_name, domain) -> makes sure a domain is set" do
|
295
295
|
lambda do
|
296
296
|
capture_stderr do # capture deprecation message
|
297
|
-
@client.remove_domain('
|
297
|
+
@client.remove_domain('example', '')
|
298
298
|
end
|
299
299
|
end.should raise_error(ArgumentError)
|
300
300
|
end
|
301
301
|
|
302
302
|
it "remove_domains(app_name) -> removes all domain names from app" do
|
303
|
-
stub_api_request(:delete, "/apps/
|
303
|
+
stub_api_request(:delete, "/apps/example/domains")
|
304
304
|
capture_stderr do # capture deprecation message
|
305
|
-
@client.remove_domains('
|
305
|
+
@client.remove_domains('example')
|
306
306
|
end
|
307
307
|
end
|
308
308
|
|
309
309
|
it "add_ssl(app_name, pem, key) -> adds a ssl cert to the domain" do
|
310
|
-
stub_api_request(:post, "/apps/
|
310
|
+
stub_api_request(:post, "/apps/example/ssl").with do |request|
|
311
311
|
body = CGI::parse(request.body)
|
312
312
|
body["key"].first.should == "thekey"
|
313
313
|
body["pem"].first.should == "thepem"
|
314
314
|
end.to_return(:body => "{}")
|
315
|
-
@client.add_ssl('
|
315
|
+
@client.add_ssl('example', 'thepem', 'thekey')
|
316
316
|
end
|
317
317
|
|
318
318
|
it "remove_ssl(app_name, domain) -> removes the ssl cert for the domain" do
|
319
|
-
stub_api_request(:delete, "/apps/
|
320
|
-
@client.remove_ssl('
|
319
|
+
stub_api_request(:delete, "/apps/example/domains/example.com/ssl")
|
320
|
+
@client.remove_ssl('example', 'example.com')
|
321
321
|
end
|
322
322
|
end
|
323
323
|
|
@@ -364,63 +364,63 @@ describe Heroku::Client do
|
|
364
364
|
end
|
365
365
|
end
|
366
366
|
|
367
|
-
stub_api_request(:post, "/apps/
|
368
|
-
@client.database_session('
|
367
|
+
stub_api_request(:post, "/apps/example/database/session2").to_return(:body => "{\"session_id\":\"x234\"}")
|
368
|
+
@client.database_session('example')
|
369
369
|
end
|
370
370
|
|
371
371
|
it "database_reset(app_name) -> reset an app's database" do
|
372
|
-
stub_api_request(:post, "/apps/
|
373
|
-
@client.database_reset('
|
372
|
+
stub_api_request(:post, "/apps/example/database/reset")
|
373
|
+
@client.database_reset('example')
|
374
374
|
end
|
375
375
|
|
376
376
|
it "maintenance(app_name, :on) -> sets maintenance mode for an app" do
|
377
|
-
stub_api_request(:post, "/apps/
|
377
|
+
stub_api_request(:post, "/apps/example/server/maintenance").with(:body => "maintenance_mode=1")
|
378
378
|
capture_stderr do # capture deprecation message
|
379
|
-
@client.maintenance('
|
379
|
+
@client.maintenance('example', :on)
|
380
380
|
end
|
381
381
|
end
|
382
382
|
|
383
383
|
it "maintenance(app_name, :off) -> turns off maintenance mode for an app" do
|
384
|
-
stub_api_request(:post, "/apps/
|
384
|
+
stub_api_request(:post, "/apps/example/server/maintenance").with(:body => "maintenance_mode=0")
|
385
385
|
capture_stderr do # capture deprecation message
|
386
|
-
@client.maintenance('
|
386
|
+
@client.maintenance('example', :off)
|
387
387
|
end
|
388
388
|
end
|
389
389
|
end
|
390
390
|
|
391
391
|
describe "config vars" do
|
392
392
|
it "config_vars(app_name) -> json hash of config vars for the app" do
|
393
|
-
stub_api_request(:get, "/apps/
|
393
|
+
stub_api_request(:get, "/apps/example/config_vars").to_return(:body => '{"A":"one", "B":"two"}')
|
394
394
|
capture_stderr do # capture deprecation message
|
395
|
-
@client.config_vars('
|
395
|
+
@client.config_vars('example').should == { 'A' => 'one', 'B' => 'two'}
|
396
396
|
end
|
397
397
|
end
|
398
398
|
|
399
399
|
it "add_config_vars(app_name, vars)" do
|
400
|
-
stub_api_request(:put, "/apps/
|
400
|
+
stub_api_request(:put, "/apps/example/config_vars").with(:body => '{"x":"y"}')
|
401
401
|
capture_stderr do # capture deprecation message
|
402
|
-
@client.add_config_vars('
|
402
|
+
@client.add_config_vars('example', {'x'=> 'y'})
|
403
403
|
end
|
404
404
|
end
|
405
405
|
|
406
406
|
it "remove_config_var(app_name, key)" do
|
407
|
-
stub_api_request(:delete, "/apps/
|
407
|
+
stub_api_request(:delete, "/apps/example/config_vars/mykey")
|
408
408
|
capture_stderr do # capture deprecation message
|
409
|
-
@client.remove_config_var('
|
409
|
+
@client.remove_config_var('example', 'mykey')
|
410
410
|
end
|
411
411
|
end
|
412
412
|
|
413
413
|
it "clear_config_vars(app_name) -> resets all config vars for this app" do
|
414
|
-
stub_api_request(:delete, "/apps/
|
414
|
+
stub_api_request(:delete, "/apps/example/config_vars")
|
415
415
|
capture_stderr do # capture deprecation message
|
416
|
-
@client.clear_config_vars('
|
416
|
+
@client.clear_config_vars('example')
|
417
417
|
end
|
418
418
|
end
|
419
419
|
|
420
420
|
it "can handle config vars with special characters" do
|
421
|
-
stub_api_request(:delete, "/apps/
|
421
|
+
stub_api_request(:delete, "/apps/example/config_vars/foo%5Bbar%5D")
|
422
422
|
capture_stderr do # capture deprecation message
|
423
|
-
lambda { @client.remove_config_var('
|
423
|
+
lambda { @client.remove_config_var('example', 'foo[bar]') }.should_not raise_error
|
424
424
|
end
|
425
425
|
end
|
426
426
|
end
|
@@ -432,68 +432,68 @@ describe Heroku::Client do
|
|
432
432
|
end
|
433
433
|
|
434
434
|
it "installed_addons(app_name) -> array of installed addons" do
|
435
|
-
stub_api_request(:get, "/apps/
|
436
|
-
@client.installed_addons('
|
435
|
+
stub_api_request(:get, "/apps/example/addons").to_return(:body => '[{"name":"addon1"}]')
|
436
|
+
@client.installed_addons('example').should == [{'name' => 'addon1'}]
|
437
437
|
end
|
438
438
|
|
439
439
|
it "install_addon(app_name, addon_name)" do
|
440
|
-
stub_api_request(:post, "/apps/
|
441
|
-
@client.install_addon('
|
440
|
+
stub_api_request(:post, "/apps/example/addons/addon1")
|
441
|
+
@client.install_addon('example', 'addon1').should be_nil
|
442
442
|
end
|
443
443
|
|
444
444
|
it "upgrade_addon(app_name, addon_name)" do
|
445
|
-
stub_api_request(:put, "/apps/
|
446
|
-
@client.upgrade_addon('
|
445
|
+
stub_api_request(:put, "/apps/example/addons/addon1")
|
446
|
+
@client.upgrade_addon('example', 'addon1').should be_nil
|
447
447
|
end
|
448
448
|
|
449
449
|
it "downgrade_addon(app_name, addon_name)" do
|
450
|
-
stub_api_request(:put, "/apps/
|
451
|
-
@client.downgrade_addon('
|
450
|
+
stub_api_request(:put, "/apps/example/addons/addon1")
|
451
|
+
@client.downgrade_addon('example', 'addon1').should be_nil
|
452
452
|
end
|
453
453
|
|
454
454
|
it "uninstall_addon(app_name, addon_name)" do
|
455
|
-
stub_api_request(:delete, "/apps/
|
455
|
+
stub_api_request(:delete, "/apps/example/addons/addon1?").
|
456
456
|
to_return(:body => json_encode({"message" => nil, "price" => "free", "status" => "uninstalled"}))
|
457
457
|
|
458
|
-
@client.uninstall_addon('
|
458
|
+
@client.uninstall_addon('example', 'addon1').should be_true
|
459
459
|
end
|
460
460
|
|
461
461
|
it "uninstall_addon(app_name, addon_name) with confirmation" do
|
462
|
-
stub_api_request(:delete, "/apps/
|
462
|
+
stub_api_request(:delete, "/apps/example/addons/addon1?confirm=example").
|
463
463
|
to_return(:body => json_encode({"message" => nil, "price" => "free", "status" => "uninstalled"}))
|
464
464
|
|
465
|
-
@client.uninstall_addon('
|
465
|
+
@client.uninstall_addon('example', 'addon1', :confirm => "example").should be_true
|
466
466
|
end
|
467
467
|
|
468
468
|
it "install_addon(app_name, addon_name) with response" do
|
469
|
-
stub_request(:post, "https://api.heroku.com/apps/
|
469
|
+
stub_request(:post, "https://api.heroku.com/apps/example/addons/addon1").
|
470
470
|
to_return(:body => json_encode({'price' => 'free', 'message' => "Don't Panic"}))
|
471
471
|
|
472
|
-
@client.install_addon('
|
472
|
+
@client.install_addon('example', 'addon1').
|
473
473
|
should == { 'price' => 'free', 'message' => "Don't Panic" }
|
474
474
|
end
|
475
475
|
|
476
476
|
it "upgrade_addon(app_name, addon_name) with response" do
|
477
|
-
stub_request(:put, "https://api.heroku.com/apps/
|
477
|
+
stub_request(:put, "https://api.heroku.com/apps/example/addons/addon1").
|
478
478
|
to_return(:body => json_encode('price' => 'free', 'message' => "Don't Panic"))
|
479
479
|
|
480
|
-
@client.upgrade_addon('
|
480
|
+
@client.upgrade_addon('example', 'addon1').
|
481
481
|
should == { 'price' => 'free', 'message' => "Don't Panic" }
|
482
482
|
end
|
483
483
|
|
484
484
|
it "downgrade_addon(app_name, addon_name) with response" do
|
485
|
-
stub_request(:put, "https://api.heroku.com/apps/
|
485
|
+
stub_request(:put, "https://api.heroku.com/apps/example/addons/addon1").
|
486
486
|
to_return(:body => json_encode('price' => 'free', 'message' => "Don't Panic"))
|
487
487
|
|
488
|
-
@client.downgrade_addon('
|
488
|
+
@client.downgrade_addon('example', 'addon1').
|
489
489
|
should == { 'price' => 'free', 'message' => "Don't Panic" }
|
490
490
|
end
|
491
491
|
|
492
492
|
it "uninstall_addon(app_name, addon_name) with response" do
|
493
|
-
stub_api_request(:delete, "/apps/
|
493
|
+
stub_api_request(:delete, "/apps/example/addons/addon1?").
|
494
494
|
to_return(:body => json_encode('price'=> 'free', 'message'=> "Don't Panic"))
|
495
495
|
|
496
|
-
@client.uninstall_addon('
|
496
|
+
@client.uninstall_addon('example', 'addon1').
|
497
497
|
should == { 'price' => 'free', 'message' => "Don't Panic" }
|
498
498
|
end
|
499
499
|
end
|
@@ -548,16 +548,16 @@ describe Heroku::Client do
|
|
548
548
|
|
549
549
|
describe "stacks" do
|
550
550
|
it "list_stacks(app_name) -> json hash of available stacks" do
|
551
|
-
stub_api_request(:get, "/apps/
|
551
|
+
stub_api_request(:get, "/apps/example/stack?include_deprecated=false").to_return(:body => '{"stack":"one"}')
|
552
552
|
capture_stderr do # capture deprecation message
|
553
|
-
@client.list_stacks("
|
553
|
+
@client.list_stacks("example").should == { 'stack' => 'one' }
|
554
554
|
end
|
555
555
|
end
|
556
556
|
|
557
557
|
it "list_stacks(app_name, include_deprecated=true) passes the deprecated option" do
|
558
|
-
stub_api_request(:get, "/apps/
|
558
|
+
stub_api_request(:get, "/apps/example/stack?include_deprecated=true").to_return(:body => '{"stack":"one"}')
|
559
559
|
capture_stderr do # capture deprecation message
|
560
|
-
@client.list_stacks("
|
560
|
+
@client.list_stacks("example", :include_deprecated => true).should == { 'stack' => 'one' }
|
561
561
|
end
|
562
562
|
end
|
563
563
|
end
|