brynary-testjour 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.
- data/History.txt +6 -0
- data/MIT-LICENSE.txt +19 -0
- data/README.rdoc +47 -0
- data/Rakefile +39 -0
- data/bin/testjour +8 -0
- data/lib/testjour/bonjour.rb +56 -0
- data/lib/testjour/cli.rb +78 -0
- data/lib/testjour/colorer.rb +8 -0
- data/lib/testjour/commands/base_command.rb +53 -0
- data/lib/testjour/commands/help.rb +20 -0
- data/lib/testjour/commands/list.rb +57 -0
- data/lib/testjour/commands/local_run.rb +83 -0
- data/lib/testjour/commands/run.rb +165 -0
- data/lib/testjour/commands/slave_run.rb +88 -0
- data/lib/testjour/commands/slave_start.rb +65 -0
- data/lib/testjour/commands/slave_stop.rb +25 -0
- data/lib/testjour/commands/slave_warm.rb +31 -0
- data/lib/testjour/commands/version.rb +18 -0
- data/lib/testjour/commands/warm.rb +73 -0
- data/lib/testjour/commands.rb +10 -0
- data/lib/testjour/cucumber_extensions/drb_formatter.rb +30 -0
- data/lib/testjour/cucumber_extensions/queueing_executor.rb +120 -0
- data/lib/testjour/mysql.rb +82 -0
- data/lib/testjour/pid_file.rb +43 -0
- data/lib/testjour/progressbar.rb +124 -0
- data/lib/testjour/queue_server.rb +66 -0
- data/lib/testjour/rsync.rb +29 -0
- data/lib/testjour/slave_server.rb +98 -0
- data/lib/testjour.rb +68 -0
- data/vendor/authprogs +231 -0
- metadata +102 -0
@@ -0,0 +1,124 @@
|
|
1
|
+
#
|
2
|
+
# Ruby/ProgressBar - a text progress bar library
|
3
|
+
#
|
4
|
+
# Copyright (C) 2001 Satoru Takabayashi <satoru@namazu.org>
|
5
|
+
# All rights reserved.
|
6
|
+
# This is free software with ABSOLUTELY NO WARRANTY.
|
7
|
+
#
|
8
|
+
# You can redistribute it and/or modify it under the terms
|
9
|
+
# of Ruby's licence.
|
10
|
+
#
|
11
|
+
|
12
|
+
class ProgressBar
|
13
|
+
VERSION = "0.3"
|
14
|
+
|
15
|
+
attr_accessor :colorer
|
16
|
+
attr_writer :title
|
17
|
+
|
18
|
+
def initialize (title, total, out = STDERR)
|
19
|
+
@title = title
|
20
|
+
@total = total
|
21
|
+
@out = out
|
22
|
+
@current = 0
|
23
|
+
@previous = 0
|
24
|
+
@is_finished = false
|
25
|
+
@start_time = Time.now
|
26
|
+
show_progress
|
27
|
+
end
|
28
|
+
|
29
|
+
def inspect
|
30
|
+
"(ProgressBar: #{@current}/#{@total})"
|
31
|
+
end
|
32
|
+
|
33
|
+
def format_time (t)
|
34
|
+
t = t.to_i
|
35
|
+
sec = t % 60
|
36
|
+
min = (t / 60) % 60
|
37
|
+
hour = t / 3600
|
38
|
+
sprintf("%02d:%02d:%02d", hour, min, sec);
|
39
|
+
end
|
40
|
+
|
41
|
+
# ETA stands for Estimated Time of Arrival.
|
42
|
+
def eta
|
43
|
+
if @current == 0
|
44
|
+
"ETA: --:--:--"
|
45
|
+
else
|
46
|
+
elapsed = Time.now - @start_time
|
47
|
+
eta = elapsed * @total / @current - elapsed;
|
48
|
+
sprintf("ETA: %s", format_time(eta))
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def elapsed
|
53
|
+
elapsed = Time.now - @start_time
|
54
|
+
sprintf("Time: %s", format_time(elapsed))
|
55
|
+
end
|
56
|
+
|
57
|
+
def time
|
58
|
+
if @is_finished then elapsed else eta end
|
59
|
+
end
|
60
|
+
|
61
|
+
def eol
|
62
|
+
if @is_finished then "\n" else "\r" end
|
63
|
+
end
|
64
|
+
|
65
|
+
def bar(percentage)
|
66
|
+
@bar = "=" * 41
|
67
|
+
len = percentage * (@bar.length + 1) / 100
|
68
|
+
sprintf("[%.*s%s%*s]", len, @bar, ">", [@bar.size - len, 0].max, "")
|
69
|
+
end
|
70
|
+
|
71
|
+
def show (percentage)
|
72
|
+
output = sprintf("%-25s %3d%% %s %s%s",
|
73
|
+
@title[0,25],
|
74
|
+
percentage,
|
75
|
+
bar(percentage),
|
76
|
+
time,
|
77
|
+
eol
|
78
|
+
)
|
79
|
+
|
80
|
+
unless @colorer.nil?
|
81
|
+
output = colorer.call(output)
|
82
|
+
end
|
83
|
+
|
84
|
+
@out.print(output)
|
85
|
+
end
|
86
|
+
|
87
|
+
def show_progress
|
88
|
+
if @total.zero?
|
89
|
+
cur_percentage = 100
|
90
|
+
prev_percentage = 0
|
91
|
+
else
|
92
|
+
cur_percentage = (@current * 100 / @total).to_i
|
93
|
+
prev_percentage = (@previous * 100 / @total).to_i
|
94
|
+
end
|
95
|
+
|
96
|
+
if cur_percentage > prev_percentage || @is_finished
|
97
|
+
show(cur_percentage)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
public
|
102
|
+
def finish
|
103
|
+
@current = @total
|
104
|
+
@is_finished = true
|
105
|
+
show_progress
|
106
|
+
end
|
107
|
+
|
108
|
+
def set (count)
|
109
|
+
if count < 0 || count > @total
|
110
|
+
raise "invalid count: #{count} (total: #{total})"
|
111
|
+
end
|
112
|
+
@current = count
|
113
|
+
show_progress
|
114
|
+
@previous = @current
|
115
|
+
end
|
116
|
+
|
117
|
+
def inc (step = 1)
|
118
|
+
@current += step
|
119
|
+
@current = @total if @current > @total
|
120
|
+
show_progress
|
121
|
+
@previous = @current
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require "thread"
|
2
|
+
require "drb"
|
3
|
+
require "timeout"
|
4
|
+
|
5
|
+
module Testjour
|
6
|
+
|
7
|
+
class QueueServer
|
8
|
+
TIMEOUT_IN_SECONDS = 60
|
9
|
+
|
10
|
+
def self.with_server
|
11
|
+
server = new
|
12
|
+
DRb.start_service(nil, server)
|
13
|
+
yield server
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.stop
|
17
|
+
DRb.stop_service
|
18
|
+
end
|
19
|
+
|
20
|
+
def initialize
|
21
|
+
reset
|
22
|
+
end
|
23
|
+
|
24
|
+
def reset
|
25
|
+
@work_queue = Queue.new
|
26
|
+
@result_queue = Queue.new
|
27
|
+
end
|
28
|
+
|
29
|
+
def done_with_work
|
30
|
+
@done_with_work = true
|
31
|
+
end
|
32
|
+
|
33
|
+
def take_result
|
34
|
+
Timeout.timeout(TIMEOUT_IN_SECONDS, ResultOverdueError) do
|
35
|
+
@result_queue.pop
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def take_work
|
40
|
+
raise NoWorkUnitsRemainingError if @done_with_work
|
41
|
+
|
42
|
+
@work_queue.pop(true)
|
43
|
+
rescue Object => ex
|
44
|
+
if ex.message == "queue empty"
|
45
|
+
raise NoWorkUnitsAvailableError
|
46
|
+
else
|
47
|
+
raise
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def write_result(uri, dot, message = nil, backtrace = [])
|
52
|
+
@result_queue.push [uri, dot, message.to_s, backtrace.join("\n")]
|
53
|
+
nil
|
54
|
+
end
|
55
|
+
|
56
|
+
def write_work(work_unit)
|
57
|
+
@work_queue.push work_unit
|
58
|
+
nil
|
59
|
+
end
|
60
|
+
|
61
|
+
class NoWorkUnitsAvailableError < StandardError; end
|
62
|
+
class NoWorkUnitsRemainingError < StandardError; end
|
63
|
+
class ResultOverdueError < StandardError; end
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require "uri"
|
2
|
+
|
3
|
+
module Testjour
|
4
|
+
|
5
|
+
class RsyncFailed < StandardError
|
6
|
+
end
|
7
|
+
|
8
|
+
class Rsync
|
9
|
+
|
10
|
+
def self.copy_to_current_directory_from(source_uri)
|
11
|
+
destination_dir = File.expand_path(".")
|
12
|
+
uri = URI.parse(source_uri)
|
13
|
+
|
14
|
+
command = "rsync -az --delete --exclude=.git --exclude=*.log --exclude=*.pid #{uri.user}@#{uri.host}:#{uri.path}/ #{destination_dir}"
|
15
|
+
|
16
|
+
Testjour.logger.info "Rsyncing: #{command}"
|
17
|
+
start_time = Time.now
|
18
|
+
successful = system command
|
19
|
+
|
20
|
+
if successful
|
21
|
+
time = Time.now - start_time
|
22
|
+
Testjour.logger.debug("Rsync finished in %.2fs" % time)
|
23
|
+
else
|
24
|
+
raise RsyncFailed.new
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
require "thread"
|
2
|
+
require "drb"
|
3
|
+
require "uri"
|
4
|
+
require "timeout"
|
5
|
+
require "systemu"
|
6
|
+
|
7
|
+
module Testjour
|
8
|
+
|
9
|
+
class SlaveServer
|
10
|
+
|
11
|
+
def self.start
|
12
|
+
server = self.new
|
13
|
+
DRb.start_service(nil, server)
|
14
|
+
uri = URI.parse(DRb.uri)
|
15
|
+
return uri.port.to_i
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.stop
|
19
|
+
DRb.stop_service
|
20
|
+
end
|
21
|
+
|
22
|
+
def status
|
23
|
+
running? ? "busy" : "available"
|
24
|
+
end
|
25
|
+
|
26
|
+
def warm(queue_server_url)
|
27
|
+
if running?
|
28
|
+
Testjour.logger.info "Not running because pid exists: #{@pid}"
|
29
|
+
return false
|
30
|
+
end
|
31
|
+
|
32
|
+
pid_queue = Queue.new
|
33
|
+
@pid = nil
|
34
|
+
|
35
|
+
Thread.new do
|
36
|
+
Thread.current.abort_on_exception = true
|
37
|
+
cmd = command_to_warm_for(queue_server_url)
|
38
|
+
Testjour.logger.debug "Starting warm with command: #{cmd}"
|
39
|
+
status, stdout, stderr = systemu(cmd) { |pid| pid_queue << pid }
|
40
|
+
Testjour.logger.warn stderr if stderr.strip.size > 0
|
41
|
+
end
|
42
|
+
|
43
|
+
@pid = pid_queue.pop
|
44
|
+
|
45
|
+
Testjour.logger.info "Warming from server #{queue_server_url} on PID #{@pid}"
|
46
|
+
|
47
|
+
return @pid
|
48
|
+
end
|
49
|
+
|
50
|
+
def run(queue_server_url, cucumber_options)
|
51
|
+
if running?
|
52
|
+
Testjour.logger.info "Not running because pid exists: #{@pid}"
|
53
|
+
return false
|
54
|
+
end
|
55
|
+
|
56
|
+
pid_queue = Queue.new
|
57
|
+
@pid = nil
|
58
|
+
|
59
|
+
Thread.new do
|
60
|
+
Thread.current.abort_on_exception = true
|
61
|
+
cmd = command_to_run_for(queue_server_url, cucumber_options)
|
62
|
+
Testjour.logger.debug "Starting runner with command: #{cmd}"
|
63
|
+
status, stdout, stderr = systemu(cmd) { |pid| pid_queue << pid }
|
64
|
+
Testjour.logger.warn stderr if stderr.strip.size > 0
|
65
|
+
end
|
66
|
+
|
67
|
+
@pid = pid_queue.pop
|
68
|
+
|
69
|
+
Testjour.logger.info "Running tests from queue #{queue_server_url} on PID #{@pid}"
|
70
|
+
|
71
|
+
return @pid
|
72
|
+
end
|
73
|
+
|
74
|
+
protected
|
75
|
+
|
76
|
+
def command_to_run_for(master_server_uri, cucumber_options)
|
77
|
+
"#{testjour_bin_path} slave:run #{master_server_uri} -- #{cucumber_options.join(' ')}".strip
|
78
|
+
end
|
79
|
+
|
80
|
+
def command_to_warm_for(master_server_uri)
|
81
|
+
"#{testjour_bin_path} slave:warm #{master_server_uri}".strip
|
82
|
+
end
|
83
|
+
|
84
|
+
def testjour_bin_path
|
85
|
+
File.expand_path(File.dirname(__FILE__) + "/../../bin/testjour")
|
86
|
+
end
|
87
|
+
|
88
|
+
def running?
|
89
|
+
return false unless @pid
|
90
|
+
Process::kill 0, @pid.to_s.to_i
|
91
|
+
true
|
92
|
+
rescue Errno::ESRCH, Errno::EPERM
|
93
|
+
false
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
data/lib/testjour.rb
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
require "rubygems"
|
2
|
+
require "English"
|
3
|
+
require "logger"
|
4
|
+
|
5
|
+
$LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__))) unless $LOAD_PATH.include?(File.expand_path(File.dirname(__FILE__)))
|
6
|
+
|
7
|
+
module Kernel
|
8
|
+
# Options:
|
9
|
+
# * :tries - Number of retries to perform. Defaults to 1.
|
10
|
+
# * :on - The Exception on which a retry will be performed. Defaults to Exception, which retries on any Exception.
|
11
|
+
#
|
12
|
+
# Example
|
13
|
+
# =======
|
14
|
+
# retryable(:tries => 1, :on => OpenURI::HTTPError) do
|
15
|
+
# # your code here
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
def retryable(options = {}, &block)
|
19
|
+
opts = { :tries => 1, :on => Exception }.merge(options)
|
20
|
+
|
21
|
+
retry_exception, retries = opts[:on], opts[:tries]
|
22
|
+
|
23
|
+
begin
|
24
|
+
return yield
|
25
|
+
rescue retry_exception
|
26
|
+
retry if (retries -= 1) > 0
|
27
|
+
end
|
28
|
+
|
29
|
+
yield
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
module Testjour
|
34
|
+
VERSION = '0.1.0'
|
35
|
+
|
36
|
+
class << self
|
37
|
+
attr_accessor :step_mother
|
38
|
+
attr_accessor :executor
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.load_cucumber
|
42
|
+
$LOAD_PATH.unshift(File.expand_path("./vendor/plugins/cucumber/lib"))
|
43
|
+
|
44
|
+
require "cucumber"
|
45
|
+
require "cucumber/formatters/ansicolor"
|
46
|
+
require "cucumber/treetop_parser/feature_en"
|
47
|
+
Cucumber.load_language("en")
|
48
|
+
|
49
|
+
# Expose this because we need it
|
50
|
+
class << Cucumber::CLI
|
51
|
+
attr_reader :executor
|
52
|
+
attr_reader :step_mother
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.logger
|
57
|
+
return @logger if @logger
|
58
|
+
setup_logger
|
59
|
+
@logger
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.setup_logger
|
63
|
+
@logger = Logger.new("testjour.log")
|
64
|
+
@logger.formatter = proc { |severity, time, progname, msg| "#{time.strftime("%b %d %H:%M:%S")} [#{$PID}]: #{msg}\n" }
|
65
|
+
@logger.level = Logger::DEBUG
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
data/vendor/authprogs
ADDED
@@ -0,0 +1,231 @@
|
|
1
|
+
#!/usr/bin/perl
|
2
|
+
#
|
3
|
+
# authprogs, Copyright 2003, Brian Hatch.
|
4
|
+
#
|
5
|
+
# Released under the GPL. See the file
|
6
|
+
# COPYING for more information.
|
7
|
+
#
|
8
|
+
# This program is intended to be called from an authorized_keys
|
9
|
+
# file, i.e. triggered by use of specific SSH identities.
|
10
|
+
#
|
11
|
+
# It will check the original command (saved in $SSH_ORIGINAL_COMMAND
|
12
|
+
# environment variable by sshd) and see if it is on the 'approved'
|
13
|
+
# list.
|
14
|
+
#
|
15
|
+
# Allowed commands are stored in ~/.ssh/authprogs.conf
|
16
|
+
# The format of this file is as follows:
|
17
|
+
#
|
18
|
+
# [ ALL ]
|
19
|
+
# command0 arg arg arg
|
20
|
+
#
|
21
|
+
# [ ip.ad.dr.01 ip.ad.dr.02 ]
|
22
|
+
# command1 arg arg arg
|
23
|
+
#
|
24
|
+
# [ ip.ad.dr.03 ]
|
25
|
+
# command2 arg arg arg
|
26
|
+
# command3 arg arg
|
27
|
+
#
|
28
|
+
# There is no regexp or shell metacharacter support. If
|
29
|
+
# you want to allow 'ls /dir1' and 'ls /dir2' you need to
|
30
|
+
# explicitly create those two rules. Putting "ls /dir[12]"
|
31
|
+
# in the authprogs.conf file will *not* work.
|
32
|
+
#
|
33
|
+
# NOTE: Some versions of Bash do not export the (already exported)
|
34
|
+
# SSH_CLIENT environment variable. You can get around this by adding
|
35
|
+
# export SSH_CLIENT=${SSH_CLIENT}
|
36
|
+
# or something similar in your ~/.bashrc, /etc/profile, etc.
|
37
|
+
# http://mail.gnu.org/archive/html/bug-bash/2002-01/msg00096.html
|
38
|
+
#
|
39
|
+
# Changes:
|
40
|
+
# 2003-10-27: fixed exit status, noted by Brad Fritz.
|
41
|
+
# 2003-10-27: added blank SSH_ORIGINAL_COMMAND debug log message
|
42
|
+
|
43
|
+
|
44
|
+
use strict;
|
45
|
+
use subs qw(bail log);
|
46
|
+
use POSIX qw(strftime);
|
47
|
+
use File::Basename;
|
48
|
+
use FileHandle;
|
49
|
+
|
50
|
+
# DEBUGLEVEL values:
|
51
|
+
# 0 - log nothing
|
52
|
+
# 1 - log errors
|
53
|
+
# 2 - log failed commands
|
54
|
+
# 3 - log successful commands
|
55
|
+
# 4 - log debugging info
|
56
|
+
my $DEBUGLEVEL = 4;
|
57
|
+
|
58
|
+
# Salt to taste. /dev/null might be a likely
|
59
|
+
# place if you don't want any logging.
|
60
|
+
my $LOGFILE = "$ENV{HOME}/.ssh/authprogs.log";
|
61
|
+
|
62
|
+
# Configfile - location of the host/commands allowed.
|
63
|
+
my $CONFIGFILE = "$ENV{HOME}/.ssh/authprogs.conf";
|
64
|
+
|
65
|
+
# $CLIENT_COMMAND is the string the client sends us.
|
66
|
+
#
|
67
|
+
# Unfortunately, the actual spacing is lost. IE
|
68
|
+
# ("some string" and "some" "string" are not differentiable.)
|
69
|
+
my ($CLIENT_COMMAND) = $ENV{SSH_ORIGINAL_COMMAND};
|
70
|
+
|
71
|
+
# strip quotes - we'll explain later on.
|
72
|
+
$CLIENT_COMMAND =~ s/['"]//g;
|
73
|
+
|
74
|
+
# Set CLIENT_IP to just the ip addr, sans port numbers.
|
75
|
+
my ($CLIENT_IP) = $ENV{SSH_CLIENT} =~ /^(\S+)/;
|
76
|
+
|
77
|
+
|
78
|
+
# Open log in append mode. Note that the use of '>>'
|
79
|
+
# means you better be doing it somewhere that is only
|
80
|
+
# writeable by you, lest you have a symlink/etc attack.
|
81
|
+
# Since we default to ~/.ssh, this should not be a problem.
|
82
|
+
|
83
|
+
if ( $DEBUGLEVEL ) {
|
84
|
+
open LOG, ">>$LOGFILE" or bail "Can't open $LOGFILE\n";
|
85
|
+
LOG->autoflush(1);
|
86
|
+
}
|
87
|
+
|
88
|
+
if ( ! $ENV{SSH_ORIGINAL_COMMAND} ) {
|
89
|
+
log(4, "SSH_ORIGINAL_COMMAND not set - either the client ".
|
90
|
+
"didn't send one, or your shell is removing it from ".
|
91
|
+
"the environment.");
|
92
|
+
}
|
93
|
+
|
94
|
+
|
95
|
+
# Ok, let's scan the authprogs.conf file
|
96
|
+
open CONFIGFILE, $CONFIGFILE or bail "Config '$CONFIGFILE' not readable!";
|
97
|
+
|
98
|
+
# Note: we do not verify that the configuration file is owned by
|
99
|
+
# this user. Some might argue that we should. (A quick stat
|
100
|
+
# compared to $< would do the trick.) However some installations
|
101
|
+
# relax the requirement that the .ssh dir is owned by the user
|
102
|
+
# s.t. it can be owned by root and only modifyable in that way to
|
103
|
+
# keep even the user from making changes. We should trust the
|
104
|
+
# administrator's SSH setup (StrictModes) and not bother checking
|
105
|
+
# the ownership/perms of configfile.
|
106
|
+
|
107
|
+
my $VALID_COMMAND=0; # flag: is this command appopriate for this host?
|
108
|
+
|
109
|
+
READ_CONF: while (<CONFIGFILE>) {
|
110
|
+
chomp;
|
111
|
+
|
112
|
+
# Skip blanks and comments.
|
113
|
+
if ( /^\s*#/ ) { next }
|
114
|
+
if ( /^\s*$/ ) { next }
|
115
|
+
|
116
|
+
# Are we the beginning of a new set of
|
117
|
+
# clients?
|
118
|
+
if ( /^\[/ ) {
|
119
|
+
|
120
|
+
# Snag the IP address(es) in question.
|
121
|
+
|
122
|
+
/^ \[ ( [^\]]+ ) \] /x;
|
123
|
+
$_ = $1;
|
124
|
+
|
125
|
+
if ( /^\s*ALL\s*$/ ) { # If wildcard selected
|
126
|
+
$_ = $CLIENT_IP;
|
127
|
+
}
|
128
|
+
|
129
|
+
my @clients = split;
|
130
|
+
|
131
|
+
log 4, "Found new clients line for @clients\n";
|
132
|
+
|
133
|
+
# This would be a great place to add
|
134
|
+
# ip <=> name mapping so we can have it work
|
135
|
+
# on hostnames rather than just IP addresses.
|
136
|
+
# If so, better make sure that forward and
|
137
|
+
# reverse lookups match -- an attacker in
|
138
|
+
# control of his network can easily set a PTR
|
139
|
+
# record, so don't rely on it alone.
|
140
|
+
|
141
|
+
unless ( grep /^$CLIENT_IP$/, @clients ) {
|
142
|
+
|
143
|
+
log 4, "Client IP does not match this list.\n";
|
144
|
+
|
145
|
+
$VALID_COMMAND=0;
|
146
|
+
|
147
|
+
# Nope, not relevant - go to next
|
148
|
+
# host definition list.
|
149
|
+
while (<CONFIGFILE>) {
|
150
|
+
last if /^\[/;
|
151
|
+
}
|
152
|
+
|
153
|
+
# Never found another host definition. Bail.
|
154
|
+
redo READ_CONF;
|
155
|
+
}
|
156
|
+
$VALID_COMMAND=1;
|
157
|
+
log 4, "Client matches this list.\n";
|
158
|
+
|
159
|
+
next;
|
160
|
+
}
|
161
|
+
|
162
|
+
# We must be a potential command
|
163
|
+
if ( ! $VALID_COMMAND ) {
|
164
|
+
bail "Parsing error at line $. of $CONFIGFILE\n";
|
165
|
+
}
|
166
|
+
|
167
|
+
|
168
|
+
my $allowed_command = $_;
|
169
|
+
$allowed_command =~ s/\s+$//; # strip trailing slashes
|
170
|
+
$allowed_command =~ s/^\s+//; # strip leading slashes
|
171
|
+
|
172
|
+
# We've now got the command as we'd run it through 'system'.
|
173
|
+
#
|
174
|
+
# Problem: SSH sticks the command in $SSH_ORIGINAL_COMMAND
|
175
|
+
# but doesn't retain the argument breaks.
|
176
|
+
#
|
177
|
+
# Solution: Let's guess by stripping double and single quotes
|
178
|
+
# from both the client and the config file. If those
|
179
|
+
# versions match, we'll assume the client was right.
|
180
|
+
|
181
|
+
my $allowed_command_sans_quotes = $allowed_command;
|
182
|
+
$allowed_command_sans_quotes =~ s/["']//g;
|
183
|
+
|
184
|
+
log 4, "Comparing allowed command and client's command:\n";
|
185
|
+
log 4, " Allowed: $allowed_command_sans_quotes\n";
|
186
|
+
log 4, " Client: $CLIENT_COMMAND\n";
|
187
|
+
|
188
|
+
if ( $allowed_command_sans_quotes eq $CLIENT_COMMAND ) {
|
189
|
+
log 3, "Running [$allowed_command] from $ENV{SSH_CLIENT}\n";
|
190
|
+
|
191
|
+
# System is a bad thing to use on untrusted input.
|
192
|
+
# But $allowed_command comes from the user on the SSH
|
193
|
+
# server, from his authprogs.conf file. So we can trust
|
194
|
+
# it as much as we trust him, since it's running as that
|
195
|
+
# user.
|
196
|
+
|
197
|
+
system $allowed_command;
|
198
|
+
exit $? >> 8;
|
199
|
+
}
|
200
|
+
|
201
|
+
}
|
202
|
+
|
203
|
+
# The remote end wants to run something they're not allowed to run.
|
204
|
+
# Log it, and chastize them.
|
205
|
+
|
206
|
+
log 2, "Denying request '$ENV{SSH_ORIGINAL_COMMAND}' from $ENV{SSH_CLIENT}\n";
|
207
|
+
print STDERR "You're not allowed to run '$ENV{SSH_ORIGINAL_COMMAND}'\n";
|
208
|
+
exit 1;
|
209
|
+
|
210
|
+
|
211
|
+
sub bail {
|
212
|
+
# print log message (w/ guarenteed newline)
|
213
|
+
if (@_) {
|
214
|
+
$_ = join '', @_;
|
215
|
+
chomp $_;
|
216
|
+
log 1, "$_\n";
|
217
|
+
}
|
218
|
+
|
219
|
+
close LOG if $DEBUGLEVEL;
|
220
|
+
exit 1
|
221
|
+
}
|
222
|
+
|
223
|
+
sub log {
|
224
|
+
my ($level,@list) = @_;
|
225
|
+
return if $DEBUGLEVEL < $level;
|
226
|
+
|
227
|
+
my $timestamp = strftime "%Y/%m/%d %H:%M:%S", localtime;
|
228
|
+
my $progname = basename $0;
|
229
|
+
grep { s/^/$timestamp $progname\[$$\]: / } @list;
|
230
|
+
print LOG @list;
|
231
|
+
}
|
metadata
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: brynary-testjour
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Bryan Helmkamp
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2008-12-09 00:00:00 -08:00
|
13
|
+
default_executable: testjour
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: systemu
|
17
|
+
version_requirement:
|
18
|
+
version_requirements: !ruby/object:Gem::Requirement
|
19
|
+
requirements:
|
20
|
+
- - ">="
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 1.2.0
|
23
|
+
version:
|
24
|
+
- !ruby/object:Gem::Dependency
|
25
|
+
name: dnssd
|
26
|
+
version_requirement:
|
27
|
+
version_requirements: !ruby/object:Gem::Requirement
|
28
|
+
requirements:
|
29
|
+
- - ">="
|
30
|
+
- !ruby/object:Gem::Version
|
31
|
+
version: 0.6.0
|
32
|
+
version:
|
33
|
+
description: Distributed test running with autodiscovery via Bonjour (for Cucumber first)
|
34
|
+
email: bryan@brynary.com
|
35
|
+
executables:
|
36
|
+
- testjour
|
37
|
+
extensions: []
|
38
|
+
|
39
|
+
extra_rdoc_files: []
|
40
|
+
|
41
|
+
files:
|
42
|
+
- History.txt
|
43
|
+
- MIT-LICENSE.txt
|
44
|
+
- README.rdoc
|
45
|
+
- Rakefile
|
46
|
+
- bin/testjour
|
47
|
+
- lib/testjour
|
48
|
+
- lib/testjour/bonjour.rb
|
49
|
+
- lib/testjour/cli.rb
|
50
|
+
- lib/testjour/colorer.rb
|
51
|
+
- lib/testjour/commands
|
52
|
+
- lib/testjour/commands/base_command.rb
|
53
|
+
- lib/testjour/commands/help.rb
|
54
|
+
- lib/testjour/commands/list.rb
|
55
|
+
- lib/testjour/commands/local_run.rb
|
56
|
+
- lib/testjour/commands/run.rb
|
57
|
+
- lib/testjour/commands/slave_run.rb
|
58
|
+
- lib/testjour/commands/slave_start.rb
|
59
|
+
- lib/testjour/commands/slave_stop.rb
|
60
|
+
- lib/testjour/commands/slave_warm.rb
|
61
|
+
- lib/testjour/commands/version.rb
|
62
|
+
- lib/testjour/commands/warm.rb
|
63
|
+
- lib/testjour/commands.rb
|
64
|
+
- lib/testjour/cucumber_extensions
|
65
|
+
- lib/testjour/cucumber_extensions/drb_formatter.rb
|
66
|
+
- lib/testjour/cucumber_extensions/queueing_executor.rb
|
67
|
+
- lib/testjour/mysql.rb
|
68
|
+
- lib/testjour/pid_file.rb
|
69
|
+
- lib/testjour/progressbar.rb
|
70
|
+
- lib/testjour/queue_server.rb
|
71
|
+
- lib/testjour/rsync.rb
|
72
|
+
- lib/testjour/slave_server.rb
|
73
|
+
- lib/testjour.rb
|
74
|
+
- vendor/authprogs
|
75
|
+
has_rdoc: false
|
76
|
+
homepage: http://github.com/brynary/testjour
|
77
|
+
post_install_message:
|
78
|
+
rdoc_options: []
|
79
|
+
|
80
|
+
require_paths:
|
81
|
+
- lib
|
82
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
83
|
+
requirements:
|
84
|
+
- - ">="
|
85
|
+
- !ruby/object:Gem::Version
|
86
|
+
version: "0"
|
87
|
+
version:
|
88
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
89
|
+
requirements:
|
90
|
+
- - ">="
|
91
|
+
- !ruby/object:Gem::Version
|
92
|
+
version: "0"
|
93
|
+
version:
|
94
|
+
requirements: []
|
95
|
+
|
96
|
+
rubyforge_project:
|
97
|
+
rubygems_version: 1.2.0
|
98
|
+
signing_key:
|
99
|
+
specification_version: 2
|
100
|
+
summary: Distributed test running with autodiscovery via Bonjour (for Cucumber first)
|
101
|
+
test_files: []
|
102
|
+
|