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>
168 lines
6.2 KiB
YAML
168 lines
6.2 KiB
YAML
# Drop-in Gitea Actions pipeline for a pnpm + Turborepo monorepo.
|
|
# Copy to <repo>/.gitea/workflows/ci-cd.yaml.
|
|
#
|
|
# It discovers every app with a Dockerfile and every chart under helm-charts/, builds them,
|
|
# and — only on the default branch or a tag — pushes images and OCI Helm charts to Harbor.
|
|
#
|
|
# Prerequisites (see the devsecops-ci-cd-gitea skill):
|
|
# Gitea GLOBAL variables (Site Admin -> Actions -> Variables), set once for all orgs:
|
|
# HL_V1_HARBOR_ADDRESS e.g. harbor-v1.apps.lego-cloud.eu
|
|
# HL_V1_HARBOR_ROBOT_GITEA_ACTIONS_V1_USERNAME e.g. robot$gondor-v1+gitea-actions-v1
|
|
# HL_V1_HARBOR_PROJECT (optional; defaults to gondor-v1 below)
|
|
# Gitea ORG secret (per org, Settings -> Actions -> Secrets):
|
|
# HL_V1_HARBOR_ROBOT_GITEA_ACTIONS_V1_SECRET
|
|
#
|
|
# TODO before first run:
|
|
# - set `runs-on` to your runner's label (its jobs must provide docker, helm, git)
|
|
name: ci-cd
|
|
|
|
on:
|
|
push:
|
|
branches: [test, main]
|
|
tags: ["v*"]
|
|
pull_request:
|
|
branches: [test, main]
|
|
|
|
env:
|
|
HARBOR_ADDRESS: ${{ vars.HL_V1_HARBOR_ADDRESS }}
|
|
HARBOR_USERNAME: ${{ vars.HL_V1_HARBOR_ROBOT_GITEA_ACTIONS_V1_USERNAME }}
|
|
HARBOR_SECRET: ${{ secrets.HL_V1_HARBOR_ROBOT_GITEA_ACTIONS_V1_SECRET }}
|
|
HARBOR_PROJECT: ${{ vars.HL_V1_HARBOR_PROJECT || 'gondor-v1' }}
|
|
|
|
jobs:
|
|
build-and-push-images:
|
|
runs-on: [self-hosted] # TODO: set to your runner label (needs docker + git)
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Compute tag and publish flag
|
|
id: meta
|
|
run: |
|
|
set -euo pipefail
|
|
if [ "${GITHUB_REF_TYPE}" = "tag" ]; then
|
|
TAG="${GITHUB_REF_NAME}"
|
|
else
|
|
TAG="$(git rev-parse --short HEAD)"
|
|
fi
|
|
if [ "${GITHUB_EVENT_NAME}" = "pull_request" ]; then
|
|
PUBLISH=false
|
|
elif [ "${GITHUB_REF_TYPE}" = "tag" ] \
|
|
|| [ "${GITHUB_REF_NAME}" = "test" ] \
|
|
|| [ "${GITHUB_REF_NAME}" = "main" ]; then
|
|
PUBLISH=true
|
|
else
|
|
PUBLISH=false
|
|
fi
|
|
echo "tag=${TAG}" >> "$GITHUB_OUTPUT"
|
|
echo "publish=${PUBLISH}" >> "$GITHUB_OUTPUT"
|
|
echo "Tag=${TAG} Publish=${PUBLISH}"
|
|
|
|
- name: Log in to Harbor (publish runs only)
|
|
if: steps.meta.outputs.publish == 'true'
|
|
run: |
|
|
set -euo pipefail
|
|
echo "${HARBOR_SECRET}" | docker login "${HARBOR_ADDRESS}" -u "${HARBOR_USERNAME}" --password-stdin
|
|
|
|
- name: Build and push images
|
|
env:
|
|
TAG: ${{ steps.meta.outputs.tag }}
|
|
PUBLISH: ${{ steps.meta.outputs.publish }}
|
|
run: |
|
|
set -euo pipefail
|
|
shopt -s nullglob
|
|
found=0
|
|
for df in applications-backend/*/Dockerfile applications-frontend/*/Dockerfile; do
|
|
found=1
|
|
app="$(basename "$(dirname "$df")")"
|
|
image="${HARBOR_ADDRESS}/${HARBOR_PROJECT}/${app}"
|
|
echo "::group::${app}"
|
|
# Build context is the REPOSITORY ROOT — the Dockerfile runs turbo prune / COPY . .
|
|
docker build -f "$df" -t "${image}:${TAG}" .
|
|
if [ "${PUBLISH}" = "true" ]; then
|
|
docker push "${image}:${TAG}"
|
|
# moving :latest on branch builds only (never on tags)
|
|
if [ "${GITHUB_REF_TYPE}" != "tag" ]; then
|
|
docker tag "${image}:${TAG}" "${image}:latest"
|
|
docker push "${image}:latest"
|
|
fi
|
|
else
|
|
echo "PUBLISH=false — built ${image}:${TAG}, not pushing"
|
|
fi
|
|
echo "::endgroup::"
|
|
done
|
|
[ "${found}" = "1" ] || echo "No Dockerfiles found under applications-backend/* or applications-frontend/*"
|
|
|
|
- name: Log out
|
|
if: always() && steps.meta.outputs.publish == 'true'
|
|
run: docker logout "${HARBOR_ADDRESS}" || true
|
|
|
|
package-and-push-charts:
|
|
runs-on: [self-hosted] # TODO: set to your runner label (needs helm + git)
|
|
needs: build-and-push-images
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Compute publish flag
|
|
id: meta
|
|
run: |
|
|
set -euo pipefail
|
|
if [ "${GITHUB_REF_TYPE}" = "tag" ]; then
|
|
TAG="${GITHUB_REF_NAME#v}"
|
|
else
|
|
TAG=""
|
|
fi
|
|
if [ "${GITHUB_EVENT_NAME}" = "pull_request" ]; then
|
|
PUBLISH=false
|
|
elif [ "${GITHUB_REF_TYPE}" = "tag" ] \
|
|
|| [ "${GITHUB_REF_NAME}" = "test" ] \
|
|
|| [ "${GITHUB_REF_NAME}" = "main" ]; then
|
|
PUBLISH=true
|
|
else
|
|
PUBLISH=false
|
|
fi
|
|
echo "tag=${TAG}" >> "$GITHUB_OUTPUT"
|
|
echo "publish=${PUBLISH}" >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Log in to Harbor (publish runs only)
|
|
if: steps.meta.outputs.publish == 'true'
|
|
run: |
|
|
set -euo pipefail
|
|
echo "${HARBOR_SECRET}" | helm registry login "${HARBOR_ADDRESS}" -u "${HARBOR_USERNAME}" --password-stdin
|
|
|
|
- name: Lint, package and push charts
|
|
env:
|
|
TAG: ${{ steps.meta.outputs.tag }}
|
|
PUBLISH: ${{ steps.meta.outputs.publish }}
|
|
run: |
|
|
set -euo pipefail
|
|
shopt -s nullglob
|
|
mkdir -p .cicd-charts
|
|
found=0
|
|
for chart in helm-charts/*/Chart.yaml; do
|
|
found=1
|
|
dir="$(dirname "$chart")"
|
|
echo "::group::$(basename "$dir")"
|
|
helm lint "$dir"
|
|
if [ -n "${TAG}" ]; then
|
|
# On a tag build, align chart + app version with the release tag.
|
|
helm package "$dir" -d .cicd-charts --version "${TAG}" --app-version "${TAG}"
|
|
else
|
|
helm package "$dir" -d .cicd-charts
|
|
fi
|
|
echo "::endgroup::"
|
|
done
|
|
[ "${found}" = "1" ] || { echo "No charts under helm-charts/*"; exit 0; }
|
|
if [ "${PUBLISH}" = "true" ]; then
|
|
for tgz in .cicd-charts/*.tgz; do
|
|
helm push "$tgz" "oci://${HARBOR_ADDRESS}/${HARBOR_PROJECT}/charts"
|
|
done
|
|
else
|
|
echo "PUBLISH=false — packaged charts, not pushing"
|
|
fi
|
|
|
|
- name: Log out
|
|
if: always() && steps.meta.outputs.publish == 'true'
|
|
run: helm registry logout "${HARBOR_ADDRESS}" || true
|