This commit is contained in:
@@ -0,0 +1,219 @@
|
||||
# Pull-request deployment workflow
|
||||
|
||||
## Intended result
|
||||
|
||||
Production deployment should happen only after a change has followed this path:
|
||||
|
||||
```text
|
||||
feature branch
|
||||
-> local test and local application run
|
||||
-> pull request with automated checks
|
||||
-> owner reviews the result and approves by merging
|
||||
-> merge to main
|
||||
-> Gitea Actions builds and publishes an immutable image
|
||||
-> Flux updates magnus/deploy
|
||||
-> Flux reconciles Kubernetes
|
||||
-> production verification
|
||||
```
|
||||
|
||||
For this single-user repository, clicking **Merge** after reviewing the diff and successful checks
|
||||
is the approval. The merge creates the `push` event which starts the existing production build.
|
||||
|
||||
## Findings (2026-09-17)
|
||||
|
||||
- The default branch is `main`.
|
||||
- Gitea reports no branch-protection rule for this repository. A contributor can therefore push
|
||||
directly to `main`, bypassing a pull request and approval.
|
||||
- `.gitea/workflows/pr.yml` now runs tests for pull requests targeting `main`; it has no publish
|
||||
step and receives no Harbor credentials.
|
||||
- `.gitea/workflows/build.yml` remains restricted to pushes to `main` and `v*` tags. A merged PR
|
||||
therefore starts the existing image publication and automatic Kubernetes deployment path.
|
||||
- Recent `main` workflow runs show that the existing post-merge build and publish workflow works.
|
||||
- The workflow runs `pytest`, builds a linux/amd64 image, and publishes immutable
|
||||
`main-<UTC timestamp>` and SHA-qualified tags to
|
||||
`harbor.pmagnus.cloud/btcprice/btcprice`.
|
||||
- Flux watches the plain `main-<UTC timestamp>` tags. Its image automation writes the selected tag
|
||||
into `magnus/deploy`, after which Flux reconciles the Kubernetes cluster.
|
||||
- Kubernetes runs two replicas with rolling updates, health/readiness probes, and one pod per node.
|
||||
- Gitea currently has only one user (`magnus`). Mandatory reviewer approval is therefore not
|
||||
appropriate: Gitea does not provide meaningful independent self-approval. The owner's explicit
|
||||
merge action is the approval, while branch protection and CI remain the enforceable gates.
|
||||
|
||||
## Required one-time configuration
|
||||
|
||||
### 1. Pull-request validation workflow (implemented)
|
||||
|
||||
`.gitea/workflows/pr.yml` validates every pull request targeting `main`. It checks out the proposed
|
||||
commit and runs the tests without publishing an image or receiving Harbor credentials. The
|
||||
production Docker image is built locally before the PR and again by `build.yml` after merge.
|
||||
|
||||
```bash
|
||||
python -m pip install -r requirements-dev.txt
|
||||
python -m pytest tests/
|
||||
```
|
||||
|
||||
The contributor performs the Docker build locally before opening the PR. Keeping it out of the PR
|
||||
job avoids giving proposed code credentials for the private Harbor base images. After merge, the
|
||||
trusted `build.yml` workflow builds the production image with Harbor credentials.
|
||||
|
||||
Keep image publication restricted to the existing `push` event for `main` and `v*` tags. PR code,
|
||||
especially code from an untrusted fork, must not receive the Harbor credentials.
|
||||
|
||||
The workflows are intentionally separated:
|
||||
|
||||
- `pr.yml`: `pull_request` trigger; tests only; no secrets and no push.
|
||||
- `build.yml`: existing `push` trigger; tests, image build, Harbor scan gate, and image push.
|
||||
|
||||
### 2. Protect `main` in Gitea (configuration still required)
|
||||
|
||||
In `magnus/btcprice`, open **Settings -> Branches -> Branch protection rules** and protect `main`.
|
||||
Configure the rule to:
|
||||
|
||||
- disable direct pushes (and force pushes);
|
||||
- require pull requests before merge;
|
||||
- set required approvals to zero while the repository has only one user;
|
||||
- block merge when a review requests changes;
|
||||
- require the PR validation status check;
|
||||
- require the branch to be up to date before merge;
|
||||
- restrict bypass/whitelist permissions to emergency administrators only.
|
||||
|
||||
Create the PR workflow and let it run once before selecting its status-check context in the branch
|
||||
rule; this avoids guessing Gitea's generated context name. With required approvals set to zero, the
|
||||
owner reviews the diff and approves it by clicking **Merge** after the required check passes. If a
|
||||
second trusted user is added later, change the rule to require one approval and dismiss stale
|
||||
approvals when new commits are pushed.
|
||||
|
||||
## Per-change procedure
|
||||
|
||||
### 1. Create a feature branch
|
||||
|
||||
Start from the latest production branch:
|
||||
|
||||
```bash
|
||||
git switch main
|
||||
git pull --ff-only
|
||||
git switch -c feature/<short-description>
|
||||
```
|
||||
|
||||
Make and commit the change on this branch. Do not commit `.env`; it contains local configuration.
|
||||
|
||||
### 2. Test and run the branch locally before opening the PR
|
||||
|
||||
Run the automated tests:
|
||||
|
||||
```bash
|
||||
make test
|
||||
```
|
||||
|
||||
For a functional check, start `btcdata` in one terminal:
|
||||
|
||||
```bash
|
||||
cd ../btcdata
|
||||
make run
|
||||
```
|
||||
|
||||
Then start this branch of `btcprice` in another terminal:
|
||||
|
||||
```bash
|
||||
cd ../btcprice
|
||||
make run
|
||||
```
|
||||
|
||||
Open `http://localhost:4000` and confirm that both live price streams update. Also check the
|
||||
application endpoints:
|
||||
|
||||
```bash
|
||||
curl --fail http://localhost:4000/api/health
|
||||
curl --fail http://localhost:4000/api/ready
|
||||
curl --fail http://localhost:4000/api/version
|
||||
```
|
||||
|
||||
`/api/health` checks the btcprice process; `/api/ready` also verifies that btcdata is reachable.
|
||||
The local version normally reports `development`.
|
||||
|
||||
Finally, exercise the production build path:
|
||||
|
||||
```bash
|
||||
docker build \
|
||||
--build-arg BUILD_SHA="$(git rev-parse --short HEAD)" \
|
||||
-t btcprice:pr-check .
|
||||
```
|
||||
|
||||
If Docker validation needs the complete local stack, create `btc-network` once, start btcdata's
|
||||
Compose project, and run `docker compose up --build` in this repository. This application then
|
||||
listens at `http://localhost:4041`.
|
||||
|
||||
Record the tests and manual behavior checked in the PR description.
|
||||
|
||||
### 3. Push and open the pull request
|
||||
|
||||
```bash
|
||||
git push -u origin feature/<short-description>
|
||||
tea pulls create \
|
||||
--repo magnus/btcprice \
|
||||
--base main \
|
||||
--head feature/<short-description> \
|
||||
--title "<concise change title>"
|
||||
```
|
||||
|
||||
The PR should explain the change, risk, local verification, and rollback considerations. Wait for
|
||||
the PR validation job to pass, then review and merge it. Any new commit triggers validation again.
|
||||
|
||||
### 4. Approve and merge
|
||||
|
||||
As repository owner, check the diff, confirm the automated result, and verify the stated manual
|
||||
test. Clicking **Merge** is the approval step in this single-user setup. Prefer **squash merge** for
|
||||
small, single-purpose PRs or use another consistent merge policy.
|
||||
|
||||
Do not press merge if the check is failing or the PR branch is behind `main`. Do not deploy with
|
||||
`kubectl apply` or `deploy-citadel.sh`; those bypass the GitOps audit trail.
|
||||
|
||||
### 5. Observe the deployment
|
||||
|
||||
After merge, observe this sequence:
|
||||
|
||||
1. Gitea Actions tests the merge commit.
|
||||
2. It publishes `main-<UTC timestamp>` and `main-<UTC timestamp>-<short SHA>`.
|
||||
3. Harbor scans the image and permits the companion tag after policy approval.
|
||||
4. Flux image reflection selects the newest plain `main-<UTC timestamp>` tag.
|
||||
5. Flux commits the updated image reference to `magnus/deploy`.
|
||||
6. Flux applies that Git state and Kubernetes performs a rolling update.
|
||||
|
||||
The CI job intentionally has no Kubernetes credentials. A successful image build is not by itself
|
||||
proof of a successful rollout, so verify production separately:
|
||||
|
||||
```bash
|
||||
curl --fail https://btcprice.k8s.pmagnus.cloud/api/health
|
||||
curl --fail https://btcprice.k8s.pmagnus.cloud/api/ready
|
||||
curl --fail https://btcprice.k8s.pmagnus.cloud/api/version
|
||||
```
|
||||
|
||||
The production version should equal the short SHA of the merged btcprice commit. Also confirm in
|
||||
the deployment repository or cluster that both replicas became Ready. If rollout access is
|
||||
available, useful checks are:
|
||||
|
||||
```bash
|
||||
flux get kustomizations
|
||||
kubectl -n btcprice rollout status deployment/btcprice
|
||||
kubectl -n btcprice get pods -o wide
|
||||
```
|
||||
|
||||
## Failure and rollback behavior
|
||||
|
||||
- If PR validation fails, fix the feature branch; nothing has been published or deployed.
|
||||
- If the post-merge build fails, Kubernetes keeps running the previous image. Fix forward through a
|
||||
new PR; do not manually push a replacement under an existing immutable tag.
|
||||
- If Harbor rejects the image, Flux cannot select it and production stays on the previous image.
|
||||
- If a new image deploys but is bad, revert the Flux image commit in `magnus/deploy`, or commit the
|
||||
last known-good immutable tag there. Flux then restores that version. Follow up with a revert or
|
||||
fix PR in this repository so future automation does not reintroduce the bad build.
|
||||
|
||||
## Security boundary
|
||||
|
||||
The separation is intentional:
|
||||
|
||||
- PR jobs validate untrusted proposed code without deployment or registry credentials.
|
||||
- The protected `main` branch is the release authorization boundary.
|
||||
- Gitea Actions may write images to Harbor only after merge.
|
||||
- Flux alone has authority to update the GitOps repository and reconcile Kubernetes.
|
||||
- Kubernetes changes remain reviewable and recoverable through Git history.
|
||||
Reference in New Issue
Block a user