Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
297 changes: 297 additions & 0 deletions .github/workflows/publish-helm-oci.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,297 @@
# Publish csi-driver-nfs container images and Helm chart to GHCR.
#
# Triggers:
# - Automatically on GitHub Release publish (tag must match v*)
# - Manually via workflow_dispatch with a version input
Comment thread
andyzhangx marked this conversation as resolved.
#
# Publishes:
# - Multi-arch container image: ghcr.io/kubernetes-csi/nfsplugin:<version>
# (linux/amd64, linux/arm64, linux/arm/v7, linux/ppc64le)
# - Helm chart (OCI): oci://ghcr.io/kubernetes-csi/csi-driver-nfs
Comment thread
andyzhangx marked this conversation as resolved.
#
name: Publish to GHCR
Comment thread
andyzhangx marked this conversation as resolved.

on:
release:
types: [published]
workflow_dispatch:
inputs:
version:
description: 'Version to publish (e.g., v4.13.2)'
required: true
type: string

permissions:
contents: read
packages: write

env:
REGISTRY: ghcr.io/kubernetes-csi
IMAGE_NAME: nfsplugin
CHART_NAME: csi-driver-nfs
# GO_VERSION sourced from go.mod via go-version-file

jobs:
# ── Validate version format early ──────────────────────────────────────────
validate:
runs-on: ubuntu-latest
outputs:
version: ${{ steps.version.outputs.version }}
chart_version: ${{ steps.version.outputs.chart_version }}
steps:
- name: Determine and validate version
id: version
run: |
if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
VERSION="${{ github.event.inputs.version }}"
else
VERSION="${{ github.event.release.tag_name }}"
fi

if [[ ! "$VERSION" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "::error::Invalid version format: '${VERSION}'. Expected: v<major>.<minor>.<patch> (e.g., v4.13.2)"
exit 1
fi

echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
echo "chart_version=${VERSION#v}" >> "$GITHUB_OUTPUT"
echo "Version: ${VERSION}"

# ── Build & push container images ──────────────────────────────────────────
build-linux:
runs-on: ubuntu-latest
needs: [validate]
strategy:
matrix:
include:
- goarch: amd64
platform: linux/amd64
suffix: linux-amd64
- goarch: arm64
platform: linux/arm64
suffix: linux-arm64
- goarch: ppc64le
platform: linux/ppc64le
suffix: linux-ppc64le
- goarch: arm
goarm: '7'
platform: linux/arm/v7
suffix: linux-arm-v7
steps:
- name: Checkout at version tag
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
ref: ${{ needs.validate.outputs.version }}

- name: Set up Go
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6
with:
go-version-file: go.mod

- name: Build binary
run: |
VERSION="${{ needs.validate.outputs.version }}"
if [[ "${{ matrix.goarch }}" == "arm" ]]; then
make nfs-armv7 IMAGE_VERSION="${VERSION}" PUBLISH=true
else
make nfs ARCH=${{ matrix.goarch }} IMAGE_VERSION="${VERSION}" PUBLISH=true
fi

- name: Set up QEMU
uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3

- name: Login to GHCR
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Build and push ${{ matrix.platform }}
run: |
VERSION="${{ needs.validate.outputs.version }}"
# Match Makefile convention: --build-arg ARCH=arm/v7 for armv7
if [[ "${{ matrix.goarch }}" == "arm" ]]; then
DOCKER_ARCH="arm/v7"
else
DOCKER_ARCH="${{ matrix.goarch }}"
fi
GIT_COMMIT=$(git rev-parse HEAD)
docker buildx build --pull --push \
--platform=${{ matrix.platform }} \
--build-arg ARCH=${DOCKER_ARCH} \
--provenance=false --sbom=false \
--tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${VERSION}-${{ matrix.suffix }} \
--label org.opencontainers.image.revision=${GIT_COMMIT} \
--label org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }} \
.

# ── Create multi-arch manifest ─────────────────────────────────────────────
manifest:
runs-on: ubuntu-latest
needs: [validate, build-linux]
steps:
- name: Login to GHCR
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Create and push multi-arch manifest
env:
DOCKER_CLI_EXPERIMENTAL: enabled
run: |
VERSION="${{ needs.validate.outputs.version }}"
IMAGE="${REGISTRY}/${IMAGE_NAME}"

docker manifest create --amend "${IMAGE}:${VERSION}" \
"${IMAGE}:${VERSION}-linux-amd64" \
"${IMAGE}:${VERSION}-linux-arm64" \
"${IMAGE}:${VERSION}-linux-arm-v7" \
Comment thread
andyzhangx marked this conversation as resolved.
"${IMAGE}:${VERSION}-linux-ppc64le"

docker manifest push -p "${IMAGE}:${VERSION}"
echo "Pushed manifest: ${IMAGE}:${VERSION}"
Comment thread
andyzhangx marked this conversation as resolved.

# ── Publish Helm chart as OCI (independent of image builds) ────────────────
publish-helm:
runs-on: ubuntu-latest
needs: [validate]
outputs:
helm_chart_version: ${{ steps.verify_version.outputs.helm_chart_version }}
steps:
- name: Checkout at version tag
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
Comment thread
andyzhangx marked this conversation as resolved.
with:
ref: ${{ needs.validate.outputs.version }}

- name: Validate chart directory
id: chart
run: |
VERSION="${{ needs.validate.outputs.version }}"
CHART_DIR="charts/${VERSION}/${CHART_NAME}"
# Fallback: some repos store the chart under charts/latest/
if [[ ! -f "${CHART_DIR}/Chart.yaml" ]]; then
CHART_DIR="charts/latest/${CHART_NAME}"
fi
if [[ ! -f "${CHART_DIR}/Chart.yaml" ]]; then
echo "::error::Chart not found for ${VERSION}"
echo "Searched: charts/${VERSION}/${CHART_NAME} and charts/latest/${CHART_NAME}"
echo "Available chart directories:"
ls -1 charts/ || echo " (none)"
exit 1
fi
echo "chart_dir=${CHART_DIR}" >> "$GITHUB_OUTPUT"

- name: Setup Helm
uses: azure/setup-helm@b9e51907a09c216f16ebe8536097933489208112 # v4
with:
version: v3.17.0

- name: Lint chart
run: helm lint "${{ steps.chart.outputs.chart_dir }}"

- name: Verify chart version matches
id: verify_version
run: |
YAML_VERSION=$(helm show chart "${{ steps.chart.outputs.chart_dir }}" | grep '^version:' | awk '{print $2}')
EXPECTED="${{ needs.validate.outputs.chart_version }}"
# Handle older charts that may have leading 'v' in Chart.yaml version
YAML_VERSION_STRIPPED="${YAML_VERSION#v}"
if [[ "$YAML_VERSION_STRIPPED" != "$EXPECTED" ]]; then
echo "::error::Chart.yaml version ($YAML_VERSION) != expected ($EXPECTED)"
exit 1
fi
# Use the actual Chart.yaml version for helm commands
echo "helm_chart_version=${YAML_VERSION}" >> "$GITHUB_ENV"
echo "helm_chart_version=${YAML_VERSION}" >> "$GITHUB_OUTPUT"

- name: Login to GHCR (Helm)
run: |
echo "${{ secrets.GITHUB_TOKEN }}" | helm registry login ghcr.io \
--username "${{ github.actor }}" --password-stdin

- name: Package and push chart
run: |
helm package "${{ steps.chart.outputs.chart_dir }}" --destination /tmp/helm-pkg
PACKAGE=$(ls /tmp/helm-pkg/${CHART_NAME}-*.tgz)
helm push "${PACKAGE}" "oci://${REGISTRY}"
Comment thread
andyzhangx marked this conversation as resolved.

- name: Verify published chart
run: |
helm show chart "oci://${REGISTRY}/${CHART_NAME}" \
--version "${{ steps.verify_version.outputs.helm_chart_version }}"

# ── Ensure GHCR packages are public ─────────────────────────────────────────
set-visibility:
runs-on: ubuntu-latest
needs: [manifest, publish-helm]
if: needs.manifest.result == 'success' || needs.publish-helm.result == 'success'
steps:
- name: Set container package visibility to public
if: needs.manifest.result == 'success'
run: |
curl -s -X PATCH \
-H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" \
-H "Accept: application/vnd.github+json" \
"https://api.github.com/orgs/${{ github.repository_owner }}/packages/container/${IMAGE_NAME}" \
-d '{"visibility":"public"}' || echo "::warning::Failed to set container package visibility (may need manual setup)"

- name: Set Helm chart package visibility to public
if: needs.publish-helm.result == 'success'
run: |
curl -s -X PATCH \
-H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" \
-H "Accept: application/vnd.github+json" \
"https://api.github.com/orgs/${{ github.repository_owner }}/packages/container/${CHART_NAME}" \
-d '{"visibility":"public"}' || echo "::warning::Failed to set Helm chart package visibility (may need manual setup)"

# ── Summary ────────────────────────────────────────────────────────────────
summary:
runs-on: ubuntu-latest
needs: [validate, manifest, publish-helm, set-visibility]
if: always()
steps:
- name: Job summary
env:
VERSION: ${{ needs.validate.outputs.version }}
CHART_VERSION: ${{ needs.publish-helm.outputs.helm_chart_version || needs.validate.outputs.chart_version }}
MANIFEST_RESULT: ${{ needs.manifest.result }}
HELM_RESULT: ${{ needs.publish-helm.result }}
shell: bash
run: |
FENCE='```'
echo "## Publish to GHCR — Results" >> "$GITHUB_STEP_SUMMARY"
echo "" >> "$GITHUB_STEP_SUMMARY"

if [[ "$MANIFEST_RESULT" == "success" ]]; then
{
echo "### ✅ Container Image"
echo "| Image | Tag |"
echo "|-------|-----|"
echo "| ${REGISTRY}/${IMAGE_NAME} | ${VERSION} (linux/amd64, arm64, arm/v7, ppc64le) |"
echo ""
echo "${FENCE}bash"
echo "docker pull ${REGISTRY}/${IMAGE_NAME}:${VERSION}"
echo "${FENCE}"
} >> "$GITHUB_STEP_SUMMARY"
else
echo "### ❌ Container Image — ${MANIFEST_RESULT}" >> "$GITHUB_STEP_SUMMARY"
fi

echo "" >> "$GITHUB_STEP_SUMMARY"

if [[ "$HELM_RESULT" == "success" ]]; then
{
echo "### ✅ Helm Chart (OCI)"
echo "${FENCE}bash"
echo "helm install csi-driver-nfs oci://${REGISTRY}/${CHART_NAME} --version ${CHART_VERSION} -n kube-system"
echo "${FENCE}"
} >> "$GITHUB_STEP_SUMMARY"
else
echo "### ❌ Helm Chart — ${HELM_RESULT}" >> "$GITHUB_STEP_SUMMARY"
fi
Loading