diff --git a/.github/workflows/shared-docker-release.yaml b/.github/workflows/shared-docker-release.yaml new file mode 100644 index 0000000..3dd89ae --- /dev/null +++ b/.github/workflows/shared-docker-release.yaml @@ -0,0 +1,100 @@ +name: Shared Docker Release + +on: + workflow_call: + inputs: + runs-on: + description: "The runner to use for the job (must be a Linux runner; this workflow uses Docker buildx)" + required: false + default: "ubuntu-latest" + type: string + image-name: + description: "Full GHCR image name, e.g. ghcr.io/cloudoperators/myapp" + required: true + type: string + dockerfile: + description: "Path to the Dockerfile" + required: false + default: "Dockerfile" + type: string + context: + description: "Docker build context path" + required: false + default: "." + type: string + platforms: + description: "Comma-separated list of target platforms" + required: false + default: "linux/amd64,linux/arm64" + type: string + environment: + description: "GitHub environment name to use for this job (e.g. release), for environment protection rules and scoped secrets" + required: false + default: "" + type: string + secrets: + registry-token: + description: "Token to authenticate to GHCR; falls back to GITHUB_TOKEN if not provided" + required: false + +jobs: + docker-release: + runs-on: ${{ inputs.runs-on }} + environment: ${{ inputs.environment != '' && inputs.environment || null }} + permissions: + contents: read + packages: write + steps: + - name: Validate inputs + env: + IMAGE_NAME: ${{ inputs.image-name }} + run: | + if [ "$RUNNER_OS" != "Linux" ]; then + echo "ERROR: this workflow requires a Linux runner (uses Docker buildx); got: $RUNNER_OS" + exit 1 + fi + case "$IMAGE_NAME" in + ghcr.io/*) ;; + *) echo "ERROR: image-name must start with 'ghcr.io/' (got: '$IMAGE_NAME')"; exit 1 ;; + esac + + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + + - name: Set up QEMU + uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v3 + + - name: Log in to container registry + uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.registry-token != '' && secrets.registry-token || secrets.GITHUB_TOKEN }} + + - name: Extract Docker metadata + id: meta + uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v5 + with: + images: ${{ inputs.image-name }} + flavor: | + latest=false + tags: | + type=semver,pattern={{version}} + type=semver,pattern=v{{version}} + type=semver,pattern=v{{major}}.{{minor}} + type=semver,pattern=v{{major}} + type=raw,value=latest,enable={{is_default_branch}} + type=sha,format=long + + - name: Build and push Docker image + uses: docker/build-push-action@14487ce63c7a62a4a324b0bfb37086795e31c6c1 # v6 + with: + context: ${{ inputs.context }} + file: ${{ inputs.dockerfile }} + platforms: ${{ inputs.platforms }} + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} diff --git a/.github/workflows/shared-goreleaser.yaml b/.github/workflows/shared-goreleaser.yaml new file mode 100644 index 0000000..9df80de --- /dev/null +++ b/.github/workflows/shared-goreleaser.yaml @@ -0,0 +1,71 @@ +name: Shared GoReleaser + +on: + workflow_call: + inputs: + runs-on: + description: "The runner to use for the job (must be a Linux runner)" + required: false + default: "ubuntu-latest" + type: string + goreleaser-version: + description: "GoReleaser version constraint passed to goreleaser-action (e.g. '~> v2')" + required: false + default: "~> v2" + type: string + goreleaser-config: + description: "Path to .goreleaser.yaml config file; leave empty to use GoReleaser's default discovery" + required: false + default: "" + type: string + go-version-file: + description: "Path to the go.mod file used by setup-go" + required: false + default: "go.mod" + type: string + environment: + description: "GitHub environment name to use for this job (e.g. release), for environment protection rules and scoped secrets" + required: false + default: "" + type: string + secrets: + release-token: + description: "Token used for GoReleaser GitHub Release creation; falls back to GITHUB_TOKEN if not provided" + required: false + +jobs: + goreleaser: + runs-on: ${{ inputs.runs-on }} + environment: ${{ inputs.environment != '' && inputs.environment || null }} + concurrency: + group: goreleaser-${{ github.repository }}-${{ github.ref }} + cancel-in-progress: false + permissions: + contents: write + steps: + - name: Validate inputs + run: | + if [ "$RUNNER_OS" != "Linux" ]; then + echo "ERROR: this workflow requires a Linux runner; got: $RUNNER_OS" + exit 1 + fi + + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + with: + fetch-depth: 0 + + - name: Set up Go + uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5 + with: + go-version-file: ${{ inputs.go-version-file }} + cache: true + + - name: Run GoReleaser + uses: goreleaser/goreleaser-action@e435ccd777264be153ace6237001ef4d979d3a7a # v6 + with: + distribution: goreleaser + version: ${{ inputs.goreleaser-version }} + args: release --clean${{ inputs.goreleaser-config != '' && format(' --config {0}', inputs.goreleaser-config) || '' }} + env: + GITHUB_TOKEN: ${{ secrets.release-token != '' && secrets.release-token || secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/shared-helm-release.yaml b/.github/workflows/shared-helm-release.yaml new file mode 100644 index 0000000..f38b007 --- /dev/null +++ b/.github/workflows/shared-helm-release.yaml @@ -0,0 +1,138 @@ +name: Shared Helm Release + +on: + workflow_call: + inputs: + runs-on: + description: "The runner to use for the job (must be a Linux runner)" + required: false + default: "ubuntu-latest" + type: string + registry: + description: "Container registry hostname" + required: false + default: "ghcr.io" + type: string + chart-dir: + description: "Path to the chart directory containing Chart.yaml (e.g. charts/myapp)" + required: true + type: string + chart-name: + description: "Chart name used for the OCI duplicate-version check (must match the name field in Chart.yaml)" + required: true + type: string + helm-version: + description: "Helm CLI version to install" + required: false + default: "v3.17.0" + type: string + run-dependency-update: + description: "Run helm dependency update before packaging" + required: false + default: false + type: boolean + allow-existing: + description: "When true, exit 0 instead of failing if the chart version already exists in the registry. Useful when not all charts in a batch have changed." + required: false + default: false + type: boolean + environment: + description: "GitHub environment name to use for this job (e.g. release), for environment protection rules and scoped secrets" + required: false + default: "" + type: string + secrets: + registry-token: + description: "Token to authenticate to the container registry; falls back to GITHUB_TOKEN if not provided" + required: false + +jobs: + helm-release: + runs-on: ${{ inputs.runs-on }} + environment: ${{ inputs.environment != '' && inputs.environment || null }} + concurrency: + group: helm-release-${{ github.repository }}-${{ inputs.chart-name }}-${{ github.ref }} + cancel-in-progress: false + permissions: + contents: read + packages: write + steps: + - name: Validate inputs + env: + CHART_DIR: ${{ inputs.chart-dir }} + run: | + if [ "$RUNNER_OS" != "Linux" ]; then + echo "ERROR: this workflow requires a Linux runner; got: $RUNNER_OS" + exit 1 + fi + if [ -z "$CHART_DIR" ]; then + echo "ERROR: chart-dir is required" + exit 1 + fi + + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + + - name: Set up Helm + uses: azure/setup-helm@9bc31f4ebc9c6b171d7bfbaa5d006ae7abdb4310 # v5.0.1 + with: + version: ${{ inputs.helm-version }} + + - name: Log in to container registry + uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3 + with: + registry: ${{ inputs.registry }} + username: ${{ github.actor }} + password: ${{ secrets.registry-token != '' && secrets.registry-token || secrets.GITHUB_TOKEN }} + + - name: Validate chart and check for duplicate version + id: chart-check + env: + CHART_DIR: ${{ inputs.chart-dir }} + CHART_NAME: ${{ inputs.chart-name }} + REGISTRY: ${{ inputs.registry }} + ALLOW_EXISTING: ${{ inputs.allow-existing }} + run: | + if [ ! -f "${CHART_DIR}/Chart.yaml" ]; then + echo "ERROR: chart-dir '${CHART_DIR}' does not contain Chart.yaml" + exit 1 + fi + CHART_VERSION=$(helm show chart "${CHART_DIR}" | grep -E '^version:' | head -1 | sed 's/^version:[[:space:]]*//' | tr -d '[:space:]') + if ! echo "$CHART_VERSION" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+'; then + echo "ERROR: could not parse semver from chart metadata (got: '$CHART_VERSION')" + exit 1 + fi + echo "chart_version=${CHART_VERSION}" >> "$GITHUB_OUTPUT" + PULL_STDERR=$(helm pull "oci://${REGISTRY}/${{ github.repository }}/charts/${CHART_NAME}" --version "${CHART_VERSION}" 2>&1 >/dev/null) && PULL_EXIT=0 || PULL_EXIT=$? + if [ "$PULL_EXIT" -eq 0 ]; then + if [ "$ALLOW_EXISTING" = "true" ]; then + echo "Chart ${CHART_NAME}:${CHART_VERSION} already exists in OCI registry — skipping (allow-existing=true)" + echo "skip=true" >> "$GITHUB_OUTPUT" + else + echo "ERROR: chart ${CHART_NAME}:${CHART_VERSION} already exists in OCI registry — bump Chart.yaml version before pushing" + exit 1 + fi + elif echo "$PULL_STDERR" | grep -qiE 'not found|does not exist|manifest unknown|tag does not exist'; then + : # version absent — proceed with push + else + echo "ERROR: helm pull failed with an unexpected error (auth/network/transport issue):" + echo "$PULL_STDERR" + exit 1 + fi + + - name: Run helm dependency update + if: inputs.run-dependency-update == true && steps.chart-check.outputs.skip != 'true' + env: + CHART_DIR: ${{ inputs.chart-dir }} + run: helm dependency update "$CHART_DIR" + + - name: Package and push Helm chart + if: steps.chart-check.outputs.skip != 'true' + env: + CHART_DIR: ${{ inputs.chart-dir }} + CHART_NAME: ${{ inputs.chart-name }} + CHART_VERSION: ${{ steps.chart-check.outputs.chart_version }} + REGISTRY: ${{ inputs.registry }} + run: | + helm package "$CHART_DIR" + helm push "${CHART_NAME}-${CHART_VERSION}.tgz" "oci://${REGISTRY}/${{ github.repository }}/charts/" diff --git a/.github/workflows/shared-release.yaml b/.github/workflows/shared-release.yaml index ee8be7a..6068551 100644 --- a/.github/workflows/shared-release.yaml +++ b/.github/workflows/shared-release.yaml @@ -32,6 +32,11 @@ on: required: false default: false type: boolean + chart-only: + description: "Bump Chart.yaml version only (skip appVersion, tag as helm-chart-vX.Y.Z, skip GitHub Release). Use for chart-only releases that do not involve a new app binary." + required: false + default: false + type: boolean dispatch-greenhouse-extensions: description: "Trigger a repository_dispatch event on cloudoperators/greenhouse-extensions after release" required: false @@ -79,14 +84,26 @@ jobs: env: MAKEFILE_PATH: ${{ inputs.makefile-path }} CHART_PATH: ${{ inputs.chart-path }} + CHART_ONLY: ${{ inputs.chart-only }} run: | if [ "$RUNNER_OS" != "Linux" ]; then echo "ERROR: this workflow requires a Linux runner (uses GNU sed); got: $RUNNER_OS" exit 1 fi - if [ -z "$MAKEFILE_PATH" ] && [ -z "$CHART_PATH" ]; then - echo "ERROR: at least one of makefile-path or chart-path is required for version computation" - exit 1 + if [ "$CHART_ONLY" = "true" ]; then + if [ -z "$CHART_PATH" ]; then + echo "ERROR: chart-path is required when chart-only is true" + exit 1 + fi + if [ -n "$MAKEFILE_PATH" ]; then + echo "ERROR: makefile-path must not be set when chart-only is true — chart-only mode reads and writes Chart.yaml only" + exit 1 + fi + else + if [ -z "$MAKEFILE_PATH" ] && [ -z "$CHART_PATH" ]; then + echo "ERROR: at least one of makefile-path or chart-path is required for version computation" + exit 1 + fi fi if [ -n "$MAKEFILE_PATH" ] && [ ! -f "$MAKEFILE_PATH" ]; then echo "ERROR: makefile-path '$MAKEFILE_PATH' does not exist" @@ -174,7 +191,7 @@ jobs: sed -i "s/^version:.*/version: ${NEW_VERSION}/" "$CHART_PATH" - name: Update Chart.yaml appVersion - if: inputs.chart-path != '' && inputs.bump-chart-app-version == true + if: inputs.chart-path != '' && inputs.bump-chart-app-version == true && inputs.chart-only == false env: NEW_VERSION: ${{ steps.version.outputs.new }} CHART_PATH: ${{ inputs.chart-path }} @@ -193,10 +210,16 @@ jobs: if [ -n "$MAKEFILE_PATH" ]; then git add "$MAKEFILE_PATH"; fi if [ -n "$CHART_PATH" ]; then git add "$CHART_PATH"; fi git commit -m "chore: bump version to ${NEW_VERSION}" - git tag -a "v${NEW_VERSION}" -m "Release v${NEW_VERSION}" + if [ "${{ inputs.chart-only }}" = "true" ]; then + TAG="helm-chart-v${NEW_VERSION}" + else + TAG="v${NEW_VERSION}" + fi + git tag -a "$TAG" -m "Release $TAG" git push origin "$DEFAULT_BRANCH" --follow-tags - name: Create GitHub release + if: inputs.chart-only == false uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7 with: github-token: ${{ secrets.release-token }} diff --git a/RELEASING.md b/RELEASING.md new file mode 100644 index 0000000..f741458 --- /dev/null +++ b/RELEASING.md @@ -0,0 +1,446 @@ +# Releasing + +This document describes the standard release process for repositories in the `cloudoperators` org. All shared release workflows live in [`cloudoperators/common`](https://github.com/cloudoperators/common) and are designed to be called from any repo. + +--- + +## Workflows + +| Workflow | Purpose | +|---|---| +| [`shared-release.yaml`](#shared-releaseyaml) | Bump version, commit, create git tag, create GitHub Release | +| [`shared-helm-release.yaml`](#shared-helm-releaseyaml) | Package and push a Helm chart to GHCR OCI | +| [`shared-docker-release.yaml`](#shared-docker-releaseyaml) | Build and push a Docker image to GHCR | +| [`shared-goreleaser.yaml`](#shared-goreleaseryaml) | Build Go binaries and create a GitHub Release via GoReleaser | + +A typical release flow chains these together: + +``` +shared-release (bumps version, creates tag) + → shared-docker-release (triggered by v* tag) + → shared-helm-release (triggered by v* or helm-chart-* tag) + → shared-goreleaser (triggered by v* tag) +``` + +--- + +## `shared-release.yaml` + +The entry point for all releases. Bumps the version in `Makefile` and/or `Chart.yaml`, commits, creates a git tag, and creates a GitHub Release. + +### Inputs + +| Input | Required | Default | Description | +|---|---|---|---| +| `bump` | ✅ | — | Version bump type: `patch`, `minor`, or `major` | +| `chart-path` | | `""` | Relative path to `Chart.yaml` (e.g. `charts/myapp/Chart.yaml`). Leave empty to skip. | +| `makefile-path` | | `""` | Relative path to `Makefile` containing a `VERSION` variable. Leave empty to skip. At least one of `chart-path` or `makefile-path` is required. | +| `bump-chart-app-version` | | `true` | Update `appVersion` in `Chart.yaml` to the new version. Set to `false` for chart-only releases (or use `chart-only`). | +| `bump-make-version` | | `false` | Also update the `IMG` tag line in `Makefile` to the new version. | +| `chart-only` | | `false` | When `true`: skip `appVersion`, tag as `helm-chart-vX.Y.Z` instead of `vX.Y.Z`, skip GitHub Release. Use for Helm chart changes that don't involve a new app binary. | +| `dispatch-greenhouse-extensions` | | `false` | Trigger a `repository_dispatch` event on `cloudoperators/greenhouse-extensions` after release. | +| `plugin-name` | | `""` | Plugin name passed as payload when dispatching to `greenhouse-extensions`. Required when `dispatch-greenhouse-extensions: true`. | +| `environment` | | `""` | GitHub environment name to activate (e.g. `release`), for protection rules and scoped secrets. | +| `runs-on` | | `ubuntu-latest` | Runner label. Must be a Linux runner (workflow uses GNU sed). | + +### Secrets + +| Secret | Required | Description | +|---|---|---| +| `release-token` | ✅ | PAT or GitHub App token with `contents: write` permission | +| `dispatch-app-id` | | GitHub App ID for dispatching to `greenhouse-extensions` | +| `dispatch-app-private-key` | | GitHub App private key for dispatching to `greenhouse-extensions` | + +> **Important:** Tags pushed with the built-in `GITHUB_TOKEN` do **not** trigger downstream `push`-triggered workflows (this is a GitHub security restriction). If you want the tag created by `shared-release` to automatically kick off `shared-docker-release`, `shared-goreleaser`, or `shared-helm-release`, you must pass a **PAT** or **GitHub App token** as `release-token`. Repos that use only `GITHUB_TOKEN` will need to trigger those downstream workflows manually after the release tag is created. + +### Example: App release (bumps version + appVersion) + +```yaml +name: Cut Release + +on: + workflow_dispatch: + inputs: + bump: + description: "Version bump type" + required: true + type: choice + options: [patch, minor, major] + +jobs: + release: + uses: cloudoperators/common/.github/workflows/shared-release.yaml@main + permissions: + contents: write + with: + bump: ${{ inputs.bump }} + makefile-path: Makefile + chart-path: charts/myapp/Chart.yaml + secrets: + release-token: ${{ secrets.RELEASE_TOKEN }} +``` + +### Example: Chart-only release (bumps chart version, not appVersion) + +```yaml +name: Cut Chart Release + +on: + workflow_dispatch: + inputs: + bump: + description: "Version bump type" + required: true + type: choice + options: [patch, minor, major] + +jobs: + release: + uses: cloudoperators/common/.github/workflows/shared-release.yaml@main + permissions: + contents: write + with: + bump: ${{ inputs.bump }} + chart-path: charts/myapp/Chart.yaml + chart-only: true + secrets: + release-token: ${{ secrets.RELEASE_TOKEN }} +``` + +This creates a `helm-chart-vX.Y.Z` tag, which can then trigger `shared-helm-release.yaml`. + +### Version source + +The workflow reads the current version from `makefile-path` first (looks for `VERSION ?= x.y.z`), then falls back to `chart-path` (`version: x.y.z`). At least one must be provided. + +> **Note for Makefile repos:** the `VERSION` variable must be a static value, not a shell expression. Replace `VERSION ?= $(shell git describe ...)` with `VERSION ?= 1.2.3` — the workflow will own version bumping from that point. + +--- + +## `shared-helm-release.yaml` + +Packages a Helm chart and pushes it to GHCR OCI. Includes a duplicate-version guard. + +### Inputs + +| Input | Required | Default | Description | +|---|---|---|---| +| `chart-dir` | ✅ | — | Path to the chart directory containing `Chart.yaml` (e.g. `charts/myapp`) | +| `chart-name` | ✅ | — | Chart name for the OCI duplicate-version check. Must match the `name:` field in `Chart.yaml`. | +| `registry` | | `ghcr.io` | Container registry hostname | +| `helm-version` | | `v3.17.0` | Helm CLI version | +| `run-dependency-update` | | `false` | Run `helm dependency update` before packaging. Set to `true` if the chart has local `file://` dependencies. | +| `allow-existing` | | `false` | When `true`: exit 0 instead of failing if the chart version already exists in the registry. Useful when multiple charts are released in batch and not all have changed. | +| `environment` | | `""` | GitHub environment name | +| `runs-on` | | `ubuntu-latest` | Runner label (must be Linux) | + +### Secrets + +| Secret | Required | Description | +|---|---|---| +| `registry-token` | | Registry token; falls back to `GITHUB_TOKEN` | + +### OCI path + +Charts are pushed to `oci://ghcr.io/{repo}/charts/` (repo-scoped). For example, a chart in `cloudoperators/myrepo` is pushed to `ghcr.io/cloudoperators/myrepo/charts/{chart-name}`. + +### Example: Single chart, tag-triggered + +```yaml +name: Helm Chart Publish + +on: + push: + tags: + - "helm-chart-*.*.*" + paths: + - 'charts/**' + +jobs: + helm-release: + uses: cloudoperators/common/.github/workflows/shared-helm-release.yaml@main + permissions: + contents: read + packages: write + with: + chart-dir: charts/myapp + chart-name: myapp +``` + +### Example: Multiple charts in one workflow + +```yaml +jobs: + helm-release-app: + uses: cloudoperators/common/.github/workflows/shared-helm-release.yaml@main + permissions: + contents: read + packages: write + with: + chart-dir: charts/myapp + chart-name: myapp + run-dependency-update: true + + helm-release-ui: + uses: cloudoperators/common/.github/workflows/shared-helm-release.yaml@main + permissions: + contents: read + packages: write + with: + chart-dir: charts/myapp-ui + chart-name: myapp-ui +``` + +### Example: Batch release where not all charts may have changed + +Use `allow-existing: true` so jobs for unchanged charts (same version) exit 0 instead of failing: + +```yaml + helm-release-plugin-a: + uses: cloudoperators/common/.github/workflows/shared-helm-release.yaml@main + permissions: + contents: read + packages: write + with: + chart-dir: plugin-a/charts + chart-name: plugin-a + allow-existing: true +``` + +--- + +## `shared-docker-release.yaml` + +Builds a multi-arch Docker image and pushes it to GHCR with a standard tag set. + +### Inputs + +| Input | Required | Default | Description | +|---|---|---|---| +| `image-name` | ✅ | — | Full GHCR image name, e.g. `ghcr.io/cloudoperators/myapp` | +| `dockerfile` | | `Dockerfile` | Path to the Dockerfile | +| `context` | | `.` | Docker build context path | +| `platforms` | | `linux/amd64,linux/arm64` | Comma-separated target platforms | +| `environment` | | `""` | GitHub environment name | +| `runs-on` | | `ubuntu-latest` | Runner label (must be Linux) | + +### Secrets + +| Secret | Required | Description | +|---|---|---| +| `registry-token` | | Registry token; falls back to `GITHUB_TOKEN` | + +### Tags applied + +| Tag | When | +|---|---| +| `1.2.3` | Always (full semver from tag) | +| `v1.2` | Always | +| `v1` | Always | +| `latest` | Only on default branch pushes | +| `sha-` | Always | + +### Example: Triggered by app release tag + +```yaml +name: Container Registry + +on: + push: + tags: + - 'v*.*.*' + +jobs: + docker-release: + uses: cloudoperators/common/.github/workflows/shared-docker-release.yaml@main + permissions: + contents: read + packages: write + with: + image-name: ghcr.io/cloudoperators/myapp +``` + +### Example: Non-default Dockerfile location + +```yaml + docker-release: + uses: cloudoperators/common/.github/workflows/shared-docker-release.yaml@main + permissions: + contents: read + packages: write + with: + image-name: ghcr.io/cloudoperators/myapp + dockerfile: build/Dockerfile + context: . +``` + +--- + +## `shared-goreleaser.yaml` + +Runs GoReleaser to build Go binaries and create a GitHub Release. Requires a `.goreleaser.yaml` config in the calling repo (or uses GoReleaser's default discovery). + +### Inputs + +| Input | Required | Default | Description | +|---|---|---|---| +| `goreleaser-version` | | `~> v2` | GoReleaser version constraint passed to `goreleaser-action` | +| `goreleaser-config` | | `""` | Path to `.goreleaser.yaml`. Leave empty to use GoReleaser's default file discovery. | +| `go-version-file` | | `go.mod` | Path to `go.mod` used by `setup-go` | +| `environment` | | `""` | GitHub environment name | +| `runs-on` | | `ubuntu-latest` | Runner label (must be Linux) | + +### Secrets + +| Secret | Required | Description | +|---|---|---| +| `release-token` | | Token for GoReleaser GitHub Release creation; falls back to `GITHUB_TOKEN` | + +### Example: Standard Go project + +```yaml +name: Release Binaries + +on: + push: + tags: + - "v*" + +jobs: + goreleaser: + uses: cloudoperators/common/.github/workflows/shared-goreleaser.yaml@main + permissions: + contents: write +``` + +### Example: Custom config path + +```yaml + goreleaser: + uses: cloudoperators/common/.github/workflows/shared-goreleaser.yaml@main + permissions: + contents: write + with: + goreleaser-config: .github/.goreleaser.yaml +``` + +--- + +## Complete example: Full release pipeline + +App releases and chart releases are **separate flows** with separate tags — `v*` for app releases, `helm-chart-*` for chart-only releases. Wire them up as two independent pipelines. + +> **Prerequisite:** For tags to automatically trigger downstream `push` workflows, `release-token` must be a PAT or GitHub App token. Tags pushed with `GITHUB_TOKEN` do not trigger other workflows. + +### App release pipeline + +Triggered manually → creates `vX.Y.Z` tag → Docker and binary publish fire automatically. + +**`.github/workflows/cut-release.yaml`** — manually triggered, bumps version and creates `v*` tag: +```yaml +name: Cut Release + +on: + workflow_dispatch: + inputs: + bump: + type: choice + options: [patch, minor, major] + required: true + +jobs: + release: + uses: cloudoperators/common/.github/workflows/shared-release.yaml@main + permissions: + contents: write + with: + bump: ${{ inputs.bump }} + makefile-path: Makefile + chart-path: charts/myapp/Chart.yaml + secrets: + release-token: ${{ secrets.RELEASE_TOKEN }} +``` + +**`.github/workflows/release-docker.yaml`** — fires when `cut-release` creates a `v*` tag: +```yaml +name: Release Docker + +on: + push: + tags: + - "v*.*.*" + +jobs: + docker: + uses: cloudoperators/common/.github/workflows/shared-docker-release.yaml@main + permissions: + contents: read + packages: write + with: + image-name: ghcr.io/cloudoperators/myapp +``` + +**`.github/workflows/release-binaries.yaml`** — fires when `cut-release` creates a `v*` tag: +```yaml +name: Release Binaries + +on: + push: + tags: + - "v*.*.*" + +jobs: + goreleaser: + uses: cloudoperators/common/.github/workflows/shared-goreleaser.yaml@main + permissions: + contents: write +``` + +### Chart release pipeline + +Triggered manually → creates `helm-chart-vX.Y.Z` tag → Helm publish fires automatically. + +**`.github/workflows/cut-chart-release.yaml`** — manually triggered, bumps chart version and creates `helm-chart-*` tag: +```yaml +name: Cut Chart Release + +on: + workflow_dispatch: + inputs: + bump: + type: choice + options: [patch, minor, major] + required: true + +jobs: + release: + uses: cloudoperators/common/.github/workflows/shared-release.yaml@main + permissions: + contents: write + with: + bump: ${{ inputs.bump }} + chart-path: charts/myapp/Chart.yaml + chart-only: true + secrets: + release-token: ${{ secrets.RELEASE_TOKEN }} +``` + +**`.github/workflows/release-chart.yaml`** — fires when `cut-chart-release` creates a `helm-chart-*` tag: +```yaml +name: Release Helm Chart + +on: + push: + tags: + - "helm-chart-*.*.*" + +jobs: + helm: + uses: cloudoperators/common/.github/workflows/shared-helm-release.yaml@main + permissions: + contents: read + packages: write + with: + chart-dir: charts/myapp + chart-name: myapp +```