"""Module for syncing a repo with Copybara and setting up configurations.""" import argparse import fileinput import subprocess import os import sys from typing import Optional from github import GithubIntegration from buildscripts.util.read_config import read_config_file from evergreen.api import RetryingEvergreenApi # Commit hash of Copybara to use (v20251110) COPYBARA_COMMIT_HASH = "3f050c9e08b84aeda98875bf1b02a3288d351333" def run_command(command): # noqa: D406 """ Execute a shell command and return its standard output (`stdout`). Args: command (str): The shell command to be executed. Returns: str: The standard output of the executed command. Raises: subprocess.CalledProcessError: If the command execution fails. """ try: return subprocess.run(command, shell=True, check=True, text=True, capture_output=True).stdout except subprocess.CalledProcessError as err: print(f"Error while executing: '{command}'.\n{err}\nStandard Error: {err.stderr}") raise def create_mongodb_bot_gitconfig(): """Create the mongodb-bot.gitconfig file with the desired content.""" content = """ [user] name = MongoDB Bot email = mongo-bot@mongodb.com """ gitconfig_path = os.path.expanduser("~/mongodb-bot.gitconfig") with open(gitconfig_path, 'w') as file: file.write(content) print("mongodb-bot.gitconfig file created.") def get_installation_access_token(app_id: int, private_key: str, installation_id: int) -> Optional[str]: # noqa: D406,D407,D413 """ Obtain an installation access token using JWT. Args: - app_id (int): The application ID for GitHub App. - private_key (str): The private key associated with the GitHub App. - installation_id (int): The installation ID of the GitHub App for a particular account. Returns: - Optional[str]: The installation access token. Returns `None` if there's an error obtaining the token. """ integration = GithubIntegration(app_id, private_key) auth = integration.get_access_token(installation_id) if auth: return auth.token else: print("Error obtaining installation token") return None def send_failure_message_to_slack(expansions): """ Send a failure message to a specific Slack channel when the Copybara task fails. :param expansions: Dictionary containing various expansion data. """ current_version_id = expansions.get("version_id", None) error_msg = ( "Evergreen task '* Copybara Sync Between Repos' failed\n" f"For more details: .") evg_api = RetryingEvergreenApi.get_api(config_file=".evergreen.yml") evg_api.send_slack_message( target="#devprod-build-automation", msg=error_msg, ) def main(): """Clone the Copybara repo, build its Docker image, and set up and run migrations.""" parser = argparse.ArgumentParser() parser.add_argument("--expansion-file", dest="expansion_file", type=str, help="Location of expansions file generated by evergreen.") args = parser.parse_args() # Check if the copybara directory already exists if os.path.exists('copybara'): print("Copybara directory already exists.") else: run_command("git clone https://github.com/10gen/copybara.git") # Checkout the specific commit of Copybara we want to use run_command(f"cd copybara && git checkout {COPYBARA_COMMIT_HASH}") # Navigate to the Copybara directory and build the Copybara Docker image run_command("cd copybara && docker build --rm -t copybara .") # Read configurations expansions = read_config_file(args.expansion_file) token_mongodb_mongo = get_installation_access_token( expansions["app_id_copybara_syncer"], expansions["private_key_copybara_syncer"], expansions["installation_id_copybara_syncer"], ) token_10gen_mongo = get_installation_access_token( expansions["app_id_copybara_syncer_10gen"], expansions["private_key_copybara_syncer_10gen"], expansions["installation_id_copybara_syncer_10gen"], ) tokens_map = { "https://github.com/mongodb/mongo.git": token_mongodb_mongo, "https://github.com/10gen/mongo.git": token_10gen_mongo, } # Create the mongodb-bot.gitconfig file as necessary. create_mongodb_bot_gitconfig() current_dir = os.getcwd() config_file = f"{current_dir}/copy.bara.sky" # Overwrite repo urls in copybara config in-place with fileinput.FileInput(config_file, inplace=True) as file: for line in file: token = None for repo, value in tokens_map.items(): if repo in line: token = value if token: print( line.replace( "https://github.com", f"https://x-access-token:{token}@github.com", ), end="", ) else: print(line, end="") # Set up the Docker command and execute it docker_cmd = [ "docker run", "-v ~/.ssh:/root/.ssh", "-v ~/mongodb-bot.gitconfig:/root/.gitconfig", f'-v "{config_file}":/usr/src/app/copy.bara.sky', "-e COPYBARA_CONFIG='copy.bara.sky'", "-e COPYBARA_SUBCOMMAND='migrate'", "-e COPYBARA_OPTIONS='-v'", "copybara copybara", ] try: run_command(" ".join(docker_cmd)) except subprocess.CalledProcessError as err: error_message = str(err.stderr) acceptable_error_messages = [ # Indicates the two repositories are identical "No new changes to import for resolved ref", # Indicates differences exist but no changes affect the destination, for example: exclusion rules "Iterative workflow produced no changes in the destination for resolved ref", # Indicates the commits have already been synced over with another copybara task "Updates were rejected because the remote contains work that you do", ] if any(acceptable_message in error_message for acceptable_message in acceptable_error_messages): return # Send a failure message to #devprod-build-automation if the Copybara sync task fails. send_failure_message_to_slack(expansions) if __name__ == "__main__": main()