|
| 1 | +#!/usr/bin/env python |
| 2 | + |
| 3 | +import argparse |
| 4 | +import arrow |
| 5 | +import json |
| 6 | +import logging |
| 7 | +import sys |
| 8 | + |
| 9 | +from blackduck.HubRestApi import HubInstance, object_id |
| 10 | + |
| 11 | +def get_policy_status_comment(policy_status): |
| 12 | + '''Return the most recent policy status comment |
| 13 | + ''' |
| 14 | + comments = list() |
| 15 | + for view in policy_status.get('policyRuleViolationViews', []): |
| 16 | + for record in view.get('updatedBy', []): |
| 17 | + comments.append((record['comment'], arrow.get(record['updatedAt']))) |
| 18 | + comments = sorted(comments, key=lambda c: c[1]) |
| 19 | + return comments[-1] |
| 20 | + |
| 21 | +def components_with_overrides(hub_instance, version): |
| 22 | + # components_url = hub_instance.get_link(version, "components") + "?limit=999" |
| 23 | + # components = hub_instance.execute_get(components_url).json().get('items', []) |
| 24 | + components = hub_instance.get_version_components(version, limit=999).get('items', []) |
| 25 | + have_overrides = [c for c in components if c['policyStatus'] == 'IN_VIOLATION_OVERRIDDEN'] |
| 26 | + # need to retrieve override comments |
| 27 | + for component in have_overrides: |
| 28 | + policy_status_url = hub_instance.get_link(component, "policy-status") |
| 29 | + # Note: The public endpoint/media type does NOT return the override comment |
| 30 | + # shown in the GUI. So, using a private internal media type, below, which allows retrieval |
| 31 | + # of the comment info. |
| 32 | + # Ref: <blackduck_url>/api-doc/public.html#policy-status-representation |
| 33 | + # |
| 34 | + # Warning: Use of an internal media type is not supported. What does this mean? |
| 35 | + # It can break on a future release of Black Duck so this script will |
| 36 | + # need to be re-tested for each future version of Black Duck. And, if it fails, |
| 37 | + # it will need to be reworked accordingly |
| 38 | + custom_headers = {'Accept':'application/vnd.blackducksoftware.internal-1+json'} |
| 39 | + policy_status = hub_instance.execute_get(policy_status_url, custom_headers=custom_headers).json() |
| 40 | + comment, comment_dt = get_policy_status_comment(policy_status) |
| 41 | + component['comment'] = comment |
| 42 | + component['comment_dt'] = comment_dt |
| 43 | + return have_overrides |
| 44 | + |
| 45 | +def clone_policy_status(hub_instance, version, component_overrides): |
| 46 | + overrides_by_name = {f"{c['componentName']}:{c['componentVersionName']}":c for c in component_overrides} |
| 47 | + components = hub_instance.get_version_components(version, limit = 999).get('items', []) |
| 48 | + for component in components: |
| 49 | + cn = f"{component['componentName']}:{component['componentVersionName']}" |
| 50 | + override = overrides_by_name.get(cn) |
| 51 | + if override: |
| 52 | + policy_status_url = hub_instance.get_link(component, "policy-status") |
| 53 | + # TODO: use comment_dt from override or accept the dt for when we cloned? |
| 54 | + data = { |
| 55 | + 'approvalStatus': override['policyStatus'], |
| 56 | + 'comment': override['comment'], |
| 57 | + } |
| 58 | + response = hub_instance.execute_put(policy_status_url, data=data) |
| 59 | + if response.status_code == 202: |
| 60 | + logging.info(f"Cloned override to {cn} in version {version['versionName']}") |
| 61 | + else: |
| 62 | + logging.error(f"Failed to clone overide to {cn} in version {version['versionName']}") |
| 63 | + else: |
| 64 | + logging.debug(f"{cn} not in overrides to clone") |
| 65 | + |
| 66 | + |
| 67 | +def clone_overrides(hub_instance, project, baseline_version_name): |
| 68 | + versions = hub_instance.get_project_versions(project).get('items', []) |
| 69 | + version_names = ",".join([v['versionName'] for v in versions]) |
| 70 | + assert baseline_version_name in version_names, "The baseline version must exist in the project" |
| 71 | + |
| 72 | + logging.debug(f"Cloning overrides from version '{baseline_version_name}' to the other versions ({version_names}) in project {project['name']}") |
| 73 | + baseline_version = next(v for v in versions if v['versionName'] == baseline_version_name) |
| 74 | + overrides_to_clone = components_with_overrides(hub_instance, baseline_version) |
| 75 | + versions_to_clone_to = [v for v in versions if v['versionName'] != baseline_version_name] |
| 76 | + for version in versions_to_clone_to: |
| 77 | + clone_policy_status(hub_instance, version, overrides_to_clone) |
| 78 | + |
| 79 | +parser = argparse.ArgumentParser("Clone component everrides from a baseline version to all other versions in the project") |
| 80 | +parser.add_argument("-p", "--project", help="Specify a project to do the override cloning on (default is to clone overrides in all projects)") |
| 81 | +parser.add_argument("-b", "--baseline_version", default="baseline", help="The name of the baseline version from which to clone overrides") |
| 82 | +args = parser.parse_args() |
| 83 | + |
| 84 | + |
| 85 | +logging.basicConfig(format='%(asctime)s:%(levelname)s:%(message)s', stream=sys.stderr, level=logging.DEBUG) |
| 86 | +logging.getLogger("requests").setLevel(logging.WARNING) |
| 87 | +logging.getLogger("urllib3").setLevel(logging.WARNING) |
| 88 | +logging.getLogger("blackduck").setLevel(logging.WARNING) |
| 89 | + |
| 90 | +hub = HubInstance() |
| 91 | + |
| 92 | +if args.project: |
| 93 | + projects = [hub.get_project_by_name(args.project)] |
| 94 | +else: |
| 95 | + projects = hub.get_projects(limit=999).get('items', []) |
| 96 | + |
| 97 | +for project in projects: |
| 98 | + clone_overrides(hub, project, args.baseline_version) |
| 99 | + |
0 commit comments