-
Notifications
You must be signed in to change notification settings - Fork 0
Add reusable workflows #11
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: 8-ci-upgrade-github-actions-to-latest-major-versions
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,121 @@ | ||
| # Reusable workflow: lint and (optionally) kubeconform-validate Helm charts. | ||
| # | ||
| # Language-agnostic — works for single-chart repos (chart_glob: "charts/") | ||
| # and monorepos with several charts (chart_glob: "charts/*/"). | ||
| # | ||
| # Single-chart caller (sugar-suite / course-workload-estimator): | ||
| # | ||
| # jobs: | ||
| # helm-lint: | ||
| # uses: bcit-tlu/.github/.github/workflows/helm-lint.yaml@main | ||
| # with: | ||
| # chart_glob: "charts/" | ||
| # extra_command: bash tests/helm-cdn-rewrite.sh # optional | ||
| # | ||
| # Monorepo caller (hriv): | ||
| # | ||
| # jobs: | ||
| # helm-lint: | ||
| # uses: bcit-tlu/.github/.github/workflows/helm-lint.yaml@main | ||
| # with: | ||
| # chart_glob: "charts/*/" | ||
| # validate_continue_on_error: true | ||
| # extra_command: bash scripts/test-helm-chart-regressions.sh | ||
|
|
||
| name: Helm lint | ||
|
|
||
| on: | ||
| workflow_call: | ||
| inputs: | ||
| chart_glob: | ||
| description: "Space/glob-expandable list of chart directories to lint (e.g. 'charts/' or 'charts/*/')." | ||
| required: false | ||
| type: string | ||
| default: "charts/" | ||
| kubeconform_version: | ||
| description: "kubeconform release to install." | ||
| required: false | ||
| type: string | ||
| default: v0.6.7 | ||
| validate_continue_on_error: | ||
| description: "Treat kubeconform validation failures as non-fatal (schema downloads can be flaky). Strict `helm lint` stays the hard gate." | ||
| required: false | ||
| type: boolean | ||
| default: false | ||
| extra_command: | ||
| description: "Optional shell command run after validation (e.g. a repo-specific chart test script)." | ||
| required: false | ||
| type: string | ||
| default: "" | ||
|
|
||
| permissions: | ||
| contents: read | ||
|
|
||
| jobs: | ||
| lint: | ||
| runs-on: ubuntu-latest | ||
| permissions: | ||
| contents: read | ||
| steps: | ||
| # Bring the chart sources into the runner. | ||
| - uses: actions/checkout@v7 | ||
|
|
||
| # `helm` is used both to lint and to render charts for kubeconform. | ||
| - uses: azure/setup-helm@v5 | ||
|
|
||
| # kubeconform renders + schema-validates the templated manifests. | ||
| - name: Install kubeconform | ||
| env: | ||
| KUBECONFORM_VERSION: ${{ inputs.kubeconform_version }} | ||
| run: | | ||
| curl -sSL "https://github.com/yannh/kubeconform/releases/download/${KUBECONFORM_VERSION}/kubeconform-linux-amd64.tar.gz" \ | ||
| | sudo tar xz -C /usr/local/bin kubeconform | ||
|
|
||
| # Cache the downloaded JSON schemas so repeated runs don't re-fetch them | ||
| # (keyed on the kubeconform version + Chart.yaml set). | ||
| - name: Cache kubeconform schemas | ||
| uses: actions/cache@v5 | ||
| with: | ||
| path: /tmp/kubeconform-cache | ||
| key: kubeconform-schemas-${{ inputs.kubeconform_version }}-${{ hashFiles('charts/**/Chart.yaml') }} | ||
| restore-keys: | | ||
| kubeconform-schemas-${{ inputs.kubeconform_version }}- | ||
|
|
||
| # Hard gate: `helm lint` each chart in the glob. Any failure fails CI. | ||
| - name: Lint charts (strict gate) | ||
| env: | ||
| CHART_GLOB: ${{ inputs.chart_glob }} | ||
| run: | | ||
| set -euo pipefail | ||
| for chart in ${CHART_GLOB}; do | ||
| echo "::group::helm lint ${chart}" | ||
| helm lint "${chart}" | ||
| echo "::endgroup::" | ||
| done | ||
|
|
||
| # kubeconform schema downloads from raw.githubusercontent.com can be flaky | ||
| # on shared runners. Callers that want the strict `helm lint` above to be | ||
| # the primary gate can set validate_continue_on_error to absorb transient | ||
| # download failures here. | ||
| - name: Validate rendered charts with kubeconform | ||
| continue-on-error: ${{ inputs.validate_continue_on_error }} | ||
| env: | ||
| CHART_GLOB: ${{ inputs.chart_glob }} | ||
| run: | | ||
| set -euo pipefail | ||
| mkdir -p /tmp/kubeconform-cache | ||
| for chart in ${CHART_GLOB}; do | ||
| echo "::group::kubeconform ${chart}" | ||
| helm template test "${chart}" \ | ||
| | kubeconform -cache /tmp/kubeconform-cache -strict -summary \ | ||
| -schema-location default \ | ||
| -schema-location 'https://raw.githubusercontent.com/datreeio/CRDs-catalog/main/{{.Group}}/{{.ResourceKind}}_{{.ResourceAPIVersion}}.json' \ | ||
| -ignore-missing-schemas | ||
| echo "::endgroup::" | ||
| done | ||
|
|
||
| # Optional repo-specific check (e.g. a chart regression/CDN-rewrite | ||
| # script) run only when the caller supplies `extra_command`. | ||
| - name: Run extra command | ||
| if: inputs.extra_command != '' | ||
| run: ${{ inputs.extra_command }} | ||
|
Comment on lines
+119
to
+121
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟨 Reusable workflow input interpolated directly into a shell run block The Was this helpful? React with 👍 or 👎 to provide feedback. |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,220 @@ | ||
| # Reusable workflow: package, push, and cosign-sign a Helm chart to an OCI | ||
| # registry. | ||
| # | ||
| # Two version modes: | ||
| # * RC publish (from ci.yaml): pass `version` directly (e.g. the | ||
| # oci-build rc_version). Empty `version` = nothing to publish → skip. | ||
| # * Release publish (from a `release: published` workflow): pass | ||
| # `tag_name` + `tag_prefix`; the tag is parsed to a semver `version`. | ||
| # A tag that doesn't match the prefix is skipped (useful for monorepo | ||
| # matrices where only one component's tag matches). | ||
| # | ||
| # RC caller (single chart, sugar-suite ci.yaml): | ||
| # | ||
| # helm-publish: | ||
| # needs: [helm-lint, build-sugar-suite, cdn-upload] | ||
| # if: github.event_name != 'pull_request' && needs.build-sugar-suite.outputs.rc_version != '' | ||
| # uses: bcit-tlu/.github/.github/workflows/helm-publish.yaml@main | ||
| # permissions: | ||
| # contents: read | ||
| # packages: write | ||
| # id-token: write | ||
| # with: | ||
| # chart_dir: charts | ||
| # version: ${{ needs.build-sugar-suite.outputs.rc_version }} | ||
| # inject_cdn_sha: true | ||
| # secrets: inherit | ||
| # | ||
| # Release caller (monorepo matrix, hriv helm-publish.yaml): | ||
| # | ||
| # publish: | ||
| # strategy: | ||
| # matrix: | ||
| # component: [frontend, backend, backup] | ||
| # uses: bcit-tlu/.github/.github/workflows/helm-publish.yaml@main | ||
| # permissions: | ||
| # contents: read | ||
| # packages: write | ||
| # id-token: write | ||
| # with: | ||
| # chart_dir: charts/${{ matrix.component }} | ||
| # tag_name: ${{ inputs.tag_name || github.event.release.tag_name }} | ||
| # tag_prefix: ${{ matrix.component }}-v | ||
| # ref: ${{ inputs.tag_name || github.event.release.tag_name }} | ||
| # update_dependencies: true | ||
| # secrets: inherit | ||
|
|
||
| name: Helm publish | ||
|
|
||
| on: | ||
| workflow_call: | ||
| inputs: | ||
| chart_dir: | ||
| description: "Path to the chart directory (e.g. 'charts' or 'charts/frontend')." | ||
| required: true | ||
| type: string | ||
| version: | ||
| description: "Explicit chart/app version to publish. When set, tag_name/tag_prefix are ignored." | ||
| required: false | ||
| type: string | ||
| default: "" | ||
| tag_name: | ||
| description: "Release tag to parse when `version` is empty (e.g. v1.2.3 or backend-v1.2.3)." | ||
| required: false | ||
| type: string | ||
| default: "" | ||
| tag_prefix: | ||
| description: "Prefix preceding the semver in tag_name (e.g. 'v' or 'backend-v'). A tag not matching is skipped." | ||
| required: false | ||
| type: string | ||
| default: v | ||
| ref: | ||
| description: "Git ref to checkout (defaults to the triggering commit). Set to the release tag for release publishes." | ||
| required: false | ||
| type: string | ||
| default: "" | ||
| inject_cdn_sha: | ||
| description: "Inject the short commit SHA into <chart_dir>/values.yaml `cdn.commitSha` before packaging." | ||
| required: false | ||
| type: boolean | ||
| default: false | ||
| update_dependencies: | ||
| description: "Run `helm dependency update` before packaging (charts with subchart deps)." | ||
| required: false | ||
| type: boolean | ||
| default: false | ||
| registry: | ||
| description: "OCI registry hosting the chart." | ||
| required: false | ||
| type: string | ||
| default: ghcr.io | ||
|
|
||
| permissions: | ||
| contents: read | ||
|
|
||
| jobs: | ||
| publish: | ||
| runs-on: ubuntu-latest | ||
| permissions: | ||
| contents: read | ||
| packages: write | ||
| id-token: write | ||
| env: | ||
| REGISTRY: ${{ inputs.registry }} | ||
| steps: | ||
| # Decide what version to publish (and whether to publish at all): | ||
| # * explicit `version` wins (RC publishes from ci.yaml); | ||
| # * otherwise parse the semver out of `tag_name` using `tag_prefix`; | ||
| # * a non-matching tag sets publish=false so the rest of the job is | ||
| # skipped (lets monorepo matrices no-op on other components' tags). | ||
| - name: Resolve version | ||
| id: resolve | ||
| shell: bash | ||
| env: | ||
| VERSION_IN: ${{ inputs.version }} | ||
| TAG: ${{ inputs.tag_name }} | ||
| PREFIX: ${{ inputs.tag_prefix }} | ||
| run: | | ||
| set -euo pipefail | ||
| if [[ -n "${VERSION_IN}" ]]; then | ||
| echo "version=${VERSION_IN}" >> "$GITHUB_OUTPUT" | ||
| echo "publish=true" >> "$GITHUB_OUTPUT" | ||
| elif [[ "${TAG}" =~ ^${PREFIX}([0-9]+\.[0-9]+\.[0-9]+)$ ]]; then | ||
| echo "version=${BASH_REMATCH[1]}" >> "$GITHUB_OUTPUT" | ||
| echo "publish=true" >> "$GITHUB_OUTPUT" | ||
| else | ||
| echo "publish=false" >> "$GITHUB_OUTPUT" | ||
| echo "No version input and tag '${TAG}' does not match prefix '${PREFIX}'; skipping." | ||
| fi | ||
|
|
||
| # Check out the exact ref being published (release tag for releases, | ||
| # triggering commit for RC publishes) so the packaged chart matches it. | ||
| - uses: actions/checkout@v7 | ||
| if: steps.resolve.outputs.publish == 'true' | ||
| with: | ||
| ref: ${{ inputs.ref || github.sha }} | ||
|
|
||
| # `helm` packages/pushes the chart; cosign signs the pushed artifact. | ||
| - uses: azure/setup-helm@v5 | ||
| if: steps.resolve.outputs.publish == 'true' | ||
|
|
||
| - uses: sigstore/cosign-installer@v4 | ||
| if: steps.resolve.outputs.publish == 'true' | ||
|
|
||
| # cosign + helm each need their own OCI login to the registry. | ||
| - name: Cosign login (OCI) | ||
| if: steps.resolve.outputs.publish == 'true' | ||
| env: | ||
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||
| REGISTRY: ${{ env.REGISTRY }} | ||
| ACTOR: ${{ github.actor }} | ||
| run: | | ||
| echo "${GITHUB_TOKEN}" | \ | ||
| cosign login "${REGISTRY}" \ | ||
| -u "${ACTOR}" \ | ||
| --password-stdin | ||
|
|
||
| # `helm registry login` is separate from the cosign login above. | ||
| - name: Helm login (OCI) | ||
| if: steps.resolve.outputs.publish == 'true' | ||
| shell: bash | ||
| env: | ||
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||
| REGISTRY: ${{ env.REGISTRY }} | ||
| ACTOR: ${{ github.actor }} | ||
| run: | | ||
| echo "${GITHUB_TOKEN}" | \ | ||
| helm registry login "${REGISTRY}" \ | ||
| -u "${ACTOR}" \ | ||
| --password-stdin | ||
|
|
||
| # Package the chart at the resolved version, push it to the OCI registry, | ||
| # then keyless-sign the pushed digest with cosign. | ||
| - name: Package, push & sign chart | ||
| if: steps.resolve.outputs.publish == 'true' | ||
| shell: bash | ||
| env: | ||
| VERSION: ${{ steps.resolve.outputs.version }} | ||
| CHART_DIR: ${{ inputs.chart_dir }} | ||
| INJECT_CDN_SHA: ${{ inputs.inject_cdn_sha }} | ||
| UPDATE_DEPS: ${{ inputs.update_dependencies }} | ||
| OCI_BASE: oci://${{ env.REGISTRY }}/${{ github.repository }}/charts | ||
| IMAGE_BASE: ${{ env.REGISTRY }}/${{ github.repository }}/charts | ||
|
Comment on lines
+181
to
+182
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📝 Info: OCI paths rely on github.repository already being lowercase
Was this helpful? React with 👍 or 👎 to provide feedback. |
||
| run: | | ||
| set -euo pipefail | ||
|
|
||
| CHART_NAME=$(yq '.name' "${CHART_DIR}/Chart.yaml") | ||
| DEST_DIR="/tmp/charts" | ||
| mkdir -p "${DEST_DIR}" | ||
|
|
||
| # Inject short commit SHA for CDN immutable path (matched by upload prefix). | ||
| if [[ "${INJECT_CDN_SHA}" == "true" ]]; then | ||
| SHORT_SHA="$(git rev-parse --short HEAD)" | ||
| yq -i ".cdn.commitSha = \"${SHORT_SHA}\"" "${CHART_DIR}/values.yaml" | ||
| fi | ||
|
Comment on lines
+190
to
+194
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔍 cdn.commitSha injected without the 'g' prefix used by the RC version
Was this helpful? React with 👍 or 👎 to provide feedback.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The g was added to prevent |
||
|
|
||
| if [[ "${UPDATE_DEPS}" == "true" ]]; then | ||
| helm dependency update "${CHART_DIR}" || true | ||
| fi | ||
|
|
||
| echo "::group::${CHART_NAME} ${VERSION}" | ||
| helm package "${CHART_DIR}" \ | ||
| --version "${VERSION}" \ | ||
| --app-version "${VERSION}" \ | ||
| -d "${DEST_DIR}" | ||
|
|
||
| PUSH_OUT=$(helm push \ | ||
| "${DEST_DIR}/${CHART_NAME}-${VERSION}.tgz" \ | ||
| "${OCI_BASE}" 2>&1) || { echo "${PUSH_OUT}"; exit 1; } | ||
| echo "${PUSH_OUT}" | ||
|
|
||
| # Tolerant digest parse: case-insensitive, allows leading whitespace. | ||
| DIGEST=$(printf '%s\n' "${PUSH_OUT}" \ | ||
| | awk 'tolower($1)=="digest:"{print $2; exit}') | ||
| if [[ -z "${DIGEST}" ]]; then | ||
| echo "::error::helm push for ${CHART_NAME} succeeded but no digest found in output (helm output format may have changed)" | ||
| exit 1 | ||
| fi | ||
|
|
||
| cosign sign --yes "${IMAGE_BASE}/${CHART_NAME}@${DIGEST}" | ||
| echo "::endgroup::" | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
📝 Info: kubeconform schema cache key is keyed only on Chart.yaml, not chart_glob
The cache key
kubeconform-schemas-${{ inputs.kubeconform_version }}-${{ hashFiles('charts/**/Chart.yaml') }}hardcodes thecharts/**/Chart.yamlglob, independent of the configurablechart_globinput. This works for both documented callers (single-chartcharts/→charts/Chart.yaml, and monorepocharts/*/→charts/*/Chart.yaml, both matched bycharts/**/Chart.yaml). It would only break for a caller whose charts live outsidecharts/, wherehashFilesreturns empty and the key collapses to a constant. Not a correctness issue because kubeconform re-downloads any missing schemas andrestore-keysprovides prefix fallback; noting for awareness in case chart layouts diverge from the two documented shapes.Was this helpful? React with 👍 or 👎 to provide feedback.