This commit is contained in:
@@ -0,0 +1,20 @@
|
|||||||
|
name: pull-request
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches: [main]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
validate:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: '3.13'
|
||||||
|
|
||||||
|
- name: Test
|
||||||
|
run: |
|
||||||
|
python -m pip install -r requirements-dev.txt
|
||||||
|
python -m pytest tests/
|
||||||
@@ -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