GitLab CI integration
API testing in GitLab CI
The first-party GitLab CI/CD component is public in source and awaiting catalog publication. Total Shift Left's public REST API lets you trigger test packs, gate merge requests on pass-rate thresholds, and pull results into any pipeline today.
The Total Shift Left CI/CD component is not in the GitLab CI/CD Catalog yet, and the totalshiftleft/ci-runner image it uses is not on Docker Hub, so an include: component: reference will not resolve today. Its source is public — see gitlab-component/. Everything below is a real, working pattern using the public REST API directly.
What this integration gives you
The job below runs a Total Shift Left test pack as part of any GitLab CI pipeline, using nothing but curl and jq against the public REST API: authenticate, trigger a run, and check the result. Use it to gate merge requests on API quality, run scheduled regression suites against staging environments, or chain API tests into broader release pipelines.
Quality gates on merge requests
Exit non-zero when the pass rate misses your threshold — blocks the merge via GitLab's "Pipelines must succeed" setting.
JSON results
The execution endpoint returns structured JSON; pipe it into your own reporting or artifact upload step.
Self-managed friendly
Works on any GitLab Runner — shared, self-managed, or air-gapped — as long as the runner can reach your Shift-Left deployment.
Protected variable support
Credentials stay masked in logs and only expose on protected branches via GitLab's built-in CI/CD variable scoping.
.gitlab-ci.yml example
Drop this into your .gitlab-ci.yml. Stores credentials as protected, masked CI/CD variables, runs on every merge request and main-branch push, and fails the job when the pass rate drops below 95%.
stages:
- test
api-tests:
stage: test
image: alpine:latest
variables:
SHIFTLEFT_URL: $SHIFTLEFT_URL
SHIFTLEFT_EMAIL: $SHIFTLEFT_EMAIL
SHIFTLEFT_PASSWORD: $SHIFTLEFT_PASSWORD
TEST_PACK_ID: $SHIFTLEFT_TEST_PACK_ID
before_script:
- apk add --no-cache curl jq
script:
- |
TOKEN=$(curl -s -X POST "$SHIFTLEFT_URL/api/v1/auth/login" \
-H "Content-Type: application/json" \
-d "{\"email\":\"$SHIFTLEFT_EMAIL\",\"password\":\"$SHIFTLEFT_PASSWORD\"}" \
| jq -r .token)
EXEC_ID=$(curl -s -X POST "$SHIFTLEFT_URL/api/v1/test-packs/$TEST_PACK_ID/run" \
-H "Authorization: Bearer $TOKEN" | jq -r .executionId)
# Poll until the run has a result, up to 2 minutes
for i in $(seq 1 24); do
RESULT=$(curl -s "$SHIFTLEFT_URL/api/v1/executions/$EXEC_ID" -H "Authorization: Bearer $TOKEN")
[ "$(echo "$RESULT" | jq -r '.passRate != null')" = "true" ] && break
sleep 5
done
echo "$RESULT" | jq .
# Fails closed: a missing/null/non-numeric passRate (auth error, bad execution id, run
# still in progress after the timeout above) is treated as a failure, not a pass.
echo "$RESULT" | jq -e '(.passRate // 0) >= 95' > /dev/null
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH == "main"CI/CD variables
Configure these as GitLab CI/CD variables at the project, group, or instance level. Use the Protected and Masked flags for any value containing credentials.
| Variable | Description |
|---|---|
| SHIFTLEFT_URL | Base URL of your Total Shift Left deployment. Store as a CI/CD variable; mark as masked. |
| SHIFTLEFT_EMAIL / SHIFTLEFT_PASSWORD | Authentication credentials. Store as masked, protected CI/CD variables. |
| SHIFTLEFT_TEST_PACK_ID | ID of the test pack to execute. Project- or group-level variable. |
Quality gates on merge requests
Combine the script's exit code with GitLab's merge request settings to block merges on API quality regressions:
- In GitLab: Settings → Merge Requests → Pipelines must succeed.
- Edit the pass-rate threshold inline in the
jq -echeck at the end of the script (default is95). - Extend the script with your own logic — error-test counts, specific endpoint checks — since you control the shell, not a fixed set of image inputs.
- Optional: require a manual approver from the QE / Architecture team for protected branches.
For data-flow specifics — including how the runner reaches a self-hosted Total Shift Left deployment — see the security page and deployment page.
The component, once it is published
Not yet installableThe component source is public and wraps @totalshiftleft/ci — the single implementation behind the GitHub Action, the Azure DevOps task, the CircleCI orb and the Bitbucket pipe, so it grades a run identically to all of them. It is not in the CI/CD Catalog yet, so the snippet below is what the include will look like, not something you can run today.
stages:
- test
include:
- component: gitlab.com/totalshiftleft/shiftleft-ci/run-test-pack@1.0.0
inputs:
server-url: https://tenant.totalshiftleft.ai
pack-id: pack_123
pass-threshold-percent: 95The component reads credentials by variable name — api-email-variable and api-password-variable default to SHIFTLEFT_API_EMAIL and SHIFTLEFT_API_PASSWORD — so no secret ever lands in .gitlab-ci.yml. It publishes both artifacts with when: always and feeds the JUnit XML to artifacts:reports:junit, so results show up in the merge request widget and the pipeline's Tests tab.
Gate decision codes
Printed to the job log as [ShiftLeft] Output decision=<code> and repeated in the JSON summary artifact. The job exits 0 when the gate passes and 1 when it fails, so it gates the pipeline.
| Decision | Meaning |
|---|---|
| PASSED | The run finished and cleared every gate you configured. |
| GATE_FAIL_THRESHOLD | The run finished below your pass-rate threshold. |
| GATE_FAIL_ERROR_TESTS | At least one test finished in ERROR and fail-on-error-tests was on. |
| FAILED | The execution itself failed. |
| COMPLETED_WITH_ISSUES | The gate failed but gate-failure-result was set to warn instead. |
| OK | The run completed with no gate configured to judge it. |
| TIMEOUT | The run had not finished within timeout-minutes. |
| TRIGGER_ONLY | wait-for-completion was false, so nothing was graded. |
GitLab CI integration — FAQ
Is there a first-party Total Shift Left GitLab CI component?
The component exists in source — it lives in the public Shift-Left-API-Integrations repository and wraps the same shared runner as every other Total Shift Left CI integration. It is not in the GitLab CI/CD Catalog yet, and the container image it runs is not on Docker Hub, so an include: component: reference will not resolve today. Until it does, the script above — calling the public REST API directly with curl and jq from any GitLab Runner — is the real, working way to run test packs from GitLab CI.Does this work with self-managed GitLab and self-hosted Total Shift Left?
Yes. The script runs on any GitLab Runner — shared or self-managed — and calls the public /api/v1 endpoints, so any reachable Shift-Left deployment works. For self-hosted Shift-Left deployments behind a firewall, use a GitLab Runner inside the same network.How do I gate merge requests on API test results?
The example script exits non-zero when the pass rate drops below your threshold, which fails the job. Combine with GitLab's "Pipelines must succeed" merge request setting under Settings → Merge Requests to block merges on API test failures.Can I run multiple test packs in parallel?
Yes. Use a parallel:matrix block with different SHIFTLEFT_TEST_PACK_ID values, or define multiple jobs in the test stage. Each job is independent and runs on its own runner.How do credentials reach the runner safely?
Use protected, masked CI/CD variables defined at the project, group, or instance level. Protected variables are exposed only on protected branches and tags. Masked variables are scrubbed from job logs. Never inline credentials in .gitlab-ci.yml.When will the component be installable?
The source is written, tested and public. Publishing it means mirroring the directory into a GitLab-hosted catalog project and tagging a release, plus pushing the runner image to Docker Hub. We are not committing to a date. This REST API pattern will keep working either way — the component is a convenience wrapper around the same endpoints, not a replacement for them.
More CI/CD integrations
Add API testing to your GitLab pipelines
Forever-free Citizen Developer or 15-day Enterprise trial. The REST API pattern above works today — no need to wait for the component.