renovate/artifacts Artifact file update failure
Validate Helm Charts / Detect changed charts (pull_request) Successful in 25s
Validate Helm Charts / Build ${{ fromJSON(needs.detect-changed-charts.outputs.charts) }} (pull_request) Skipped
Validate Helm Charts / Install-test (pull_request) Skipped
Validate Helm Charts / Resource quantities (pull_request) Successful in 25s
Validate Helm Charts / Trivy config ${{ fromJSON(needs.detect-changed-charts.outputs.scan-charts) }} (pull_request) Successful in 20s
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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
|
- name: "Install Helm"
|
|
uses: azure/setup-helm@9bc31f4ebc9c6b171d7bfbaa5d006ae7abdb4310 # v5
|
|
- name: "Configure OP-Connect"
|
|
uses: 1password/load-secrets-action/configure@70062d7a876d3eb6334754fa26efd2fbd90c32f2 # 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@70062d7a876d3eb6334754fa26efd2fbd90c32f2 # 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 }}
|