587
|
1 |
#============================================================================
|
|
2 |
#Name : __init__.py
|
|
3 |
#Part of : Helium
|
|
4 |
|
|
5 |
#Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies).
|
|
6 |
#All rights reserved.
|
|
7 |
#This component and the accompanying materials are made available
|
|
8 |
#under the terms of the License "Eclipse Public License v1.0"
|
|
9 |
#which accompanies this distribution, and is available
|
|
10 |
#at the URL "http://www.eclipse.org/legal/epl-v10.html".
|
|
11 |
#
|
|
12 |
#Initial Contributors:
|
|
13 |
#Nokia Corporation - initial contribution.
|
|
14 |
#
|
|
15 |
#Contributors:
|
|
16 |
#
|
|
17 |
#Description:
|
|
18 |
#===============================================================================
|
|
19 |
|
|
20 |
""" CM/Synergy Python toolkit.
|
|
21 |
|
|
22 |
"""
|
|
23 |
|
|
24 |
import logging
|
|
25 |
import netrc
|
|
26 |
import os
|
|
27 |
import re
|
|
28 |
import subprocess
|
|
29 |
import threading
|
|
30 |
|
|
31 |
import fileutils
|
|
32 |
import nokia.gscm
|
|
33 |
import tempfile
|
|
34 |
import socket
|
|
35 |
|
|
36 |
# Uncomment this line to enable logging in this module, or configure logging elsewhere
|
|
37 |
_logger = logging.getLogger("ccm")
|
|
38 |
#logging.basicConfig(level=logging.DEBUG)
|
|
39 |
|
|
40 |
|
|
41 |
VALID_OBJECT_STATES = ('working', 'checkpoint', 'public', 'prep', 'integrate', 'sqa', 'test','released')
|
|
42 |
STATIC_OBJECT_STATES = ('integrate', 'sqa', 'test','released')
|
|
43 |
CCM_SESSION_LOCK = os.path.join(tempfile.gettempdir(), "ccm_session.lock")
|
|
44 |
|
|
45 |
def _execute(command, timeout=None):
|
|
46 |
""" Runs a command and returns the result data. """
|
|
47 |
targ = ""
|
|
48 |
if timeout is not None:
|
|
49 |
targ = "--timeout=%s" % timeout
|
|
50 |
process = subprocess.Popen("python -m timeout_launcher %s -- %s" % (targ, command), stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=True)
|
|
51 |
stdout = process.communicate()[0]
|
|
52 |
process.wait()
|
|
53 |
_logger.debug(stdout)
|
|
54 |
_logger.debug("Return code: %s" % process.returncode)
|
|
55 |
return (stdout, process.returncode)
|
|
56 |
|
|
57 |
|
|
58 |
class CCMException(Exception):
|
|
59 |
""" Base exception that should be raised by methods of this framework. """
|
|
60 |
def __init__(self, reason, result = None):
|
|
61 |
Exception.__init__(self, reason)
|
|
62 |
self.result = result
|
|
63 |
|
|
64 |
|
|
65 |
class Result(object):
|
|
66 |
"""Class that abstracts ccm call result handling.
|
|
67 |
|
|
68 |
Subclass it to implement a new generic output parser.
|
|
69 |
"""
|
|
70 |
def __init__(self, session):
|
|
71 |
self._session = session
|
|
72 |
self.status = None
|
|
73 |
self._output = None
|
|
74 |
self._output_str = None
|
|
75 |
|
|
76 |
def _setoutput(self, output):
|
|
77 |
"""set output"""
|
|
78 |
self._output = output
|
|
79 |
|
|
80 |
def __setoutput(self, output):
|
|
81 |
""" Internal function to allow overloading, you must override _setoutput.
|
|
82 |
"""
|
|
83 |
# the output is automatically converted to ascii before any treatment
|
|
84 |
if isinstance(output, unicode):
|
|
85 |
self._output_str = output.encode('ascii', 'replace')
|
|
86 |
else:
|
|
87 |
self._output_str = output.decode('ascii', 'ignore')
|
|
88 |
_logger.debug("output ---->")
|
|
89 |
for line in self._output_str.splitlines():
|
|
90 |
_logger.debug(line)
|
|
91 |
_logger.debug("<----")
|
|
92 |
self._setoutput(self._output_str)
|
|
93 |
|
|
94 |
def _getoutput(self):
|
|
95 |
""" Returns the content of _output. """
|
|
96 |
return self._output
|
|
97 |
|
|
98 |
def __str__(self):
|
|
99 |
""" Synergy output log. """
|
|
100 |
return self._output_str.encode('ascii', 'replace')
|
|
101 |
|
|
102 |
output = property(_getoutput, __setoutput)
|
|
103 |
|
|
104 |
class ResultWithError(Result):
|
|
105 |
""" A result class to parse output errors """
|
|
106 |
def __init__(self, session):
|
|
107 |
Result.__init__(self, session)
|
|
108 |
self._error = None
|
|
109 |
self._error_str = None
|
|
110 |
|
|
111 |
def _seterror(self, error):
|
|
112 |
"""set the error """
|
|
113 |
self._error = error
|
|
114 |
|
|
115 |
def __seterror(self, error):
|
|
116 |
""" Internal function to allow overloading, you must override _seterror.
|
|
117 |
"""
|
|
118 |
# the error output is automatically converted to ascii before any treatment
|
|
119 |
if isinstance(error, unicode):
|
|
120 |
self._error_str = error.encode('ascii', 'replace')
|
|
121 |
else:
|
|
122 |
self._error_str = error.decode('ascii', 'ignore')
|
|
123 |
_logger.debug("error ---->")
|
|
124 |
for line in self._error_str.splitlines():
|
|
125 |
_logger.debug(line)
|
|
126 |
_logger.debug("<----")
|
|
127 |
self._seterror(self._error_str)
|
|
128 |
|
|
129 |
def _geterror(self):
|
|
130 |
""" Returns the content of _output. """
|
|
131 |
_logger.debug("_geterror")
|
|
132 |
return self._error
|
|
133 |
|
|
134 |
error = property(_geterror, __seterror)
|
|
135 |
|
|
136 |
class ProjectCheckoutResult(Result):
|
|
137 |
""" Project checkout output parser.
|
|
138 |
Sets project to the created project or None if failed.
|
|
139 |
"""
|
|
140 |
def __init__(self, session, project):
|
|
141 |
Result.__init__(self, session)
|
|
142 |
self.__project = project
|
|
143 |
self.__result_project = None
|
|
144 |
|
|
145 |
def _setoutput(self, output):
|
|
146 |
""" Parsing the output of the checkout command. """
|
|
147 |
self._output = output
|
|
148 |
for line in output.splitlines():
|
|
149 |
mresult = re.match(r"Saved work area options for project: '(.+)'", line, re.I)
|
|
150 |
#(?P<name>.+)-(?P<version>.+?)(:(?P<type>\S+):(?P<instance>\S+))?
|
|
151 |
if mresult != None:
|
|
152 |
#self.__project.name + "-" + mo.groupdict()['version'] + ":" + self.__project.type + ":" + self.__project.instance
|
|
153 |
self.__result_project = self._session.create(mresult.group(1))
|
|
154 |
_logger.debug("ProjectCheckoutResult: project: '%s'" % self.__result_project)
|
|
155 |
return
|
|
156 |
|
|
157 |
def __get_result_project(self):
|
|
158 |
""" return the checked out project. """
|
|
159 |
return self.__result_project
|
|
160 |
|
|
161 |
project = property(__get_result_project)
|
|
162 |
|
|
163 |
|
|
164 |
class ProjectPurposeResult(Result):
|
|
165 |
""" Parses purpose query output. """
|
|
166 |
def __init__(self, session):
|
|
167 |
Result.__init__(self, session)
|
|
168 |
|
|
169 |
def _setoutput(self, output):
|
|
170 |
"""set output """
|
|
171 |
self._output = {}
|
|
172 |
for line in output.splitlines():
|
|
173 |
mresult = re.match(r"(?P<purpose>.+?)\s+(?P<member_status>\w+)\s+(?P<status>\w+)$", line)
|
|
174 |
if mresult != None:
|
|
175 |
data = mresult.groupdict()
|
|
176 |
if re.match(r'^\s+Purpose\s+Member$', data['purpose'], re.I) == None:
|
|
177 |
self._output[data['purpose'].strip()] = {'member_status' : data['member_status'].strip(),
|
|
178 |
'status' : data['status'].strip()}
|
|
179 |
|
|
180 |
class ConflictsResult(Result):
|
|
181 |
""" Parses purpose query output. """
|
|
182 |
def __init__(self, session):
|
|
183 |
Result.__init__(self, session)
|
|
184 |
|
|
185 |
def _setoutput(self, output):
|
|
186 |
""" set output """
|
|
187 |
self._output = {}
|
|
188 |
project = None
|
|
189 |
|
|
190 |
for line in output.splitlines():
|
|
191 |
mresult = re.match(r"Project:\s*(.+)\s*$", line)
|
|
192 |
if mresult != None:
|
|
193 |
project = self._session.create(mresult.group(1))
|
|
194 |
self._output[project] = []
|
|
195 |
mresult = re.match(r"^(.*)\s+(\w+#\d+)\s+(.+)$", line)
|
|
196 |
if mresult != None and project != None:
|
|
197 |
self._output[project].append({'object': self._session.create(mresult.group(1)),
|
|
198 |
'task': self._session.create("Task %s" % mresult.group(2)),
|
|
199 |
'comment': mresult.group(3)})
|
|
200 |
mresult = re.match(r"^(\w+#\d+)\s+(.+)$", line)
|
|
201 |
if mresult != None and project != None:
|
|
202 |
self._output[project].append({'task': self._session.create("Task %s" % mresult.group(1)),
|
|
203 |
'comment': mresult.group(2)})
|
|
204 |
|
|
205 |
|
|
206 |
class FinduseResult(Result):
|
|
207 |
""" Parses finduse query output. """
|
|
208 |
def __init__(self, ccm_object):
|
|
209 |
Result.__init__(self, ccm_object.session)
|
|
210 |
self.__object = ccm_object
|
|
211 |
|
|
212 |
def _setoutput(self, output):
|
|
213 |
"""set output"""
|
|
214 |
self._output = []
|
|
215 |
for line in output.splitlines():
|
|
216 |
_logger.debug("FinduseResult: ---->%s<----" % line)
|
|
217 |
_logger.debug("FinduseResult: ---->%s-%s<----" % (self.__object.name, self.__object.version))
|
|
218 |
|
|
219 |
# MCNaviscroll\NaviAnim-username7@MCNaviscroll-username6
|
|
220 |
mresult = re.match(r"^\s*(?P<path>.+)[\\/]%s-%s@(?P<project>.+)" % (self.__object.name, self.__object.version), line, re.I)
|
|
221 |
if mresult != None:
|
|
222 |
data = mresult.groupdict()
|
|
223 |
_logger.debug("FinduseResult: %s" % data)
|
|
224 |
project = self._session.create(data['project'])
|
|
225 |
self._output.append({'path' : data['path'], 'project' : project})
|
|
226 |
|
|
227 |
|
|
228 |
class UpdateTemplateInformation(Result):
|
|
229 |
""" Parse update template information output. """
|
|
230 |
def __init__(self, session):
|
|
231 |
Result.__init__(self, session)
|
|
232 |
|
|
233 |
def _setoutput(self, output):
|
|
234 |
"""
|
|
235 |
Baseline Selection Mode: Latest Baseline Projects
|
|
236 |
Prep Allowed: No
|
|
237 |
Versions Matching: *abs.50*
|
|
238 |
Release Purposes:
|
|
239 |
Use by Default: Yes
|
|
240 |
Modifiable in Database: tr1s60
|
|
241 |
In Use For Release: Yes
|
|
242 |
Folder Templates and Folders:
|
|
243 |
- Template assigned or completed tasks for %owner for release %release
|
|
244 |
- Template all completed tasks for release %release
|
|
245 |
- Folder tr1s60#4844: All completed Xuikon/Xuikon_rel_X tasks
|
|
246 |
- Folder tr1s60#4930: All tasks for release AppBaseDo_50
|
|
247 |
"""
|
|
248 |
self._output = {}
|
|
249 |
for line in output.splitlines():
|
|
250 |
rmo = re.match(r"^\s*(.+):\s*(.*)\s*", line)
|
|
251 |
if rmo != None:
|
|
252 |
if rmo.group(1) == "Baseline Selection Mode":
|
|
253 |
self._output['baseline_selection_mode'] = rmo.group(2)
|
|
254 |
elif rmo.group(1) == "Prep Allowed":
|
|
255 |
self._output['prep_allowed'] = (rmo.group(2) != "No")
|
|
256 |
elif rmo.group(1) == "Versions Matching":
|
|
257 |
self._output['version_matching'] = rmo.group(2)
|
|
258 |
elif rmo.group(1) == "Release Purposes":
|
|
259 |
self._output['release_purpose'] = rmo.group(2)
|
|
260 |
elif rmo.group(1) == "Use by Default":
|
|
261 |
self._output['default'] = (rmo.group(2) != "No")
|
|
262 |
elif rmo.group(1) == "Modifiable in Database":
|
|
263 |
self._output['modifiable_in_database'] = rmo.group(2).strip()
|
|
264 |
elif rmo.group(1) == "In Use For Release":
|
|
265 |
self._output['in_use_for_release'] = (rmo.group(2) != "No")
|
|
266 |
|
|
267 |
|
|
268 |
class UpdatePropertiesRefreshResult(Result):
|
|
269 |
""" Parse update template refresh output. """
|
|
270 |
def __init__(self, session):
|
|
271 |
Result.__init__(self, session)
|
|
272 |
|
|
273 |
def _setoutput(self, output):
|
|
274 |
"""set output"""
|
|
275 |
self._output = {'added': [], 'removed': []}
|
|
276 |
match_added = re.compile(r"^Added the following tasks")
|
|
277 |
match_removed = re.compile(r"^Removed the following tasks")
|
|
278 |
match_task_new = re.compile(r"^\s+(Task \S+#\d+)")
|
|
279 |
section = None
|
|
280 |
|
|
281 |
for line in output.splitlines():
|
|
282 |
res = match_added.match(line)
|
|
283 |
if res != None:
|
|
284 |
section = 'added'
|
|
285 |
continue
|
|
286 |
res = match_removed.match(line)
|
|
287 |
if res != None:
|
|
288 |
section = 'removed'
|
|
289 |
continue
|
|
290 |
if section is not None:
|
|
291 |
res = match_task_new.match(line)
|
|
292 |
if res != None:
|
|
293 |
self._output[section].append(self._session.create(res.group(1)))
|
|
294 |
continue
|
|
295 |
|
|
296 |
|
|
297 |
class UpdateResultSimple(Result):
|
|
298 |
""" Parse update output. """
|
|
299 |
def __init__(self, session):
|
|
300 |
Result.__init__(self, session)
|
|
301 |
self._success = True
|
|
302 |
|
|
303 |
def _setoutput(self, output):
|
|
304 |
"""set output"""
|
|
305 |
self._output = output
|
|
306 |
match_failed = re.compile(r"(Update failed)")
|
|
307 |
for line in output.splitlines():
|
|
308 |
res = match_failed.match(line)
|
|
309 |
if res != None:
|
|
310 |
self._success = False
|
|
311 |
|
|
312 |
@property
|
|
313 |
def successful(self):
|
|
314 |
"""successful"""
|
|
315 |
return self._success
|
|
316 |
|
|
317 |
class UpdateResult(UpdateResultSimple):
|
|
318 |
""" Parse update output. """
|
|
319 |
def __init__(self, session):
|
|
320 |
UpdateResultSimple.__init__(self, session)
|
|
321 |
|
|
322 |
def _setoutput(self, output):
|
|
323 |
"""set output"""
|
|
324 |
self._output = {"tasks":[], "modifications": [], "errors": [], "warnings": []}
|
|
325 |
match_object_update = re.compile(r"^\s+'(.*)'\s+replaces\s+'(.*)'\s+under\s+'(.*)'\.")
|
|
326 |
match_object_new = re.compile(r"^\s+(?:Subproject\s+)?'(.*)'\s+is now bound under\s+'(.*)'\.")
|
|
327 |
match_task_new = re.compile(r"^\s+(Task \S+#\d+)")
|
|
328 |
match_no_candidate = re.compile(r"^\s+(.+) in project (.+) had no candidates")
|
|
329 |
match_update_failure = re.compile(r"^\s+Failed to use selected object\s+(.+)\s+under directory\s+(.+)\s+in project\s+(.+)\s+:\s+(.+)")
|
|
330 |
match_warning = re.compile(r"^Warning:(.*)")
|
|
331 |
match_failed = re.compile(r"(Update failed)")
|
|
332 |
|
|
333 |
# TODO: cleanup the parsing to do that in a more efficient way.
|
|
334 |
for line in output.splitlines():
|
|
335 |
_logger.info(line)
|
|
336 |
res = match_object_update.match(line)
|
|
337 |
if res != None:
|
|
338 |
self._output['modifications'].append({ "new": self._session.create(res.group(1)),
|
|
339 |
"old": self._session.create(res.group(2)),
|
|
340 |
"project": self._session.create(res.group(3))})
|
|
341 |
continue
|
|
342 |
res = match_object_new.match(line)
|
|
343 |
if res != None:
|
|
344 |
self._output['modifications'].append({ "new": self._session.create(res.group(1)),
|
|
345 |
"old": None,
|
|
346 |
"project": self._session.create(res.group(2))})
|
|
347 |
continue
|
|
348 |
res = match_task_new.match(line)
|
|
349 |
if res != None:
|
|
350 |
self._output['tasks'].append(self._session.create(res.group(1)))
|
|
351 |
continue
|
|
352 |
res = match_no_candidate.match(line)
|
|
353 |
if res != None:
|
|
354 |
self._output['errors'].append({'family': res.group(1),
|
|
355 |
'project': self._session.create(res.group(2)),
|
|
356 |
'comment': "had no candidates",
|
|
357 |
'line': line,})
|
|
358 |
continue
|
|
359 |
res = match_update_failure.match(line)
|
|
360 |
if res != None:
|
|
361 |
self._output['errors'].append({'family': res.group(1),
|
|
362 |
'dir': self._session.create(res.group(2)),
|
|
363 |
'project': self._session.create(res.group(3)),
|
|
364 |
'comment': res.group(4),
|
|
365 |
'line': line,
|
|
366 |
})
|
|
367 |
continue
|
|
368 |
res = match_warning.match(line)
|
|
369 |
if res != None:
|
|
370 |
self._output['warnings'].append({'family': None,
|
|
371 |
'project': None,
|
|
372 |
'comment': res.group(1),
|
|
373 |
'line': line,
|
|
374 |
})
|
|
375 |
continue
|
|
376 |
res = match_failed.match(line)
|
|
377 |
if res != None:
|
|
378 |
self._success = False
|
|
379 |
self._output['errors'].append({'Serious': res.group(1),
|
|
380 |
})
|
|
381 |
continue
|
|
382 |
|
|
383 |
|
|
384 |
|
|
385 |
class WorkAreaInfoResult(Result):
|
|
386 |
""" Parse work area info output. """
|
|
387 |
def __init__(self, session):
|
|
388 |
Result.__init__(self, session)
|
|
389 |
|
|
390 |
def _setoutput(self, output):
|
|
391 |
""" Returns a dict with the following fields:
|
|
392 |
* project: a ccm.Project instance
|
|
393 |
* maintain: a boolean
|
|
394 |
* copies: a boolean
|
|
395 |
* relative: a boolean
|
|
396 |
* time: a boolean
|
|
397 |
* translate: a boolean
|
|
398 |
* modify: a boolean
|
|
399 |
* path: a string representing the project wa path
|
|
400 |
"""
|
|
401 |
self._output = None
|
|
402 |
for line in output.splitlines():
|
|
403 |
mresult = re.match(r"(?P<project>.*)\s+(?P<maintain>TRUE|FALSE)\s+(?P<copies>TRUE|FALSE)\s+(?P<relative>TRUE|FALSE)\s+(?P<time>TRUE|FALSE)\s+(?P<translate>TRUE|FALSE)\s+(?P<modify>TRUE|FALSE)\s+'(?P<path>.*)'", line)
|
|
404 |
if mresult != None:
|
|
405 |
data = mresult.groupdict()
|
|
406 |
self._output = {'project': self._session.create(data['project']),
|
|
407 |
'maintain' : data['maintain'] == "TRUE",
|
|
408 |
'copies' : data['copies'] == "TRUE",
|
|
409 |
'relative' : data['relative'] == "TRUE",
|
|
410 |
'time' : data['time'] == "TRUE",
|
|
411 |
'translate' : data['translate'] == "TRUE",
|
|
412 |
'modify' : data['modify'] == "TRUE",
|
|
413 |
'path' : data['path']
|
|
414 |
}
|
|
415 |
return
|
|
416 |
|
|
417 |
|
|
418 |
class CreateNewTaskResult(Result):
|
|
419 |
""" A result class to parse newly created tasks """
|
|
420 |
def __init__(self, session):
|
|
421 |
Result.__init__(self, session)
|
|
422 |
|
|
423 |
def _setoutput(self, output):
|
|
424 |
"""set output"""
|
|
425 |
self._output = None
|
|
426 |
for line in output.splitlines():
|
|
427 |
mresult = re.match(r"Task\s+(?P<task>\S+\#\d+)\s+created\.", line)
|
|
428 |
if mresult != None:
|
|
429 |
self._output = self._session.create("Task " + mresult.groupdict()['task'])
|
|
430 |
return
|
|
431 |
|
|
432 |
|
|
433 |
class AttributeNameListResult(Result):
|
|
434 |
""" Class that abstract ccm call result handling.
|
|
435 |
Subclass it to implement a new generic output parser.
|
|
436 |
"""
|
|
437 |
def __init__(self, session):
|
|
438 |
Result.__init__(self, session)
|
|
439 |
|
|
440 |
def _setoutput(self, obj):
|
|
441 |
"""set output"""
|
|
442 |
def _create(arg):
|
|
443 |
"""create"""
|
|
444 |
mresult = re.match(r"^\s*(?P<name>\w+)", arg.strip())
|
|
445 |
if mresult != None:
|
|
446 |
return mresult.groupdict()['name']
|
|
447 |
return None
|
|
448 |
self._output = [_create(line) for line in obj.strip().splitlines()]
|
|
449 |
|
|
450 |
|
|
451 |
class ObjectListResult(Result):
|
|
452 |
""" Parses an object list Synergy output. """
|
|
453 |
def __init__(self, session):
|
|
454 |
Result.__init__(self, session)
|
|
455 |
|
|
456 |
def _setoutput(self, obj):
|
|
457 |
"""set output"""
|
|
458 |
self._output = []
|
|
459 |
if re.match(r"^None|^No tasks|^Warning", obj, re.M) != None:
|
|
460 |
return
|
|
461 |
def _create(arg):
|
|
462 |
"""create"""
|
|
463 |
arg = arg.strip()
|
|
464 |
if arg != "":
|
|
465 |
return self._session.create(arg)
|
|
466 |
return None
|
|
467 |
result = [_create(line) for line in obj.strip().splitlines()]
|
|
468 |
for result_line in result:
|
|
469 |
if result_line != None:
|
|
470 |
self._output.append(result_line)
|
|
471 |
|
|
472 |
class DataMapperListResult(Result):
|
|
473 |
""" Parses an object list Synergy output. """
|
|
474 |
|
|
475 |
dataconv = {'ccmobject': lambda x, y: x.create(y),
|
|
476 |
'string': lambda x, y: y,
|
|
477 |
'int': lambda x, y: int(y),
|
|
478 |
'boolean': lambda x, y: (y.lower() == "true")}
|
|
479 |
|
|
480 |
def __init__(self, session, separator, keywords, datamodel):
|
|
481 |
self._separator = separator
|
|
482 |
self._keywords = keywords
|
|
483 |
self._datamodel = datamodel
|
|
484 |
Result.__init__(self, session)
|
|
485 |
|
|
486 |
def format(self):
|
|
487 |
"""format keywords"""
|
|
488 |
formatted_keywords = ["%s%s%s%%%s" % (self._separator, x, self._separator, x) for x in self._keywords]
|
|
489 |
return "".join(formatted_keywords) + self._separator
|
|
490 |
|
|
491 |
def regex(self):
|
|
492 |
"""regular expression"""
|
|
493 |
regex_keywords = [r'%s%s%s(.*?)' % (self._separator, x, self._separator) for x in self._keywords]
|
|
494 |
regex = r''.join(regex_keywords)
|
|
495 |
regex = r"%s%s\s*\n" % (regex, self._separator)
|
|
496 |
return re.compile(regex, re.MULTILINE | re.I | re.DOTALL | re.VERBOSE | re.U)
|
|
497 |
|
|
498 |
def _setoutput(self, obj):
|
|
499 |
"""set output"""
|
|
500 |
self._output = []
|
|
501 |
regex = self.regex()
|
|
502 |
_logger.debug("Regex %s" % (regex.pattern))
|
|
503 |
for match in regex.finditer(obj):
|
|
504 |
_logger.debug("Found: %s" % (match))
|
|
505 |
if match != None:
|
|
506 |
output_line = {}
|
|
507 |
for i in range(len(self._datamodel)):
|
|
508 |
_logger.debug("Found %d: %s" % (i, match.group(i + 1)))
|
|
509 |
model = self._datamodel[i]
|
|
510 |
output_line[self._keywords[i]] = self.dataconv[model](self._session, match.group(i + 1))
|
|
511 |
i += 1
|
|
512 |
self._output.append(output_line)
|
|
513 |
|
|
514 |
|
|
515 |
class FolderCopyResult(Result):
|
|
516 |
""" Parses a folder copy result """
|
|
517 |
def __init__(self, session):
|
|
518 |
Result.__init__(self, session)
|
|
519 |
|
|
520 |
def _setoutput(self, output):
|
|
521 |
"""set output"""
|
|
522 |
self._output = None
|
|
523 |
for line in output.splitlines():
|
|
524 |
match_output = re.match(r"appended to", line)
|
|
525 |
if match_output != None:
|
|
526 |
self._output = self._session.create(line)
|
|
527 |
return
|
|
528 |
|
|
529 |
CHECKOUT_LOG_RULES = [[r'^Derive failed for', logging.ERROR],
|
|
530 |
[r'^Serious:', logging.ERROR],
|
|
531 |
[r'^Warning: .* failed.', logging.ERROR],
|
|
532 |
[r'^Invalid work area', logging.ERROR],
|
|
533 |
[r'^WARNING:', logging.WARNING],
|
|
534 |
[r'^Warning:', logging.WARNING],]
|
|
535 |
|
|
536 |
|
|
537 |
UPDATE_LOG_RULES = [[r'^Update failed.', logging.ERROR],
|
|
538 |
[r'^Serious:', logging.ERROR],
|
|
539 |
[r'^\s+Failed to', logging.ERROR],
|
|
540 |
[r'^\d+ failures to', logging.ERROR],
|
|
541 |
[r"^Warning: This work area '.+' cannot be reused", logging.ERROR],
|
|
542 |
[r'^Rebind of .* failed', logging.ERROR],
|
|
543 |
[r'^Warning: .* failed.', logging.ERROR],
|
|
544 |
[r'^Skipping \'.*\'\. You do not have permission to modify this project.', logging.ERROR],
|
|
545 |
[r'^Work area conflict exists for file', logging.ERROR],
|
|
546 |
[r'^Warning: No candidates found for directory entry', logging.ERROR],
|
|
547 |
[r'^WARNING:', logging.WARNING],
|
|
548 |
[r'^Warning:', logging.WARNING],]
|
|
549 |
|
|
550 |
CONFLICTS_LOG_RULES = [[r'^\w+#\d+\s+Implicit', logging.WARNING],
|
|
551 |
[r'^(.*)\s+(\w+#\d+)\s+(.+)', logging.WARNING],
|
|
552 |
[r'.*Explicitly specified but not included', logging.WARNING],]
|
|
553 |
|
|
554 |
SYNC_LOG_RULES = [[r'^\s+0\s+Conflict\(s\) for project', logging.INFO],
|
|
555 |
[r'^\s+\d+\s+Conflict\(s\) for project', logging.ERROR],
|
|
556 |
[r'^Project \'.*\' does not maintain a workarea.', logging.ERROR],
|
|
557 |
[r'^Work area conflict exists for file', logging.ERROR],
|
|
558 |
[r'^Warning: Conflicts detected during synchronization. Check your logs.', logging.ERROR],
|
|
559 |
[r'^Warning:', logging.WARNING],]
|
|
560 |
|
|
561 |
def log_result(result, rules, logger=None):
|
|
562 |
""" Rules it a list of tuple defining a regular expression and an log level. """
|
|
563 |
if logger is None:
|
|
564 |
logger = _logger
|
|
565 |
crules = []
|
|
566 |
if rules is not None:
|
|
567 |
for rule in rules:
|
|
568 |
crules.append([re.compile(rule[0]), rule[1]])
|
|
569 |
|
|
570 |
for line in str(result).splitlines():
|
|
571 |
for rule in crules:
|
|
572 |
if rule[0].match(line) != None:
|
|
573 |
logger.log(rule[1], line)
|
|
574 |
break
|
|
575 |
else:
|
|
576 |
logger.info(line)
|
|
577 |
|
|
578 |
class AbstractSession(object):
|
|
579 |
"""An abstract Synergy session.
|
|
580 |
|
|
581 |
Must be overridden to implement either a single session or
|
|
582 |
multiple session handling.
|
|
583 |
"""
|
|
584 |
def __init__(self, username, engine, dbpath, ccm_addr):
|
|
585 |
self.username = username
|
|
586 |
self.engine = engine
|
|
587 |
self.dbpath = dbpath
|
|
588 |
self._session_addr = ccm_addr
|
|
589 |
# internal object list
|
|
590 |
self.__ccm_objects = {}
|
|
591 |
|
|
592 |
def addr(self):
|
|
593 |
""" Returns the Synergy session id."""
|
|
594 |
return self._session_addr
|
|
595 |
|
|
596 |
def database(self):
|
|
597 |
"""database"""
|
|
598 |
_logger.debug("AbstractSession: database")
|
|
599 |
self.__find_dbpath()
|
|
600 |
_logger.debug("AbstractSession: database: %s" % self.dbpath)
|
|
601 |
return os.path.basename(self.dbpath)
|
|
602 |
|
|
603 |
def __find_dbpath(self):
|
|
604 |
""" retrieve the database path from current session status. """
|
|
605 |
_logger.debug("AbstractSession: __find_dbpath")
|
|
606 |
if (self.dbpath != None):
|
|
607 |
return
|
|
608 |
result = self.execute("status")
|
|
609 |
for match in re.finditer(r'(?:(?:Graphical)|(?:Command)) Interface\s+@\s+(?P<ccmaddr>\w+:\d+(?:\:\d+\.\d+\.\d+\.\d+)+)(?P<current_session>\s+\(current\s+session\))?\s*\nDatabase:\s*(?P<dbpath>\S+)', result.output, re.M | re.I):
|
|
610 |
dictionary = match.groupdict()
|
|
611 |
if (dictionary['current_session'] != None):
|
|
612 |
_logger.debug("AbstractSession: __find_dbpath: Found dbpath: %s" % dictionary['dbpath'])
|
|
613 |
self.dbpath = dictionary['dbpath']
|
|
614 |
assert self.dbpath != None
|
|
615 |
|
|
616 |
def execute(self, _, result=None):
|
|
617 |
""" Abstract function that should implement the execution of ccm command
|
|
618 |
line call.
|
|
619 |
"""
|
|
620 |
return result
|
|
621 |
|
|
622 |
def create(self, fpn):
|
|
623 |
""" Object factory, this is the toolkit entry point to create objects from
|
|
624 |
four part names. Objects are stored into a dictionary, so you have
|
|
625 |
only one wrapper per synergy object.
|
|
626 |
"""
|
|
627 |
result = re.search(r"^(?P<project>.+)-(?P<version>[^:]+?)$", fpn)
|
|
628 |
if result != None:
|
|
629 |
matches = result.groupdict()
|
|
630 |
fpn = "%s-%s:project:%s#1" % (matches['project'], matches['version'], self.database())
|
|
631 |
_logger.debug("session.create('%s')" % fpn)
|
|
632 |
ofpn = FourPartName(fpn)
|
|
633 |
if not self.__ccm_objects.has_key(str(fpn)):
|
|
634 |
obj = None
|
|
635 |
if ofpn.type == 'project':
|
|
636 |
obj = Project(self, fpn)
|
|
637 |
elif ofpn.type == 'dir':
|
|
638 |
obj = Dir(self, fpn)
|
|
639 |
elif ofpn.type == 'task':
|
|
640 |
obj = Task(self, fpn)
|
|
641 |
elif ofpn.type == 'folder':
|
|
642 |
obj = Folder(self, fpn)
|
|
643 |
elif ofpn.type == 'releasedef':
|
|
644 |
obj = Releasedef(self, fpn)
|
|
645 |
else:
|
|
646 |
obj = File(self, fpn)
|
|
647 |
self.__ccm_objects[str(fpn)] = obj
|
|
648 |
return self.__ccm_objects[str(fpn)]
|
|
649 |
|
|
650 |
def get_workarea_info(self, dir_):
|
|
651 |
""" Return a dictionary containing workarea info from directory dir.
|
|
652 |
"""
|
|
653 |
if (not os.path.exists(dir_)):
|
|
654 |
raise CCMException("Error retrieving work_area info for the directory '%s' (doesn't exists)" % dir_)
|
|
655 |
path = os.path.abspath(os.path.curdir)
|
|
656 |
path_ccmwaid = os.path.join(dir_,"_ccmwaid.inf")
|
|
657 |
if(not os.path.exists(path_ccmwaid)):
|
|
658 |
raise CCMException("No work area in '%s'" % dir_)
|
|
659 |
os.chdir(dir_)
|
|
660 |
result = self.execute("wa -show", WorkAreaInfoResult(self))
|
|
661 |
os.chdir(path)
|
|
662 |
if result.output == None:
|
|
663 |
raise CCMException("Error retrieving work_area info for the directory '%s'" % dir_)
|
|
664 |
return result.output
|
|
665 |
|
|
666 |
def _get_role(self):
|
|
667 |
"""get CCM role"""
|
|
668 |
result = self.execute("set role")
|
|
669 |
return result.output.strip()
|
|
670 |
|
|
671 |
def _set_role_internal(self, role):
|
|
672 |
""" method to be override by child class else property accession is not working properly. """
|
|
673 |
if role == None or len(role) == 0:
|
|
674 |
raise CCMException("You must provide a role.")
|
|
675 |
result = self.execute("set role %s" % role)
|
|
676 |
if re.match(r'^Warning:', result.output, re.M) != None:
|
|
677 |
raise CCMException("Error switching to role %s: %s" %(role, result.output.strip()))
|
|
678 |
|
|
679 |
def _set_role(self, role):
|
|
680 |
"""set CCM role"""
|
|
681 |
self._set_role_internal(role)
|
|
682 |
|
|
683 |
role = property(fget=_get_role, fset=_set_role)
|
|
684 |
|
|
685 |
def _get_home(self):
|
|
686 |
"""get home"""
|
|
687 |
result = self.execute("set Home")
|
|
688 |
return result.output.strip()
|
|
689 |
|
|
690 |
def _set_home(self, home):
|
|
691 |
"""set home"""
|
|
692 |
if len(home) == 0 or home == None:
|
|
693 |
raise CCMException("You must provide a home.")
|
|
694 |
result = self.execute("set Home %s" % home)
|
|
695 |
if re.match(r'^Warning:', result.output, re.M) != None:
|
|
696 |
raise CCMException("Error switching to Home %s: %s" %(home, result.output.strip()))
|
|
697 |
|
|
698 |
home = property(_get_home, _set_home)
|
|
699 |
|
|
700 |
def close(self):
|
|
701 |
"""close does nothing"""
|
|
702 |
pass
|
|
703 |
|
|
704 |
def __str__(self):
|
|
705 |
self.__find_dbpath()
|
|
706 |
return self._session_addr + ':' + self.dbpath
|
|
707 |
|
|
708 |
def __repr__(self):
|
|
709 |
return self.__str__()
|
|
710 |
|
|
711 |
def __del__(self):
|
|
712 |
self.close()
|
|
713 |
|
|
714 |
def purposes(self, role=None):
|
|
715 |
""" Returns available purposes. """
|
|
716 |
args = ""
|
|
717 |
if role != None:
|
|
718 |
args = "-role \"%s\"" % role
|
|
719 |
result = self.execute("project_purpose -show %s" % args, ProjectPurposeResult(self))
|
|
720 |
return result.output
|
|
721 |
|
|
722 |
class Session(AbstractSession):
|
|
723 |
"""A Synergy session.
|
|
724 |
"""
|
|
725 |
def __init__(self, username, engine, dbpath, ccm_addr, close_on_exit=True):
|
|
726 |
AbstractSession.__init__(self, username, engine, dbpath, ccm_addr)
|
|
727 |
self._execute_lock = threading.Lock()
|
|
728 |
self.close_on_exit = close_on_exit
|
|
729 |
|
|
730 |
@staticmethod
|
|
731 |
def start(username, password, engine, dbpath, timeout=300):
|
|
732 |
"""start the session"""
|
|
733 |
if username == None:
|
|
734 |
raise CCMException('username is not valid')
|
|
735 |
if password == None:
|
|
736 |
raise CCMException('password is not valid')
|
|
737 |
if CCM_BIN == None:
|
|
738 |
raise CCMException("Could not find CM/Synergy executable in the path.")
|
|
739 |
command = "%s start -m -q -nogui -n %s -pw %s -h %s -d %s" % \
|
|
740 |
(CCM_BIN, username, password, engine, dbpath)
|
|
741 |
_logger.debug('Starting new session:' + command.replace(password, "***"))
|
|
742 |
(result, status) = _execute(command, timeout=timeout)
|
|
743 |
if status != 0:
|
|
744 |
raise Exception("Error creating a session: result:\n%s\nCommand: %s" % (result, command.replace(password, "***")))
|
|
745 |
session_addr = result.strip()
|
|
746 |
_logger.debug(session_addr)
|
|
747 |
if not re.match(r'[a-zA-Z0-9_\-.]+:\d+:\d+\.\d+\.\d+\.\d+(:\d+\.\d+\.\d+\.\d+)?', session_addr):
|
|
748 |
raise Exception("Error creating a session: result:\n%s" % result)
|
|
749 |
return Session(username, engine, dbpath, session_addr)
|
|
750 |
|
|
751 |
def execute(self, cmdline, result=None):
|
|
752 |
""" Executes a Synergy CLI operation. """
|
|
753 |
if self._session_addr == None:
|
|
754 |
raise CCMException("No Synergy session running")
|
|
755 |
if CCM_BIN == None:
|
|
756 |
raise CCMException("Could not find CM/Synergy executable in the path.")
|
|
757 |
self._execute_lock.acquire()
|
|
758 |
output = ""
|
|
759 |
error = ""
|
|
760 |
try:
|
|
761 |
if result == None:
|
|
762 |
result = Result(self)
|
|
763 |
if os.sep == '\\':
|
|
764 |
command = "set CCM_ADDR=" + self._session_addr + " && " + CCM_BIN + " %s" % cmdline
|
|
765 |
else:
|
|
766 |
command = "export CCM_ADDR=" + self._session_addr + " && " + CCM_BIN + " %s" % cmdline
|
|
767 |
_logger.debug('Execute > ' + command)
|
|
768 |
|
|
769 |
if hasattr(result, 'error'):
|
|
770 |
process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
|
771 |
output = process.stdout.read()
|
|
772 |
error = process.stderr.read()
|
|
773 |
result.status = process.returncode
|
|
774 |
else:
|
|
775 |
process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
|
776 |
output = process.stdout.read()
|
|
777 |
result.status = process.returncode
|
|
778 |
finally:
|
|
779 |
self._execute_lock.release()
|
|
780 |
result.output = output.strip()
|
|
781 |
if hasattr(result, 'error'):
|
|
782 |
result.error = error.strip()
|
|
783 |
return result
|
|
784 |
|
|
785 |
def close(self):
|
|
786 |
""" Closes this Synergy session if it was not previously running anyway. """
|
|
787 |
_logger.debug("Closing session %s" % self._session_addr)
|
|
788 |
if self._session_addr != None and self.close_on_exit:
|
|
789 |
_logger.debug("Closing session %s" % self._session_addr)
|
|
790 |
self._execute_lock.acquire()
|
|
791 |
if os.sep == '\\':
|
|
792 |
command = "set CCM_ADDR=" + self._session_addr + " && " + CCM_BIN + " stop"
|
|
793 |
else:
|
|
794 |
command = "export CCM_ADDR=" + self._session_addr + " && " + CCM_BIN + " stop"
|
|
795 |
_logger.debug('Execute > ' + command)
|
|
796 |
pipe = os.popen(command)
|
|
797 |
pipe.close()
|
|
798 |
self._session_addr = None
|
|
799 |
self._execute_lock.release()
|
|
800 |
elif self._session_addr != None and not self.close_on_exit:
|
|
801 |
_logger.debug("Keeping session %s alive." % self._session_addr)
|
|
802 |
|
|
803 |
|
|
804 |
class SessionPool(AbstractSession):
|
|
805 |
""" Session that transparently handled several subsession, to easily enable
|
|
806 |
multithreaded application.
|
|
807 |
"""
|
|
808 |
def __init__(self, username, password, engine, dbpath, database=None, size=4, opener=None):
|
|
809 |
AbstractSession.__init__(self, username, engine, dbpath, None)
|
|
810 |
self._opener = opener
|
|
811 |
if self._opener is None:
|
|
812 |
self._opener = open_session
|
|
813 |
self._free_sessions = []
|
|
814 |
self._used_sessions = []
|
|
815 |
self._thread_sessions = {}
|
|
816 |
self._pool_lock = threading.Condition()
|
|
817 |
self._lock_pool = False
|
|
818 |
self.__password = password
|
|
819 |
self.__database = database
|
|
820 |
self.size = size
|
|
821 |
|
|
822 |
def _set_size(self, size):
|
|
823 |
""" Set the pool size """
|
|
824 |
self._pool_lock.acquire()
|
|
825 |
poolsize = len(self._free_sessions) + len(self._used_sessions)
|
|
826 |
if poolsize > size:
|
|
827 |
to_be_remove = poolsize - size
|
|
828 |
self._lock_pool = True
|
|
829 |
while len(self._free_sessions) < to_be_remove:
|
|
830 |
self._pool_lock.wait()
|
|
831 |
for _ in range(to_be_remove):
|
|
832 |
self._free_sessions.pop().close()
|
|
833 |
self._lock_pool = False
|
|
834 |
else:
|
|
835 |
for _ in range(size - poolsize):
|
|
836 |
self._free_sessions.append(self._opener(self.username, self.__password, self.engine, self.dbpath, self.__database, False))
|
|
837 |
self._pool_lock.release()
|
|
838 |
|
|
839 |
def _get_size(self):
|
|
840 |
"""get pool size"""
|
|
841 |
self._pool_lock.acquire()
|
|
842 |
poolsize = len(self._free_sessions) + len(self._used_sessions)
|
|
843 |
self._pool_lock.release()
|
|
844 |
return poolsize
|
|
845 |
|
|
846 |
size = property (_get_size, _set_size)
|
|
847 |
|
|
848 |
def execute(self, cmdline, result=None):
|
|
849 |
""" Executing a ccm command on a free session. """
|
|
850 |
_logger.debug("SessionPool:execute: %s %s" % (cmdline, type(result)))
|
|
851 |
|
|
852 |
# waiting for a free session
|
|
853 |
self._pool_lock.acquire()
|
|
854 |
|
|
855 |
# check for recursion, in that case reallocate the same session,
|
|
856 |
if threading.currentThread() in self._thread_sessions:
|
|
857 |
_logger.debug("Same thread, reusing allocation session.")
|
|
858 |
# release the pool and reuse associated session
|
|
859 |
self._pool_lock.release()
|
|
860 |
return self._thread_sessions[threading.currentThread()].execute(cmdline, result)
|
|
861 |
|
|
862 |
while len(self._free_sessions)==0 or self._lock_pool:
|
|
863 |
self._pool_lock.wait()
|
|
864 |
session = self._free_sessions.pop(0)
|
|
865 |
self._used_sessions.append(session)
|
|
866 |
self._thread_sessions[threading.currentThread()] = session
|
|
867 |
self._pool_lock.release()
|
|
868 |
|
|
869 |
# running command
|
|
870 |
try:
|
|
871 |
result = session.execute(cmdline, result)
|
|
872 |
finally:
|
|
873 |
# we can now release the session - anyway
|
|
874 |
self._pool_lock.acquire()
|
|
875 |
self._thread_sessions.pop(threading.currentThread())
|
|
876 |
self._used_sessions.remove(session)
|
|
877 |
self._free_sessions.append(session)
|
|
878 |
self._pool_lock.notifyAll()
|
|
879 |
self._pool_lock.release()
|
|
880 |
return result
|
|
881 |
|
|
882 |
def close(self):
|
|
883 |
""" Closing all subsessions. """
|
|
884 |
_logger.debug("Closing session pool sub-sessions")
|
|
885 |
self._lock_pool = True
|
|
886 |
self._pool_lock.acquire()
|
|
887 |
while len(self._used_sessions) > 0:
|
|
888 |
_logger.debug("Waiting to free used sessions.")
|
|
889 |
_logger.debug("Waiting to free used sessions. %s %s" % (len(self._used_sessions), len(self._free_sessions)))
|
|
890 |
_logger.debug(self._used_sessions)
|
|
891 |
_logger.debug(self._free_sessions)
|
|
892 |
self._pool_lock.wait()
|
|
893 |
_logger.debug("Closing all free session from the pool.")
|
|
894 |
while len(self._free_sessions) > 0:
|
|
895 |
self._free_sessions.pop().close()
|
|
896 |
self._lock_pool = False
|
|
897 |
self._pool_lock.notifyAll()
|
|
898 |
self._pool_lock.release()
|
|
899 |
|
|
900 |
def _set_role_internal(self, role):
|
|
901 |
""" Set role on all subsessions. """
|
|
902 |
self._lock_pool = True
|
|
903 |
self._pool_lock.acquire()
|
|
904 |
while len(self._used_sessions)!=0:
|
|
905 |
self._pool_lock.wait()
|
|
906 |
|
|
907 |
try:
|
|
908 |
for session in self._free_sessions:
|
|
909 |
session.role = session._set_role(role)
|
|
910 |
finally:
|
|
911 |
self._lock_pool = False
|
|
912 |
self._pool_lock.notifyAll()
|
|
913 |
self._pool_lock.release()
|
|
914 |
|
|
915 |
|
|
916 |
class Query(object):
|
|
917 |
""" This object wrap a synergy query, it takes a query as input as well as the
|
|
918 |
attribute you want as output, and get them translated using the model configuration.
|
|
919 |
e.g
|
|
920 |
Query(session, "type='task' and release='test/next'", ['objectname', 'task_synopsis'], ['ccmobject', 'string'])
|
|
921 |
|
|
922 |
This will return a list of hash: [{'objectname': Task(xxx), 'task_synopsis': 'xxx'}, ...]
|
|
923 |
"""
|
|
924 |
|
|
925 |
def __init__(self, session, query, keywords, model, cmd="query"):
|
|
926 |
""" Initialize a Synergy query."""
|
|
927 |
self._session = session
|
|
928 |
self._query = query
|
|
929 |
self._keywords = keywords
|
|
930 |
self._model = model
|
|
931 |
self._cmd = cmd
|
|
932 |
|
|
933 |
def execute(self):
|
|
934 |
""" Executing the query on the database. """
|
|
935 |
mapper = DataMapperListResult(self._session, '@@@', self._keywords, self._model)
|
|
936 |
query = "%s %s -u -f \"%s\"" % (self._cmd, self._query, mapper.format())
|
|
937 |
return self._session.execute(query, mapper)
|
|
938 |
|
|
939 |
|
|
940 |
|
|
941 |
class InvalidFourPartNameException(CCMException):
|
|
942 |
""" Badly formed Synergy four-part name. """
|
|
943 |
def __init__(self, fpn = ""):
|
|
944 |
CCMException.__init__(self, fpn)
|
|
945 |
|
|
946 |
|
|
947 |
class FourPartName(object):
|
|
948 |
""" This class handle four part name parsing and validation.
|
|
949 |
"""
|
|
950 |
|
|
951 |
def __init__(self, ifpn):
|
|
952 |
""" Create a FourPartName object based on a ifpn string.
|
|
953 |
|
|
954 |
The string have to match the following patterns:
|
|
955 |
- name-version:type:instance
|
|
956 |
- name:version:releasedef:instance
|
|
957 |
- Task database#id
|
|
958 |
- Folder database#id
|
|
959 |
|
|
960 |
Anything else is considered as old release string format.
|
|
961 |
|
|
962 |
"""
|
|
963 |
_logger.debug("FourPartName: '%s'", ifpn)
|
|
964 |
fpn = FourPartName.convert(ifpn)
|
|
965 |
result = re.search(r"^(?P<name>.+)-(?P<version>.+?):(?P<type>\S+):(?P<instance>\S+)$", fpn)
|
|
966 |
if result == None:
|
|
967 |
result = re.search(r"^(?P<name>.+):(?P<version>.+?):(?P<type>releasedef):(?P<instance>\S+)$", fpn)
|
|
968 |
if result == None:
|
|
969 |
raise InvalidFourPartNameException(fpn)
|
|
970 |
# set all attributes
|
|
971 |
self._name = result.groupdict()['name']
|
|
972 |
self._version = result.groupdict()['version']
|
|
973 |
self._type = result.groupdict()['type']
|
|
974 |
self._instance = result.groupdict()['instance']
|
|
975 |
|
|
976 |
def __getname(self):
|
|
977 |
""" Returns the name of the object. """
|
|
978 |
return self._name
|
|
979 |
|
|
980 |
def __getversion(self):
|
|
981 |
""" Returns the version of the object. """
|
|
982 |
return self._version
|
|
983 |
|
|
984 |
def __gettype(self):
|
|
985 |
""" Returns the type of the object. """
|
|
986 |
return self._type
|
|
987 |
|
|
988 |
def __getinstance(self):
|
|
989 |
""" Returns the instance of the object. """
|
|
990 |
return self._instance
|
|
991 |
|
|
992 |
def __getobjectname(self):
|
|
993 |
""" Returns the objectname of the object. """
|
|
994 |
if (self.type == 'releasedef'):
|
|
995 |
return "%s:%s:%s:%s" % (self.name, self.version, self.type, self.instance)
|
|
996 |
return "%s-%s:%s:%s" % (self.name, self.version, self.type, self.instance)
|
|
997 |
|
|
998 |
def __str__(self):
|
|
999 |
""" Returns the string representation of the object. """
|
|
1000 |
return self.objectname
|
|
1001 |
|
|
1002 |
def __repr__(self):
|
|
1003 |
""" Returns the string representation of the python object. """
|
|
1004 |
if (self.type == 'releasedef'):
|
|
1005 |
return "<%s:%s:%s:%s>" % (self.name, self.version, self.type, self.instance)
|
|
1006 |
return "<%s-%s:%s:%s>" % (self.name, self.version, self.type, self.instance)
|
|
1007 |
|
|
1008 |
def is_same_family(self, ccmobject):
|
|
1009 |
""" Returns True if the ccmobject is part of the same family (=same name, type and instance) as self. """
|
|
1010 |
assert isinstance(ccmobject, FourPartName)
|
|
1011 |
return (self.name == ccmobject.name and self.type == ccmobject.type and self.instance == ccmobject.instance)
|
|
1012 |
|
|
1013 |
def __getfamily(self):
|
|
1014 |
"""get family"""
|
|
1015 |
return "%s:%s:%s" % (self.name, self.type, self.instance)
|
|
1016 |
|
|
1017 |
def __eq__(self, ccmobject):
|
|
1018 |
""" Returns True if object four parts name are identical. """
|
|
1019 |
if ccmobject == None:
|
|
1020 |
return False
|
|
1021 |
assert isinstance(ccmobject, FourPartName)
|
|
1022 |
return (self.name == ccmobject.name and self.version == ccmobject.version and self.type == ccmobject.type and self.instance == ccmobject.instance)
|
|
1023 |
|
|
1024 |
def __ne__(self, ccmobject):
|
|
1025 |
""" Returns True if object four parts name are different. """
|
|
1026 |
if ccmobject == None:
|
|
1027 |
return True
|
|
1028 |
assert isinstance(ccmobject, FourPartName)
|
|
1029 |
return (self.name != ccmobject.name or self.version != ccmobject.version or self.type != ccmobject.type or self.instance != ccmobject.instance)
|
|
1030 |
|
|
1031 |
@staticmethod
|
|
1032 |
def is_valid(fpn):
|
|
1033 |
""" Check if a given string represents a valid four part name.
|
|
1034 |
"""
|
|
1035 |
return (re.match(r"^(.+)-(.+?):(\S+):(\S+)|(.+):(.+?):releasedef:(\S+)$", fpn) != None)
|
|
1036 |
|
|
1037 |
@staticmethod
|
|
1038 |
def convert(fpn):
|
|
1039 |
""" Update a CCM output string to a valid four part name. This is due to the inconsistent
|
|
1040 |
output of CM/Synergy CLI.
|
|
1041 |
"""
|
|
1042 |
fpn = fpn.strip()
|
|
1043 |
if FourPartName.is_valid(fpn):
|
|
1044 |
return fpn
|
|
1045 |
result = re.search(r"^(?P<type>Task|Folder)\s+(?P<instance>\w+)#(?P<id>\d+)$", fpn)
|
|
1046 |
if result != None:
|
|
1047 |
matches = result.groupdict()
|
|
1048 |
if matches["type"] == "Task":
|
|
1049 |
return "task%s-1:task:%s" % (matches["id"], matches["instance"])
|
|
1050 |
elif matches["type"] == "Folder":
|
|
1051 |
return "%s-1:folder:%s" % (matches['id'], matches['instance'])
|
|
1052 |
else:
|
|
1053 |
result = re.search(r"^(?P<project>\S+)/(?P<version>\S+)$", fpn)
|
|
1054 |
if result != None:
|
|
1055 |
matches = result.groupdict()
|
|
1056 |
return "%s:%s:releasedef:1" % (matches['project'], matches['version'])
|
|
1057 |
else:
|
|
1058 |
# Check the name doesn't contains any of the following character: " :-"
|
|
1059 |
result = re.search(r"^[^\s^:^-]+$", fpn)
|
|
1060 |
if result != None:
|
|
1061 |
return "none:%s:releasedef:1" % (fpn)
|
|
1062 |
raise InvalidFourPartNameException(fpn)
|
|
1063 |
|
|
1064 |
name = property (__getname)
|
|
1065 |
version = property (__getversion)
|
|
1066 |
type = property (__gettype)
|
|
1067 |
instance = property (__getinstance)
|
|
1068 |
objectname = property (__getobjectname)
|
|
1069 |
family = property(__getfamily)
|
|
1070 |
|
|
1071 |
|
|
1072 |
class CCMObject(FourPartName):
|
|
1073 |
""" Base class for any Synergy object. """
|
|
1074 |
|
|
1075 |
def __init__(self, session, fpn):
|
|
1076 |
"""initialisation"""
|
|
1077 |
FourPartName.__init__(self, fpn)
|
|
1078 |
self._session = session
|
|
1079 |
|
|
1080 |
def _getsession(self):
|
|
1081 |
"""get session"""
|
|
1082 |
return self._session
|
|
1083 |
|
|
1084 |
session = property(_getsession)
|
|
1085 |
|
|
1086 |
def exists(self):
|
|
1087 |
""" Check if an the object exists in the database. """
|
|
1088 |
return (len(self._session.execute("query \"name='%s' and version='%s' and type='%s' and instance='%s'\" -u -f \"%%objectname\"" % (self.name, self.version, self.type, self.instance), ObjectListResult(self._session)).output) == 1)
|
|
1089 |
|
|
1090 |
def __setitem__(self, name, value):
|
|
1091 |
project = ""
|
|
1092 |
if self.type == 'project':
|
|
1093 |
project = "-p"
|
|
1094 |
if value.endswith("\\"):
|
|
1095 |
value += "\\"
|
|
1096 |
result = self._session.execute("attribute -modify \"%s\" -v \"%s\" %s \"%s\"" % (name, value, project, self))
|
|
1097 |
if result.status != 0 and result.status != None:
|
|
1098 |
raise CCMException("Error modifying '%s' attribute. Result: '%s'" % (name, result.output), result)
|
|
1099 |
|
|
1100 |
def __getitem__(self, name):
|
|
1101 |
""" Provides access to Synergy object attributes through the dictionary
|
|
1102 |
item interface.
|
|
1103 |
"""
|
|
1104 |
result = self._session.execute("query \"name='%s' and version='%s' and type='%s' and instance='%s'\" -u -f \"%%%s\"" % (self.name, self.version, self.type, self.instance, name), ResultWithError(self._session))
|
|
1105 |
if result.status != 0 and result.status != None:
|
|
1106 |
raise CCMException("Error retrieving '%s' attribute. Result: '%s'" % (name, result.output), result)
|
|
1107 |
if len(result.error.strip()) > 0:
|
|
1108 |
raise CCMException("Error retrieving '%s' attribute. Reason: '%s'" % (name, result.error), result)
|
|
1109 |
if result.output.strip() == "<void>":
|
|
1110 |
return None
|
|
1111 |
return result.output.strip()
|
|
1112 |
|
|
1113 |
def create_attribute(self, name, type_, value=None):
|
|
1114 |
"""create attribute"""
|
|
1115 |
if name in self.keys():
|
|
1116 |
raise CCMException("Attribute '%s' already exist." % (name))
|
|
1117 |
args = ""
|
|
1118 |
proj_arg = ""
|
|
1119 |
if value != None:
|
|
1120 |
args += " -value \"%s\"" % value
|
|
1121 |
if self.type == "project":
|
|
1122 |
proj_arg = "-p"
|
|
1123 |
result = self._session.execute("attribute -create \"%s\" -type \"%s\" %s %s \"%s\"" % (name, type_, args, proj_arg, self.objectname))
|
|
1124 |
if result.status != 0 and result.status != None:
|
|
1125 |
raise CCMException("Error creating '%s' attribute. Result: '%s'" % (name, result.output), result)
|
|
1126 |
|
|
1127 |
def keys(self):
|
|
1128 |
""" The list of supported Synergy attributes. """
|
|
1129 |
result = self._session.execute("attribute -la \"%s\"" % self, AttributeNameListResult(self._session))
|
|
1130 |
return result.output
|
|
1131 |
|
|
1132 |
def is_predecessor_of(self, obj):
|
|
1133 |
"""is predecessor of returns boolean"""
|
|
1134 |
result = self._session.execute("query \"is_predecessor_of('%s') and name='%s'and version='%s'and type='%s'and instance='%s'\" -u -f \"%%objectname\"" % (obj, self.name, self.version, self.type, self.instance), ObjectListResult(self._session))
|
|
1135 |
if len(result.output):
|
|
1136 |
return True
|
|
1137 |
return False
|
|
1138 |
|
|
1139 |
def predecessors(self):
|
|
1140 |
"""predecessors"""
|
|
1141 |
result = self._session.execute("query \"is_predecessor_of('%s')\" -u -f \"%%objectname\"" % self, ObjectListResult(self._session))
|
|
1142 |
return result.output
|
|
1143 |
|
|
1144 |
def successors(self):
|
|
1145 |
"""successors"""
|
|
1146 |
result = self._session.execute("query \"is_successor_of('%s')\" -u -f \"%%objectname\"" % self, ObjectListResult(self._session))
|
|
1147 |
return result.output
|
|
1148 |
|
|
1149 |
def is_recursive_predecessor_of(self, obj):
|
|
1150 |
"""is_recursive_predecessor_of returns boolean"""
|
|
1151 |
result = self._session.execute("query \"has_predecessor('%s')\" -u -f \"%%objectname\"" % self, ObjectListResult(self._session))
|
|
1152 |
for sesh in result.output:
|
|
1153 |
if sesh == obj:
|
|
1154 |
return True
|
|
1155 |
for sesh in result.output:
|
|
1156 |
if sesh.is_recursive_predecessor_of(obj):
|
|
1157 |
return True
|
|
1158 |
return False
|
|
1159 |
|
|
1160 |
def is_recursive_predecessor_of_fast(self, obj):
|
|
1161 |
""" Fast implementation of the recursive is_predecessor_of method. """
|
|
1162 |
input_objects = [self]
|
|
1163 |
while len(input_objects) > 0:
|
|
1164 |
query = " or ".join(["has_predecessor('%s')" % x for x in input_objects])
|
|
1165 |
result = self._session.execute("query \"query\" -u -f \"%%objectname\"" % query, ObjectListResult(self._session))
|
|
1166 |
for sesh in result.output:
|
|
1167 |
if sesh == obj:
|
|
1168 |
return True
|
|
1169 |
return False
|
|
1170 |
|
|
1171 |
def is_recursive_sucessor_of(self, obj):
|
|
1172 |
"""is_recursive_sucessor_of returns boolean"""
|
|
1173 |
result = self._session.execute("query \"has_successor('%s')\" -u -f \"%%objectname\"" % self, ObjectListResult(self._session))
|
|
1174 |
for sesh in result.output:
|
|
1175 |
if sesh == obj:
|
|
1176 |
return True
|
|
1177 |
for sesh in result.output:
|
|
1178 |
if sesh.is_recursive_sucessor_of(obj):
|
|
1179 |
return True
|
|
1180 |
return False
|
|
1181 |
|
|
1182 |
def is_recursive_successor_of_fast(self, obj):
|
|
1183 |
""" Fast implementation of the recursive is_successor_of method. """
|
|
1184 |
input_objects = [self]
|
|
1185 |
while len(input_objects) > 0:
|
|
1186 |
query = " or ".join(["has_successor('%s')" % x for x in input_objects])
|
|
1187 |
result = self._session.execute("query \"query\" -u -f \"%%objectname\"" % query, ObjectListResult(self._session))
|
|
1188 |
for sesh in result.output:
|
|
1189 |
if sesh == obj:
|
|
1190 |
return True
|
|
1191 |
return False
|
|
1192 |
|
|
1193 |
def relate(self, ccm_object):
|
|
1194 |
"""relate"""
|
|
1195 |
result = self._session.execute("relate -name successor -from \"%s\" -to \"%s\"" % self, ccm_object, Result(self._session))
|
|
1196 |
if result.status != None and result.status != 0:
|
|
1197 |
raise CCMException("Error relating objects %s to %s\n%s" % (self, ccm_object, result.output))
|
|
1198 |
|
|
1199 |
def finduse(self):
|
|
1200 |
""" Tries to find where an object is used. """
|
|
1201 |
result = self._session.execute("finduse \"%s\"" % self, FinduseResult(self))
|
|
1202 |
return result.output
|
|
1203 |
|
|
1204 |
def delete(self, recurse=False):
|
|
1205 |
""" Delete a synergy project. """
|
|
1206 |
args = ""
|
|
1207 |
if recurse:
|
|
1208 |
args = args + " -recurse"
|
|
1209 |
parg = ""
|
|
1210 |
if self.type == "project":
|
|
1211 |
parg = "-project"
|
|
1212 |
result = self._session.execute("delete %s %s \"%s\"" % (args, parg, self))
|
|
1213 |
if result.status != 0 and result.status != None:
|
|
1214 |
raise CCMException("An error occurred while deleting object %s (error status: %s)\n%s" % (self, result.status, result.output), result)
|
|
1215 |
return result
|
|
1216 |
|
|
1217 |
|
|
1218 |
class File(CCMObject):
|
|
1219 |
""" Wrapper for any Synergy file object """
|
|
1220 |
|
|
1221 |
def __init__(self, session, fpn):
|
|
1222 |
CCMObject.__init__(self, session, fpn)
|
|
1223 |
|
|
1224 |
def content(self):
|
|
1225 |
"""content"""
|
|
1226 |
result = self._session.execute("cat \"%s\"" % self)
|
|
1227 |
return result.output
|
|
1228 |
|
|
1229 |
def to_file(self, path):
|
|
1230 |
"""content to file"""
|
|
1231 |
if os.path.exists(path):
|
|
1232 |
_logger.error("Error file %s already exists" % path)
|
|
1233 |
if not os.path.exists(os.path.dirname(path)):
|
|
1234 |
os.makedirs(os.path.dirname(path))
|
|
1235 |
# Content to file
|
|
1236 |
result = self._session.execute("cat \"%s\" > \"%s\"" % (self, os.path.normpath(path)))
|
|
1237 |
if result.status != 0 and result.status != None:
|
|
1238 |
raise CCMException("Error retrieving content from object %s in %s (error status: %s)\n%s" % (self, path, result.status, result.output), result)
|
|
1239 |
|
|
1240 |
def merge(self, ccm_object, task):
|
|
1241 |
"""merge"""
|
|
1242 |
assert ccm_object != None, "object must be defined."
|
|
1243 |
assert task != None, "task must be defined."
|
|
1244 |
assert task.type == "task", "task parameter must be of 'task' type."
|
|
1245 |
result = self._session.execute("merge -task %s \"%s\" \"%s\"" % (task['displayname'], self, ccm_object))
|
|
1246 |
|
|
1247 |
validity = 0
|
|
1248 |
for line in result.output.splitlines():
|
|
1249 |
if re.match(r"Merge Source completed successfully\.", line):
|
|
1250 |
validity = 2
|
|
1251 |
elif re.match(r"Warning: Merge Source warning. \(overlaps during merge\)\.", line):
|
|
1252 |
validity = 1
|
|
1253 |
else:
|
|
1254 |
result = re.match(r"Associated object\s+(?P<object>.+)\s+with task", line)
|
|
1255 |
if result != None:
|
|
1256 |
return (self._session.create(result.groupdict()['object']), validity)
|
|
1257 |
|
|
1258 |
raise CCMException("Error during merge operation.\n" + result.output, result)
|
|
1259 |
|
|
1260 |
def checkin(self, state, comment=None):
|
|
1261 |
"""checkin"""
|
|
1262 |
if comment != None:
|
|
1263 |
comment = "-c \"%s\"" % comment
|
|
1264 |
else:
|
|
1265 |
comment = "-nc"
|
|
1266 |
result = self._session.execute("checkin -s \"%s\" %s \"%s\" " % (state, comment, self))
|
|
1267 |
for line in result.output.splitlines():
|
|
1268 |
_logger.debug(line)
|
|
1269 |
_logger.debug(r"Checked\s+in\s+'.+'\s+to\s+'%s'" % state)
|
|
1270 |
if re.match(r"Checked\s+in\s+'.+'\s+to\s+'%s'" % state, line) != None:
|
|
1271 |
return
|
|
1272 |
raise CCMException("Error checking in object %s,\n%s" % (self, result.output), result)
|
|
1273 |
|
|
1274 |
|
|
1275 |
class Project(CCMObject):
|
|
1276 |
""" Wrapper class for Synergy project object. """
|
|
1277 |
|
|
1278 |
def __init__(self, session, fpn):
|
|
1279 |
CCMObject.__init__(self, session, fpn)
|
|
1280 |
self._release = None
|
|
1281 |
self._baseline = None
|
|
1282 |
|
|
1283 |
def _gettasks(self):
|
|
1284 |
"""gettasks"""
|
|
1285 |
result = self._session.execute("rp -show tasks \"%s\" -u -f \"%%objectname\"" % self, ObjectListResult(self._session))
|
|
1286 |
return result.output
|
|
1287 |
|
|
1288 |
def add_task(self, task):
|
|
1289 |
""" Add a task to the update properties. """
|
|
1290 |
result = self._session.execute("up -add -task %s \"%s\"" % (task['displayname'], self.objectname))
|
|
1291 |
if result.status != None and result.status != 0:
|
|
1292 |
raise CCMException("Error adding task %s to project '%s'\n%s" % (task, self, result.output))
|
|
1293 |
|
|
1294 |
def remove_task(self, task):
|
|
1295 |
""" Remove a task to the update properties. """
|
|
1296 |
result = self._session.execute("up -remove -task %s \"%s\"" % (task['displayname'], self.objectname))
|
|
1297 |
if result.status != None and result.status != 0:
|
|
1298 |
raise CCMException("Error removing task %s from project '%s'\n%s" % (task, self, result.output))
|
|
1299 |
|
|
1300 |
def add_folder(self, folder):
|
|
1301 |
""" Add a folder to the update properties. """
|
|
1302 |
result = self._session.execute("up -add -folder %s \"%s\"" % (folder['displayname'], self.objectname))
|
|
1303 |
if result.status != None and result.status != 0:
|
|
1304 |
raise CCMException("Error adding folder %s to project '%s'\n%s" % (folder, self, result.output))
|
|
1305 |
|
|
1306 |
def remove_folder(self, folder):
|
|
1307 |
""" Remove a folder to the update properties. """
|
|
1308 |
result = self._session.execute("up -remove -folder %s \"%s\"" % (folder['displayname'], self.objectname))
|
|
1309 |
if result.status != None and result.status != 0:
|
|
1310 |
raise CCMException("Error removing folder %s to project '%s'\n%s" % (folder, self, result.output))
|
|
1311 |
|
|
1312 |
def _getfolders(self):
|
|
1313 |
""" Wrapper method to return the folder list from the update properties - please use the folders attribute to access it. """
|
|
1314 |
result = self._session.execute("up -show folders \"%s\" -u -f \"%%objectname\"" % self, ObjectListResult(self._session))
|
|
1315 |
return result.output
|
|
1316 |
|
|
1317 |
def _getsubprojects(self):
|
|
1318 |
""" Wrapper method to return the subprojects list - please use the subprojects attribute to access it. """
|
|
1319 |
result = self._session.execute("query -t project \"recursive_is_member_of('%s', none)\" -u -f \"%%objectname\"" % self.objectname, ObjectListResult(self._session))
|
|
1320 |
return result.output
|
|
1321 |
|
|
1322 |
def get_members(self, recursive=False, **kargs):
|
|
1323 |
"""get members"""
|
|
1324 |
query = "is_member_of('%s')" % self.objectname
|
|
1325 |
if recursive:
|
|
1326 |
query = "recursive_is_member_of('%s', none)" % self.objectname
|
|
1327 |
for key in kargs.keys():
|
|
1328 |
query += " and %s='%s'" % (key, kargs[key])
|
|
1329 |
result = self._session.execute("query \"%s\" -u -f \"%%objectname\"" % query, ObjectListResult(self._session))
|
|
1330 |
return result.output
|
|
1331 |
|
|
1332 |
def _getrelease(self):
|
|
1333 |
""" Get the release of the current object. Returns a Releasedef object. """
|
|
1334 |
self._release = Releasedef(self._session, self['release'])
|
|
1335 |
return self._release
|
|
1336 |
|
|
1337 |
def _setrelease(self, release):
|
|
1338 |
""" Set the release of the current object. """
|
|
1339 |
self['release'] = release['displayname']
|
|
1340 |
|
|
1341 |
def refresh(self):
|
|
1342 |
""" Refresh project update properties. """
|
|
1343 |
result = self._session.execute("up -refresh \"%s\"" % self.objectname, UpdatePropertiesRefreshResult(self._session))
|
|
1344 |
return result.output
|
|
1345 |
|
|
1346 |
def _getbaseline(self):
|
|
1347 |
""" Get the baseline of the current project. """
|
|
1348 |
if self._baseline == None:
|
|
1349 |
result = self._session.execute("up -show baseline_project \"%s\" -f \"%%displayname\" -u" % self.objectname)
|
|
1350 |
if result.output.strip().endswith('does not have a baseline project.'):
|
|
1351 |
return None
|
|
1352 |
self._baseline = self._session.create(result.output)
|
|
1353 |
_logger.debug('baseline: %s' % self._baseline)
|
|
1354 |
return self._baseline
|
|
1355 |
|
|
1356 |
def set_baseline(self, baseline, recurse=False):
|
|
1357 |
""" Set project baseline. raise a CCMException in case or error. """
|
|
1358 |
args = ""
|
|
1359 |
if recurse:
|
|
1360 |
args += " -r"
|
|
1361 |
self._baseline = None
|
|
1362 |
result = self._session.execute("up -mb \"%s\" %s \"%s\"" % (baseline, args, self.objectname))
|
|
1363 |
if result.status != None and result.status != 0:
|
|
1364 |
raise CCMException("Error setting basline of project '%s'\n%s" % (self.objectname, result.output))
|
|
1365 |
|
|
1366 |
def set_update_method(self, name, recurse = False):
|
|
1367 |
""" Set the update method for the project (and subproject if recurse is True). """
|
|
1368 |
assert name != None, "name must not be None."
|
|
1369 |
assert len(name) > 0, "name must not be an empty string."
|
|
1370 |
args = "-ru %s" % name
|
|
1371 |
if recurse:
|
|
1372 |
args += " -r"
|
|
1373 |
result = self._session.execute("up %s \"%s\"" % (args, self))
|
|
1374 |
if result.status != None and result.status != 0:
|
|
1375 |
raise CCMException("Error setting reconfigure properties to %s for project '%s'\nStatus: %s\n%s" % (name, self.objectname, result.status, result.output))
|
|
1376 |
|
|
1377 |
def apply_update_properties(self, baseline = True, tasks_and_folders = True, recurse=True):
|
|
1378 |
""" Apply update properties to subprojects. """
|
|
1379 |
args = ""
|
|
1380 |
if not baseline:
|
|
1381 |
args += "-no_baseline"
|
|
1382 |
if not tasks_and_folders:
|
|
1383 |
args += " -no_tasks_and_folders"
|
|
1384 |
if recurse:
|
|
1385 |
args += " -apply_to_subprojs"
|
|
1386 |
result = self._session.execute("rp %s \"%s\"" % (args, self.objectname))
|
|
1387 |
if result.status != None and result.status != 0:
|
|
1388 |
raise CCMException("Error applying update properties to subprojects for '%s'\n%s" % (self.objectname, result.output))
|
|
1389 |
|
|
1390 |
def root_dir(self):
|
|
1391 |
""" Return the directory attached to a project. """
|
|
1392 |
result = self._session.execute("query \"is_child_of('%s','%s')\" -u -f \"%%objectname\"" % (self.objectname, self.objectname), ObjectListResult(self._session))
|
|
1393 |
return result.output[0]
|
|
1394 |
|
|
1395 |
def snapshot(self, targetdir, recursive=False):
|
|
1396 |
""" Take a snapshot of the project. """
|
|
1397 |
assert targetdir != None, "targetdir must be defined."
|
|
1398 |
if recursive:
|
|
1399 |
recursive = "-recurse"
|
|
1400 |
else:
|
|
1401 |
recursive = ""
|
|
1402 |
result = self._session.execute("wa_snapshot -path \"%s\" %s \"%s\"" % (os.path.normpath(targetdir), recursive, self.objectname))
|
|
1403 |
for line in result.output.splitlines():
|
|
1404 |
if re.match(r"^Creation of snapshot work area complete.|Copying to file system complete\.\s*$", line):
|
|
1405 |
return result.output
|
|
1406 |
raise CCMException("Error creation snapshot of %s,\n%s" % (self.objectname, result.output), result)
|
|
1407 |
|
|
1408 |
def checkout(self, release, version=None, purpose=None, subprojects=True):
|
|
1409 |
""" Create a checkout of this project.
|
|
1410 |
|
|
1411 |
This will only checkout the project in Synergy. It does not create a work area.
|
|
1412 |
|
|
1413 |
:param release: The Synergy release tag to use.
|
|
1414 |
:param version: The new version to use for the project. This is applied to all subprojects.
|
|
1415 |
:param purpose: The purpose of the checkout. Determines automatically the role from the purpose
|
|
1416 |
and switch it automatically (Could be any role from the DB).
|
|
1417 |
"""
|
|
1418 |
assert release != None, "Release object must be defined."
|
|
1419 |
if not release.exists():
|
|
1420 |
raise CCMException("Release '%s' must exist in the database." % release)
|
|
1421 |
|
|
1422 |
args = ''
|
|
1423 |
if version != None:
|
|
1424 |
args += '-to "%s"' % version
|
|
1425 |
role = None
|
|
1426 |
if purpose:
|
|
1427 |
#save current role before changing
|
|
1428 |
role = self._session.role
|
|
1429 |
|
|
1430 |
self._session.role = get_role_for_purpose(self._session, purpose)
|
|
1431 |
|
|
1432 |
args += " -purpose \"%s\"" % purpose
|
|
1433 |
if subprojects:
|
|
1434 |
args += " -subprojects"
|
|
1435 |
result = self._session.execute("checkout -project \"%s\" -release \"%s\" -no_wa %s" \
|
|
1436 |
% (self, release['displayname'], args), ProjectCheckoutResult(self._session, self.objectname))
|
|
1437 |
if not role is None:
|
|
1438 |
self._session.role = role
|
|
1439 |
if result.project == None:
|
|
1440 |
raise CCMException("Error checking out project %s,\n%s" % (self.objectname, result.output), result)
|
|
1441 |
return result
|
|
1442 |
|
|
1443 |
def work_area(self, maintain, recursive=None, relative=None, path=None, pst=None, wat=False):
|
|
1444 |
""" Configure the work area. This allow to enable it or disable it, set the path, recursion... """
|
|
1445 |
args = ""
|
|
1446 |
if maintain:
|
|
1447 |
args += "-wa"
|
|
1448 |
else:
|
|
1449 |
args += "-nwa"
|
|
1450 |
# path
|
|
1451 |
if path != None:
|
|
1452 |
args += " -path \"%s\"" % path
|
|
1453 |
# pst
|
|
1454 |
if pst != None:
|
|
1455 |
args += " -pst \"%s\"" % pst
|
|
1456 |
# relative
|
|
1457 |
if relative != None and relative:
|
|
1458 |
args += " -relative"
|
|
1459 |
elif relative != None and not relative:
|
|
1460 |
args += " -not_relative"
|
|
1461 |
# recursive
|
|
1462 |
if recursive != None and recursive:
|
|
1463 |
args += " -recurse"
|
|
1464 |
elif recursive != None and not recursive:
|
|
1465 |
args += " -no_recurse"
|
|
1466 |
#wat
|
|
1467 |
if wat:
|
|
1468 |
args += " -wat"
|
|
1469 |
result = self._session.execute("work_area -project \"%s\" %s" \
|
|
1470 |
% (self.objectname, args), Result(self._session))
|
|
1471 |
return result.output
|
|
1472 |
|
|
1473 |
def update(self, recurse=True, replaceprojects=True, keepgoing=False, result=None):
|
|
1474 |
""" Update the project based on its reconfigure properties. """
|
|
1475 |
args = ""
|
|
1476 |
if recurse:
|
|
1477 |
args += " -r "
|
|
1478 |
if replaceprojects:
|
|
1479 |
args += " -rs "
|
|
1480 |
else:
|
|
1481 |
args += " -ks "
|
|
1482 |
if result == None:
|
|
1483 |
result = UpdateResult(self._session)
|
|
1484 |
result = self._session.execute("update %s -project %s" % (args, self.objectname), result)
|
|
1485 |
if not result.successful and not keepgoing:
|
|
1486 |
raise CCMException("Error updating %s" % (self.objectname), result)
|
|
1487 |
return result
|
|
1488 |
|
|
1489 |
def reconcile(self, updatewa=True, recurse=True, consideruncontrolled=True, missingwafile=True, report=True):
|
|
1490 |
""" Reconcile the project to force the work area to match the database. """
|
|
1491 |
args = ""
|
|
1492 |
if updatewa:
|
|
1493 |
args += " -update_wa "
|
|
1494 |
if recurse:
|
|
1495 |
args += " -recurse "
|
|
1496 |
if consideruncontrolled:
|
|
1497 |
args += " -consider_uncontrolled "
|
|
1498 |
if missingwafile:
|
|
1499 |
args += " -missing_wa_file "
|
|
1500 |
if report:
|
|
1501 |
args += " -report reconcile.txt "
|
|
1502 |
result = self._session.execute("reconcile %s -project %s" % (args, self.objectname), Result(self._session))
|
|
1503 |
if re.search(r"There are no conflicts in the Work Area", result.output) == None and re.search(r"Reconcile completed", result.output) == None:
|
|
1504 |
raise CCMException("Error reconciling %s,\n%s" % (self.objectname, result.output), result)
|
|
1505 |
return result.output
|
|
1506 |
|
|
1507 |
def get_latest_baseline(self, filterstring="*", state="released"):
|
|
1508 |
"""get_latest_baseline"""
|
|
1509 |
result = self._session.execute("query -n %s -t project -f \"%%displayname\" -s %s -u -ns \"version smatch'%s'\"" % (self.name, state, filterstring))
|
|
1510 |
lines = result.output.splitlines()
|
|
1511 |
return lines[-1]
|
|
1512 |
|
|
1513 |
def create_baseline(self, baseline_name, release, baseline_tag, purpose="System Testing", state="published_baseline"):
|
|
1514 |
"""create_baseline"""
|
|
1515 |
result = self._session.execute("baseline -create %s -release %s -purpose \"%s\" -vt %s -project \"%s\" -state \"%s\"" % (baseline_name, release, purpose, baseline_tag, self.objectname, state))
|
|
1516 |
return result.output
|
|
1517 |
|
|
1518 |
def sync(self, recurse=False, static=False):
|
|
1519 |
""" Synchronize project content. By default it is not been done recusively. (Not unittested)"""
|
|
1520 |
args = ""
|
|
1521 |
if recurse:
|
|
1522 |
args += " -recurse"
|
|
1523 |
if static:
|
|
1524 |
args += " -static"
|
|
1525 |
result = self._session.execute("sync %s -project \"%s\"" % (args, self.objectname))
|
|
1526 |
if result.status != None and result.status != 0:
|
|
1527 |
raise CCMException("Error during synchronization of %s: %s." % (self.objectname, result.output))
|
|
1528 |
return result.output
|
|
1529 |
|
|
1530 |
def conflicts(self, recurse=False, tasks=False):
|
|
1531 |
"""conflicts"""
|
|
1532 |
args = "-noformat "
|
|
1533 |
if recurse:
|
|
1534 |
args += " -r"
|
|
1535 |
if tasks:
|
|
1536 |
args += " -t"
|
|
1537 |
|
|
1538 |
result = self._session.execute("conflicts %s \"%s\"" % (args, self.objectname), ConflictsResult(self._session))
|
|
1539 |
if result.status != None and result.status != 0:
|
|
1540 |
raise CCMException("Error during conflict detection of %s: %s." % (self.objectname, result))
|
|
1541 |
return result
|
|
1542 |
|
|
1543 |
tasks = property(_gettasks)
|
|
1544 |
folders = property(_getfolders)
|
|
1545 |
subprojects = property(_getsubprojects)
|
|
1546 |
release = property(_getrelease, _setrelease)
|
|
1547 |
baseline = property(_getbaseline, set_baseline)
|
|
1548 |
|
|
1549 |
|
|
1550 |
class Dir(CCMObject):
|
|
1551 |
""" Wrapper class for Synergy dir object """
|
|
1552 |
|
|
1553 |
def __init__(self, session, fpn):
|
|
1554 |
CCMObject.__init__(self, session, fpn)
|
|
1555 |
|
|
1556 |
def children(self, project):
|
|
1557 |
"""children"""
|
|
1558 |
assert(project.type == 'project')
|
|
1559 |
result = self._session.execute("query \"is_child_of('%s','%s')\" -u -f \"%%objectname\"" % (self.objectname, project), ObjectListResult(self._session))
|
|
1560 |
return result.output
|
|
1561 |
|
|
1562 |
|
|
1563 |
class Releasedef(CCMObject):
|
|
1564 |
""" Wrapper class for Synergy releasedef object """
|
|
1565 |
|
|
1566 |
def __init__(self, session, fpn):
|
|
1567 |
CCMObject.__init__(self, session, fpn)
|
|
1568 |
|
|
1569 |
def _getcomponent(self):
|
|
1570 |
"""get component"""
|
|
1571 |
return self.name
|
|
1572 |
|
|
1573 |
component = property(_getcomponent)
|
|
1574 |
|
|
1575 |
|
|
1576 |
class Folder(CCMObject):
|
|
1577 |
""" Wrapper class for Synergy folder object """
|
|
1578 |
|
|
1579 |
def __init__(self, session, fpn):
|
|
1580 |
CCMObject.__init__(self, session, fpn)
|
|
1581 |
|
|
1582 |
def _gettasks(self):
|
|
1583 |
""" Accessor for 'tasks' property. """
|
|
1584 |
result = self._session.execute("folder -show tasks \"%s\" -u -f \"%%objectname\"" % self.objectname, ObjectListResult(self._session))
|
|
1585 |
return result.output
|
|
1586 |
|
|
1587 |
def _getobjects(self):
|
|
1588 |
"""get objects"""
|
|
1589 |
result = self._session.execute("folder -show objects \"%s\" -u -f \"%%objectname\"" % self.objectname, ObjectListResult(self._session))
|
|
1590 |
return result.output
|
|
1591 |
|
|
1592 |
def _getmode(self):
|
|
1593 |
""" Get the mode used by the folder. """
|
|
1594 |
result = self._session.execute("folder -show mode \"%s\"" % self.objectname)
|
|
1595 |
return result.output.strip()
|
|
1596 |
|
|
1597 |
def _getquery(self):
|
|
1598 |
""" Get the query that populate the folder. """
|
|
1599 |
if self.mode.lower() == "query":
|
|
1600 |
result = self._session.execute("folder -show query \"%s\"" % self.objectname)
|
|
1601 |
return result.output.strip()
|
|
1602 |
else:
|
|
1603 |
raise CCMException("%s is not a query base folder." % (self.objectname))
|
|
1604 |
|
|
1605 |
def _getdescription(self):
|
|
1606 |
""" Get the description associated with the folder. """
|
|
1607 |
result = self._session.execute("query -t folder -n %s -i %s -u -f \"%%description\"" % (self.name, self.instance))
|
|
1608 |
return result.output.strip()
|
|
1609 |
|
|
1610 |
def remove(self, task):
|
|
1611 |
""" Remove task from this folder. """
|
|
1612 |
result = self._session.execute("folder -m \"%s\" -remove_task \"%s\"" % (self.objectname, task.objectname))
|
|
1613 |
if result.status != None and result.status != 0:
|
|
1614 |
raise CCMException("Error removing task %s from %s: %s." % (task.objectname, self.objectname, result.output))
|
|
1615 |
|
|
1616 |
def update(self):
|
|
1617 |
"""update"""
|
|
1618 |
result = self._session.execute("folder -m -update -f \"%%objectname\"" % self.objectname)
|
|
1619 |
if result.status != None and result.status != 0:
|
|
1620 |
raise CCMException("Error updating the folder content %s: %s." % (self.objectname, result.output))
|
|
1621 |
|
|
1622 |
def append(self, task):
|
|
1623 |
""" Associate an object to a task """
|
|
1624 |
class AddTaskException(CCMException):
|
|
1625 |
"""add task exception"""
|
|
1626 |
def __init__(self, reason, task, result):
|
|
1627 |
CCMException.__init__(self, reason, result)
|
|
1628 |
self.task = task
|
|
1629 |
|
|
1630 |
result = self._session.execute("folder -m -at \"%s\" \"%s\"" % (task.objectname, self.objectname))
|
|
1631 |
if re.search(r"(Added 1 task to)|(is already in folder)", result.output, re.M) is None:
|
|
1632 |
raise AddTaskException(result.output, result, task)
|
|
1633 |
|
|
1634 |
def copy(self, existing_folder):
|
|
1635 |
""" Copy the contents of existing_folder into this folder.
|
|
1636 |
|
|
1637 |
This appends to the destination folder by default.
|
|
1638 |
|
|
1639 |
:param existing_folder: The destination Folder object.
|
|
1640 |
"""
|
|
1641 |
result = self._session.execute("folder -copy %s -existing %s -append" % (self.objectname, existing_folder), FolderCopyResult(self._session))
|
|
1642 |
return result.output
|
|
1643 |
|
|
1644 |
objects = property(_getobjects)
|
|
1645 |
tasks = property(_gettasks)
|
|
1646 |
mode = property(_getmode)
|
|
1647 |
query = property(_getquery)
|
|
1648 |
is_query_based = property(lambda x: x.mode.lower() == "query")
|
|
1649 |
description = property(_getdescription)
|
|
1650 |
|
|
1651 |
|
|
1652 |
class Task(CCMObject):
|
|
1653 |
""" Wrapper class for Synergy task object """
|
|
1654 |
|
|
1655 |
def __init__(self, session, fpn):
|
|
1656 |
CCMObject.__init__(self, session, fpn)
|
|
1657 |
self.__unicode_str_text = None
|
|
1658 |
|
|
1659 |
def _getobjects(self):
|
|
1660 |
"""get objects"""
|
|
1661 |
result = self._session.execute("task -show objects \"%s\" -u -f \"%%objectname\"" % self.objectname, ObjectListResult(self._session))
|
|
1662 |
return result.output
|
|
1663 |
|
|
1664 |
def append(self, ccm_object):
|
|
1665 |
""" Associate an object to a task """
|
|
1666 |
class AddObjectException(CCMException):
|
|
1667 |
"""add object exception"""
|
|
1668 |
def __init__(self, comment, ccm_object):
|
|
1669 |
CCMException.__init__(self, comment)
|
|
1670 |
self.ccm_object = ccm_object
|
|
1671 |
|
|
1672 |
result = self._session.execute("task -associate \"%s\" -object \"%s\"" % (self.objectname, ccm_object.objectname))
|
|
1673 |
if not re.match(r"Associated object .+ with task .*\.", result.output, re.M):
|
|
1674 |
raise AddObjectException(result.output)
|
|
1675 |
|
|
1676 |
def assign(self, username):
|
|
1677 |
"""assign"""
|
|
1678 |
result = self._session.execute("task -modify \"%s\" -resolver %s" % (self.objectname, username))
|
|
1679 |
if not re.match(r"Changed resolver of task", result.output, re.M):
|
|
1680 |
raise CCMException("Error assigning task to user '%s',\n%s" % (username, result.output), result)
|
|
1681 |
|
|
1682 |
def _getsynopsis(self):
|
|
1683 |
"""get synopsis"""
|
|
1684 |
return self['task_synopsis']
|
|
1685 |
|
|
1686 |
@staticmethod
|
|
1687 |
def create(session, release_tag, synopsis=""):
|
|
1688 |
"""create"""
|
|
1689 |
assert release_tag.type == "releasedef", "release_tag must be a CCM object wrapper of releasedef type"
|
|
1690 |
result = session.execute("task -create -synopsis \"%s\" -release \"%s\"" % (synopsis, release_tag['displayname']), CreateNewTaskResult(session))
|
|
1691 |
return result.output
|
|
1692 |
|
|
1693 |
objects = property(_getobjects)
|
|
1694 |
|
|
1695 |
def __unicode__(self):
|
|
1696 |
# TODO: use optimised query that makes only 1 ccm query with suitable format
|
|
1697 |
if self.__unicode_str_text == None:
|
|
1698 |
self.__unicode_str_text = u'%s: %s' % (self['displayname'], self['task_synopsis'])
|
|
1699 |
return self.__unicode_str_text
|
|
1700 |
|
|
1701 |
def __str__(self):
|
|
1702 |
return self.__unicode__().encode('ascii', 'replace')
|
|
1703 |
|
|
1704 |
def get_release_tag(self):
|
|
1705 |
""" Get task release. Use release property!"""
|
|
1706 |
result = self._session.execute("attribute -show release \"%s\"" % (self.objectname), Result(self._session))
|
|
1707 |
return result.output
|
|
1708 |
|
|
1709 |
def set_release_tag(self, release_tag):
|
|
1710 |
""" Set task release. Use release property!"""
|
|
1711 |
result = self._session.execute("attribute -modify release -value \"%s\" \"%s\"" % (release_tag, self.objectname), Result(self._session))
|
|
1712 |
return result.output
|
|
1713 |
|
|
1714 |
release = property(get_release_tag, set_release_tag)
|
|
1715 |
|
|
1716 |
class UpdateTemplate:
|
|
1717 |
""" Allow to access Update Template property using Release and Purpose. """
|
|
1718 |
def __init__(self, releasedef, purpose):
|
|
1719 |
assert(releasedef != None)
|
|
1720 |
assert(purpose != None)
|
|
1721 |
self._releasedef = releasedef
|
|
1722 |
self._purpose = purpose
|
|
1723 |
|
|
1724 |
def objectname(self):
|
|
1725 |
""" Return the objectname representing this virtual object. """
|
|
1726 |
return "%s:%s" % (self._releasedef['displayname'], self._purpose)
|
|
1727 |
|
|
1728 |
def baseline_projects(self):
|
|
1729 |
""" Query all projects for this UpdateTemplate. """
|
|
1730 |
result = self._releasedef.session.execute("ut -sh baseline_projects \"%s\"" % self.objectname(), ObjectListResult(self._releasedef.session))
|
|
1731 |
print result.output
|
|
1732 |
return result.output
|
|
1733 |
|
|
1734 |
def information(self):
|
|
1735 |
""" Query all projects for this UpdateTemplate. """
|
|
1736 |
result = self._releasedef.session.execute("ut -sh information \"%s\"" % self.objectname(), UpdateTemplateInformation(self._releasedef.session))
|
|
1737 |
print result.output
|
|
1738 |
return result.output
|
|
1739 |
|
|
1740 |
def baseline_selection_mode(self):
|
|
1741 |
""" The current Baseline selection mode """
|
|
1742 |
result = self._releasedef.session.execute("ut -sh bsm \"%s\"" % self.objectname())
|
|
1743 |
print result.output.strip()
|
|
1744 |
return result.output.strip()
|
|
1745 |
|
|
1746 |
|
|
1747 |
def read_ccmwaid_info(filename):
|
|
1748 |
""" Read data from a ccmwaid file. This method is an helper to retreive a project from a physical location. """
|
|
1749 |
ccmwaid = open(filename, 'r')
|
|
1750 |
# first line: database
|
|
1751 |
dbpath = os.path.dirname(ccmwaid.readline().strip())
|
|
1752 |
database = os.path.basename(dbpath)
|
|
1753 |
# 2nd line should be a timestamp
|
|
1754 |
ccmwaid.readline().strip()
|
|
1755 |
# 3rd line is the objectname
|
|
1756 |
objectref = ccmwaid.readline().strip()
|
|
1757 |
ccmwaid.close()
|
|
1758 |
return {'dbpath': dbpath, 'database': database, 'objectname': objectref}
|
|
1759 |
|
|
1760 |
def create_project_from_path(session, path):
|
|
1761 |
""" Uses the (_|.)ccmwaid.inf file to create a Project object. """
|
|
1762 |
ccmwaid = ".ccmwaid.inf"
|
|
1763 |
if os.name == 'nt':
|
|
1764 |
ccmwaid = "_ccmwaid.inf"
|
|
1765 |
|
|
1766 |
if (not os.path.exists(path + "/" + ccmwaid)):
|
|
1767 |
return None
|
|
1768 |
result = read_ccmwaid_info(path + "/" + ccmwaid)
|
|
1769 |
|
|
1770 |
return session.create(result['objectname'])
|
|
1771 |
|
|
1772 |
|
|
1773 |
def open_session(username=None, password=None, engine=None, dbpath=None, database=None, reuse=True):
|
|
1774 |
"""Provides a Session object.
|
|
1775 |
|
|
1776 |
Attempts to return a Session, based either on existing Synergy
|
|
1777 |
sessions or by creating a new one.
|
|
1778 |
|
|
1779 |
- If a .netrc file can be found on the user's personal drive,
|
|
1780 |
that will be read to obtain Synergy login information if it
|
|
1781 |
is defined there. This will be used to fill in any missing
|
|
1782 |
parameters not passed in the call to open_session().
|
|
1783 |
|
|
1784 |
The format of the .netrc file entries should be:
|
|
1785 |
|
|
1786 |
machine synergy login USERNAME password foobar account DATABASE_PATH@SERVER
|
|
1787 |
|
|
1788 |
If the details refer to a specific database, the machine can be the database name,
|
|
1789 |
instead of "synergy".
|
|
1790 |
- If an existing session is running that matches the supplied
|
|
1791 |
parameters, it will reuse that.
|
|
1792 |
|
|
1793 |
"""
|
|
1794 |
# See if a .netrc file can be used
|
|
1795 |
if CCM_BIN == None:
|
|
1796 |
raise CCMException("Could not find CM/Synergy executable in the path.")
|
|
1797 |
if password == None or username == None or engine == None or dbpath == None:
|
|
1798 |
if os.sep == '\\':
|
|
1799 |
os.environ['HOME'] = "H:" + os.sep
|
|
1800 |
_logger.debug('Opening .netrc file')
|
|
1801 |
try:
|
|
1802 |
netrc_file = netrc.netrc()
|
|
1803 |
netrc_info = None
|
|
1804 |
# If settings for a specific database
|
|
1805 |
if database != None:
|
|
1806 |
netrc_info = netrc_file.authenticators(database)
|
|
1807 |
|
|
1808 |
# if not found just try generic one
|
|
1809 |
if netrc_info == None:
|
|
1810 |
netrc_info = netrc_file.authenticators('synergy')
|
|
1811 |
|
|
1812 |
if netrc_info != None:
|
|
1813 |
(n_username, n_account, n_password) = netrc_info
|
|
1814 |
if username == None:
|
|
1815 |
username = n_username
|
|
1816 |
if password == None:
|
|
1817 |
password = n_password
|
|
1818 |
if n_account != None:
|
|
1819 |
(n_dbpath, n_engine) = n_account.split('@')
|
|
1820 |
if dbpath == None and n_dbpath is not None:
|
|
1821 |
_logger.info('Database path set using .netrc (%s)' % n_dbpath)
|
|
1822 |
dbpath = n_dbpath
|
|
1823 |
if engine == None and n_engine is not None:
|
|
1824 |
_logger.info('Database engine set using .netrc (%s)' % n_engine)
|
|
1825 |
engine = n_engine
|
|
1826 |
except IOError:
|
|
1827 |
_logger.debug('Error accessing .netrc file')
|
|
1828 |
|
|
1829 |
# last chance...
|
|
1830 |
if username == None:
|
|
1831 |
username = os.environ['USERNAME']
|
|
1832 |
|
|
1833 |
# looking for dbpath using GSCM database
|
|
1834 |
if dbpath == None and database != None:
|
|
1835 |
_logger.info('Database path set using the GSCM database.')
|
|
1836 |
dbpath = nokia.gscm.get_db_path(database)
|
|
1837 |
|
|
1838 |
# looking for engine host using GSCM database
|
|
1839 |
if engine == None and database != None:
|
|
1840 |
_logger.info('Database engine set using the GSCM database.')
|
|
1841 |
engine = nokia.gscm.get_engine_host(database)
|
|
1842 |
|
|
1843 |
_sessions = []
|
|
1844 |
# See if any currently running sessions can be used, only if no password submitted, else use a brand new session!
|
|
1845 |
if password == None and reuse:
|
|
1846 |
_logger.debug('Querying for existing Synergy sessions')
|
|
1847 |
command = "%s status" % (CCM_BIN)
|
|
1848 |
pipe = os.popen(command, 'r')
|
|
1849 |
result = pipe.read()
|
|
1850 |
pipe.close()
|
|
1851 |
_logger.debug('ccm status result: ' + result)
|
|
1852 |
for match in re.finditer(r'(?P<ccmaddr>\w+:\d+:\d+.\d+.\d+.\d+(:\d+.\d+.\d+.\d+)?)(?P<current_session>\s+\(current\s+session\))?\nDatabase:\s*(?P<dbpath>\S+)', result, re.M):
|
|
1853 |
dictionary = match.groupdict()
|
|
1854 |
_logger.debug(dictionary['ccmaddr'])
|
|
1855 |
_logger.debug(socket.gethostname())
|
|
1856 |
_logger.debug(dictionary['current_session'])
|
|
1857 |
if dictionary['ccmaddr'].lower().startswith(socket.gethostname().lower()):
|
|
1858 |
# These session objects should not close the session on deletion,
|
|
1859 |
# because they did not initially create the session
|
|
1860 |
existing_session = Session(username, engine, dictionary['dbpath'], dictionary['ccmaddr'], close_on_exit=False)
|
|
1861 |
_logger.debug('Existing session found: %s' % existing_session)
|
|
1862 |
_sessions.append(existing_session)
|
|
1863 |
# looking for session using dbpath
|
|
1864 |
for session in _sessions:
|
|
1865 |
if session.dbpath == dbpath:
|
|
1866 |
return session
|
|
1867 |
else:
|
|
1868 |
# looking for router address using GSCM database
|
|
1869 |
router_address = None
|
|
1870 |
if database == None and dbpath != None:
|
|
1871 |
database = os.path.basename(dbpath)
|
|
1872 |
|
|
1873 |
lock = fileutils.Lock(CCM_SESSION_LOCK)
|
|
1874 |
try:
|
|
1875 |
lock.lock(wait=True)
|
|
1876 |
# if we have the database name we can switch to the correct Synergy router
|
|
1877 |
if database != None:
|
|
1878 |
_logger.info('Getting router address.')
|
|
1879 |
router_address = nokia.gscm.get_router_address(database)
|
|
1880 |
if os.sep == '\\' and router_address != None:
|
|
1881 |
routerfile = open(os.path.join(os.path.dirname(CCM_BIN), "../etc/_router.adr"), 'r')
|
|
1882 |
current_router = routerfile.read().strip()
|
|
1883 |
routerfile.close()
|
|
1884 |
if current_router != router_address.strip():
|
|
1885 |
_logger.info('Updating %s' % (os.path.normpath(os.path.join(os.path.dirname(CCM_BIN), "../etc/_router.adr"))))
|
|
1886 |
routerfile = open(os.path.join(os.path.dirname(CCM_BIN), "../etc/_router.adr"), "w+")
|
|
1887 |
routerfile.write("%s\n" % router_address)
|
|
1888 |
routerfile.close()
|
|
1889 |
|
|
1890 |
# If no existing sessions were available, start a new one
|
|
1891 |
_logger.info('Opening session.')
|
|
1892 |
new_session = Session.start(username, password, engine, dbpath)
|
|
1893 |
lock.unlock()
|
|
1894 |
return new_session
|
|
1895 |
finally:
|
|
1896 |
lock.unlock()
|
|
1897 |
raise CCMException("Cannot open session for user '%s'" % username)
|
|
1898 |
|
|
1899 |
|
|
1900 |
def get_role_for_purpose(session, purpose):
|
|
1901 |
""" return role needed to modify project with checkout for purpose. """
|
|
1902 |
purposes = session.purposes()
|
|
1903 |
if purpose in purposes:
|
|
1904 |
if purposes[purpose]['status'] == 'prep':
|
|
1905 |
return 'build_mgr'
|
|
1906 |
else:
|
|
1907 |
raise CCMException("Could not find purpose '%s' in the database.\n Valid purpose are: %s." % (purpose, ','.join(purposes.keys())))
|
|
1908 |
return 'developer'
|
|
1909 |
|
|
1910 |
def get_role_for_status(status):
|
|
1911 |
""" return role needed to modify project with a specific status. """
|
|
1912 |
if status == 'prep':
|
|
1913 |
return 'build_mgr'
|
|
1914 |
elif status == 'shared':
|
|
1915 |
return 'developer'
|
|
1916 |
elif status == 'working':
|
|
1917 |
return 'developer'
|
|
1918 |
else:
|
|
1919 |
raise CCMException("Unknow status '%s'" % status)
|
|
1920 |
|
|
1921 |
def running_sessions(database=None):
|
|
1922 |
""" Return the list of synergy session currently available on the local machine.
|
|
1923 |
If database is given then it tries to update the router address.
|
|
1924 |
"""
|
|
1925 |
_logger.debug('Querying for existing Synergy sessions')
|
|
1926 |
if CCM_BIN == None:
|
|
1927 |
raise CCMException("Could not find CM/Synergy executable in the path.")
|
|
1928 |
command = "%s status" % (CCM_BIN)
|
|
1929 |
|
|
1930 |
lock = fileutils.Lock(CCM_SESSION_LOCK)
|
|
1931 |
result = ""
|
|
1932 |
output = []
|
|
1933 |
try:
|
|
1934 |
# if we have the database name we can switch to the correct Synergy router
|
|
1935 |
if database != None:
|
|
1936 |
lock.lock(wait=True)
|
|
1937 |
_logger.info('Updating router address.')
|
|
1938 |
router_address = nokia.gscm.get_router_address(database)
|
|
1939 |
if os.sep == '\\' and router_address != None:
|
|
1940 |
routerfile = open(os.path.join(os.path.dirname(CCM_BIN), "../etc/_router.adr"), 'r')
|
|
1941 |
current_router = routerfile.read().strip()
|
|
1942 |
routerfile.close()
|
|
1943 |
if current_router != router_address.strip():
|
|
1944 |
_logger.info('Updating %s' % (os.path.normpath(os.path.join(os.path.dirname(CCM_BIN), "../etc/_router.adr"))))
|
|
1945 |
routerfile = open(os.path.join(os.path.dirname(CCM_BIN), "../etc/_router.adr"), "w+")
|
|
1946 |
routerfile.write("%s\n" % router_address)
|
|
1947 |
routerfile.close()
|
|
1948 |
|
|
1949 |
_logger.debug('Command: ' + command)
|
|
1950 |
(result, status) = _execute(command)
|
|
1951 |
if database != None:
|
|
1952 |
lock.unlock()
|
|
1953 |
if (status != 0):
|
|
1954 |
raise CCMException("Ccm status execution returned an error.")
|
|
1955 |
_logger.debug('ccm status result: ' + result)
|
|
1956 |
for match in re.finditer(r'Command Interface\s+@\s+(?P<ccmaddr>\w+:\d+:\d+.\d+.\d+.\d+(:\d+.\d+.\d+.\d+)*)(?P<current_session>\s+\(current\s+session\))?\s+Database:\s*(?P<dbpath>\S+)', result, re.M):
|
|
1957 |
data = match.groupdict()
|
|
1958 |
_logger.debug(data['ccmaddr'])
|
|
1959 |
_logger.debug(socket.gethostname())
|
|
1960 |
_logger.debug(data['current_session'])
|
|
1961 |
if data['ccmaddr'].lower().startswith(socket.gethostname().lower()):
|
|
1962 |
# These session objects should not close the session on deletion,
|
|
1963 |
# because they did not initially create the session
|
|
1964 |
existing_session = Session(None, None, data['dbpath'], data['ccmaddr'], close_on_exit=False)
|
|
1965 |
_logger.debug('Existing session found: %s' % existing_session)
|
|
1966 |
output.append(existing_session)
|
|
1967 |
finally:
|
|
1968 |
if database != None:
|
|
1969 |
lock.unlock()
|
|
1970 |
return output
|
|
1971 |
|
|
1972 |
def session_exists(sessionid, database=None):
|
|
1973 |
"""determine if session exists"""
|
|
1974 |
for session in running_sessions(database=database):
|
|
1975 |
_logger.debug(session.addr() + "==" + sessionid + "?")
|
|
1976 |
if session.addr() == sessionid:
|
|
1977 |
return True
|
|
1978 |
return False
|
|
1979 |
|
|
1980 |
# The location of the ccm binary must be located to know where the _router.adr file is, to support
|
|
1981 |
# switching databases.
|
|
1982 |
CCM_BIN = fileutils.which("ccm")
|
|
1983 |
if os.sep == '\\':
|
|
1984 |
CCM_BIN = fileutils.which("ccm.exe")
|