heroku-commander 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/CHANGELOG.md +9 -0
- data/README.md +16 -0
- data/examples/executor-run.rb +16 -0
- data/examples/heroku-processes.rb +12 -0
- data/lib/config/locales/en.yml +10 -1
- data/lib/heroku-commander.rb +1 -1
- data/lib/heroku/commander.rb +17 -0
- data/lib/heroku/commander/errors.rb +2 -0
- data/lib/heroku/commander/errors/command_error.rb +22 -9
- data/lib/heroku/commander/errors/missing_pid_error.rb +13 -0
- data/lib/heroku/commander/errors/no_such_process_error.rb +13 -0
- data/lib/heroku/commander/version.rb +1 -1
- data/lib/heroku/executor.rb +50 -20
- data/lib/heroku/process.rb +25 -0
- data/lib/heroku/runner.rb +20 -19
- data/spec/heroku/commander_spec.rb +42 -2
- data/spec/heroku/executor_spec.rb +11 -2
- data/spec/heroku/process_spec.rb +24 -0
- data/spec/heroku/runner_spec.rb +20 -4
- metadata +10 -4
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,12 @@
|
|
1
|
+
0.2.0 (02/14/2013)
|
2
|
+
==================
|
3
|
+
|
4
|
+
* When the process exit status cannot be determined the error message says "The command failed without returning an exit status." - [@dblock](https://github.com/dblock).
|
5
|
+
* Added the process ID into the `Heroku::Commander::Errors::CommandError` problem description - [@dblock](https://github.com/dblock).
|
6
|
+
* Fix: Heroku `run` or `run:detached` output does not always combine "attached to terminal" and "up" status, which causes the runner to incorrectly parse the Heroku PID - [@dblock](https://github.com/dblock).
|
7
|
+
* Added `Heroku::Commander.processes` that returns or yields an array of `Heroku::Process` by running `heroku ps` - [@dblock](https://github.com/dblock).
|
8
|
+
* Added a `tail_timeout` option to `Heroku::Commander.run` with `detached: true` that suspends tail process termination for a chance to receive additional `heroku logs --tail` output - [@dblock](https://github.com/dblock).
|
9
|
+
|
1
10
|
0.1.0 (01/31/2013)
|
2
11
|
==================
|
3
12
|
|
data/README.md
CHANGED
@@ -25,6 +25,18 @@ commander = Heroku::Commander.new({ :app => "heroku-commander" })
|
|
25
25
|
commander.config # => a hash of all settings for the heroku-commander app
|
26
26
|
```
|
27
27
|
|
28
|
+
Heroku Processes
|
29
|
+
----------------
|
30
|
+
|
31
|
+
Returns or yields an array of processes by running `heroku ps`.
|
32
|
+
|
33
|
+
``` ruby
|
34
|
+
commander = Heroku::Commander.new({ :app => "heroku-commander" })
|
35
|
+
commander.processes do |process|
|
36
|
+
# try process.pid and process.status
|
37
|
+
end
|
38
|
+
```
|
39
|
+
|
28
40
|
Heroku Run
|
29
41
|
----------
|
30
42
|
|
@@ -53,6 +65,10 @@ commander.run("ls -R", { :detached => true }) do |line|
|
|
53
65
|
end
|
54
66
|
```
|
55
67
|
|
68
|
+
You can pass the following options along with `:detached`:
|
69
|
+
|
70
|
+
* **tail_timeout**: number of seconds to wait before terminating `heroku logs --tail`, expecting more output (default to 5).
|
71
|
+
|
56
72
|
For more information about Heroku one-off dynos see [this documentation](https://devcenter.heroku.com/articles/one-off-dynos).
|
57
73
|
|
58
74
|
More Examples
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'bundler'
|
2
|
+
Bundler.setup(:default, :development)
|
3
|
+
|
4
|
+
require 'heroku-commander'
|
5
|
+
|
6
|
+
logger = Logger.new($stdout)
|
7
|
+
logger.level = Logger::INFO
|
8
|
+
|
9
|
+
uname = Heroku::Executor.run "uname -a", { :logger => logger }
|
10
|
+
logger.info "Local system is #{uname.join('\n')}."
|
11
|
+
|
12
|
+
files = []
|
13
|
+
Heroku::Executor.run "ls -1", { :logger => logger } do |line|
|
14
|
+
files << line
|
15
|
+
end
|
16
|
+
logger.info "Local file system has #{files.count} file(s): #{files.join(', ')}"
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'bundler'
|
2
|
+
Bundler.setup(:default, :development)
|
3
|
+
|
4
|
+
require 'heroku-commander'
|
5
|
+
|
6
|
+
logger = Logger.new($stdout)
|
7
|
+
logger.level = Logger::DEBUG
|
8
|
+
commander = Heroku::Commander.new({ :logger => logger })
|
9
|
+
|
10
|
+
commander.processes.each do |process|
|
11
|
+
logger.info "Process: #{process.pid} (#{process.status})"
|
12
|
+
end
|
data/lib/config/locales/en.yml
CHANGED
@@ -8,7 +8,7 @@ en:
|
|
8
8
|
summary: "The heroku client stopped sending output before exiting."
|
9
9
|
resolution: "This is unexpected and represents a Heroku server error. Try again."
|
10
10
|
command_error:
|
11
|
-
message: "The command `%{cmd}
|
11
|
+
message: "The command `%{cmd}`%{pid} failed%{status_message}."
|
12
12
|
summary: "%{message}%{lines}"
|
13
13
|
resolution: "Examine the command line and refer to the command documentation."
|
14
14
|
unexpected_output:
|
@@ -20,7 +20,16 @@ en:
|
|
20
20
|
summary: "The instance of Heroku::Runner is missing a command argument."
|
21
21
|
resolution: "Specify a command, for example:\n
|
22
22
|
\_\_Heroku::Runner.new({ :command => 'ls -1' })"
|
23
|
+
missing_pid_error:
|
24
|
+
message: "Missing pid."
|
25
|
+
summary: "The instance of Heroku::Status is missing a pid argument."
|
26
|
+
resolution: "Specify a pid, for example:\n
|
27
|
+
\_\_Heroku::Status.new({ :pid => 'run.1234' })"
|
23
28
|
already_running_error:
|
24
29
|
message: "The process is already running with pid %{pid}."
|
25
30
|
summary: "You can only run! or detach! an instance of Heroku::Runner once."
|
26
31
|
resolution: "This is a bug in your code."
|
32
|
+
no_such_process:
|
33
|
+
message: "The process `%{pid}` does not exist."
|
34
|
+
summary: "The output of heroku ps did not list the requested process."
|
35
|
+
resolution: "Verify that the process is running."
|
data/lib/heroku-commander.rb
CHANGED
data/lib/heroku/commander.rb
CHANGED
@@ -15,6 +15,23 @@ module Heroku
|
|
15
15
|
end
|
16
16
|
end
|
17
17
|
|
18
|
+
def processes(&block)
|
19
|
+
processes = []
|
20
|
+
cmdline = [ "heroku", "ps", @app ? "--app #{@app}" : nil ].compact.join(" ")
|
21
|
+
Heroku::Executor.run cmdline, { :logger => logger } do |line|
|
22
|
+
next if ! line || line[0] == '=' || line.length == 0
|
23
|
+
if (match = line.match /(\w+\.\d+): (\w+)/)
|
24
|
+
process = Heroku::Process.new({ :logger => logger, :pid => match[1], :status => match[2], :app => app })
|
25
|
+
processes << process
|
26
|
+
logger.info "Process: #{process.pid} (#{process.status}) from '#{line}'" if logger
|
27
|
+
yield process if block_given?
|
28
|
+
else
|
29
|
+
logger.warn "Unexpected line from heroku ps: #{line}" if logger
|
30
|
+
end
|
31
|
+
end
|
32
|
+
processes
|
33
|
+
end
|
34
|
+
|
18
35
|
# Run a process synchronously
|
19
36
|
def run(command, options = {}, &block)
|
20
37
|
runner = Heroku::Runner.new({ :app => app, :logger => logger, :command => command })
|
@@ -2,5 +2,7 @@ require 'heroku/commander/errors/base'
|
|
2
2
|
require 'heroku/commander/errors/client_eio_error'
|
3
3
|
require 'heroku/commander/errors/command_error'
|
4
4
|
require 'heroku/commander/errors/missing_command_error'
|
5
|
+
require 'heroku/commander/errors/missing_pid_error'
|
5
6
|
require 'heroku/commander/errors/unexpected_output_error'
|
6
7
|
require 'heroku/commander/errors/already_running_error'
|
8
|
+
require 'heroku/commander/errors/no_such_process_error'
|
@@ -7,27 +7,40 @@ module Heroku
|
|
7
7
|
|
8
8
|
def initialize(opts = {})
|
9
9
|
@inner_exception = opts[:inner_exception]
|
10
|
-
|
10
|
+
opts = opts.dup
|
11
|
+
prepare_lines(opts)
|
12
|
+
prepare_status_message(opts)
|
13
|
+
prepare_pid(opts)
|
14
|
+
super(compose_message("command_error", opts))
|
11
15
|
end
|
12
16
|
|
13
17
|
private
|
14
18
|
|
19
|
+
def prepare_status_message(opts)
|
20
|
+
if opts[:status] && opts[:status] != ""
|
21
|
+
opts[:status_message] = " with exit status #{opts[:status]}"
|
22
|
+
else
|
23
|
+
opts[:status_message] = " without reporting an exit status"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
15
27
|
def prepare_lines(opts)
|
16
28
|
if opts[:lines] && opts[:lines].size > 4
|
17
29
|
lines = opts[:lines][0..2]
|
18
30
|
lines.push "... skipping #{opts[:lines].size - 4} line(s) ..."
|
19
31
|
lines.concat opts[:lines][-2..-1]
|
20
|
-
|
21
|
-
result[:lines] = "\n\t" + lines.join("\n\t")
|
22
|
-
result
|
32
|
+
opts[:lines] = "\n\t" + lines.join("\n\t")
|
23
33
|
elsif opts[:lines]
|
24
|
-
|
25
|
-
result[:lines] = "\n\t" + result[:lines].join("\n\t")
|
26
|
-
result
|
27
|
-
else
|
28
|
-
opts
|
34
|
+
opts[:lines] = "\n\t" + opts[:lines].join("\n\t")
|
29
35
|
end
|
30
36
|
end
|
37
|
+
|
38
|
+
def prepare_pid(opts)
|
39
|
+
if opts[:pid]
|
40
|
+
opts[:pid] = " (pid: #{opts[:pid]})"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
31
44
|
end
|
32
45
|
end
|
33
46
|
end
|
data/lib/heroku/executor.rb
CHANGED
@@ -2,6 +2,12 @@ module Heroku
|
|
2
2
|
class Executor
|
3
3
|
|
4
4
|
class Terminate < StandardError
|
5
|
+
|
6
|
+
attr_accessor :timeout
|
7
|
+
|
8
|
+
def initialize(timeout = 0)
|
9
|
+
@timeout = timeout
|
10
|
+
end
|
5
11
|
end
|
6
12
|
|
7
13
|
class << self
|
@@ -9,47 +15,41 @@ module Heroku
|
|
9
15
|
# Executes a command and yields output line-by-line.
|
10
16
|
def run(cmd, options = {}, &block)
|
11
17
|
lines = []
|
18
|
+
running_pid = nil
|
12
19
|
logger = options[:logger]
|
13
20
|
logger.debug "Running: #{cmd}" if logger
|
14
21
|
PTY.spawn(cmd) do |r, w, pid|
|
22
|
+
running_pid = pid
|
15
23
|
logger.debug "Started: #{pid}" if logger
|
16
24
|
terminated = false
|
17
25
|
begin
|
18
26
|
r.sync = true
|
19
|
-
r
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
yield line
|
24
|
-
end
|
25
|
-
lines << line
|
26
|
-
end
|
27
|
-
rescue Heroku::Executor::Terminate
|
28
|
-
logger.debug "Terminating #{pid}." if logger
|
29
|
-
Process.kill("TERM", pid)
|
27
|
+
read_from(r, pid, options, lines, &block)
|
28
|
+
rescue Heroku::Executor::Terminate => e
|
29
|
+
logger.debug "Waiting: #{e.timeout} second(s) to terminate #{pid}" if logger
|
30
|
+
terminate_process! pid, e.timeout
|
30
31
|
terminated = true
|
31
32
|
rescue Errno::EIO, IOError => e
|
32
|
-
|
33
|
+
# ignore
|
33
34
|
rescue PTY::ChildExited => e
|
34
35
|
logger.debug "Terminated: #{pid}" if logger
|
35
|
-
|
36
|
+
terminated = true
|
36
37
|
raise e
|
37
38
|
ensure
|
38
39
|
unless terminated
|
40
|
+
# wait for process
|
39
41
|
logger.debug "Waiting: #{pid}" if logger
|
40
|
-
Process.wait(pid)
|
42
|
+
::Process.wait(pid)
|
41
43
|
end
|
42
44
|
end
|
43
45
|
end
|
44
|
-
check_exit_status! cmd, $?.exitstatus, lines
|
46
|
+
check_exit_status! cmd, running_pid, $?.exitstatus, lines
|
45
47
|
lines
|
46
48
|
rescue Errno::ECHILD => e
|
47
|
-
|
48
|
-
check_exit_status! cmd, $?.exitstatus, lines
|
49
|
+
check_exit_status! cmd, running_pid, $?.exitstatus, lines
|
49
50
|
lines
|
50
51
|
rescue PTY::ChildExited => e
|
51
|
-
|
52
|
-
check_exit_status! cmd, $!.status.exitstatus, lines
|
52
|
+
check_exit_status! cmd, running_pid, $!.status.exitstatus, lines
|
53
53
|
lines
|
54
54
|
rescue Heroku::Commander::Errors::Base => e
|
55
55
|
logger.debug "Error: #{e.problem}" if logger
|
@@ -58,6 +58,7 @@ module Heroku
|
|
58
58
|
logger.debug "#{e.class}: #{e.respond_to?(:problem) ? e.problem : e.message}" if logger
|
59
59
|
raise Heroku::Commander::Errors::CommandError.new({
|
60
60
|
:cmd => cmd,
|
61
|
+
:pid => running_pid,
|
61
62
|
:status => $?.exitstatus,
|
62
63
|
:message => e.message,
|
63
64
|
:inner_exception => e,
|
@@ -67,16 +68,45 @@ module Heroku
|
|
67
68
|
|
68
69
|
private
|
69
70
|
|
70
|
-
def
|
71
|
+
def read_from(r, pid, options, lines, &block)
|
72
|
+
logger = options[:logger]
|
73
|
+
while ! r.eof do
|
74
|
+
line = r.readline
|
75
|
+
line.strip! if line
|
76
|
+
logger.debug "#{pid}: #{line}" if logger
|
77
|
+
if block_given?
|
78
|
+
yield line
|
79
|
+
end
|
80
|
+
lines << line
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def check_exit_status!(cmd, pid, status, lines = nil)
|
71
85
|
return if ! status || status == 0
|
72
86
|
raise Heroku::Commander::Errors::CommandError.new({
|
73
87
|
:cmd => cmd,
|
88
|
+
:pid => pid,
|
74
89
|
:status => status,
|
75
90
|
:message => "The command #{cmd} failed with exit status #{status}.",
|
76
91
|
:lines => lines
|
77
92
|
})
|
78
93
|
end
|
79
94
|
|
95
|
+
def terminate_process!(pid, timeout)
|
96
|
+
if timeout
|
97
|
+
# Delay terminating of the process, usually to let the output flush.
|
98
|
+
Thread.new(pid, timeout) do |pid, timeout|
|
99
|
+
begin
|
100
|
+
sleep(timeout) if timeout
|
101
|
+
::Process.kill("TERM", pid)
|
102
|
+
rescue
|
103
|
+
end
|
104
|
+
end
|
105
|
+
else
|
106
|
+
::Process.kill("TERM", pid)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
80
110
|
end
|
81
111
|
end
|
82
112
|
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Heroku
|
2
|
+
class Process
|
3
|
+
|
4
|
+
attr_accessor :app, :logger, :pid, :status
|
5
|
+
|
6
|
+
def initialize(options = {})
|
7
|
+
@app = options[:app]
|
8
|
+
@logger = options[:logger]
|
9
|
+
@pid = options[:pid]
|
10
|
+
@status = options[:status]
|
11
|
+
raise Heroku::Commander::Errors::MissingPidError unless @pid
|
12
|
+
end
|
13
|
+
|
14
|
+
def refresh_status!
|
15
|
+
refreshed = false
|
16
|
+
Heroku::Commander.new({ :logger => logger, :app => app }).processes.each do |process|
|
17
|
+
next unless ! refreshed && process.pid == pid
|
18
|
+
@status = process.status
|
19
|
+
refreshed = true
|
20
|
+
end
|
21
|
+
refreshed
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
data/lib/heroku/runner.rb
CHANGED
@@ -17,38 +17,40 @@ module Heroku
|
|
17
17
|
|
18
18
|
def run!(options = {}, &block)
|
19
19
|
if options && options[:detached]
|
20
|
-
run_detached! &block
|
20
|
+
run_detached! options, &block
|
21
21
|
else
|
22
|
-
run_attached! &block
|
22
|
+
run_attached! options, &block
|
23
23
|
end
|
24
24
|
end
|
25
25
|
|
26
26
|
protected
|
27
27
|
|
28
|
-
def run_attached!(&block)
|
28
|
+
def run_attached!(options = {}, &block)
|
29
29
|
@pid = nil
|
30
30
|
previous_line = nil # delay by 1 to avoid rc=status line
|
31
|
+
lines_until_pid = 0
|
31
32
|
lines = Heroku::Executor.run cmdline, { :logger => logger } do |line|
|
32
33
|
if ! @pid
|
33
34
|
check_pid(line)
|
35
|
+
lines_until_pid += 1
|
34
36
|
elsif block_given?
|
35
37
|
yield previous_line if previous_line
|
36
38
|
previous_line = line
|
37
39
|
end
|
38
40
|
end
|
39
|
-
lines.shift # remove Running `...` attached to terminal... up, run.xyz
|
41
|
+
lines.shift(lines_until_pid) # remove Running `...` attached to terminal... up, run.xyz
|
40
42
|
check_exit_status! lines
|
41
43
|
lines
|
42
44
|
end
|
43
45
|
|
44
|
-
def run_detached!(&block)
|
46
|
+
def run_detached!(options = {}, &block)
|
45
47
|
raise Heroku::Commander::Errors::AlreadyRunningError.new({ :pid => @pid }) if running?
|
46
48
|
@running = true
|
47
49
|
@pid = nil
|
48
50
|
@tail = nil
|
49
51
|
lines = Heroku::Executor.run cmdline({ :detached => true }), { :logger => logger } do |line|
|
50
52
|
check_pid(line) unless @pid
|
51
|
-
@tail ||= tail!(&block) if @pid
|
53
|
+
@tail ||= tail!(options, &block) if @pid
|
52
54
|
end
|
53
55
|
check_exit_status! @tail || lines
|
54
56
|
@running = false
|
@@ -64,37 +66,36 @@ module Heroku
|
|
64
66
|
lines.pop if status
|
65
67
|
raise Heroku::Commander::Errors::CommandError.new({
|
66
68
|
:cmd => @command,
|
69
|
+
:pid => @pid,
|
67
70
|
:status => status,
|
68
|
-
:message => "The command #{@command} failed
|
71
|
+
:message => "The command #{@command} failed.",
|
69
72
|
:lines => lines
|
70
73
|
}) unless status && status == "0"
|
71
74
|
end
|
72
75
|
|
73
76
|
def check_pid(line)
|
74
|
-
if (match = line.match /
|
77
|
+
if (match = line.match /up, (run.\d+)$/)
|
75
78
|
@pid = match[1]
|
76
79
|
logger.debug "Heroku pid #{@pid} up." if logger
|
77
|
-
elsif (match = line.match /detached... up, (run.\d+)$/)
|
78
|
-
@pid = match[1]
|
79
|
-
logger.debug "Heroku detached pid #{@pid} up." if logger
|
80
|
-
else
|
81
|
-
@pid = ''
|
82
80
|
end
|
83
81
|
end
|
84
82
|
|
85
|
-
def tail!(&block)
|
83
|
+
def tail!(options = {}, &block)
|
86
84
|
lines = []
|
87
85
|
tail_cmdline = [ "heroku", "logs", "-p #{@pid}", "--tail", @app ? "--app #{@app}" : nil ].compact.join(" ")
|
88
|
-
previous_line = nil # delay by 1 to avoid rc=status
|
86
|
+
previous_line = nil # delay by 1 to avoid rc=status lines
|
89
87
|
Heroku::Executor.run tail_cmdline, { :logger => logger } do |line|
|
88
|
+
line ||= ""
|
90
89
|
# remove any ANSI output
|
91
90
|
line = line.gsub /\e\[(\d+)m/, ''
|
92
91
|
# lines are returned as [date/time] app/heroku[pid]: output
|
93
|
-
|
92
|
+
if (line_after_prefix = line.split("[#{@pid}]:")[-1])
|
93
|
+
line = line_after_prefix.strip
|
94
|
+
end
|
94
95
|
if line.match(/Starting process with command/) || line.match(/State changed from \w+ to up/)
|
95
96
|
# ignore
|
96
97
|
elsif line.match(/State changed from \w+ to complete/) || line.match(/Process exited with status \d+/)
|
97
|
-
terminate_executor!
|
98
|
+
terminate_executor!(options[:tail_timeout] || 5)
|
98
99
|
else
|
99
100
|
if block_given?
|
100
101
|
yield previous_line if previous_line
|
@@ -106,8 +107,8 @@ module Heroku
|
|
106
107
|
lines
|
107
108
|
end
|
108
109
|
|
109
|
-
def terminate_executor!
|
110
|
-
raise Heroku::Executor::Terminate
|
110
|
+
def terminate_executor!(timeout = 10)
|
111
|
+
raise Heroku::Executor::Terminate.new(timeout)
|
111
112
|
end
|
112
113
|
|
113
114
|
end
|
@@ -73,8 +73,48 @@ describe Heroku::Commander do
|
|
73
73
|
"2013-01-31T01:39:33+00:00 heroku[run.8748]: Process exited with status 0",
|
74
74
|
"2013-01-31T01:39:33+00:00 heroku[run.8748]: State changed from up to complete"
|
75
75
|
])
|
76
|
-
Heroku::Runner.any_instance.should_receive(:terminate_executor!).twice
|
77
|
-
subject.run("ls -1", { :detached => true }).should == [ "bin", "app" ]
|
76
|
+
Heroku::Runner.any_instance.should_receive(:terminate_executor!).with(42).twice
|
77
|
+
subject.run("ls -1", { :detached => true, :tail_timeout => 42 }).should == [ "bin", "app" ]
|
78
|
+
end
|
79
|
+
end
|
80
|
+
context "processes" do
|
81
|
+
context "without processes" do
|
82
|
+
before :each do
|
83
|
+
Heroku::Executor.stub(:run).with("heroku ps", { :logger => nil })
|
84
|
+
end
|
85
|
+
its(:processes) { should be_empty }
|
86
|
+
end
|
87
|
+
context "a web process" do
|
88
|
+
before :each do
|
89
|
+
Heroku::Executor.stub(:run).with("heroku ps", { :logger => nil }).
|
90
|
+
and_yield("=== web: `bundle exec ruby config.ru`").
|
91
|
+
and_yield("web.1: idle 2013/02/04 13:23:40 (~ 4h ago)")
|
92
|
+
end
|
93
|
+
its(:processes) { should_not be_empty }
|
94
|
+
it "has correct pid and status" do
|
95
|
+
processes = subject.processes
|
96
|
+
processes.count.should == 1
|
97
|
+
process = processes.first
|
98
|
+
process.pid.should eq "web.1"
|
99
|
+
process.status.should eq "idle"
|
100
|
+
end
|
101
|
+
end
|
102
|
+
context "a combination of one-off and web processes" do
|
103
|
+
before :each do
|
104
|
+
Heroku::Executor.stub(:run).with("heroku ps", { :logger => nil }).
|
105
|
+
and_yield("=== run: one-off processes").
|
106
|
+
and_yield("run.9174: up 2013/02/04 17:37:37 (~ 9m ago): `bundle exec rails console`").
|
107
|
+
and_yield(nil).
|
108
|
+
and_yield("").
|
109
|
+
and_yield("=== web: `bundle exec ruby config.ru`").
|
110
|
+
and_yield("web.1: up 2013/02/04 11:14:53 (~ 6h ago)").
|
111
|
+
and_yield("web.2: up 2013/02/04 11:14:17 (~ 6h ago)")
|
112
|
+
end
|
113
|
+
it "has correct pid and status" do
|
114
|
+
processes = subject.processes
|
115
|
+
processes.count.should == 3
|
116
|
+
processes.map(&:pid).should == [ "run.9174", "web.1", "web.2" ]
|
117
|
+
end
|
78
118
|
end
|
79
119
|
end
|
80
120
|
end
|
@@ -7,7 +7,15 @@ describe Heroku::Executor do
|
|
7
7
|
end
|
8
8
|
context "command does not exist" do
|
9
9
|
subject { lambda { Heroku::Executor.run "executor_spec.rb" } }
|
10
|
-
it { should raise_error Heroku::Commander::Errors::CommandError, /The command `executor_spec.rb`
|
10
|
+
it { should raise_error Heroku::Commander::Errors::CommandError, /The command `executor_spec.rb`(.*)failed with exit status \d+./ }
|
11
|
+
end
|
12
|
+
context "command does not return exit status" do
|
13
|
+
before do
|
14
|
+
PTY.stub(:spawn).and_raise("foobar!")
|
15
|
+
Process::Status.any_instance.stub(:exitstatus).and_return(nil)
|
16
|
+
end
|
17
|
+
subject { lambda { Heroku::Executor.run "executor_spec.rb" } }
|
18
|
+
it { should raise_error Heroku::Commander::Errors::CommandError, /The command `executor_spec.rb` failed without reporting an exit status./ }
|
11
19
|
end
|
12
20
|
context "command exists" do
|
13
21
|
subject { lambda { Heroku::Executor.run "ls -1" } }
|
@@ -25,7 +33,8 @@ describe Heroku::Executor do
|
|
25
33
|
it "doesn't yield nil lines" do
|
26
34
|
r = double(IO)
|
27
35
|
r.stub(:sync=)
|
28
|
-
r.stub(:
|
36
|
+
r.stub(:eof).and_return(false, false, false, true)
|
37
|
+
r.stub(:readline).and_return("line1", nil, "rc=0")
|
29
38
|
Process.stub(:wait)
|
30
39
|
PTY.stub(:spawn).and_yield(r, nil, 42)
|
31
40
|
Heroku::Executor.run("foobar").should == [ "line1", nil, "rc=0" ]
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Heroku::Process do
|
4
|
+
context "without pid" do
|
5
|
+
subject { lambda { Heroku::Process.new } }
|
6
|
+
it { should raise_error Heroku::Commander::Errors::MissingPidError, /Missing pid./ }
|
7
|
+
end
|
8
|
+
context "with pid" do
|
9
|
+
subject do
|
10
|
+
Heroku::Process.new({ :pid => "run.1234", :status => "idle" })
|
11
|
+
end
|
12
|
+
its(:pid) { should eq "run.1234" }
|
13
|
+
its(:status) { should eq "idle" }
|
14
|
+
context "refresh_status!" do
|
15
|
+
before do
|
16
|
+
Heroku::Executor.stub(:run).with("heroku ps", { :logger => nil }).
|
17
|
+
and_yield("run.1234: up for ~ 1m")
|
18
|
+
subject.refresh_status!
|
19
|
+
end
|
20
|
+
its(:status) { should eq "up" }
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
data/spec/heroku/runner_spec.rb
CHANGED
@@ -17,14 +17,29 @@ describe Heroku::Runner do
|
|
17
17
|
its(:command) { should eq "ls -1" }
|
18
18
|
its(:cmdline) { should eq "heroku run \"(ls -1 2>&1 ; echo rc=\\$?)\"" }
|
19
19
|
its(:running?) { should be_false }
|
20
|
+
context "check_pid" do
|
21
|
+
it "parses up, run.1234" do
|
22
|
+
subject.send(:check_pid, "up, run.1234")
|
23
|
+
subject.pid.should == "run.1234"
|
24
|
+
end
|
25
|
+
it "parses attached to terminal ... up, run.1234" do
|
26
|
+
subject.send(:check_pid, "parses attached to terminal ... up, run.1234")
|
27
|
+
subject.pid.should == "run.1234"
|
28
|
+
end
|
29
|
+
it "parses detached ... up, run.1234" do
|
30
|
+
subject.send(:check_pid, "detached ... up, run.1234")
|
31
|
+
subject.pid.should == "run.1234"
|
32
|
+
end
|
33
|
+
end
|
20
34
|
context "run!" do
|
21
35
|
before :each do
|
22
36
|
Heroku::Executor.stub(:run).with(subject.send(:cmdline), { :logger => nil }).
|
23
|
-
and_yield("Running `...`
|
37
|
+
and_yield("Running `...`").
|
38
|
+
and_yield("attached to terminal... up, run.9783").
|
24
39
|
and_yield("app").
|
25
40
|
and_yield("bin").
|
26
41
|
and_yield("rc= 0").
|
27
|
-
and_return([ "Running `...` attached to terminal... up, run.9783", "app", "bin", "rc=0" ])
|
42
|
+
and_return([ "Running `...`", "attached to terminal... up, run.9783", "app", "bin", "rc=0" ])
|
28
43
|
end
|
29
44
|
it "runs the command w/o a block" do
|
30
45
|
subject.run!.should == [ "app", "bin" ]
|
@@ -59,6 +74,7 @@ describe Heroku::Runner do
|
|
59
74
|
and_yield("2013-01-31T01:39:30+00:00 heroku[run.8748]: Starting process with command `ls -1`").
|
60
75
|
and_yield("2013-01-31T01:39:31+00:00 app[run.8748]: bin").
|
61
76
|
and_yield("2013-01-31T01:39:31+00:00 app[run.8748]: app").
|
77
|
+
and_yield(nil).
|
62
78
|
and_yield("2013-01-31T00:56:13+00:00 app[run.8748]: rc=0").
|
63
79
|
and_yield("2013-01-31T01:39:33+00:00 heroku[run.8748]: Process exited with status 0").
|
64
80
|
and_yield("2013-01-31T01:39:33+00:00 heroku[run.8748]: State changed from up to complete").
|
@@ -73,7 +89,7 @@ describe Heroku::Runner do
|
|
73
89
|
Heroku::Runner.any_instance.should_receive(:terminate_executor!).twice
|
74
90
|
end
|
75
91
|
it "runs the command w/o a block" do
|
76
|
-
subject.run!({ :detached => true }).should == [ "bin", "app" ]
|
92
|
+
subject.run!({ :detached => true }).should == [ "bin", "app", "" ]
|
77
93
|
subject.pid.should == "run.8748"
|
78
94
|
subject.should_not be_running
|
79
95
|
end
|
@@ -82,7 +98,7 @@ describe Heroku::Runner do
|
|
82
98
|
subject.run!({ :detached => true }).each do |line|
|
83
99
|
lines << line
|
84
100
|
end
|
85
|
-
lines.should == [ "bin", "app" ]
|
101
|
+
lines.should == [ "bin", "app", "" ]
|
86
102
|
subject.pid.should == "run.8748"
|
87
103
|
subject.should_not be_running
|
88
104
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: heroku-commander
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date: 2013-02-
|
13
|
+
date: 2013-02-14 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: i18n
|
@@ -43,7 +43,9 @@ files:
|
|
43
43
|
- README.md
|
44
44
|
- Rakefile
|
45
45
|
- assets/heroku-commander.png
|
46
|
+
- examples/executor-run.rb
|
46
47
|
- examples/heroku-config.rb
|
48
|
+
- examples/heroku-processes.rb
|
47
49
|
- examples/heroku-run-detached.rb
|
48
50
|
- examples/heroku-run.rb
|
49
51
|
- heroku-commander.gemspec
|
@@ -56,16 +58,20 @@ files:
|
|
56
58
|
- lib/heroku/commander/errors/client_eio_error.rb
|
57
59
|
- lib/heroku/commander/errors/command_error.rb
|
58
60
|
- lib/heroku/commander/errors/missing_command_error.rb
|
61
|
+
- lib/heroku/commander/errors/missing_pid_error.rb
|
62
|
+
- lib/heroku/commander/errors/no_such_process_error.rb
|
59
63
|
- lib/heroku/commander/errors/unexpected_output_error.rb
|
60
64
|
- lib/heroku/commander/version.rb
|
61
65
|
- lib/heroku/config.rb
|
62
66
|
- lib/heroku/executor.rb
|
67
|
+
- lib/heroku/process.rb
|
63
68
|
- lib/heroku/pty.rb
|
64
69
|
- lib/heroku/runner.rb
|
65
70
|
- lib/heroku_commander.rb
|
66
71
|
- spec/heroku/commander_spec.rb
|
67
72
|
- spec/heroku/config_spec.rb
|
68
73
|
- spec/heroku/executor_spec.rb
|
74
|
+
- spec/heroku/process_spec.rb
|
69
75
|
- spec/heroku/runner_spec.rb
|
70
76
|
- spec/heroku/version_spec.rb
|
71
77
|
- spec/spec_helper.rb
|
@@ -84,7 +90,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
84
90
|
version: '0'
|
85
91
|
segments:
|
86
92
|
- 0
|
87
|
-
hash:
|
93
|
+
hash: -3503047870647109606
|
88
94
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
89
95
|
none: false
|
90
96
|
requirements:
|
@@ -93,7 +99,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
93
99
|
version: 1.3.6
|
94
100
|
requirements: []
|
95
101
|
rubyforge_project:
|
96
|
-
rubygems_version: 1.8.
|
102
|
+
rubygems_version: 1.8.25
|
97
103
|
signing_key:
|
98
104
|
specification_version: 3
|
99
105
|
summary: Control Heroku from Ruby via its `heroku` shell command.
|