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)methodandpathare positional-only, for example"GET"and"/api/v4/projects/42".queryis a dict of string or bytes values, or lists of them. Sensitive values are refused because query parameters end up in the URL.headersis a dict whose values are strings, bytes, sensitive strings, sensitive bytes, or lists of those.bodyis a string or bytes, sensitive or not. SetContent-Typeyourself throughheaders.
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:
statusis an int,headersa dict of header name to list of bytes,bodybytes, anderrorisNone. 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,headersandbodyareNoneanderroris 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.
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
- Poll until ready wraps
call_apiin a derived action and polls it. - Policy enforcement uses the module’s other action,
post_value, to hand a channel to GitLab.