The publish step no longer speaks OCI. "helm registry login", "helm push" and "helm registry logout" are replaced by a single HTTP POST of the packaged .tgz to <charts-url>/api/charts, with Basic auth per request. Credentials and the target URL come from a new 1Password item op://GitHub-Actions/jlab-registry-access (username, password, charts-url). The old item is left untouched, so a revert of this commit restores the previous pipeline without any further work. No registry address enters the repository. The three properties of the old step survive: - values are read via steps.<id>.outputs.* into the step's own env block and used as quoted shell variables, each guarded with ":?" - credentials stay out of argv: "curl --config -" instead of "--user" - a failed upload fails the job: "--fail-with-body", because curl exits 0 on HTTP 401/403/409 without it and the publish would go green with nothing uploaded The "<chart>-*.tgz" glob is replaced by the file name built from the chart name and the version already read from Chart.yaml; --upload-file takes exactly one file. Two small additions fall out of measuring the new step: the registry host is masked, because a curl connection error prints only "<host>:<port>" and the mask on the full URL does not cover that, and whitespace in the credentials is rejected up front, because an unquoted curl config value ends at the first space. No chart is touched and no chart version is bumped.
150 lines
7.7 KiB
YAML
150 lines
7.7 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>/api/charts,
|
|
# authenticated per request. Three 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 }}
|
|
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}"
|
|
: "${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%%/*}"
|
|
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" "${REGISTRY_CHARTS_URL%/}/api/charts" |
|
|
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 }}
|