# GitLab and Bitbucket

Deploy from GitLab or Bitbucket in one of two ways. Deplane never holds a credential that reaches more than one repository.

- **From your pipeline** (GitLab CI or Bitbucket Pipelines): your job runs `deplane deploy`, proving who it is with the short ID token its CI host signs for each job. **Deplane stores no credential at all.**
- **Connect a repository**: like [GitHub](github.md), every push deploys and merge or pull requests get previews, with no pipeline file. You paste a token that reaches one repository only.

## From your pipeline

### Trust the repository

An app trusts a repository's jobs to deploy it. In the app's **Settings**, under **Deploy from GitLab CI or Bitbucket Pipelines**, or with the CLI:

```sh
deplane pipelines trust gitlab --app web --project 123456 --branch main
deplane pipelines trust bitbucket --app web --workspace '{workspace-uuid}' --repository '{repository-uuid}' --name acme/web --branch main
```

The repository is named by its IDs, never its path, because a path can be renamed and taken by someone else. GitLab shows the project ID on the project's page. Bitbucket shows the UUIDs in Repository settings, OpenID Connect.

The deploy branch deploys the app. Any other branch can only deploy previews, unless you pass `--no-previews`. With `--preview-database`, each preview gets its own empty database, as GitHub previews can (free, at most 3 per team, deleted with the preview). Adding a trust asks for your passkey, as creating a CI token does. An app can trust at most 3 repositories. `deplane pipelines list` shows each trust with its last use, and `deplane pipelines remove ID` stops it at once.

### GitLab CI

The job asks GitLab for an ID token whose audience is your Deplane API URL (`https://api.deplane.io`):

```yaml
deploy:
  image: alpine
  id_tokens:
    DEPLANE_ID_TOKEN:
      aud: https://api.deplane.io
  script:
    - deplane deploy --app web
  rules:
    - if: $CI_COMMIT_BRANCH == "main"

preview:
  image: alpine
  id_tokens:
    DEPLANE_ID_TOKEN:
      aud: https://api.deplane.io
  script:
    - deplane deploy --app web --preview "$CI_MERGE_REQUEST_IID"
  environment:
    name: review/$CI_MERGE_REQUEST_IID
    on_stop: stop_preview
    auto_stop_in: 1 week
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"

stop_preview:
  image: alpine
  id_tokens:
    DEPLANE_ID_TOKEN:
      aud: https://api.deplane.io
  script:
    - deplane previews remove --app web "$CI_MERGE_REQUEST_IID"
  environment:
    name: review/$CI_MERGE_REQUEST_IID
    action: stop
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
      when: manual
```

GitLab runs the stop job when the merge request is merged or closed.

### Bitbucket Pipelines

A step with `oidc: true` gets `BITBUCKET_STEP_OIDC_TOKEN`. Give it your API URL as its audience:

```yaml
pipelines:
  branches:
    main:
      - step:
          oidc:
            audiences: [https://api.deplane.io]
          script:
            - deplane deploy --app web
  pull-requests:
    '**':
      - step:
          oidc:
            audiences: [https://api.deplane.io]
          script:
            - deplane deploy --app web --preview "$BITBUCKET_PR_ID"
  custom:
    stop-preview:
      - variables:
          - name: PR_ID
      - step:
          oidc:
            audiences: [https://api.deplane.io]
          script:
            - deplane previews remove --app web "$PR_ID"
```

Run `stop-preview` from a `pullrequest-fulfilled` or `pullrequest-rejected` trigger.

### What a pipeline can do

Deplane checks each ID token:

- its signature: RS256, with the CI host's own keys;
- the issuer and the audience (your API URL);
- its age;
- the repository's IDs: on GitLab the project of the pipeline and of the job, so a fork's merge request never matches;
- the branch.

It then answers with a pipeline token that lasts 15 minutes. That token belongs to the trust, not to a person, and keeps working after whoever added the trust leaves the team. Every refusal is `pipeline_not_trusted`; the app's activity log says why.

From the deploy branch, a pipeline uploads the folder and follows its build and release. From other branches, it deploys and removes the app's previews. It cannot roll back, change variables or scaling, see tokens, or reach other apps. A pipeline never creates the app or changes its settings: deploy it once yourself first. Previews get the app's variables except `DATABASE_URL` and the other database addresses. A preview not deployed for 7 days is deleted, in case a stop job never ran.

The CLI never prints the ID token, and `--debug` hides it.

## Connect a repository

### GitLab

1. On your own GitLab account, make a **fine-grained personal access token** with "Group and project access" set to this one project, an expiry within 90 days, and exactly these permissions, no others (no Push):

   | Tab | Category | Resource | Action |
   |---|---|---|---|
   | Group and project | Projects | Project | Read |
   | Group and project | Repository | Repository | Read |
   | Group and project | Repository | Branch | Read |
   | Group and project | Repository | Commit | Read |
   | Group and project | Repository | Merge Request | Read |
   | Group and project | Project Planning | Work Item | Read, Create, Update |
   | Group and project | CI/CD | Commit Status | Create |
   | User | System Access | Personal Access Token | Read, Rotate, Revoke |

   Your account needs Developer or higher in the project. The token's permissions, not your role, keep Deplane from pushing.
2. In the app's **Settings**, under **GitLab**, enter the project ID and branch, and paste the token. Deplane checks it, seals it, and deploys the branch.
3. In GitLab, under **Settings › Webhooks**, add the URL Deplane shows, with the Push and Merge request events. Paste the webhook's signing token back into Settings.

Deplane renews the token every 7 days. Commit statuses and the one merge request comment are posted as you, so each starts with "Deplane:".

### Bitbucket

1. In the repository's settings, under **Access tokens**, make a **repository access token** with only **Repositories: Read** and **Pull requests: Read**.
2. In the app's **Settings**, under **Bitbucket**, enter the repository as `workspace/repository` and the branch, and paste the token.
3. Deplane shows a webhook URL and a secret, once. In Bitbucket, under **Repository settings › Webhooks**, add them with the Repository push and the Pull request created, updated, merged and declined events.

Bitbucket does not let Deplane read the token's expiry: deploys stop when it expires.

### What a connection does

- **A push to the branch deploys its latest commit.** A webhook only tells Deplane to look: Deplane asks the host for the branch's head and builds that, so an old or forged delivery cannot deploy anything else. Only signed deliveries count.
- **Merge and pull requests get previews.** Requests from forks get none, because their code would run with the app's variables. A comment says why instead.
- **The files are those of `deplane deploy`**, with your `.gitignore` and `.dockerignore`.
- **If the host refuses the token**, or its permissions or projects change, Deplane disconnects the app, deletes its previews from this connection, shows why in Settings, and emails the team's owners.

`deplane gitlab` and `deplane bitbucket` show a connection; `deplane gitlab deploy` and `deplane bitbucket deploy` deploy the branch's head now.

### Disconnect

In the app's Settings. On GitLab, Deplane revokes the token and forgets it. Bitbucket offers no way to revoke a token from outside: Deplane forgets its copy at once, and asks you to delete the token and the webhook on Bitbucket, then to confirm. Either way the app keeps running its current release.

## What Deplane stores

- **Pipelines:** the trusts (repository IDs, branch, whether previews are on). No credential.
- **Connections:** the token, sealed with the platform's keyring, opened only to talk to the host, and never logged or shown. The webhook's secret, sealed too. For display: the repository's path, the token's name and its expiry.
