Validate Helm Charts / Detect changed charts (pull_request) Successful in 2s
Validate Helm Charts / Build ${{ fromJSON(needs.detect-changed-charts.outputs.charts) }} (pull_request) Skipped
Validate Helm Charts / Trivy config ${{ fromJSON(needs.detect-changed-charts.outputs.scan-charts) }} (pull_request) Skipped
Validate Helm Charts / Install-test (pull_request) Skipped
Validate Helm Charts / Resource quantities (pull_request) Successful in 7s
github.repository_owner is populated on this Gitea instance: measured as "jlab-cluster" (12 characters) in run 282 of validate-charts.yml. The value is now in the comment above the upload step, so the fact survives without the step that produced it.
200 lines
11 KiB
YAML
200 lines
11 KiB
YAML
name: Publish Helm Chart with patched Application Version
|
|
on:
|
|
workflow_dispatch:
|
|
inputs:
|
|
chart-location:
|
|
required: false
|
|
type: string
|
|
default: "."
|
|
description: "location of the helm chart directory"
|
|
chart-name:
|
|
required: true
|
|
type: string
|
|
description: "name of the helm chart"
|
|
workflow_call:
|
|
inputs:
|
|
chart-location:
|
|
required: false
|
|
type: string
|
|
default: "."
|
|
description: "location of the helm chart directory"
|
|
chart-name:
|
|
required: true
|
|
type: string
|
|
description: "name of the helm chart"
|
|
outputs:
|
|
chart-version:
|
|
description: "the version of the published helm chart"
|
|
value: ${{ jobs.publish-helm-chart.outputs.chart-version }}
|
|
secrets:
|
|
OP_CONNECT_TOKEN:
|
|
required: true
|
|
jobs:
|
|
publish-helm-chart:
|
|
outputs:
|
|
chart-version: ${{ steps.get-chart-version.outputs.CHART_VERSION }}
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- name: "Checkout Repository"
|
|
uses: actions/checkout@v7
|
|
- name: "Install Helm"
|
|
uses: azure/setup-helm@v5
|
|
- name: "Configure OP-Connect"
|
|
uses: 1password/load-secrets-action/configure@v5
|
|
with:
|
|
connect-host: https://op-connect.jk-dev.de
|
|
# helm package exits 1 with "found in Chart.yaml, but missing in charts/
|
|
# directory" while a declared dependency has not been fetched, so this
|
|
# comes first. It is a no-op for every chart without a dependencies:
|
|
# block; ci/helm-dependencies.sh explains why it resolves from Chart.yaml
|
|
# instead of a committed Chart.lock. Note the consequence for the
|
|
# published artifact: the subchart .tgz is packaged INTO ours, so the
|
|
# version pinned in Chart.yaml is the version operators get, and nothing
|
|
# downstream ever sees the dependency again.
|
|
- name: "Fetch chart dependencies"
|
|
run: ${{ github.workspace }}/ci/helm-dependencies.sh "${{ inputs.chart-name }}"
|
|
working-directory: ${{ inputs.chart-location }}
|
|
- name: "Package Helm Chart"
|
|
run: helm package ${{ inputs.chart-name }}
|
|
working-directory: ${{ inputs.chart-location }}
|
|
- name: "Get Helm Chart Version"
|
|
id: get-chart-version
|
|
run: |-
|
|
CHART_VERSION=$(grep '^version:' ${{ inputs.chart-name}}/Chart.yaml | awk '{print $2}')
|
|
echo $CHART_VERSION
|
|
echo "CHART_VERSION=$CHART_VERSION" >> $GITHUB_OUTPUT
|
|
working-directory: ${{ inputs.chart-location }}
|
|
- name: "Load Chart Registry Credentials"
|
|
id: load-registry-creds
|
|
uses: 1password/load-secrets-action@v5
|
|
env:
|
|
OP_CONNECT_TOKEN: ${{ secrets.OP_CONNECT_TOKEN }}
|
|
REGISTRY_PASSWORD: op://GitHub-Actions/jlab-registry-access/password
|
|
REGISTRY_USERNAME: op://GitHub-Actions/jlab-registry-access/username
|
|
REGISTRY_CHARTS_URL: op://GitHub-Actions/jlab-registry-access/charts-url
|
|
# The target is a classic, index-based Helm registry, not an OCI one, so
|
|
# there is no "helm registry login" session and no "helm push": the upload
|
|
# is a single HTTP POST of the packaged .tgz to
|
|
# <charts-url>/<owner>/helm/api/charts, authenticated per request.
|
|
#
|
|
# That URL is composed, not stored (#63). charts-url holds only the base
|
|
# up to "/api/packages"; the owner segment comes from
|
|
# github.repository_owner - which Gitea does populate: measured as
|
|
# "jlab-cluster" in run 282 of validate-charts.yml, on a temporary step
|
|
# that was removed again. So no value pointing at the registry - not even
|
|
# a path segment - lives in this repository, and a second owner on the same
|
|
# instance needs no second setting. The mirror on github.com has Actions
|
|
# switched off, so the publish never runs there; if it ever did, the
|
|
# derived owner would be one the instance does not know and the upload
|
|
# would 404 rather than land somewhere wrong. Two guards make the switch
|
|
# from the old shape loud instead of puzzling: charts-url is rejected
|
|
# unless it ends in "/api/packages" (an old full URL still carrying
|
|
# "/helm", "/api/charts" or the owner is named explicitly), and the derived
|
|
# owner is rejected when empty or when it holds characters that are not a
|
|
# path segment - an empty one would build "<base>//helm/api/charts", i.e.
|
|
# another bare 404 against a masked URL.
|
|
#
|
|
# Three further properties of this step are load-bearing and each of them
|
|
# was paid for once already.
|
|
#
|
|
# 1) The values are mapped into the step's own env block and read as quoted
|
|
# shell variables - never interpolated into the command line. load-secrets-
|
|
# action v5 (bumped in #140) no longer exports the loaded values as
|
|
# environment variables for later steps the way v4 did; only the step
|
|
# outputs carry them. Every "${{ env.* }}" reference therefore resolved to
|
|
# an empty string from that bump on, with nothing reporting an error - the
|
|
# loading step logs "Populating variable" and stays green either way. The
|
|
# ":?" guards fail loudly and name the op:// field that is missing, and
|
|
# quoting keeps an empty value from silently shifting arguments.
|
|
#
|
|
# 2) The credentials never reach the process list. "curl --user $U:$P"
|
|
# would put them in argv; "curl --config -" reads its options from stdin
|
|
# instead, so nothing sensitive appears in any argument. The price of that
|
|
# config file is its value syntax: an unquoted value ends at the first
|
|
# space (measured on curl 8.5.0 - "has space" arrived as "has"), so the
|
|
# step refuses whitespace in the credentials up front rather than letting
|
|
# a truncated password come back as a puzzling 401. A Gitea access token
|
|
# is 40 hex characters, so this only ever fires on a mis-entered item.
|
|
#
|
|
# 3) "--fail-with-body" is what makes a failed upload fail the job. Unlike
|
|
# "helm push", curl exits 0 on HTTP 401/403/409 unless asked not to - the
|
|
# publish would go green while nothing was uploaded. Measured against Gitea
|
|
# 1.24.7: a 401 without the flag is exit 0, with the flag exit 22, and the
|
|
# response body is still printed so the log names the cause.
|
|
#
|
|
# The file name is built from the chart name and the version read above
|
|
# instead of a "<chart>-*.tgz" glob: --upload-file takes exactly one file,
|
|
# and a glob matching two turns the second into curl's URL argument, while
|
|
# a glob matching none is passed through literally. Both fail obscurely.
|
|
# "helm package" keeps the "+" of a "1.4.0+up1.9.2" version verbatim in the
|
|
# file name, so "<name>-<version>.tgz" is an exact match (measured).
|
|
- name: "Upload Helm Chart to the Chart Registry"
|
|
env:
|
|
REGISTRY_PASSWORD: ${{ steps.load-registry-creds.outputs.REGISTRY_PASSWORD }}
|
|
REGISTRY_USERNAME: ${{ steps.load-registry-creds.outputs.REGISTRY_USERNAME }}
|
|
REGISTRY_CHARTS_URL: ${{ steps.load-registry-creds.outputs.REGISTRY_CHARTS_URL }}
|
|
REGISTRY_OWNER: ${{ github.repository_owner }}
|
|
CHART_NAME: ${{ inputs.chart-name }}
|
|
CHART_VERSION: ${{ steps.get-chart-version.outputs.CHART_VERSION }}
|
|
run: |-
|
|
: "${REGISTRY_CHARTS_URL:?empty - check op://GitHub-Actions/jlab-registry-access/charts-url}"
|
|
: "${REGISTRY_USERNAME:?empty - check op://GitHub-Actions/jlab-registry-access/username}"
|
|
: "${REGISTRY_PASSWORD:?empty - check op://GitHub-Actions/jlab-registry-access/password}"
|
|
: "${REGISTRY_OWNER:?empty - github.repository_owner is not populated on this forge; do not hardcode an owner, report it}"
|
|
: "${CHART_NAME:?empty - the chart-name input did not arrive}"
|
|
: "${CHART_VERSION:?empty - could not read version: from ${CHART_NAME}/Chart.yaml}"
|
|
# 1Password masks the full URL, but a curl connection error prints only
|
|
# "<host>:<port>", which that mask would not cover. Mask the host too.
|
|
CHARTS_HOST="${REGISTRY_CHARTS_URL#*://}"
|
|
echo "::add-mask::${CHARTS_HOST%%/*}"
|
|
# charts-url is the base only; this workflow appends the owner, the
|
|
# package type and the endpoint. Reject the old, full shape by name so
|
|
# the failure says what to change in the vault item instead of 404ing
|
|
# against a URL the log has masked.
|
|
CHARTS_BASE="${REGISTRY_CHARTS_URL%/}"
|
|
case "$CHARTS_BASE" in
|
|
*/api/charts)
|
|
echo "::error::op://GitHub-Actions/jlab-registry-access/charts-url still ends in /api/charts - it must hold only the base up to /api/packages (https://<host>/api/packages); this workflow appends /<owner>/helm/api/charts itself"
|
|
exit 1
|
|
;;
|
|
*/helm|*/helm/*)
|
|
echo "::error::op://GitHub-Actions/jlab-registry-access/charts-url still contains the /helm path segment - it must hold only the base up to /api/packages (https://<host>/api/packages); this workflow appends /<owner>/helm/api/charts itself"
|
|
exit 1
|
|
;;
|
|
*/api/packages) ;;
|
|
*)
|
|
echo "::error::op://GitHub-Actions/jlab-registry-access/charts-url must end in /api/packages (https://<host>/api/packages) - no owner, no package type, no endpoint; this workflow appends /<owner>/helm/api/charts itself"
|
|
exit 1
|
|
;;
|
|
esac
|
|
# An empty owner would build "<base>//helm/api/charts", a stray "/" or
|
|
# "?" would build something else entirely - both are bare 404s.
|
|
case "$REGISTRY_OWNER" in
|
|
*[!A-Za-z0-9._-]*)
|
|
echo "::error::the derived repository owner is not usable as a URL path segment: '${REGISTRY_OWNER}'"
|
|
exit 1
|
|
;;
|
|
esac
|
|
CHARTS_API_URL="${CHARTS_BASE}/${REGISTRY_OWNER}/helm/api/charts"
|
|
case "${REGISTRY_USERNAME}${REGISTRY_PASSWORD}" in
|
|
*[[:space:]]*)
|
|
echo "::error::username/password in op://GitHub-Actions/jlab-registry-access must not contain whitespace - curl --config truncates an unquoted value at the first space"
|
|
exit 1
|
|
;;
|
|
esac
|
|
CHART_PACKAGE="${CHART_NAME}-${CHART_VERSION}.tgz"
|
|
test -f "$CHART_PACKAGE" || {
|
|
echo "::error::${CHART_PACKAGE} does not exist - helm package produced: $(ls -1 ./*.tgz 2>&1)"
|
|
exit 1
|
|
}
|
|
echo "uploading ${CHART_PACKAGE}"
|
|
printf 'user = %s:%s\nurl = %s\n' \
|
|
"$REGISTRY_USERNAME" "$REGISTRY_PASSWORD" "$CHARTS_API_URL" |
|
|
curl --config - \
|
|
--fail-with-body \
|
|
--silent --show-error \
|
|
--request POST \
|
|
--upload-file "$CHART_PACKAGE" \
|
|
--write-out '\nHTTP %{http_code}\n'
|
|
working-directory: ${{ inputs.chart-location }}
|