# Proposing a change from the console

> Goal: change an upstream's delay, a tenant's budget or a policy's configuration from the console, without ever writing to the gateway: the change becomes a…

Guide - Since version 1.2.0 - Flexway v1.6.3 - https://flexway.io/en/docs/operations-proposals/

> Operations guide: configure, deploy and observe the gateway. What the capability does, product side: [product guide](https://flexway.io/en/docs/product-overview/); calling it: [developer guide](https://flexway.io/en/docs/developer-overview/).

**Goal**: change an upstream's delay, a tenant's budget or a policy's configuration **from the
console**, without ever writing to the gateway: the change becomes a pull request in the GitOps
repository, reviewed and merged under its rules; GitOps then reconciles it.

**Configuration** - once, by the operator:

| Step | What to do |
| --- | --- |
| 1. Create the GitHub application | `Contents` and `Pull requests` read and write, `Checks`, `Commit statuses` and `Metadata` read; **neither** `Administration` **nor** `Workflows`; no webhook. Install it on **the one** GitOps repository; do not add it to the actors allowed to bypass the rules |
| 2. Set the key by reference | The private key in the secret manager, synced into a `Secret` (ESO), mounted as a file: `repo.existingSecret` (key `private-key` by default) - never a chart value |
| 3. Protect the target branch | A ruleset requiring a pull request and at least one approval, readable by the application; classic protection (unreadable without `Administration`) is declared: `repo.protection: declared`, shown "not verified" |
| 4. Fill in the repository | `repo.url`, `repo.branch`, `repo.path` (the directory of `flexway.yaml`), `repo.appId`, `repo.installationId` |

```yaml
repo:
  url: https://github.com/acme-platform/gateway-config
  branch: main
  path: deploy/flexway
  appId: "123456"
  installationId: "7890123"
  existingSecret: console-github-app
```

Outside Kubernetes (Docker, VM): `FLEXWAY_CONSOLE_REPO_URL`, `_REPO_BRANCH`, `_REPO_PATH`, `_REPO_APP_ID`,
`_REPO_INSTALLATION_ID`, `_REPO_APP_KEY=file:/run/secrets/github-app/private-key` and
`FLEXWAY_CONSOLE_PROPOSALS_STORE=file:/var/lib/flexway-console/proposals` on a volume, **one replica**.
The roles: `flexway-proposer` and `flexway-reviewer` at the provider (with or without a tenant), or
chosen in Users for a local account.

**The path of a proposal**:

1. A `proposer` (or a `reviewer`, an admin) opens a policy's step, an upstream's detail or a tenant's
   usage and clicks **"Propose this change"**: new value, title, reason.
2. The console reads `flexway.yaml` in the repository, replaces the node keeping comments and order,
   shows the **diff**, has **the gateway's own loader** load it and simulates the described requests.
   Nothing is written to the repository.
3. The author **submits it for review**: the diff is frozen (fingerprint).
4. A `reviewer` **opens the pull request** (or rejects it, with a reason): a `flexway/p-NNNN-...`
   branch, one commit carrying exactly the reviewed diff, a generated body.
5. Review and merge happen **in the repository**, under its rules; the console reads reviews, checks and
   the merge every 30 s.
6. After the merge, the console computes the expected `config_digest` on the merge commit; the proposal
   is **applied** when every replica serves it (`flexway_config_info`).

| Status | Meaning |
| --- | --- |
| Draft | Its author's; refused by the loader, it cannot be submitted |
| In review | Diff frozen; a reviewer opens the pull request or rejects it |
| PR open | In the repository; "modified in the repository" when the branch no longer carries the reviewed diff alone |
| Merged | Waiting for the reload; "merged with a gap" when the merged content differs from the reviewed one |
| Applied | Every replica serves the expected digest |
| Stale | `flexway.yaml` changed on the target branch: **Regenerate** replays the edit on the new head |
| Rejected, withdrawn | Finished; kept 400 days, with its journal |

**Check**: `flexway validate` on the repository after the merge prints the same `config_digest` the
console expects; each replica's `/config` publishes it.

```bash
docker run --rm -v "$PWD/deploy/flexway:/cfg" ghcr.io/flexwiz/flexway:<version> validate --config /cfg/flexway.yaml
# valid: 12 operation(s) served from 1 contract(s), 2 upstream(s)
# config_digest 9e41b7c0d2aa
```

**Troubleshooting**

| Symptom | Cause and fix |
| --- | --- |
| `503 repo_unprotected`, "branch not protected" banner | No readable rule requires a pull request and an approval: add a ruleset, or declare the protection (`repo.protection: declared`) |
| `502 repo_refused` | The key is revoked, the installation does not cover the repository, or a permission is missing: check the application, then wait for the key to be read again (no restart) |
| "Modified in the repository" | Someone pushed to the `flexway/*` branch: review the pull request in the repository; the console keeps both fingerprints in the journal |
| "Merged with a gap" | The merge carries other contents than the reviewed ones: the journal says so; the expected digest is the merge commit's |
| "Reload expected" past 15 min | GitOps has not synchronised, or the gateway refused the reload (`last_reload` of `/config`) |

*Available since `1.2.0`; automatic rules and the operations AI are planned for `1.10.0`.
Proven against a GitHub double; a run against a demonstration repository remains to be done before
promising GitHub Enterprise Server compatibility and signed commits.*
