vcseif 1.2.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/lib/vcseif.rb +23 -0
- data/lib/vcseif/connection.rb +285 -0
- data/lib/vcseif/rally_vcs_connection.rb +925 -0
- data/lib/vcseif/utils/auxloader.rb +255 -0
- data/lib/vcseif/utils/exceptions.rb +101 -0
- data/lib/vcseif/utils/fuzzer.rb +71 -0
- data/lib/vcseif/utils/konfigger.rb +421 -0
- data/lib/vcseif/utils/lock_file.rb +90 -0
- data/lib/vcseif/utils/proctbl.rb +146 -0
- data/lib/vcseif/utils/rally_logger.rb +223 -0
- data/lib/vcseif/utils/time_file.rb +80 -0
- data/lib/vcseif/vcs_connector.rb +487 -0
- data/lib/vcseif/vcs_connector_driver.rb +227 -0
- data/lib/vcseif/vcs_connector_runner.rb +283 -0
- data/lib/version.rb +18 -0
- metadata +173 -0
@@ -0,0 +1,90 @@
|
|
1
|
+
# Copyright 2001-2013 Rally Software Development Corp. All Rights Reserved.
|
2
|
+
|
3
|
+
require 'time'
|
4
|
+
|
5
|
+
require_relative "exceptions"
|
6
|
+
require_relative "proctbl"
|
7
|
+
|
8
|
+
class LockFile
|
9
|
+
|
10
|
+
def self.exists?(filename)
|
11
|
+
begin
|
12
|
+
if File.exists?(filename) and File.readable?(filename)
|
13
|
+
return true
|
14
|
+
else
|
15
|
+
return false
|
16
|
+
end
|
17
|
+
rescue => ex
|
18
|
+
lockex = VCSEIF_Exceptions::UnrecoverableException.new("#{ex.message}")
|
19
|
+
raise lockex, ex.message.to_s
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.locker_is_running?(filename, locker)
|
24
|
+
# return false if no lock file exists OR the PID that wrote the lock file is no longer running
|
25
|
+
return false if !File.exists?(filename)
|
26
|
+
holder = self.current_lock_holder(filename)
|
27
|
+
return false if holder == "" or holder.nil?
|
28
|
+
locker_pid = 0
|
29
|
+
started = ""
|
30
|
+
holder.scan(/^(\d+)/) {|p| locker_pid = p[0]}
|
31
|
+
running = ProcTable.target_process(locker_pid)
|
32
|
+
|
33
|
+
return false if running.nil?
|
34
|
+
return true
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.current_lock_holder(filename)
|
38
|
+
holder = ""
|
39
|
+
if !File.exists?(filename)
|
40
|
+
raise VCSEIF_Exceptions::UnrecoverableException.new("lock file %s does not exists" % filename)
|
41
|
+
end
|
42
|
+
begin
|
43
|
+
lf = File.open(filename, "r")
|
44
|
+
entry = lf.read()
|
45
|
+
if !entry.nil? and entry.length > 0 and entry.strip().length > 0
|
46
|
+
entry = entry.strip()
|
47
|
+
chunks = entry.split(' ')
|
48
|
+
if (not chunks.empty?) and (chunks.length == 4)
|
49
|
+
holder = chunks.first
|
50
|
+
end
|
51
|
+
end
|
52
|
+
lf.close
|
53
|
+
rescue => ex
|
54
|
+
lockex = VCSEIF_Exceptions::UnrecoverableException.new("#{ex.message}")
|
55
|
+
raise lockex, ex.message.to_s
|
56
|
+
end
|
57
|
+
if holder.empty?
|
58
|
+
raise VCSEIF_Exceptions::UnrecoverableException.new("lock file is empty or contains non-standard content")
|
59
|
+
end
|
60
|
+
return holder
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.create_lock(filename)
|
64
|
+
# remove any existing lock
|
65
|
+
self.destroy_lock(filename)
|
66
|
+
|
67
|
+
# and write the lock file with the PID of this process and a current timestamp
|
68
|
+
begin
|
69
|
+
info = "%d %s\n" % [$$, Time.now.utc.strftime("%Y-%m-%d %H:%M:%S Z")]
|
70
|
+
lf = File.new(filename, "w")
|
71
|
+
lf.write("%s\n" % info)
|
72
|
+
lf.close
|
73
|
+
rescue => ex
|
74
|
+
lockex = VCSEIF_Exceptions::UnrecoverableException.new("#{ex.message}")
|
75
|
+
raise lockex, ex.message.to_s
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
|
80
|
+
def self.destroy_lock(filename)
|
81
|
+
# remove the existing lock file
|
82
|
+
begin
|
83
|
+
File.delete(filename) if File.exists?(filename)
|
84
|
+
rescue => ex
|
85
|
+
lockex = VCSEIF_Exceptions::UnrecoverableException.new("#{ex.message}")
|
86
|
+
raise lockex, ex.message.to_s
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
@@ -0,0 +1,146 @@
|
|
1
|
+
# Copyright 2001-2013 Rally Software Development Corp. All Rights Reserved.
|
2
|
+
|
3
|
+
require 'socket'
|
4
|
+
require 'date'
|
5
|
+
|
6
|
+
$os = 'posix'
|
7
|
+
begin
|
8
|
+
require 'win32ole'
|
9
|
+
$os = 'windows'
|
10
|
+
rescue LoadError => ex
|
11
|
+
end
|
12
|
+
|
13
|
+
class ProcessInfo
|
14
|
+
|
15
|
+
attr_accessor :uid, :pid, :ppid, :started, :cmdline
|
16
|
+
|
17
|
+
def initialize(fields)
|
18
|
+
@uid = 0 # default to a Windows "safe" value
|
19
|
+
user_field = fields[0]
|
20
|
+
@pid = fields[1].to_i
|
21
|
+
@ppid = fields[2].to_i
|
22
|
+
junk = fields[3]
|
23
|
+
@started = fields[4]
|
24
|
+
@cmdline = fields[5]
|
25
|
+
begin
|
26
|
+
uid = user_field.to_i
|
27
|
+
@uid = uid
|
28
|
+
rescue Exception => ex
|
29
|
+
begin
|
30
|
+
require 'etc'
|
31
|
+
user = Etc.getpwnam(user_field)
|
32
|
+
@uid = user.uid.to_i
|
33
|
+
rescue
|
34
|
+
# do nothing, we'll limp along with the default value established earlier
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def to_s
|
40
|
+
"%5s %5s %5s %12s %s" % [@pid, @uid, @ppid, @started, @cmdline]
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
|
45
|
+
class ProcTable
|
46
|
+
|
47
|
+
def self.all_processes()
|
48
|
+
all_procs = []
|
49
|
+
|
50
|
+
case $os
|
51
|
+
when 'posix'
|
52
|
+
all_procs = ProcTable.posix_processes()
|
53
|
+
when 'windows'
|
54
|
+
all_procs = ProcTable.windows_processes()
|
55
|
+
end
|
56
|
+
|
57
|
+
#all_procs.each do |proc|
|
58
|
+
# puts "%5d %s" % [proc['pid'], proc['cmdline']]
|
59
|
+
#end
|
60
|
+
|
61
|
+
return all_procs
|
62
|
+
end
|
63
|
+
|
64
|
+
def self.posix_processes()
|
65
|
+
all_procs = []
|
66
|
+
ps_output = `ps -ef` # ps_output is single string
|
67
|
+
pslines = ps_output.split("\n")
|
68
|
+
hdr_line = pslines[0]
|
69
|
+
cmd_column_ix = hdr_line.index('CMD')
|
70
|
+
proc_lines = pslines[1..pslines.length-1]
|
71
|
+
proc_lines.each do |ps_entry|
|
72
|
+
#puts "|#{ps_entry}|"
|
73
|
+
fields = ps_entry.lstrip().split(/\s+/)
|
74
|
+
#uid, pid, ppid, junk, started = fields[0], fields[1], fields[2], fields[3], fields[4]
|
75
|
+
fields[5] = ps_entry[cmd_column_ix..ps_entry.length-1]
|
76
|
+
proc_info = ProcessInfo.new(fields)
|
77
|
+
all_procs << proc_info
|
78
|
+
#puts "#{proc.inspect}"
|
79
|
+
end
|
80
|
+
return all_procs
|
81
|
+
end
|
82
|
+
|
83
|
+
def self.windows_processes()
|
84
|
+
all_procs = []
|
85
|
+
host = Socket.gethostname()
|
86
|
+
wmi = WIN32OLE.connect("winmgmts://#{host}/root/cimv2")
|
87
|
+
wmi.InstancesOf("Win32_Process").each do |wproc|
|
88
|
+
if wproc.CreationDate.nil?
|
89
|
+
startDate = nil
|
90
|
+
else
|
91
|
+
startDate = Date.parse(wproc.CreationDate.split('.').first)
|
92
|
+
end
|
93
|
+
fields = [0, wproc.ProcessId, wproc.ParentProcessId, "", startDate, wproc.CommandLine]
|
94
|
+
proc_info = ProcessInfo.new(fields)
|
95
|
+
all_procs << proc_info
|
96
|
+
end
|
97
|
+
return all_procs
|
98
|
+
end
|
99
|
+
|
100
|
+
def self.target_process(target_pid)
|
101
|
+
aps = ProcTable.all_processes
|
102
|
+
target_proc = aps.select{|proc| proc.pid == target_pid.to_i}
|
103
|
+
return target_proc.first if target_proc.length > 0
|
104
|
+
return nil
|
105
|
+
end
|
106
|
+
|
107
|
+
def self.processes_owned_by(target_uid=Process.uid)
|
108
|
+
aps = ProcTable.all_processes
|
109
|
+
tups = aps.select{|proc| proc.uid == target_uid.to_i}
|
110
|
+
return tups
|
111
|
+
end
|
112
|
+
|
113
|
+
def self.processes_matching_pattern(pattern)
|
114
|
+
# this is not a true and foolproof regex pattern matching operation
|
115
|
+
# The unfortunate truth is that any pattern arg that has a backslash in it
|
116
|
+
# is probably going to fail, even though we'd like things like "foo\w+niski" to work.
|
117
|
+
# So, given that we expect most callers to provide a simple string, we'll first
|
118
|
+
# "adjust" their pattern by replacing backslashes with a token string of "<BACKWHACK>"
|
119
|
+
# and then also "adjust" each process cmdline text that same way (mostly to compensate
|
120
|
+
# for the MSWIN unfortunate choice of '\' as the path element separator char).
|
121
|
+
# and only then do we do a regex check for a match.
|
122
|
+
# If there are any matches using this algorithm, we'll return the results.
|
123
|
+
# Otherwise we will go ahead and perform the normal regex without any adjustments
|
124
|
+
# on the off chance that the caller actually provided a valid regex pattern string
|
125
|
+
# instead of a string literal with no regex entities embedded.
|
126
|
+
|
127
|
+
aps = ProcTable.all_processes
|
128
|
+
|
129
|
+
matches = []
|
130
|
+
aux_pattern = pattern.gsub('\\', '<BACKWHACK>')
|
131
|
+
aps.each do |proc|
|
132
|
+
next if proc.cmdline == nil # seems to only happen in Windoze land...
|
133
|
+
command = proc['cmdline']
|
134
|
+
aux_cmdline = command.gsub('\\', '<BACKWHACK>')
|
135
|
+
matches << proc if aux_cmdline =! /#{aux_pattern}/
|
136
|
+
end
|
137
|
+
|
138
|
+
return matches if matches.length > 0
|
139
|
+
|
140
|
+
mps = aps.select{|proc| proc.cmdline =~ /#{pattern}/}
|
141
|
+
return mps
|
142
|
+
end
|
143
|
+
|
144
|
+
end
|
145
|
+
|
146
|
+
|
@@ -0,0 +1,223 @@
|
|
1
|
+
# Copyright 2001-2013 Rally Software Development Corp. All Rights Reserved.
|
2
|
+
|
3
|
+
require "logger"
|
4
|
+
require "time"
|
5
|
+
|
6
|
+
# Tweak logger message format, with thanks to
|
7
|
+
# blog.grayproductions.net/articles/the_books_are_wrong_about_logger+ruby+logger+format
|
8
|
+
class CustomLogFormat < Logger::Formatter
|
9
|
+
def call(severity, time, program_name, message)
|
10
|
+
datetime = time.utc.strftime("%Y-%m-%d %H:%M:%S Z")
|
11
|
+
return "[%s] %5s : %s\n" % [datetime, severity, message]
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class RallyLogger
|
16
|
+
|
17
|
+
attr_accessor :errors_cache, :cache_errors, :warnings_cache, :cache_warnings
|
18
|
+
attr_reader :logger
|
19
|
+
|
20
|
+
VCSEIF_MAX_LOGFILES = 10
|
21
|
+
VCSEIF_MAX_LOGFILE_SIZE = 5*1024*1024 # 5 MB
|
22
|
+
|
23
|
+
def initialize(logfile_name=nil, max_logfiles=VCSEIF_MAX_LOGFILES, max_file_size=VCSEIF_MAX_LOGFILE_SIZE)
|
24
|
+
if logfile_name.nil?
|
25
|
+
@logger = Logger.new(STDOUT)
|
26
|
+
else
|
27
|
+
@logger = Logger.new(logfile_name, max_logfiles, max_file_size)
|
28
|
+
end
|
29
|
+
|
30
|
+
@cache_errors = false
|
31
|
+
@cache_warnings = false
|
32
|
+
@errors_cache = []
|
33
|
+
@warnings_cache = []
|
34
|
+
|
35
|
+
@logger.formatter = CustomLogFormat.new # Install custom formatter
|
36
|
+
@logger.level = Logger::DEBUG
|
37
|
+
end
|
38
|
+
|
39
|
+
public
|
40
|
+
def set_level(level)
|
41
|
+
case level
|
42
|
+
when 'Fatal'
|
43
|
+
@logger.level = Logger::FATAL
|
44
|
+
when 'Error'
|
45
|
+
@logger.level = Logger::ERROR
|
46
|
+
when 'Warn'
|
47
|
+
@logger.level = Logger::WARN
|
48
|
+
when 'Warning'
|
49
|
+
@logger.level = Logger::WARN
|
50
|
+
when 'Info'
|
51
|
+
@logger.level = Logger::INFO
|
52
|
+
when 'Debug'
|
53
|
+
@logger.level = Logger::DEBUG
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def fatal(text, exception=nil)
|
58
|
+
context = !exception.nil? ? exception_context(exception) : nominal_context()
|
59
|
+
@logger.fatal("#{context} - " + text)
|
60
|
+
end
|
61
|
+
|
62
|
+
def error(text, exception=nil)
|
63
|
+
context = !exception.nil? ? exception_context(exception) : nominal_context()
|
64
|
+
message = "#{context} - " + text
|
65
|
+
@logger.error(message)
|
66
|
+
if @cache_errors
|
67
|
+
datetime = Time.now.utc.strftime("%Y-%m-%d %H:%M:%S Z")
|
68
|
+
info = {:time => datetime, :message => message}
|
69
|
+
@errors_cache.push(info)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def warning(text, exception=nil)
|
74
|
+
context = !exception.nil? ? exception_context(exception) : nominal_context()
|
75
|
+
message = "#{context} - " + text
|
76
|
+
@logger.warn(message)
|
77
|
+
if @cache_warnings
|
78
|
+
datetime = Time.now.utc.strftime("%Y-%m-%d %H:%M:%S Z")
|
79
|
+
info = {:time => datetime, :message => message}
|
80
|
+
@warnings_cache.push(info)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
alias_method :warn, :warning
|
85
|
+
|
86
|
+
def info(text, exception=nil)
|
87
|
+
@logger.info("#{nominal_context()} - " + text)
|
88
|
+
end
|
89
|
+
|
90
|
+
def debug(text, exception=nil)
|
91
|
+
context = !exception.nil? ? exception_context(exception) : nominal_context()
|
92
|
+
@logger.debug("#{context} - " + text)
|
93
|
+
end
|
94
|
+
|
95
|
+
def unknown(text, exception=nil)
|
96
|
+
context = !exception.nil? ? exception_context(exception) : nominal_context()
|
97
|
+
@logger.unknown("#{context} - " + text)
|
98
|
+
end
|
99
|
+
|
100
|
+
def write(text)
|
101
|
+
@logger << (text + "\n")
|
102
|
+
end
|
103
|
+
|
104
|
+
def entry(text)
|
105
|
+
timestamp = Time.now.utc.strftime("%Y-%m-%d %H:%M:%S Z")
|
106
|
+
twig = "[%s] %s" % [timestamp, text]
|
107
|
+
write(twig)
|
108
|
+
end
|
109
|
+
|
110
|
+
# def exception(ex)
|
111
|
+
# if (ex.kind_of? RecoverableException)
|
112
|
+
# warn("Message " + ex.message)
|
113
|
+
# else
|
114
|
+
# error("Message " + ex.message)
|
115
|
+
# error( "Stack Trace")
|
116
|
+
# ex.backtrace.each {|trace| write(trace) }
|
117
|
+
# end
|
118
|
+
# end
|
119
|
+
|
120
|
+
def cache_errors(cache_on)
|
121
|
+
if cache_on === true
|
122
|
+
@cache_errors = true
|
123
|
+
else
|
124
|
+
@cache_errors = false
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def cache_warnings(cache_on)
|
129
|
+
if cache_on === true
|
130
|
+
@cache_warnings = true
|
131
|
+
else
|
132
|
+
@cache_warnings = false
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
def clear_caches_and_flags
|
137
|
+
@warnings_cache = []
|
138
|
+
@errors_cache = []
|
139
|
+
@cache_errors = false
|
140
|
+
@cache_warnings = false
|
141
|
+
end
|
142
|
+
|
143
|
+
private
|
144
|
+
|
145
|
+
def caller_info()
|
146
|
+
##
|
147
|
+
## stack_frames = caller(0)
|
148
|
+
## stack_frames.each_with_index do |stack, ix|
|
149
|
+
## puts "%d) %s" % [ix+1, stack]
|
150
|
+
## end
|
151
|
+
##
|
152
|
+
file, line, method = ["", "", ""]
|
153
|
+
whence = caller(3).first
|
154
|
+
if whence =~ /^(.+?):(\d+)(?::in `(.*)')?/
|
155
|
+
file, line, method = $1, $2.to_i, $3
|
156
|
+
[file, line, method]
|
157
|
+
end
|
158
|
+
return [file, line, method]
|
159
|
+
end
|
160
|
+
|
161
|
+
def nominal_context()
|
162
|
+
# ideally, we'd like to include the file_name:line_number only if this is being
|
163
|
+
# called because an exception has been raised
|
164
|
+
target_class = "<main>"
|
165
|
+
file_name, line_number, method = caller_info()
|
166
|
+
begin
|
167
|
+
file_content = File.open(file_name, "r").read()
|
168
|
+
file_content.gsub(/\r\n?/, "\n")
|
169
|
+
lines = file_content.split("\n")
|
170
|
+
line_number.downto(0).each do |ix|
|
171
|
+
if lines[ix] =~ /^\s*class\s+(\w+)/
|
172
|
+
target_class = $1
|
173
|
+
#puts "found target_class #{target_class}"
|
174
|
+
break
|
175
|
+
else
|
176
|
+
#puts "no class in line: #{file_content[ix]}"
|
177
|
+
end
|
178
|
+
end
|
179
|
+
rescue Exception => ex
|
180
|
+
puts "unable to open or read #{file_name}"
|
181
|
+
end
|
182
|
+
|
183
|
+
# squish out any leading 'block in ' text in method
|
184
|
+
# or rearrange if it mentions 'rescue in block in ' or 'block (n levels) in '
|
185
|
+
method = method.sub(/^block in /, '')
|
186
|
+
if method =~ /^block \(\d+ levels\) in /
|
187
|
+
method = method.sub(/^block \(\d+ levels\) in /, '')
|
188
|
+
end
|
189
|
+
if method =~ /rescue in block in /
|
190
|
+
method = method.sub(/rescue in block in/, '') + "- rescue block(#{line_number})"
|
191
|
+
end
|
192
|
+
#return "<class_name>.<method_name>(<file_name> line <line_number>)"
|
193
|
+
#return "%s.%s (%s line %d)" % [target_class, method, file_name, line_number]
|
194
|
+
return "%s.%s" % [target_class, method]
|
195
|
+
end
|
196
|
+
|
197
|
+
def exception_context(exception)
|
198
|
+
stack_frames = caller(0)
|
199
|
+
##
|
200
|
+
## puts "Exception context stack_frames to follow ..."
|
201
|
+
## ix = 0
|
202
|
+
## for frame in stack_frames.reverse do
|
203
|
+
## ix += 1
|
204
|
+
## path, line, method = frame.split(':', 3)
|
205
|
+
## path_elements = path.split('/')
|
206
|
+
## puts "%d) %s" % [ix, frame] unless path_elements.count('gems') > 1
|
207
|
+
## end
|
208
|
+
## puts "!- " * 25
|
209
|
+
## puts "(identified target frame as third to last)"
|
210
|
+
##
|
211
|
+
target_frame = stack_frames[2] # the third item in the stack
|
212
|
+
file_name, colon, line_number = target_frame.rpartition(':')
|
213
|
+
tattle = "%s line %s" % [file_name, line_number]
|
214
|
+
ex_class_name = exception.class.name
|
215
|
+
if ex_class_name.count(':') == 2
|
216
|
+
ex_module_name, ex_class_name = ex_class_name.split('::')
|
217
|
+
end
|
218
|
+
context = "#{ex_class_name} (#{tattle})"
|
219
|
+
return context
|
220
|
+
end
|
221
|
+
|
222
|
+
|
223
|
+
end
|