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:
Oliver Smith 2019-02-09 22:18:00 +01:00
parent d64c80992d
commit e8a7926eb6
No known key found for this signature in database
GPG key ID: 5AE7F5513E0885CB
2 changed files with 169 additions and 2 deletions

View file

@ -8,9 +8,24 @@ stages:
- first
- 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
wiki:
stage: first
<<: *only-default
before_script:
- apk -q add python3
script:
@ -19,6 +34,7 @@ wiki:
# static code analysis (shellcheck is not in Alpine, so we use Debian)
py-sh-static:
stage: first
<<: *only-default
image: python:3.6-slim-stretch
before_script:
- apt -q update >/dev/null
@ -29,6 +45,7 @@ py-sh-static:
# aports checks (generic)
aports-static:
stage: first
<<: *only-default
before_script:
- .gitlab-ci/install_pmbootstrap.sh pytest
script:
@ -43,9 +60,22 @@ aports-static:
- pmbootstrap.cfg
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-static-upstream:
stage: second
only:
- master@postmarketOS/pmaports
before_script:
- .gitlab-ci/install_pmbootstrap.sh pytest
script:
@ -58,12 +88,11 @@ aports-static-upstream:
- log_testsuite_pmaports.txt
- pmbootstrap.cfg
expire_in: 1 week
only:
- master@postmarketOS/pmaports
# build changed aports
aports-build:
stage: second
<<: *only-default
before_script:
- .gitlab-ci/install_pmbootstrap.sh git
script:

138
.gitlab-ci/check_mr_settings.py Executable file
View 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())