vcseif 1.2.0

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