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.
@@ -0,0 +1,255 @@
1
+ # Copyright 2001-2013 Rally Software Development Corp. All Rights Reserved.
2
+
3
+ #################################################################################################
4
+ #
5
+ # auxload - Auxiliary functionality loader
6
+ # Contains classes to load auxiliary (plugin and extension) classes
7
+ # or the *_connection classes.
8
+ # A specification for functionality looks like:
9
+ # class_name.method_name
10
+ # or
11
+ # facility_name.class_name.method_name
12
+ #
13
+ #################################################################################################
14
+
15
+ class AuxLoader
16
+
17
+ @@facility_class_name = ""
18
+ @@facility_class = nil
19
+ @@facility = {} # key by @@facility_class_name, value is @@facility_class object
20
+
21
+ def self.getFacility(facility_type, facility_class_name, facility_name=nil)
22
+ """
23
+ Given a facility_class_name, look into @@facility for a key match.
24
+ If found return the corresponding value, which must be the
25
+ class object for the facility_class.
26
+ """
27
+ if not @@facility.include?(facility_class_name)
28
+ loadFacility(facility_type, facility_class_name, facility_name)
29
+ end
30
+ return @@facility[facility_class_name] || nil
31
+ end
32
+
33
+ def self.is_qualified?(target_dir, fn)
34
+ qualified = fn.end_with?('.rb') and File.file?('%s/%s' % [target_dir, fn])
35
+ return qualified
36
+ end
37
+
38
+ def self.loadFacility(subdir, class_name, facility=nil, method_existence=nil)
39
+ %{
40
+ Work the Ruby machinery to require in a Ruby file such that
41
+ a subsequent Kernel.const_get(class_name) works successfully.
42
+ The subdir parm is relative to ENV['VCS_APP_ROOT'].
43
+ The optional facility parm can "name" a specific file the class
44
+ must exist in.
45
+ Caller can also specify a string or an array of strings with method
46
+ names that must exist in the loaded class.
47
+ }
48
+ fq_facility_dir = "#{ENV['VCS_APP_ROOT']}/#{subdir}" # fully qualified path to subdir
49
+ ##
50
+ ## puts " loadFacility fq_facility_dir value: |%s|" % fq_facility_dir
51
+ ## $stdout.flush()
52
+ ##
53
+
54
+ if not File.exists?(fq_facility_dir)
55
+ raise StandardError, 'target subdir: %s not found' % [fq_facility_dir]
56
+ end
57
+ if not File.directory?(fq_facility_dir)
58
+ raise StandardError, 'target path %s is not a directory' % [fq_facility_dir]
59
+ end
60
+
61
+ if facility.nil?
62
+ target_dir = fq_facility_dir
63
+ rbfiles = Dir.entries(target_dir).select {|fn| self.is_qualified?(target_dir, fn)}
64
+ facility_names = rbfiles.collect {|fn| fn[0...-3]} # squeeze off the '.rb' portion of the name
65
+ else
66
+ facility_path = '%s/%s.rb' % [fq_facility_dir, facility]
67
+ if not File.file?(facility_path)
68
+ raise StandardError, 'No such %s module: %s found' % [subdir, facility_path]
69
+ end
70
+ facility_names = [facility]
71
+ end
72
+ ##
73
+ ## puts " facility_names: #{facility_names.inspect}"
74
+ ## $stdout.flush()
75
+ ##
76
+
77
+ target_classes_found = 0
78
+ tc_facility = {} # target class facility lookup dict
79
+ # look in all facilities, record the facility name if the facility_class_name is defined in there
80
+ facility_names.each do |facility_name|
81
+ facility_file = '%s/%s.rb' % [fq_facility_dir, facility_name]
82
+ File.open(facility_file) do |ff|
83
+ target_class_defs = ff.readlines.select {|line| line =~ /^class #{class_name}\s*/}
84
+ if target_class_defs.length > 0
85
+ tc_facility[class_name] = "%s/%s" % [subdir, facility_name]
86
+ target_classes_found +=1
87
+ end
88
+ end
89
+ end
90
+
91
+ if target_classes_found < 1
92
+ problem = "No class: '%s' defined in any file located in the '%s' directory"
93
+ raise StandardError, problem % [class_name, fq_facility_dir]
94
+ end
95
+ if target_classes_found > 1
96
+ problem = "%s class: '%s' defined in more than one file located in the '%s' directory"
97
+ raise StandardError, problem % [subdir, class_name, fq_facility_dir]
98
+ end
99
+
100
+ begin
101
+ require "#{tc_facility[class_name]}"
102
+ rescue Exception => ex
103
+ $stderr.write("Auxloader detected exception on require of #{tc_facility[class_name]}:\n")
104
+ $stderr.write(" #{ex.message}\n")
105
+ $stderr.flush()
106
+ raise
107
+ end
108
+
109
+ begin
110
+ @@facility_class = Kernel.const_get(class_name)
111
+ rescue Exception => ex
112
+ $stderr.write("Caught exception loading #{class_name}: #{ex.message}\n")
113
+ raise
114
+ end
115
+
116
+ if method_existence != nil
117
+ if method_existence.class.name == 'String'
118
+ method_existence = method_existence.split(',').collect {|mn| mn.strip()}
119
+ elsif method_existence.class.name != 'Array'
120
+ raise Exception, "method_existence parm must be a String or an Array of Strings"
121
+ end
122
+ for method_name in method_existence do
123
+ method = @@facility_class.instance_methods.select {|m| m == method_name.to_sym}
124
+ if method == nil or method == false
125
+ puts "Unable to validate that the %s class has a %s method" % [class_name, method_name]
126
+ end
127
+ end
128
+ end
129
+
130
+ @@facility[class_name] = @@facility_class
131
+ return @@facility[class_name]
132
+ end
133
+
134
+ end
135
+
136
+ #################################################################################################
137
+
138
+ class ClassLoader < AuxLoader
139
+
140
+ def self.loadConnectionClass(subdir, class_name, facility_name=nil)
141
+ %{
142
+ A generalized means of loading a class.
143
+ The priority ladder is to first attempt to find and load
144
+ the class_name from the specific subdir named in any facility_name supplied,
145
+ In the event that fails, the next attempt to get the class loaded in
146
+ looks in the '#{ENV['VCS_APP_ROOT']}/lib/vcseif' subdirectory.
147
+ If that also fails, an attempt is made to load the class via
148
+ Kernel.const_get in the event that the class is in an already loaded gem.
149
+ If a facility_name is given, it restricts the location of the class
150
+ to a specific Ruby file (named by facility_name, which would be the part of
151
+ the filename without the ".rb" suffix).
152
+ }
153
+ ##
154
+ ## $stdout.write("call of ClassLoader.loadConnectionClass(#{subdir}, #{class_name}, #{facility_name})\n")
155
+ ## $stdout.flush()
156
+ ##
157
+ candidates = [[1, subdir], [2, 'lib/vcseif']]
158
+ for rung, candidate in candidates do
159
+ fq_facility_dir = "#{ENV['VCS_APP_ROOT']}/#{candidate}" # fully qualified path to candidate
160
+ if not File.exists?(fq_facility_dir) or not File.directory?(fq_facility_dir)
161
+ ##
162
+ ## $stdout.write(" rung ##{rung} of loadConnectionClass skipped\n")
163
+ ##
164
+ next
165
+ end
166
+
167
+ success = false
168
+ begin
169
+ conn_class = loadFacility(candidate, class_name, facility_name)
170
+ ##
171
+ ## $stdout.write(" rung ##{rung} of loadConnectionClass succeeded\n")
172
+ ## $stdout.flush()
173
+ ##
174
+ success = true
175
+ rescue Exception => ex
176
+ ##
177
+ ## $stdout.write(" rung ##{rung} of loadConnectionClass failed...\n")
178
+ ## $stdout.flush()
179
+ ##
180
+ raise if ex.message.include?("defined in more than one file")
181
+ end
182
+ return conn_class if success
183
+
184
+ end
185
+
186
+ #
187
+ # try a Kernel.const_get(class_name) to see
188
+ # if we can obtain the class from an already loaded relevant *eif gem
189
+ #
190
+ begin
191
+ @@facility_class = Kernel.const_get(class_name)
192
+ success = true
193
+ rescue Exception => ex
194
+ $stderr.write("Caught exception attempting to load #{class_name}: #{ex.message}\n")
195
+ raise
196
+ end
197
+ return @@facility_class if success
198
+
199
+ return nil
200
+ end
201
+
202
+ end
203
+
204
+ #################################################################################################
205
+
206
+ class PluginLoader < AuxLoader
207
+
208
+ def self.loadFacility(facility_type, facility_class_name, facility_name)
209
+ super('plugin', facility_class_name, facility_name, 'service')
210
+ end
211
+
212
+ def self.getPlugin(plugin_class_name)
213
+ """
214
+ Given a plugin_class_name as a String, return the corresponding class
215
+ if such a class exists in the conventional location.
216
+ """
217
+ return getFacility('plugin', plugin_class_name)
218
+ end
219
+
220
+ end
221
+
222
+ ##########################################################################################################
223
+
224
+ class ExtensionLoader < AuxLoader
225
+
226
+ def self.loadFacility(facility_type, facility_class_name, facility_name)
227
+ super('extension', facility_class_name, facility_name, 'service')
228
+ end
229
+
230
+ def self.getExtension(extension_spec)
231
+ """
232
+ Given an extension_spec as a String (in either 'ClassName or facility.ClassName'
233
+ format), return the corresponding class if such a class exists in the
234
+ extension subdirectory.
235
+ """
236
+ ##
237
+ ## puts "ExtensionLoader.getExtension extension_spec: |%s|" % extension_spec
238
+ ##
239
+ facility_name = nil
240
+ #split the extension_spec on '.'
241
+ if not extension_spec.include?('.')
242
+ extension_class_name = extension_spec
243
+ elsif extension_spec.count('.') == 1
244
+ facility_name, extension_class_name = extension_spec.split('.')
245
+ # where facility_name is the basename of a Ruby file containing the extension_class_name
246
+ else
247
+ raise StandardError, 'Invalid extension specification: %s' % extension_spec
248
+ end
249
+
250
+ return getFacility('extension', extension_class_name, facility_name)
251
+ end
252
+
253
+ end
254
+
255
+
@@ -0,0 +1,101 @@
1
+ # Copyright 2001-2013 Rally Software Development Corp. All Rights Reserved.
2
+
3
+ module VCSEIF_Exceptions
4
+
5
+ ######################################################################################
6
+
7
+ $_blurt = false
8
+ $_logger = nil
9
+
10
+ ######################################################################################
11
+
12
+ def self.logAllExceptions(truthiness, logger=nil)
13
+ """
14
+ Call this method when you want the convenience of raising the
15
+ Exception subclasses defined in this file module without having to supply
16
+ an instance of a logger for each raise statement.
17
+ Simply supply the logger to be used in this call and you're set
18
+ for the duration!
19
+ """
20
+ if truthiness and !logger.nil?
21
+ $_blurt = true
22
+ $_logger = logger
23
+ else
24
+ $_blurt = false
25
+ $_logger = nil
26
+ end
27
+ end
28
+
29
+ ######################################################################################
30
+
31
+ class VCSEIF_Exception < StandardError
32
+
33
+ @@log_level = 'info'
34
+
35
+ attr_accessor :verbiage
36
+
37
+ def initialize(msg, logger=nil)
38
+ mod_name, class_name = self.class.name.split("::")
39
+ if !logger.nil?
40
+ logger.send(@@log_level, msg, self)
41
+ elsif ($_blurt and !$_logger.nil?)
42
+ $_logger.send(@@log_level, msg, self)
43
+ #else
44
+ ## prepend the specific exception class name to the msg text
45
+ # $stderr.write("%s: %s\n" % [class_name, msg])
46
+ end
47
+ @verbiage = msg
48
+ end
49
+ end
50
+
51
+ class RecoverableException < VCSEIF_Exception
52
+ """
53
+ This brand of VCSEIF_Exception is intended be used when a single atomic operation
54
+ has failed but further processing can be attempted.
55
+ """
56
+ @@log_level = 'warn'
57
+ end
58
+
59
+ class UnrecoverableException < VCSEIF_Exception
60
+ """
61
+ This brand of VCSEIF_Exception is intended be used when an operation has failed and
62
+ no further processing can or should be attempted.
63
+ The condition that caused this operation failure is either caused by a
64
+ bad configuration or something else that won't be different with the passage
65
+ of time.
66
+ """
67
+ @@log_level = 'error'
68
+ end
69
+
70
+ class ConfigurationError < VCSEIF_Exception
71
+ """
72
+ This brand of VCSEIF_Exception is intended to be used when a configuration error
73
+ has been detected. Because of the configuration error no further processing
74
+ can or should proceed. The configuration will have to be fixed or this
75
+ exception would be raised again in a future invocation.
76
+ """
77
+ @@log_level = 'fatal'
78
+ end
79
+
80
+ class NonFatalConfigurationError < VCSEIF_Exception
81
+ """
82
+ This brand of VCSEIF_Exception is intended to be used when a configuration error
83
+ has been detected. This exception differs from the ConfigurationError
84
+ in that the configuration error detected will not prevent reasonable operation
85
+ from proceeding but should be noted for future correction.
86
+ """
87
+ @@log_level = 'error'
88
+ end
89
+
90
+ class OperationalError < VCSEIF_Exception
91
+ """
92
+ This brand of VCSEIF_Exception is intendedto be used when an operation has failed and
93
+ no further processing can or should be attempted.
94
+ The condition that caused this though may not be in place if the operation were
95
+ attempted again at a later time.
96
+ """
97
+ @@log_level = 'error'
98
+ end
99
+
100
+ end # of the VCSEIF_Exceptions Module
101
+
@@ -0,0 +1,71 @@
1
+ # Copyright 2001-2013 Rally Software Development Corp. All Rights Reserved.
2
+
3
+ require "base64"
4
+
5
+ # This really basic algorithm encodes but does not encrypt strings
6
+
7
+ #Thanks to Erika for the idea about adding some text at the beginning of the encoded password
8
+ # This clues people in that the password is encoded and also makes sure that the encoding decoding will never fail.
9
+ # Without the extra text, "It will fail if a password has the same number of SEPARATORs as characters"
10
+
11
+ class Fuzzer
12
+ SEPARATOR = "-"
13
+ ENCODED = "encoded" + SEPARATOR
14
+
15
+ def self.encode(string)
16
+ return string if string.class.name != 'String'
17
+ return string if string.empty?
18
+ encoded = ""
19
+ Base64.encode64(string).each_byte { |b|
20
+ if ( b != 10 ) # \n
21
+ encoded = encoded + b.chr + SEPARATOR
22
+ end
23
+ }
24
+ return ENCODED + encoded
25
+ end
26
+
27
+ def self.fuzz(string)
28
+ # would have been nice to be able to do alias_method :fuzz, :encode
29
+ return self.encode(string)
30
+ end
31
+
32
+
33
+ def self.decode(string)
34
+ return string if string.class.name != 'String'
35
+ return string if string.empty?
36
+ if self.is_encoded?(string)
37
+ decoded = self.remove_every_second_char(string.slice(ENCODED.length,string.length))
38
+ return Base64.decode64(decoded)
39
+ else
40
+ return string
41
+ end
42
+ end
43
+
44
+ def self.defuzz(string)
45
+ #would have been nice to be able to do alias_method :defuzz, :decode
46
+ return self.decode(string)
47
+ end
48
+
49
+
50
+ def self.is_encoded?(string)
51
+ %{
52
+ Does the string arg start with "encoded-" and are there an
53
+ equal number of separator strings and regular characters?
54
+ }
55
+ return false if string.class.name != 'String'
56
+ sliced = string.slice(ENCODED.length, string.length)
57
+ return string.slice(0,ENCODED.length) == ENCODED && sliced.length == sliced.count(SEPARATOR)*2
58
+ end
59
+
60
+ private
61
+ def self.remove_every_second_char(string)
62
+ scrunched = ""
63
+ for i in 0..string.length-1
64
+ if i%2 == 0
65
+ scrunched = scrunched + string.slice(i, 1)
66
+ end
67
+ end
68
+ return scrunched
69
+ end
70
+
71
+ end
@@ -0,0 +1,421 @@
1
+ # Copyright 2001-2013 Rally Software Development Corp. All Rights Reserved.
2
+
3
+ __doc__ = %{
4
+ The Konfabulator class defined in this file handles deconstructing a Hash based
5
+ configuration to enable easy access to the semantics of the configuration specification.
6
+ In addition, the Konfabulator encodes any clear text passwords in the underlying file
7
+ from which the Hash was populated.
8
+ }
9
+
10
+ #################################################################################################
11
+
12
+ require 'fileutils' # for copy, move, remove_file
13
+
14
+ require_relative "exceptions" # for ConfigurationError, NonFatalConfigurationError
15
+ require_relative "fuzzer" # for Fuzzer
16
+
17
+ #################################################################################################
18
+
19
+ SUPPORTED_VCS_TARGETS = ['Mercurial', 'Git', 'Subversion', 'Perforce', 'TFS', 'Bazaar']
20
+
21
+ #################################################################################################
22
+
23
+ class Konfabulator
24
+ %{
25
+ An instance of this class provides a means to slurp configuration information
26
+ from a Ruby Hash structure and arrange it within the instance for easy consumption
27
+ by holders of a Konfabulator instance.
28
+ An instance also offers the capability to "fuzz" clear-text passwords in a
29
+ config file into an encoded (but not encrypted) form and to be able to handle
30
+ defuzzing those encoded passwords for holders of the instance when they access
31
+ those values in the instance.
32
+ }
33
+ attr_accessor :top_level_sequence
34
+ attr_reader :config, :config_name, :log, :vcs_header, :content
35
+
36
+ def initialize(config_spec, logger)
37
+ """
38
+ config_spec is a Hash
39
+ """
40
+
41
+ @config = config_spec
42
+ @config_name = config_spec['ConfigName']
43
+ @log = logger
44
+ @top_level_sequence = []
45
+
46
+ content = []
47
+ begin
48
+ cf = File.open(config_spec['ConfigPath'], "r")
49
+ @content = cf.readlines()
50
+ cf.close()
51
+ rescue IOError => ex
52
+ raise
53
+ end
54
+
55
+ conf_lines = @content.select { |line| line.class.name == "String" and line !~ /^\s*#/ }
56
+ section_headers = conf_lines.select { |line| line =~ /^[A-Z][A-Za-z_]+.+\s*:/}
57
+ if section_headers.length < 2
58
+ problem = 'Insufficient content in config file: %s' % @config['ConfigPath']
59
+ confex = VCSEIF_Exceptions::ConfigurationError.new(problem)
60
+ raise confex, problem
61
+ end
62
+ if section_headers.length > 5
63
+ admonition = 'Excess content in config file: %s' % @config['ConfigPath']
64
+ confex = VCSEIF_Exceptions::NonFatalConfigurationError.new(admonition)
65
+ raise confex, admonition
66
+ end
67
+ section_headers = section_headers.collect {|sh| sh.strip().sub(/\s*:.*$/, '')}
68
+
69
+ connector_ident_header = section_headers.shift()
70
+ if connector_ident_header !~ /Connector/
71
+ problem = 'First section in config file must identify the Connector type'
72
+ confex = VCSEIF_Exceptions::ConfigurationError.new(problem)
73
+ raise confex, problem
74
+ end
75
+
76
+ rally_header = section_headers.shift()
77
+ if not rally_header.start_with?('Rally')
78
+ problem = 'Second section in config file must be Rally section'
79
+ confex = VCSEIF_Exceptions::ConfigurationError.new(problem)
80
+ raise confex, problem
81
+ end
82
+ @top_level_sequence << 'Rally'
83
+
84
+ @vcs_header = section_headers.shift
85
+
86
+ known = SUPPORTED_VCS_TARGETS.include?(@vcs_header)
87
+ if known.nil? or not known
88
+ problem = 'Third section in config file must identify a known VCS identifier'
89
+ confex = VCSEIF_Exceptions::ConfigurationError.new(problem)
90
+ raise confex, problem
91
+ end
92
+ @top_level_sequence << @vcs_header
93
+
94
+ # set up defaults for the Services and Transforms sections if those sections aren't in the config file
95
+ if not @config.has_key?('Services')
96
+ @config['Services'] = {}
97
+ @config['Services']['LogLevel'] = 'Info'
98
+ @config['Services']['Preview'] = false
99
+ @config['Services']['PostBatchExtension'] = nil
100
+ @log.info('no Services section specified in the config, using default settings')
101
+ @top_level_sequence << 'Services'
102
+ end
103
+
104
+ if not @config.has_key?('Transforms')
105
+ @config['Transforms'] = {}
106
+ @log.info('no Transforms section specified in the config')
107
+ @top_level_sequence << 'Transforms'
108
+ end
109
+
110
+ while section_headers.length > 0 do
111
+ header = section_headers.shift
112
+ begin
113
+ if not ['Services', 'Transforms'].include?(header)
114
+ problem = 'Unrecognized section header "%s" found in config' % header
115
+ confex = VCSEIF_Exceptions::ConfigurationError.new(problem)
116
+ raise confex, problem
117
+ else
118
+ @top_level_sequence << header
119
+ end
120
+ rescue VCSEIF_Exceptions::ConfigurationError => ex
121
+ # noop
122
+ end
123
+ end
124
+
125
+ checkRallySectionSanity
126
+ checkTargetVCSSectionSanity
127
+
128
+ # defuzz the passwords for our internal use if they are fuzzed,
129
+ # and if they are not, fuzz them in the file
130
+ defuzzPasswords()
131
+ end
132
+
133
+
134
+ private
135
+ def checkRallySectionSanity()
136
+ """ do some rudimentary checking for Rally section subitems of Server,
137
+ Protocol, Username, Password, Workspace, RepositoryName
138
+ """
139
+ rally = @config['Rally']
140
+
141
+ server_spec = rally['Server'] || nil
142
+ if server_spec.nil? or server_spec.empty?
143
+ problem = 'No Server specification item value detected in the Rally section of the config'
144
+ confex = VCSEIF_Exceptions::ConfigurationError.new(problem)
145
+ raise confex, problem
146
+ end
147
+
148
+ proto_spec = rally['Protocol'] || nil
149
+ if proto_spec.nil? or proto_spec.empty?
150
+ proto_spec = 'https'
151
+ rally['Protocol'] = proto_spec
152
+ end
153
+ if not ['http', 'https'].include?(proto_spec)
154
+ problem = "Invalid Protocol specification item value '%s' detected in the Rally section of the config" % proto_spec
155
+ confex = VCSEIF_Exceptions::ConfigurationError.new(problem)
156
+ raise confex, problem
157
+ end
158
+
159
+ user_name = rally['Username'] || nil
160
+ if user_name.nil? or user_name.empty?
161
+ problem = 'No Username value detected in the Rally section of the config'
162
+ confex = VCSEIF_Exceptions::ConfigurationError.new(problem)
163
+ raise confex, problem
164
+ end
165
+
166
+ password = rally['Password'] || nil
167
+ if password.nil? or password.empty?
168
+ problem = 'No Password value detected in the Rally section of the config'
169
+ confex = VCSEIF_Exceptions::ConfigurationError.new(problem)
170
+ raise confex, problem
171
+ end
172
+
173
+ workspace = rally['Workspace'] || nil
174
+ if workspace.nil? or workspace.empty?
175
+ problem = 'No Workspace value detected in the Rally section of the config'
176
+ confex = VCSEIF_Exceptions::ConfigurationError.new(problem)
177
+ raise confex, problem
178
+ end
179
+
180
+ repo_name = rally['RepositoryName'] || nil
181
+ if repo_name.nil? or repo_name.empty?
182
+ problem = 'No RepositoryName value detected in the Rally section of the config'
183
+ confex = VCSEIF_Exceptions::ConfigurationError.new(problem)
184
+ raise confex, problem
185
+ end
186
+
187
+ lookback = rally['Lookback'] || nil
188
+ if lookback.nil? #or lookback.class.name != "Fixnum"
189
+ rally['Lookback'] = 90 # default to 90 minutes if not specified
190
+ end
191
+
192
+ upd_art_state = rally['UpdateArtifactState'] || nil
193
+ if upd_art_state.nil?
194
+ rally['UpdateArtifactState'] = false
195
+ end
196
+
197
+ if rally['UpdateArtifactState']
198
+ statex_class_name = rally['StateExtractorClass'] || nil
199
+ if statex_class_name.nil?
200
+ rally['StateExtractorClass'] = 'BasicActionsAndArtifactsExtractor'
201
+ @log.warn('Defaulting StateExtractorClass to %s, as UpdateArtifactState was set to true but no value was provided for the StateExtractorClass' % rally['StateExtractorClass'])
202
+ end
203
+ end
204
+
205
+ end
206
+
207
+ def checkTargetVCSSectionSanity()
208
+ """
209
+ Need to have RepositoryBase for non TFS VCS systems (Git, Hg, Subversion)
210
+ Can have Server, Username, Password
211
+ Uri, Lookback, MaxItems, Debug
212
+ """
213
+ non_vcs_sections = ['Rally', 'Transforms', 'Services']
214
+ sections = topLevels().select {|name| non_vcs_sections.include?(name) == false}
215
+ vcs_section_name = sections.first
216
+ vcs = @config[vcs_section_name]
217
+
218
+ if not vcs_section_name.start_with?('TFS')
219
+ repo_base = vcs['RepositoryBase'] || nil
220
+ if repo_base.nil?
221
+ problem = 'No RepositoryBase value detected in the %s section of the config' % vcs_section_name
222
+ confex = VCSEIF_Exceptions::ConfigurationError.new(problem)
223
+ raise confex, problem
224
+ end
225
+ else
226
+ collection_name = vcs['CollectionName'] || nil
227
+ if collection_name.nil? || collection_name.empty?
228
+ problem = 'No CollectionName value detected in the %s section of the config' % vcs_section_name
229
+ confex = VCSEIF_Exceptions::ConfigurationError.new(problem)
230
+ raise confex, problem
231
+ end
232
+ team_project_name = vcs['TeamProject'] || nil
233
+ if team_project_name.nil? || team_project_name.empty?
234
+ problem = 'No TeamProject value detected in the %s section of the config' % vcs_section_name
235
+ confex = VCSEIF_Exceptions::ConfigurationError.new(problem)
236
+ raise confex, problem
237
+ end
238
+ tfs_server_version = vcs['TfsServerVersion'] || nil
239
+ if tfs_server_version.nil? || tfs_server_version.to_s.empty?
240
+ problem = 'No TfsServerVersion value detected in the %s section of the config' % vcs_section_name
241
+ confex = VCSEIF_Exceptions::ConfigurationError.new(problem)
242
+ raise confex, problem
243
+ end
244
+ end
245
+
246
+ # default VCS Lookback value to 90 if not set or not set correctly
247
+ lookback = vcs['Lookback'] || nil
248
+ if lookback.nil? #or lookback.class.name != "Fixnum"
249
+ vcs['Lookback'] = 90
250
+ end
251
+
252
+ # default VCS MaxItems to 1000 if not set or not set correctly
253
+ max_items = vcs['MaxItems'] || nil
254
+ if max_items.nil? or max_items.class.name != "Fixnum"
255
+ vcs['MaxItems'] = 1000
256
+ end
257
+ end
258
+
259
+ public
260
+ def topLevels()
261
+ return @top_level_sequence
262
+ end
263
+
264
+
265
+ def topLevel(section_name)
266
+ if section_name == 'VCS'
267
+ non_vcs_sections = ['Rally', 'Transforms', 'Services']
268
+ sections = topLevels().select {|name| non_vcs_sections.include?(name) == false}
269
+ section_name = sections.first
270
+ end
271
+
272
+ if @top_level_sequence.include?(section_name) and @config.has_key?(section_name)
273
+ return @config[section_name]
274
+ else
275
+ problem = 'Attempt to retrieve non-existent top level config section for %s'
276
+ confex = VCSEIF_Exceptions::ConfigurationError.new(problem % section_name)
277
+ raise confex, problem % section_name
278
+ end
279
+ end
280
+
281
+
282
+ def connectionClassName(section_name)
283
+ if not @config.has_key?(section_name)
284
+ problem = 'Attempt to identify connection class name for %s, operation not supported'
285
+ confex = VCSEIF_Exceptions::ConfigurationError.new(problem % section_name)
286
+ raise confex, problem % section_name
287
+ end
288
+ if not ['Rally', @vcs_header].include?(section_name)
289
+ problem ='Candidate connection class name "%s" not viable for operation' % section_name
290
+ confex = VCSEIF_Exceptions::ConfigurationError.new(problem)
291
+ raise confex, problem % section_name
292
+ end
293
+ section = @config[section_name]
294
+ if section.has_key?('Class')
295
+ class_name = section['Class']
296
+ else
297
+ class_name = 'RallyVCSConnection'
298
+ if section_name != 'Rally'
299
+ class_name = '%sConnection' % @vcs_header
300
+ end
301
+ end
302
+
303
+ return class_name
304
+ end
305
+
306
+
307
+ private
308
+ def defuzzPasswords()
309
+ rpw = @config['Rally']['Password']
310
+ if Fuzzer.is_encoded?(rpw)
311
+ @config['Rally']['Password'] = Fuzzer.defuzz(rpw)
312
+ else
313
+ fuzzPassword('Rally', 'Password', rpw)
314
+ end
315
+
316
+ proxy_pw = @config['Rally']['ProxyPassword']
317
+ unless proxy_pw.nil?
318
+ if Fuzzer.is_encoded?(proxy_pw)
319
+ @config['Rally']['ProxyPassword'] = Fuzzer.defuzz(proxy_pw)
320
+ else
321
+ fuzzPassword('Rally', 'ProxyPassword', proxy_pw)
322
+ end
323
+ end
324
+
325
+ # in many cases, there isn't a requirement that the VCS section have a Password entry
326
+ vpw = @config[@vcs_header]['Password'] || nil
327
+ if not vpw.nil?
328
+ if Fuzzer.is_encoded?(vpw)
329
+ @config[@vcs_header]['Password'] = Fuzzer.defuzz(vpw)
330
+ else
331
+ fuzzPassword(@vcs_header, 'Password', vpw)
332
+ end
333
+ end
334
+ end
335
+
336
+
337
+ def fuzzPassword(conn_section, password_section, clear_text_password)
338
+ """
339
+ Check to see whether or not the Password entries in one or both of the connection
340
+ sections are not encoded. If not, encode them and write out the config file
341
+ changing *only* those two entries.
342
+ """
343
+ return false if Fuzzer.is_encoded?(clear_text_password)
344
+ encoded_password = Fuzzer.encode(clear_text_password)
345
+
346
+ conf_lines = []
347
+ begin
348
+ cf = File.open(@config['ConfigPath'], 'r')
349
+ rescue IOError => ex
350
+ problem = 'Unable to open %s for reading, %s' % [@config['ConfigPath'], ex.message]
351
+ confex = VCSEIF_Exceptions::ConfigurationError.new(problem)
352
+ raise confex, problem
353
+ end
354
+ conf_lines = cf.readlines()
355
+ cf.close()
356
+
357
+ out_lines = []
358
+ ix = 0
359
+
360
+ # objective: Find index of conn_section entry in conf_lines
361
+ # then find index of next occurring Password : xxxx entry
362
+ # substitute entry for Password : current with Password : encoded_password
363
+ hits = []
364
+ conf_lines.each_with_index do |line, ix|
365
+ hits << ix if line =~ /^#{conn_section}\s*:/
366
+ end
367
+ return false if hits.length == 0
368
+
369
+ section_ix = hits.first
370
+ hits = []
371
+ conf_lines.each_with_index do |line, ix|
372
+ hits << ix if line =~ /^\s+#{password_section}\s*:\s*/ and ix > section_ix
373
+ end
374
+ return false if hits.length == 0
375
+
376
+ pwent_ix = hits.first
377
+ conf_lines[pwent_ix] = " #{password_section} : %s\n" % encoded_password
378
+
379
+ enc_file_name = "#{@config['ConfigPath']}.pwenc"
380
+ enf = File.open(enc_file_name, 'w')
381
+ enf.write(conf_lines.join(""))
382
+ enf.close()
383
+
384
+ bkup_name = "#{@config['ConfigPath']}.bak"
385
+ begin
386
+ FileUtils.copy(@config['ConfigPath'], bkup_name)
387
+ rescue Exception => ex
388
+ problem = "Unable to write a temporary backup file '%s' with config info, %s"
389
+ @log.warn(problem % [bkup_name, ex.message])
390
+ return false
391
+ end
392
+
393
+ begin
394
+ FileUtils.remove_file(@config['ConfigPath'])
395
+ rescue Exception => ex
396
+ problem = "Unable to remove config file prior to replacement with "
397
+ problem << "password encoded version of the file, %s"
398
+ @log.warn(problem % ex.message)
399
+ return false
400
+ end
401
+
402
+ begin
403
+ FileUtils.move(enc_file_name, @config['ConfigPath'])
404
+ rescue Exception => ex
405
+ problem = "Unable to rename config file with password encoded "
406
+ problem << "to standard config filename of %s, %s"
407
+ @log.error(problem % [@onfig['ConfigPath'], ex.message])
408
+ return false
409
+ end
410
+
411
+ begin
412
+ FileUtils.remove_file(bkup_name)
413
+ rescue Exception => ex
414
+ @log.warn("Unable to remove temporary backup file for config, %s" % ex.message)
415
+ return false
416
+ end
417
+
418
+ return true
419
+ end
420
+
421
+ end # of Konfabulator