ruboty-exec_command 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +21 -3
- data/commands/example/sleep +1 -1
- data/lib/ruboty/exec_command/actions/command.rb +14 -10
- data/lib/ruboty/exec_command/command.rb +38 -10
- data/lib/ruboty/exec_command/command_slot.rb +6 -7
- data/lib/ruboty/exec_command/version.rb +1 -1
- data/lib/ruboty/handlers/command.rb +2 -1
- data/spec/lib/ruboty/handlers/command_spec.rb +18 -10
- metadata +18 -18
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ed267f398d5e36b654ce35cece09794eec585a8b
|
4
|
+
data.tar.gz: 36b55cda29a3336492150319973786b11805eab7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d3616c0a4b014f983cf81f8838b5404ac654e391fa63dc984409b602adcff3809d9235ebbc58db2e2fba0a1a07a6fdf30ca5a189c2b6c361200fe9cce9fec6bf
|
7
|
+
data.tar.gz: 203d2ece0eefb17fd223a6740957ba576725a03c6acca090b3c5682d00efb0b08d5b69bc688563c1952d0e30edbdce0f422f67c1bab737a8145f07af2f0d2f52
|
data/README.md
CHANGED
@@ -9,8 +9,9 @@ When you say '@bot: example hello', ruboty runs the command
|
|
9
9
|
$PWD/commands/example/hello or $RUBOTY_ROOT/commands/example/hello
|
10
10
|
if RUBOTY_ROOT is defined.
|
11
11
|
|
12
|
-
|
13
|
-
|
12
|
+
All of commands under `commands/` directory are executed with `-h`
|
13
|
+
option once to gather their usage information used for help message
|
14
|
+
of ruboty. Please implement `-h` option into the commands.
|
14
15
|
|
15
16
|
## Command Controll
|
16
17
|
|
@@ -38,9 +39,26 @@ Or install it yourself as:
|
|
38
39
|
|
39
40
|
$ gem install ruboty-exec_command
|
40
41
|
|
42
|
+
## Environment Variables
|
43
|
+
|
44
|
+
| Name | Description | Default |
|
45
|
+
|--------------------------|------------------------------|-------------------|
|
46
|
+
| LOG_LEVEL | log level | 1 (Logger::INFO) |
|
47
|
+
| EXEC_COMMAND_OUTPUT_ROOT | The command output root | logs/exec_command |
|
48
|
+
| EXEC_COMMAND_OUTPUT_DIR | The command output directory | "#{root}/%Y-%m |
|
49
|
+
|
41
50
|
## History
|
42
51
|
|
43
|
-
- 0.
|
52
|
+
- 0.1.2:
|
53
|
+
- fix: not to match with shorter name commands
|
54
|
+
- add: symlink to output files to tail -F easily
|
55
|
+
- add: Bundler.with_clean_env to run a ruby script with bundler inside ruboty
|
56
|
+
- add: logging to see what's going on with ruboty
|
57
|
+
|
58
|
+
- 0.1.1:
|
59
|
+
- each message contains PID
|
60
|
+
|
61
|
+
- 0.1.0:
|
44
62
|
- command runs as a back ground thread
|
45
63
|
- command accepts option arguments
|
46
64
|
|
data/commands/example/sleep
CHANGED
@@ -19,16 +19,16 @@ module Ruboty
|
|
19
19
|
def kill_command
|
20
20
|
# TODO: command list lock
|
21
21
|
# kill running process, command is "kill command <index>"
|
22
|
-
|
23
|
-
|
24
|
-
if killed.nil?
|
22
|
+
if command_slot.kill(message.body.split.last.to_i).nil?
|
25
23
|
message.reply("Command [#{message.body.split.last}] not found.")
|
26
24
|
end
|
27
25
|
end
|
28
26
|
|
29
27
|
def run_and_monitor(comm)
|
30
28
|
pid = command_slot.run(comm)
|
31
|
-
|
29
|
+
msg = "[#{comm.command_name}] invoked. PID: #{comm.pid}"
|
30
|
+
Ruboty.logger.info { "[EXEC_COMMAND] #{msg}" }
|
31
|
+
message.reply(msg)
|
32
32
|
|
33
33
|
# Waiter thread
|
34
34
|
thread = Thread.new do
|
@@ -36,15 +36,19 @@ module Ruboty
|
|
36
36
|
command_slot.forget(pid)
|
37
37
|
|
38
38
|
if status.exitstatus == 0
|
39
|
-
|
39
|
+
msg = "[#{comm.command_name}] completed successfully. PID: #{comm.pid}"
|
40
|
+
Ruboty.logger.info { "[EXEC_COMMAND] #{msg}" }
|
41
|
+
message.reply(msg)
|
40
42
|
message.reply(comm.stdout_log.chomp)
|
41
43
|
elsif status.signaled?
|
42
|
-
|
44
|
+
msg = "[#{comm.command_name}] killed by signal #{status.termsig} PID: #{comm.pid}"
|
45
|
+
Ruboty.logger.info { "[EXEC_COMMAND] #{msg}" }
|
46
|
+
message.reply(msg)
|
43
47
|
else
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
+
msg = "[#{comm.command_name}] exit status with #{status} PID: #{comm.pid}\n" +
|
49
|
+
comm.stdout_log + "stderr: " + comm.stderr_log.chomp
|
50
|
+
Ruboty.logger.info { "[EXEC_COMMAND] #{msg}" }
|
51
|
+
message.reply(msg)
|
48
52
|
end
|
49
53
|
end
|
50
54
|
|
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
|
1
3
|
module Ruboty
|
2
4
|
module ExecCommand
|
3
5
|
class Command
|
@@ -11,8 +13,8 @@ module Ruboty
|
|
11
13
|
"#{ruboty_root}/commands"
|
12
14
|
end
|
13
15
|
|
14
|
-
def
|
15
|
-
"#{ruboty_root}/logs/exec_command"
|
16
|
+
def output_root
|
17
|
+
ENV['EXEC_COMMAND_OUTPUT_ROOT'] || "#{ruboty_root}/logs/exec_command"
|
16
18
|
end
|
17
19
|
|
18
20
|
def command?(path)
|
@@ -86,28 +88,38 @@ module Ruboty
|
|
86
88
|
Time.now.strftime "%Y-%m-%d_%H:%M:%S"
|
87
89
|
end
|
88
90
|
|
89
|
-
def
|
90
|
-
d = "#{self.class.
|
91
|
+
def output_dir
|
92
|
+
d = ENV['EXEC_COMMAND_OUTPUT_DIR'] || "#{self.class.output_root}/#{this_month}"
|
91
93
|
FileUtils.mkdir_p(d) if not Dir.exists?(d)
|
92
94
|
d
|
93
95
|
end
|
94
96
|
|
97
|
+
# symlink to output_file_name so that we can easily tail -F
|
98
|
+
def symlink_file_name
|
99
|
+
%Q(#{output_dir}/#{command_name.gsub(" ", "_")})
|
100
|
+
end
|
101
|
+
|
95
102
|
def output_file_name
|
96
|
-
%Q(#{
|
103
|
+
%Q(#{output_dir}/#{command_name.gsub(" ", "_")}-#{this_time})
|
104
|
+
end
|
105
|
+
|
106
|
+
# return symlink output file name [stdout, stderr]
|
107
|
+
def symlink_files
|
108
|
+
["#{symlink_file_name}.out", "#{symlink_file_name}.err"]
|
97
109
|
end
|
98
110
|
|
111
|
+
# return temporary output file name [stdout, stderr]
|
99
112
|
def output_files
|
100
|
-
# return temporary output file IO objects [stdout, stderr]
|
101
113
|
["#{output_file_name}.out", "#{output_file_name}.err"]
|
102
114
|
end
|
103
115
|
|
116
|
+
# return contents of stdout
|
104
117
|
def stdout_log
|
105
|
-
# return contents of stdout
|
106
118
|
File.open(output_files[0]).read
|
107
119
|
end
|
108
120
|
|
121
|
+
# return contents of stderr
|
109
122
|
def stderr_log
|
110
|
-
# return contents of stderr
|
111
123
|
File.open(output_files[1]).read
|
112
124
|
end
|
113
125
|
|
@@ -118,14 +130,30 @@ module Ruboty
|
|
118
130
|
def run_bg(args=[])
|
119
131
|
stdout, stderr = output_files
|
120
132
|
@start_at = this_time
|
121
|
-
|
122
|
-
|
133
|
+
stdout_link, stderr_link = symlink_files
|
134
|
+
FileUtils.ln_sf(stdout, stdout_link)
|
135
|
+
FileUtils.ln_sf(stderr, stderr_link)
|
136
|
+
cmd = %Q(#{absolute_path} #{args.join(" ")})
|
137
|
+
with_clean_env do
|
138
|
+
@pid = Process.spawn(cmd, pgroup: true, out: stdout, err: stderr)
|
139
|
+
end
|
140
|
+
Ruboty.logger.debug { "[EXEC_COMMAND] Invoked `#{cmd}`. PID: #{@pid}" }
|
141
|
+
@pid
|
123
142
|
end
|
124
143
|
|
125
144
|
def help
|
126
145
|
run(args=['-h']).chomp
|
127
146
|
end
|
128
147
|
|
148
|
+
def with_clean_env(&block)
|
149
|
+
if defined?(Bundler)
|
150
|
+
Bundler.with_clean_env do
|
151
|
+
yield
|
152
|
+
end
|
153
|
+
else
|
154
|
+
yield
|
155
|
+
end
|
156
|
+
end
|
129
157
|
end
|
130
158
|
end
|
131
159
|
end
|
@@ -28,9 +28,9 @@ module Ruboty
|
|
28
28
|
found = @commands.index { |c| c.pid == idx_or_pid }
|
29
29
|
|
30
30
|
if found.nil?
|
31
|
+
# look for the command with index
|
31
32
|
i = idx_or_pid.to_i
|
32
|
-
|
33
|
-
if i <= 0 or i > num_commands
|
33
|
+
if i <= 0 or i > @commands.size
|
34
34
|
nil
|
35
35
|
else
|
36
36
|
@commands[i-1]
|
@@ -49,10 +49,8 @@ module Ruboty
|
|
49
49
|
if @commands.size == 0
|
50
50
|
"No command running."
|
51
51
|
else
|
52
|
-
|
53
|
-
|
54
|
-
number += 1
|
55
|
-
"#{number}: #{c.command_name} (PID[#{c.pid}], started at #{c.start_at})\n"
|
52
|
+
@commands.map.with_index do |c, number|
|
53
|
+
"#{number+1}: #{c.command_name} (PID[#{c.pid}], started at #{c.start_at})\n"
|
56
54
|
end.join.chomp
|
57
55
|
end
|
58
56
|
end
|
@@ -62,8 +60,9 @@ module Ruboty
|
|
62
60
|
unless command.nil?
|
63
61
|
Process.kill(-9, command.pid) # kill process group
|
64
62
|
forget(command.pid)
|
63
|
+
else
|
64
|
+
false
|
65
65
|
end
|
66
|
-
command
|
67
66
|
end
|
68
67
|
end
|
69
68
|
end
|
@@ -14,10 +14,11 @@ module Ruboty
|
|
14
14
|
# under "commands" directory. The path name to the
|
15
15
|
# executable command is gonna be a command name.
|
16
16
|
# i.e. commands/server/monitor => /server monitor/
|
17
|
+
# All of commands are called with -h option on the startup.
|
17
18
|
# The command should return a usage with -h option
|
18
19
|
def self.register_commands
|
19
20
|
Ruboty::ExecCommand::Command.all.each do |e|
|
20
|
-
on /#{e.command_name}/i, name: "command_handler", description: e.help
|
21
|
+
on /#{e.command_name}(?:\z|\s+)/i, name: "command_handler", description: e.help
|
21
22
|
end
|
22
23
|
end
|
23
24
|
|
@@ -19,7 +19,7 @@ describe Ruboty::Handlers::Command do
|
|
19
19
|
end
|
20
20
|
|
21
21
|
let(:said_to_sleep) do
|
22
|
-
"@ruboty example sleep
|
22
|
+
"@ruboty example sleep 5"
|
23
23
|
end
|
24
24
|
|
25
25
|
let(:said_to_kill) do
|
@@ -31,15 +31,15 @@ describe Ruboty::Handlers::Command do
|
|
31
31
|
end
|
32
32
|
|
33
33
|
let(:replied) do
|
34
|
-
|
34
|
+
/\[example hello\] invoked. PID: \d+/
|
35
35
|
end
|
36
36
|
|
37
37
|
let(:replied_sleep) do
|
38
|
-
|
38
|
+
/\[example sleep\] invoked. PID: \d+/
|
39
39
|
end
|
40
40
|
|
41
41
|
let(:replied_success) do
|
42
|
-
|
42
|
+
/\[example hello\] completed successfully. PID: \d+/
|
43
43
|
end
|
44
44
|
|
45
45
|
let(:replied_stdout) do
|
@@ -47,7 +47,7 @@ describe Ruboty::Handlers::Command do
|
|
47
47
|
end
|
48
48
|
|
49
49
|
let(:replied_after_kill) do
|
50
|
-
|
50
|
+
/\[example sleep\] killed by signal 9 PID: \d+/
|
51
51
|
end
|
52
52
|
|
53
53
|
def reply_data(body, original_body)
|
@@ -64,6 +64,13 @@ describe Ruboty::Handlers::Command do
|
|
64
64
|
}
|
65
65
|
end
|
66
66
|
|
67
|
+
|
68
|
+
def should_receive_body(reply)
|
69
|
+
robot.should_receive(:say) do |args, options|
|
70
|
+
args[:body].should match(reply)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
67
74
|
before do
|
68
75
|
ENV['RUBOTY_ROOT'] = Dir.pwd
|
69
76
|
end
|
@@ -75,8 +82,9 @@ describe Ruboty::Handlers::Command do
|
|
75
82
|
end
|
76
83
|
|
77
84
|
it "run example command" do
|
78
|
-
robot.should_receive(:say).with(reply_data(replied, said))
|
79
|
-
|
85
|
+
#robot.should_receive(:say).with(reply_data(replied, said))
|
86
|
+
should_receive_body(replied)
|
87
|
+
should_receive_body(replied_success)
|
80
88
|
robot.should_receive(:say).with(reply_data(replied_stdout, said))
|
81
89
|
robot.receive(body: said, from: from, to: to)
|
82
90
|
end
|
@@ -90,8 +98,8 @@ describe Ruboty::Handlers::Command do
|
|
90
98
|
it "run kill command" do
|
91
99
|
thread = Thread.new do
|
92
100
|
ENV['RUBOTY_ENV'] = "blocked_test"
|
93
|
-
|
94
|
-
|
101
|
+
should_receive_body(replied_sleep)
|
102
|
+
should_receive_body(replied_after_kill)
|
95
103
|
robot.receive(body: said_to_sleep, from: from, to: to)
|
96
104
|
end
|
97
105
|
|
@@ -103,7 +111,7 @@ describe Ruboty::Handlers::Command do
|
|
103
111
|
# Wait killed message
|
104
112
|
thread.join
|
105
113
|
end
|
106
|
-
|
114
|
+
|
107
115
|
after do
|
108
116
|
ENV['RUBOTY_ENV'] = "test"
|
109
117
|
end
|
metadata
CHANGED
@@ -1,55 +1,55 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ruboty-exec_command
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nakai Tooru
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
11
|
+
date: 2015-06-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: ruboty
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- -
|
17
|
+
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
19
|
version: '0'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- -
|
24
|
+
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '0'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: bundler
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- - ~>
|
31
|
+
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
33
|
version: '1.7'
|
34
34
|
type: :development
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
|
-
- - ~>
|
38
|
+
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '1.7'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: rake
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
|
-
- - ~>
|
45
|
+
- - "~>"
|
46
46
|
- !ruby/object:Gem::Version
|
47
47
|
version: '10.0'
|
48
48
|
type: :development
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
|
-
- - ~>
|
52
|
+
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '10.0'
|
55
55
|
- !ruby/object:Gem::Dependency
|
@@ -70,42 +70,42 @@ dependencies:
|
|
70
70
|
name: guard-rspec
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
72
72
|
requirements:
|
73
|
-
- -
|
73
|
+
- - ">="
|
74
74
|
- !ruby/object:Gem::Version
|
75
75
|
version: '0'
|
76
76
|
type: :development
|
77
77
|
prerelease: false
|
78
78
|
version_requirements: !ruby/object:Gem::Requirement
|
79
79
|
requirements:
|
80
|
-
- -
|
80
|
+
- - ">="
|
81
81
|
- !ruby/object:Gem::Version
|
82
82
|
version: '0'
|
83
83
|
- !ruby/object:Gem::Dependency
|
84
84
|
name: growl
|
85
85
|
requirement: !ruby/object:Gem::Requirement
|
86
86
|
requirements:
|
87
|
-
- -
|
87
|
+
- - ">="
|
88
88
|
- !ruby/object:Gem::Version
|
89
89
|
version: '0'
|
90
90
|
type: :development
|
91
91
|
prerelease: false
|
92
92
|
version_requirements: !ruby/object:Gem::Requirement
|
93
93
|
requirements:
|
94
|
-
- -
|
94
|
+
- - ">="
|
95
95
|
- !ruby/object:Gem::Version
|
96
96
|
version: '0'
|
97
97
|
- !ruby/object:Gem::Dependency
|
98
98
|
name: simplecov
|
99
99
|
requirement: !ruby/object:Gem::Requirement
|
100
100
|
requirements:
|
101
|
-
- -
|
101
|
+
- - ">="
|
102
102
|
- !ruby/object:Gem::Version
|
103
103
|
version: '0'
|
104
104
|
type: :development
|
105
105
|
prerelease: false
|
106
106
|
version_requirements: !ruby/object:Gem::Requirement
|
107
107
|
requirements:
|
108
|
-
- -
|
108
|
+
- - ">="
|
109
109
|
- !ruby/object:Gem::Version
|
110
110
|
version: '0'
|
111
111
|
description: ''
|
@@ -115,7 +115,7 @@ executables: []
|
|
115
115
|
extensions: []
|
116
116
|
extra_rdoc_files: []
|
117
117
|
files:
|
118
|
-
- .gitignore
|
118
|
+
- ".gitignore"
|
119
119
|
- Gemfile
|
120
120
|
- Guardfile
|
121
121
|
- LICENSE.txt
|
@@ -144,17 +144,17 @@ require_paths:
|
|
144
144
|
- lib
|
145
145
|
required_ruby_version: !ruby/object:Gem::Requirement
|
146
146
|
requirements:
|
147
|
-
- -
|
147
|
+
- - ">="
|
148
148
|
- !ruby/object:Gem::Version
|
149
149
|
version: '0'
|
150
150
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
151
151
|
requirements:
|
152
|
-
- -
|
152
|
+
- - ">="
|
153
153
|
- !ruby/object:Gem::Version
|
154
154
|
version: '0'
|
155
155
|
requirements: []
|
156
156
|
rubyforge_project:
|
157
|
-
rubygems_version: 2.
|
157
|
+
rubygems_version: 2.4.5
|
158
158
|
signing_key:
|
159
159
|
specification_version: 4
|
160
160
|
summary: Add command to ruboty as a handler
|