|
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") |