# GitLab

Qualor works with GitLab.com and self-managed GitLab. Setup has two parts:

1. **The CI job** runs `qualor scan`, uploads the analysis and fails the pipeline when the quality gate
   fails. That is enough to block bad merge requests.
2. **The connection (optional, recommended)** lets the Qualor server comment on merge requests. It
   posts one summary comment, one discussion per new issue on a changed line, and a commit status
   `qualor/<project key>`.

## Before you start

- A running Qualor server that the runners can reach over HTTPS ([Install the server](https://qualor.dev/docs/install-server.md)).
- Runners that can pull `qualor/scanner` from Docker Hub, or your own copy of it
  ([Images](https://qualor.dev/docs/install-server.md#images)).
- A project in Qualor whose key is the GitLab project path (`group/subgroup/app`), and a token. A
  project analysis token is best ([Tokens](https://qualor.dev/docs/users-projects-tokens.md#tokens)).

## 1. CI/CD variables

In GitLab, under **Settings → CI/CD → Variables** of the project (or of a group, for many projects at
once):

| Variable | Value | Flags |
|---|---|---|
| `QUALOR_URL` | `https://qualor.example.com` | — |
| `QUALOR_TOKEN` | the token | **Masked**, and **not** Protected |

The token must not be *protected*. GitLab gives protected variables only to pipelines of protected
branches, so merge request pipelines would get no token.

A group-level token has to be a personal token with **Upload analyses**, because a project token
uploads to one project only. If the token's user is an org admin, Qualor creates missing projects on
their first upload.

## 2. The CI job

### With the CI/CD component (recommended)

Qualor is in the GitLab CI/CD catalog as [`gitlab.com/qualor/qualor`](https://gitlab.com/qualor/qualor).
On GitLab.com:

```yaml
# .gitlab-ci.yml
include:
  - component: gitlab.com/qualor/qualor/qualor@1
    inputs:
      image-tag: '1'
```

**Self-managed GitLab** can include components only from its own instance. Create a project there
that mirrors `https://gitlab.com/qualor/qualor.git` (**New project → Import project → Repository by
URL** with **Mirror repository**, or a pull mirror), for example at `tools/qualor`, and include from it:

```yaml
include:
  - component: $CI_SERVER_FQDN/tools/qualor/qualor@1
    inputs:
      image-tag: '1'
      # image: mirror.acme.internal/qualor/scanner   # your own copy of the image, without the tag
```

Pin the component and the image to the same release. Use a full version (`@1.2.3`,
`image-tag: '1.2.3'`) where every pipeline must run exactly the same analyzers.

The job runs in merge request pipelines and on the default branch. It uploads the analysis, fails
with the quality gate, and keeps GitLab's **Code Quality**, **SAST** and **Dependency Scanning**
reports as artifacts. Findings then show in the merge request widget and in the security tab. The SAST
and Dependency Scanning widgets need GitLab Ultimate. The Code Quality report works on every tier.

| Input | Default | Meaning |
|---|---|---|
| `image-tag` | required | the scanner image tag, such as `1` or `1.2.3` |
| `image` | `qualor/scanner` | the scanner image, without the tag |
| `stage` | `test` | the stage of the job |
| `job-name` | `qualor` | the job's name |
| `args` | empty | extra `qualor scan` arguments, for example `--sarif osv.sarif` |
| `allow-failure` | `false` | let the pipeline pass when the gate fails (a soft rollout) |
| `dotnet` | `false` | C#: run `qualor dotnet begin`, the build, then `qualor dotnet end` |
| `build-command` | `dotnet build --no-incremental` | the build that runs between `begin` and `end` when `dotnet` is true |

The component runs `qualor scan` from the repository root. If your project needs dependencies
installed (JavaScript/TypeScript) or a build (Java) first, use the job below instead, or extend the
component's job with a `before_script`:

```yaml
qualor:            # the component's job-name
  before_script:
    - npm ci
```

### Without the component

```yaml
qualor:
  stage: test
  image: { name: qualor/scanner:1, entrypoint: [''] }
  variables: { GIT_DEPTH: 0 }        # full history: new code is computed from git
  script:
    - npm ci                          # JS/TS: ESLint runs from node_modules. Java: build first.
    - >-
      qualor scan
      --gitlab-code-quality gl-code-quality-report.json
      --gitlab-sast gl-sast-report.json
      --gitlab-dependency-scanning gl-dependency-scanning-report.json
  artifacts:
    when: always
    reports:
      codequality: gl-code-quality-report.json
      sast: gl-sast-report.json
      dependency_scanning: gl-dependency-scanning-report.json
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
```

- `entrypoint: ['']` is needed because the image's entrypoint is `qualor`, and GitLab needs a shell.
- `GIT_DEPTH: 0` gives the job the full history. With a shallow clone, the CLI tries to fetch the
  baseline. If that fails, new-code conditions end in `error`, and Qualor fails the gate rather than
  guessing.
- Use **merge request pipelines** (`merge_request_event`). Only in them does the CLI know the target
  branch, and the analysis becomes a merge request analysis.

A C# project uses `qualor/scanner-dotnet` and wraps its own build. See
[Languages and analyzers](https://qualor.dev/docs/languages-and-analyzers.md#c).

## 3. Merge request comments and commit status

In Qualor, an **org admin** opens **Settings → GitLab**:

1. **New GitLab connection.** Enter the GitLab address (`https://gitlab.example.com`; a path such as
   `/gitlab` is allowed) and an access token:
   - scope **`api`** and role **Developer**;
   - best, a **project access token** of the GitLab project, because it can reach nothing else. For
     many projects, use a **group access token**, or a personal token of a dedicated bot user. Never
     use a person's own token: every comment would carry their name.
2. In the **Projects** table on the same page, pick the connection for each Qualor project and enter
   the GitLab project: its numeric id or its full path `group/project`. Use the test action to check
   that the token can see the project.
3. Ask the server operator to set `QUALOR_PUBLIC_URL` so that comments link back to Qualor.

**Self-managed GitLab on an internal network.** The server calls only hosts that resolve to public
addresses, unless the operator allows the host explicitly:

```sh
QUALOR_SCM_INTERNAL_HOSTS=gitlab.corp.example.com            # https on 443
QUALOR_SCM_INTERNAL_HOSTS=gitlab.corp.example.com:8443,10.0.0.12
```

If GitLab's certificate comes from a private CA, give the server that CA with `NODE_EXTRA_CA_CERTS`.

### What reviewers see

- A **commit status** `qualor/<project key>`: *success* when the gate passes, *failed* with the failed
  conditions otherwise. It is posted for every analysed branch, not only for merge requests.
- **One summary comment** per merge request, edited in place on every analysis and never posted
  twice. It holds the verdict, the failed conditions, the new issues by severity, the ten most severe
  ones and a link to Qualor.
- **A discussion on each new issue** that sits on an added line, up to 50 per merge request. Qualor
  resolves the discussion when the issue is fixed. It never reopens a thread a person resolved, and
  it leaves alone a thread a person replied in.
- Marking an issue **false positive** or **won't fix** in Qualor re-evaluates the gate right away, with
  no new scan. If that was the only new issue, the status turns green and the thread is resolved.

Everything a comment quotes from the code is put in code spans. No snippet, no rule text and no link
that the report controls reaches GitLab.

## 4. Block merges on the gate

Either:

- keep the job's default `allow_failure: false` and turn on **Settings → Merge requests → Pipelines
  must succeed**; or
- keep the pipeline green (`allow-failure: true` during a soft rollout) and rely on the commit status
  as information.

## Troubleshooting

| Symptom | Cause |
|---|---|
| Exit 5 | the token is wrong, revoked, or of another project |
| `new code unavailable`, gate `error` | shallow clone: set `GIT_DEPTH: 0` |
| The analysis appears as a branch, not a merge request | the job ran in a branch pipeline. Use `merge_request_event` rules |
| No comments | no connection or mapping, `QUALOR_SCM_INTERNAL_HOSTS` missing, or the token lacks `api`/Developer. The test on **Settings → GitLab** says which |
| Comments but no links | `QUALOR_PUBLIC_URL` is not set on the server |

More in [Troubleshooting](https://qualor.dev/docs/troubleshooting.md).