diff --git a/.woodpecker.yml b/.woodpecker.yml index 38f540f..1f04503 100644 --- a/.woodpecker.yml +++ b/.woodpecker.yml @@ -9,6 +9,10 @@ variables: pnpm install --frozen-lockfile - &use_deps | corepack enable + # Kaniko base command setup + - &kaniko_setup | + mkdir -p /kaniko/.docker + echo "{\"auths\":{\"reg.mosaicstack.dev\":{\"username\":\"$HARBOR_USER\",\"password\":\"$HARBOR_PASS\"}}}" > /kaniko/.docker/config.json steps: install: @@ -83,6 +87,12 @@ steps: # Docker Build & Push (main/develop only) # ====================== # Requires secrets: harbor_username, harbor_password + # + # Tagging Strategy: + # - Always: commit SHA (e.g., 658ec077) + # - main branch: 'latest' + # - develop branch: 'dev' + # - git tags: version tag (e.g., v1.0.0) # Build and push API image using Kaniko docker-build-api: @@ -92,13 +102,25 @@ steps: from_secret: harbor_username HARBOR_PASS: from_secret: harbor_password + CI_COMMIT_BRANCH: ${CI_COMMIT_BRANCH} + CI_COMMIT_TAG: ${CI_COMMIT_TAG} + CI_COMMIT_SHA: ${CI_COMMIT_SHA} commands: - - mkdir -p /kaniko/.docker - - echo "{\"auths\":{\"reg.mosaicstack.dev\":{\"username\":\"$HARBOR_USER\",\"password\":\"$HARBOR_PASS\"}}}" > /kaniko/.docker/config.json - - /kaniko/executor --context . --dockerfile apps/api/Dockerfile --destination reg.mosaicstack.dev/mosaic/api:${CI_COMMIT_SHA:0:8} --destination reg.mosaicstack.dev/mosaic/api:latest + - *kaniko_setup + - | + DESTINATIONS="--destination reg.mosaicstack.dev/mosaic/api:${CI_COMMIT_SHA:0:8}" + if [ "$CI_COMMIT_BRANCH" = "main" ]; then + DESTINATIONS="$DESTINATIONS --destination reg.mosaicstack.dev/mosaic/api:latest" + elif [ "$CI_COMMIT_BRANCH" = "develop" ]; then + DESTINATIONS="$DESTINATIONS --destination reg.mosaicstack.dev/mosaic/api:dev" + fi + if [ -n "$CI_COMMIT_TAG" ]; then + DESTINATIONS="$DESTINATIONS --destination reg.mosaicstack.dev/mosaic/api:$CI_COMMIT_TAG" + fi + /kaniko/executor --context . --dockerfile apps/api/Dockerfile $DESTINATIONS when: - branch: [main, develop] - event: [push, manual] + event: [push, manual, tag] depends_on: - build @@ -110,13 +132,25 @@ steps: from_secret: harbor_username HARBOR_PASS: from_secret: harbor_password + CI_COMMIT_BRANCH: ${CI_COMMIT_BRANCH} + CI_COMMIT_TAG: ${CI_COMMIT_TAG} + CI_COMMIT_SHA: ${CI_COMMIT_SHA} commands: - - mkdir -p /kaniko/.docker - - echo "{\"auths\":{\"reg.mosaicstack.dev\":{\"username\":\"$HARBOR_USER\",\"password\":\"$HARBOR_PASS\"}}}" > /kaniko/.docker/config.json - - /kaniko/executor --context . --dockerfile apps/web/Dockerfile --build-arg NEXT_PUBLIC_API_URL=https://api.mosaicstack.dev --destination reg.mosaicstack.dev/mosaic/web:${CI_COMMIT_SHA:0:8} --destination reg.mosaicstack.dev/mosaic/web:latest + - *kaniko_setup + - | + DESTINATIONS="--destination reg.mosaicstack.dev/mosaic/web:${CI_COMMIT_SHA:0:8}" + if [ "$CI_COMMIT_BRANCH" = "main" ]; then + DESTINATIONS="$DESTINATIONS --destination reg.mosaicstack.dev/mosaic/web:latest" + elif [ "$CI_COMMIT_BRANCH" = "develop" ]; then + DESTINATIONS="$DESTINATIONS --destination reg.mosaicstack.dev/mosaic/web:dev" + fi + if [ -n "$CI_COMMIT_TAG" ]; then + DESTINATIONS="$DESTINATIONS --destination reg.mosaicstack.dev/mosaic/web:$CI_COMMIT_TAG" + fi + /kaniko/executor --context . --dockerfile apps/web/Dockerfile --build-arg NEXT_PUBLIC_API_URL=https://api.mosaicstack.dev $DESTINATIONS when: - branch: [main, develop] - event: [push, manual] + event: [push, manual, tag] depends_on: - build @@ -128,12 +162,24 @@ steps: from_secret: harbor_username HARBOR_PASS: from_secret: harbor_password + CI_COMMIT_BRANCH: ${CI_COMMIT_BRANCH} + CI_COMMIT_TAG: ${CI_COMMIT_TAG} + CI_COMMIT_SHA: ${CI_COMMIT_SHA} commands: - - mkdir -p /kaniko/.docker - - echo "{\"auths\":{\"reg.mosaicstack.dev\":{\"username\":\"$HARBOR_USER\",\"password\":\"$HARBOR_PASS\"}}}" > /kaniko/.docker/config.json - - /kaniko/executor --context docker/postgres --dockerfile docker/postgres/Dockerfile --destination reg.mosaicstack.dev/mosaic/postgres:${CI_COMMIT_SHA:0:8} --destination reg.mosaicstack.dev/mosaic/postgres:latest + - *kaniko_setup + - | + DESTINATIONS="--destination reg.mosaicstack.dev/mosaic/postgres:${CI_COMMIT_SHA:0:8}" + if [ "$CI_COMMIT_BRANCH" = "main" ]; then + DESTINATIONS="$DESTINATIONS --destination reg.mosaicstack.dev/mosaic/postgres:latest" + elif [ "$CI_COMMIT_BRANCH" = "develop" ]; then + DESTINATIONS="$DESTINATIONS --destination reg.mosaicstack.dev/mosaic/postgres:dev" + fi + if [ -n "$CI_COMMIT_TAG" ]; then + DESTINATIONS="$DESTINATIONS --destination reg.mosaicstack.dev/mosaic/postgres:$CI_COMMIT_TAG" + fi + /kaniko/executor --context docker/postgres --dockerfile docker/postgres/Dockerfile $DESTINATIONS when: - branch: [main, develop] - event: [push, manual] + event: [push, manual, tag] depends_on: - build diff --git a/docs/harbor-tag-retention-policy.md b/docs/harbor-tag-retention-policy.md new file mode 100644 index 0000000..8a462ff --- /dev/null +++ b/docs/harbor-tag-retention-policy.md @@ -0,0 +1,167 @@ +# Harbor Tag Retention Policy + +This document describes the recommended tag retention policy for the Mosaic Stack container images in Harbor. + +## Tagging Strategy + +Images are tagged based on branch and event type: + +| Trigger | Tags Applied | Example | +| ----------------- | ----------------- | -------------------- | +| Push to `main` | `{sha}`, `latest` | `658ec077`, `latest` | +| Push to `develop` | `{sha}`, `dev` | `a1b2c3d4`, `dev` | +| Git tag (release) | `{sha}`, `{tag}` | `658ec077`, `v1.0.0` | + +### Tag Meanings + +| Tag | Purpose | Stability | +| -------------------------- | ------------------------------------------ | --------- | +| `latest` | Current production-ready build from `main` | Stable | +| `dev` | Current development build from `develop` | Unstable | +| `v*` (e.g., `v1.0.0`) | Versioned release | Immutable | +| `{sha}` (e.g., `658ec077`) | Specific commit for traceability | Immutable | + +## Retention Policy Configuration + +Configure in Harbor UI: **Projects → mosaic → Policy → Tag Retention** + +### Recommended Rules + +Create the following retention rules in order: + +#### Rule 1: Keep Release Tags Forever + +``` +Repositories: ** +Tag filter: v* +Retain: all +``` + +Keeps all versioned releases (v1.0.0, v2.0.0, etc.) + +#### Rule 2: Keep Latest and Dev Tags + +``` +Repositories: ** +Tag filter: {latest,dev} +Retain: all +``` + +Keeps the `latest` and `dev` tags (always exactly one of each) + +#### Rule 3: Keep Recent SHA Tags + +``` +Repositories: ** +Tag filter: * +Retain: most recent 10 tags +``` + +Keeps the 10 most recent commit SHA tags for rollback capability + +### Expected Result + +After retention runs: + +- All `v*` tags preserved +- `latest` and `dev` tags preserved +- Last 10 SHA tags preserved +- Older SHA tags deleted + +## Garbage Collection + +Tag retention only removes tag references. Actual blob storage is reclaimed via garbage collection. + +### Schedule GC + +**Harbor UI:** Administration → Garbage Collection + +Recommended schedule: **Weekly** (Sunday 2:00 AM) + +Options: + +- ☑ Delete untagged artifacts (removes images with no tags) +- Workers: 1 (adjust based on registry size) + +### Manual GC + +Run on-demand after large cleanup operations: + +1. Go to Administration → Garbage Collection +2. Click "GC Now" +3. Monitor job status + +## Cleanup Commands + +### Delete Specific Tag (API) + +```bash +# Delete a specific tag +curl -sk -X DELETE -u "$HARBOR_AUTH" \ + "https://reg.mosaicstack.dev/api/v2.0/projects/mosaic/repositories/api/artifacts/{tag}" + +# Example: delete old test tag +curl -sk -X DELETE -u "robot\$woodpecker-ci:$TOKEN" \ + "https://reg.mosaicstack.dev/api/v2.0/projects/mosaic/repositories/api/artifacts/test" +``` + +### List All Tags + +```bash +# List tags for a repository +curl -sk -u "$HARBOR_AUTH" \ + "https://reg.mosaicstack.dev/v2/mosaic/api/tags/list" | jq '.tags' +``` + +### Bulk Delete Old SHA Tags (Script) + +```bash +#!/bin/bash +# Delete SHA tags older than the 10 most recent +HARBOR_AUTH="robot\$woodpecker-ci:$TOKEN" +REPO="mosaic/api" + +# Get all SHA tags (8 char hex), sorted by push time +TAGS=$(curl -sk -u "$HARBOR_AUTH" \ + "https://reg.mosaicstack.dev/api/v2.0/projects/mosaic/repositories/${REPO#mosaic/}/artifacts?with_tag=true" | \ + jq -r 'sort_by(.push_time) | .[:-10] | .[].tags[]?.name | select(test("^[a-f0-9]{8}$"))') + +for tag in $TAGS; do + echo "Deleting $REPO:$tag" + curl -sk -X DELETE -u "$HARBOR_AUTH" \ + "https://reg.mosaicstack.dev/api/v2.0/projects/mosaic/repositories/${REPO#mosaic/}/artifacts/$tag" +done +``` + +## Monitoring + +### Check Repository Size + +```bash +curl -sk -u "$HARBOR_AUTH" \ + "https://reg.mosaicstack.dev/api/v2.0/projects/mosaic" | \ + jq '{name, repo_count, chart_count}' +``` + +### Check Artifact Count Per Repository + +```bash +for repo in api web postgres; do + count=$(curl -sk -u "$HARBOR_AUTH" \ + "https://reg.mosaicstack.dev/api/v2.0/projects/mosaic/repositories/$repo/artifacts" | jq 'length') + echo "$repo: $count artifacts" +done +``` + +## Best Practices + +1. **Never delete `latest` or `dev` manually** - CI will recreate them on next push +2. **Don't delete release tags (`v*`)** - These should be preserved for rollbacks +3. **Run GC after bulk deletions** - Reclaim storage space +4. **Monitor storage usage** - Set up alerts if approaching quota +5. **Test retention policy** - Use "Dry Run" option before enabling + +## Related Documentation + +- [Woodpecker-Harbor Integration Tips](../docs/work/woodpecker-harbor-integration-tips.md) (in jarvis-brain) +- [Harbor Official Docs: Tag Retention](https://goharbor.io/docs/2.0.0/administration/tag-retention/)