Source code for e3_build_tools.cli.e3_release

import argparse
from datetime import datetime
import logging
import sys
import yaml
from pprint import pformat
from typing import Any, Dict, Tuple

from gitlab.exceptions import GitlabGetError, GitlabAuthenticationError
from gitlab.v4 import objects

from e3_build_tools.git.registry import (
    GitLabCommitPayload,
    WrapperRegistry,
    GitLabInterface,
)
from e3_build_tools.git.tag.remote import generate_remote_tag
from e3_build_tools.logging import set_up_logger, pretty_log
from e3_build_tools.module import ModuleSource

[docs] logger = logging.getLogger(__name__)
[docs] MergeRequestPayload = Dict[str, Any]
[docs] GITLAB_WRAPPER_GROUP = 650
[docs] SPECIFICATION_GITLAB_ID = 5300
[docs] E3_TEAM_IDS = [ 235, # simonrose 368, # douglasaraujo 432, # lucasmagalhaes 260, # anderslindh1 810, # grzegorzkowalski ]
[docs] def perform_gitlab_actions( registry: WrapperRegistry, module: str, reference: str, specification: str, specification_project: objects.Project, assume_yes: bool, tag: str, commit_payload: GitLabCommitPayload, mr_payload: MergeRequestPayload, ): """Create the tag, commit, and merge request on GitLab.""" if not assume_yes: if ( not input("Are you sure you want to proceed? [Y/N] ") .strip() .lower() .startswith("y") ): return logger.info(f"Tagging wrapper e3-{module} with tag {tag}...") registry.tag_project(module, tag, reference) logger.info(f"Commiting new tag to specification {specification}...") specification_project.commits.create(commit_payload) logger.info("Creating merge request...") mr = specification_project.mergerequests.create(mr_payload) logger.info(f"Merge request: {mr.web_url}") logger.info("Module release ready!")
[docs] def create_release_commit( specification_project: objects.Project, specification_file: str, source_branch: str, contents: str, module: str, module_ver: str, ) -> GitLabCommitPayload: """Create the commit payload.""" changes = [ { "action": "update", "file_path": f"specifications/{specification_file}.yml", "content": contents, } ] return { "branch": source_branch, "start_branch": specification_project.default_branch, "commit_message": f"Release {module} {module_ver} to {specification_file}", "actions": changes, }
[docs] def create_specification_payloads( specification_project: objects.Project, specification: str, jira_key: str, creator_id: int, module: str, module_ver: str, tag: str, ) -> Tuple[GitLabCommitPayload, MergeRequestPayload]: """Create the necessary commit and merge request payloads for the release.""" try: contents = yaml.safe_load( specification_project.files.raw( file_path=f"specifications/{specification}.yml", ref=specification_project.default_branch, ).decode("utf-8") ) except GitlabGetError as e: logger.error(f"Failed to fetch specification file {specification}. Aborting.") logger.debug(f"Error message: {str(e)}") sys.exit(-1) # allow inputs both like "e3-foo" and just "foo" if module.startswith("e3-"): module = module[3:] # In case it is a new module if module not in contents["modules"]: contents["modules"][module] = {"versions": []} contents["modules"][module]["versions"].append(tag) # To avoid colliding branch names unique_id = datetime.now().strftime("%M%S") source_branch = f"{jira_key.lower()}-release-{module}-{module_ver}-{unique_id}" commit_payload = create_release_commit( specification_project, specification, source_branch, yaml.safe_dump(contents), module, module_ver, ) merge_payload: MergeRequestPayload = { "source_branch": source_branch, "target_branch": specification_project.default_branch, "description": f"Resolves {jira_key.upper()}", "title": f"{jira_key.upper()}: Release {module} {module_ver}", "reviewer_ids": [id_ for id_ in E3_TEAM_IDS if id_ != creator_id], "assignee_id": creator_id, "remove_source_branch": True, } return commit_payload, merge_payload
[docs] def e3_release( debug: bool, private_token: str, assume_yes: bool, module: str, reference: str, specification: str, jira_key: str, ): """Release an e3 module to the specified environment.""" set_up_logger(debug) if not jira_key.lower().startswith("ics-"): logger.error("JIRA key should start with ICS") sys.exit(-1) logger.info(f"Releasing {module}...\n") pretty_log("Environment", specification) if private_token: registry = WrapperRegistry( private_token=private_token, top_group=GITLAB_WRAPPER_GROUP ) else: registry = WrapperRegistry(config="ess-gitlab", top_group=GITLAB_WRAPPER_GROUP) # Although it adds some overhead, we use a separate interface for non-wrapper objects and actions if private_token: gl_interface = GitLabInterface(private_token=private_token) else: gl_interface = GitLabInterface(config="ess-gitlab") try: user_id = gl_interface.get_current_user_id() except GitlabAuthenticationError: logger.error("Failed to authenticate user. Aborting.") sys.exit(-1) # Default branch is used if no ref is provided if reference is None: try: reference = registry.get_project(module).default_branch except KeyError: sys.exit(-1) pretty_log("Reference", "* using default branch") else: pretty_log("Reference", reference) try: module_ver = registry.read_version_from( module, reference, config=ModuleSource.version_config_file, var=ModuleSource.version_config_var, validate=False, ) except Exception as e: logger.error("Failed to fetch module version. Aborting.") logger.debug(f"Error message: {str(e)}") sys.exit(-1) tag = generate_remote_tag(registry, module, reference) logger.info("") pretty_log("New module tag", tag) logger.info("") specification_project = gl_interface.get_project_by_id(SPECIFICATION_GITLAB_ID) commit_payload, mr_payload = create_specification_payloads( specification_project, specification, jira_key, user_id, module, module_ver, tag, ) logger.debug(f"Commit payload: {pformat(commit_payload)}") logger.debug(f"Merge request payload: {pformat(mr_payload)}") perform_gitlab_actions( registry, module, reference, specification, specification_project, assume_yes, tag, commit_payload, mr_payload, )
[docs] def main(): """Run the main function.""" parser = argparse.ArgumentParser() parser.add_argument("-d", "--debug", action="store_true", help=argparse.SUPPRESS) parser.add_argument( "-t", "--token", dest="private_token", help="Token for GitLab access - required if no config with token", ) parser.add_argument("-k", "--jira-key", help="Jira key for release", required=True) parser.add_argument("-y", "--assume-yes", action="store_true") parser.add_argument( "-e", "--environment", dest="specification", help="Environment to release to", default="2024q3", ) parser.add_argument("module") parser.add_argument("-r", "--reference", help="Git reference for release") e3_release(**vars(parser.parse_args()))