Skip to content
Technical preview. This site is published for review. Everything on it, including the API, tokens and module protocol, is subject to change.
Call the GitLab API

Call the GitLab API

The built-in gitlab module lets a workflow call any GitLab REST API endpoint. Its call_api action is deliberately low level: it sends the request you describe and hands back the response as data, so the workflow decides what a status code means.

Signature

load("module:gitlab", "call_api")

call_api(method, path, query=None, headers=None, body=None)
  • method and path are positional-only, for example "GET" and "/api/v4/projects/42".
  • query is a dict of string or bytes values, or lists of them. Sensitive values are refused because query parameters end up in the URL.
  • headers is a dict whose values are strings, bytes, sensitive strings, sensitive bytes, or lists of those.
  • body is a string or bytes, sensitive or not. Set Content-Type yourself through headers.

Like every action, call_api returns a future; gather it to get the result, see Futures and gather.

The 4-tuple result

The result is always a tuple (status, headers, body, error):

  • On a response of any status: status is an int, headers a dict of header name to list of bytes, body bytes, and error is None. A 404 or a 500 arrives here, as data, not as an error.
  • On a transport failure, such as GitLab being unreachable or a response body over the cap: status, headers and body are None and error is a string.

The workflow fails only when it decides to, with fail(). Check error first, then status.

Authentication

The module sends no credential of its own: AutoFlow’s own token is deliberately withheld. Authentication comes from the headers argument, and the token comes from the caller as a sensitive keyword argument to main.

headers = {"Authorization": "Bearer " + token}

"Bearer " + token is a sensitive_string because token is one. The workflow can build and pass it but never read it, and print renders it as <sensitive_string>. With the autoflow CLI, --kwarg 'token=sensitive("glpat-...")' marks the argument sensitive; over the API, send it as a sensitive_string value.

Idempotency

AutoFlow may run an action more than once: autocore retries the call into the module when it fails or times out, and a crash between the request and the record of its result runs it again. For a method that is not idempotent, anything other than GET, HEAD, PUT, DELETE, OPTIONS, TRACE and QUERY, call_api adds an Idempotency-Key header carrying the invocation’s idempotency key. The key is stable across every attempt of the same invocation and cannot be overridden from the workflow definition. An endpoint that honors the header handles the duplicate as the same request.

Response size

Response bodies are capped at 256 KiB minus a 16 KiB margin for the status and headers, so about 240 KiB. A larger body is reported as a transport failure in the fourth element of the tuple. Use paginated endpoints, per_page, or field selection when a response could be large.

Example: comment on an issue

This workflow definition posts a note on an issue and returns the note’s id.

load("module:gitlab", "call_api")

def main(w, project_id, issue_iid, message, token):
    headers = {
        "Authorization": "Bearer " + token,
        "Content-Type": "application/json",
    }
    status, _, body, err = gather(call_api(
        "POST",
        "/api/v4/projects/%d/issues/%d/notes" % (project_id, issue_iid),
        headers = headers,
        body = json.encode({"body": message}),
    ))
    if err != None:
        fail("GitLab unreachable: %s" % err)
    if status != 201:
        fail("GitLab answered %d: %s" % (status, body))
    return json.decode(str(body))["id"]

Run it with the CLI:

autoflow run -s note.star --secret-file /path/to/secret \
  --kwarg project_id=42 --kwarg issue_iid=7 \
  --kwarg 'message="Deployed to staging."' \
  --kwarg 'token=sensitive("glpat-...")'

Because POST is not idempotent, the request carries an Idempotency-Key. body is bytes, so str(body) turns it into a string before json.decode.

This example is assembled from the module’s contract and the GitLab notes API. Run it against a project of your own before relying on it.

Reading paginated results

query takes strings, so convert numbers first:

status, headers, body, err = gather(call_api(
    "GET", "/api/v4/projects/%d/issues" % project_id,
    query = {"state": "opened", "per_page": "100", "page": str(page)},
    headers = {"Authorization": "Bearer " + token},
))

Response header values are lists of bytes, so the next page is int(str(headers["X-Next-Page"][0])) when that header is present.

Related

Last updated on