ruboty-exec_command 0.0.2 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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