Report Build Status to GitLab/GitHub

This example will introduce you to a workflow that can be leveraged to update a pipeline’s build status on a remote GitLab or GitHub repository within your source project’s CI pipeline:

../../_images/build_status.svg

We will be using the GitLab/GitHub API to post the build status to a specific commit. This can easily be accomplished with newer version of curl and GitLab’s file CI/CD variable type. In this example secrets are stored during the life of the job, limited to a user’s permissions, and will not be visable in either the job log or to other users on multi-tenant systems.

For GitLab you will require Pesronal Access Token (or Project Access Token if targeting a self-hosted instance) with a Developer role in your target project and an API scope.

Add this to your source projec as a CI/CD variable:

We strongly advise that the GITLAB_CURL_HEADERS variable is limited to specific environment to prevent including it in every job in your pipeline.

Finally once you’ve updated your project’s CI/CD settings and added the necessary script you should incorporate it into your pipeline (the .gitlab-ci.yml file). The example jobs/stages below are envisioned as additions to already existing stages using the .pre and .post functionality.

 1.report-status:
 2  variables:
 3    STATUS_GITLAB: https://gitlab.com
 4    STATUS_PROJECT: 123456
 5    STATUS_NAME: code
 6  script:
 7    # For complete details on the GitLab API please see:
 8    # https://docs.gitlab.com/ee/api/commits.html#post-the-build-status-to-a-commit
 9    - curl -X POST -H @${GITLAB_CURL_HEADERS} ${STATUS_GITLAB}/api/v4/projects/${STATUS_PROJECT}/statuses/${CI_COMMIT_SHA}?state=${CI_JOB_NAME}\&name=${STATUS_NAME}\&target_url=${CI_PIPELINE_URL}
10  environment:
11    name: reporting-gitlab
12  dependencies: []
13
14pending:
15  stage: .pre
16  extends:
17    - .report-status
18
19success:
20  stage: .post
21  extends:
22    - .report-status
23
24failure:
25  stage: .post
26  extends:
27    - .report-status
28  rules:
29    - when: on_failure

For GitHub you will require an access token. with the repo:status scope.

Add this to your source projec as a CI/CD variable:

We strongly advise that the GITHUB_CURL_HEADERS variable is limited to specific environment to prevent including it in every job in your pipeline.

Finally once you’ve updated your project’s CI/CD settings and added the necessary script you should incorporate it into your pipeline (the .gitlab-ci.yml file). The example jobs/stages below are envisioned as additions to already existing stages using the .pre and .post functionality.

 1.report-status:
 2  variables:
 3    STATUS_PROJECT: group/project
 4    STATUS_NAME: example
 5  script:
 6    # For complete details on the GitHub API please see:
 7    # https://developer.github.com/v3/repos/statuses
 8    - curl -X POST -H @${GITHUB_CURL_HEADERS} https://api.github.com/repos/${STATUS_PROJECT}/statuses/${CI_COMMIT_SHA}?state=${CI_JOB_NAME}\&context=${STATUS_NAME}\&target_url=${CI_PIPELINE_URL}
 9  environment:
10    name: reporting-github
11  dependencies: []
12
13pending:
14  stage: .pre
15  extends:
16    - .report-status
17
18success:
19  stage: .post
20  extends:
21    - .report-status
22
23failed:
24  stage: .post
25  extends:
26    - .report-status
27  rules:
28    - when: on_failure

After completion trigger a pipeline from the target project and review the results on your source project:

../../_images/gitlab-results.png

Note that in either case you will need to ensure that the GitLab CI project pipelines are publicly available or that necessary reviewers have access to the instance for the link (CI_PIPELINE_URL) to function. Else they will only be able to observe the status with not additioanl context.

Python Scripts

If using an older deployment with Curl prior v7.55 it is advisable to utlize a Python script on multi-tenant resources:

 1#!/usr/bin/env python3
 2
 3import os
 4import requests
 5
 6# In order to improve the readability of this example we've seperated
 7# obtaining the target environment variables from the request.
 8# Note that any variables prefixed by "BUILDSTATUS_" are established
 9# by the project maintainer, else they are provided by the runner.
10
11project = os.getenv('BUILDSTATUS_PROJECT')
12api = os.getenv('BUILDSTATUS_APIURL')
13token = os.getenv('BUILDSTATUS_TOKEN')
14name = os.getenv('BUILDSTATUS_JOB')
15
16sha = os.getenv('CI_COMMIT_SHA')
17state = os.getenv('CI_JOB_NAME')
18url = os.getenv('CI_PIPELINE_URL')
19
20# https://docs.gitlab.com/ee/api/commits.html#post-the-build-status-to-a-commit
21# Load and identify requirement from environment.
22status = {
23    'id': project,
24    'sha': sha,
25    'state': state,
26    'name': name,
27    'target_url': url
28}
29r = requests.post("{}/projects/{}/statuses/{}".format(api, project, sha),
30                headers={'PRIVATE-TOKEN': token},
31                data=status)
32if r.status_code != 201:
33    print(r.text)
34    exit(1)

In the above script we identify all variables from the environment. You may recognize that we have used several predefined variables that are provided to each CI job as well as several of our own:

 1#!/usr/bin/env python3
 2
 3import json
 4import os
 5import requests
 6
 7# In order to improve the readability of this example we've seperated
 8# obtaining the target environment variables from the request.
 9# Note that any variables prefixed by "BUILDSTATUS_" are established
10# by the project maintainer, else they are provided by the runner.
11
12owner = os.getenv('BUILDSTATUS_OWNER')
13project = os.getenv('BUILDSTATUS_PROJECT')
14api = os.getenv('BUILDSTATUS_APIURL')
15token = os.getenv('BUILDSTATUS_TOKEN')
16name = os.getenv('BUILDSTATUS_JOB')
17
18sha = os.getenv('CI_COMMIT_SHA')
19state = os.getenv('CI_JOB_NAME')
20url = os.getenv('CI_PIPELINE_URL')
21
22# https://developer.github.com/v3/repos/statuses/
23# Load and identify requirement from environment.
24status = {
25    'state': state,
26    'target_url': url,
27    'context': name
28}
29
30r = requests.post("{}/repos/{}/{}/statuses/{}".format(api, owner, project, sha),
31                headers={'Authorization': 'token {}'.format(token)},
32                data=json.dumps(status))
33if r.status_code != 201:
34    print(r.text)
35    exit(1)

In the above script we identify all variables from the environment. You may recognize that we have used several predefined variables that are provided to each CI job as well as several of our own:

  • BUILDSTATUS_OWNER: Project owner/group as it appears in the repository’s url.

  • BUILDSTATUS_PROJECT: Project name as it appears in the repository’s url.

  • BUILDSTATUS_APIURL: The sites API url (e.g. https://api.github.com)

  • BUILDSTATUS_TOKEN: A repo:status scoped access token stored in the source GitLab as a masked CI/CD variable.

  • BUILDSTATUS_JOB: Name of the job as it will appear on the source project.