Skip to content

Commit 1e94f1a

Browse files
author
klehman-rally
committed
start on dealing with multi-element-path Project concept
1 parent e57ca16 commit 1e94f1a

2 files changed

Lines changed: 140 additions & 19 deletions

File tree

pyral/context.py

Lines changed: 65 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -302,7 +302,7 @@ def _setOperatingContext(self, project_name):
302302
result = self.agent.get('Project', fetch="Name", workspace=self._currentWorkspace, project=None)
303303

304304
if not result or result.resultCount == 0:
305-
problem = "No Projects found in the Workspace '%s'" % self._defaultWorkspace
305+
problem = "No accessible Projects found in the Workspace '%s'" % self._defaultWorkspace
306306
raise RallyRESTAPIError(problem)
307307

308308
try:
@@ -311,27 +311,40 @@ def _setOperatingContext(self, project_name):
311311
problem = "Unable to obtain Project Name values for projects in the '%s' Workspace"
312312
raise RallyRESTAPIError(problem % self._defaultWorkspace)
313313

314-
match_for_default_project = [project for project in projects if project.Name == self._defaultProject]
315-
match_for_named_project = [project for project in projects if project.Name == project_name]
314+
# does the project_name contain a ' // ' path element separator token?
315+
# if so, then we have to sidebar process this
316+
if ' // ' in project_name:
317+
target_project = self.__findMultiElementPathToProject(project_name)
318+
if not target_project:
319+
problem = "No such accessible multi-element-path Project: %s found in the Workspace '%s'"
320+
raise RallyRESTAPIError(problem % (project_name, self._currentWorkspace))
321+
# have to set:
322+
# self._defaultProject, self._currentProject
323+
# self._workspace_ref, self._project_ref
324+
# self.defaultContext, self.operatingContext
316325

317-
if project_name:
318-
if not match_for_named_project:
319-
problem = "The current Workspace '%s' does not contain a Project with the name of '%s'"
320-
raise RallyRESTAPIError(problem % (self._currentWorkspace, project_name))
321-
else:
322-
project = match_for_named_project[0]
323-
proj_ref = project._ref
324-
self._defaultProject = project.Name
325-
self._currentProject = project.Name
326326
else:
327-
if not match_for_default_project:
328-
problem = "The current Workspace '%s' does not contain a Project with the name of '%s'"
329-
raise RallyRESTAPIError(problem % (self._currentWorkspace, project_name))
327+
match_for_default_project = [project for project in projects if project.Name == self._defaultProject]
328+
match_for_named_project = [project for project in projects if project.Name == project_name]
329+
330+
if project_name:
331+
if not match_for_named_project:
332+
problem = "The current Workspace '%s' does not contain an accessible Project with the name of '%s'"
333+
raise RallyRESTAPIError(problem % (self._currentWorkspace, project_name))
334+
else:
335+
project = match_for_named_project[0]
336+
proj_ref = project._ref
337+
self._defaultProject = project.Name
338+
self._currentProject = project.Name
330339
else:
331-
project = match_for_default_project[0]
332-
proj_ref = project._ref
333-
self._defaultProject = project.Name
334-
self._currentProject = project.Name
340+
if not match_for_default_project:
341+
problem = "The current Workspace '%s' does not contain a Project with the name of '%s'"
342+
raise RallyRESTAPIError(problem % (self._currentWorkspace, project_name))
343+
else:
344+
project = match_for_default_project[0]
345+
proj_ref = project._ref
346+
self._defaultProject = project.Name
347+
self._currentProject = project.Name
335348
##
336349
## print(" Default Workspace : %s" % self._defaultWorkspace)
337350
## print(" Default Project : %s" % self._defaultProject)
@@ -341,6 +354,7 @@ def _setOperatingContext(self, project_name):
341354
if not self._projects:
342355
self._projects = {self._defaultWorkspace : [self._defaultProject]}
343356
if not self._workspace_ref:
357+
wksp_name, wkspace_ref = self.getWorkspace()
344358
short_ref = "/".join(wkspace_ref.split('/')[-2:]) # we only need the 'workspace/<oid>' part to be a valid ref
345359
self._workspace_ref = {self._defaultWorkspace : short_ref}
346360
if not self._project_ref:
@@ -365,6 +379,38 @@ def _setOperatingContext(self, project_name):
365379
## print(" completed _setOperatingContext processing...")
366380
##
367381

382+
def __findMultiElementPathToProject(self, project_name):
383+
"""
384+
Given a project_name in BaseProject // NextLevelProject // TargetProjectName form,
385+
determine the existence/accessiblity of each successive path from the BaseProject
386+
on towards the full path ending with TargetProjectName.
387+
If found return a pyral entity for the TargetProject which will include the ObjectID (oid)
388+
after setting an attribute for FullProjectPath with the value of project_name.
389+
"""
390+
proj_path_elements = project_name.split(' // ')
391+
base_path_element = proj_path_elements[0]
392+
result = self.agent.get('Project', fetch="Name", workspace=self._currentWorkspace, project=base_path_element)
393+
if not result or result.errors or result.resultCount != 1:
394+
problem = "No such accessible base Project found in the Workspace '%s'" % project_name
395+
raise RallyRESTAPIError(problem)
396+
base_project = result.next()
397+
parent = base_project
398+
project_path = base_project.Name
399+
400+
for proj_path_element in proj_path_elements[1:]:
401+
project_path.append(proj_path_element)
402+
criteria = ['Name = ""' % proj_path_element , 'Parent = %s' % parent._ref]
403+
result = self.agent.get('Project', fetch="Name", query=criteria, workspace=self._currentWorkspace, project=parent)
404+
if not result or result.errors or result.resultCount != 1:
405+
problem = "No such accessible Project found: '%s'" % ' // '.join(project_path)
406+
raise RallyRESTAPIError(problem)
407+
path_el = result.next()
408+
if ' // '.join(project_path) != project_name:
409+
raise RallyRESTAPIError()
410+
return path_el
411+
412+
413+
368414
def _getDefaults(self, user_response):
369415
"""
370416
We have to circumvent the normal machinery as this is part of setting up the

test/test_project_pathing.py

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
#!/usr/bin/env python
2+
3+
import sys, os
4+
import types
5+
6+
import py
7+
8+
from pyral import Rally, RallyUrlBuilder
9+
10+
##################################################################################################
11+
12+
from rally_targets import TRIAL, TRIAL_USER, TRIAL_PSWD
13+
from rally_targets import DEFAULT_WORKSPACE, DEFAULT_PROJECT
14+
from rally_targets import ALTERNATE_WORKSPACE, ALTERNATE_PROJECT
15+
from rally_targets import BOONDOCKS_WORKSPACE, BOONDOCKS_PROJECT
16+
from rally_targets import API_KEY
17+
from rally_targets import ACCOUNT_WITH_NO_DEFAULTS_CREDENTIALS
18+
19+
##################################################################################################
20+
21+
"""
22+
4 ways we'll have to accommodate multi-element path Project, ie., BaseProject // NextLevelProject // TargetProject
23+
(aka m-e-p Project)
24+
25+
o - Rally instance constructor with a m-e-p Project argument
26+
o - specification of a m-e-p Project in a Rally basic HTTP operation call
27+
get,put,post,delete as the project=m-e-p Project
28+
o - setProject(m-e-p Project)
29+
o - as a data payload item for Project as name instead of _ref
30+
31+
Example of Jenkins Build Connector spec for
32+
Jenkins // Corral // Salamandra
33+
"""
34+
35+
DUPLICATED_PROJECTS = ['Jenkins // Salamandra', 'Jenkins // Corral // Salamandra']
36+
DUPLICATED_PROJECT = 'Salamandra'
37+
SHORT_DUPE = DUPLICATED_PROJECTS[0]
38+
DEEP_DUPE = DUPLICATED_PROJECTS[1]
39+
40+
def test_get_duplicated_project():
41+
"""
42+
Using a known valid Rally server and known valid access credentials,
43+
issue a query to retrieve Project items that match a specific project name that occurs
44+
in more than one location in the tree of Projects under the target Workspace.
45+
"""
46+
yeti, cred = "yeti@rallydev.com", "Vistabahn"
47+
rally = Rally(server=TRIAL, user=yeti, password=cred, workspace=BOONDOCKS_WORKSPACE, project=BOONDOCKS_PROJECT)
48+
response = rally.get('Project', fetch=False, query='Name = "%s"' % DUPLICATED_PROJECT, limit=10)
49+
assert response.status_code == 200
50+
assert response.errors == []
51+
#assert response.warnings == []
52+
assert response.resultCount > 1
53+
assert response.resultCount == 2
54+
for proj in response:
55+
assert str(proj.Name) == DUPLICATED_PROJECT
56+
print(proj.details())
57+
58+
def test_get_distant_duplicated_project():
59+
"""
60+
Using a known valid Rally server and known valid access credentials,
61+
issue a query to retrieve a Project item that matches a full project path within the target workspace.
62+
Expect the result to return one and only one Project that is the correct Project.
63+
"""
64+
yeti, cred = "yeti@rallydev.com", "Vistabahn"
65+
rally = Rally(server=TRIAL, user=yeti, password=cred, workspace=BOONDOCKS_WORKSPACE, project=BOONDOCKS_PROJECT)
66+
response = rally.get('Project', fetch=False, query='Name = "%s"' % DEEP_DUPE, limit=10)
67+
assert response.status_code == 200
68+
assert response.errors == []
69+
#assert response.warnings == []
70+
assert response.resultCount == 1
71+
proj = response.next()
72+
assert str(proj.Name) == DUPLICATED_PROJECT
73+
74+
sp = rally.setProject(DEEP_DUPE)
75+
assert sp.Name == DEEP_DUPE

0 commit comments

Comments
 (0)