cuted 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/cute +55 -0
- data/bin/cuted +57 -0
- data/lib/cuted/command.rb +62 -0
- data/lib/cuted/queue.rb +96 -0
- metadata +97 -0
data/bin/cute
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# David Selassie
|
4
|
+
# April 28, 2011
|
5
|
+
|
6
|
+
# USAGE: cute CMD
|
7
|
+
# Enqueues a command to be executed by cuted.
|
8
|
+
|
9
|
+
require 'trollop'
|
10
|
+
require 'drb'
|
11
|
+
|
12
|
+
config = {'uri' => 'drbunix:///tmp/cuted.sock'}
|
13
|
+
if File.exists?(File.expand_path('~/.cuterc')) then
|
14
|
+
config.merge!(ParseConfig.new(File.expand_path('~/.cuterc')).params)
|
15
|
+
end
|
16
|
+
|
17
|
+
options = Trollop::options do
|
18
|
+
opt :dir, 'run command in a dir other than the CWD', :default => Dir.pwd
|
19
|
+
opt :weight, 'weight of this command', :default => 1
|
20
|
+
opt :priority, 'priority of this command', :default => 0
|
21
|
+
opt :log, 'custom log file to use'
|
22
|
+
opt :prowl_key, 'custom Prowl key to notify'
|
23
|
+
opt :uri, 'server to submit commands to', :type => :string,
|
24
|
+
:default => ENV['cuted_uri'] || config['uri']
|
25
|
+
end
|
26
|
+
Trollop::die 'cute: You must specify a command' if ARGV.length < 1
|
27
|
+
|
28
|
+
# Join together the rest of the arguments as the command.
|
29
|
+
cmd = ARGV.join(' ')
|
30
|
+
|
31
|
+
begin
|
32
|
+
DRb.start_service
|
33
|
+
queue = DRbObject.new(nil, options[:uri])
|
34
|
+
|
35
|
+
# Get the real command stored in the queue.
|
36
|
+
cmd = queue.push_cmd(cmd)
|
37
|
+
|
38
|
+
# Set up attributes.
|
39
|
+
cmd.dir = options[:dir]
|
40
|
+
cmd.priority = options[:priority]
|
41
|
+
cmd.weight = options[:weight]
|
42
|
+
# Set a custom Prowl key if one is specified.
|
43
|
+
cmd.prowl_key = options[:prowl_key] if options[:prowl_key]
|
44
|
+
# Set a custom log if one is specified and it isn't empty.
|
45
|
+
cmd.log = options[:log] if options[:log] and options[:log].size > 0
|
46
|
+
|
47
|
+
# Try to run tasks.
|
48
|
+
queue.pop_run
|
49
|
+
|
50
|
+
puts "Command '#{cmd}' submitted"
|
51
|
+
rescue DRb::DRbConnError => err
|
52
|
+
puts "Could not connect to '#{options[:server]}'"
|
53
|
+
|
54
|
+
exit 1
|
55
|
+
end
|
data/bin/cuted
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# David Selassie
|
4
|
+
# April 28, 2011
|
5
|
+
|
6
|
+
# USAGE: cuted start | stop | run [-- ...]
|
7
|
+
# A simple process queue daemon. Add processes using the cute command.
|
8
|
+
|
9
|
+
# Requires a daemonizer!
|
10
|
+
require 'daemons'
|
11
|
+
# Also requires this simple config parser.
|
12
|
+
require 'parseconfig'
|
13
|
+
require 'logger'
|
14
|
+
require 'drb'
|
15
|
+
|
16
|
+
require 'cuted/queue'
|
17
|
+
|
18
|
+
Daemons.run_proc('cuted', :dir => '~', :log_output => true) do
|
19
|
+
logger = Logger.new(STDOUT)
|
20
|
+
|
21
|
+
# Set up defaults that might be overwritten.
|
22
|
+
config = {'max_concurrent' => 4, 'log' => 'cute.log',
|
23
|
+
'uri' => 'drbunix:///tmp/cuted.sock'}
|
24
|
+
# See if there's any user settings in ~/.cuterc and write them on top.
|
25
|
+
if File.exists?(File.expand_path('~/.cuterc'))
|
26
|
+
config.merge!(ParseConfig.new(File.expand_path('~/.cuterc')).params)
|
27
|
+
# If the log variable is there but blank, make it nil.
|
28
|
+
if config['log'] and config['log'].strip.length == 0 then
|
29
|
+
config['log'] = nil
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Use the environment variable PROWLKEY if it's there.
|
34
|
+
config['prowl_key'] = ENV['PROWLKEY'] if ENV['PROWLKEY']
|
35
|
+
|
36
|
+
logger.info "Will run #{config['max_concurrent']} processes concurrently"
|
37
|
+
logger.info 'Will log command outputs' if config['log']
|
38
|
+
if config['prowl_key'] then
|
39
|
+
logger.info "Will notify Prowl key #{config['prowl_key']} of completions"
|
40
|
+
end
|
41
|
+
|
42
|
+
begin
|
43
|
+
queue = Cuted::Queue.new(:logger => logger,
|
44
|
+
:max_concurrent => config['max_concurrent'],
|
45
|
+
:defaults => {:prowl_key => config['prowl_key'],
|
46
|
+
:log => config['log']})
|
47
|
+
DRb.start_service(config['uri'], queue)
|
48
|
+
|
49
|
+
logger.info "Running on #{DRb.uri}"
|
50
|
+
ENV['cuted_uri'] = DRb.uri
|
51
|
+
|
52
|
+
# Wait. Let clients call methods on the queue.
|
53
|
+
DRb.thread.join
|
54
|
+
rescue Interrupt
|
55
|
+
logger.info 'Exiting'
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# David Selassie
|
4
|
+
# March 29, 2012
|
5
|
+
# cutejob.rb
|
6
|
+
|
7
|
+
# Structure for holding a queued shell command.
|
8
|
+
|
9
|
+
module Cuted
|
10
|
+
class Command
|
11
|
+
require 'drb'
|
12
|
+
require 'fileutils'
|
13
|
+
|
14
|
+
# Make sure the command is stored on the server.
|
15
|
+
include DRbUndumped
|
16
|
+
|
17
|
+
attr_accessor :dir, :cmd, :weight, :priority, :log, :prowl_key
|
18
|
+
attr_reader :pid
|
19
|
+
|
20
|
+
def initialize(cmd, opts = {})
|
21
|
+
@cmd = cmd.to_s
|
22
|
+
# If the options are set, use them; otherwise defaults.
|
23
|
+
@dir = opts[:dir] || Dir.pwd
|
24
|
+
# Since we can force opts[:log] to be nil, and we don't want the default.
|
25
|
+
@log = opts.has_key?(:log) ? opts[:log] : 'cute.log'
|
26
|
+
@priority = opts[:priority] || 0
|
27
|
+
@weight = opts[:weight] || 1
|
28
|
+
end
|
29
|
+
|
30
|
+
# Runs in the current thread.
|
31
|
+
def run
|
32
|
+
# Make sure the desired working directory exists.
|
33
|
+
FileUtils.makedirs(@dir) if not File.directory?(@dir)
|
34
|
+
# Actually run the command.
|
35
|
+
if @log then
|
36
|
+
@pid = spawn(@cmd, :chdir => @dir, [:out, :err] => [@log, 'a'])
|
37
|
+
else
|
38
|
+
@pid = spawn(@cmd, :chdir => @dir, [:out, :err] => '/dev/null')
|
39
|
+
end
|
40
|
+
|
41
|
+
# In the forked process, wait for the command to be done.
|
42
|
+
Process.waitall
|
43
|
+
|
44
|
+
# Report that this command finished.
|
45
|
+
begin
|
46
|
+
require 'prowler'
|
47
|
+
Prowler.verify_certificate = false
|
48
|
+
Prowler.application = 'cuted'
|
49
|
+
Prowler.notify(@cmd, "Finished in #{@dir}", @prowl_key) if @prowl_key
|
50
|
+
rescue LoadError
|
51
|
+
end
|
52
|
+
|
53
|
+
# Return the CuteCommand.
|
54
|
+
self
|
55
|
+
end
|
56
|
+
|
57
|
+
# Print out dir> command string.
|
58
|
+
def to_s
|
59
|
+
"#{@dir}> #{@cmd}"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
data/lib/cuted/queue.rb
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# David Selassie
|
4
|
+
# March 29, 2012
|
5
|
+
# cutequeue.rb
|
6
|
+
|
7
|
+
# Priority command queue.
|
8
|
+
|
9
|
+
module Cuted
|
10
|
+
class Queue
|
11
|
+
attr_accessor :running, :queue
|
12
|
+
|
13
|
+
def initialize(opts = {})
|
14
|
+
@queue = []
|
15
|
+
@running = []
|
16
|
+
|
17
|
+
# If the options are set, then use them; otherwise defaults.
|
18
|
+
@defaults = opts[:defaults] || {}
|
19
|
+
@max_concurrent = opts[:max_concurrent] || 4
|
20
|
+
@logger = opts[:logger]
|
21
|
+
end
|
22
|
+
|
23
|
+
# Takes the text of a command or any structure that responds to :run.
|
24
|
+
def push_cmd(cmd)
|
25
|
+
@logger.info "Pushed command '#{cmd}'" if @logger
|
26
|
+
|
27
|
+
# Really we just need a command to be able to run.
|
28
|
+
if not cmd.respond_to?(:run)
|
29
|
+
require 'cuted/command'
|
30
|
+
cmd = Command.new(cmd)
|
31
|
+
end
|
32
|
+
|
33
|
+
# Set up the command with the default values.
|
34
|
+
@defaults.each do |k, v|
|
35
|
+
cmd.send("#{k}=", v) if cmd.respond_to?("#{k}=")
|
36
|
+
end
|
37
|
+
|
38
|
+
@queue << cmd
|
39
|
+
sort
|
40
|
+
|
41
|
+
cmd
|
42
|
+
end
|
43
|
+
|
44
|
+
# Puts the highest priority commands first.
|
45
|
+
def sort
|
46
|
+
# If there is no priority, use 0.
|
47
|
+
@queue.sort_by! { |cmd| cmd.respond_to?(:priority) ? -cmd.priority : 0 }
|
48
|
+
|
49
|
+
self
|
50
|
+
end
|
51
|
+
|
52
|
+
# Return the highest priority command.
|
53
|
+
def peek
|
54
|
+
@queue[0]
|
55
|
+
end
|
56
|
+
|
57
|
+
# Find the highest priority item and run it.
|
58
|
+
# If continue is true, then run other commands in the queue when done.
|
59
|
+
def pop_run(continue = true)
|
60
|
+
# If there are no commands, do nothing.
|
61
|
+
if @queue.length < 1 then
|
62
|
+
@logger.info 'No commands to run' if @logger
|
63
|
+
return
|
64
|
+
# If there isn't enough free weight to run, do nothing.
|
65
|
+
elsif peek.weight > @max_concurrent - @running.size and
|
66
|
+
@running.size > 0 then
|
67
|
+
@logger.info 'Not enough free weight to run' if @logger
|
68
|
+
return
|
69
|
+
end
|
70
|
+
|
71
|
+
# Pop off the highest priority command.
|
72
|
+
cmd = @queue.delete_at(0)
|
73
|
+
sort
|
74
|
+
|
75
|
+
# Run the command and return it.
|
76
|
+
@logger.info "Starting #{cmd}" if @logger
|
77
|
+
|
78
|
+
# Find out the weight of this command.
|
79
|
+
weight = cmd.respond_to?(:weight) ? cmd.weight : 1
|
80
|
+
# Add this command that many times to the running array.
|
81
|
+
@running.concat([cmd] * weight)
|
82
|
+
|
83
|
+
# In a separate thread...
|
84
|
+
thread = Thread.new do
|
85
|
+
# Run the command.
|
86
|
+
cmd.run
|
87
|
+
|
88
|
+
@logger.info "Finished #{cmd}" if @logger
|
89
|
+
# Make the command remove itself from the running list.
|
90
|
+
@running.delete_if { |o| o === cmd }
|
91
|
+
# Try to run queued commands once this one is done if we're continuing.
|
92
|
+
pop_run if continue
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
metadata
ADDED
@@ -0,0 +1,97 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: cuted
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- David Selassie
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-04-03 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: riot
|
16
|
+
requirement: &70245431926940 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0.12'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70245431926940
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: daemons
|
27
|
+
requirement: &70245431926160 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ~>
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '1.1'
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *70245431926160
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: trollop
|
38
|
+
requirement: &70245431925580 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ~>
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '1.16'
|
44
|
+
type: :runtime
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *70245431925580
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: parseconfig
|
49
|
+
requirement: &70245431924920 !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ~>
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0.5'
|
55
|
+
type: :runtime
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: *70245431924920
|
58
|
+
description:
|
59
|
+
email: selassid@gmail.com
|
60
|
+
executables:
|
61
|
+
- cuted
|
62
|
+
- cute
|
63
|
+
extensions: []
|
64
|
+
extra_rdoc_files: []
|
65
|
+
files:
|
66
|
+
- lib/cuted/queue.rb
|
67
|
+
- lib/cuted/command.rb
|
68
|
+
- !binary |-
|
69
|
+
YmluL2N1dGVk
|
70
|
+
- !binary |-
|
71
|
+
YmluL2N1dGU=
|
72
|
+
homepage: http://github.com/selassid/cuted
|
73
|
+
licenses: []
|
74
|
+
post_install_message: Install optional gem 'prowler' for Prowl notifications when
|
75
|
+
commands are complete.
|
76
|
+
rdoc_options: []
|
77
|
+
require_paths:
|
78
|
+
- lib
|
79
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
80
|
+
none: false
|
81
|
+
requirements:
|
82
|
+
- - ! '>='
|
83
|
+
- !ruby/object:Gem::Version
|
84
|
+
version: '0'
|
85
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
86
|
+
none: false
|
87
|
+
requirements:
|
88
|
+
- - ! '>='
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
version: '0'
|
91
|
+
requirements: []
|
92
|
+
rubyforge_project:
|
93
|
+
rubygems_version: 1.8.17
|
94
|
+
signing_key:
|
95
|
+
specification_version: 3
|
96
|
+
summary: A simple single-machine batch queueing system.
|
97
|
+
test_files: []
|