console-mux 2.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,206 @@
1
+ #
2
+ # Copyright (C) 2012 Common Ground Publishing
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy of
5
+ # this software and associated documentation files (the "Software"), to deal in
6
+ # the Software without restriction, including without limitation the rights to
7
+ # use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
8
+ # the Software, and to permit persons to whom the Software is furnished to do so,
9
+ # subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in all
12
+ # copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
16
+ # FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
17
+ # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
18
+ # IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
19
+ # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20
+ #
21
+ require 'log4r'
22
+ require 'pty'
23
+
24
+ require 'console/mux/env_with_merged'
25
+ require 'console/mux/pty_handler'
26
+ require 'console/mux/events'
27
+
28
+ module Console
29
+ module Mux
30
+ class Process
31
+ include Log4r
32
+ include Console::Mux::Events
33
+
34
+ attr_reader :command, :pid, :logger, :started_at, :name
35
+
36
+ class << self
37
+ alias_method :start, :new
38
+ end
39
+
40
+ def initialize(command, name = nil)
41
+ @command = command
42
+ @name = name
43
+ @logger = Logger["process::#{name}"] || Logger.new("process::#{name}")
44
+ start
45
+ end
46
+
47
+ def stop(&block)
48
+ if @handler
49
+ @handler.detach
50
+ end
51
+ end
52
+
53
+ def running?
54
+ @handler != nil
55
+ end
56
+
57
+ def name
58
+ command.name
59
+ end
60
+
61
+ def to_s
62
+ "#{command} (#{running? ? pid : 'stopped'})"
63
+ end
64
+
65
+ # Called by the child handler
66
+ def unbind
67
+ @stdin.close rescue nil
68
+ @stdout.close rescue nil
69
+
70
+ pid2, status = ::Process.waitpid2(pid, ::Process::WNOHANG)
71
+ if pid2
72
+ on_exit(status)
73
+ else
74
+ try_kill(['INT', 'TERM', 'KILL'])
75
+ end
76
+ end
77
+
78
+ def receive_line(line)
79
+ logger.info { line }
80
+ end
81
+
82
+ # @return [Integer] uptime in seconds
83
+ def uptime
84
+ Time.now.to_i - started_at.to_i
85
+ end
86
+
87
+ # Capture the output of a single field using the 'ps' command.
88
+ def ps1(field)
89
+ `ps -o #{field}= -p #{pid}`.strip
90
+ end
91
+
92
+ # @return [String] elapsed time
93
+ def etime
94
+ ps1('etime')
95
+ end
96
+
97
+ # @return [Integer] resident size in bytes
98
+ def rss
99
+ ps1('rss').to_i * 1024
100
+ end
101
+
102
+ # @return [String] cputime in
103
+ def cputime
104
+ ps1('cputime')
105
+ end
106
+
107
+ protected
108
+
109
+ # This is for comparison purposes.
110
+ def to_hash
111
+ {
112
+ :name => name,
113
+ :command => command,
114
+ :opts => opts
115
+ }
116
+ end
117
+
118
+ private
119
+
120
+ # Clean some common crud from ENV before spawning a command.
121
+ def with_clean_env(env)
122
+ ENV.restore do
123
+ ENV.delete_if do |k,_|
124
+ k.start_with?('BUNDLE_') ||
125
+ k == 'RUBY' ||
126
+ k.start_with?('GEM_')
127
+ end
128
+ ENV.update(env)
129
+ yield
130
+ end
131
+ end
132
+
133
+ def start
134
+ raise 'already started' if @handler
135
+
136
+ @started_at = Time.now
137
+
138
+ stdin, stdout, pid = Dir.chdir command.dir do
139
+ with_clean_env(command.env) do
140
+ PTY.spawn(command.commandline)
141
+ end
142
+ end
143
+
144
+ @stdin = stdin
145
+ @stdout = stdout
146
+ @pid = pid
147
+
148
+ logger.info { "in #{File.expand_path(command.dir)}" }
149
+ logger.info { "with #{command.env.to_a.map{|k,v| "#{k}=#{v}"}.join(' ')}" }
150
+ logger.info { command }
151
+ logger.info { "started process #{pid}" }
152
+
153
+ @handler = EventMachine.attach(stdout, PTYHandler, self)
154
+ end
155
+
156
+ def try_kill(signals)
157
+ if (signals.size > 0)
158
+ signal = signals.shift
159
+ logger.info "sending #{signal} to #{pid}"
160
+ ::Process.kill(signal, pid) rescue nil
161
+
162
+ check(20, 0.1) do
163
+ try_kill(signals)
164
+ end
165
+ else
166
+ on_exit(nil)
167
+ end
168
+ end
169
+
170
+ def check(tries, period, &on_fail)
171
+ if tries <= 0
172
+ on_fail.call
173
+ return
174
+ end
175
+
176
+ EventMachine.add_timer(period) do
177
+ pid2, status = ::Process.waitpid2(pid, ::Process::WNOHANG)
178
+ if !pid2
179
+ check(tries - 1, period, &on_fail)
180
+ else
181
+ on_exit(status)
182
+ end
183
+ end
184
+ end
185
+
186
+ def on_exit(status)
187
+ begin
188
+ if status == nil
189
+ logger.warn { "error killing process #{pid}" }
190
+ elsif status.signaled?
191
+ signo = status.termsig
192
+ signm = SignalException.new(signo).signm
193
+ logger.warn { "process #{pid} exited with uncaught signal #{signm} (#{signo})" }
194
+ elsif status.exited?
195
+ logger.info { "process #{pid} exited #{status.exitstatus}" }
196
+ else
197
+ logger.warn { "process #{pid} exited #{status.inspect}" }
198
+ end
199
+ ensure
200
+ @handler = nil
201
+ fire(:exit)
202
+ end
203
+ end
204
+ end
205
+ end
206
+ end
@@ -0,0 +1,46 @@
1
+ #
2
+ # Copyright (C) 2012 Common Ground Publishing
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy of
5
+ # this software and associated documentation files (the "Software"), to deal in
6
+ # the Software without restriction, including without limitation the rights to
7
+ # use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
8
+ # the Software, and to permit persons to whom the Software is furnished to do so,
9
+ # subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in all
12
+ # copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
16
+ # FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
17
+ # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
18
+ # IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
19
+ # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20
+ #
21
+ module Console
22
+ module Mux
23
+ # Handle input from a PTY that has been connected to EventMachine
24
+ module PTYHandler
25
+ include EventMachine::Protocols::LineText2
26
+
27
+ attr_reader :parent
28
+
29
+ # @param [Object] parent unbind and receive_line events are
30
+ # forwarded to this object
31
+ def initialize(parent)
32
+ super()
33
+ @parent = parent
34
+ end
35
+
36
+ def unbind
37
+ parent.unbind
38
+ super
39
+ end
40
+
41
+ def receive_line(line)
42
+ parent.receive_line(line)
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,41 @@
1
+ #
2
+ # Copyright (C) 2012 Common Ground Publishing
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy of
5
+ # this software and associated documentation files (the "Software"), to deal in
6
+ # the Software without restriction, including without limitation the rights to
7
+ # use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
8
+ # the Software, and to permit persons to whom the Software is furnished to do so,
9
+ # subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in all
12
+ # copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
16
+ # FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
17
+ # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
18
+ # IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
19
+ # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20
+ #
21
+ module Console
22
+ module Mux
23
+ # Array that limits itself to some fixed size. Newly added elements
24
+ # kick out the oldest element when at the limit.
25
+ class RollingArray < Array
26
+ attr_accessor :maxsize
27
+
28
+ # @param maxsize the maximum size of the array
29
+ def initialize(maxsize)
30
+ @maxsize = maxsize
31
+ end
32
+
33
+ # Append an element to the array. If the size of the array would
34
+ # overflow the maxsize, the array is shifted.
35
+ def <<(other)
36
+ super
37
+ shift if size > maxsize
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,74 @@
1
+ #
2
+ # Copyright (C) 2012 Common Ground Publishing
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy of
5
+ # this software and associated documentation files (the "Software"), to deal in
6
+ # the Software without restriction, including without limitation the rights to
7
+ # use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
8
+ # the Software, and to permit persons to whom the Software is furnished to do so,
9
+ # subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in all
12
+ # copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
16
+ # FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
17
+ # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
18
+ # IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
19
+ # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20
+ #
21
+ module Console
22
+ module Mux
23
+ class RunWith
24
+ class << self
25
+ # A filter for running a command with RVM shell. Looks at the
26
+ # :ruby option.
27
+ #
28
+ # @param [Command] c
29
+ # @param [String] str the command string
30
+ def rvm_shell(c, str)
31
+ rvm_command = 'rvm-shell '
32
+ rvm_command += "'#{c[:ruby]}' " if c[:ruby]
33
+ rvm_command += "-c '#{str}'"
34
+ rvm_command
35
+ end
36
+
37
+ # A filter for running a command with rbenv. Sets RBENV_VERSION
38
+ # environment var will be set to the value of the :ruby
39
+ # option.
40
+ #
41
+ # @param [Command] c
42
+ # @param [String] str the command string
43
+ def rbenv(c, str)
44
+ c.env['RBENV_VERSION'] = c[:ruby]
45
+
46
+ # Strip out any rbenv version bin path. Assumes rbenv is
47
+ # installed in .rbenv. This is necessary because if we run
48
+ # console-mux via an rbenv shim, that shim first tacks on
49
+ # the bin path to the in-use ruby version prior to exec'ing
50
+ # the actual console-mux... But we don't want that path to
51
+ # follow down to sub processes spawned by console-mux.
52
+ c.env['PATH'] =
53
+ ENV['PATH'].
54
+ split(File::PATH_SEPARATOR).
55
+ reject{|p| p =~ %r{.rbenv/versions}}.
56
+ join(File::PATH_SEPARATOR)
57
+
58
+ str
59
+ end
60
+
61
+ # A filter for running a command with bundle exec. Ensures
62
+ # the BUNDLE_GEMFILE environment variable is cleared before
63
+ # running bundle exec.
64
+ def bundle_exec(c, str)
65
+ "bundle exec #{str}"
66
+ end
67
+
68
+ def bundle_exec_sh(c, str)
69
+ "#{::Console::Mux::BUNDLE_EXEC_SH} #{str}"
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,85 @@
1
+ #
2
+ # Copyright (C) 2012 Common Ground Publishing
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy of
5
+ # this software and associated documentation files (the "Software"), to deal in
6
+ # the Software without restriction, including without limitation the rights to
7
+ # use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
8
+ # the Software, and to permit persons to whom the Software is furnished to do so,
9
+ # subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in all
12
+ # copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
16
+ # FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
17
+ # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
18
+ # IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
19
+ # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20
+ #
21
+ module Console
22
+ module Mux
23
+ # The interactive console is run within an instance of Shell. All
24
+ # instance methods are thus available as user commands.
25
+ class Shell
26
+ attr_reader :console
27
+
28
+ class << self
29
+ def delegate(obj_getter, method_syms)
30
+ method_syms.each do |sym|
31
+ send(:define_method, sym) do |*args, &block|
32
+ obj = send(obj_getter)
33
+ obj.send(sym, *args, &block)
34
+ end
35
+ end
36
+ end
37
+
38
+ def delegate_ok(obj_getter, method_syms)
39
+ method_syms.each do |sym|
40
+ send(:define_method, sym) do |*args, &block|
41
+ obj = send(obj_getter)
42
+ obj.send(sym, *args, &block)
43
+ :ok
44
+ end
45
+ end
46
+ end
47
+ end
48
+
49
+ def initialize(console)
50
+ @console = console
51
+ end
52
+
53
+ def commands
54
+ console.commands
55
+ end
56
+
57
+ delegate :console, [
58
+ :default_options
59
+ ]
60
+
61
+ delegate_ok :console, [
62
+ :status,
63
+ :lastlog,
64
+ :run,
65
+ :start,
66
+ :stop,
67
+ :restart,
68
+ :set_default_options,
69
+ :shutdown
70
+ ]
71
+
72
+ # Yield the block with +hash+ merged into the default options.
73
+ # The options are restored.
74
+ def with_defaults(hash={})
75
+ orig_options = default_options
76
+ set_default_options(default_options.merge(hash))
77
+ begin
78
+ yield
79
+ ensure
80
+ set_default_options(orig_options)
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end