CI: check if maintainers can write to MR branch (!248)
Check if users activated the 'Allow commits from members who can merge to the target branch' option in their MRs. Add the "only" parameter to each job in .gitlab.yml, so the pipeline can properly run in a "merge request specific context" and give us the environment variable that contains the MR ID.
This commit is contained in:
parent
d64c80992d
commit
e8a7926eb6
2 changed files with 169 additions and 2 deletions
|
@ -8,9 +8,24 @@ stages:
|
||||||
- first
|
- first
|
||||||
- second
|
- second
|
||||||
|
|
||||||
|
# defaults for "only"
|
||||||
|
# We need to run the CI jobs in a "merge request specific context", if CI is
|
||||||
|
# running in a merge request. Otherwise the environment variable that holds the
|
||||||
|
# merge request ID is not available. This means, we must set the "only"
|
||||||
|
# variable accordingly - and if we only do it for one job, all other jobs will
|
||||||
|
# not get executed. So have the defaults here, and use them in all jobs that
|
||||||
|
# should run on both the master branch, and in merge requests.
|
||||||
|
# https://docs.gitlab.com/ee/ci/merge_request_pipelines/index.html#excluding-certain-jobs
|
||||||
|
.only-default: &only-default
|
||||||
|
only:
|
||||||
|
- master
|
||||||
|
- merge_requests
|
||||||
|
- tags
|
||||||
|
|
||||||
# device documentation
|
# device documentation
|
||||||
wiki:
|
wiki:
|
||||||
stage: first
|
stage: first
|
||||||
|
<<: *only-default
|
||||||
before_script:
|
before_script:
|
||||||
- apk -q add python3
|
- apk -q add python3
|
||||||
script:
|
script:
|
||||||
|
@ -19,6 +34,7 @@ wiki:
|
||||||
# static code analysis (shellcheck is not in Alpine, so we use Debian)
|
# static code analysis (shellcheck is not in Alpine, so we use Debian)
|
||||||
py-sh-static:
|
py-sh-static:
|
||||||
stage: first
|
stage: first
|
||||||
|
<<: *only-default
|
||||||
image: python:3.6-slim-stretch
|
image: python:3.6-slim-stretch
|
||||||
before_script:
|
before_script:
|
||||||
- apt -q update >/dev/null
|
- apt -q update >/dev/null
|
||||||
|
@ -29,6 +45,7 @@ py-sh-static:
|
||||||
# aports checks (generic)
|
# aports checks (generic)
|
||||||
aports-static:
|
aports-static:
|
||||||
stage: first
|
stage: first
|
||||||
|
<<: *only-default
|
||||||
before_script:
|
before_script:
|
||||||
- .gitlab-ci/install_pmbootstrap.sh pytest
|
- .gitlab-ci/install_pmbootstrap.sh pytest
|
||||||
script:
|
script:
|
||||||
|
@ -43,9 +60,22 @@ aports-static:
|
||||||
- pmbootstrap.cfg
|
- pmbootstrap.cfg
|
||||||
expire_in: 1 week
|
expire_in: 1 week
|
||||||
|
|
||||||
|
# MR settings
|
||||||
|
# (Checks for "Allow commits from members who can merge to the target branch")
|
||||||
|
mr-settings:
|
||||||
|
stage: first
|
||||||
|
only:
|
||||||
|
- merge_requests
|
||||||
|
before_script:
|
||||||
|
- apk -q add python3
|
||||||
|
script:
|
||||||
|
- .gitlab-ci/check_mr_settings.py
|
||||||
|
|
||||||
# aports checks (upstream compatibility)
|
# aports checks (upstream compatibility)
|
||||||
aports-static-upstream:
|
aports-static-upstream:
|
||||||
stage: second
|
stage: second
|
||||||
|
only:
|
||||||
|
- master@postmarketOS/pmaports
|
||||||
before_script:
|
before_script:
|
||||||
- .gitlab-ci/install_pmbootstrap.sh pytest
|
- .gitlab-ci/install_pmbootstrap.sh pytest
|
||||||
script:
|
script:
|
||||||
|
@ -58,12 +88,11 @@ aports-static-upstream:
|
||||||
- log_testsuite_pmaports.txt
|
- log_testsuite_pmaports.txt
|
||||||
- pmbootstrap.cfg
|
- pmbootstrap.cfg
|
||||||
expire_in: 1 week
|
expire_in: 1 week
|
||||||
only:
|
|
||||||
- master@postmarketOS/pmaports
|
|
||||||
|
|
||||||
# build changed aports
|
# build changed aports
|
||||||
aports-build:
|
aports-build:
|
||||||
stage: second
|
stage: second
|
||||||
|
<<: *only-default
|
||||||
before_script:
|
before_script:
|
||||||
- .gitlab-ci/install_pmbootstrap.sh git
|
- .gitlab-ci/install_pmbootstrap.sh git
|
||||||
script:
|
script:
|
||||||
|
|
138
.gitlab-ci/check_mr_settings.py
Executable file
138
.gitlab-ci/check_mr_settings.py
Executable file
|
@ -0,0 +1,138 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
# Copyright 2019 Oliver Smith
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import urllib.parse
|
||||||
|
import urllib.request
|
||||||
|
|
||||||
|
|
||||||
|
def check_environment_variables():
|
||||||
|
""" Make sure that all environment variables from GitLab CI are present,
|
||||||
|
and exit right here when a variable is missing. """
|
||||||
|
keys = ["CI_MERGE_REQUEST_IID",
|
||||||
|
"CI_MERGE_REQUEST_PROJECT_PATH",
|
||||||
|
"CI_MERGE_REQUEST_SOURCE_PROJECT_URL"]
|
||||||
|
for key in keys:
|
||||||
|
if key in os.environ:
|
||||||
|
continue
|
||||||
|
print("ERROR: missing environment variable: " + key)
|
||||||
|
print("Reference: https://docs.gitlab.com/ee/ci/variables/")
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def get_url_api():
|
||||||
|
""" :returns: single merge request API URL, as documented here:
|
||||||
|
https://docs.gitlab.com/ee/api/merge_requests.html#get-single-mr """
|
||||||
|
project_path = os.environ["CI_MERGE_REQUEST_PROJECT_PATH"]
|
||||||
|
project_path = urllib.parse.quote_plus(project_path)
|
||||||
|
mr_id = os.environ["CI_MERGE_REQUEST_IID"]
|
||||||
|
|
||||||
|
url = "https://gitlab.com/api/v4/projects/{}/merge_requests/{}"
|
||||||
|
return url.format(project_path, mr_id)
|
||||||
|
|
||||||
|
|
||||||
|
def get_url_mr_edit():
|
||||||
|
""" :returns: the link where the user can edit their own MR """
|
||||||
|
url = "https://gitlab.com/{}/merge_requests/{}/edit"
|
||||||
|
return url.format(os.environ["CI_MERGE_REQUEST_PROJECT_PATH"],
|
||||||
|
os.environ["CI_MERGE_REQUEST_IID"])
|
||||||
|
|
||||||
|
|
||||||
|
def get_url_repo_settings():
|
||||||
|
""" :returns: link to the user's forked pmaports project's settings """
|
||||||
|
prefix = os.environ["CI_MERGE_REQUEST_SOURCE_PROJECT_URL"]
|
||||||
|
return prefix + "/settings/repository"
|
||||||
|
|
||||||
|
|
||||||
|
def get_mr_settings(path):
|
||||||
|
""" Get the merge request API data and parse it as JSON. Print the whole
|
||||||
|
thing on failure.
|
||||||
|
|
||||||
|
:param path: to a local file with the saved API data (will download a
|
||||||
|
fresh copy when set to None)
|
||||||
|
:returns: dict of settings data (see GitLab API reference) """
|
||||||
|
content = ""
|
||||||
|
if path:
|
||||||
|
# Read file
|
||||||
|
with open(path, encoding="utf-8") as handle:
|
||||||
|
content = handle.read()
|
||||||
|
else:
|
||||||
|
# Download from GitLab API
|
||||||
|
url = get_url_api()
|
||||||
|
print("Download " + url)
|
||||||
|
content = urllib.request.urlopen(url).read().decode("utf-8")
|
||||||
|
|
||||||
|
# Parse JSON
|
||||||
|
try:
|
||||||
|
return json.loads(content)
|
||||||
|
except:
|
||||||
|
print("ERROR: failed to decode JSON. Here's the whole content for"
|
||||||
|
" debugging:")
|
||||||
|
print("---")
|
||||||
|
print(content)
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def check_allow_push(settings):
|
||||||
|
""" :returns: True when maintainers are allowed to push to the branch
|
||||||
|
(what we want!), False otherwise """
|
||||||
|
if "allow_maintainer_to_push" not in settings:
|
||||||
|
print("ERROR: missing 'allow_maintainer_to_push' key in settings!")
|
||||||
|
print("Here are the whole settings for debugging:")
|
||||||
|
print("---")
|
||||||
|
print(settings)
|
||||||
|
exit(1)
|
||||||
|
return settings["allow_maintainer_to_push"]
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
# Parse arguments
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument("--path", help="use a local file instead of querying"
|
||||||
|
" the GitLab API", default=None)
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
# Check the merge request
|
||||||
|
check_environment_variables()
|
||||||
|
settings = get_mr_settings(args.path)
|
||||||
|
if check_allow_push(settings):
|
||||||
|
print("*** MR settings check successful!")
|
||||||
|
else:
|
||||||
|
print("*** MR settings check failed!")
|
||||||
|
print()
|
||||||
|
print("We need to be able to push changes to your merge request.")
|
||||||
|
print("So we can rebase it on master right before merging, add the")
|
||||||
|
print("MR-ID to the commit messages, etc.")
|
||||||
|
print()
|
||||||
|
print("How to fix it:")
|
||||||
|
print("1) Open the 'edit' page of your merge request:")
|
||||||
|
print(" " + get_url_mr_edit())
|
||||||
|
print("2) Enable this option and save:")
|
||||||
|
print(" 'Allow commits from members who can merge to the target"
|
||||||
|
" branch'")
|
||||||
|
print("3) Run these tests again with an empty commit in your MR:")
|
||||||
|
print(" $ git commit --allow-empty -m 'run mr-settings test again'")
|
||||||
|
print()
|
||||||
|
print("If that setting is disabled, then you have created the MR from")
|
||||||
|
print("a protected branch. When you had forked the repository from")
|
||||||
|
print("postmarketOS, the protected branch settings were copied to")
|
||||||
|
print("your fork.")
|
||||||
|
print()
|
||||||
|
print("Resolve this with:")
|
||||||
|
print("1) Open your repository settings:")
|
||||||
|
print(" " + get_url_repo_settings())
|
||||||
|
print("2) Scroll down to 'Protected Branches' and expand it")
|
||||||
|
print("3) Click 'unprotect' next to the branch from your MR")
|
||||||
|
print("4) Follow steps 1-3 from the first list again, the option")
|
||||||
|
print(" should not be disabled anymore.")
|
||||||
|
print()
|
||||||
|
print("Thank you and sorry for the inconvenience.")
|
||||||
|
return 1
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
sys.exit(main())
|
Loading…
Reference in a new issue