chore: sync local Mosaic changes
This commit is contained in:
@@ -12,7 +12,7 @@ GIT PUSH
|
||||
QUALITY GATES (lint, typecheck, test, audit)
|
||||
↓ all pass
|
||||
BUILD (compile all packages)
|
||||
↓ only on main/develop/tags
|
||||
↓ only on main/tags
|
||||
DOCKER BUILD & PUSH (Kaniko → Gitea Container Registry)
|
||||
↓ all images pushed
|
||||
PACKAGE LINKING (associate images with repository in Gitea)
|
||||
@@ -123,10 +123,10 @@ when:
|
||||
# Top-level: run quality gates on everything
|
||||
- event: [push, pull_request, manual]
|
||||
|
||||
# Per-step: only build Docker images on main/develop/tags
|
||||
# Per-step: only build Docker images on main/tags
|
||||
docker-build-api:
|
||||
when:
|
||||
- branch: [main, develop]
|
||||
- branch: [main]
|
||||
event: [push, manual, tag]
|
||||
```
|
||||
|
||||
@@ -150,24 +150,29 @@ docker-build-SERVICE:
|
||||
from_secret: gitea_username
|
||||
GITEA_TOKEN:
|
||||
from_secret: gitea_token
|
||||
RELEASE_BASE_VERSION: ${RELEASE_BASE_VERSION}
|
||||
CI_COMMIT_BRANCH: ${CI_COMMIT_BRANCH}
|
||||
CI_COMMIT_TAG: ${CI_COMMIT_TAG}
|
||||
CI_COMMIT_SHA: ${CI_COMMIT_SHA}
|
||||
CI_PIPELINE_NUMBER: ${CI_PIPELINE_NUMBER}
|
||||
commands:
|
||||
- *kaniko_setup
|
||||
- |
|
||||
DESTINATIONS="--destination REGISTRY/ORG/IMAGE_NAME:${CI_COMMIT_SHA:0:8}"
|
||||
SHORT_SHA="${CI_COMMIT_SHA:0:8}"
|
||||
BUILD_ID="${CI_PIPELINE_NUMBER:-$SHORT_SHA}"
|
||||
BASE_VERSION="${RELEASE_BASE_VERSION:?RELEASE_BASE_VERSION is required (example: 0.0.1)}"
|
||||
|
||||
DESTINATIONS="--destination REGISTRY/ORG/IMAGE_NAME:sha-$SHORT_SHA"
|
||||
if [ "$CI_COMMIT_BRANCH" = "main" ]; then
|
||||
DESTINATIONS="$DESTINATIONS --destination REGISTRY/ORG/IMAGE_NAME:latest"
|
||||
elif [ "$CI_COMMIT_BRANCH" = "develop" ]; then
|
||||
DESTINATIONS="$DESTINATIONS --destination REGISTRY/ORG/IMAGE_NAME:dev"
|
||||
DESTINATIONS="$DESTINATIONS --destination REGISTRY/ORG/IMAGE_NAME:v${BASE_VERSION}-rc.${BUILD_ID}"
|
||||
DESTINATIONS="$DESTINATIONS --destination REGISTRY/ORG/IMAGE_NAME:testing"
|
||||
fi
|
||||
if [ -n "$CI_COMMIT_TAG" ]; then
|
||||
DESTINATIONS="$DESTINATIONS --destination REGISTRY/ORG/IMAGE_NAME:$CI_COMMIT_TAG"
|
||||
fi
|
||||
/kaniko/executor --context . --dockerfile PATH/TO/Dockerfile $DESTINATIONS
|
||||
when:
|
||||
- branch: [main, develop]
|
||||
- branch: [main]
|
||||
event: [push, manual, tag]
|
||||
depends_on:
|
||||
- build
|
||||
@@ -184,15 +189,69 @@ docker-build-SERVICE:
|
||||
|
||||
### Image Tagging Strategy
|
||||
|
||||
Every build produces multiple tags:
|
||||
Tagging MUST follow a two-layer model: immutable identity tags + mutable environment tags.
|
||||
|
||||
Immutable tags:
|
||||
|
||||
| Condition | Tag | Purpose |
|
||||
|-----------|-----|---------|
|
||||
| Always | `${CI_COMMIT_SHA:0:8}` | Immutable reference to exact commit |
|
||||
| `main` branch | `latest` | Current production release |
|
||||
| `develop` branch | `dev` | Current development build |
|
||||
| Always | `sha-${CI_COMMIT_SHA:0:8}` | Immutable reference to exact commit |
|
||||
| `main` branch | `v{BASE_VERSION}-rc.{BUILD_ID}` | Intermediate release candidate for the active milestone |
|
||||
| Git tag (e.g., `v1.0.0`) | `v1.0.0` | Semantic version release |
|
||||
|
||||
Mutable environment tags:
|
||||
|
||||
| Tag | Purpose |
|
||||
|-----|---------|
|
||||
| `testing` | Current candidate under situational validation |
|
||||
| `staging` (optional) | Pre-production validation target |
|
||||
| `prod` | Current production pointer |
|
||||
|
||||
Hard rules:
|
||||
- Do NOT use `latest` for deployment.
|
||||
- Do NOT use `dev` as the primary deployment tag.
|
||||
- Deployments MUST resolve to an immutable image digest.
|
||||
|
||||
### Digest-First Promotion (Hard Rule)
|
||||
|
||||
Deploy and promote by digest, not by mutable tag:
|
||||
|
||||
1. Build and push candidate tags (`sha-*`, `vX.Y.Z-rc.N`, `testing`).
|
||||
2. Resolve the digest from `sha-*` tag.
|
||||
3. Deploy that digest to testing and run situational tests.
|
||||
4. If green, promote the same digest to `staging`/`prod` tags.
|
||||
5. Create final semantic release tag (`vX.Y.Z`) only at milestone completion.
|
||||
|
||||
Example with `crane`:
|
||||
|
||||
```bash
|
||||
DIGEST=$(crane digest REGISTRY/ORG/IMAGE:sha-${CI_COMMIT_SHA:0:8})
|
||||
crane tag REGISTRY/ORG/IMAGE@${DIGEST} testing
|
||||
# after situational tests pass:
|
||||
crane tag REGISTRY/ORG/IMAGE@${DIGEST} prod
|
||||
```
|
||||
|
||||
### Deployment Strategy: Blue-Green Default
|
||||
|
||||
- Blue-green is the default release strategy for lights-out operation.
|
||||
- Canary is OPTIONAL and allowed only when automated SLO/error-rate monitoring and rollback triggers are configured.
|
||||
- If canary guardrails are missing, you MUST use blue-green.
|
||||
|
||||
### Image Retention and Cleanup (Hard Rule)
|
||||
|
||||
Registry cleanup MUST be automated (daily or weekly job).
|
||||
|
||||
Retention policy:
|
||||
- Keep all final release tags (`vX.Y.Z`) indefinitely.
|
||||
- Keep digests currently referenced by `prod` and `testing` tags.
|
||||
- Keep the most recent 20 RC tags (`vX.Y.Z-rc.N`) per service.
|
||||
- Delete RC and `sha-*` tags older than 30 days when they are not referenced by active environments/releases.
|
||||
|
||||
Before deleting any image/tag:
|
||||
- Verify digest is not currently deployed.
|
||||
- Verify digest is not referenced by any active release/tag notes.
|
||||
- Log cleanup actions in CI job output.
|
||||
|
||||
### Kaniko Options
|
||||
|
||||
Common flags for `/kaniko/executor`:
|
||||
@@ -252,7 +311,10 @@ In `docker-compose.yml`:
|
||||
```yaml
|
||||
services:
|
||||
api:
|
||||
image: git.example.com/org/image:${IMAGE_TAG:-dev}
|
||||
# Preferred: pin digest produced by CI and promoted by environment
|
||||
image: git.example.com/org/image@${IMAGE_DIGEST}
|
||||
# Optional channel pointer for non-prod:
|
||||
# image: git.example.com/org/image:${IMAGE_TAG:-testing}
|
||||
```
|
||||
|
||||
## Package Linking
|
||||
@@ -315,7 +377,7 @@ link-packages:
|
||||
link_package "image-name-1"
|
||||
link_package "image-name-2"
|
||||
when:
|
||||
- branch: [main, develop]
|
||||
- branch: [main]
|
||||
event: [push, manual, tag]
|
||||
depends_on:
|
||||
- docker-build-image-1
|
||||
@@ -350,6 +412,12 @@ Configure these in the Woodpecker UI (Settings > Secrets) or via CLI:
|
||||
| `gitea_username` | Gitea username or service account | `push`, `manual`, `tag` |
|
||||
| `gitea_token` | Gitea token with `package:write` scope | `push`, `manual`, `tag` |
|
||||
|
||||
### Required CI Variables (Non-Secret)
|
||||
|
||||
| Variable | Example | Purpose |
|
||||
|----------|---------|---------|
|
||||
| `RELEASE_BASE_VERSION` | `0.0.1` | Base milestone version used to generate RC tags (`v0.0.1-rc.N`) |
|
||||
|
||||
### Setting Secrets via CLI
|
||||
|
||||
```bash
|
||||
@@ -554,9 +622,9 @@ security-trivy:
|
||||
mkdir -p ~/.docker
|
||||
echo "{\"auths\":{\"REGISTRY\":{\"username\":\"$$GITEA_USER\",\"password\":\"$$GITEA_TOKEN\"}}}" > ~/.docker/config.json
|
||||
trivy image --exit-code 1 --severity HIGH,CRITICAL --ignore-unfixed \
|
||||
REGISTRY/ORG/IMAGE:$${CI_COMMIT_SHA:0:8}
|
||||
REGISTRY/ORG/IMAGE:sha-$${CI_COMMIT_SHA:0:8}
|
||||
when:
|
||||
- branch: [main, develop]
|
||||
- branch: [main]
|
||||
event: [push, manual, tag]
|
||||
depends_on:
|
||||
- docker-build-SERVICE
|
||||
@@ -574,7 +642,7 @@ Docker build MUST depend on ALL quality + security steps. Trivy runs AFTER build
|
||||
|
||||
## Monorepo Considerations
|
||||
|
||||
### pnpm + Turbo (Mosaic Stack pattern)
|
||||
### pnpm + Turbo
|
||||
|
||||
```yaml
|
||||
variables:
|
||||
@@ -589,7 +657,7 @@ steps:
|
||||
- pnpm build # Turbo handles dependency order and caching
|
||||
```
|
||||
|
||||
### npm Workspaces (U-Connect pattern)
|
||||
### npm Workspaces
|
||||
|
||||
```yaml
|
||||
variables:
|
||||
@@ -684,24 +752,28 @@ steps:
|
||||
from_secret: gitea_username
|
||||
GITEA_TOKEN:
|
||||
from_secret: gitea_token
|
||||
RELEASE_BASE_VERSION: ${RELEASE_BASE_VERSION}
|
||||
CI_COMMIT_BRANCH: ${CI_COMMIT_BRANCH}
|
||||
CI_COMMIT_TAG: ${CI_COMMIT_TAG}
|
||||
CI_COMMIT_SHA: ${CI_COMMIT_SHA}
|
||||
CI_PIPELINE_NUMBER: ${CI_PIPELINE_NUMBER}
|
||||
commands:
|
||||
- *kaniko_setup
|
||||
- |
|
||||
DESTINATIONS="--destination git.example.com/org/api:${CI_COMMIT_SHA:0:8}"
|
||||
SHORT_SHA="${CI_COMMIT_SHA:0:8}"
|
||||
BUILD_ID="${CI_PIPELINE_NUMBER:-$SHORT_SHA}"
|
||||
BASE_VERSION="${RELEASE_BASE_VERSION:?RELEASE_BASE_VERSION is required}"
|
||||
DESTINATIONS="--destination git.example.com/org/api:sha-$SHORT_SHA"
|
||||
if [ "$CI_COMMIT_BRANCH" = "main" ]; then
|
||||
DESTINATIONS="$DESTINATIONS --destination git.example.com/org/api:latest"
|
||||
elif [ "$CI_COMMIT_BRANCH" = "develop" ]; then
|
||||
DESTINATIONS="$DESTINATIONS --destination git.example.com/org/api:dev"
|
||||
DESTINATIONS="$DESTINATIONS --destination git.example.com/org/api:v${BASE_VERSION}-rc.${BUILD_ID}"
|
||||
DESTINATIONS="$DESTINATIONS --destination git.example.com/org/api:testing"
|
||||
fi
|
||||
if [ -n "$CI_COMMIT_TAG" ]; then
|
||||
DESTINATIONS="$DESTINATIONS --destination git.example.com/org/api:$CI_COMMIT_TAG"
|
||||
fi
|
||||
/kaniko/executor --context . --dockerfile src/api/Dockerfile $DESTINATIONS
|
||||
when:
|
||||
- branch: [main, develop]
|
||||
- branch: [main]
|
||||
event: [push, manual, tag]
|
||||
depends_on: [build]
|
||||
|
||||
@@ -712,24 +784,28 @@ steps:
|
||||
from_secret: gitea_username
|
||||
GITEA_TOKEN:
|
||||
from_secret: gitea_token
|
||||
RELEASE_BASE_VERSION: ${RELEASE_BASE_VERSION}
|
||||
CI_COMMIT_BRANCH: ${CI_COMMIT_BRANCH}
|
||||
CI_COMMIT_TAG: ${CI_COMMIT_TAG}
|
||||
CI_COMMIT_SHA: ${CI_COMMIT_SHA}
|
||||
CI_PIPELINE_NUMBER: ${CI_PIPELINE_NUMBER}
|
||||
commands:
|
||||
- *kaniko_setup
|
||||
- |
|
||||
DESTINATIONS="--destination git.example.com/org/web:${CI_COMMIT_SHA:0:8}"
|
||||
SHORT_SHA="${CI_COMMIT_SHA:0:8}"
|
||||
BUILD_ID="${CI_PIPELINE_NUMBER:-$SHORT_SHA}"
|
||||
BASE_VERSION="${RELEASE_BASE_VERSION:?RELEASE_BASE_VERSION is required}"
|
||||
DESTINATIONS="--destination git.example.com/org/web:sha-$SHORT_SHA"
|
||||
if [ "$CI_COMMIT_BRANCH" = "main" ]; then
|
||||
DESTINATIONS="$DESTINATIONS --destination git.example.com/org/web:latest"
|
||||
elif [ "$CI_COMMIT_BRANCH" = "develop" ]; then
|
||||
DESTINATIONS="$DESTINATIONS --destination git.example.com/org/web:dev"
|
||||
DESTINATIONS="$DESTINATIONS --destination git.example.com/org/web:v${BASE_VERSION}-rc.${BUILD_ID}"
|
||||
DESTINATIONS="$DESTINATIONS --destination git.example.com/org/web:testing"
|
||||
fi
|
||||
if [ -n "$CI_COMMIT_TAG" ]; then
|
||||
DESTINATIONS="$DESTINATIONS --destination git.example.com/org/web:$CI_COMMIT_TAG"
|
||||
fi
|
||||
/kaniko/executor --context . --dockerfile src/web/Dockerfile $DESTINATIONS
|
||||
when:
|
||||
- branch: [main, develop]
|
||||
- branch: [main]
|
||||
event: [push, manual, tag]
|
||||
depends_on: [build]
|
||||
|
||||
@@ -763,8 +839,8 @@ steps:
|
||||
}
|
||||
link_package "api"
|
||||
link_package "web"
|
||||
when:
|
||||
- branch: [main, develop]
|
||||
when:
|
||||
- branch: [main]
|
||||
event: [push, manual, tag]
|
||||
depends_on:
|
||||
- docker-build-api
|
||||
@@ -780,49 +856,130 @@ steps:
|
||||
5. **Add package linking step** after all Docker builds
|
||||
6. **Update `docker-compose.yml`** to reference registry images instead of local builds:
|
||||
```yaml
|
||||
image: git.example.com/org/service:${IMAGE_TAG:-dev}
|
||||
image: git.example.com/org/service@${IMAGE_DIGEST}
|
||||
```
|
||||
7. **Test on develop branch first** — push a small change and verify the pipeline
|
||||
7. **Test on a short-lived non-main branch first** — open a PR and verify quality gates before merging to `main`
|
||||
8. **Verify images appear** in Gitea Packages tab after successful pipeline
|
||||
|
||||
## Post-Merge CI Monitoring (Hard Rule)
|
||||
|
||||
For source-code delivery, completion is not allowed at "PR opened" stage.
|
||||
|
||||
Required sequence:
|
||||
|
||||
1. Merge PR to `main` (squash) via Mosaic wrapper.
|
||||
2. Monitor CI to terminal status:
|
||||
```bash
|
||||
~/.config/mosaic/rails/git/pr-ci-wait.sh -n <PR_NUMBER>
|
||||
```
|
||||
3. Require green status before claiming completion.
|
||||
4. If CI fails, create remediation task(s) and continue until green.
|
||||
5. If monitoring command fails, report blocker with the exact failed wrapper command and stop.
|
||||
|
||||
Woodpecker note:
|
||||
- In Gitea + Woodpecker environments, commit status contexts generally reflect Woodpecker pipeline results.
|
||||
- Always include CI run/status evidence in completion report.
|
||||
|
||||
## Queue Guard Before Push/Merge (Hard Rule)
|
||||
|
||||
Before pushing a branch or merging a PR, guard against overlapping project pipelines:
|
||||
|
||||
```bash
|
||||
~/.config/mosaic/rails/git/ci-queue-wait.sh --purpose push -B main
|
||||
~/.config/mosaic/rails/git/ci-queue-wait.sh --purpose merge -B main
|
||||
```
|
||||
|
||||
Behavior:
|
||||
- If pipeline state is running/queued/pending, wait until queue clears.
|
||||
- If timeout or API/auth failure occurs, treat as `blocked`, report exact failed wrapper command, and stop.
|
||||
|
||||
## Gitea as Unified Platform
|
||||
|
||||
Gitea provides **three services in one**, eliminating the need for separate Harbor and Verdaccio deployments:
|
||||
Gitea provides **multiple services in one**, eliminating the need for separate registry platforms:
|
||||
|
||||
| Service | What Gitea Replaces | Registry URL |
|
||||
|---------|---------------------|-------------|
|
||||
| **Git hosting** | GitHub/GitLab | `https://GITEA_HOST/org/repo` |
|
||||
| **Container registry** | Harbor, Docker Hub | `docker pull GITEA_HOST/org/image:tag` |
|
||||
| **npm registry** | Verdaccio, Artifactory | `https://GITEA_HOST/api/packages/org/npm/` |
|
||||
| **PyPI registry** | Private PyPI/Artifactory | `https://GITEA_HOST/api/packages/org/pypi` |
|
||||
| **Maven registry** | Nexus, Artifactory | `https://GITEA_HOST/api/packages/org/maven` |
|
||||
| **NuGet registry** | Azure Artifacts, Artifactory | `https://GITEA_HOST/api/packages/org/nuget/index.json` |
|
||||
| **Cargo registry** | crates.io mirrors, Artifactory | `https://GITEA_HOST/api/packages/org/cargo` |
|
||||
| **Composer registry** | Private Packagist, Artifactory | `https://GITEA_HOST/api/packages/org/composer` |
|
||||
| **Conan registry** | Artifactory Conan | `https://GITEA_HOST/api/packages/org/conan` |
|
||||
| **Conda registry** | Anaconda Server, Artifactory | `https://GITEA_HOST/api/packages/org/conda` |
|
||||
| **Generic registry** | Generic binary stores | `https://GITEA_HOST/api/packages/org/generic` |
|
||||
|
||||
### Additional Package Types
|
||||
|
||||
Gitea also supports PyPI, Maven, NuGet, Cargo, Composer, Conan, Conda, Generic, and more. All use the same token authentication.
|
||||
|
||||
### Single Token, Three Services
|
||||
### Single Token, Multiple Services
|
||||
|
||||
A Gitea token with `package:write` scope handles:
|
||||
- `git push` / `git pull`
|
||||
- `docker push` / `docker pull` (container registry)
|
||||
- `npm publish` / `npm install` (npm registry)
|
||||
- `twine upload` / `pip install` (PyPI registry)
|
||||
- package operations for Maven/NuGet/Cargo/Composer/Conan/Conda/Generic registries
|
||||
|
||||
This means a single `gitea_token` secret in Woodpecker CI covers all CI/CD operations.
|
||||
This means a single `gitea_token` secret in Woodpecker CI covers all CI/CD package operations.
|
||||
|
||||
## Python Packages on Gitea PyPI
|
||||
|
||||
For Python libraries and internal packages, use Gitea's built-in PyPI registry.
|
||||
|
||||
### Publish (Local or CI)
|
||||
|
||||
```bash
|
||||
python -m pip install --upgrade build twine
|
||||
python -m build
|
||||
python -m twine upload \
|
||||
--repository-url "https://GITEA_HOST/api/packages/ORG/pypi" \
|
||||
--username "$GITEA_USERNAME" \
|
||||
--password "$GITEA_TOKEN" \
|
||||
dist/*
|
||||
```
|
||||
|
||||
### Install (Consumer Projects)
|
||||
|
||||
```bash
|
||||
pip install \
|
||||
--extra-index-url "https://$GITEA_USERNAME:$GITEA_TOKEN@GITEA_HOST/api/packages/ORG/pypi/simple" \
|
||||
your-package-name
|
||||
```
|
||||
|
||||
### Woodpecker Step (Python Publish)
|
||||
|
||||
```yaml
|
||||
publish-python-package:
|
||||
image: python:3.12-slim
|
||||
environment:
|
||||
GITEA_USERNAME:
|
||||
from_secret: gitea_username
|
||||
GITEA_TOKEN:
|
||||
from_secret: gitea_token
|
||||
commands:
|
||||
- python -m pip install --upgrade build twine
|
||||
- python -m build
|
||||
- python -m twine upload --repository-url https://GITEA_HOST/api/packages/ORG/pypi --username "$$GITEA_USERNAME" --password "$$GITEA_TOKEN" dist/*
|
||||
when:
|
||||
branch: [main]
|
||||
event: [push]
|
||||
```
|
||||
|
||||
### Architecture Simplification
|
||||
|
||||
**Before (3 services):**
|
||||
**Before (4 services):**
|
||||
```
|
||||
Gitea (git) + Harbor (containers) + Verdaccio (npm)
|
||||
↓ separate auth ↓ separate auth ↓ OAuth/Authentik
|
||||
3 tokens 1 robot account 1 OIDC integration
|
||||
3 backup targets complex RBAC group-based access
|
||||
Gitea (git) + Harbor (containers) + Verdaccio (npm) + Private PyPI
|
||||
↓ separate auth ↓ separate auth ↓ extra auth ↓ extra auth
|
||||
multiple tokens robot/service users npm-specific token pip/twine token
|
||||
fragmented access fragmented RBAC fragmented RBAC fragmented RBAC
|
||||
```
|
||||
|
||||
**After (1 service):**
|
||||
```
|
||||
Gitea (git + containers + npm)
|
||||
↓ single token
|
||||
1 secret in Woodpecker
|
||||
Gitea (git + containers + npm + pypi)
|
||||
↓ unified secrets
|
||||
1 credentials model in CI
|
||||
1 backup target
|
||||
unified RBAC via Gitea teams
|
||||
```
|
||||
@@ -900,5 +1057,5 @@ If a project currently uses Verdaccio (e.g., U-Connect at `npm.uscllc.net`), fol
|
||||
- Link manually: Gitea UI > Packages > Select package > Link to repository
|
||||
|
||||
### Pipeline runs Docker builds on pull requests
|
||||
- Verify `when` clause on Docker build steps restricts to `branch: [main, develop]`
|
||||
- Verify `when` clause on Docker build steps restricts to `branch: [main]`
|
||||
- Pull requests should only run quality gates, not build/push images
|
||||
|
||||
Reference in New Issue
Block a user