123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283 |
- import base64
- import json
- import random
- # import re
- import os
- # import shutil
- import sys
- # import tempfile
- from azure.devops.connection import Connection
- from msrest.authentication import BasicAuthentication
- from bdscan import classSCMProvider
- from bdscan import globals
- # from bdscan import utils
- # import azure
- # import azure.devops
- import requests
- from azure.devops.v6_0.git import GitPushRef, GitRefUpdate, GitPush, GitCommitRef, GitPullRequest, \
- GitPullRequestCommentThread, Comment, GitPullRequestSearchCriteria
- class AzureProvider(classSCMProvider.SCMProvider):
- def __init__(self):
- super().__init__()
- self.scm = 'azure'
- self.azure_base_url = ''
- self.azure_api_token = ''
- self.azure_pull_request_id = ''
- self.azure_project = ''
- self.azure_project_id = ''
- self.azure_repo_id = ''
- self.azure_build_source_branch = ''
- self.azure_credentials = None
- self.azure_connection = None
- self.azure_git_client = None
- def init(self):
- globals.printdebug(f"DEBUG: Initializing Azure DevOps SCM Provider")
- self.azure_base_url = os.getenv('SYSTEM_COLLECTIONURI')
- self.azure_api_token = os.getenv('SYSTEM_ACCESSTOKEN')
- if not self.azure_api_token:
- self.azure_api_token = os.getenv('AZURE_API_TOKEN')
- self.azure_pull_request_id = os.getenv('SYSTEM_PULLREQUEST_PULLREQUESTID')
- self.azure_project = os.getenv('SYSTEM_TEAMPROJECT')
- self.azure_project_id = os.getenv('SYSTEM_TEAMPROJECTID')
- self.azure_repo_id = os.getenv('BUILD_REPOSITORY_ID')
- self.azure_build_source_branch = os.getenv('BUILD_SOURCEBRANCH')
- globals.printdebug(f'DEBUG: Azure DevOps base_url={self.azure_base_url} api_token={self.azure_api_token} '
- f'pull_request_id={self.azure_pull_request_id} project={self.azure_project} '
- f'project_id={self.azure_project_id} repo_id={self.azure_repo_id}')
- if not self.azure_base_url or not self.azure_project or not self.azure_repo_id or not self.azure_api_token \
- or not self.azure_project_id:
- print(f'BD-Scan-Action: ERROR: Azure DevOps requires that SYSTEM_COLLECTIONURI, SYSTEM_TEAMPROJECT,'
- 'SYSTEM_TEAMPROJECTID, SYSTEM_ACCESSTOKEN or AZURE_API_TOKEN, and BUILD_REPOSITORY_ID be set.')
- sys.exit(1)
- if globals.args.comment_on_pr and not self.azure_pull_request_id:
- print(f'BD-Scan-Action: ERROR: Azure DevOps requires that SYSTEM_PULLREQUEST_PULLREQUESTID be set'
- 'when operating on a pull request')
- sys.exit(1)
- if globals.args.fix_pr and not self.azure_build_source_branch:
- print(f'BD-Scan-Action: ERROR: Azure DevOps requires that BUILD_SOURCEBRANCH be set'
- 'when operating on a pull request')
- sys.exit(1)
- self.azure_credentials = BasicAuthentication('', self.azure_api_token)
- self.azure_connection = Connection(base_url=self.azure_base_url, creds=self.azure_credentials)
- # Get a client (the "core" client provides access to projects, teams, etc)
- self.azure_git_client = self.azure_connection.clients.get_git_client()
- return True
- def azure_create_branch(self, from_ref, branch_name):
- authorization = str(base64.b64encode(bytes(':' + self.azure_api_token, 'ascii')), 'ascii')
- url = f"{self.azure_base_url}/_apis/git/repositories/{self.azure_repo_id}/refs?api-version=6.0"
- headers = {
- 'Authorization': 'Basic ' + authorization
- }
- body = [
- {
- 'name': f"refs/heads/{branch_name}",
- 'oldObjectId': '0000000000000000000000000000000000000000',
- 'newObjectId': from_ref
- }
- ]
- if globals.debug > 0:
- print("DEBUG: perform API Call to ADO: " + url + " : " + json.dumps(body, indent=4, sort_keys=True) + "\n")
- r = requests.post(url, json=body, headers=headers)
- if r.status_code == 200:
- if globals.debug > 0:
- print(f"DEBUG: Success creating branch")
- print(r.text)
- return True
- else:
- print(f"BD-Scan-Action: ERROR: Failure creating branch: Error {r.status_code}")
- print(r.text)
- return False
- def comp_commit_file_and_create_fixpr(self, comp, files_to_patch):
- if len(files_to_patch) == 0:
- print('BD-Scan-Action: WARN: Unable to apply fix patch - cannot determine containing package file')
- return False
- new_branch_seed = '%030x' % random.randrange(16 ** 30)
- new_branch_name = f"synopsys-enablement-{new_branch_seed}"
- globals.printdebug(f"DEBUG: Get commit for head of {self.azure_build_source_branch}'")
- commits = self.azure_git_client.get_commits(self.azure_repo_id, None)
- head_commit = commits[0]
- globals.printdebug(f"DEBUG: Head commit={head_commit.commit_id}")
- globals.printdebug(f"DEBUG: Creating new ref 'refs/heads/{new_branch_name}'")
- self.azure_create_branch(head_commit.commit_id, new_branch_name)
- gitRefUpdate = GitRefUpdate()
- gitRefUpdate.name = f"refs/heads/{new_branch_name}"
- gitRefUpdate.old_object_id = head_commit.commit_id
- gitPush = GitPush()
- gitPush.commits = []
- gitPush.ref_updates = [gitRefUpdate]
- # for file_to_patch in globals.files_to_patch:
- for pkgfile in files_to_patch:
- globals.printdebug(f"DEBUG: Upload file '{pkgfile}'")
- try:
- with open(files_to_patch[pkgfile], 'r') as fp:
- new_contents = fp.read()
- except Exception as exc:
- print(f"BD-Scan-Action: ERROR: Unable to open package file '{files_to_patch[pkgfile]}'"
- f" - {str(exc)}")
- return False
- gitCommitRef = GitCommitRef()
- gitCommitRef.comment = "Added Synopsys pipeline template"
- gitCommitRef.changes = [
- {
- 'changeType': 'edit',
- 'item': {
- 'path': pkgfile
- },
- 'newContent': {
- 'content': new_contents,
- 'contentType': 'rawText'
- }
- }
- ]
- gitPush.commits.append(gitCommitRef)
- # globals.printdebug(f"DEBUG: Update file '{pkgfile}' with commit message '{commit_message}'")
- # file = repo.update_file(pkgfile, commit_message, new_contents, orig_contents.sha, branch=new_branch_name)
- push = self.azure_git_client.create_push(gitPush, self.azure_repo_id)
- if not push:
- print(f"BD-Scan-Action: ERROR: Create push failed")
- sys.exit(1)
- pr_title = f"Black Duck: Upgrade {comp.name} to version {comp.goodupgrade} fix known security vulerabilities"
- pr_body = f"\n# Synopsys Black Duck Auto Pull Request\n" \
- f"Upgrade {comp.name} from version {comp.version} to " \
- f"{comp.goodupgrade} in order to fix security vulnerabilities:\n\n"
- gitPullRequest = GitPullRequest()
- gitPullRequest.source_ref_name = f"refs/heads/{new_branch_name}"
- gitPullRequest.target_ref_name = self.azure_build_source_branch
- gitPullRequest.title = pr_title
- gitPullRequest.description = pr_body
- pull = self.azure_git_client.create_pull_request(gitPullRequest, self.azure_repo_id)
- if not pull:
- print(f"BD-Scan-Action: ERROR: Create pull request failed")
- sys.exit(1)
- return True
- def comp_fix_pr(self, comp):
- ret = True
- globals.printdebug(f"DEBUG: Fix '{comp.name}' version '{comp.version}' in "
- f"file '{comp.projfiles}' using ns '{comp.ns}' to version "
- f"'{comp.goodupgrade}'")
- pull_request_title = f"Black Duck: Upgrade {comp.name} to version " \
- f"{comp.goodupgrade} to fix known security vulnerabilities"
- search_criteria = None # GitPullRequestSearchCriteria()
- pulls = self.azure_git_client.get_pull_requests(self.azure_repo_id, search_criteria)
- for pull in pulls:
- if pull_request_title in pull.title:
- globals.printdebug(f"DEBUG: Skipping pull request for {comp.name}' version "
- f"'{comp.goodupgrade} as it is already present")
- return
- files_to_patch = comp.do_upgrade_dependency()
- if len(files_to_patch) == 0:
- print('BD-Scan-Action: WARN: Unable to apply fix patch - cannot determine containing package file')
- return False
- if not self.comp_commit_file_and_create_fixpr(comp, files_to_patch):
- ret = False
- return ret
- def pr_comment(self, comment):
- pr_threads = self.azure_git_client.get_threads(self.azure_repo_id, self.azure_pull_request_id)
- existing_thread = None
- existing_comment = None
- for pr_thread in pr_threads:
- for pr_thread_comment in pr_thread.comments:
- if pr_thread_comment.content and globals.comment_on_pr_header in pr_thread_comment.content:
- existing_thread = pr_thread
- existing_comment = pr_thread_comment
- comments_markdown = f"# {globals.comment_on_pr_header}\n{comment}"
- if len(comments_markdown) > 65535:
- comments_markdown = comments_markdown[:65535]
- if existing_comment is not None:
- globals.printdebug(f"DEBUG: Update/edit existing comment for PR #{self.azure_pull_request_id}\n"
- f"{comments_markdown}")
- pr_thread_comment = Comment()
- pr_thread_comment.parent_comment_id = 0
- pr_thread_comment.content = comments_markdown
- pr_thread_comment.comment_type = 1
- retval = self.azure_git_client.update_comment(pr_thread_comment, self.azure_repo_id,
- self.azure_pull_request_id, existing_thread.id,
- existing_comment.id)
- globals.printdebug(f"DEBUG: Updated thread, retval={retval}")
- else:
- globals.printdebug(f"DEBUG: Create new thread for PR #{self.azure_pull_request_id}")
- pr_thread_comment = Comment()
- pr_thread_comment.parent_comment_id = 0
- pr_thread_comment.content = comments_markdown
- pr_thread_comment.comment_type = 1
- pr_thread = GitPullRequestCommentThread()
- pr_thread.comments = [pr_thread_comment]
- pr_thread.status = 1
- retval = self.azure_git_client.create_thread(pr_thread, self.azure_repo_id, self.azure_pull_request_id)
- globals.printdebug(f"DEBUG: Created thread, retval={retval}")
- return True
- def set_commit_status(self, is_ok):
- globals.printdebug(f"WARNING: Azure DevOps does not support set_commit_status")
- return
- def check_files_in_pull_request(self):
- globals.printdebug(f"WARNING: Azure DevOps does not support querying changed files, returning True")
- found = True
- return found
- def check_files_in_commit(self):
- globals.printdebug(f"WARNING: Azure DevOps does not support querying committed files, returning True")
- found = True
- return found
|