示例#1
0
def _iter_issue_batches(ids):
    """Generates a sequence of batches of issues from Issue Tracker by IDs."""
    cli = issues.Client()

    for i in xrange(0, len(ids), _BATCH_SIZE):
        chunk = ids[i:i + _BATCH_SIZE]
        logger.debug('Issue ids to process: %s', chunk)
        try:
            response = cli.search({
                'issue_ids': chunk,
                'page_size': _BATCH_SIZE,
            })
        except integrations_errors.HttpError as error:
            logger.error('Unable to fetch Issue Tracker issues by IDs: %r',
                         error)
            return

        issue_infos = {}
        response_issues = response.get('issues') or []
        for info in response_issues:
            state = info['issueState'] or {}
            issue_infos[info['issueId']] = {
                'status': state.get('status'),
                'type': state.get('type'),
                'priority': state.get('priority'),
                'severity': state.get('severity'),
            }
        if issue_infos:
            yield issue_infos
  def build_params_for_issue_link(self, obj, ticket_id, it_info):
    """Build update issue query for linking IssueTracker ticket to Issue"""
    allowed_emails = self._build_allowed_emails(obj)
    if allowed_emails is None:
      return self.params

    try:
      res = issues.Client().get_issue(ticket_id)
    except integrations_errors.Error as error:
      logger.error(
          "Unable to link a ticket while creating object ID=%d: %s",
          obj.id,
          error,
      )
      obj.add_warning(
          "Ticket tracker ID does not exist or you do not have access to it."
      )
    else:
      self.params.status = res["issueState"]["status"]
      self._add_link_message(obj)
      self.handle_issue_tracker_info(obj, it_info)
      self._populate_hotlist(it_info, res)
      self._handle_emails_from_response(res)
      self.params.reporter = obj.modified_by.email

    return self.params
def _handle_assessment_deleted(sender, obj=None, service=None):
  """Handles assessment delete event."""
  del sender, service  # Unused

  issue_obj = all_models.IssuetrackerIssue.get_issue(
      _ASSESSMENT_MODEL_NAME, obj.id)

  if issue_obj:
    if (issue_obj.enabled and
            issue_obj.issue_id and
            _is_issue_tracker_enabled(audit=obj.audit)):
      issue_params = {
          'status': 'OBSOLETE',
          'comment': (
              'Assessment has been deleted. Changes to this GGRC '
              'Assessment will no longer be tracked within this bug.'
          ),
      }
      try:
        issues.Client().update_issue(issue_obj.issue_id, issue_params)
      except integrations_errors.Error as error:
        logger.error('Unable to update a ticket ID=%s while deleting'
                     ' assessment ID=%d: %s',
                     issue_obj.issue_id, obj.id, error)
    db.session.delete(issue_obj)
def _create_issuetracker_issue(assessment, issue_tracker_info):
  """Collects information and sends a request to create external issue."""
  _normalize_issue_tracker_info(issue_tracker_info)

  person, acl, acr = (all_models.Person, all_models.AccessControlList,
                      all_models.AccessControlRole)
  reporter_email = db.session.query(
      person.email,
  ).join(
      acl,
      person.id == acl.person_id,
  ).join(
      acr,
      sa.and_(
          acl.ac_role_id == acr.id,
          acr.name == "Audit Captains",
      ),
  ).filter(
      acl.object_id == assessment.audit_id,
      acl.object_type == all_models.Audit.__name__,
  ).order_by(
      person.email,
  ).first()

  if reporter_email:
    reporter_email = reporter_email.email

  comment = [_INITIAL_COMMENT_TMPL % _get_assessment_url(assessment)]
  test_plan = assessment.test_plan
  if test_plan:
    comment.extend([
        'Following is the assessment Requirements/Assessment Procedure '
        'from GGRC:',
        html2text.HTML2Text().handle(test_plan).strip('\n'),
    ])

  hotlist_id = issue_tracker_info.get('hotlist_id')

  issue_params = {
      'component_id': issue_tracker_info['component_id'],
      'hotlist_ids': [hotlist_id] if hotlist_id else [],
      'title': issue_tracker_info['title'],
      'type': issue_tracker_info['issue_type'],
      'priority': issue_tracker_info['issue_priority'],
      'severity': issue_tracker_info['issue_severity'],
      'reporter': reporter_email,
      'assignee': '',
      'verifier': '',
      'ccs': [],
      'comment': '\n'.join(comment),
  }

  _update_issue_params_assignee(issue_params, issue_tracker_info)

  cc_list = issue_tracker_info.get('cc_list')
  if cc_list is not None:
    issue_params['ccs'] = cc_list

  res = issues.Client().create_issue(issue_params)
  return res['issueId']
示例#5
0
def create_ticket_for_new_issue(obj, issue_tracker_info):
    """Create new IssueTracker ticket for issue"""
    builder = issue_tracker_params_builder.IssueParamsBuilder()
    issue_tracker_params = builder.build_create_issue_tracker_params(
        obj, issue_tracker_info)

    if issue_tracker_params.is_empty():
        return

    # Query to IssueTracker.
    issue_tracker_query = issue_tracker_params.get_issue_tracker_params()

    # Parameters for creation IssuetrackerIssue object in GGRC.
    issuetracker_issue_params = issue_tracker_params.get_params_for_ggrc_object(
    )

    try:
        res = issues.Client().create_issue(issue_tracker_query)

        issue_url = integration_utils.build_issue_tracker_url(res["issueId"])
        issuetracker_issue_params["issue_url"] = issue_url
        issuetracker_issue_params["issue_id"] = res["issueId"]
    except integrations_errors.Error as error:
        logger.error(
            "Unable to create a ticket while creating object ID=%d: %s",
            obj.id, error)
        obj.add_warning("Unable to create a ticket in issue tracker.")
        issuetracker_issue_params["enabled"] = False

    # Create object in GGRC with info about issue tracker integration.
    all_models.IssuetrackerIssue.create_or_update_from_dict(
        obj, issuetracker_issue_params)
def sync_assessment_statuses():
    """Synchronizes issue tracker ticket statuses with the Assessment statuses.

  Checks for Assessments which are in sync with Issue Tracker issues and
  updates their statuses in accordance to the corresponding Assessments
  if differ.
  """
    assessment_issues = sync_utils.collect_issue_tracker_info("Assessment",
                                                              include_ccs=True)
    if not assessment_issues:
        return
    logger.debug('Syncing state of %d issues.', len(assessment_issues))

    cli = issues.Client()
    processed_ids = set()
    for batch in sync_utils.iter_issue_batches(assessment_issues.keys()):
        for issue_id, issuetracker_state in batch.iteritems():
            issue_id = str(issue_id)
            issue_info = assessment_issues.get(issue_id)
            if not issue_info:
                logger.warning(
                    'Got an unexpected issue from Issue Tracker: %s', issue_id)
                continue

            processed_ids.add(issue_id)
            assessment_state = issue_info['state']

            status_value = ASSESSMENT_STATUSES_MAPPING.get(
                assessment_state["status"])
            if not status_value:
                logger.error(
                    'Inexistent Issue Tracker status for assessment ID=%d '
                    'with status: %s.', issue_info['object_id'], status_value)
                continue

            assessment_state["status"] = status_value
            if all(
                    assessment_state.get(field) == issuetracker_state.get(
                        field) for field in FIELDS_TO_CHECK) and _compare_ccs(
                            assessment_state.get("ccs", []),
                            issuetracker_state.get("ccs", [])):
                continue

            try:
                sync_utils.update_issue(cli, issue_id, assessment_state)
            except integrations_errors.Error as error:
                logger.error(
                    'Unable to update status of Issue Tracker issue ID=%s for '
                    'assessment ID=%d: %r', issue_id, issue_info['object_id'],
                    error)

    logger.debug('Sync is done, %d issue(s) were processed.',
                 len(processed_ids))

    missing_ids = set(assessment_issues) - processed_ids
    if missing_ids:
        logger.warning(
            'Some issues are linked to Assessments '
            'but were not found in Issue Tracker: %s',
            ', '.join(str(i) for i in missing_ids))
def _link_assessment(assessment, issue_tracker_info):
    """Link Assessment to existing IssueTracker ticket"""
    ticket_id = issue_tracker_info['issue_id']
    if integration_utils.is_already_linked(ticket_id):
        logger.error(
            "Unable to link a ticket while creating object ID=%d: %s ticket ID is "
            "already linked to another GGRC object",
            assessment.id,
            ticket_id,
        )
        assessment.add_warning(
            "This ticket was already linked to another GGRC issue, assessment or "
            "review object. Linking the same ticket to multiple objects is not "
            "allowed due to potential for conflicting updates.")
        return

    try:
        response = issues.Client().get_issue(ticket_id)
    except integrations_errors.Error as error:
        logger.error(
            "Unable to link a ticket while creating object ID=%d: %s",
            assessment.id,
            error,
        )
        assessment.add_warning(
            "Ticket tracker ID does not exist or you do not have access to it."
        )
        return

    issue_params = prepare_issue_json(assessment, issue_tracker_info, True)
    issuetracker_ccs = response.get("issueState", {}).get("ccs", [])
    grouped_ccs = group_cc_emails(object_ccs=issue_params.get("ccs", []),
                                  additional_ccs=issuetracker_ccs)
    issue_params["ccs"] = grouped_ccs
    try:
        issues.Client().update_issue(ticket_id, issue_params)
    except integrations_errors.Error as error:
        logger.error(
            'Unable to link a ticket while creating assessment ID=%d: %s',
            assessment.id, error)
        issue_tracker_info['enabled'] = False
        assessment.add_warning('Unable to link a ticket.')
    else:
        issue_url = integration_utils.build_issue_tracker_url(ticket_id)
        issue_tracker_info['issue_url'] = issue_url
        all_models.IssuetrackerIssue.create_or_update_from_dict(
            assessment, issue_tracker_info)
def _update_issuetracker_issue(assessment, issue_tracker_info,
                               initial_assessment, initial_info, request):
    """Collects information and sends a request to update external issue."""
    # pylint: disable=too-many-locals
    issue_id = issue_tracker_info.get('issue_id')
    if not issue_id:
        return

    comments = []

    # Handle switching of 'enabled' property.
    enabled = issue_tracker_info.get('enabled', False)
    if initial_info.get('enabled', False) != enabled:
        # Add comment about toggling feature and process further.
        comments.append(_ENABLED_TMPL if enabled else _DISABLED_TMPL)
    elif not enabled:
        # If feature remains in the same status which is 'disabled'.
        return

    integration_utils.normalize_issue_tracker_info(issue_tracker_info)

    issue_params = _handle_basic_props(issue_tracker_info, initial_info)

    # Handle status update.
    status_value, status_comment = _build_status_comment(
        assessment, initial_assessment)
    if status_value:
        issue_params['status'] = status_value
        comments.append(status_comment)

    # Attach user comments if any.
    comment_text, comment_author = _get_added_comment_text(request)
    if comment_text is not None:
        comments.append(
            _COMMENT_TMPL %
            (comment_author, comment_text, _get_assessment_url(assessment)))

    if comments:
        issue_params['comment'] = '\n\n'.join(comments)

    # Handle hotlist ID update.
    hotlist_id = issue_tracker_info.get('hotlist_id')
    if hotlist_id is not None and hotlist_id != initial_info.get('hotlist_id'):
        issue_params['hotlist_ids'] = [hotlist_id] if hotlist_id else []

    # handle assignee and cc_list update
    assignee_email, cc_list = _collect_issue_emails(assessment)
    if assignee_email is not None:
        issue_tracker_info['assignee'] = assignee_email
        issue_params['assignee'] = assignee_email
        issue_params['verifier'] = assignee_email
        issue_params['ccs'] = cc_list

    if issue_params:
        # Resend all properties upon any change.
        issue_params = _fill_current_value(issue_params, assessment,
                                           initial_info)
        issues.Client().update_issue(issue_id, issue_params)
示例#9
0
 def test_get_issue_raises_exception(self, error_code, fetch_mock):
     """Test issues.Client raises appropriate exception for error codes"""
     fetch_mock.return_value = ObjectDict({
         'status_code': error_code,
         'content': '{"status": "content"}'
     })
     with mock.patch.multiple(self.testable_cls, ENDPOINT='endpoint'):
         self.assertRaises(integrations_errors.HttpError,
                           issues.Client().get_issue, 'some_id')
示例#10
0
def _detach_assessment(new_ticket_id, old_ticket_id):
    """Send to old IssueTracker ticket detachment comment."""
    builder = issue_tracker_params_builder.AssessmentParamsBuilder()
    params = builder.build_detach_comment(new_ticket_id)
    query = params.get_issue_tracker_params()
    try:
        issues.Client().update_issue(old_ticket_id, query)
    except integrations_errors.Error as error:
        logger.error("Unable to add detach comment to ticket issue ID=%d: %s",
                     old_ticket_id, error)
示例#11
0
def link_issue(obj, ticket_id, issue_tracker_info):
  """Link issue to existing IssueTracker ticket"""

  if _is_already_linked(ticket_id):
    logger.error(
        "Unable to link a ticket while creating object ID=%d: %s ticket ID is "
        "already linked to another GGRC object",
        obj.id,
        ticket_id,
    )
    obj.add_warning(
        "This ticket was already linked to another GGRC issue, assessment "
        "or review object. Linking the same ticket to multiple objects is not "
        "allowed due to potential for conflicting updates."
    )
    return

  builder = issue_tracker_params_builder.IssueParamsBuilder()
  issue_tracker_container = builder.build_params_for_issue_link(
      obj,
      ticket_id,
      issue_tracker_info,
  )

  if issue_tracker_container.is_empty():
    return

  # Query to IssueTracker.
  issue_tracker_query = issue_tracker_container.get_issue_tracker_params()

  # Parameters for creation IssuetrackerIssue object in GGRC.
  issuetracker_issue_params = \
      issue_tracker_container.get_params_for_ggrc_object()

  try:
    issues.Client().update_issue(ticket_id, issue_tracker_query)

    ticket_url = integration_utils.build_issue_tracker_url(ticket_id)
    issuetracker_issue_params["issue_url"] = ticket_url
    issuetracker_issue_params["issue_id"] = ticket_id
    update_initial_issue(obj, issue_tracker_container)
  except integrations_errors.Error as error:
    logger.error("Unable to update a ticket ID=%s while deleting"
                 " issue ID=%d: %s",
                 ticket_id, obj.id, error)
    obj.add_warning("Unable to update a ticket in issue tracker.")
    issuetracker_issue_params["enabled"] = False
    return

  if issuetracker_issue_params:
    all_models.IssuetrackerIssue.create_or_update_from_dict(
        obj, issuetracker_issue_params
    )
示例#12
0
def _create_issuetracker_issue(assessment, issue_tracker_info):
    """Collects information and sends a request to create external issue."""
    _normalize_issue_tracker_info(issue_tracker_info)
    reported_email = None
    reporter_id = get_current_user_id()
    if reporter_id:
        reporter = all_models.Person.query.filter(
            all_models.Person.id == reporter_id).first()
        if reporter is not None:
            reported_email = reporter.email

    comment = [
        'This bug was auto-generated to track a GGRC assessment '
        '(a.k.a PBC Item). Use the following link to find the '
        'assessment - %s.' % _get_assessment_url(assessment),
    ]
    test_plan = assessment.test_plan
    if test_plan:
        comment.extend([
            'Following is the assessment Requirements/Assessment Procedure '
            'from GGRC:',
            html2text.HTML2Text().handle(test_plan).strip('\n'),
        ])

    hotlist_id = issue_tracker_info.get('hotlist_id')

    issue_params = {
        'component_id': issue_tracker_info['component_id'],
        'hotlist_ids': [hotlist_id] if hotlist_id else [],
        'title': issue_tracker_info['title'],
        'type': issue_tracker_info['issue_type'],
        'priority': issue_tracker_info['issue_priority'],
        'severity': issue_tracker_info['issue_severity'],
        'reporter': reported_email,
        'assignee': '',
        'verifier': '',
        'ccs': [],
        'comment': '\n'.join(comment),
    }

    assignee = issue_tracker_info.get('assignee')
    if assignee:
        issue_params['status'] = 'ASSIGNED'
        issue_params['assignee'] = assignee
        issue_params['verifier'] = assignee

    cc_list = issue_tracker_info.get('cc_list')
    if cc_list is not None:
        issue_params['ccs'] = cc_list

    res = issues.Client().create_issue(issue_params)
    return res['issueId']
示例#13
0
def update_issue_handler(obj, initial_state, new_issue_tracker_info=None):
    """Event handler for issue object renewal."""
    issue_tracker_object = all_models.IssuetrackerIssue.get_issue(
        "Issue", obj.id)

    if not issue_tracker_object:
        if new_issue_tracker_info and new_issue_tracker_info["enabled"]:
            create_issue_handler(obj, new_issue_tracker_info)
        return

    # Try to create ticket in Issue tracker if previous try failed.
    if new_issue_tracker_info and new_issue_tracker_info["enabled"] \
       and not issue_tracker_object.issue_id:
        create_issue_handler(obj, new_issue_tracker_info)
        return

    current_issue_tracker_info = issue_tracker_object.to_dict(
        include_issue=True, include_private=True)

    if not new_issue_tracker_info:
        # Use existing issue tracker info if object is updating via import
        new_issue_tracker_info = current_issue_tracker_info

    # Build query
    builder = issue_tracker_params_builder.IssueParamsBuilder()
    issue_tracker_params = builder.build_update_issue_tracker_params(
        obj, initial_state, new_issue_tracker_info, current_issue_tracker_info)

    # Query to IssueTracker.
    issue_tracker_query = issue_tracker_params.get_issue_tracker_params()

    # Parameters for creation IssuetrackerIssue object in GGRC.
    issuetracker_issue_params = issue_tracker_params.get_params_for_ggrc_object(
    )

    if not issue_tracker_params.is_empty():
        try:
            issue_id = issue_tracker_object.issue_id
            issues.Client().update_issue(issue_id, issue_tracker_query)
        except integrations_errors.Error as error:
            logger.error(
                "Unable to update a ticket ID=%s while deleting"
                " issue ID=%d: %s", issue_tracker_object.issue_id, obj.id,
                error)
            obj.add_warning("Unable to update a ticket in issue tracker.")

    if issuetracker_issue_params:
        all_models.IssuetrackerIssue.create_or_update_from_dict(
            obj, issuetracker_issue_params)
示例#14
0
def sync_issue_tracker_statuses():
    """Synchronizes issue tracker ticket statuses with the Assessment statuses.

  Checks for Assessments which are in sync with Issue Tracker issues and
  updates their statuses in accordance to the corresponding Assessments
  if differ.
  """
    assessment_issues = _collect_assessment_issues()
    if not assessment_issues:
        return
    logger.debug('Syncing state of %d issues.', len(assessment_issues))

    cli = issues.Client()
    processed_ids = set()
    for batch in _iter_issue_batches(list(assessment_issues)):
        for issue_id, issuetracker_state in batch.iteritems():
            issue_id = str(issue_id)
            issue_info = assessment_issues.get(issue_id)
            if not issue_info:
                logger.warning(
                    'Got an unexpected issue from Issue Tracker: %s', issue_id)
                continue

            processed_ids.add(issue_id)
            assessment_state = issue_info['state']
            if all(
                    assessment_state.get(field) == issuetracker_state.get(
                        field) for field in _FIELDS_TO_CHECK):
                continue

            try:
                _update_issue(cli, issue_id, assessment_state)
            except integrations_errors.Error as error:
                logger.error(
                    'Unable to update status of Issue Tracker issue ID=%s for '
                    'assessment ID=%d: %r', issue_id,
                    issue_info['assessment_id'], error)

    logger.debug('Sync is done, %d issue(s) were processed.',
                 len(processed_ids))

    missing_ids = set(assessment_issues) - processed_ids
    if missing_ids:
        logger.warning(
            'Some issues are linked to Assessments '
            'but were not found in Issue Tracker: %s',
            ', '.join(str(i) for i in missing_ids))
示例#15
0
def _create_new_issuetracker_ticket(assessment, issue_tracker_info):
    """Create new IssueTracker ticket for assessment"""
    issue_tracker_request = prepare_issue_json(assessment,
                                               issue_tracker_info,
                                               create_issuetracker=True)
    try:
        res = issues.Client().create_issue(issue_tracker_request)
    except integrations_errors.Error as error:
        logger.error(
            'Unable to create a ticket while creating assessment ID=%d: %s',
            assessment.id, error)
        issue_tracker_info['enabled'] = False
        assessment.add_warning('Unable to create a ticket.')
    else:
        issue_id = res['issueId']
        issue_url = integration_utils.build_issue_tracker_url(issue_id)
        issue_tracker_info['issue_id'] = issue_id
        issue_tracker_info['issue_url'] = issue_url
示例#16
0
def delete_issue_handler(obj, **kwargs):
  """Event handler for issue object deletion."""
  issue_tracker_object = all_models.IssuetrackerIssue.get_issue("Issue",
                                                                obj.id)

  if issue_tracker_object:
    if issue_tracker_object.enabled and issue_tracker_object.issue_id:
      builder = issue_tracker_params_builder.IssueParamsBuilder()
      issue_tracker_params = builder.build_delete_issue_tracker_params()
      issue_tracker_query = issue_tracker_params.get_issue_tracker_params()
      try:
        issues.Client().update_issue(issue_tracker_object.issue_id,
                                     issue_tracker_query)
      except integrations_errors.Error as error:
        logger.error("Unable to update a ticket ID=%s while deleting"
                     " issue ID=%d: %s",
                     issue_tracker_object.issue_id, obj.id, error)
        obj.add_warning("Unable to update a ticket in issue tracker.")
    db.session.delete(issue_tracker_object)
示例#17
0
def create_comment_handler(sync_object, comment, author):
    """Event handler for adding comment to Issue object."""
    issue_tracker_object = all_models.IssuetrackerIssue.get_issue(
        "Issue", sync_object.id)

    if not issue_tracker_object or not issue_tracker_object.enabled:
        return

    builder = issue_tracker_params_builder.IssueParamsBuilder()
    params = builder.build_params_for_comment(sync_object, comment.description,
                                              author)
    query = params.get_issue_tracker_params()

    try:
        issues.Client().update_issue(issue_tracker_object.issue_id, query)
    except integrations_errors.Error as error:
        logger.error("Unable to add comment to ticket issue ID=%d: %s",
                     issue_tracker_object.issue_id, error)
        sync_object.add_warning("Unable to update a ticket in issue tracker.")
示例#18
0
def update_audit_issues(args):
  """Web hook to update the issues associated with an audit."""
  audit_id = args.parameters['audit_id']
  message = args.parameters['message']

  if not audit_id or not message:
    logger.warning(
        'One or more of required parameters (audit_id, message) is empty.')
    return app.make_response((
        'Parameters audit_id and message are required.',
        400, [('Content-Type', 'text/html')]))

  issue_tracker = models.all_models.IssuetrackerIssue
  relationships = models.all_models.Relationship
  query = db.session.query(
      issue_tracker.enabled,
      issue_tracker.issue_id,
      issue_tracker.object_id
  ).join(
      relationships,
      relationships.source_id == issue_tracker.object_id
  ).filter(
      relationships.source_type == 'Assessment',
      relationships.destination_type == 'Audit',
      relationships.destination_id == audit_id
  )
  cli = issues.Client()
  issue_params = {
      'comment': message,
  }
  for enabled, issue_id, assessment_id in query.all():
    if not enabled:
      continue

    try:
      cli.update_issue(issue_id, issue_params)
    except integrations_errors.Error as error:
      logger.error(
          'Unable to update IssueTracker issue ID=%s '
          'for Assessment ID=%s while archiving/unarchiving Audit ID=%s: %s',
          issue_id, assessment_id, audit_id, error)
  return app.make_response(('success', 200, [('Content-Type', 'text/html')]))
示例#19
0
def iter_issue_batches(ids, batch_size=_BATCH_SIZE):
    """Generates a sequence of batches of issues from Issue Tracker by IDs."""
    cli = issues.Client()

    for i in xrange(0, len(ids), batch_size):
        chunk = ids[i:i + batch_size]
        logger.debug('Issue ids to process: %s', chunk)
        try:
            response = cli.search({
                'issue_ids': chunk,
                'page_size': batch_size,
            })
        except integrations_errors.HttpError as error:
            logger.error('Unable to fetch Issue Tracker issues by IDs: %r',
                         error)
            return

        issue_infos = {}
        response_issues = response.get('issues') or []
        for info in response_issues:
            state = info['issueState'] or {}
            issue_info = {
                'status': state.get('status'),
                'type': state.get('type'),
                'priority': state.get('priority'),
                'severity': state.get('severity'),
                'custom_fields': state.get('custom_fields', []),
                'ccs': state.get('ccs', []),
                'assignee': state.get('assignee'),
                'reporter': state.get('reporter'),
                'verifier': state.get('verifier'),
                'hotlist_ids': state.get('hotlist_ids'),
                'component_id': state.get('component_id')
            }

            issue_infos[info['issueId']] = issue_info

        if issue_infos:
            yield issue_infos
示例#20
0
def create_issue_handler(obj, issue_tracker_info):
    """Event handler for issue object creation."""
    if not issue_tracker_info or not issue_tracker_info.get("enabled"):
        return

    # We need in flush here because we need object id for URL generation.
    db.session.flush()

    builder = issue_tracker_params_builder.IssueParamsBuilder()
    issue_tracker_params = builder.build_create_issue_tracker_params(
        obj, issue_tracker_info)

    if issue_tracker_params.is_empty():
        return

    # Query to IssueTracker.
    issue_tracker_query = issue_tracker_params.get_issue_tracker_params()

    # Parameters for creation IssuetrackerIssue object in GGRC.
    issuetracker_issue_params = issue_tracker_params.get_params_for_ggrc_object(
    )

    try:
        res = issues.Client().create_issue(issue_tracker_query)

        issue_url = integration_utils.build_issue_tracker_url(res["issueId"])
        issuetracker_issue_params["issue_url"] = issue_url
        issuetracker_issue_params["issue_id"] = res["issueId"]
    except integrations_errors.Error as error:
        logger.error(
            "Unable to create a ticket while creating object ID=%d: %s",
            obj.id, error)
        obj.add_warning("Unable to create a ticket in issue tracker.")
        issuetracker_issue_params["enabled"] = False

    # Create object in GGRC with info about issue tracker integration.
    all_models.IssuetrackerIssue.create_or_update_from_dict(
        obj, issuetracker_issue_params)
def sync_assessment_attributes():  # noqa
    """Synchronizes issue tracker ticket statuses with the Assessment statuses.

  Checks for Assessments which are in sync with Issue Tracker issues and
  updates their statuses in accordance to the corresponding Assessments
  if differ.
  """
    assessment_issues = sync_utils.collect_issue_tracker_info("Assessment",
                                                              include_ccs=True)
    if not assessment_issues:
        return
    logger.debug("Syncing state of %d issues.", len(assessment_issues))

    cli = issues.Client()
    processed_ids = set()
    for batch in sync_utils.iter_issue_batches(assessment_issues.keys()):
        for issue_id, issuetracker_state in batch.iteritems():
            issue_id, issue_info = _get_issue_info_by_issue_id(
                issue_id, assessment_issues)
            if not issue_info:
                logger.warning(
                    "Got an unexpected issue from Issue Tracker: %s", issue_id)
                continue

            object_id = issue_info["object_id"]
            processed_ids.add(issue_id)
            issue_payload = _prepare_issue_payload(issue_info)

            if not _is_need_synchronize_issue(object_id, issue_payload,
                                              issuetracker_state):
                continue

            _update_issue(cli, issue_id, object_id, issue_payload)

    logger.debug("Sync is done, %d issue(s) were processed.",
                 len(processed_ids))
    _check_missing_ids(assessment_issues, processed_ids)
示例#22
0
def sync_issue_tracker_statuses():
    """Synchronize issue tracker ticket statuses with the Assessment statuses.

  Check for Assessments which are in sync with issue tracker issues and update
  their statuses in accordance to the corresponding Assessments if differ.
  """
    issue_objects = IssuetrackerIssue.query.filter(
        IssuetrackerIssue.object_type == 'Assessment',
        IssuetrackerIssue.enabled == expression.true(),
        IssuetrackerIssue.issue_id.isnot(None),
    ).order_by(IssuetrackerIssue.object_id).all()
    for iti in issue_objects:
        asmt = iti.issue_tracked_obj
        if not asmt:
            logger.error(
                "The Assessment corresponding to the Issue Tracker Issue ID=%d "
                "does not exist.", iti.issue_id)
            continue
        status_value = issues.STATUSES.get(asmt.status)
        if status_value:
            issue_params = {
                'status': status_value,
                'type': iti.issue_type,
                'priority': iti.issue_priority,
                'severity': iti.issue_severity,
            }
        else:
            logger.error(
                "Inexistent Issue Tracker status for assessment ID=%d "
                "with status: %s.", asmt.id, status_value)
        try:
            issues.Client().update_issue(iti.issue_id, issue_params)
        except integrations_errors.Error as error:
            logger.error(
                'Unable to update IssueTracker issue status ID=%s '
                'for assessment ID=%d: %s', iti.issue_id, asmt.id, error)
def _update_issuetracker_issue(
        assessment,
        issue_tracker_info,  # noqa
        initial_assessment,
        initial_info,
        request):
    """Collects information and sends a request to update external issue."""
    # pylint: disable=too-many-locals
    issue_id = issue_tracker_info.get('issue_id')
    if not issue_id:
        return

    comments = []

    # Handle switching of 'enabled' property.
    enabled = issue_tracker_info.get('enabled', False)
    if initial_info.get('enabled', False) != enabled:
        # Add comment about toggling feature and process further.
        comments.append(_ENABLED_TMPL if enabled else _DISABLED_TMPL)
    elif not enabled:
        # If feature remains in the same status which is 'disabled'.
        return

    integration_utils.normalize_issue_tracker_info(issue_tracker_info)

    issue_params = _handle_basic_props(issue_tracker_info, initial_info)

    # Handle status update.
    status_value, status_comment = _build_status_comment(
        assessment, initial_assessment)
    if status_value:
        issue_params['status'] = status_value
        comments.append(status_comment)

    # Attach user comments if any.
    comment_text, comment_author = _get_added_comment_text(request)
    if comment_text is not None:
        builder = issue_tracker_params_builder.AssessmentParamsBuilder()
        comments.append(
            builder.COMMENT_TMPL.format(author=comment_author,
                                        comment=comment_text,
                                        model=_ASSESSMENT_MODEL_NAME,
                                        link=_get_assessment_url(assessment)))

    if comments:
        issue_params['comment'] = '\n\n'.join(comments)

    # Handle hotlist ID update.
    hotlist_id = issue_tracker_info.get('hotlist_id')
    if hotlist_id is not None and hotlist_id != initial_info.get('hotlist_id'):
        issue_params['hotlist_ids'] = [hotlist_id] if hotlist_id else []

    # handle assignee and cc_list update
    assignee_email, cc_list = _collect_assessment_emails(assessment)
    del cc_list

    if assignee_email is not None:
        issue_tracker_info['assignee'] = assignee_email
        issue_params['assignee'] = assignee_email
        issue_params['verifier'] = assignee_email

    custom_fields = []

    # handle due_date update
    due_date = issue_tracker_info.get('due_date')
    if due_date:
        custom_fields.append({
            "name": constants.CUSTOM_FIELDS_DUE_DATE,
            "value": due_date.strftime("%Y-%m-%d"),
            "type": "DATE",
            "display_string": constants.CUSTOM_FIELDS_DUE_DATE
        })

    if custom_fields:
        issue_params['custom_fields'] = custom_fields

    if issue_params:
        # Resend all properties upon any change.
        issue_params = _fill_current_value(issue_params, assessment,
                                           initial_info)
        issues.Client().update_issue(issue_id, issue_params)
示例#24
0
def update_issue_handler(obj,
                         initial_state,
                         new_issuetracker_info=None):  # noqa
    """Event handler for issue object renewal."""
    # TODO: refactor this handler to be not so complex and more generic
    if not new_issuetracker_info:
        return

    it_object = all_models.IssuetrackerIssue.get_issue("Issue", obj.id)
    old_ticket_id = None
    if it_object:
        old_ticket_id = int(it_object.issue_id) if it_object.issue_id else None

    get_id = new_issuetracker_info.get("issue_id") if new_issuetracker_info \
        else None

    new_ticket_id = int(get_id) if get_id else None

    # We should create new ticket if new ticket_id is empty, we don't store
    # IssueTrackerIssue object or it contains empty ticket_id
    needs_creation = (not it_object) or \
                     (not old_ticket_id) or (not new_ticket_id)

    if needs_creation:
        create_issue_handler(obj, new_issuetracker_info)
        if not obj.warnings:
            it_issue = all_models.IssuetrackerIssue.get_issue(
                obj.__class__.__name__, obj.id)
            new_ticket_id = it_issue.issue_id if it_issue else None
            if old_ticket_id and new_ticket_id and old_ticket_id != new_ticket_id:
                detach_issue(new_ticket_id, old_ticket_id)
        return

    if not it_object:
        return

    if (new_ticket_id != old_ticket_id) and new_issuetracker_info["enabled"]:
        link_issue(obj, new_ticket_id, new_issuetracker_info)
        if not obj.warnings:
            detach_issue(new_ticket_id, old_ticket_id)
        return

    current_issue_tracker_info = it_object.to_dict(include_issue=True,
                                                   include_private=True)

    # Build query
    builder = issue_tracker_params_builder.IssueParamsBuilder()
    issue_tracker_params = builder.build_update_issue_tracker_params(
        obj, initial_state, new_issuetracker_info, current_issue_tracker_info)

    # Query to IssueTracker.
    issue_tracker_query = issue_tracker_params.get_issue_tracker_params()

    # Parameters for creation IssuetrackerIssue object in GGRC.
    issuetracker_issue_params = issue_tracker_params.get_params_for_ggrc_object(
    )

    if not issue_tracker_params.is_empty():
        try:
            issue_id = it_object.issue_id
            issues.Client().update_issue(issue_id, issue_tracker_query)
        except integrations_errors.Error as error:
            logger.error(
                "Unable to update a ticket ID=%s while deleting"
                " issue ID=%d: %s", it_object.issue_id, obj.id, error)
            obj.add_warning("Unable to update a ticket in issue tracker.")

    if issuetracker_issue_params:
        all_models.IssuetrackerIssue.create_or_update_from_dict(
            obj, issuetracker_issue_params)
示例#25
0
 def __init__(self):
   self.break_on_errs = False
   self.client = issues.Client()
def _create_issuetracker_issue(assessment, issue_tracker_info):
  """Collects information and sends a request to create external issue."""
  issue_params = prepare_issue_json(assessment, issue_tracker_info)
  res = issues.Client().create_issue(issue_params)
  return res['issueId']