Generate and maintain Gitea Actions CI/CD pipelines that build & publish Docker images (applications-backend/*, applications-frontend/*) and OCI Helm charts (helm-charts/*) from a pnpm + Turborepo monorepo to a Harbor registry, authenticated by a project robot account via Gitea org secret + global vars. - SKILL.md: auth model, convention-driven discovery, publish gating, gotchas - references/harbor-auth.md: docker/helm login, robot accounts, secret/var taxonomy, bake-into-runner alternative, troubleshooting - references/pipeline-workflow.md: jobs, discovery loops, tagging, turbo prune - references/conventions.md: Dockerfile/chart locations, image & chart naming - assets/workflows/ci-cd.yaml: ready-to-drop .gitea/workflows pipeline Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
102 lines
4.2 KiB
Markdown
102 lines
4.2 KiB
Markdown
# Discovery conventions
|
|
|
|
Where the pipeline looks for work, and how names are derived. These conventions come from the
|
|
**development-monorepo-pnpm** skill (`references/containers-and-helm.md`) — this file restates
|
|
only what the pipeline depends on. If the monorepo skill and this file ever disagree, the
|
|
monorepo skill wins; update this one.
|
|
|
|
---
|
|
|
|
## Repository shape the pipeline assumes
|
|
|
|
```text
|
|
<repo>/
|
|
├── applications-backend/<svc>/Dockerfile # deployable services (NestJS by default)
|
|
├── applications-frontend/<app>/Dockerfile # deployable browser apps (React + Carbon)
|
|
├── helm-charts/<chart>/Chart.yaml # ALL charts — centralized, not per-app
|
|
├── pnpm-workspace.yaml
|
|
├── turbo.json
|
|
└── pnpm-lock.yaml
|
|
```
|
|
|
|
Two independent discovery sets:
|
|
|
|
| Artifact | Glob | Produced by |
|
|
|---|---|---|
|
|
| Image | `applications-backend/*/Dockerfile`, `applications-frontend/*/Dockerfile` | `docker build` from repo root |
|
|
| Chart | `helm-charts/*/Chart.yaml` | `helm package` |
|
|
|
|
### Charts are centralized — do not look under the app directory
|
|
|
|
A frequent wrong assumption is that a chart lives at `applications-frontend/<app>/helm/` or
|
|
`.../chart/`. It does **not** in this monorepo family. Charts are collected under
|
|
`helm-charts/<chart>/` at the repo root. Practical consequences:
|
|
|
|
- Discover images and charts **separately**; there is **no enforced 1:1** mapping.
|
|
- An app may have a `Dockerfile` and **no** chart (deployed by a shared/umbrella chart, or
|
|
not yet deployable).
|
|
- A chart may exist with **no** same-named app (an umbrella chart, or a chart that deploys a
|
|
third-party image).
|
|
- Do not infer a chart's existence from an app's, or vice versa. Loop each glob on its own.
|
|
|
|
---
|
|
|
|
## Naming
|
|
|
|
### Image repository = application directory name
|
|
|
|
```
|
|
${HARBOR_ADDRESS}/${HARBOR_PROJECT}/<app-dir-name>:<tag>
|
|
# e.g. harbor-v1.apps.lego-cloud.eu/gondor-v1/user-service:9f3a1c2
|
|
```
|
|
|
|
- The **directory name** under `applications-backend/` or `applications-frontend/` is the image
|
|
repository name. It is not derived from the package.json `name` / scope — using the directory
|
|
name keeps the image path stable and needs no Node in the job.
|
|
- Backend and frontend images share the flat `<project>/<app>` namespace. If two apps across the
|
|
two roots ever share a directory name they would collide — keep directory names unique across
|
|
both roots (they already must be, since pnpm workspace names must be unique).
|
|
|
|
### Chart repository = chart name, under a `charts` subpath
|
|
|
|
```
|
|
oci://${HARBOR_ADDRESS}/${HARBOR_PROJECT}/charts # push target (base)
|
|
# a chart named "user-service" becomes:
|
|
# ${HARBOR_ADDRESS}/${HARBOR_PROJECT}/charts/user-service:<chart-version>
|
|
```
|
|
|
|
Charts are pushed under a `charts` subpath so OCI chart artifacts and image artifacts do not
|
|
share the project's top-level namespace. The chart name is the `name:` field in `Chart.yaml`
|
|
(which should match its directory name).
|
|
|
|
### Harbor project
|
|
|
|
`HARBOR_PROJECT` (default `gondor-v1`) is the Harbor project everything is pushed into. It must
|
|
be a project the robot has **push** on. One project per monorepo is a clean default; if you host
|
|
several monorepos, either give each its own project (and a per-project push robot) or accept a
|
|
shared project with distinct `<app>` names.
|
|
|
|
---
|
|
|
|
## Tag → deploy handoff
|
|
|
|
The pipeline publishes `:<sha>` (immutable) and, on the default branch, `:latest` (moving). The
|
|
**immutable** `:<sha>` is what a GitOps deployment should pin. This skill does not edit
|
|
deployment values — it hands the image reference and tag to **development-gitops-argo-cd**, where
|
|
the tag flows through Copier variables into the Helm values Argo CD syncs.
|
|
|
|
---
|
|
|
|
## Quick audit of a repo before generating a pipeline
|
|
|
|
```bash
|
|
echo "images:"; ls -d applications-backend/*/ applications-frontend/*/ 2>/dev/null \
|
|
| while read d; do [ -f "$d/Dockerfile" ] && echo " $d"; done
|
|
echo "charts:"; ls -d helm-charts/*/ 2>/dev/null \
|
|
| while read d; do [ -f "$d/Chart.yaml" ] && echo " $d"; done
|
|
```
|
|
|
|
If either list is empty, confirm you are at the repo root and that the monorepo actually follows
|
|
this layout before wiring the pipeline — an empty discovery set means the workflow will run
|
|
green while building nothing.
|