feat: devsecops-ci-cd-gitea skill — Gitea Actions → Harbor pipelines
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>
This commit is contained in:
@@ -0,0 +1,167 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user