pogo 2.32.14 → 2.39.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. data/README.md +0 -2
  2. data/lib/heroku/auth.rb +3 -1
  3. data/lib/heroku/client.rb +8 -5
  4. data/lib/heroku/client/cisaurus.rb +25 -0
  5. data/lib/heroku/client/heroku_postgresql.rb +0 -3
  6. data/lib/heroku/client/rendezvous.rb +2 -1
  7. data/lib/heroku/command.rb +1 -1
  8. data/lib/heroku/command/addons.rb +11 -2
  9. data/lib/heroku/command/apps.rb +105 -20
  10. data/lib/heroku/command/base.rb +1 -1
  11. data/lib/heroku/command/certs.rb +95 -34
  12. data/lib/heroku/command/config.rb +5 -5
  13. data/lib/heroku/command/domains.rb +4 -4
  14. data/lib/heroku/command/fork.rb +160 -0
  15. data/lib/heroku/command/git.rb +19 -20
  16. data/lib/heroku/command/help.rb +18 -2
  17. data/lib/heroku/command/keys.rb +1 -1
  18. data/lib/heroku/command/labs.rb +1 -1
  19. data/lib/heroku/command/logs.rb +3 -56
  20. data/lib/heroku/command/maintenance.rb +2 -2
  21. data/lib/heroku/command/pg.rb +7 -16
  22. data/lib/heroku/command/pgbackups.rb +37 -17
  23. data/lib/heroku/command/ps.rb +82 -35
  24. data/lib/heroku/command/regions.rb +23 -0
  25. data/lib/heroku/command/releases.rb +3 -3
  26. data/lib/heroku/command/run.rb +40 -27
  27. data/lib/heroku/command/sharing.rb +4 -4
  28. data/lib/heroku/command/ssl.rb +7 -25
  29. data/lib/heroku/command/stack.rb +1 -1
  30. data/lib/heroku/helpers/heroku_postgresql.rb +13 -17
  31. data/lib/heroku/helpers/log_displayer.rb +70 -0
  32. data/lib/heroku/plugin.rb +3 -0
  33. data/lib/heroku/updater.rb +11 -2
  34. data/lib/heroku/version.rb +1 -1
  35. data/spec/heroku/auth_spec.rb +10 -0
  36. data/spec/heroku/client/ssl_endpoint_spec.rb +12 -12
  37. data/spec/heroku/client_spec.rb +100 -100
  38. data/spec/heroku/command/addons_spec.rb +63 -59
  39. data/spec/heroku/command/apps_spec.rb +68 -65
  40. data/spec/heroku/command/base_spec.rb +21 -21
  41. data/spec/heroku/command/certs_spec.rb +31 -31
  42. data/spec/heroku/command/config_spec.rb +18 -18
  43. data/spec/heroku/command/db_spec.rb +3 -3
  44. data/spec/heroku/command/domains_spec.rb +13 -13
  45. data/spec/heroku/command/drains_spec.rb +3 -3
  46. data/spec/heroku/command/fork_spec.rb +56 -0
  47. data/spec/heroku/command/git_spec.rb +57 -29
  48. data/spec/heroku/command/labs_spec.rb +8 -8
  49. data/spec/heroku/command/logs_spec.rb +3 -3
  50. data/spec/heroku/command/maintenance_spec.rb +5 -5
  51. data/spec/heroku/command/pg_spec.rb +11 -25
  52. data/spec/heroku/command/pgbackups_spec.rb +13 -8
  53. data/spec/heroku/command/ps_spec.rb +23 -23
  54. data/spec/heroku/command/releases_spec.rb +22 -24
  55. data/spec/heroku/command/run_spec.rb +12 -15
  56. data/spec/heroku/command/sharing_spec.rb +9 -9
  57. data/spec/heroku/command/stack_spec.rb +4 -4
  58. data/spec/heroku/command_spec.rb +12 -12
  59. data/spec/heroku/helpers/heroku_postgresql_spec.rb +35 -14
  60. data/spec/spec_helper.rb +17 -2
  61. data/spec/support/openssl_mock_helper.rb +1 -1
  62. metadata +11 -6
  63. data/spec/heroku/command/ssl_spec.rb +0 -32
@@ -46,11 +46,11 @@ class Heroku::Command::Config < Heroku::Command::Base
46
46
  #Example:
47
47
  #
48
48
  # $ heroku config:set A=one
49
- # Setting config vars and restarting myapp... done, v123
49
+ # Setting config vars and restarting example... done, v123
50
50
  # A: one
51
51
  #
52
52
  # $ heroku config:set A=one B=two
53
- # Setting config vars and restarting myapp... done, v123
53
+ # Setting config vars and restarting example... done, v123
54
54
  # A: one
55
55
  # B: two
56
56
  #
@@ -107,11 +107,11 @@ class Heroku::Command::Config < Heroku::Command::Base
107
107
  # unset one or more config vars
108
108
  #
109
109
  # $ heroku config:unset A
110
- # Unsetting A and restarting myapp... done, v123
110
+ # Unsetting A and restarting example... done, v123
111
111
  #
112
112
  # $ heroku config:unset A B
113
- # Unsetting A and restarting myapp... done, v123
114
- # Unsetting B and restarting myapp... done, v124
113
+ # Unsetting A and restarting example... done, v123
114
+ # Unsetting B and restarting example... done, v124
115
115
  #
116
116
  def unset
117
117
  if args.empty?
@@ -13,7 +13,7 @@ module Heroku::Command
13
13
  #Examples:
14
14
  #
15
15
  # $ heroku domains
16
- # === Domain names for myapp
16
+ # === Domain names for example
17
17
  # example.com
18
18
  #
19
19
  def index
@@ -34,7 +34,7 @@ module Heroku::Command
34
34
  #Examples:
35
35
  #
36
36
  # $ heroku domains:add example.com
37
- # Adding example.com to myapp... done
37
+ # Adding example.com to example... done
38
38
  #
39
39
  def add
40
40
  unless domain = shift_argument
@@ -53,7 +53,7 @@ module Heroku::Command
53
53
  #Examples:
54
54
  #
55
55
  # $ heroku domains:remove example.com
56
- # Removing example.com from myapp... done
56
+ # Removing example.com from example... done
57
57
  #
58
58
  def remove
59
59
  unless domain = shift_argument
@@ -72,7 +72,7 @@ module Heroku::Command
72
72
  #Examples:
73
73
  #
74
74
  # $ heroku domains:clear
75
- # Removing all domain names for myapp... done
75
+ # Removing all domain names for example... done
76
76
  #
77
77
  def clear
78
78
  validate_arguments!
@@ -0,0 +1,160 @@
1
+ require "heroku/client/cisaurus"
2
+ require "heroku/command/base"
3
+
4
+ module Heroku::Command
5
+
6
+ # clone an existing app
7
+ #
8
+ class Fork < Base
9
+
10
+ # fork [NEWNAME]
11
+ #
12
+ # Fork an existing app -- copy config vars and Heroku Postgres data, and re-provision add-ons to a new app.
13
+ # New app name should not be an existing app. The new app will be created as part of the forking process.
14
+ #
15
+ # -s, --stack STACK # specify a stack for the new app
16
+ # --region REGION # specify a region
17
+ #
18
+ def index
19
+
20
+ from = app
21
+ to = shift_argument || "#{from}-#{(rand*1000).to_i}"
22
+
23
+ from_info = api.get_app(from).body
24
+
25
+ to_info = action("Creating fork #{to}") do
26
+ api.post_app({
27
+ :name => to,
28
+ :region => options[:region] || from_info["region"],
29
+ :stack => options[:stack] || from_info["stack"],
30
+ :tier => from_info["tier"] == "legacy" ? "production" : from_info["tier"]
31
+ }).body
32
+ end
33
+
34
+ action("Copying slug") do
35
+ job_location = cisaurus_client.copy_slug(from, to)
36
+ loop do
37
+ break if cisaurus_client.job_done?(job_location)
38
+ sleep 1
39
+ end
40
+ end
41
+
42
+ from_config = api.get_config_vars(from).body
43
+ from_addons = api.get_addons(from).body
44
+
45
+ from_addons.each do |addon|
46
+ print "Adding #{addon["name"]}... "
47
+ begin
48
+ to_addon = api.post_addon(to, addon["name"]).body
49
+ puts "done"
50
+ rescue Heroku::API::Errors::RequestFailed => ex
51
+ puts "skipped (%s)" % json_decode(ex.response.body)["error"]
52
+ rescue Heroku::API::Errors::NotFound
53
+ puts "skipped (not found)"
54
+ end
55
+ if addon["name"] =~ /^heroku-postgresql:/
56
+ from_var_name = "#{addon["attachment_name"]}_URL"
57
+ from_attachment = to_addon["message"].match(/Attached as (\w+)_URL\n/)[1]
58
+ if from_config[from_var_name] == from_config["DATABASE_URL"]
59
+ from_config["DATABASE_URL"] = api.get_config_vars(to).body["#{from_attachment}_URL"]
60
+ end
61
+ from_config.delete(from_var_name)
62
+
63
+ plan = addon["name"].split(":").last
64
+ unless %w(dev basic).include? plan
65
+ wait_for_db to, to_addon
66
+ end
67
+
68
+ check_for_pgbackups! from
69
+ check_for_pgbackups! to
70
+ migrate_db addon, from, to_addon, to
71
+ end
72
+ end
73
+
74
+ to_config = api.get_config_vars(to).body
75
+
76
+ action("Copying config vars") do
77
+ diff = from_config.inject({}) do |ax, (key, val)|
78
+ ax[key] = val unless to_config[key]
79
+ ax
80
+ end
81
+ api.put_config_vars to, diff
82
+ end
83
+
84
+ puts "Fork complete, view it at #{to_info['web_url']}"
85
+ end
86
+
87
+ private
88
+
89
+ def cisaurus_client
90
+ cisaurus_url = ENV["CISAURUS_HOST"] || "https://cisaurus.herokuapp.com"
91
+ @cisaurus_client ||= Heroku::Client::Cisaurus.new(cisaurus_url)
92
+ end
93
+
94
+ def check_for_pgbackups!(app)
95
+ unless api.get_addons(app).body.detect { |addon| addon["name"] =~ /^pgbackups:/ }
96
+ action("Adding pgbackups:plus to #{app}") do
97
+ api.post_addon app, "pgbackups:plus"
98
+ end
99
+ end
100
+ end
101
+
102
+ def migrate_db(from_addon, from, to_addon, to)
103
+ transfer = nil
104
+
105
+ action("Creating database backup from #{from} (this can take some time)") do
106
+ from_config = api.get_config_vars(from).body
107
+ from_attachment = from_addon["attachment_name"]
108
+ pgb = Heroku::Client::Pgbackups.new(from_config["PGBACKUPS_URL"])
109
+ transfer = pgb.create_transfer(from_config["#{from_attachment}_URL"], from_attachment, nil, "BACKUP", :expire => "true")
110
+ error transfer["errors"].values.flatten.join("\n") if transfer["errors"]
111
+ loop do
112
+ transfer = pgb.get_transfer(transfer["id"])
113
+ error transfer["errors"].values.flatten.join("\n") if transfer["errors"]
114
+ break if transfer["finished_at"]
115
+ sleep 1
116
+ end
117
+ print " "
118
+ end
119
+
120
+ action("Restoring database backup to #{to} (this can take some time)") do
121
+ to_config = api.get_config_vars(to).body
122
+ to_attachment = to_addon["message"].match(/Attached as (\w+)_URL\n/)[1]
123
+ pgb = Heroku::Client::Pgbackups.new(to_config["PGBACKUPS_URL"])
124
+ transfer = pgb.create_transfer(transfer["public_url"], "EXTERNAL_BACKUP", to_config["#{to_attachment}_URL"], to_attachment)
125
+ error transfer["errors"].values.flatten.join("\n") if transfer["errors"]
126
+ loop do
127
+ transfer = pgb.get_transfer(transfer["id"])
128
+ error transfer["errors"].values.flatten.join("\n") if transfer["errors"]
129
+ break if transfer["finished_at"]
130
+ sleep 1
131
+ end
132
+ print " "
133
+ end
134
+ end
135
+
136
+ def pg_api(starter=false)
137
+ host = starter ? "postgres-starter-api.heroku.com" : "postgres-api.heroku.com"
138
+ RestClient::Resource.new "https://#{host}/client/v11/databases", Heroku::Auth.user, Heroku::Auth.password
139
+ end
140
+
141
+ def wait_for_db(app, attachment)
142
+ attachments = api.get_attachments(app).body.inject({}) { |ax,att| ax.update(att["name"] => att["resource"]["name"]) }
143
+ attachment_name = attachment["message"].match(/Attached as (\w+)_URL\n/)[1]
144
+ action("Waiting for database to be ready (this can take some time)") do
145
+ loop do
146
+ begin
147
+ waiting = json_decode(pg_api["#{attachments[attachment_name]}/wait_status"].get.to_s)["waiting?"]
148
+ break unless waiting
149
+ sleep 5
150
+ rescue RestClient::ResourceNotFound
151
+ rescue Interrupt
152
+ exit 0
153
+ end
154
+ end
155
+ print " "
156
+ end
157
+ end
158
+
159
+ end
160
+ end
@@ -4,36 +4,35 @@ require "heroku/command/base"
4
4
  #
5
5
  class Heroku::Command::Git < Heroku::Command::Base
6
6
 
7
- # git:clone [OPTIONS]
7
+ # git:clone APP [DIRECTORY]
8
8
  #
9
- # clones an app repo
9
+ # clones a heroku app to your local machine at DIRECTORY (defaults to app name)
10
10
  #
11
- # if OPTIONS are specified they will be passed to git clone
12
- #
13
- # -n, --no-remote # don't create a git remote
14
- # -r, --remote REMOTE # the git remote to create, default "heroku"
11
+ # -r, --remote REMOTE # the git remote to create, default "heroku"
15
12
  #
16
13
  #Examples:
17
14
  #
18
- # $ heroku git:clone -a myapp
19
- # Cloning into 'myapp'...
20
- # Git remote heroku added
15
+ # $ heroku git:clone example
16
+ # Cloning from app 'example'...
17
+ # Cloning into 'example'...
18
+ # remote: Counting objects: 42, done.
19
+ # ...
21
20
  #
22
21
  def clone
23
- git_options = args.join(" ")
24
- remote = options[:remote] || 'heroku'
22
+ remote = options[:remote] || "heroku"
25
23
 
26
- app_data = api.get_app(app).body
24
+ name = options[:app] || shift_argument || error("Usage: heroku git:clone APP [DIRECTORY]")
25
+ directory = shift_argument
26
+ validate_arguments!
27
27
 
28
- display git("clone #{app_data['git_url']} #{git_options}")
28
+ git_url = api.get_app(name).body["git_url"]
29
29
 
30
- unless $?.exitstatus > 0 || options[:no_remote].is_a?(FalseClass)
31
- FileUtils.chdir(app_data['name']) do
32
- create_git_remote(remote, app_data['git_url'])
33
- end
34
- end
30
+ puts "Cloning from app '#{name}'..."
31
+ system "git clone -o #{remote} #{git_url} #{directory}".strip
35
32
  end
36
33
 
34
+ alias_command "clone", "git:clone"
35
+
37
36
  # git:remote [OPTIONS]
38
37
  #
39
38
  # adds a git remote to an app repo
@@ -44,10 +43,10 @@ class Heroku::Command::Git < Heroku::Command::Base
44
43
  #
45
44
  #Examples:
46
45
  #
47
- # $ heroku git:remote -a myapp
46
+ # $ heroku git:remote -a example
48
47
  # Git remote heroku added
49
48
  #
50
- # $ heroku git:remote -a myapp
49
+ # $ heroku git:remote -a example
51
50
  # ! Git remote heroku already exists
52
51
  #
53
52
  def remote
@@ -87,6 +87,18 @@ private
87
87
  nil
88
88
  end
89
89
 
90
+ def skip_namespace?(ns)
91
+ return true if ns[:description] =~ /DEPRECATED:/
92
+ return true if ns[:description] =~ /HIDDEN:/
93
+ false
94
+ end
95
+
96
+ def skip_command?(command)
97
+ return true if command[:help] =~ /DEPRECATED:/
98
+ return true if command[:help] =~ /^ HIDDEN:/
99
+ false
100
+ end
101
+
90
102
  def primary_namespaces
91
103
  PRIMARY_NAMESPACES.map { |name| namespaces[name] }.compact
92
104
  end
@@ -98,6 +110,7 @@ private
98
110
  def summary_for_namespaces(namespaces)
99
111
  size = longest(namespaces.map { |n| n[:name] })
100
112
  namespaces.sort_by {|namespace| namespace[:name]}.each do |namespace|
113
+ next if skip_namespace?(namespace)
101
114
  name = namespace[:name]
102
115
  namespace[:description] ||= legacy_help_for_namespace(name)
103
116
  puts " %-#{size}s # %s" % [ name, namespace[:description] ]
@@ -123,7 +136,7 @@ private
123
136
  unless namespace_commands.empty?
124
137
  size = longest(namespace_commands.map { |c| c[:banner] })
125
138
  namespace_commands.sort_by { |c| c[:banner].to_s }.each do |command|
126
- next if command[:help] =~ /DEPRECATED/
139
+ next if skip_command?(command)
127
140
  command[:summary] ||= legacy_help_for_command(command[:command])
128
141
  puts " %-#{size}s # %s" % [ command[:banner], command[:summary] ]
129
142
  end
@@ -139,7 +152,10 @@ private
139
152
  puts "Usage: heroku #{command[:banner]}"
140
153
 
141
154
  if command[:help].strip.length > 0
142
- puts command[:help].split("\n")[1..-1].join("\n")
155
+ help = command[:help].split("\n").reject do |line|
156
+ line =~ /HIDDEN/
157
+ end
158
+ puts help[1..-1].join("\n")
143
159
  else
144
160
  puts
145
161
  puts " " + legacy_help_for_command(name).to_s
@@ -95,7 +95,7 @@ module Heroku::Command
95
95
  #
96
96
  #Examples:
97
97
  #
98
- # $ heroku keys:cleare
98
+ # $ heroku keys:clear
99
99
  # Removing all SSH keys... done
100
100
  #
101
101
  def clear
@@ -15,7 +15,7 @@ class Heroku::Command::Labs < Heroku::Command::Base
15
15
  #
16
16
  # === App Features (glacial-retreat-5913)
17
17
  # [ ] preboot Provide seamless web dyno deploys
18
- # [ ] user-env-compile Add user config vars to the environment during slug compilation # $ heroku labs -a myapp
18
+ # [ ] user-env-compile Add user config vars to the environment during slug compilation # $ heroku labs -a example
19
19
  #
20
20
  def index
21
21
  validate_arguments!
@@ -1,4 +1,5 @@
1
1
  require "heroku/command/base"
2
+ require "heroku/helpers/log_displayer"
2
3
 
3
4
  # display logs for an app
4
5
  #
@@ -28,25 +29,8 @@ class Heroku::Command::Logs < Heroku::Command::Base
28
29
  opts << "ps=#{URI.encode(options[:ps])}" if options[:ps]
29
30
  opts << "source=#{URI.encode(options[:source])}" if options[:source]
30
31
 
31
- @assigned_colors = {}
32
- @line_start = true
33
- @token = nil
34
-
35
- heroku.read_logs(app, opts) do |chunk|
36
- unless chunk.empty?
37
- if STDOUT.isatty && ENV.has_key?("TERM")
38
- display(colorize(chunk))
39
- else
40
- display(chunk)
41
- end
42
- end
43
- end
44
- rescue Errno::EPIPE
45
- rescue Interrupt => interrupt
46
- if STDOUT.isatty && ENV.has_key?("TERM")
47
- display("\e[0m")
48
- end
49
- raise(interrupt)
32
+ log_displayer = ::Heroku::Helpers::LogDisplayer.new(heroku, app, opts)
33
+ log_displayer.display_logs
50
34
  end
51
35
 
52
36
  # logs:drains
@@ -58,41 +42,4 @@ class Heroku::Command::Logs < Heroku::Command::Base
58
42
  display("~ `heroku logs:drains` has been deprecated and replaced with `heroku drains`")
59
43
  Heroku::Command::Drains.new.index
60
44
  end
61
-
62
- protected
63
-
64
- COLORS = %w( cyan yellow green magenta red )
65
- COLOR_CODES = {
66
- "red" => 31,
67
- "green" => 32,
68
- "yellow" => 33,
69
- "magenta" => 35,
70
- "cyan" => 36,
71
- }
72
-
73
- def colorize(chunk)
74
- lines = []
75
- chunk.split("\n").map do |line|
76
- if parsed_line = parse_log(line)
77
- header, identifier, body = parsed_line
78
- @assigned_colors[identifier] ||= COLORS[@assigned_colors.size % COLORS.size]
79
- lines << [
80
- "\e[#{COLOR_CODES[@assigned_colors[identifier]]}m",
81
- header,
82
- "\e[0m",
83
- body,
84
- ].join("")
85
- elsif not line.empty?
86
- lines << line
87
- end
88
- end
89
- lines.join("\n")
90
- end
91
-
92
- def parse_log(log)
93
- return unless parsed = log.match(/^(.*\[(\w+)([\d\.]+)?\]:)(.*)?$/)
94
- [1, 2, 4].map { |i| parsed[i] }
95
- end
96
-
97
45
  end
98
-
@@ -31,7 +31,7 @@ class Heroku::Command::Maintenance < Heroku::Command::Base
31
31
  #Example:
32
32
  #
33
33
  # $ heroku maintenance:on
34
- # Enabling maintenance mode for myapp
34
+ # Enabling maintenance mode for example
35
35
  #
36
36
  def on
37
37
  validate_arguments!
@@ -48,7 +48,7 @@ class Heroku::Command::Maintenance < Heroku::Command::Base
48
48
  #Example:
49
49
  #
50
50
  # $ heroku maintenance:off
51
- # Disabling maintenance mode for myapp
51
+ # Disabling maintenance mode for example
52
52
  #
53
53
  def off
54
54
  validate_arguments!