Sync job files with internal CI

Sync job files with platform-ci commit:
539c151d0cd99a5e6ca6c0e6966f6d8579fe864e

Signed-off-by: Zelalem <zelalem.aweke@arm.com>
Change-Id: Ida470e00da76188ce3987d1fa93ec758b5e0f23a
diff --git a/job/tf-gerrit-bot/gerrit_bot.py b/job/tf-gerrit-bot/gerrit_bot.py
new file mode 100644
index 0000000..1bad974
--- /dev/null
+++ b/job/tf-gerrit-bot/gerrit_bot.py
@@ -0,0 +1,232 @@
+#!/usr/bin/env python3
+#
+# Copyright (c) 2019, Arm Limited. All rights reserved.
+#
+# SPDX-License-Identifier: BSD-3-Clause
+#
+#
+# Assigns reviewers according to maintainers file.
+
+import argparse
+import os
+from pygerrit2 import GerritRestAPI, HTTPBasicAuth
+import re
+
+DEFAULT_GERRIT_URL = 'https://review.trustedfirmware.org'
+DEFAULT_GERRIT_PROJECT_NAME = 'TF-A/trusted-firmware-a'
+DEFAULT_MAINTAINERS_FILE_NAME = 'maintainers.rst'
+
+# Commit message is returned in a file list, ignore it
+COMMIT_MSG_FILE = '/COMMIT_MSG'
+
+def connect_to_gerrit(gerrit_url, gerrit_user, gerrit_password):
+    '''
+    Connect to Gerrit server.
+    The password is not a plaintext password,
+    it can be obtained from Profile/Settings/HTTP Password page.
+    Returns GerritRestAPI class.
+    '''
+
+    auth = HTTPBasicAuth(gerrit_user, gerrit_password)
+    return GerritRestAPI(url=gerrit_url, auth=auth)
+
+
+def get_open_changes(rest_api, project_name):
+    '''
+    Get list of open reviews for the project.
+    '''
+
+    # Pass DETAILED_ACCOUNTS to get owner username
+    return rest_api.get("/changes/?q=status:open%20project:" + project_name + "&o=DETAILED_ACCOUNTS")
+
+
+def get_files(rest_api, change_id):
+    '''
+    Get list of changed files for the review.
+    Commit message is removed from the list.
+    '''
+
+    files_list = rest_api.get("/changes/" + change_id + "/revisions/current/files/")
+    del files_list[COMMIT_MSG_FILE]
+
+    return files_list
+
+
+def add_reviewer(rest_api, change_id, username, dry_run):
+    '''
+    Add reviewer to the review.
+    '''
+
+    endpoint = "/changes/" + change_id + "/reviewers"
+    kwargs = {"data": {"reviewer": username}}
+
+    # Exception is thrown if username is wrong, so just print it
+    try:
+        if not dry_run:
+            rest_api.post(endpoint, **kwargs)
+    except Exception as e:
+        print("  Add reviewer failed, username: " + str(username))
+        print("  " + str(e))
+    else:
+        print("  Reviewer added, username: " + str(username))
+
+
+def parse_maintainers_file(file_path):
+    '''
+    Parse maintainers file.
+    Returns a dictionary {file_path:set{user1, user2, ...}}
+    '''
+
+    f = open(file_path, encoding='utf8')
+    file_text = f.read()
+    f.close()
+
+    FILE_PREFIX = "\n:F: "
+
+    regex = r"^:G: `(?P<user>.*)`_$(?P<paths>(" + FILE_PREFIX + r".*$)+)"
+    matches = re.finditer(regex, file_text, re.MULTILINE)
+
+    # Create a dictionary {file_path:set{user1, user2, ...}} for faster search
+    result_dict = {}
+
+    for match in matches:
+        user_name = match.group("user")
+
+        paths = match.group("paths").split(FILE_PREFIX)
+        paths.remove("")
+
+        # Fill the dictionary
+        for path in paths:
+            if path not in result_dict:
+                result_dict[path] = set()
+
+            result_dict[path].add(user_name)
+
+    return result_dict
+
+
+def get_file_maintainers(file_path, maintainers_dictionary):
+    '''
+    Returns a set of usernames(mainteiners) for the file.
+    '''
+
+    maintainers = set()
+
+    file = file_path
+
+    # Get maintainers of the file
+    maintainers_set = maintainers_dictionary.get(file)
+    if maintainers_set:
+        maintainers.update(maintainers_set)
+
+    # Get maintainers of the directories
+    while (file > "/"):
+        # Get upper directory on each step.
+        file = os.path.dirname(file)
+        path_to_check = file + "/"
+
+        maintainers_set = maintainers_dictionary.get(path_to_check)
+        if maintainers_set:
+            maintainers.update(maintainers_set)
+
+    return maintainers
+
+
+def assign_reviewers(rest_api, maintainers_dictionary, change, dry_run):
+    '''
+    Assign maintainers to the review.
+    '''
+
+    # It looks like some accounts may not have username
+    owner_username = None
+    if ('username' in change['owner']):
+        owner_username = change['owner']['username']
+
+    print("\nChange: " + str(change['id']))
+    print("  Topic: " + str(change.get('topic')))
+    print("  Owner: " + str(owner_username))
+
+    change_maintainers = set()
+
+    # Get list of all files in the change
+    files = get_files(rest_api, change['id'])
+
+    for file in files:
+        # Get all maintainers of the file
+        file_maintainers = get_file_maintainers(file, maintainers_dictionary)
+
+        if (len(file_maintainers) > 0):
+            print("  File: " + file + " maintainers: " + str(file_maintainers))
+
+        change_maintainers.update(file_maintainers)
+
+    # Don't add owner even if he is a maintainer
+    change_maintainers.discard(owner_username)
+
+    for maintainer in change_maintainers:
+        add_reviewer(rest_api, change['id'], maintainer, dry_run)
+
+
+def parse_cmd_line():
+
+    parser = argparse.ArgumentParser(
+        description="Gerrit bot",
+        epilog="""
+            Assigns reviewers according to maintainers file.
+        """
+    )
+
+    required_group = parser.add_argument_group('required arguments')
+
+    parser.add_argument("--url", "-u",
+                        help = """
+                        Gerrit URL (default: %(default)s)
+                        """,
+                        default = DEFAULT_GERRIT_URL)
+
+    parser.add_argument("--project", "-p",
+                        help = """
+                        Project name (default: %(default)s).
+                        """,
+                        default = DEFAULT_GERRIT_PROJECT_NAME)
+
+    parser.add_argument("--maintainers", "-m",
+                        help = """
+                        Path to maintainers file (default: %(default)s).
+                        """,
+                        default = DEFAULT_MAINTAINERS_FILE_NAME)
+
+    parser.add_argument("--dry-run",
+                        help = """
+                        Check maintainers, but don't add them (default: %(default)s).
+                        """,
+                        action='store_true',
+                        default = False)
+
+    required_group.add_argument("--user",
+                        help = """
+                        Gerrit user.
+                        """,
+                        required = True)
+
+    required_group.add_argument("--password",
+                        help="""
+                        Gerrit HTTP password.
+                        This is NOT a plaintext password.
+                        But the value from Profile/Settings/HTTP Password
+                        """,
+                        required = True)
+
+    return parser.parse_args()
+
+
+if __name__ == '__main__':
+
+    args = parse_cmd_line()
+
+    maintainers_dict = parse_maintainers_file(args.maintainers)
+    rest = connect_to_gerrit(args.url, args.user, args.password)
+    changes = get_open_changes(rest, args.project)
+
+    for change in changes:
+        assign_reviewers(rest, maintainers_dict, change, args.dry_run)