diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 8f48a3415a2e8ca2bdc81bff57ac775d24d4dd16..bb561ca5464f4472d64d07aeb48049fd2f0da127 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -19,7 +19,7 @@ default: stages: - analyze - integrate - - check + - build - merge workflow: @@ -62,7 +62,6 @@ yamllint: # --------------------------------------------------------------------------------------- # Stage: integrate # --------------------------------------------------------------------------------------- - .ci-test-projects: variables: PROJECT_ROOT: @@ -104,6 +103,8 @@ yamllint: - if: $CI_MERGE_REQUEST_IID when: manual allow_failure: true + variables: + MERGE: "" script: - cd ${CI_PROJECT_DIR} - ./deploy_gitlab_ci.py @@ -113,18 +114,8 @@ yamllint: --submodule=.gitlab-ci --revision=${CI_COMMIT_SHA} --verbose + ${MERGE} ${INTEGRATE_INTO} - - - ./generate_job_from_template.py - --template=gitlab-ci-integration.jinja2 - --image=${CI_IMAGE_PYTHON} - --branch="integrate/${CI_PROJECT_NAME}/${CI_COMMIT_REF_NAME}" - --manifest-project=${MANIFEST_PROJECT} - --parent_merge_request="${CI_MERGE_REQUEST_PROJECT_URL}/-/merge_requests/${CI_MERGE_REQUEST_IID}" - --verbose - ${INTEGRATE_INTO} - > integration.yml - - cat integration.yml artifacts: paths: - integration.yml @@ -139,47 +130,47 @@ integrate-ci-test: - .integrate - .ci-test-projects -trigger-yocto: - stage: integrate +# -------------------------------------------------------------------------------------- +# Stage: build +# -------------------------------------------------------------------------------------- +build-yocto: + stage: build + needs: [integrate-yocto] rules: - if: $CI_MERGE_REQUEST_IID allow_failure: true - needs: [integrate-yocto] + - if: $CI_COMMIT_BRANCH == "master" + when: manual trigger: - include: - - artifact: integration.yml - job: integrate-yocto + project: SECO-Northern-Europe/yocto/manifest + branch: "integrate/${CI_PROJECT_NAME}/${CI_COMMIT_REF_NAME}" strategy: depend -trigger-ci-test: - stage: integrate +build-ci-test: + stage: build + needs: [integrate-ci-test] rules: - if: $CI_MERGE_REQUEST_IID - needs: [integrate-ci-test] + allow_failure: true + - if: $CI_COMMIT_BRANCH == "master" + when: manual trigger: - include: - - artifact: integration.yml - job: integrate-ci-test + project: SECO-Northern-Europe/yocto/infrastructure/ci-test/minimal-manifest + branch: "integrate/${CI_PROJECT_NAME}/${CI_COMMIT_REF_NAME}" strategy: depend # -------------------------------------------------------------------------------------- # Stage: merge # -------------------------------------------------------------------------------------- .merge: + extends: .integrate stage: merge rules: - if: $CI_COMMIT_BRANCH == "master" when: manual allow_failure: true - script: - - cd ${CI_PROJECT_DIR} - - ./merge_gitlab_ci.py - --gitlab-url=${CI_SERVER_URL} - --token=${GITBOT_TOKEN} - --manifest-project=${MANIFEST_PROJECT} - --submodule=.gitlab-ci - --revision=${CI_COMMIT_SHA} - ${INTEGRATE_INTO} + variables: + MERGE: --merge merge-ci-test: extends: @@ -190,40 +181,3 @@ merge-yocto: extends: - .merge - .yocto-projects - -# -------------------------------------------------------------------------------------- -# Stage: check -# -------------------------------------------------------------------------------------- -check: - stage: check - needs: [integrate-yocto, integrate-ci-test] - rules: - # Probably this job gets removed - - when: never - - if: $CI_MERGE_REQUEST_IID - tags: - - infrastructure - timeout: 2m - script: - - cd ${CI_PROJECT_DIR} - - MERGE_REQUEST="${CI_MERGE_REQUEST_IID}"; - - - MASTER_BRANCH=dunfell - - ./check_if_integration_branch_is_up_to_date.py - --gitlab-url=${CI_SERVER_URL} - --token=${GITBOT_TOKEN} - --manifest-project=${CI_PROJECT_ROOT_NAMESPACE}/yocto/manifest - --integration-base=${MASTER_BRANCH} - --project=${CI_PROJECT_PATH} - --merge-request=${CI_MERGE_REQUEST_IID} - - # The check is done for both manifests in one job as the retrigger - # looks for jobs named check, though there can only be one - - ./check_if_integration_branch_is_up_to_date.py - --gitlab-url=${CI_SERVER_URL} - --token=${GITBOT_TOKEN} - --manifest-project - ${CI_PROJECT_ROOT_NAMESPACE}/yocto/infrastructure/ci-test/minimal-manifest - --integration-base=master - --project=${CI_PROJECT_PATH} - --merge-request=${CI_MERGE_REQUEST_IID} diff --git a/accept_merge_request.py b/accept_merge_request.py index 779d6117857b3ed5ea5be77a36267ccf0484fbc8..7500bbe98fc152075fcb66c9c6623223f623090f 100755 --- a/accept_merge_request.py +++ b/accept_merge_request.py @@ -47,7 +47,7 @@ def accept_merge_request(project, mr, rebase=False, should_remove_source_branch= print("Merge error: %s" % mr.merge_error) else: print("Merge reported success, but MR state is '%s'" % mr.state) - return False + return False, mr.sha except GitlabMRClosedError as e: # See HTTP error codes for merge requests here: @@ -73,7 +73,7 @@ def accept_merge_request(project, mr, rebase=False, should_remove_source_branch= if pipeline_pending: print("") print("Merge not possible, has to be rebased manually") - return False + return False, mr.sha elif e.response_code == 406: # Merge conflict, automatic rebase is possible @@ -82,7 +82,7 @@ def accept_merge_request(project, mr, rebase=False, should_remove_source_branch= pipeline_pending = False print("Merge not possible, but branch can be automatically rebased") if not rebase: - return False + return False, mr.sha print("Trying to rebase...") mr = common.rebase_merge_request(project, mr) if mr.merge_error: @@ -96,7 +96,7 @@ def accept_merge_request(project, mr, rebase=False, should_remove_source_branch= print("ERROR: merge not possible: %s" % e) sys.exit(critical_error) - return True + return True, mr.sha def main(): diff --git a/deploy_gitlab_ci.py b/deploy_gitlab_ci.py index 4952e63b914f0652b1aae01556b9ec8651cdf88b..7b2609e3744415a2fc16d7f2515328d43a29f8c0 100755 --- a/deploy_gitlab_ci.py +++ b/deploy_gitlab_ci.py @@ -39,6 +39,45 @@ def read_keys_from_gitlab_ci_yml(gitlab_ci_yml): return {"recipe": recipe, "masterbranch": masterbranch} +def integrate_submodule_into( + gitlab, project_name, submodule_name, new_revision, branch, commit_and_push=True +): + + gitlab_project = common.get_project(gitlab, project_name) + + ( + project_repo, + integration_branch_name, + integration_commit, + message, + ) = update_submodule_and_include_ref( + gitlab_project, + submodule_name, + new_revision, + branch, + commit_and_push=commit_and_push, + ) + if integration_branch_name is None: + return None + # ====================================== + # Store the references for creating the integration + # commit in the manifest later + # ====================================== + ret = { + "project": gitlab_project, + "repo": project_repo, + "branch": integration_branch_name, + "commit": integration_commit, + "message": message, + } + logging.debug( + "Integration branch: %s (%s)", + integration_branch_name, + integration_commit, + ) + return ret + + def create_integration_merge_request(project, integration_branch_name, source_mr=None): # Create merge request # This should be optional @@ -134,54 +173,95 @@ def main(): gitlab = Gitlab(args.gitlab_url, private_token=args.token) + # ======================================================= + # Create integration branches and commits with updates + # submodule in all projects + # ======================================================= project_integration = {} - # Update submodule in this project, create MR if needed TODO - for p in args.projects + [args.project]: + # Update submodule in all 'child' project + for p in args.projects: logging.debug("Integrate into: %s", p) - gitlab_project = common.get_project(gitlab, p) - - ( - project_repo, - integration_branch_name, - integration_commit, - message, - ) = update_submodule_and_include_ref( - gitlab_project, - args.submodule, - args.revision, - args.branch, - # Commit in all projects except the manifest - commit_and_push=(p != args.project), + res = integrate_submodule_into( + gitlab, p, args.submodule, args.revision, args.branch ) - if integration_branch_name is not None: - # ====================================== - # Store the references for creating the integration - # commit in the manifest later - # ====================================== - project_integration[p] = { - "project": gitlab_project, - "repo": project_repo, - "branch": integration_branch_name, - "commit": integration_commit, - "message": message, - } - logging.debug( - "Integration branch: %s (%s)", - integration_branch_name, - integration_commit, - ) + if res is not None: + project_integration[p] = res + + # Update submodule in manifest project + manifest_project = integrate_submodule_into( + gitlab, + args.project, + args.submodule, + args.revision, + args.branch, + commit_and_push=False, + ) + if manifest_project is not None: + project_integration[args.project] = manifest_project # If submodule is already at specified revision in all projects, exit successfully if len(project_integration) == 0: print("No integration done, changes are already included in all projects.") sys.exit(0) + # ======================================================= + # Create and merge merge_requests if needed + # ======================================================= + if args.merge: + # Get source merge request ( the one in the gitlab-ci repo) + submodule_project_path, _ = get_submodule_project_path_and_revision( + manifest_project["project"], args.submodule, args.branch + ) + submodule_project = common.get_project(gitlab, submodule_project_path) + mrs = get_merge_requests( + submodule_project, + # TODO should this be submodule_project's default branch? + target_branch="master", + commit=args.revision, + ) + if not mrs: + sys.exit( + "ERROR: could not determine source merge request for commit %s" + % args.revision + ) + source_mr = mrs[0] + + for p in args.projects: + integration = project_integration[p] + logging.debug("Create MR in %s", integration["project"].name) + mr = create_integration_merge_request( + integration["project"], integration["branch"], source_mr + ) + integration["mr"] = mr + # Now merge + logging.debug("Merge %s!%s", p, mr.iid) + + # Wait until GitLab has checked merge status + common.wait_until_merge_status_is_set(integration["project"], mr) + + # Attempt to merge + merged, integration_commit = accept_merge_request( + integration["project"], mr, rebase=True + ) + # if this has rebased the integration commit needs to be adapted: + project_integration[p]["commit"] = integration_commit + + if not merged: + sys.exit( + "Integration MR could not be merged. You have two possibilities to fix " + "this:\n" + " 1. Checkout the MR and rebase it on the current master manually, or\n" + " 2. Delete the MR (Edit -> Delete in the MR UI)\n" + "In either case restart this job afterwards in order to get it merged." + ) + + print("Successfully merged") + # ======================================================= # Now create the integration commit in the manifest # for all subprojects at once # ======================================================= - manifest_project = project_integration[args.project] manifest_file_abs = os.path.join( manifest_project["repo"].working_tree_dir, args.manifest_file ) @@ -275,58 +355,34 @@ def main(): sys.exit(0) # ============================================ - # Create merge requests if needed + # Create merge requests for the manifest # ============================================ - # Get source merge request ( the one in the gitlab-ci repo) - submodule_project_path, _ = get_submodule_project_path_and_revision( - manifest_project["project"], args.submodule, args.branch - ) - submodule_project = common.get_project(gitlab, submodule_project_path) - mrs = get_merge_requests( - submodule_project, - # TODO should this be submodule_project's default branch? - target_branch="master", - commit=args.revision, - ) - if not mrs: - sys.exit( - "ERROR: could not determine source merge request for commit %s" - % args.revision - ) - source_mr = mrs[0] - - for p in args.projects + [args.project]: - integration = project_integration[p] - logging.debug("Create MR in %s", integration["project"].name) - integration["mr"] = create_integration_merge_request( - integration["project"], integration["branch"], source_mr - ) + logging.debug("Create MR in %s", manifest_project["project"].name) + manifest_project["mr"] = create_integration_merge_request( + manifest_project["project"], manifest_project["branch"], source_mr + ) # ================================================= - # Now merge them all + # Now merge it # ================================================= # The manifest needs to be merged at last - for p in args.projects + [args.project]: - integration = project_integration[p] - mr = integration["mr"] - logging.debug("Merge %s!%s", p, mr.iid) + mr = manifest_project["mr"] + logging.debug("Merge %s!%s", args.project, mr.iid) - # Wait until GitLab has checked merge status - common.wait_until_merge_status_is_set(integration["project"], mr) + # Wait until GitLab has checked merge status + common.wait_until_merge_status_is_set(manifest_project["project"], mr) - # Attempt to merge - # TODO if this has rebased the integration commit needs to be adapted - # change the sequence - merged = accept_merge_request(integration["project"], mr, rebase=True) + # Attempt to merge + merged = accept_merge_request(manifest_project["project"], mr, rebase=True) - if not merged: - sys.exit( - "Integration MR could not be merged. You have two possibilities to fix " - "this:\n" - " 1. Checkout the MR and rebase it on the current master manually, or\n" - " 2. Delete the MR (Edit -> Delete in the MR UI)\n" - "In either case restart this job afterwards in order to get it merged." - ) + if not merged: + sys.exit( + "Integration MR could not be merged. You have two possibilities to fix " + "this:\n" + " 1. Checkout the MR and rebase it on the current master manually, or\n" + " 2. Delete the MR (Edit -> Delete in the MR UI)\n" + "In either case restart this job afterwards in order to get it merged." + ) print("Successfully merged") diff --git a/merge_gitlab_ci.py b/merge_gitlab_ci.py deleted file mode 100755 index 217419dbcb0e23a91a14a20953a69bb12ac0dc89..0000000000000000000000000000000000000000 --- a/merge_gitlab_ci.py +++ /dev/null @@ -1,198 +0,0 @@ -#!/usr/bin/env python3 -import common - -import argparse -import logging -import sys -from gitlab import Gitlab, GitlabGetError -from accept_merge_request import accept_merge_request -import update_submodule -from gitlab.v4.objects import Project - - -def find_integration_merge_request( - project: Project, submodule, revision, target_branch -): - - ( - submodule_path, - submodule_revision, - ) = update_submodule.get_submodule_project_path_and_revision( - project, submodule, target_branch - ) - logging.debug("Module: %s, Revision: %s", submodule_path, submodule_revision) - - # Get submodule project - gitlab = project.manager.gitlab - submodule_project = common.get_project(gitlab, submodule_path) - - # Get the integration branch name - integration_branch_suffix = ( - update_submodule.get_submodule_integration_branch_suffix( - submodule_project, revision - ) - ) - integration_branch_name = common.integration_branch_name( - submodule_project.name, integration_branch_suffix - ) - logging.debug(integration_branch_name) - - # Get the merge request for the branch TODO catch exception - try: - integration_branch = project.branches.get( - integration_branch_name, retry_transient_errors=True - ) - except GitlabGetError: - print("ERROR: Failed to find integration branch for {}".format(submodule)) - return None - - logging.debug(integration_branch) - commit = project.commits.get( - integration_branch.commit["id"], retry_transient_errors=True - ) - for mr in commit.merge_requests(retry_transient_errors=True): - if mr["target_branch"] == target_branch: - integration_mr = mr - break - if integration_mr is None: - print( - "ERROR: Failed to find integration merge request for %s", - integration_branch_name, - ) - return None - logging.debug(integration_mr["iid"]) - mr = project.mergerequests.get(integration_mr["iid"], retry_transient_errors=True) - - return mr - - -def main(): - parser = argparse.ArgumentParser() - parser.add_argument( - "--gitlab-url", - help="""URL to the GitLab instance""", - dest="gitlab_url", - required=True, - ) - parser.add_argument( - "--token", - help="""GitLab REST API private access token""", - dest="token", - required=True, - ) - parser.add_argument( - "--project", - "--manifest-project", - help="""name of the GitLab project""", - dest="project", - ) - parser.add_argument( - "--submodule", - help="""submodule to update""", - dest="submodule", - required=True, - ) - parser.add_argument( - "--revision", - help="""new revision for submodule""", - dest="revision", - required=True, - ) - parser.add_argument( - "--branch", - help="""project branch (if not default branch)""", - dest="branch", - required=False, - default=None, - ) - parser.add_argument( - "--merge", - help="""if set, perform merge after integration""", - dest="merge", - action="store_true", - required=False, - default=False, - ) - parser.add_argument( - "projects", - help="""List of projects the change should be deployed to additionally - to the manifest project given as named parameter.""", - nargs="*", - ) - parser.add_argument( - "-v", - "--verbose", - action="store_true", - help="""Increase verbosity.""", - ) - - args, _ = parser.parse_known_args() - if args.verbose: - logging.basicConfig(level=logging.DEBUG) - - gitlab = Gitlab(args.gitlab_url, private_token=args.token) - - # Start with the manifest here, so the subproject - # can see that they are already integrated - for p in args.projects + [args.project]: - project = common.get_project(gitlab, p) - branch = args.branch - - if branch is None: - branch = project.default_branch - - print( - "Try to merge {}({}) into {} ({})".format( - args.submodule, args.revision, project.name, branch - ) - ) - - mr = find_integration_merge_request( - project, args.submodule, args.revision, branch - ) - if mr is None: - sys.exit("ERROR: Failed to find the integration MR.") - # TODO if this ever happens, why ever, we could call - # deploy_gitlab_ci again to create a new integration - # commit or make sure the change is already integrated. - - print("Merge {}!{}: {}".format(project.name, mr.iid, mr.title)) - # Wait until GitLab has checked merge status - common.wait_until_merge_status_is_set(project, mr) - - # Attempt to merge - merged = accept_merge_request( - project, - mr, - # Only the file manifest commit may be rebased - # other rebase would require new integration commits - # TODO implement rebase - rebase=(p == args.project), - ) - - if not merged: - print( - "Integration MR could not be merged." - "To fix this:\n" - " 1. Checkout the MR {}!{} and merge it manually.\n" - " 2. Manually merge the follow up MRs in the following project:".format( - project.name, mr.iid - ) - ) - found = False - for p2 in [args.project] + args.projects: - if p2 == p: - found = True - continue - if not found: - continue - print(" {}".format(p2)) - sys.exit() - - print("Successfully merged") - - exit() - - -if __name__ == "__main__": - main() diff --git a/merge_into_manifest.py b/merge_into_manifest.py index 603a9ccd6deb2bc1759bf6cf8a2e978b2b84224e..37205cf7f31f3f7364dd1ba5e10d96c11090bb73 100755 --- a/merge_into_manifest.py +++ b/merge_into_manifest.py @@ -78,7 +78,7 @@ def merge_into_manifest( common.wait_until_merge_status_is_set(manifest_project, mr) # Attempt to merge - merged = accept_merge_request(manifest_project, mr) + merged, _ = accept_merge_request(manifest_project, mr) if not merged: # Merge failed, reintegrate the source merge request into the manifest.