Add the CI components
Takes about 20 minutes
Four GitLab CI/CD components connect your app's merge requests to Bugoro. Include all four in
your app's .gitlab-ci.yml.
Before you start
- GitLab 18.10 or later: the components use
spec:componentandspec:description. - A runner tagged
bugorothat can reach the farm (the Self-host page sets one up). - Your project connected in the Hub. Otherwise the build registration fails with
401 invalid_ci_token. - The CI/CD variables below.
deploy.sh ci-varssets all of them but the optional keystore.
No runner of your own? The jobs also run on GitLab's hosted runners. Give them the hosted runner's tag, saas-linux-small-amd64, as runner_tag, the Hub's public URL as hub_api_url, and device-shots the Hub's farm relay, https://<hub-host>/farm, as farm_url. The relay checks the job's CI ID token and passes FARM_TOKEN on to the farm. For Bugoro's own pipeline, deploy.sh ci-vars --hosted-runners sets these.
Include the components
<group>/<project> is where the Bugoro components are published on your GitLab, and <version> a released version such as 1.0.0.
include:
- component: $CI_SERVER_FQDN/<group>/<project>/qa-build@<version>
inputs:
module: app # the app's Gradle module
hub_url: $BUGORO_HUB_URL
hub_api_url: $BUGORO_HUB_API_URL
- component: $CI_SERVER_FQDN/<group>/<project>/device-shots@<version>
inputs:
farm_url: http://172.31.250.10:8080
hub_url: $BUGORO_HUB_URL
hub_api_url: $BUGORO_HUB_API_URL
package: com.example.app.qa # the QA build's application id
default_deep_link: myapp://app/home
- component: $CI_SERVER_FQDN/<group>/<project>/store-check@<version>
inputs:
module: app # the app's Gradle module
- component: $CI_SERVER_FQDN/<group>/<project>/size-guard@<version>
inputs:
module: app
hub_url: $BUGORO_HUB_URL
hub_api_url: $BUGORO_HUB_API_URL- Two Hub URLs.
hub_urlis the Hub's public URL, for phones and the links in the notes.hub_api_urlis where the jobs call the Hub's API. On the Bugoro host, CI egress drops the host's public address, so jobs call the Hub over the Bugoro network instead:BUGORO_HUB_API_URLishttp://bugoro-hub:3000there. If your jobs can reach the public URL, leavehub_api_urlout. - The API URL may use http only for
bugoro-hub,localhostor a private address. Anything else must be https, so the CI ID token never crosses the internet in cleartext. - If the Bugoro project isn't
bugoro/bugoro, set thebugoro_projectinput on each component: the jobs fetch their scripts from it, so it must be public. store-checkstands alone.size-guardmeasures store-check's bundle when both run, so its stage must not be earlier.
Run merge request pipelines
One pipeline per push: an MR pipeline while the branch has an open MR, otherwise a branch pipeline. The fix agent's branches (bugoro/*) get no branch pipeline; their MR pipeline follows.
workflow:
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH =~ /^bugoro\//
when: never
- if: $CI_COMMIT_BRANCH && $CI_OPEN_MERGE_REQUESTS
when: never
- if: $CI_COMMIT_BRANCH
- if: $CI_COMMIT_TAGWhat each job does
qa-build
Builds the QA APK, uploads it to the project's package registry, registers it with the Hub, and points the qa/<branch> environment at the install page, so the MR gets a View app button.
| Input | Default | What it does |
|---|---|---|
module | app | The app's Gradle module. |
variant | qa | The build variant to assemble. |
hub_url | required | The Hub's public URL. Also passed to Gradle as -Pbugoro.hubUrl. |
hub_api_url | empty | Where the job calls the Hub's API, when it can't reach hub_url. Empty means hub_url. |
qa_keystore_variable | BUGORO_QA_KEYSTORE | An optional File variable with the QA keystore. |
device-shots
Opens the reported screen on the farm's emulators for the default branch's build (Before) and the MR's build (After), and posts a Before/After table on the MR. When the two shots match, the note says there is no visible difference: the fix may not be visual, or may show only after interaction. It opens the screen by its deep link and signs nothing in, so an app whose screens need a signed in user should sign in a test account in its QA build. It never fails the pipeline.
| Input | Default | What it does |
|---|---|---|
farm_url | required | The device farm's base URL; the runner's jobs must reach it. |
hub_url | required | The same as qa-build's. |
hub_api_url | empty | Where the job calls the Hub's API, when it can't reach hub_url. Empty means hub_url. |
package | empty | The QA build's application id. |
default_deep_link | bugoro-sample://app/home | The screen to open when the MR has no linked report. Set your own. |
profiles | small,large | The farm's device profiles. |
store-check
Checks the release bundle against Google Play's rules (Android lint, Greenlight and Bugoro's own rules), writes a Code Quality report and one sticky MR note, and fails only a release pipeline (a tag, or an MR into release/* or labelled release), and only for a critical finding.
| Input | Default | What it does |
|---|---|---|
module | app | The app's Gradle module. |
variant | release | The release variant. |
metadata_dir | fastlane/metadata/android | The store listing, in the fastlane layout. |
declarations | store/data-safety.json | The data safety declarations. |
fail_on | critical | The lowest severity that fails a gating pipeline. |
release_branches | ^release/ | MRs into matching branches gate. |
readiness_account | empty | The release-readiness flow's account, for the note's mention line. |
size-guard
Measures what a phone downloads with bundletool, records it in the Hub (the dashboard's trend), and keeps one sticky MR note with the change against the MR's target branch (or the default branch when the target has no recorded size) and the largest files. It never fails the pipeline.
| Input | Default | What it does |
|---|---|---|
module | app | The app's Gradle module. |
hub_url | required | Where the size is recorded, and the note's dashboard link. |
hub_api_url | empty | Where the job calls the Hub's API, when it can't reach hub_url. Empty means hub_url. |
device_spec | phone-arm64 | The device the download size is for. |
budget_kb | "0" | An absolute budget in KiB; over it, the note warns. |
warn_increase_kb | "100" | The note warns when the download grows by more. |
Every input is listed in the reference doc.
CI/CD variables and their scopes
Under Settings > CI/CD > Variables, or with deploy.sh ci-vars from the host. None is protected: fix branches aren't protected, so protected variables would never reach their pipelines.
| Variable | Environment scope | Used by and value |
|---|---|---|
BUGORO_GITLAB_TOKEN | bugoro-* | device-shots, store-check and size-guard post and edit their MR notes with it. A project access token, Reporter, api scope. Masked. |
FARM_TOKEN | bugoro-shots/* | device-shots, to use the farm. The farm's own token. Masked. |
BUGORO_HUB_URL | all | The Hub's public URL, https://<hub-host>, for hub_url. Not a secret. |
BUGORO_HUB_API_URL | all | Where the jobs call the Hub's API, for hub_api_url: http://bugoro-hub:3000 on the Bugoro host. Not a secret. |
BUGORO_QA_KEYSTORE | qa/* | Optional. qa-build's QA keystore, type File, base64. Masked. |
Why the scopes. Each job declares an environment only so its variables can be scoped. device-shots gets both tokens; store-check and size-guard run the MR's Gradle build, so they get BUGORO_GITLAB_TOKEN only, never the farm's. Unit test jobs, which run code the agent wrote, get neither. qa-build needs neither: it uses a CI ID token, so the Hub needs no CI secret.
Sign every QA build with the same key
Android installs an update only when it is signed by the same key. If your qa build type signs with the debug config, give qa-build a stable debug keystore:
keytool -genkeypair -keystore qa-debug.keystore -storetype PKCS12 \
-alias androiddebugkey -storepass android -keypass android \
-keyalg RSA -keysize 2048 -validity 10000 -dname "CN=<App> QA"
base64 -i qa-debug.keystore # on Linux: base64 -w0 qa-debug.keystoreAdd the base64 text as BUGORO_QA_KEYSTORE: type File, masked, scope qa/*, not protected. Keep the keystore file out of the repository.
GitLab's security scans
Bugoro's own pipeline also runs GitLab's Secret Detection and SAST (Semgrep) on every merge request and the default branch, the fix agent's MRs included. You can add them to your app's pipeline the same way:
include:
- template: Jobs/Secret-Detection.gitlab-ci.yml
- template: Jobs/SAST.gitlab-ci.yml- With the workflow rules above, an open MR has no branch pipeline, so set the variable
AST_ENABLE_MR_PIPELINES: "true", or the scans never run in MR pipelines. - Both jobs may fail, so a scanner outage never fails the pipeline and never starts an agent repair. GitLab reports what they find.
- The Bugoro runner runs only images pinned by digest. Override each scanner job's
image:with a pinned one and add it to the runner'sallowed_images, as Bugoro's pipeline does. - Each scan can hold back "Fix ready" until it ends, because the return trip waits for the MR pipeline to pass. Bugoro's pipeline gives each scanner a 10 minute timeout.
Bugoro's pipeline also publishes the sample app's privacy policy with GitLab Pages, from pages/ on the default branch. Its store listing links the Hub's copy, /chai/privacy/, which anyone can read, so store-check's privacy policy check has a public page to fetch even when Pages is visible to project members only.