From 4f194e2141fc72023dcf0ace4651475839392f5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20H=C3=B6ppner?= <jonas.hoeppner@garz-fricke.com> Date: Fri, 18 Mar 2022 13:23:07 +0100 Subject: [PATCH] CI: Check: integration branch may have multiple commits As all projects are commited in the same branch the 'up-to-date' check may not only check if the first parent commit points to the master/dunfell branch. Now it is needed to loop through the history until the integration branch's commit is found. On fail a message is displayed which merge request needs to be retriggered manually. This can now also be the 'parent'-MR that triggered the complete chain. The check job is used pipeline again. The retrigger job also looks in the .gitlab-ci project for check jobs to retrigger. --- .gitlab-ci.yml | 35 ++++++++++-- check_if_integration_branch_is_up_to_date.py | 56 +++++++++++++++----- deploy_gitlab_ci.py | 8 +-- foobar-manifest-integration.yml | 19 +++++-- foobar-manifest.yml | 4 ++ gitlab-ci-integration.jinja2 | 4 ++ update_submodule.py | 6 +-- 7 files changed, 105 insertions(+), 27 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 3e01dbc1..2db6bb7b 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -19,8 +19,7 @@ default: stages: - analyze - deploy - - integrate - - test + - check workflow: rules: @@ -70,6 +69,7 @@ workflow: --manifest-project=${MANIFEST_PROJECT} --submodule=.gitlab-ci --revision=${CI_COMMIT_SHA} + --verbose ${MERGE} ${DEPLOY_TO} @@ -78,6 +78,7 @@ workflow: --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 ${DEPLOY_TO} > integration.yml @@ -99,13 +100,41 @@ deploy-foobar: ${PROJECT_ROOT}/minimal-bar trigger-foobar: - stage: integrate + stage: deploy + needs: [deploy-foobar] trigger: include: - artifact: integration.yml job: deploy-foobar strategy: depend +# -------------------------------------------------------------------------------------- +# Stage: check +# -------------------------------------------------------------------------------------- +check: + stage: check + rules: + - if: $CI_MERGE_REQUEST_IID + tags: + - infrastructure + timeout: 2m + script: + - cd ${CI_PROJECT_DIR} + - MERGE_REQUEST="${CI_MERGE_REQUEST_IID}"; + + - PROJECT_ROOT=${CI_PROJECT_ROOT_NAMESPACE}/yocto/infrastructure/ci-test + - MANIFEST_PROJECT=${PROJECT_ROOT}/minimal-manifest + - MASTER_BRANCH=master + - ./check_if_integration_branch_is_up_to_date.py + --gitlab-url=${CI_SERVER_URL} + --token=${GITBOT_TOKEN} + --manifest-project=${MANIFEST_PROJECT} + --integration-base=${MASTER_BRANCH} + --project=${CI_PROJECT_PATH} + --merge-request=${MERGE_REQUEST} + --verbose + # TODO repeat this for yocto manifest + # --------------------------------------------------------------------------------------- # Stage: deploy diff --git a/check_if_integration_branch_is_up_to_date.py b/check_if_integration_branch_is_up_to_date.py index 9a0b0527..a6eeb31e 100755 --- a/check_if_integration_branch_is_up_to_date.py +++ b/check_if_integration_branch_is_up_to_date.py @@ -4,6 +4,7 @@ import common import argparse import sys import tempfile +import logging from furl import furl from git import GitCommandError, Repo from gitlab import Gitlab, GitlabGetError @@ -17,10 +18,6 @@ def check_if_integration_branch_is_up_to_date( ): gitlab = manifest_project.manager.gitlab - integration_branch = common.integration_branch_name( - project.name, merge_request.source_branch - ) - with tempfile.TemporaryDirectory() as manifest_dir: # Construct clone url containing access token @@ -31,17 +28,24 @@ def check_if_integration_branch_is_up_to_date( # Checkout manifest try: manifest_repo = Repo.clone_from( - clone_url.url, manifest_dir, branch=integration_branch + clone_url.url, manifest_dir ) except GitCommandError as e: sys.exit("ERROR: could not clone manifest repository\n" + str(e)) - branch = common.find_gitlab_ci_integration_branch( + + # Handle gitlab-ci integration + integration_branch = common.find_gitlab_ci_integration_branch( manifest_repo, merge_request.source_branch ) - if branch is not None: - integration_branch = branch + if integration_branch is None: + integration_branch = common.integration_branch_name( + project.name, merge_request.source_branch + ) + + logging.debug("Checking integration branch: %s", integration_branch) + manifest_repo.git.checkout('-b', integration_branch, "origin/{}".format(integration_branch)) # Get branches try: @@ -53,10 +57,20 @@ def check_if_integration_branch_is_up_to_date( except IndexError: sys.exit("ERROR: branch '%s' not found" % integration_base) - # The integration branch is up to date if its parent is the integration base - up_to_date = integration_branch.commit.parents[0] == integration_base.commit + logging.debug("Checking integration base: %s", integration_base) + parent = integration_branch.commit.parents[0] + while True: + logging.debug("Parent of integration branch: %s", parent) + # The integration branch is up to date if its parent is the integration base + if parent == integration_base.commit: + logging.debug("Found match") + return True + if len(parent.parents) > 0: + parent = parent.parents[0] + else: + return False - return up_to_date + return False def main(): @@ -97,11 +111,25 @@ def main(): dest="merge_request", required=True, ) + parser.add_argument( + "--parent-merge-request", + help="""parent merge requests link, only used for a hint when the check failes""", + dest="parent_merge_request", + ) + 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) + logging.debug(args) manifest_project = common.get_project(gitlab, args.manifest_project) project = common.get_project(gitlab, args.project) merge_request = common.get_merge_request( project, args.merge_request) @@ -119,11 +147,15 @@ def main(): ): print("Integration branch is up to date.") else: + mr_url=merge_request.web_url + "/pipelines" + if args.parent_merge_request is not None: + mr_url=args.parent_merge_request + "/pipelines" + sys.exit( "Integration branch is not up to date. Please re-run the MR pipeline:\n" " 1. Open the MR pipelines page:\n" " %s\n" - " 2. Click 'Run Pipeline'" % (merge_request.web_url + "/pipelines") + " 2. Click 'Run Pipeline'" % (mr_url) ) diff --git a/deploy_gitlab_ci.py b/deploy_gitlab_ci.py index d9eb0390..24df87b0 100755 --- a/deploy_gitlab_ci.py +++ b/deploy_gitlab_ci.py @@ -25,7 +25,7 @@ def update_rev_in_gitlab_ci(repo, submodule_project, submodule_revision): repo.git.add(gitlab_ci_yml) -def deploy_into(project, submodule, revision, branch, replace_exising_branch=False): +def deploy_into(project, submodule, revision, branch, replace_existing_branch=False): """Update the submodule and include refs to the submodule in the given project. Create mergerequest if needed. @@ -34,7 +34,7 @@ def deploy_into(project, submodule, revision, branch, replace_exising_branch=Fal submodule_name (string): The name of the submodule to pull submodule_revision (hex string): The sha hash of the commit to update the submodule to branch (string): branch to update, if None, the projects default branch is used - replace_exising_branch: When an existing integration branch is found it is always replaced. + replace_existing_branch: When an existing integration branch is found it is always replaced. Returns: tuple of: branch (string): Name of the newly created integration branch @@ -47,7 +47,7 @@ def deploy_into(project, submodule, revision, branch, replace_exising_branch=Fal revision, branch, pre_commit_hook=update_rev_in_gitlab_ci, - replace_exising_branch=replace_exising_branch, + replace_existing_branch=replace_existing_branch, ) logging.debug("Integration branch: %s", integration_branch) @@ -156,7 +156,7 @@ def main(): args.submodule, args.revision, args.branch, - replace_exising_branch=len(args.projects) > 0, + replace_existing_branch=len(args.projects) > 0, ) merge_request_manifest = mr merge_requests = [] diff --git a/foobar-manifest-integration.yml b/foobar-manifest-integration.yml index 89f73876..f29b147b 100644 --- a/foobar-manifest-integration.yml +++ b/foobar-manifest-integration.yml @@ -84,9 +84,9 @@ integrate: - printenv - cd ${CI_PROJECT_DIR} - if [ -n "${CI_MERGE_REQUEST_IID}" ];then - MERGE_REQUEST="${CI_MERGE_REQUEST_IID}"; + MERGE_REQUEST="${CI_MERGE_REQUEST_IID}"; else - MERGE_REQUEST="${CI_OPEN_MERGE_REQUESTS%%,*}"; + MERGE_REQUEST="${CI_OPEN_MERGE_REQUESTS%%,*}"; fi - .gitlab-ci/integrate_into_manifest.py --gitlab-url=${CI_SERVER_URL} @@ -148,18 +148,25 @@ check: rules: - if: $CI_MERGE_REQUEST_IID # Explicitly allow externally triggered pipelines in every case - # TODO disabled for now, - if: $CI_PIPELINE_SOURCE == "pipeline" || $CI_PIPELINE_SOURCE == "api" + - if: $CI_PIPELINE_SOURCE == "pipeline" || $CI_PIPELINE_SOURCE == "api" needs: ["integrate"] tags: - infrastructure timeout: 2m script: + - printenv - cd ${CI_PROJECT_DIR} - if [ -n "${CI_MERGE_REQUEST_IID}" ];then - MERGE_REQUEST="${CI_MERGE_REQUEST_IID}"; + MERGE_REQUEST="${CI_MERGE_REQUEST_IID}"; else - MERGE_REQUEST="${CI_OPEN_MERGE_REQUESTS%%,*}"; + MERGE_REQUEST="${CI_OPEN_MERGE_REQUESTS%%,*}"; fi + - if [ -n "${parent_merge_request}" ];then + PARENT_MR="--parent-merge-request=${parent_merge_request}"; + fi + - echo "${parent_merge_request}" + - echo "${PARENT_MR}" + - echo "${MERGE_REQUEST}" - .gitlab-ci/check_if_integration_branch_is_up_to_date.py --gitlab-url=${CI_SERVER_URL} --token=${GITBOT_TOKEN} @@ -167,3 +174,5 @@ check: --integration-base=${MASTER_BRANCH} --project=${CI_PROJECT_PATH} --merge-request=${MERGE_REQUEST} + --verbose + ${PARENT_MR} diff --git a/foobar-manifest.yml b/foobar-manifest.yml index e0f33aa2..2565a4d2 100644 --- a/foobar-manifest.yml +++ b/foobar-manifest.yml @@ -55,6 +55,10 @@ retrigger: --manifest=default.xml --remote=ci-test ) + # Add the gitlab-ci project + - PROJECTS="$PROJECTS ${CI_PROJECT_ROOT_NAMESPACE}/yocto/infrastructure/gitlab-ci" + # TODO retrigger gitlab-ci integration also + # Retrigger also project in SRCREV - echo -e "Projects:\n${PROJECTS}" - for PROJECT in ${PROJECTS}; do .gitlab-ci/retrigger_mr_pipeline_jobs.py diff --git a/gitlab-ci-integration.jinja2 b/gitlab-ci-integration.jinja2 index 14ab4729..8fc3d663 100644 --- a/gitlab-ci-integration.jinja2 +++ b/gitlab-ci-integration.jinja2 @@ -31,6 +31,10 @@ default: {{ projectshort }}: stage: integrate needs: [ {{ projectneeds.project }} ] +{% if parent_merge_request is defined %} + variables: + parent_merge_request: {{ parent_merge_request }} +{% endif %} trigger: project: {{ project }} branch: {{ branch }} diff --git a/update_submodule.py b/update_submodule.py index 3caa7dfc..9a7273e5 100755 --- a/update_submodule.py +++ b/update_submodule.py @@ -17,7 +17,7 @@ def update_submodule( submodule_revision, branch=None, pre_commit_hook=None, - replace_exising_branch=False, + replace_existing_branch=False, ): """Update submodule of gitlab project to given revision @@ -28,7 +28,7 @@ def update_submodule( branch (string): branch to update, if None, the projects default branch is used pre_commit_hook: Function to be called before the actual commit is done, to add additional changes. Arguments passed: ( repo, submodule_project, submodule_revision) - replace_exising_branch: When an existing integration branch is found it is always replaced. + replace_existing_branch: When an existing integration branch is found it is always replaced. Returns: tuple of: branch (string): Name of the newly created integration branch revision (string): hexsha of the new commit @@ -124,7 +124,7 @@ def update_submodule( if existing_branch: repo.head.set_reference(existing_branch) submodule = common.get_submodule(repo, submodule_name) - if replace_exising_branch or submodule.hexsha != submodule_revision: + if replace_existing_branch or submodule.hexsha != submodule_revision: print("Replacing outdated integration branch %s" % integration_branch) repo.head.set_reference(branch) submodule = common.get_submodule(repo, submodule_name) -- GitLab