ruboty-exec_command 0.0.2 → 0.1.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: c22adae587104ba29fd27dea7c0f6d391f5d4db6
4
- data.tar.gz: b4ab84f740da52a7bc08f6e8974295409ac195bb
3
+ metadata.gz: 6f31fbeb824f00f5843ea7a7168363a33539b82f
4
+ data.tar.gz: d7fabdad9e511f00165e8096e828e83f1af70023
5
5
  SHA512:
6
- metadata.gz: 761fa8ddc718ed11e1c0f8effee917c663a3b2e43f7bf9faede6a7f654532e1b154e7c6bbeadf01540500ab7ede514ba1a761dd5adce574ea33463c28bba4768
7
- data.tar.gz: 9d03859456db4d1124f6a472ae811f4d0dd32f9956e2a6dae0f5cbf88016d8bfa3acdc21ef357cd445d1dbfcae083cb175e7341529dbdb9009891c60f2901d23
6
+ metadata.gz: 45bc5f831cdc072533200b0ce52c2a4dadc83cb9808c7620c407de2fbf4e64a30b7de6660c5c5b651b0c03693d32c9abab394577d1967b53891b30a3a186b3ce
7
+ data.tar.gz: 6c91c79b5694c3017427aeea2f804395f662ff8bc949aa51a72fc1bf99436faa9fb43f3495162eeba4fc76cd174211d57466420764a83f6de57584360ea0cc6a
data/.gitignore CHANGED
@@ -12,3 +12,6 @@
12
12
  *.o
13
13
  *.a
14
14
  mkmf.log
15
+
16
+ # exec_command log files
17
+ /logs
data/README.md CHANGED
@@ -12,6 +12,16 @@ if RUBOTY_ROOT is defined.
12
12
  For your convinience, please implement -h option into the
13
13
  command. The usage will be used for help message of ruboty.
14
14
 
15
+ ## Command Controll
16
+
17
+ List running commands:
18
+
19
+ > ruboty command list
20
+
21
+ Kill running command, you can specify command index in the result of ```command list``` or PID
22
+
23
+ > ruboty command kill <index|PID>
24
+
15
25
  ## Installation
16
26
 
17
27
  Add this line to your application's Gemfile:
@@ -28,6 +38,12 @@ Or install it yourself as:
28
38
 
29
39
  $ gem install ruboty-exec_command
30
40
 
41
+ ## History
42
+
43
+ - 0.0.4:
44
+ - command runs as a back ground thread
45
+ - command accepts option arguments
46
+
31
47
  ## Contributing
32
48
 
33
49
  1. Fork it ( https://github.com/[my-github-username]/ruboty-exec_command/fork )
@@ -1,3 +1,5 @@
1
1
  #!/bin/bash
2
2
 
3
- echo hello!
3
+ [ "$1" == "-h" ] && echo "say hello!" && exit
4
+
5
+ echo hello $*!
@@ -0,0 +1,8 @@
1
+ #!/bin/bash
2
+
3
+ if [ "$1" == "-h" ]; then
4
+ echo "sleep"
5
+ else
6
+ echo "Have a good sleep." 1>&2
7
+ sleep $*
8
+ fi
@@ -4,8 +4,53 @@ module Ruboty
4
4
  class Command < Ruboty::Actions::Base
5
5
  def call
6
6
  # TODO: add timeout
7
- extension = Ruboty::ExecCommand::Command.new(command_args: command_body)
8
- message.reply(extension.run.chomp)
7
+ c = Ruboty::ExecCommand::Command.new(command_args: command_body)
8
+ run_and_monitor(c)
9
+ end
10
+
11
+ def command_slot
12
+ message.robot.brain.data[:command_slot] ||= Ruboty::ExecCommand::CommandSlot.new
13
+ end
14
+
15
+ def list_commands
16
+ message.reply(command_slot.list_commands)
17
+ end
18
+
19
+ def kill_command
20
+ # TODO: command list lock
21
+ # kill running process, command is "kill command <index>"
22
+ killed = command_slot.kill(message.body.split.last.to_i)
23
+
24
+ if killed.nil?
25
+ message.reply("Command [#{message.body.split.last}] not found.")
26
+ end
27
+ end
28
+
29
+ def run_and_monitor(comm)
30
+ pid = command_slot.run(comm)
31
+ message.reply("[#{comm.command_name}] invoked.")
32
+
33
+ # Waiter thread
34
+ thread = Thread.new do
35
+ ignore_pid, status = Process.wait2(pid)
36
+ command_slot.forget(pid)
37
+
38
+ if status.exitstatus == 0
39
+ message.reply("[#{comm.command_name}] completed successfully.")
40
+ message.reply(comm.stdout_log.chomp)
41
+ elsif status.signaled?
42
+ message.reply("[#{comm.command_name}] killed by signal #{status.termsig}")
43
+ else
44
+ message.reply("[#{comm.command_name}] exit status with #{status}\n" +
45
+ comm.stdout_log +
46
+ "stderr: " + comm.stderr_log.chomp
47
+ )
48
+ end
49
+ end
50
+
51
+ if ENV['RUBOTY_ENV'] == 'blocked_test'
52
+ thread.join
53
+ end
9
54
  end
10
55
 
11
56
  def robot_prefix_pattern
@@ -3,8 +3,16 @@ module Ruboty
3
3
  class Command
4
4
 
5
5
  class << self
6
+ def ruboty_root
7
+ "#{ENV['RUBOTY_ROOT'] || Dir.pwd}"
8
+ end
9
+
6
10
  def command_root
7
- "#{ENV['RUBOTY_ROOT'] || Dir.pwd}/commands"
11
+ "#{ruboty_root}/commands"
12
+ end
13
+
14
+ def log_root
15
+ "#{ruboty_root}/logs/exec_command"
8
16
  end
9
17
 
10
18
  def command?(path)
@@ -27,34 +35,93 @@ module Ruboty
27
35
  def initialize(args={})
28
36
  args = { absolute_path: nil, command_args: nil }.merge(args)
29
37
  @absolute_path = args[:absolute_path]
30
- @command_args = args[:command_args]
38
+ @command_args = args[:command_args].split if not args[:command_args].nil?
39
+ @pid = nil
40
+ @start_at = nil
31
41
  end
32
42
 
43
+ attr_reader :pid
44
+ attr_reader :start_at
45
+
33
46
  def absolute_path
34
- @absolute_path ||= command2path
47
+ @absolute_path ||= command2path[0]
35
48
  end
36
49
 
37
50
  def relative_path
38
51
  @relative_path ||= absolute_path.sub(/^#{self.class.command_root}\//,"")
39
52
  end
40
53
 
41
- def command_args
42
- @command_args ||= relative_path.gsub('/', ' ')
54
+ def command_name
55
+ @command_name ||= relative_path.gsub('/', ' ')
56
+ end
57
+
58
+ def __command2path(path, args)
59
+ if self.class.command?(path)
60
+ [path, args]
61
+ else
62
+ if args == []
63
+ # command not found
64
+ return ["", ""]
65
+ else
66
+ __command2path("#{path}/#{args[0]}", args.slice(1, args.length))
67
+ end
68
+ end
43
69
  end
44
70
 
45
71
  def command2path
46
72
  path = self.class.command_root
47
- @command_args.split(" ").each do |arg|
48
- path = "#{path}/#{arg}"
49
- return path if self.class.command?(path)
50
- end
51
- ""
73
+ __command2path "#{path}/#{@command_args[0]}",
74
+ @command_args.slice(1, @command_args.length)
75
+ end
76
+
77
+ def opt_args
78
+ @opt_args ||= command2path[1]
79
+ end
80
+
81
+ def this_month
82
+ Time.now.strftime "%Y-%m"
83
+ end
84
+
85
+ def this_time
86
+ Time.now.strftime "%Y-%m-%d_%H:%M:%S"
87
+ end
88
+
89
+ def log_dir
90
+ d = "#{self.class.log_root}/#{this_month}"
91
+ FileUtils.mkdir_p(d) if not Dir.exists?(d)
92
+ d
93
+ end
94
+
95
+ def output_file_name
96
+ %Q(#{log_dir}/#{command_name.gsub(" ", "_")}-#{this_time})
97
+ end
98
+
99
+ def output_files
100
+ # return temporary output file IO objects [stdout, stderr]
101
+ ["#{output_file_name}.out", "#{output_file_name}.err"]
102
+ end
103
+
104
+ def stdout_log
105
+ # return contents of stdout
106
+ File.open(output_files[0]).read
107
+ end
108
+
109
+ def stderr_log
110
+ # return contents of stderr
111
+ File.open(output_files[1]).read
52
112
  end
53
113
 
54
114
  def run(args=[])
55
115
  `#{absolute_path} #{args.join(" ")}`
56
116
  end
57
117
 
118
+ def run_bg(args=[])
119
+ stdout, stderr = output_files
120
+ @start_at = this_time
121
+ @pid = Process.spawn(%Q(#{absolute_path} #{args.join(" ")}),
122
+ pgroup: true, out: stdout, err: stderr)
123
+ end
124
+
58
125
  def help
59
126
  run(args=['-h']).chomp
60
127
  end
@@ -0,0 +1,70 @@
1
+ module Ruboty
2
+ module ExecCommand
3
+ class CommandSlot
4
+
5
+ def initialize
6
+ @commands = []
7
+ end
8
+
9
+ def running_commands
10
+ @commands
11
+ end
12
+
13
+ def remember(comm)
14
+ # remember
15
+ # comm: command object
16
+ # TODO: add owner info
17
+ @commands << comm
18
+ end
19
+
20
+ def forget(pid)
21
+ # remove thread object
22
+ @commands.delete_if do |c|
23
+ c.pid == pid
24
+ end
25
+ end
26
+
27
+ def command_in_list(idx_or_pid)
28
+ found = @commands.index { |c| c.pid == idx_or_pid }
29
+
30
+ if found.nil?
31
+ i = idx_or_pid.to_i
32
+ num_commands = @commands.size
33
+ if i <= 0 or i > num_commands
34
+ nil
35
+ else
36
+ @commands[i-1]
37
+ end
38
+ else
39
+ @commands[found]
40
+ end
41
+ end
42
+
43
+ def run(command)
44
+ remember(command)
45
+ command.run_bg(command.opt_args)
46
+ end
47
+
48
+ def list_commands
49
+ if @commands.size == 0
50
+ "No command running."
51
+ else
52
+ number = 0
53
+ @commands.map do |c|
54
+ number += 1
55
+ "#{number}: #{c.command_name} (PID[#{c.pid}], started at #{c.start_at})\n"
56
+ end.join.chomp
57
+ end
58
+ end
59
+
60
+ def kill(idx_or_pid)
61
+ command = command_in_list(idx_or_pid)
62
+ unless command.nil?
63
+ Process.kill(-9, command.pid) # kill process group
64
+ forget(command.pid)
65
+ end
66
+ command
67
+ end
68
+ end
69
+ end
70
+ end
@@ -1,5 +1,5 @@
1
1
  module Ruboty
2
2
  module ExecCommand
3
- VERSION = "0.0.2"
3
+ VERSION = "0.1.0"
4
4
  end
5
5
  end
@@ -1,3 +1,4 @@
1
1
  require "ruboty/exec_command/command"
2
+ require "ruboty/exec_command/command_slot"
2
3
  require "ruboty/exec_command/version"
3
4
  require "ruboty/handlers/command"
@@ -3,20 +3,35 @@ require "ruboty/exec_command/actions/command"
3
3
  module Ruboty
4
4
  module Handlers
5
5
  class Command < Base
6
+
7
+ on(/command list/i, name: "list_commands",
8
+ description: "List running commands in background")
9
+
10
+ on(/command kill/i, name: "kill_command",
11
+ description: "command kill <index|PID> ")
12
+
6
13
  # Registering commands. Each command is located
7
14
  # under "commands" directory. The path name to the
8
15
  # executable command is gonna be a command name.
9
- # i.e. commands/server/monitor => /^server monitor.*/
16
+ # i.e. commands/server/monitor => /server monitor/
10
17
  # The command should return a usage with -h option
11
18
  def self.register_commands
12
19
  Ruboty::ExecCommand::Command.all.each do |e|
13
- on /#{e.command_args}/i, name: "command_handler", description: e.help
20
+ on /#{e.command_name}/i, name: "command_handler", description: e.help
14
21
  end
15
22
  end
16
23
 
17
24
  def command_handler(message)
18
25
  Ruboty::ExecCommand::Actions::Command.new(message).call
19
26
  end
27
+
28
+ def list_commands(message)
29
+ Ruboty::ExecCommand::Actions::Command.new(message).list_commands
30
+ end
31
+
32
+ def kill_command(message)
33
+ Ruboty::ExecCommand::Actions::Command.new(message).kill_command
34
+ end
20
35
  end
21
36
  end
22
37
  end
@@ -19,7 +19,6 @@ Gem::Specification.new do |spec|
19
19
  spec.require_paths = ["lib"]
20
20
 
21
21
  spec.add_runtime_dependency "ruboty"
22
- spec.add_runtime_dependency "systemu"
23
22
  spec.add_development_dependency "bundler", "~> 1.7"
24
23
  spec.add_development_dependency "rake", "~> 10.0"
25
24
  spec.add_development_dependency "rspec", "2.14.1"
@@ -0,0 +1,50 @@
1
+ require "spec_helper"
2
+
3
+ describe Ruboty::ExecCommand::CommandSlot do
4
+
5
+ before do
6
+ @slot = Ruboty::ExecCommand::CommandSlot.new
7
+ @command = Ruboty::ExecCommand::Command.new(command_args: "example sleep")
8
+ end
9
+
10
+ describe "#run" do
11
+ it "should count up number of commands" do
12
+ expect { @slot.run(@command) }.to change {
13
+ @slot.running_commands.count
14
+ }.from(0).to(1)
15
+ end
16
+
17
+ it "should return pid" do
18
+ expect(@slot.run(@command)).to be > 0
19
+ end
20
+ end
21
+ end
22
+
23
+
24
+ describe Ruboty::ExecCommand::CommandSlot do
25
+
26
+ before(:each) do
27
+ @slot = Ruboty::ExecCommand::CommandSlot.new
28
+ @command = Ruboty::ExecCommand::Command.new(command_args: "example sleep")
29
+ @slot.remember(@command)
30
+ @command.run_bg(["1"])
31
+ end
32
+
33
+ describe "#forget" do
34
+ it "should count down number of commnads" do
35
+ expect { @command.pid }.not_to be_nil
36
+ expect { @slot.forget(@command.pid) }.to change {@slot.running_commands.count}.from(1).to(0)
37
+ end
38
+ end
39
+
40
+ describe "#command_in_list" do
41
+ it "should return command with pid" do
42
+ expect(@slot.command_in_list(@command.pid).pid).to eq(@command.pid)
43
+ end
44
+
45
+ it "should return command with index" do
46
+ expect(@slot.command_in_list(1).pid).to eq(@command.pid)
47
+ end
48
+ end
49
+
50
+ end
@@ -34,15 +34,16 @@ describe Ruboty::ExecCommand::Command do
34
34
 
35
35
  describe "#command" do
36
36
  it "should return command name" do
37
- expect(@e.command_args).to eq("a b")
37
+ expect(@e.command_name).to eq("a b")
38
38
  end
39
39
  end
40
40
  end
41
41
 
42
+ # convert command args -> absolute path, so this is a test for ruboty action
42
43
  describe Ruboty::ExecCommand::Command do
43
- before do
44
+ before(:each) do
44
45
  ENV['RUBOTY_ROOT'] = Dir.pwd
45
- @c = Ruboty::ExecCommand::Command.new(command_args: "example hello hoge")
46
+ @c = Ruboty::ExecCommand::Command.new(command_args: "example hello hoge -l fuga")
46
47
  end
47
48
 
48
49
  describe "#absolute_path" do
@@ -50,4 +51,10 @@ describe Ruboty::ExecCommand::Command do
50
51
  expect(@c.absolute_path).to eq("#{ENV['RUBOTY_ROOT']}/commands/example/hello")
51
52
  end
52
53
  end
54
+
55
+ describe "#opt_args" do
56
+ it "should return only command options" do
57
+ expect(@c.opt_args).to eq(["hoge", "-l", "fuga"])
58
+ end
59
+ end
53
60
  end
@@ -15,32 +15,117 @@ describe Ruboty::Handlers::Command do
15
15
  end
16
16
 
17
17
  let(:said) do
18
- "@ruboty example hello"
18
+ "@ruboty example hello world"
19
+ end
20
+
21
+ let(:said_to_sleep) do
22
+ "@ruboty example sleep 1"
23
+ end
24
+
25
+ let(:said_to_kill) do
26
+ "@ruboty command kill 1"
27
+ end
28
+
29
+ let(:said_to_list) do
30
+ "@ruboty command list"
19
31
  end
20
32
 
21
33
  let(:replied) do
22
- "hello!"
34
+ "[example hello] invoked."
23
35
  end
24
36
 
25
- before do
26
- ENV['RUBOTY_ROOT'] = Dir.pwd
37
+ let(:replied_sleep) do
38
+ "[example sleep] invoked."
27
39
  end
28
40
 
29
- describe "#command_handler" do
30
- it "run example command" do
31
- robot.should_receive(:say).with(
32
- body: replied,
41
+ let(:replied_success) do
42
+ "[example hello] completed successfully."
43
+ end
44
+
45
+ let(:replied_stdout) do
46
+ "hello world!"
47
+ end
48
+
49
+ let(:replied_after_kill) do
50
+ "[example sleep] killed by signal 9"
51
+ end
52
+
53
+ def reply_data(body, original_body)
54
+ {
55
+ body: body,
33
56
  from: to,
34
57
  to: from,
35
58
  original: {
36
- body: said,
59
+ body: original_body,
37
60
  from: from,
38
61
  robot: robot,
39
62
  to: to,
40
- },
41
- )
63
+ }
64
+ }
65
+ end
66
+
67
+ before do
68
+ ENV['RUBOTY_ROOT'] = Dir.pwd
69
+ end
70
+
71
+ describe "#command_handler" do
72
+ before do
73
+ # block waiter thread in Ruboty::ExecCommand::Actions::Command.run_and_monitor
74
+ ENV['RUBOTY_ENV'] = "blocked_test"
75
+ end
76
+
77
+ it "run example command" do
78
+ robot.should_receive(:say).with(reply_data(replied, said))
79
+ robot.should_receive(:say).with(reply_data(replied_success, said))
80
+ robot.should_receive(:say).with(reply_data(replied_stdout, said))
42
81
  robot.receive(body: said, from: from, to: to)
43
82
  end
83
+
84
+ after do
85
+ ENV['RUBOTY_ENV'] = "test"
86
+ end
87
+ end
88
+
89
+ describe "#kill_command" do
90
+ it "run kill command" do
91
+ thread = Thread.new do
92
+ ENV['RUBOTY_ENV'] = "blocked_test"
93
+ robot.should_receive(:say).with(reply_data(replied_sleep, said_to_sleep))
94
+ robot.should_receive(:say).with(reply_data(replied_after_kill, said_to_sleep))
95
+ robot.receive(body: said_to_sleep, from: from, to: to)
96
+ end
97
+
98
+
99
+ # Test command invoked
100
+ expect { robot.receive(body: said_to_kill, from: from, to: to) }.to change {
101
+ robot.brain.data[:command_slot].running_commands.count}.from(1).to(0)
102
+
103
+ # Wait killed message
104
+ thread.join
105
+ end
106
+
107
+ after do
108
+ ENV['RUBOTY_ENV'] = "test"
109
+ end
110
+ end
111
+
112
+ describe "#list_command" do
113
+ it "list running commands" do
114
+ robot.receive(body: said_to_sleep, from: from, to: to)
115
+ robot.receive(body: said_to_sleep, from: from, to: to)
116
+
117
+ comm1 = robot.brain.data[:command_slot].running_commands[0]
118
+ comm2 = robot.brain.data[:command_slot].running_commands[1]
119
+ body1="1: example sleep (PID[#{comm1.pid}], started at #{comm1.start_at})\n"
120
+ body2="2: example sleep (PID[#{comm2.pid}], started at #{comm2.start_at})"
121
+
122
+ robot.should_receive(:say).with(reply_data(body1+body2, said_to_list))
123
+ robot.receive(body: said_to_list, from: from, to: to)
124
+ end
125
+
126
+ after do
127
+ ENV['RUBOTY_ENV'] = "test"
128
+ end
44
129
  end
45
130
 
46
131
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruboty-exec_command
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nakai Tooru
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-12-25 00:00:00.000000000 Z
11
+ date: 2015-01-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ruboty
@@ -24,20 +24,6 @@ dependencies:
24
24
  - - '>='
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
27
- - !ruby/object:Gem::Dependency
28
- name: systemu
29
- requirement: !ruby/object:Gem::Requirement
30
- requirements:
31
- - - '>='
32
- - !ruby/object:Gem::Version
33
- version: '0'
34
- type: :runtime
35
- prerelease: false
36
- version_requirements: !ruby/object:Gem::Requirement
37
- requirements:
38
- - - '>='
39
- - !ruby/object:Gem::Version
40
- version: '0'
41
27
  - !ruby/object:Gem::Dependency
42
28
  name: bundler
43
29
  requirement: !ruby/object:Gem::Requirement
@@ -136,13 +122,16 @@ files:
136
122
  - README.md
137
123
  - Rakefile
138
124
  - commands/example/hello
125
+ - commands/example/sleep
139
126
  - lib/ruboty/exec_command.rb
140
127
  - lib/ruboty/exec_command/actions/command.rb
141
128
  - lib/ruboty/exec_command/command.rb
129
+ - lib/ruboty/exec_command/command_slot.rb
142
130
  - lib/ruboty/exec_command/version.rb
143
131
  - lib/ruboty/handlers/command.rb
144
132
  - ruboty-exec_command.gemspec
145
- - spec/lib/ruboty/exec-command/command_spec.rb
133
+ - spec/lib/ruboty/exec_command/command_slot_spec.rb
134
+ - spec/lib/ruboty/exec_command/command_spec.rb
146
135
  - spec/lib/ruboty/handlers/command_spec.rb
147
136
  - spec/spec_helper.rb
148
137
  homepage: ''
@@ -170,6 +159,7 @@ signing_key:
170
159
  specification_version: 4
171
160
  summary: Add command to ruboty as a handler
172
161
  test_files:
173
- - spec/lib/ruboty/exec-command/command_spec.rb
162
+ - spec/lib/ruboty/exec_command/command_slot_spec.rb
163
+ - spec/lib/ruboty/exec_command/command_spec.rb
174
164
  - spec/lib/ruboty/handlers/command_spec.rb
175
165
  - spec/spec_helper.rb