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