Пример #1
0
def test_mep_project_in_request_payload():
    """
        Using a known valid Rally server and known valid access credentials,
        obtain a Rally instance associated with a unique Project name within a
        valid Workspace.
        Assemble a payload dict to be used in creating a BuildDefinition item
        that uses a m-e-p Project value for the Project attribute.
        Issue the request to create the BuildDefinition item.
        The result should be the creation of the BuildDefinition item
        for which the Project attribute has a ref to the targeted m-e-p Project.
    """
    rally = Rally(server=TRIAL,
                  user=YETI_USER,
                  password=YETI_PSWD,
                  workspace=BOONDOCKS_WORKSPACE,
                  project=BOONDOCKS_PROJECT)
    workspace = rally.getWorkspace()
    dd_project = rally.getProject(DEEP_DUPE)
    info = {
        'Name': 'Throckmorton Nunnigan',
        'Description': 'A completely artificial sweetener',
        'Workspace': workspace._ref,
        'Project': dd_project._ref
    }

    build_defn = rally.create('BuildDefinition', info)
    assert build_defn.Project._ref == dd_project._ref
Пример #2
0
def createRallyTestCaseResult(tcid, tcr, note):
    my_server = "rally1.rallydev.com"
    my_apikey = "_LOLAPIKEYVERYLONGLOOKINGONE"  # This used to login instead of my username and password, i made it up
    my_workspace = "WORKSPACENAME"
    my_project = "PROJECTNAME"

    def timeStamp():
        return datetime.datetime.now().isoformat()

    # Passed parameters from test case (Test Case ID Number and Test Case Result)
    my_tc = tcid
    my_result = tcr
    print "Test Case: " + my_tc + ", Result: " + my_result
    target_test_case_id = 'FormattedID = "{}"'.format(my_tc)

    my_date = timeStamp()  # Date now
    print "Date: " + my_date

    rally = Rally(my_server,
                  apikey=my_apikey,
                  workspace=my_workspace,
                  project=my_project)  # Authorization using ApiKey
    #rally.enableLogging("createtestcaseresult.log")

    print "Getting Project details ... "
    time.sleep(3)
    target_project = rally.getProject()  # Getting Project details
    tp = target_project

    #print "***************************************************************"
    #print tp.details() # Printing Project details
    #print "***************************************************************"

    print "Getting Test Case details ... "
    time.sleep(3)
    target_test_case = rally.get('TestCase',
                                 fetch=True,
                                 query=target_test_case_id,
                                 project=tp.Name)  # Getting TestCase details

    tc = target_test_case.next()
    #print "***************************************************************"
    #print tc.details() # Printing TestCase details
    #print "***************************************************************"

    test_case_result_fields = {  # Body for TestCaseResults
        'TestCase': tc.ref,
        'Build':
        'Automation Build Name',  # You could also pass this parameter but I didn't need to at the time
        'Verdict': my_result,
        'Notes': note,  # Print URL that points to Jenkins test case output
        'Date': my_date
    }

    print "Adding Test Case Result ... "
    test_case_result = rally.create(
        'TestCaseResult', test_case_result_fields)  # Creating TestCaseResult
    print "Done!"
Пример #3
0
def main(args):
    options = [opt for opt in args if opt.startswith('--')]
    args    = [arg for arg in args if arg not in options]
    server, username, password, apikey, workspace, project = rallyWorkset(options)
    if apikey:
        rally = Rally(server, apikey=apikey, workspace=workspace, project=project)
    else:
        rally = Rally(server, user=username, password=password, workspace=workspace, project=project)
    rally.enableLogging("rally.hist.add_tcrs")

    if len(args) < 2:
        errout(USAGE)
        sys.exit(1)
    test_case_id, tcr_info_filename = args
    if not os.path.exists(tcr_info_filename):
        errout("ERROR:  file argument '%s' does not exist.  Respecify using corrent name or path\n" % tcr_info_filename)
        errout(USAGE)
        sys.exit(2)
    try:
        with open(tcr_info_filename, 'r') as tcif:
            content = tcif.readlines()
        tcr_info = []
        # each line must have Build, Date, Verdict
        for ix, line in enumerate(content):
            fields = line.split(',')
            if len(fields) != 3:
                raise Exception('Line #%d has invalid number of fields: %s' % (ix+1, repr(fields)))
            tcr_info.append([field.strip() for field in fields])
    except Exception:
        errout("ERROR: reading file '%s'.  Check the permissions or the content format for correctness." % tcr_info_filename)
        errout(USAGE)
        sys.exit(2)

    test_case = rally.get('TestCase', query="FormattedID = %s" % test_case_id,
                          workspace=workspace, project=None, instance=True)
    if not test_case or hasattr(test_case, 'resultCount'):
        print "Sorry, unable to find a TestCase with a FormattedID of %s in the %s workspace" % \
              (test_case_id, workspace)
        sys.exit(3)

    wksp = rally.getWorkspace()

    for build, run_date, verdict in tcr_info:
        tcr_data = { "Workspace" : wksp.ref,
                     "TestCase"  : test_case.ref,
                     "Build"     : build,
                     "Date"      : run_date,
                     "Verdict"   : verdict
                   }
        try:
            tcr = rally.create('TestCaseResult', tcr_data)
        except RallyRESTAPIError, details:
            sys.stderr.write('ERROR: %s \n' % details)
            sys.exit(4)
        
        print "Created  TestCaseResult OID: %s  TestCase: %s  Build: %s  Date: %s  Verdict: %s" % \
               (tcr.oid, test_case.FormattedID, tcr.Build, tcr.Date, tcr.Verdict)
def test_post_pull_request():
    expectedErrMsg = '422 Requested type name "pullrequest" is unknown.'
    #rally = Rally(server=TESTN, user=TESTN_USER, password=TESTN_PSWD)
    #rally = Rally(server=RALLY, user=RALLY_USER, password=RALLY_PSWD)
    rally = Rally(server=RALLY, apikey=RALLY_SUB_100_API_KEY)
    #with py.test.raises(RallyRESTAPIError) as excinfo:
    pr = rally.create('PullRequest', dummy_data, project=None)
    assert pr is not None
    assert pr.oid
def test_post_pull_request():
    expectedErrMsg = '422 Requested type name "pullrequest" is unknown.'
    #rally = Rally(server=TESTN, user=TESTN_USER, password=TESTN_PSWD)
    #rally = Rally(server=AGICEN, user=AGICEN_USER, password=AGICEN_PSWD)
    rally = Rally(server=AGICEN, apikey=AGICEN_SUB_100_API_KEY)
    #with py.test.raises(RallyRESTAPIError) as excinfo:
    pr = rally.create('PullRequest', dummy_data, project=None)
    assert pr is not None
    assert pr.oid
def test_add_tcr_attachment():
    """
        Add an Attachment to a TestCaseResult item

        Create a TestCase, save a reference
        Create a TestCaseResult to be associated with the TestCase
        Create an attachment
        Attach the Attachment to the TestCaseResult item
    """
    rally = Rally(server=TRIAL, user=TRIAL_USER, password=TRIAL_PSWD)
    wksp = rally.getWorkspace()
    assert wksp.Name == DEFAULT_WORKSPACE

    response = rally.get('Project', fetch=False, limit=10)
    assert response != None
    assert response.status_code == 200
    proj = rally.getProject()  # proj.Name == Sample Project
    assert proj.Name == 'Sample Project'

    tc_info = { "Workspace"    : wksp.ref,
                "Project"      : proj.ref,
                "Name"         : "Heat exposure",
                "Type"         : "Functional",
              }
    test_case = rally.create('TestCase', tc_info)
    assert int(test_case.oid) > 0

    tcr_info = { "Workspace" : wksp.ref,
                 "TestCase"  : test_case.ref,
                 "Date"      : "2016-05-17T14:30:28.000Z",
                 "Build"     : 17,
                 "Verdict"   : "Pass"
               }
    tcr = rally.create('TestCaseResult', tcr_info)
    assert int(tcr.oid) > 0

    attachment_name = "Addendum.txt"
    att_ok = conjureUpAttachmentFile(attachment_name)
    assert att_ok == True

    att = rally.addAttachment(tcr, attachment_name)
    assert att.Name == attachment_name
def test_epic_update():
    """
        Using a known valid Rally server and known valid access credentials,
        exercise the Rally.create method to create an Epic instance and be handed
        back a usable pyral.entity representing the newly created Epic PortfoloItem instance.
        Then a few seconds later, query for that newly created Epic and update a couple
        of the attributes of the Epic (Name, Ready) and receive back an instance of the updated Epic
        that is non-None and can be interrogated for all atribute information.
    """
    rally = Rally(server=RALLY, apikey=APIKEY)
    rally.setWorkspace('NMTest')
    target_workspace = rally.getWorkspace()

    rally.setProject('*MFA Benefit Durt')
    target_project = rally.getProject()

    epic_info = {
        "Workspace":
        target_workspace._ref,
        "Project":
        target_project._ref,
        "Name":
        "Funicular Wax Shine-athon",
        "Description":
        "Guests hands must not stick on the gate handle and must slide off the viewing port without leaving a dirty handprint",
        #"Ready"       : False,
        #"Parent"      : don't need a Feature ref for this test
    }

    print("Creating Funicular Epic ...")
    epic = rally.create('PortfolioItem/Epic', epic_info)
    assert epic
    print("Created Epic item {0}".format(epic.FormattedID))
    assert epic.Name == "Funicular Wax Shine-athon"
    assert epic.Ready == False
    time.sleep(3)

    print("Updating Funicular Epic {0}...".format(epic.FormattedID))
    upd_info = {
        'FormattedID': epic.FormattedID,
        'Name': "Funicular haz beenz shinola-ed",
        'Ready': True
    }
    upd_epic = rally.update('PortfolioItem/Epic', upd_info)
    assert upd_epic
    assert upd_epic.Name == "Funicular haz beenz shinola-ed"
    assert upd_epic.Ready == True
    print("PortfolioItem/Epic update was successful")

    time.sleep(2)
    result = rally.delete('PortfolioItem/Epic', upd_epic.FormattedID)
    assert result == True
    print("PortfolioItem/Epic crash-test-dummy {0} has been deleted".format(
        epic.FormattedID))
def test_mep_project_in_request_payload():
    """
        Using a known valid Rally server and known valid access credentials,
        obtain a Rally instance associated with a unique Project name within a
        valid Workspace.
        Assemble a payload dict to be used in creating a BuildDefinition item
        that uses a m-e-p Project value for the Project attribute.
        Issue the request to create the BuildDefinition item.
        The result should be the creation of the BuildDefinition item
        for which the Project attribute has a ref to the targeted m-e-p Project.
    """
    rally = Rally(server=TRIAL, user=YETI_USER, password=YETI_PSWD,
                  workspace=BOONDOCKS_WORKSPACE, project=BOONDOCKS_PROJECT)
    workspace = rally.getWorkspace()
    dd_project = rally.getProject(DEEP_DUPE)
    info = {
        'Name'         : 'Throckmorton Nunnigan',
        'Description'  : 'A completely artificial sweetener',
        'Workspace'    : workspace._ref,
        'Project'      : dd_project._ref
    }

    build_defn = rally.create('BuildDefinition', info)
    assert build_defn.Project._ref == dd_project._ref
Пример #9
0
def test_add_tcr_attachment():
    """
        Add an Attachment to a TestCaseResult item

        Create a TestCase, save a reference
        Create a TestCaseResult to be associated with the TestCase
        Create an attachment
        Attach the Attachment to the TestCaseResult item
    """
    #rally = Rally(server=RALLY, user=RALLY_USER, password=RALLY_PSWD)
    rally = Rally(server=RALLY,
                  user=RALLY_USER,
                  apikey=APIKEY,
                  workspace=DEFAULT_WORKSPACE,
                  project=DEFAULT_PROJECT)
    wksp = rally.getWorkspace()
    assert wksp.Name == DEFAULT_WORKSPACE

    response = rally.get('Project', fetch=False, limit=10)
    assert response != None
    assert response.status_code == 200
    proj = rally.getProject()  # proj.Name == Sample Project
    assert proj.Name == 'Sample Project'

    tc_info = {
        "Workspace": wksp.ref,
        "Project": proj.ref,
        "Name": "Heat exposure",
        "Type": "Functional",
    }
    test_case = rally.create('TestCase', tc_info)
    assert int(test_case.oid) > 0

    current = timestamp()[:-4].replace(' ', 'T') + "Z"

    tcr_info = {
        "Workspace": wksp.ref,
        "TestCase": test_case.ref,
        "Date": current,
        "Build": 27,
        "Verdict": "Pass"
    }
    tcr = rally.create('TestCaseResult', tcr_info)
    assert int(tcr.oid) > 0

    attachment_name = "Addendum.txt"
    att_ok = conjureUpAttachmentFile(attachment_name)
    assert att_ok == True

    att = rally.addAttachment(tcr, attachment_name)
    assert att.Name == attachment_name
    target = ['Build = 27', f'TestCase = {test_case.ref}']
    response = rally.get("TestCaseResult",
                         fetch='ObjectID,FormattedID,Attachments',
                         query=target,
                         project=None)
    assert response.resultCount == 1
    tcr = response.next()
    attachment = rally.getAttachment(tcr, attachment_name)
    assert attachment.Name == attachment_name
    att_type = attachment.ContentType
    assert att_type == 'text/plain'
    att = tcr.Attachments[0]
    actual_attachment_content = attachment.Content.decode('UTF-8').replace(
        "\r", '')
    att_content = att.Content.decode('UTF-8').replace("\r", '')

    assert actual_attachment_content == EXAMPLE_ATTACHMENT_CONTENT
    assert att_content == EXAMPLE_ATTACHMENT_CONTENT
    #assert attachment.Content.decode('UTF-8') == EXAMPLE_ATTACHMENT_CONTENT
    #assert att.Content.decode('UTF-8')        == EXAMPLE_ATTACHMENT_CONTENT
    rally.deleteAttachment(tcr, attachment_name)
    rally.delete('TestCaseResult', tcr)
    rally.delete('TestCase', test_case)
Пример #10
0
class RallyClient:
    """
    Rally Client base
    """
    def __init__(self, basic_auth, api_key, project, workspace, logger, is_testing):
        """
        Instantiate and return a Rally client pointed at https://rally1.rallydev.com.
        """
        self.log = logger
        self.is_testing = is_testing
        # logger.info('::: ')
        # logger.info('::: api_key: '+str(api_key))

        if api_key:
            logger.info('::: authenticate via API key')
            self.client = Rally(RALLY_SERVER, apikey=api_key, workspace=workspace, project=project)
        else:
            logger.info('::: authenticate via user CREDS')
            self.client = Rally(RALLY_SERVER, user=basic_auth[0], password=basic_auth[1], workspace=workspace, project=project)

        # self.client = Rally(RALLY_SERVER, user=basic_auth[0], password=basic_auth[1], workspace=RALLY_WORKSPACE, project=project)

    @staticmethod
    def get_value_from_response(entity, response):
        if response.errors:
            errors = "\n".join(response.errors)
            raise Exception(f'Error getting {entity}: {errors}')

        return response.next()

    @staticmethod
    def get_id(item):
        return getattr(item, 'FormattedID', None)

    @staticmethod
    def get_oid(item):
        return getattr(item, 'ObjectID', None)

    @staticmethod
    def get_notes(item):
        return getattr(item, 'Notes', None)

    def get_item(self, item_id, item_type):
        response = self.client.get(item_type, fetch=True, query=f'FormattedID = {item_id}')
        return self.get_value_from_response(item_type, response)

    # TODO:  This probably is non-functional. Fix or remove.
    def get_iteration(self, item_id):
        return self.get_item(item_id, RALLY_ITEM_TYPES['Iteration'])

    def get_user_story(self, item_id):
        return self.get_item(item_id, RALLY_ITEM_TYPES['UserStory'])

    def get_defect(self, item_id):
        return self.get_item(item_id, RALLY_ITEM_TYPES['Defect'])

    def get_task(self, item_id):
        return self.get_item(item_id, RALLY_ITEM_TYPES['Task'])

    def get_user(self, username):
        if not username:
            return None

        user = self.client.getUserInfo(username=username)
        if isinstance(user, list):
            return user[0]
        else:
            return user

    def get_rally_link(self, item_type, item):
        rally_id = self.get_oid(item)
        return f'https://{RALLY_SERVER}/#/detail/{item_type.lower()}/{rally_id}?fdp=true'

    def get_rally_user_story_link(self, item):
        return self.get_rally_link(RALLY_ITEM_TYPES['UserStory'].lower(), item)

    def get_rally_defect_link(self, item):
        return self.get_rally_link(RALLY_ITEM_TYPES['Defect'].lower(), item)

    def get_rally_task_link(self, item):
        return self.get_rally_link(RALLY_ITEM_TYPES['Task'].lower(), item)

    def get_allowed_values(self, entity):
        response = self.client.get(entity)
        return [item for item in response]

    def get_allowed_iterations(self):
        return self.get_allowed_values('Iteration')

    def get_allowed_milestones(self):
        return self.get_allowed_values('Milestone')

    @staticmethod
    def set_id(item, formatted_id):
        item.update({
            'FormattedID': formatted_id
        })

    @staticmethod
    def set_name(item, name):
        item.update({
            'Name': name
        })

    @staticmethod
    def set_description(item, description):
        item.update({
            'Description': description
        })

    @staticmethod
    def set_plan_est(item, plan_est):
        item.update({
            'PlanEstimate': plan_est
        })

    @staticmethod
    def set_scheduled_state(item, scheduled_state):
        item.update({
            'ScheduleState': scheduled_state
        })

    @staticmethod
    def set_state(item, state):
        item.update({
            'State': state
        })

    @staticmethod
    def set_priority(item, priority):
        item.update({
            'Priority': priority
        })

    @staticmethod
    def set_blocked(item, is_blocked):
        item.update({
            'Blocked': is_blocked
        })

    @staticmethod
    def set_notes(item, notes):
        item.update({
            'Notes': notes
        })

    @staticmethod
    def set_jira_linking_fields(item, jira_key, jira_link):
        item.update({
            'JiraKey': jira_key,
            'JiraLink': jira_link
        })

    @staticmethod
    def set_parent(item, parent):
        if parent:
            item.update({
                'WorkProduct': parent.ref
            })

    def set_iteration(self, item, iteration):
        allowed_iterations = self.get_allowed_iterations()

        if not allowed_iterations:
            self.log.debug('RALLY: No Allowed Iterations found')
            return

        matching_iterations = [allowed_iteration for allowed_iteration in allowed_iterations
                               if allowed_iteration.Name == iteration]

        if not len(matching_iterations):
            self.log.debug(f'RALLY: Iteration {iteration} not in Allowed Iteration Values')
            return

        item.update({
            'Iteration': matching_iterations[0].ref
        })

    def set_owner(self, item, owner):
        owner = self.get_user(owner)
        if owner:
            item.update({
                'Owner': owner.ref
            })

    def set_submitted_by(self, item, submitter):
        submitter = self.get_user(submitter)
        if submitter:
            item.update({
                'SubmittedBy': submitter.ref
            })

    def create_item(self, item_type, item_data):
        if not self.is_testing:
            new_item = self.client.create(item_type, item_data)
            self.log.debug(f"RALLY CREATED {item_type}: {getattr(new_item, 'FormattedID', None)}")
        else:
            new_item = {
                'FormattedID': 'FAKE-123'
            }
            self.log.debug(f"RALLY CREATED {item_type}: {new_item['FormattedID']}")

        self.log.debug(f'    with values: {item_data}')
        return new_item

    def create_user_story(self, item_data):
        return self.create_item(RALLY_ITEM_TYPES['UserStory'], item_data)

    def create_defect(self, item_data):
        return self.create_item(RALLY_ITEM_TYPES['Defect'], item_data)

    def create_task(self, item_data):
        return self.create_item(RALLY_ITEM_TYPES['Task'], item_data)

    def update_item(self, item_type, item_data):
        if not self.is_testing:
            updated_item = self.client.update(item_type, item_data)
            self.log.debug(f"RALLY UPDATED {item_type}: {getattr(updated_item, 'FormattedID', None)}")
        else:
            updated_item = item_data
            self.log.debug(f"RALLY UPDATED {item_type}: {updated_item['FormattedID']}")

        self.log.debug(f'    with values: {item_data}')
        return updated_item

    def update_user_story(self, item_data):
        return self.update_item(RALLY_ITEM_TYPES['UserStory'], item_data)

    def update_defect(self, item_data):
        return self.update_item(RALLY_ITEM_TYPES['Defect'], item_data)

    def update_task(self, item_data):
        return self.update_item(RALLY_ITEM_TYPES['Task'], item_data)

    def add_milestones(self, item, milestones):
        allowed_milestones = self.get_allowed_milestones()

        valid_milestones = []
        for allowed_milestone in allowed_milestones:
            if allowed_milestone.Name in milestones:
                valid_milestones.append(allowed_milestone)

        if not self.is_testing:
            self.client.addCollectionItems(item, valid_milestones)

        self.log.debug(f'RALLY ADDING Milestones {valid_milestones}')

    def add_discussion(self, item, posts):
        oid = self.get_oid(item)

        for post in posts:
            discussion_data = {"Artifact": oid, "Text": post}
            if not self.is_testing:
                self.client.create('ConversationPost', discussion_data)

            self.log.debug(f'RALLY ADDING Discussion Post {post}')

    def add_attachment(self, item, attachment):
        # TODO (https://pyral.readthedocs.io/en/latest/interface.html#addAttachment)
        self.log.debug('RALLY ADDING Attachment')
def main(args):

    # Settings
    rally_server = 'rally1.rallydev.com'
    user_id = '*****@*****.**'
    rally_password = '******'
    my_workspace = 'My Workspace'
    my_project = 'My Project'
    jira_bugs_csv = 'jirabugs.csv'

    csvfile  = open(jira_bugs_csv, "rb")
    bugsreader = csv.reader(csvfile)

    # Rally Connection info
    rally = Rally(rally_server, user_id, rally_password, workspace=my_workspace, project=my_project)

    # Set logging
    rally.enableLogging('import_defects.log')

    # Get a handle to Rally Project
    proj = rally.getProject()

    # Jira Severity to Rally Severity Lookup
    jira2rally_sev_lookup = { 
                             "Critical" : "Crash/Data Loss",
                             "Severe" : "Crash/Data Loss",
                             "Major" : "Major Problem",
                             "Minor" : "Minor Problem","Cosmetic":"Cosmetic",
                             "Trivial" : "Cosmetic",
                             "None" : "Minor Problem" 
                             }

    # Column name to index mapping
    column_name_index_lookup = {0 : 'Summary',
                                1 : 'Priority',
                                2 : 'Severity',
                                3 : 'State',
                                4 : 'Description',
                                5 : 'Assignee',
                                6 : 'Submitter',
                                7 : 'RallyStoryID',
                                8 : 'JiraID'}

    # get the first user whose DisplayName is 'Mark Williams'
    user = rally.getUserInfo(name='Mark Williams').pop(0)

    rownum = 0
    for row in bugsreader:
        # Save header row.
        if rownum == 0:
            header = row
        else:
            print "Importing Defect count %d" % (rownum)
            colnum = 0
            for col in row:
                column_name = column_name_index_lookup[colnum]
                if column_name == "Summary":
                    summary = col
                elif column_name == "Priority":
                    priority = col
                elif column_name == "Severity":
                    severity = jira2rally_sev_lookup[col]
                elif column_name == "State":
                    state = col
                elif column_name == "Description":
                    desc = col
                elif column_name == "Assignee":
                    owner = rally.getUserInfo(name=col).pop(0)
                elif column_name == "Submitter":
                    submitted_by = rally.getUserInfo(name=col).pop(0)
                elif column_name == "RallyStoryID":
                    story_formatted_id = col
                elif column_name == "JiraID":
                    jira_id = col
                colnum += 1

            # Find the story to parent to in Rally
            query_criteria = 'FormattedID = "%s"' % story_formatted_id
            response = rally.get('Story', fetch=True, query=query_criteria)
            if response.errors:
                sys.stdout.write("\n".join(errors))
                sys.exit(1)
                
            defect_data = { 
                "Project"       : proj.ref,
                "Name"          : summary,
                "Priority"      : priority,
                "Severity"      : severity,
                "State"         : state,
                "Description"   : desc,
                "ScheduleState" : "Defined",
                "Owner"         : owner.ref,
                "SubmittedBy"   : submitted_by.ref,
                "JiraID"        : jira_id }

            if response.resultCount == 0:
                print "No Story %s found in Rally - No Parent will be assigned." % (story_formatted_id)
            else:
                print "Story %s found in Rally - Defect will be associated." % (story_formatted_id)
                story_in_rally = response.next()
                defect_data = { 
                    "Project"       : proj.ref,
                    "Name"          : summary,
                    "Priority"      : priority,
                    "Severity"      : severity,
                    "State"         : state,
                    "Description"   : desc,
                    "ScheduleState" : "Defined",
                    "Owner"         : owner.ref,
                    "SubmittedBy"   : submitted_by.ref,
                    "Requirement"   : story_in_rally.ref,
                    "JiraID"        : jira_id }
            try:
                defect = rally.create('Defect', defect_data)
            except Exception, details:
                sys.stderr.write('ERROR: %s \n' % details)
                sys.exit(1)
            print "Defect created, ObjectID: %s  FormattedID: %s" % (defect.oid, defect.FormattedID)
        rownum += 1
Пример #12
0
class AgileCentralConnection(BLDConnection):
    def __init__(self, config, logger):
        super().__init__(logger)
        self.internalizeConfig(config)
        self.log.info("Rally WSAPI Version %s" % self.rallyWSAPIVersion())
        self.integration_other_version = ""
        self.username_required = True
        self.password_required = True
        self.build_def = {
        }  # key by Project, then value is in turn a dict keyed by Job name with number and date
        # of last Build

    def name(self):
        return "AgileCentral"

    def version(self):
        global __version__
        return __version__

    # Rally Web Services version, do not modify unless tested!
    def rallyWSAPIVersion(self):
        return "v2.0"

    def getBackendVersion(self):
        """
            Conform to Connection subclass protocol which requires the version of 
            the system this instance is "connected" to.
        """
        return "Rally WSAPI %s" % self.rallyWSAPIVersion()

    def internalizeConfig(self, config):
        super().internalizeConfig(config)

        server = config.get('Server', 'rally1.rallydev.com')
        if 'http' in server.lower() or '/slm' in server.lower():
            self.log.error(
                self,
                "AgileCentral URL should be in the form 'rally1.rallydev.com'")
        self.server = server

        self.url = "https://%s/slm" % server
        self.port = config.get('Port', '443')
        self.apikey = config.get("APIKey", config.get("API_Key", None))
        self.workspace_name = config.get("Workspace", None)
        self.project_name = config.get(
            "Project", None)  # This gets bled in by the BLDConnector
        self.restapi_debug = config.get("Debug", False)
        self.restapi_logger = self.log

        self.proxy = None
        if self.proxy_server:
            self.proxy = "%s://%s:%s" % (self.proxy_protocol,
                                         self.proxy_server, self.proxy_port)
            if self.proxy_username and self.proxy_password:
                self.proxy = "%s://%s:%s@%s:%s" % (
                    self.proxy_protocol, self.proxy_username,
                    self.proxy_password, self.proxy_server, self.proxy_port)

        valid_config_items = [
            'Server', 'Port', 'APIKey', 'Workspace', 'Project', 'Username',
            'Password', 'ProxyProtocol', 'ProxyServer', 'ProxyPort',
            'ProxyUser', 'ProxyUsername', 'ProxyPassword', 'Debug', 'Lookback'
        ]
        invalid_config_items = [
            item for item in config.keys() if item not in valid_config_items
        ]
        if invalid_config_items:
            problem = "AgileCentral section of the config contained these invalid entries: %s" % ", ".join(
                invalid_config_items)
            raise ConfigurationError(problem)

    def validate(self):
        satisfactory = True

        if self.username_required:
            if not self.username and not self.apikey:
                self.log.error("<Username> is required in the config file")
                satisfactory = False
            #else:
            #    self.log.debug(
            #        '%s - user entry "%s" detected in config file' % (self.__class__.__name__, self.username))

        if self.password_required:
            if not self.password and not self.apikey:
                self.log.error("<Password> is required in the config file")
                satisfactory = False
            #else:
            #    self.log.debug('%s - password entry detected in config file' % self.__class__.__name__)

        return satisfactory

    def validateProjects(self, target_projects):
        """
            make requests to AgileCentral to retrieve basic info for each project in target_projects.
            If any project name in target_projects does NOT have a corresponding project in AgileCentral
            raise and Exception naming the offending project.
        """
        try:
            mep_projects = list(
                set([
                    project for project in target_projects if ' // ' in project
                ]))
        except Exception as msg:
            raise OperationalError(
                "%s %s" %
                ("error in agicen_bld_connection.py for getting mep_projects",
                 msg))
        try:
            non_mep_projects = list(
                set([
                    project for project in target_projects
                    if ' // ' not in project
                ]))
        except Exception as msg:
            raise OperationalError("%s %s" % (
                "error in agicen_bld_connection.py for getting non_mep_projects",
                msg))
        query = self._construct_ored_Name_query(non_mep_projects)
        response = self.agicen.get('Project',
                                   fetch='Name,ObjectID',
                                   query=query,
                                   workspace=self.workspace_name,
                                   project=None,
                                   projectScopeDown=True,
                                   pagesize=200)
        if response.errors or response.resultCount == 0:
            raise ConfigurationError(
                'Unable to locate a Project with the name: %s in the target Workspace: %s'
                % (self.project_name, self.workspace_name))

        found_projects = [project for project in response]
        try:
            found_project_names = list(set([p.Name for p in found_projects]))
        except Exception as msg:
            raise OperationalError("%s %s" % (
                "error in agicen_bld_connection.py for getting found_project_names",
                msg))
        bogus = [
            name for name in target_projects if name not in found_project_names
        ]
        try:
            real_bogus = set(bogus) - set(mep_projects)
        except Exception as msg:
            raise OperationalError(
                "%s %s" %
                ("error in agicen_bld_connection.py for getting real_bogus",
                 msg))
        if real_bogus:
            problem = "These projects mentioned in the config were not located in AgileCentral Workspace %s: %s" % (
                self.workspace_name, ",".join(real_bogus))
            self.log.error(problem)
            return False

        #deal with the mep_projects
        for mep in mep_projects:
            proj = self.agicen.getProject(mep)
            if proj:
                found_projects.append(proj)

        self._project_cache = {proj.Name: proj.ref for proj in found_projects}
        return True

    def _construct_ored_Name_query(self, target_projects):
        if not target_projects: return ''
        initial = '(Name = "%s")' % target_projects[0]
        or_string = initial
        for project in target_projects[1:]:
            or_string = '(%s OR (Name = "%s"))' % (or_string, project)
        return or_string

        #return "(%s)" % or_string[1:-1]

    def setSourceIdentification(self, other_name, other_version):
        self.other_name = other_name
        self.integration_other_version = other_version

    def get_custom_headers(self):
        custom_headers = {}
        custom_headers[
            'name'] = "AgileCentral Build Connector for %s" % self.other_name
        custom_headers['vendor'] = "CA Technologies"
        custom_headers['version'] = self.version
        if self.integration_other_version:
            spoke_versions = "%s - %s " % (self.version,
                                           self.integration_other_version)
            custom_headers['version'] = spoke_versions
        return custom_headers

    def connect(self):
        if self.proxy:
            self.log.info("Proxy for AgileCentral connection:  %s" %
                          self.proxy)
            os.environ['HTTPS_PROXY'] = self.proxy

        self.log.info("Connecting to AgileCentral")
        custom_headers = self.get_custom_headers()

        try:
            before = time.time()
            ##            print("")
            ##            print("before call to pyral.Rally(): %s    using workspace name: %s" % (before, self.workspace_name))
            ##            print("   credentials:  username |%s|  password |%s|  apikey |%s|" % (self.username, self.password, self.apikey))
            self.agicen = Rally(self.server,
                                user=self.username,
                                password=self.password,
                                apikey=self.apikey,
                                workspace=self.workspace_name,
                                project=self.project_name,
                                version=self.rallyWSAPIVersion(),
                                http_headers=custom_headers,
                                server_ping=False,
                                isolated_workspace=True,
                                logger=self.restapi_logger,
                                warn=False,
                                debug=True)
            after = time.time()
            ##            print("after  call to pyral.Rally(): %s" % after)
            ##            print("initial Rally connect elapsed time: %6.3f  seconds" % (after - before))
            ##            sys.stdout.flush()
            ##
            if self.restapi_debug:
                self.agicen.enableLogging('agicen_builds.log')
        except Exception as msg:
            raise ConfigurationError("Unable to connect to Agile Central at %s: %s" % \
                                         (self.server, msg))
        self.log.info("Connected to Agile Central server: %s" % self.server)

        ##        before = time.time()
        # verify the given workspace name exists
        ##        print("")
        ##        print("before call to agicen.getWorkspaces: %s" % before)
        all_workspaces = self.agicen.getWorkspaces()
        ##        after = time.time()
        ##        print("after  call to agicen.getWorkspaces: %s" % after)
        ##        print("agicen.getWorkspaces elapsed time: %6.3f  seconds" % (after - before))

        valid = [
            wksp for wksp in all_workspaces if wksp.Name == self.workspace_name
        ]
        if not valid:
            problem = "Specified Workspace: '%s' not in list of workspaces " + \
                      "available for your credentials as user: %s"
            raise ConfigurationError(problem %
                                     (self.workspace_name, self.username))
        self.log.info("    Workspace: %s" % self.workspace_name)
        self.log.info("    Project  : %s" % self.project_name)
        wksp = self.agicen.getWorkspace()
        prjt = self.agicen.getProject()
        self.workspace_ref = wksp.ref
        self.project_ref = prjt.ref

        # find all of the Projects under the AgileCentral_Project
        ##        before = time.time()
        ##        print("")
        ##        print("before call to agicen get Project: %s" % before)
        response = self.agicen.get('Project',
                                   fetch='Name',
                                   workspace=self.workspace_name,
                                   project=self.project_name,
                                   projectScopeDown=True,
                                   pagesize=200)
        if response.errors or response.resultCount == 0:
            raise ConfigurationError(
                'Unable to locate a Project with the name: %s in the target Workspace'
                % self.project_name)

        # detect any duplicate project names
        self.project_bucket = {}
        for proj in response:
            if proj.Name not in self.project_bucket:
                self.project_bucket[proj.Name] = 0
            self.project_bucket[proj.Name] += 1
        self.duplicated_project_names = [
            p for p, c in self.project_bucket.items() if c != 1
        ]

        project_names = [proj.Name for proj in response]
        ##        after = time.time()
        ##        print("after  call to agicen get Project: %s" % after)
        ##        print("agicen.get Project  elapsed time: %6.3f  seconds  for  %d Projects" % ((after - before), len(project_names)))
        ##        print("")
        self.log.info("    %d sub-projects" % len(project_names))

        return True

    def disconnect(self):
        """
            Just reset our agicen instance variable to None
        """
        self.agicen = None

    def set_integration_header(self, header_info):
        self.integration_name = header_info['name']
        self.integration_vendor = header_info['vendor']
        self.integration_version = header_info['version']
        if 'other_version' in header_info:
            self.integration_other_version = header_info['other_version']

    def getRecentBuilds(self, ref_time, projects):
        """
            Obtain all Builds created in Agile Central at or after the ref_time parameter
            (which is a struct_time object)
             in Python, ref_time will be a struct_time item:
               (tm_year, tm_mon, tm_mday, tm_hour, tm_min, tm_sec, tm_wday, tm_yday, tm_isdst)
        """
        ref_time_readable = time.strftime("%Y-%m-%d %H:%M:%S Z", ref_time)
        ref_time_iso = time.strftime("%Y-%m-%dT%H:%M:%SZ", ref_time)
        self.log.info("Detecting recently added Agile Central Builds")
        selectors = ['CreationDate >= %s' % ref_time_iso]
        log_msg = '   recent Builds query: %s' % ' and '.join(selectors)
        self.log.info(log_msg)
        builds = {}

        for project in projects:
            response = self._retrieveBuilds(project, selectors)
            log_msg = "  %d recently added Agile Central Builds detected for project: %s"
            self.log.info(log_msg % (response.resultCount, project))

            for build in response:
                build_name = build.BuildDefinition.Name
                if project not in builds:
                    builds[project] = {}
                if build_name not in builds[project]:
                    builds[project][build_name] = []
                builds[project][build_name].append(build)
        return builds

    def _retrieveBuilds(self, project, selectors):
        fetch_fields = "ObjectID,CreationDate,Number,Start,Status,Duration,BuildDefinition,Name," +\
                       "Workspace,Project,Uri,Message,Changesets"
        try:
            response = self.agicen.get('Build',
                                       fetch=fetch_fields,
                                       query=selectors,
                                       workspace=self.workspace_name,
                                       project=project,
                                       projectScopeDown=False,
                                       projectScopeUp=False,
                                       order="CreationDate",
                                       pagesize=1000)
        except Exception as msg:
            excp_type, excp_value, tb = sys.exc_info()
            mo = re.search(r"'(?P<ex_name>.+)'", str(excp_type))
            if mo:
                excp_type = mo.group('ex_name').replace('exceptions.', '')
                msg = '%s: %s\n' % (excp_type, str(excp_value))
            raise OperationalError(msg)

        # Examine the response to see if there is any content for the 'Errors' or 'Warnings' keys.
        # and raise an Exception in that case.
        if response.errors:
            raise Exception(response.errors[0][:80])
        if response.warnings:
            raise Exception(response.warnings[0][:80])

        return response

    def retrieveChangeset(self, sha):
        query = 'Revision = %s' % sha
        response = self.agicen.get('Changeset',
                                   fetch='ObjectID',
                                   query=query,
                                   workspace=self.workspace_name,
                                   project=None)
        # should we check for errors or warnings?
        if response.resultCount > 0:
            return response.next()
        return None

    def _fillBuildDefinitionCache(self, project):
        response = self.agicen.get('BuildDefinition',
                                   fetch='ObjectID,Name,Project,LastBuild,Uri',
                                   query='Name != "Default Build Definition"',
                                   workspace=self.workspace_name,
                                   project=project,
                                   projectScopeUp=False,
                                   projectScopeDown=False,
                                   order='Project.Name,Name')

        if response.errors:
            raise OperationalError(str(response.errors))

        for build_defn in response:
            ##
            #print("_fillBuildDefinitionCache:  BuildDefinition  Project: %s  JobName: %s" % \
            #        (build_defn.Project.Name, build_defn.Name))
            ##
            job_name = build_defn.Name
            if not project in self.build_def:
                self.build_def[project] = {}
            self.build_def[project][job_name] = build_defn

    def prepAgileCentralBuildPrerequisites(self, job, target_build, project):
        """
            Given a target_build which has information about a build that has been completed on
             some target build system, accommodate/create the following:
                SCMRepository    based on the target_build.repo value
                Changesets       based on the target_build.changeSets
                BuildDefinition  based on the target_build.job  (which is the job name)
             Any or all or none of these things may already be present in AgileCentral, and if
             not create what is necessary.
             Return back the SCMRepository, Changesets, and BuildDefinition.
             These will be a  pyral entity for each or a list of pyral entities in the case of Changesets
        """
        changesets = None
        if target_build.changeSets:
            ac_changesets, missing_changesets = self.getCorrespondingChangesets(
                target_build)
            # check for ac_changesets, if present take the SCMRepository of the first in the list (very arbitrary!)
            if ac_changesets:
                first_changeset = list(ac_changesets)[0]
                criteria = 'Name = "%s"' % first_changeset.SCMRepository.Name
                scm_repo = self.agicen.get('SCMRepository',
                                           fetch="Name",
                                           query=criteria,
                                           instance=True)
            else:
                scm_repo = self.ensureSCMRepositoryExists(
                    target_build.repository, target_build.vcs)

            changesets = self.ensureChangesetsExist(scm_repo, project,
                                                    ac_changesets,
                                                    missing_changesets)

        build_defn = self.ensureBuildDefinitionExists(
            job.fully_qualified_path(), project, target_build.vcs)
        return changesets, build_defn

    def getCorrespondingChangesets(self, build):
        build_changesets = build.changeSets
        if not build_changesets:
            return [], []
        ids = [cs.commitId for cs in build_changesets]
        scm_repository_name = ''
        for id in ids:
            criteria = '(Revision = "{0}")'.format(id)
            response = self.agicen.get(
                'Changeset',
                fetch="ObjectID,Revision,SCMRepository,Name",
                query=criteria)
            if response.resultCount:
                hits = [item for item in response]
                scm_repository_name = hits[0].SCMRepository.Name
                break

        if scm_repository_name:
            fields = "ObjectID,Revision,SCMRepository,Name"
            query = "(SCMRepository.Name = {})".format(scm_repository_name)

            response = self.agicen.get('Changeset',
                                       fetch=fields,
                                       query=query,
                                       order="CreationDate",
                                       pagesize=200)
            if response.resultCount:
                changesets = [item for item in response]

            build_changesets_revisions = [
                bc.commitId for bc in build_changesets
            ]
            ac_changesets_revisions = [cs.Revision for cs in changesets]

            try:
                missing = [
                    bc for bc in build_changesets
                    if bc.commitId not in ac_changesets_revisions
                ]
            except Exception as msg:
                raise OperationalError(
                    "%s %s" %
                    ("error in agicen_bld_connection.py for getting missing",
                     msg))
            try:
                present = [
                    cs for cs in changesets
                    if cs.Revision in build_changesets_revisions
                ]
            except Exception as msg:
                raise OperationalError(
                    "%s %s" %
                    ("error in agicen_bld_connection.py for getting present",
                     msg))
            return present, missing
        else:
            vcs_type = build_changesets[0].vcs
            self.ensureSCMRepositoryExists(build.repository, vcs_type)
            return [], build_changesets

    def ensureSCMRepositoryExists(self, repo_name, vcs_type):
        """
            Use the WSAPI case-insensitive Name contains ... syntax so that we can "match" a name like 'wombat' to 'Wombat'
            in case the users have manually already created a 'Wombat' SCMRepository either manually or via VCS Connector
        """
        repo_name = repo_name.replace('\\', '/')
        name = repo_name.split('/')[-1]
        criteria = '(Name contains "{0}")'.format(name)
        response = self.agicen.get('SCMRepository',
                                   fetch="Name,ObjectID",
                                   query=criteria,
                                   project=None)
        if response.resultCount:
            scm_repos = [item for item in response]
            exact_matches = [
                scm_repo for scm_repo in scm_repos
                if scm_repo.Name.lower() == repo_name.lower()
            ]
            if exact_matches:
                return exact_matches[0]

        scm_repo_payload = {'Name': repo_name, 'SCMType': vcs_type}
        try:
            scm_repo = self.agicen.create('SCMRepository', scm_repo_payload)
            self.log.info("Created SCMRepository %s (%s)" %
                          (scm_repo.Name, scm_repo.ObjectID))
        except RallyRESTAPIError as msg:
            self.log.error(
                'Error detected on attempt to create SCMRepository %s' % msg)
            raise OperationalError("Could not create SCMRepository %s" %
                                   repo_name)

        return scm_repo

    def ensureChangesetsExist(self, scm_repo, project, ac_changesets,
                              missing_changesets):
        all_mentioned_artifact_fids = {
            mc.commitId: self.parseForArtifacts(mc.message)
            for mc in missing_changesets
        }
        valid_artifact_fids = self.validatedArtifacts(
            all_mentioned_artifact_fids)
        for mc in missing_changesets:
            valid_artifacts = valid_artifact_fids[mc.commitId]
            changeset_payload = {
                'SCMRepository':
                scm_repo.ref,
                'Revision':
                mc.commitId,
                'CommitTimestamp':
                datetime.utcfromtimestamp(mc.timestamp /
                                          1000).strftime('%Y-%m-%dT%H:%M:%SZ'),
                'Message':
                mc.message,
                'Uri':
                mc.uri,
                'Artifacts':
                valid_artifacts
            }
            try:
                changeset = self.agicen.create('Changeset', changeset_payload)
                self.log.debug("Created Changeset %s" % changeset.ObjectID)
            except RallyRESTAPIError as msg:
                self.log.error(
                    'Error detected on attempt to create Changeset %s' % msg)
                raise OperationalError("Could not create Changeset  %s" % msg)
            ac_changesets.append(changeset)

        return ac_changesets

    def parseForArtifacts(self, commit_message):
        prefixes = [
            prefix for item in utils.get_all_prefixes(self.agicen)
            for prefix in item.values()
        ]
        fid_pattern = r'((%s)\d+)' % '|'.join(prefixes)
        result = re.findall(fid_pattern, commit_message, re.IGNORECASE)
        return [item[0].upper() for item in result]

    def validatedArtifacts(self, commit_fid):
        # commit_fid is a dict
        # commit_fid = {commitID: [S123,DE123], ...}
        try:
            fids = list(
                set([fid for fids in commit_fid.values() for fid in fids]))
        except Exception as msg:
            self.log.error(
                "Cannot get a list of formatted ids of artifacts in the commit messages, in validatedArtfacts"
            )
            raise OperationalError(msg)
        query = self.makeOrQuery("FormattedID", fids)
        response = self.agicen.get('Artifact',
                                   fetch="FormattedID,ObjectID",
                                   query=query,
                                   project=None,
                                   pagesize=200,
                                   start=1)
        found_arts = [art for art in response]
        va = {}
        for ident in commit_fid.keys():
            va[ident] = []
            matches = [
                found_art for found_art in found_arts
                if found_art.FormattedID in commit_fid[ident]
            ]
            if matches:
                va[ident] = matches
                # for art in matches:
                #     if art.Project.oid != targeted_project.oid:
                #         self.log.warning('')
        return va

    def makeOrQuery(self, field, values):
        if not values:
            return None

        values = list(values)
        query = '(%s = "%s")' % (field, values[0])
        if len(values) == 1:
            return query
        else:
            values.pop(0)
            for v in values:
                query = '(%s OR (%s = "%s"))' % (query, field, v)
            return query

    def ensureBuildDefinitionExists(self, job_path, project, job_uri):
        """
            use the self.build_def dict keyed by project at first level, job_path at second level
            to determine if the job_path has a BuildDefinition for it.

            Returns a pyral BuildDefinition instance corresponding to the job (and project)
        """
        # front-truncate the job_path if the string length of that is > 256 chars
        if (len(job_path) > 256):
            job_path = job_path[-256:]
        if project in self.build_def and job_path in self.build_def[project]:
            return self.build_def[project][job_path]

        # do we have the BuildDefinition cache populated?  If not, do it now...
        if project not in self.build_def:  # to avoid build definition duplication
            self.log.debug(
                "Detected build definition cache for the project: {} is empty, populating ..."
                .format(project))
            self._fillBuildDefinitionCache(project)

        # OK, the job_path is not in the BuildDefinition cache
        # so look in the BuildDefinition cache to see if the job_path exists for the given project
        if project in self.build_def:
            if job_path in self.build_def[project]:
                return self.build_def[project][job_path]

        target_project_ref = self._project_cache[project]

        bdf_info = {
            'Workspace': self.workspace_ref,
            'Project': target_project_ref,
            'Name': job_path,
            'Uri':
            job_uri  #something like {base_url}/job/{job} where base_url comes from other spoke conn
        }
        try:
            self.log.debug(
                "Creating a BuildDefinition for job '%s' in Project '%s' ..." %
                (job_path, project))
            build_defn = self.agicen.create('BuildDefinition',
                                            bdf_info,
                                            workspace=self.workspace_name,
                                            project=project)
        except Exception as msg:
            self.log.error(
                "Unable to create a BuildDefinition for job: '%s';  %s" %
                (job_path, msg))
            raise OperationalError(
                "Unable to create a BuildDefinition for job: '%s';  %s" %
                (job_path, msg))

        # Put the freshly minted BuildDefinition in the BuildDefinition cache and return it
        if project not in self.build_def:
            self.build_def[project] = {}

        self.build_def[project][job_path] = build_defn
        return build_defn

    def preCreate(self, int_work_item):
        """
        # transform the CommitTimestamp from epoch seconds into ISO-8601'ish format
        #timestamp = int_work_item['CommitTimestamp']
        #iso8601_ts = time.strftime(ISO8601_TS_FORMAT, time.gmtime(timestamp))
        #int_work_item['CommitTimestamp'] = iso8601_ts

        # BuildDefinition has to be a ref
        # Number  is a string
        # Status  is a string
        # get Start in to iso8601 format
        # Duration is in seconds.milliseconds format
        # Uri is link to the originating build system job number page
        """

        int_work_item['Workspace'] = self.workspace_ref
        int_work_item['BuildDefinition'] = int_work_item['BuildDefinition'].ref

        if int_work_item.get('Changesets', False):
            collection_payload = [{
                '_ref': "changeset/%s" % changeset.oid
            } for changeset in int_work_item['Changesets']]
            int_work_item['Changesets'] = collection_payload

        return int_work_item

    def _createInternal(self, int_work_item):

        # snag the base Uri from the int_work_item['Uri'] burning off any ending '/' char
        base_uri = int_work_item['Uri']
        base_uri = base_uri[:-1] if base_uri and base_uri.endswith(
            '/') else base_uri

        try:
            build = self.agicen.create('Build', int_work_item)
            self.log.debug("  Created Build: %-90.90s #%5s  %-8.8s %s" %
                           (build.BuildDefinition.Name, build.Number,
                            build.Status, build.Start))
        except Exception as msg:
            print(
                "AgileCentralConnection._createInternal detected an Exception, {0}"
                .format(sys.exc_info()[1]))
            excp_type, excp_value, tb = sys.exc_info()
            mo = re.search(r"'(?P<ex_name>.+)'", str(excp_type))
            if mo:
                excp_type = mo.group('ex_name').replace('exceptions.', '')
                msg = '%s: %s\n' % (excp_type, str(excp_value))
            raise OperationalError(msg)

        return build

    def buildExists(self, build_defn, number):
        """
            Issue a query against Build to obtain the Build identified by build_defn.ObjectID and number.
            Return a boolean indication of whether such an item exists.
        """
        criteria = [
            'BuildDefinition.ObjectID = %s' % build_defn.ObjectID,
            'Number = %s' % number
        ]
        response = self.agicen.get(
            'Build',
            fetch="CreationDate,Number,Name,BuildDefinition,Project",
            query=criteria,
            workspace=self.workspace_name,
            project=None)

        if response.resultCount:
            return response.next()
        return None

    # def populateChangesetsCollectionOnBuild(self, build, changesets):
    #     csrefs = [{ "_ref" : "changeset/%s" % cs.oid} for cs in changesets]
    #     cs_coll_ref = build.Changesets
    #     #self.agicen.addCollection(cs_coll_ref, csrefs)

    def matchToChangesets(self, vcs_commits):
        valid_changesets = []
        for commit in vcs_commits:
            query = ('Revision = "%s"' % commit)
            response = self.agicen.get("Changeset",
                                       fetch="ObjectID",
                                       query=query,
                                       workspace=self.workspace_name,
                                       project=None)
            if response.resultCount > 0:
                changeset = response.next()
                valid_changesets.append(changeset)
        return valid_changesets
Пример #13
0
class RallyUtil(object):
    def __init__(self, RallyServer='rally1.rallydev.com', \
                 APIKep='_fONhEFO2RHW87EMPIc4II1yY1fx4PEmtgpW85jMq7eI', \
                 workspace='MicroStrategy Workspace', project='SR-Intelligence-Kernel', \
                 loggingfile='Rally.log'):
        self.rally = Rally(server=RallyServer,
                           apikey=APIKep,
                           workspace=workspace,
                           project=project,
                           projectScopeDown=True)
        self.rally.enableLogging(loggingfile)

    def get_testcase_by_id(self, testcase_id):
        return self._get_entity_by_id(testcase_id)

    def get_testset_by_id(self, testset_id):
        return self._get_entity_by_id(testset_id)

    def set_testcase_name(self, testcase_id, testcase_name):
        self._set_entity(testcase_id, Name=testcase_name)

    def _get_entity_by_id(self, entity_id):
        entity_type = self._get_entity_type(entity_id)
        query = 'FormattedID = {}'.format(entity_id)
        return self.rally.get(entity_type,
                              query=query,
                              project=None,
                              instance=True)

    def get_defect_by_id(self, defect_id):
        return self._get_entity_by_id(defect_id)

    def _set_entity(self, entity_id, **info):
        entity_type = self._get_entity_type(entity_id)
        info['FormattedID'] = entity_id
        logger.debug(info)
        self.rally.update(entity_type, info)

    def get_object_by_id(self, type, oid):
        response = self.rally.get(type,
                                  fetch=True,
                                  query='ObjectID = ',
                                  workspace=workspace,
                                  project=project)

    def get_alluserinfo(self):
        return self.rally.getAllUsers()

    def get_userstorys_by_product_release(self, *product_releases):

        query = 'c_ProductionRelease = "11.1.2 [2019-Jun-7]" OR c_ProductionRelease = "11.2 RC [2019-Sep-20]" OR c_ProductionRelease = "11.2 EA [2019-Jun-21]"'
        # query = 'c_ProductionRelease = "11.1.2 [2019-Jun-7]"'
        response = self.rally.get('UserStory',
                                  query=query,
                                  fetch=True,
                                  projectScopeDown=True)

        userstorys = []
        for entry in response:
            logger.debug(entry.FormattedID, entry.Name)
            userstorys.append(entry)

        return userstorys

    def create_testcase(self, testcase_name, testfolder_id, status, test_type,
                        owner):
        # note, owner should be Email address, e.g. [email protected]

        testcase_json = {
            "Workspace": self.rally.getWorkspace().ref,
            "Project": self.rally.getProject().ref,
            "Name": testcase_name,
            "TestFolder": self.get_testfolder(testfolder_id)._ref,
            "Method": "Manual",
            "Type": test_type,
            "Priority": "Useful",
            "Risk": "Low",
            "c_TestCaseStatus": status,
            "c_UPCComponent": "017 Tool : Administration",
            "c_UPCModule": "017.000 Administration",
            "Owner": self.rally.getUserInfo(username=owner)[0].ref
        }
        testcase = self.rally.create('TestCase', testcase_json)
        logger.debug(testcase.details())
        return testcase

    def create_testset(self, testsetName, iterationName, stage, test_type,
                       owner):
        # note, owner should be Email address, e.g. [email protected]

        query = 'Name = "%s"' % iterationName
        iteration = self.rally.get('Iteration',
                                   fetch=True,
                                   query=query,
                                   projectScopeDown=True)
        # workspace=workspace, project=project)
        iteration = next(iteration, None)
        if not iteration:
            logging.error(f"{create_testset.__name__} get iteration fail")
            return

        testset_json = {
            "Workspace": self.rally.getWorkspace().ref,
            "Project": self.rally.getProject().ref,
            "Name": testsetName,
            "c_Result": "Unexecuted",
            "c_Stage": stage,
            "c_Type": test_type,
            "Iteration": iteration.ref,
            # "c_UPCComponent": "017 Tool : Administration",
            # "c_UPCModule": "017.000 Administration",
            "Owner": self.rally.getUserInfo(name=owner)[0].ref
        }
        testset = self.rally.create('TestSet', testset_json)
        logger.debug(testset.details())
        return testset.FormattedID

    def create_testcaseFromDefect(self, defect, owner=None, **testcase_json):
        # note, owner should be corp user name, such as "Jine Wang"
        # ident_query = 'FormattedID = "%s"' % defectID
        # workproduct = self.rally.get('Defect', fetch=True, query=ident_query, projectScopeDown=True)
        # # workproduct = self.rally.get('Defect', fetch=True, query=ident_query, workspace='current', project='current')
        # # self.rally.get.get(entity_name, fetch=True, query=ident_query, workspace=workspace, project=project)
        # defect = workproduct.next()

        testcase_json["Workspace"] = self.rally.getWorkspace().ref
        testcase_json["Project"] = self.rally.getProject().ref
        testcase_json[
            "Name"] = defect.FormattedID + " | " + defect.c_ProductionRelease.split(
            )[0] + " | " + defect.Name
        testcase_json["Description"] = defect.Description
        testcase_json["Owner"] = defect.Owner._ref
        testcase_json["WorkProduct"] = defect._ref
        testcase_json["Risk"] = "Low"
        testcase_json["c_UPCModule"] = defect.c_UPCModule
        testcase_json["c_UPCComponent"] = defect.c_UPCComponent
        testcase_json["c_TestCaseStatus"] = "Active"
        testcase_json["Type"] = "Regression"

        testcase = self.rally.create('TestCase', testcase_json)
        print(testcase.FormattedID, defect.FormattedID)
        return testcase

    def gettestcasesFromDefect(self, defect):
        result = []
        testcases = self.rally.getCollection(
            "https://rally1.rallydev.com/slm/webservice/v2.0/Defect/" +
            str(defect.oid) + "/TestCases")
        if testcases.errors:
            logging.error(f"{defect.FormattedID} Error in getting test cases")
        else:
            flag = True
            for testcase in testcases:
                flag = False
                result.append(testcase.FormattedID)
                logging.info(
                    f'{defect.FormattedID} {testcase.FormattedID} {testcase.c_TestCaseStatus} {testcase.Method}'
                )
        if flag:
            logging.info(f'         {defect.FormattedID} has no test case')
        return result

    def get_testcases_from_testfolder(self, testfolder_id):
        # query critial to get all active test cases
        query = 'TestFolder.FormattedID = "%s"' % testfolder_id
        response = self.rally.get('TestCase',
                                  query=query,
                                  fetch=True,
                                  projectScopeDown=True)

        test_cases = []
        for entry in response:
            logger.debug(entry.FormattedID, entry.Name)
            test_cases.append(entry)

        return test_cases

    def get_testcases_from_testset(self, testset_id):
        # query critial to get all active test cases
        query = 'TestSet.FormattedID = "%s"' % testset_id
        response = self.rally.get('TestCase',
                                  query=query,
                                  fetch=True,
                                  projectScopeDown=True)
        # get('TestSet', fetch=True, query='FormattedID = "%s"' % ts)
        test_cases = []
        for entry in response:
            logger.debug(entry.FormattedID, entry.Name)
            test_cases.append(entry)

        return test_cases

    def delete_all_testcases_in_testfolder(self, testfolder_id):
        for item in self.get_testcases_from_testfolder(
                testfolder_id=testfolder_id):
            logger.info('deleting testcase {} {}'.format(
                item.FormattedID, item.Name))
            self.rally.delete(entityName='TestCase',
                              itemIdent=item.FormattedID,
                              project='current',
                              workspace='current')

    def get_defects_by_date(self, defect_id, start_date, end_date):
        query = 'Iteration.StartDate > "{}" AND Iteration.StartDate < "{}"'.format(
            start_date, end_date)
        response = self.rally.get('Defect',
                                  fetch=True,
                                  query=query,
                                  projectScopeDown=True)
        defects = []
        for item in response:
            logger.debug('{},{}'.format(item.FormattedID, item.Name))
            defects.append(item)

    def get_defects_with_query(self, query):
        response = self.rally.get('Defect',
                                  fetch=True,
                                  query=query,
                                  projectScopeDown=True)
        defects = []
        for item in response:
            logger.debug('{},{}'.format(item.FormattedID, item.Name))
            defects.append(item)

        return defects

    def set_defect(self, defect_id, **info):
        self._set_entity(defect_id, **info)

    def add_testresult(self, testcase_id, **info):
        # logger.info('adding test result to {}'.format(testcase_id))
        info['TestCase'] = self.get_testcase_by_id(testcase_id).ref
        if not 'date' in info:
            info['date'] = str(datetime.datetime.utcnow().isoformat())
        if not 'tester' in info:
            info['tester'] = self.get_testcase_by_id(testcase_id).Owner.ref
        if not 'Verdict' in info:
            info['Verdict'] = 'Unexecuted'
        if not 'c_ProductionRelease' in info:
            info['c_ProductionRelease'] = '11.1.2 [2019-Jun-7]'
        if not 'Build' in info:
            info['Build'] = '0.0.0.0'
        if not 'Notes' in info:
            info['Notes'] = 'this result was added by script automatically'

        # self.rally.create('TestCaseResult', info)
        try:
            self.rally.create('TestCaseResult', info)
            logger.info('test case result for test case {} is added'.format(
                testcase_id))
        except Exception as re:
            logger.error('Fail to update test case {}, {}'.format(
                testcase_id, re))

    def add_testcasetotestset(self, testset_id, testcase_id):
        entity = self._get_entity_by_id(testset_id)
        testcasesList = []
        testcase = self.get_testcase_by_id(testcase_id)
        testcasesList.append(testcase)

        try:
            self.rally.addCollectionItems(entity, testcasesList)
            logger.info('adding test case {} to test set {}'.format(
                testcase.FormattedID, testset_id))
        except Exception as re:
            logger.error('Fail to add test case {} to test set {}: {}'.format(
                testcase_id, testset_id, re))

    def add_testcasetotestfolder(self, testfolder_id, testcase_id):
        entity = self._get_entity_by_id(testfolder_id)
        testcasesList = []
        testcase = self.get_testcase_by_id(testcase_id)
        testcasesList.append(testcase)

        try:
            self.rally.addCollectionItems(entity, testcasesList)
            logger.info('adding test case {} to test set {}'.format(
                testcase.FormattedID, testfolder_id))
        except Exception as re:
            logger.error('Fail to add test case {} to test set {}: {}'.format(
                testcase_id, testfolder_id, re))

    def add_defectstouserstory(self, userstory_id, defect_id):
        entity = self._get_entity_by_id(userstory_id)
        testcasesList = []
        defect = self.get_testcase_by_id(defect_id)
        testcasesList.append(defect)

        try:
            self.rally.addCollectionItems(entity, testcasesList)
            logger.info('adding test case {} to test set {}'.format(
                defect_id.FormattedID, userstory_id))
        except Exception as re:
            logger.error('Fail to add test case {} to test set {}: {}'.format(
                defect_id, userstory_id, re))

    def copyTestSet(self, sourcetestset_id, c_ProductionRelease, c_Stage):
        entity = self._get_entity_by_id(sourcetestset_id)
        # ident_query = 'FormattedID = "%s"' % sourcetestset_id
        # testset = self.rally.get('TestSet', fetch=True, query=ident_query, instance=True)
        # testset = testset.next()

        # querytestcase = 'TestCase.FormattedID = "%s"' % testcase_id
        # testcaseDiscussion = self.rally.get('ConversationPost', query=querytestcase, instance=True)

        # info = {}
        # info['c_ProductionRelease'] = c_ProductionRelease
        # info['Tasks'] = entity.Tasks
        # info['Owner'] = entity.Owner
        # info['Description'] = entity.Description
        # info['Name'] = entity.Name
        # info['Project'] = entity.Project
        # info['TestCases'] = entity.TestCases
        # info['c_Stage'] = c_Stage

        # testset_json = {
        #     "Workspace" : self.rally.getWorkspace().ref,
        #     "Project" : self.rally.getProject().ref,
        #     "Name" : entity.Name,
        #     "Description" : entity.Description,
        #     "c_ProductionRelease" : c_ProductionRelease,
        #     "Tasks" : entity.Tasks['_ref'],
        #     "Owner" : entity.Owner['_ref'],
        #     "TestCases" : entity.TestCases['_ref'],
        #     "c_Stage" : c_Stage,
        #     # "c_UPCComponent" : "017 Tool : Administration",
        #     # "c_UPCModule" : "017.000 Administration",
        #     # "Owner" : self.rally.getUserInfo(username=owner)[0].ref
        # }

        # taskList = testset.__collection_ref_for_Tasks
        # testset.__getattr__(__collection_ref_for_Tasks)

        testset_json = {
            "Workspace": self.rally.getWorkspace().ref,
            "Project": self.rally.getProject().ref,
            "Name": entity.Name,
            "Description": entity.Description,
            "c_ProductionRelease": c_ProductionRelease,
            # "Tasks" : testset.__collection_ref_for_Tasks,
            # "Owner" : testset.Owner['_ref'],
            # "TestCases" : __collection_ref_for_TestCases,
            "c_Stage": c_Stage,
            # "c_UPCComponent" : "017 Tool : Administration",
            # "c_UPCModule" : "017.000 Administration",
            # "Owner" : self.rally.getUserInfo(username=owner)[0].ref
        }
        testset = self.rally.create('TestSet', testset_json)
        logger.info('create test set {}'.format(testset.FormattedID))

        ident_query = 'TestSet.FormattedID = "%s"' % sourcetestset_id
        testcaseResponse = self.rally.get('TestCase',
                                          fetch=True,
                                          query=ident_query,
                                          instance=True)

        # testcaseList = []
        for testcase in testcaseResponse:
            testcaseList = []
            testcaseList.append(testcase)
            try:
                responses = self.rally.addCollectionItems(
                    testset, testcaseList)
                logger.info('adding test case {} to test set {}'.format(
                    testcase.FormattedID, testset.FormattedID))
            except Exception as re:
                logger.error(
                    'Fail to add test case {} to test set {}: {}, {}'.format(
                        testcase.FormattedID, testset.FormattedID, re,
                        responses.errors))

        # ident_querytask = 'TestSet.FormattedID = "%s"' % sourcetestset_id
        # taskResponse = self.rally.get('Task', fetch=True, query=ident_querytask, instance=True)

        taskList = entity.Tasks
        responses = self.rally.addCollectionItems(testset, taskList)

        # for task in taskResponse :
        #     taskList = []
        #     taskList.append(task)
        #     try :
        #         responses = self.rally.addCollectionItems(testset, taskList)
        #         logger.info('adding test case {} to test set {}'.format(task.FormattedID, testset.FormattedID))
        #     except Exception as re :
        #         logger.error(
        #             'Fail to add test case {} to test set {}: {}, {}'.format(task.FormattedID, testset.FormattedID,
        #                                                                      re, responses.errors))

        print(testcase)
        logger.debug(testcase.details())
        # print(info)
        # ident_query = 'TestSet.FormattedID = "%s"' % sourcetestset_id

        # testcaseResponse = rallyUtil.rally.get('TestCase', fetch=True, query=ident_query, project='current', workspace='current')
        #
        # testcaseList = []
        # for testcase in testcaseResponse:
        #     testcaseList.append(testcase)
        #     try :
        #         self.rally.addCollectionItems(entity, testcasesList)
        #         logger.info('adding test case {} to test set {}'.format(testcase.FormattedID, testset_id))
        #     except Exception as re :
        #         logger.error('Fail to add test case {} to test set {}: {}'.format(testcase_id, testset_id, re))

    def remove_testcasesfromtestset(self, testset_id, testcase_id):
        # querytestcase = 'TestCase.FormattedID = "%s"' % testcase_id
        # testcase = self.rally.get('TestCase', query=querytestcase, instance=True)
        #
        #
        # testcasesList = []
        # testcasesList.append(testcase.next())
        # self.rally.dropCollectionItems(self._get_entity_by_id(testset_id), testcasesList)
        # logger.info('remove testcase {} from testset {}'.format(testcase.FormattedID, testset_id))
        entity = self._get_entity_by_id(testset_id)
        testcasesList = []
        testcase = self.get_testcase_by_id(testcase_id)
        testcasesList.append(testcase)

        try:
            self.rally.dropCollectionItems(entity, testcasesList)
            logger.info('remove testcase {} from testset {}'.format(
                testcase.FormattedID, testset_id))
        except Exception as re:
            logger.error(
                'Fail to remove test case {} from test set {}: {}'.format(
                    testcase.FormattedID, re))

    def remove_testset(self, testset_id, osList):
        # querytestcase = 'TestCase.FormattedID = "%s"' % testcase_id
        # testcase = self.rally.get('TestCase', query=querytestcase, instance=True)
        #
        #
        # testcasesList = []
        # testcasesList.append(testcase.next())
        # self.rally.dropCollectionItems(self._get_entity_by_id(testset_id), testcasesList)
        # logger.info('remove testcase {} from testset {}'.format(testcase.FormattedID, testset_id))
        entity = self._get_entity_by_id(testset_id)
        list = entity.Name.split('|')
        if list[1] not in osList:
            self.rally.delete("TestSet", entity.oid)
            logger.info('remove testset {}'.format(testset_id))
        # testcasesList = []
        # testcase = self.get_testcase_by_id(testcase_id)
        # testcasesList.append(testcase)
        #
        # try :
        #     self.rally.dropCollectionItems(entity, testcasesList)
        #     logger.info('remove testcase {} from testset {}'.format(testcase.FormattedID, testset_id))
        # except Exception as re :
        #     logger.error('Fail to remove test case {} from test set {}: {}'.format(testcase.FormattedID, re))

    # def add_testresult(self, testcase_id, **info):
    #     logger.info('adding test result to {}'.format(testcase_id))
    #     info['TestCase'] = self.get_testcase_by_id(testcase_id).ref
    #     if not info.has_key('date'):
    #         info['date'] = str(datetime.datetime.utcnow().isoformat())
    #     if not info.has_key('tester'):
    #         info['tester'] = self.get_testcase_by_id(testcase_id).Owner.ref
    #     if not info.has_key('Verdict'):
    #         info['Verdict'] = 'Unexecuted'
    #     if not info.has_key('c_ProductionRelease'):
    #         info['c_ProductionRelease'] = '11.1.2 [2019-Jun-7]'
    #     if not info.has_key('Build'):
    #         info['Build'] = '0.0.0.0'
    #     if not info.has_key('note'):
    #         info['Notes'] = 'this result was added by script automatically'
    #
    #     self.rally.create('TestCaseResult', info)

    def _get_all_tags(self):
        response = self.rally.get('Tag',
                                  fetch="true",
                                  order="Name",
                                  server_ping=False,
                                  isolated_workspace=True)
        return [tag for tag in response]

    def _get_tag_by_name(self, tag_name):
        query = 'Name = "{}"'.format(tag_name)
        response = self.rally.get('Tag',
                                  fetch="true",
                                  query=query,
                                  server_ping=False,
                                  isolated_workspace=True)
        if response.resultCount != 0:
            tag = response.next()
            logger.debug('find tag {}'.format(tag.Name))
            return tag
        else:
            logger.error('can not find Tag with name {}'.format(tag_name))

    def get_tags_by_names(self, *tag_names):
        tags = []
        for tag_name in tag_names:
            tags.append(self._get_tag_by_name(tag_name))

        return tags

    def get_all_tag_names(self):
        return [tag.Name for tag in self._get_all_tags()]

    def create_tag(self, tag_name):
        tag_json = {'Name': tag_name}
        tag = self.rally.create('Tag', tag_json)
        return tag

    # somehow is not working
    def del_tag_by_name(self, tag_name):

        tag = self.get_tag_by_name(tag_name)
        self.rally.delete('Tag', itemIdent=self._get_tag_by_name(tag_name).oid)

    def remove_tags_from_testcase(self, testcase_id, tags):
        self.rally.dropCollectionItems(self.get_testcase_by_id(testcase_id),
                                       self.get_tags_by_names(tags))

    # archive tag will hide the tag from drop down list on rally, it is working..
    def archive_tag(self, tag_name):
        tag = self.get_tag_by_name(tag_name)
        info = {"ObjectID": tag.oid, "Archived": True}
        self.rally.update('Tag', info)

    def add_tags_to_entity(self, entity_id, *tag_names):
        entity = self._get_entity_by_id(entity_id)

        tags = []
        for tag_name in tag_names:
            tags.append(self._get_tag_by_name(tag_name))

        logger.info('adding tags {} to {}'.format(tag_names, entity_id))
        self.rally.addCollectionItems(entity, tags)

    def _get_entity_type(self, entity_id):
        if entity_id.startswith('TC'):
            entity_type = 'TestCase'
        elif entity_id.startswith('US'):
            entity_type = 'UserStory'
        elif entity_id.startswith('DE'):
            entity_type = 'Defect'
        elif entity_id.startswith('TS'):
            entity_type = 'TestSet'
        elif entity_id.startswith('TF'):
            entity_type = 'TestFolder'

        return entity_type

    def _update_entity(self, entity_id, **info):
        entity_type = self._get_entity_type(entity_id)
        info['FormattedID'] = entity_id
        logger.debug('{},{}'.format(entity_type, info))
        self.rally.update(entity_type, info)

    # def update_defect(self, entity_id, **info):
    #     self._update_entity(entity_id = entity_id, info=info)

    def active_testcase(self, testcase_id):
        self._update_entity(testcase_id, c_TestCaseStatus='Active')

    def active_all_testcases_in_testfolder(self, testfolder_id):
        for item in self.get_testcases_from_testfolder(
                testfolder_id=testfolder_id):
            logger.info('activing test case {} {}'.format(
                item.FormattedID, item.Name))
            self.active_testcase(item.FormattedID)

    def set_testcase_type(self, testcase_id, testcase_type):
        self._update_entity(testcase_id, Type=testcase_type)

    def update_testcase_owner(self, testcase_id, owner):
        tc = self.get_testcase_by_id(testcase_id)
        self.testCasefields = {}
        self.testCasefields['ObjectID'] = tc.oid
        self.testCasefields['Owner'] = self.get_user_ref_by_owner(owner)
        try:
            response = self.rally.update('TestCase', self.testCasefields)
            logger.info('Owner for test case {} now become {}'.format(
                tc.FormattedID, response.Owner.Name))
        except Exception as re:
            logger.error('Fail to update test case {}, {}'.format(
                tc.FormattedID, re.message))

    def update_testcase_project(self, testcase_id):
        tc = self.get_testcase_by_id(testcase_id)
        self.testCasefields = {}
        self.testCasefields['ObjectID'] = tc.oid
        self.testCasefields['Project'] = self.rally.getProject().ref
        try:
            response = self.rally.update('TestCase', self.testCasefields)
            logger.info('Project for test case {} now become {}'.format(
                tc.FormattedID, response.Project.Name))
        except Exception as re:
            logger.error('Fail to update test case {}, {}'.format(
                tc.FormattedID, re.message))

    def update_testcase_status(self, testcase_id, testcase_status):
        tc = self.get_testcase_by_id(testcase_id)
        self.testCasefields = {}
        self.testCasefields['ObjectID'] = tc.oid
        self.testCasefields['c_TestCaseStatus'] = testcase_status
        try:
            response = self.rally.update('TestCase', self.testCasefields)
            logger.info('test case for test case {} now become {}'.format(
                tc.FormattedID, response.c_TestCaseStatus))
        except Exception as re:
            logger.error('Fail to update test case {}, {}'.format(
                tc.FormattedID, re.message))

    def update_testset_name(self, testset_id, oldrelease, newrelease):
        ts = self.get_testcase_by_id(testset_id)
        self.testSetfields = {}
        self.testSetfields['ObjectID'] = ts.oid

        self.testSetfields['Name'] = ts.Name.replace(oldrelease, newrelease)
        try:
            response = self.rally.update('TestSet', self.testSetfields)
            logger.info('test set for test set {} now become {}'.format(
                ts.FormattedID, response.Name))
        except Exception as re:
            logger.error('Fail to update test set {}, {}'.format(
                ts.FormattedID, re.message))

    def get_user_ref_by_owner(self, owner):
        owner = owner.split('@')[0] if '@' in owner else owner
        return self.rally.getUserInfo(username=('*****@*****.**' %
                                                owner))[0].ref

    def get_discussion(self, entity_id):
        entity = elf._get_entity_by_id(entity_id)
        discussion_context = []
        if hasattr(entity, '__collection_ref_for_Discussion'):
            discussion_url = entity.__collection_ref_for_Discussion
            content = json.loads(
                ralutil.rally.session.get(discussion_url).content)
            for post in content['QueryResult']['Results']:
                discussion_context.append(post)
        else:
            logger.error('{} has no discussion'.format(entity_id))
            return

    def get_testsets(self):
        query = 'c_ProductionRelease = "11.2 EA [2019-Jun-21]"'
        testsets = self.rally.get('TestSet', query=query, instance=True)
        for testset in testsets:
            print(testset.Name)

    def add_teststeps_to_testcase(self, testcase_id, inputs, expect_results):
        testcase = self.get_testset_by_id(testcase_id)

        if len(inputs) != len(expect_results):
            logger.warning(
                'your input# is not match your expect# {}/{}'.format(
                    len(inputs), len(expect_results)))
        if len(inputs) <= len(expect_results):
            index = len(inputs)
        else:
            index = len(expect_results)
        for i in range(index):
            Step = {'StepIndex': i + 1}
            Step['Input'] = inputs[i]
            Step['ExpectedResult'] = expect_results[i]
            Step['TestCase'] = testcase.ref
            logger.debug(Step)
            self.rally.create('TestCaseStep', Step)

    def add_discussion_to_testcase(self, testcase_id, **info):
        querytestcase = 'TestCase.FormattedID = "%s"' % testcase_id
        testcaseDiscussion = self.rally.get('ConversationPost',
                                            query=querytestcase,
                                            instance=True)
        testcase = self.get_testcase_by_id(testcase_id)
        queryuser = '******' % info['User']
        user = self.rally.get('User',
                              fetch=True,
                              query=queryuser,
                              instance=True)

        info["User"] = user.ref
        info['Artifact'] = testcase.ref
        # info['_type']     = "ConversationPost"

        self.rally.create('ConversationPost', info)

    def clean_teststeps_on_testcase(self, testcase_id):
        testcase = self.get_testset_by_id(testcase_id)

        for step in testcase.Steps:
            self.rally.delete('TestCaseStep', step.oid)
Пример #14
0
from pyral import Rally, rallyWorkset

options = [opt for opt in sys.argv[1:] if opt.startswith('--')]
server, user, password, apikey, workspace, project = rallyWorkset(options)
rally = Rally('rally1.rallydev.com',
              '*****@*****.**',
              'KRIIsh123@%',
              workspace='Workspace 1',
              project='Sample Project',
              server_ping=True)
#rally = Rally(server, user, password, workspace=workspace, project=project)
rally.enableLogging('rally.simple-use.log')

proj = rally.getProject()

# get the first (and hopefully only) user whose DisplayName is 'Sally Submitter'
user = rally.getUserInfo(name='VENKAT KRIISH').pop(0)

userStory_data = {
    "Name": "Hello2",
    "Notes": "Hello Notes",
    "Description": "This is a sample user story for jenkins automation"
}
try:
    ustory = rally.create('UserStory', userStory_data)
except Exception as details:
    sys.stderr.write('ERROR: %s \n' % details)
    sys.exit(1)
print("ustory created, ObjectID: %s  FormattedID: %s" %
      (ustory.oid, ustory.FormattedID))
Пример #15
0
              '*****@*****.**',
              'KRIIsh123@%',
              workspace='Workspace 1',
              project='Sample Project',
              server_ping=True)
#rally = Rally(server, user, password, workspace=workspace, project=project)
rally.enableLogging('rally.simple-use.log')

proj = rally.getProject()

# get the first (and hopefully only) user whose DisplayName is 'Sally Submitter'
user = rally.getUserInfo(name='VENKAT KRIISH').pop(0)

defect_data = {
    "Project": proj.ref,
    "SubmittedBy": user.ref,
    "Name": "Jagath kriish",
    "Severity": "Major Problem",
    "Priority": "High Attention",
    "State": "Open",
    "ScheduleState": "Defined",
    "Description": "This is a sample defect for jenkins automation"
}
try:
    defect = rally.create('Defect', defect_data)
except details:
    sys.stderr.write('ERROR: %s \n' % details)
    sys.exit(1)
print("Defect created, ObjectID: %s  FormattedID: %s" %
      (defect.oid, defect.FormattedID))
Пример #16
0
class AgileCentralConnection(BLDConnection):

    def __init__(self, config, logger):
        super().__init__(logger)
        self.internalizeConfig(config) 
        self.log.info("Rally WSAPI Version %s" % self.rallyWSAPIVersion())
        self.integration_other_version = ""
        self.username_required = True
        self.password_required = True
        self.build_def = {}  # key by Project, then value is in turn a dict keyed by Job name with number and date
                             # of last Build

    def name(self):
        return "AgileCentral"

    def version(self):
        global __version__
        return __version__

    # Rally Web Services version, do not modify unless tested!
    def rallyWSAPIVersion(self):
        return "v2.0"

    def getBackendVersion(self):
        """
            Conform to Connection subclass protocol which requires the version of 
            the system this instance is "connected" to.
        """
        return "Rally WSAPI %s" % self.rallyWSAPIVersion()

    def internalizeConfig(self, config):
        super().internalizeConfig(config)

        server = config.get('Server', 'rally1.rallydev.com')
        if 'http' in server.lower() or '/slm' in server.lower():
            self.log.error(self, "AgileCentral URL should be in the form 'rally1.rallydev.com'")
        self.server = server

        self.url = "https://%s/slm" % server
        self.port = config.get('Port', '443')
        self.apikey          = config.get("APIKey", config.get("API_Key", None))
        self.workspace_name  = config.get("Workspace", None)
        self.project_name    = config.get("Project",   None)  # This gets bled in by the BLDConnector
        self.restapi_debug   = config.get("Debug", False)
        self.restapi_logger  = self.log

        self.proxy = None
        if self.proxy_server:
            self.proxy  = "%s://%s:%s" % (self.proxy_protocol, self.proxy_server, self.proxy_port)
            if self.proxy_username and self.proxy_password:
                self.proxy  = "%s://%s:%s@%s:%s" % (self.proxy_protocol, self.proxy_username, self.proxy_password, self.proxy_server, self.proxy_port)

        valid_config_items = ['Server', 'Port', 'APIKey','Workspace','Project','Username','Password','ProxyProtocol', 'ProxyServer','ProxyPort','ProxyUser','ProxyUsername', 'ProxyPassword','Debug', 'Lookback']
        invalid_config_items = [item for item in config.keys() if item not in valid_config_items]
        if invalid_config_items:
            problem = "AgileCentral section of the config contained these invalid entries: %s" % ", ".join(invalid_config_items)
            raise ConfigurationError(problem)

    def validate(self):
        satisfactory = True

        if self.username_required:
            if not self.username and not self.apikey:
                self.log.error("<Username> is required in the config file")
                satisfactory = False
            #else:
            #    self.log.debug(
            #        '%s - user entry "%s" detected in config file' % (self.__class__.__name__, self.username))

        if self.password_required:
            if not self.password and not self.apikey:
                self.log.error("<Password> is required in the config file")
                satisfactory = False
            #else:
            #    self.log.debug('%s - password entry detected in config file' % self.__class__.__name__)

        return satisfactory

    def validateProjects(self, target_projects):
        """
            make requests to AgileCentral to retrieve basic info for each project in target_projects.
            If any project name in target_projects does NOT have a corresponding project in AgileCentral
            raise and Exception naming the offending project.
        """
        try:
            mep_projects     = list(set([project for project in target_projects if ' // '     in project]))
        except Exception as msg:
            raise OperationalError("%s %s" % ("error in agicen_bld_connection.py for getting mep_projects", msg))
        try:
            non_mep_projects = list(set([project for project in target_projects if ' // ' not in project]))
        except Exception as msg:
            raise OperationalError("%s %s" % ("error in agicen_bld_connection.py for getting non_mep_projects", msg))
        query = self._construct_ored_Name_query(non_mep_projects)
        response = self.agicen.get('Project', fetch='Name,ObjectID', query=query, workspace=self.workspace_name,
                                   project=None, projectScopeDown=True, pagesize=200)
        if response.errors or response.resultCount == 0:
            raise ConfigurationError(
                'Unable to locate a Project with the name: %s in the target Workspace: %s' % (self.project_name, self.workspace_name))

        found_projects = [project for project in response]
        try:
            found_project_names = list(set([p.Name for p in found_projects]))
        except Exception as msg:
            raise OperationalError("%s %s" % ("error in agicen_bld_connection.py for getting found_project_names", msg))
        bogus = [name for name in target_projects if name not in found_project_names]
        try:
            real_bogus = set(bogus) - set(mep_projects)
        except Exception as msg:
            raise OperationalError("%s %s" % ("error in agicen_bld_connection.py for getting real_bogus", msg))
        if real_bogus:
            problem = "These projects mentioned in the config were not located in AgileCentral Workspace %s: %s" % (self.workspace_name, ",".join(real_bogus))
            self.log.error(problem)
            return False

        #deal with the mep_projects
        for mep in mep_projects:
            proj = self.agicen.getProject(mep)
            if proj:
                found_projects.append(proj)

        self._project_cache = {proj.Name : proj.ref for proj in found_projects}
        return True


    def _construct_ored_Name_query(self, target_projects):
        if not target_projects: return ''
        initial = '(Name = "%s")' % target_projects[0]
        or_string = initial
        for project in target_projects[1:]:
            or_string = '(%s OR (Name = "%s"))' % (or_string, project)
        return or_string

        #return "(%s)" % or_string[1:-1]

    def setSourceIdentification(self, other_name, other_version):
        self.other_name = other_name
        self.integration_other_version  = other_version

    def get_custom_headers(self):
        custom_headers =  {}
        custom_headers['name']    = "AgileCentral Build Connector for %s" % self.other_name
        custom_headers['vendor']  = "CA Technologies"
        custom_headers['version'] = self.version
        if self.integration_other_version: 
            spoke_versions = "%s - %s " % (self.version, self.integration_other_version)
            custom_headers['version'] = spoke_versions
        return custom_headers


    def connect(self):
        if self.proxy:
            self.log.info("Proxy for AgileCentral connection:  %s" % self.proxy)
            os.environ['HTTPS_PROXY'] = self.proxy

        self.log.info("Connecting to AgileCentral")
        custom_headers = self.get_custom_headers()

        try:
            before = time.time()
##            print("")
##            print("before call to pyral.Rally(): %s    using workspace name: %s" % (before, self.workspace_name))
##            print("   credentials:  username |%s|  password |%s|  apikey |%s|" % (self.username, self.password, self.apikey))
            self.agicen = Rally(self.server, user=self.username, password=self.password, apikey=self.apikey,
                                workspace=self.workspace_name, project=self.project_name,
                                version=self.rallyWSAPIVersion(), http_headers=custom_headers,
                                server_ping=False, isolated_workspace=True,
                                logger=self.restapi_logger, warn=False, debug=True)
            after = time.time()
##            print("after  call to pyral.Rally(): %s" % after)
##            print("initial Rally connect elapsed time: %6.3f  seconds" % (after - before))
##            sys.stdout.flush()
##
            if self.restapi_debug:
                self.agicen.enableLogging('agicen_builds.log')
        except Exception as msg:
            raise ConfigurationError("Unable to connect to Agile Central at %s: %s" % \
                                         (self.server, msg))
        self.log.info("Connected to Agile Central server: %s" % self.server)    

##        before = time.time()
        # verify the given workspace name exists
##        print("")
##        print("before call to agicen.getWorkspaces: %s" % before)
        all_workspaces = self.agicen.getWorkspaces()
##        after = time.time()
##        print("after  call to agicen.getWorkspaces: %s" % after)
##        print("agicen.getWorkspaces elapsed time: %6.3f  seconds" % (after - before))

        valid = [wksp for wksp in all_workspaces if wksp.Name == self.workspace_name]
        if not valid:
            problem = "Specified Workspace: '%s' not in list of workspaces " + \
                      "available for your credentials as user: %s" 
            raise ConfigurationError(problem % (self.workspace_name, self.username))
        self.log.info("    Workspace: %s" % self.workspace_name)
        self.log.info("    Project  : %s" % self.project_name)
        wksp = self.agicen.getWorkspace()
        prjt  = self.agicen.getProject()
        self.workspace_ref = wksp.ref
        self.project_ref   = prjt.ref

        # find all of the Projects under the AgileCentral_Project
##        before = time.time()
##        print("")
##        print("before call to agicen get Project: %s" % before)
        response = self.agicen.get('Project', fetch='Name', workspace=self.workspace_name,
                                   project=self.project_name,
                                   projectScopeDown=True,
                                   pagesize=200)
        if response.errors or response.resultCount == 0:
            raise ConfigurationError('Unable to locate a Project with the name: %s in the target Workspace' % self.project_name)

        # detect any duplicate project names
        self.project_bucket = {}
        for proj in response:
            if proj.Name not in self.project_bucket:
                self.project_bucket[proj.Name] = 0
            self.project_bucket[proj.Name] += 1
        self.duplicated_project_names = [p for p,c in self.project_bucket.items() if c != 1]

        project_names = [proj.Name for proj in response]
##        after = time.time()
##        print("after  call to agicen get Project: %s" % after)
##        print("agicen.get Project  elapsed time: %6.3f  seconds  for  %d Projects" % ((after - before), len(project_names)))
##        print("")
        self.log.info("    %d sub-projects" % len(project_names))

        return True


    def disconnect(self):
        """
            Just reset our agicen instance variable to None
        """
        self.agicen = None


    def set_integration_header(self, header_info):
        self.integration_name    = header_info['name']
        self.integration_vendor  = header_info['vendor']
        self.integration_version = header_info['version']
        if 'other_version' in header_info:
            self.integration_other_version = header_info['other_version']


    def getRecentBuilds(self, ref_time, projects):
        """
            Obtain all Builds created in Agile Central at or after the ref_time parameter
            (which is a struct_time object)
             in Python, ref_time will be a struct_time item:
               (tm_year, tm_mon, tm_mday, tm_hour, tm_min, tm_sec, tm_wday, tm_yday, tm_isdst)
        """
        ref_time_readable = time.strftime("%Y-%m-%d %H:%M:%S Z", ref_time)
        ref_time_iso      = time.strftime("%Y-%m-%dT%H:%M:%SZ",  ref_time)
        self.log.info("Detecting recently added Agile Central Builds")
        selectors = ['CreationDate >= %s' % ref_time_iso]
        log_msg = '   recent Builds query: %s' %  ' and '.join(selectors)
        self.log.info(log_msg)
        builds = {}

        for project in projects:
            response = self._retrieveBuilds(project, selectors)
            log_msg = "  %d recently added Agile Central Builds detected for project: %s"
            self.log.info(log_msg % (response.resultCount, project))

            for build in response:
                build_name = build.BuildDefinition.Name
                if project not in builds:
                    builds[project] = {}
                if build_name not in builds[project]:
                    builds[project][build_name] = []
                builds[project][build_name].append(build)
        return builds


    def _retrieveBuilds(self, project, selectors):
        fetch_fields = "ObjectID,CreationDate,Number,Start,Status,Duration,BuildDefinition,Name," +\
                       "Workspace,Project,Uri,Message,Changesets"
        try:
            response = self.agicen.get('Build',
                                       fetch=fetch_fields,
                                       query=selectors,
                                       workspace=self.workspace_name,
                                       project=project,
                                       projectScopeDown=False,
                                       projectScopeUp=False,
                                       order="CreationDate",
                                       pagesize=1000
                                      )
        except Exception as msg:
            excp_type, excp_value, tb = sys.exc_info()
            mo = re.search(r"'(?P<ex_name>.+)'", str(excp_type))
            if mo:
                excp_type = mo.group('ex_name').replace('exceptions.', '')
                msg = '%s: %s\n' % (excp_type, str(excp_value))
            raise OperationalError(msg)

        # Examine the response to see if there is any content for the 'Errors' or 'Warnings' keys.
        # and raise an Exception in that case.
        if response.errors:
            raise Exception(response.errors[0][:80])
        if response.warnings:
            raise Exception(response.warnings[0][:80])

        return response

    def retrieveChangeset(self, sha):
        query = 'Revision = %s' % sha
        response = self.agicen.get('Changeset', fetch='ObjectID', query=query,
                                    workspace=self.workspace_name,project=None)
        # should we check for errors or warnings?
        if response.resultCount > 0:
            return response.next()
        return None

    def _fillBuildDefinitionCache(self, project):
        response = self.agicen.get('BuildDefinition',
                                  fetch='ObjectID,Name,Project,LastBuild,Uri', 
                                  query='Name != "Default Build Definition"',
                                  workspace=self.workspace_name,
                                  project=project,
                                  projectScopeUp=False, projectScopeDown=False,
                                  order='Project.Name,Name')

        if response.errors:
            raise OperationalError(str(response.errors))

        for build_defn in response:
##
           #print("_fillBuildDefinitionCache:  BuildDefinition  Project: %s  JobName: %s" % \
           #        (build_defn.Project.Name, build_defn.Name))
##
            job_name = build_defn.Name
            if not project in self.build_def:
                self.build_def[project] = {}
            self.build_def[project][job_name] = build_defn


    def prepAgileCentralBuildPrerequisites(self, job, target_build, project):
        """
            Given a target_build which has information about a build that has been completed on
             some target build system, accommodate/create the following:
                SCMRepository    based on the target_build.repo value
                Changesets       based on the target_build.changeSets
                BuildDefinition  based on the target_build.job  (which is the job name)
             Any or all or none of these things may already be present in AgileCentral, and if
             not create what is necessary.
             Return back the SCMRepository, Changesets, and BuildDefinition.
             These will be a  pyral entity for each or a list of pyral entities in the case of Changesets
        """
        changesets = None
        if target_build.changeSets:
            ac_changesets, missing_changesets = self.getCorrespondingChangesets(target_build)
            # check for ac_changesets, if present take the SCMRepository of the first in the list (very arbitrary!)
            if ac_changesets:
                first_changeset = list(ac_changesets)[0]
                criteria = 'Name = "%s"' % first_changeset.SCMRepository.Name
                scm_repo = self.agicen.get('SCMRepository', fetch="Name", query=criteria, instance=True)
            else:
                scm_repo = self.ensureSCMRepositoryExists(target_build.repository, target_build.vcs)

            changesets = self.ensureChangesetsExist(scm_repo, project, ac_changesets, missing_changesets)

        build_defn = self.ensureBuildDefinitionExists(job.fully_qualified_path(), project, target_build.vcs)
        return changesets, build_defn


    def getCorrespondingChangesets(self, build) :
        build_changesets = build.changeSets
        if not build_changesets:
            return [], []
        ids = [cs.commitId for cs in build_changesets]
        scm_repository_name = ''
        for id in ids:
            criteria = '(Revision = "{0}")'.format(id)
            response = self.agicen.get('Changeset', fetch="ObjectID,Revision,SCMRepository,Name", query=criteria)
            if response.resultCount:
                hits = [item for item in response]
                scm_repository_name = hits[0].SCMRepository.Name
                break

        if scm_repository_name:
            fields = "ObjectID,Revision,SCMRepository,Name"
            query = "(SCMRepository.Name = {})".format(scm_repository_name)

            response = self.agicen.get('Changeset', fetch=fields, query=query, order="CreationDate", pagesize=200)
            if response.resultCount:
                changesets = [item for item in response]

            build_changesets_revisions = [bc.commitId for bc in build_changesets]
            ac_changesets_revisions    = [cs.Revision for cs in changesets]

            try:
                missing = [bc for bc in build_changesets if bc.commitId not in ac_changesets_revisions]
            except Exception as msg:
                raise OperationalError("%s %s" % ("error in agicen_bld_connection.py for getting missing", msg))
            try:
                present = [cs for cs in changesets if cs.Revision in build_changesets_revisions]
            except Exception as msg:
                raise OperationalError("%s %s" % ("error in agicen_bld_connection.py for getting present", msg))
            return present, missing
        else:
            vcs_type = build_changesets[0].vcs
            self.ensureSCMRepositoryExists(build.repository, vcs_type)
            return [], build_changesets


    def ensureSCMRepositoryExists(self, repo_name, vcs_type):
        """
            Use the WSAPI case-insensitive Name contains ... syntax so that we can "match" a name like 'wombat' to 'Wombat'
            in case the users have manually already created a 'Wombat' SCMRepository either manually or via VCS Connector
        """
        repo_name = repo_name.replace('\\','/')
        name = repo_name.split('/')[-1]
        criteria = '(Name contains "{0}")'.format(name)
        response = self.agicen.get('SCMRepository', fetch="Name,ObjectID", query = criteria, project=None)
        if response.resultCount:
            scm_repos = [item for item in response]
            exact_matches = [scm_repo for scm_repo in scm_repos if scm_repo.Name.lower() == repo_name.lower()]
            if exact_matches:
                return exact_matches[0]

        scm_repo_payload = {'Name': repo_name, 'SCMType': vcs_type}
        try:
            scm_repo = self.agicen.create('SCMRepository', scm_repo_payload)
            self.log.info("Created SCMRepository %s (%s)" % (scm_repo.Name, scm_repo.ObjectID))
        except RallyRESTAPIError as msg:
            self.log.error('Error detected on attempt to create SCMRepository %s' % msg)
            raise OperationalError("Could not create SCMRepository %s" % repo_name)

        return scm_repo


    def ensureChangesetsExist(self, scm_repo, project, ac_changesets, missing_changesets):
        all_mentioned_artifact_fids = {mc.commitId: self.parseForArtifacts(mc.message) for mc in missing_changesets}
        valid_artifact_fids         = self.validatedArtifacts(all_mentioned_artifact_fids)
        for mc in missing_changesets:
            valid_artifacts = valid_artifact_fids[mc.commitId]
            changeset_payload = {
                'SCMRepository'   : scm_repo.ref,
                'Revision'        : mc.commitId,
                'CommitTimestamp' : datetime.utcfromtimestamp(mc.timestamp / 1000).strftime('%Y-%m-%dT%H:%M:%SZ'),
                'Message'         : mc.message,
                'Uri'             : mc.uri,
                'Artifacts'       : valid_artifacts
            }
            try:
                changeset = self.agicen.create('Changeset', changeset_payload)
                self.log.debug("Created Changeset %s" % changeset.ObjectID)
            except RallyRESTAPIError as msg:
                self.log.error('Error detected on attempt to create Changeset %s' % msg)
                raise OperationalError("Could not create Changeset  %s" % msg)
            ac_changesets.append(changeset)

        return ac_changesets

    def parseForArtifacts(self, commit_message):
        prefixes = [prefix for item in utils.get_all_prefixes(self.agicen) for prefix in item.values()]
        fid_pattern = r'((%s)\d+)' % '|'.join(prefixes)
        result = re.findall(fid_pattern, commit_message, re.IGNORECASE)
        return [item[0].upper() for item in result]


    def validatedArtifacts(self, commit_fid):
        # commit_fid is a dict
        # commit_fid = {commitID: [S123,DE123], ...}
        try:
            fids = list(set([fid for fids in commit_fid.values() for fid in fids]))
        except Exception as msg:
            self.log.error("Cannot get a list of formatted ids of artifacts in the commit messages, in validatedArtfacts")
            raise OperationalError(msg)
        query = self.makeOrQuery("FormattedID", fids)
        response = self.agicen.get('Artifact', fetch="FormattedID,ObjectID", query=query, project=None, pagesize=200, start=1)
        found_arts = [art for art in response]
        va = {}
        for ident in commit_fid.keys():
            va[ident] = []
            matches = [found_art for found_art in found_arts if found_art.FormattedID in commit_fid[ident]]
            if matches:
                va[ident] = matches
                # for art in matches:
                #     if art.Project.oid != targeted_project.oid:
                #         self.log.warning('')
        return va

    def makeOrQuery(self,field, values):
        if not values:
            return None

        values = list(values)
        query = '(%s = "%s")' %(field, values[0])
        if len(values) == 1:
            return query
        else:
            values.pop(0)
            for v in values:
                query = '(%s OR (%s = "%s"))' % (query, field, v)
            return query

    def ensureBuildDefinitionExists(self, job_path, project, job_uri):
        """
            use the self.build_def dict keyed by project at first level, job_path at second level
            to determine if the job_path has a BuildDefinition for it.

            Returns a pyral BuildDefinition instance corresponding to the job (and project)
        """
        # front-truncate the job_path if the string length of that is > 256 chars
        if (len(job_path) > 256):
            job_path = job_path[-256:]
        if project in self.build_def and job_path in self.build_def[project]:
            return self.build_def[project][job_path]

        # do we have the BuildDefinition cache populated?  If not, do it now...
        if project not in self.build_def:  # to avoid build definition duplication
            self.log.debug("Detected build definition cache for the project: {} is empty, populating ...".format(project))
            self._fillBuildDefinitionCache(project)

        # OK, the job_path is not in the BuildDefinition cache
        # so look in the BuildDefinition cache to see if the job_path exists for the given project
        if project in self.build_def:
            if job_path in self.build_def[project]:
                return self.build_def[project][job_path]

        target_project_ref = self._project_cache[project]

        bdf_info = {'Workspace' : self.workspace_ref,
                    'Project'   : target_project_ref,
                    'Name'      : job_path,
                    'Uri'       : job_uri #something like {base_url}/job/{job} where base_url comes from other spoke conn
                   }
        try:
            self.log.debug("Creating a BuildDefinition for job '%s' in Project '%s' ..." % (job_path, project))
            build_defn = self.agicen.create('BuildDefinition', bdf_info, workspace=self.workspace_name, project=project)
        except Exception as msg:
            self.log.error("Unable to create a BuildDefinition for job: '%s';  %s" % (job_path, msg))
            raise OperationalError("Unable to create a BuildDefinition for job: '%s';  %s" % (job_path, msg))

        # Put the freshly minted BuildDefinition in the BuildDefinition cache and return it
        if project not in self.build_def:
            self.build_def[project] = {}

        self.build_def[project][job_path] = build_defn
        return build_defn


    def preCreate(self, int_work_item):
        """
        # transform the CommitTimestamp from epoch seconds into ISO-8601'ish format
        #timestamp = int_work_item['CommitTimestamp']
        #iso8601_ts = time.strftime(ISO8601_TS_FORMAT, time.gmtime(timestamp))
        #int_work_item['CommitTimestamp'] = iso8601_ts

        # BuildDefinition has to be a ref
        # Number  is a string
        # Status  is a string
        # get Start in to iso8601 format
        # Duration is in seconds.milliseconds format
        # Uri is link to the originating build system job number page
        """

        int_work_item['Workspace']       = self.workspace_ref 
        int_work_item['BuildDefinition'] = int_work_item['BuildDefinition'].ref

        if int_work_item.get('Changesets', False):
            collection_payload = [{'_ref': "changeset/%s" % changeset.oid} for changeset in int_work_item['Changesets']]
            int_work_item['Changesets']= collection_payload

        return int_work_item


    def _createInternal(self, int_work_item):

        # snag the base Uri from the int_work_item['Uri'] burning off any ending '/' char
        base_uri = int_work_item['Uri']
        base_uri = base_uri[:-1] if base_uri and base_uri.endswith('/') else base_uri

        try:
            build = self.agicen.create('Build', int_work_item)
            self.log.debug("  Created Build: %-90.90s #%5s  %-8.8s %s" % (build.BuildDefinition.Name, build.Number, build.Status, build.Start))
        except Exception as msg:
            print("AgileCentralConnection._createInternal detected an Exception, {0}".format(sys.exc_info()[1]))
            excp_type, excp_value, tb = sys.exc_info()
            mo = re.search(r"'(?P<ex_name>.+)'", str(excp_type))
            if mo:
                excp_type = mo.group('ex_name').replace('exceptions.', '')
                msg = '%s: %s\n' % (excp_type, str(excp_value))
            raise OperationalError(msg)

        return build


    def buildExists(self, build_defn, number):
        """
            Issue a query against Build to obtain the Build identified by build_defn.ObjectID and number.
            Return a boolean indication of whether such an item exists.
        """
        criteria = ['BuildDefinition.ObjectID = %s' % build_defn.ObjectID, 'Number = %s' % number]
        response = self.agicen.get('Build', fetch="CreationDate,Number,Name,BuildDefinition,Project", query=criteria,
                                            workspace=self.workspace_name, project=None)

        if response.resultCount:
            return response.next()
        return None


    # def populateChangesetsCollectionOnBuild(self, build, changesets):
    #     csrefs = [{ "_ref" : "changeset/%s" % cs.oid} for cs in changesets]
    #     cs_coll_ref = build.Changesets
    #     #self.agicen.addCollection(cs_coll_ref, csrefs)

    def matchToChangesets(self, vcs_commits):
        valid_changesets = []
        for commit in vcs_commits:
            query = ('Revision = "%s"' % commit)
            response = self.agicen.get("Changeset", fetch="ObjectID", query=query, workspace=self.workspace_name, project=None)
            if response.resultCount > 0:
                changeset = response.next()
                valid_changesets.append(changeset)
        return valid_changesets