Skip to content
Merged
Show file tree
Hide file tree
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
9 changes: 8 additions & 1 deletion .github/workflows/artifacthub-verify.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
ref: ${{ github.event.inputs.tag || github.ref }}
- name: Install ah CLI
run: |
set -euo pipefail
Expand All @@ -46,12 +48,17 @@ jobs:
if: github.event_name != 'pull_request'
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
ref: ${{ github.event.inputs.tag || github.ref }}
- uses: azure/setup-helm@dda3372f752e03dde6b3237bc9431cdc2f7a02a2 # v5
with:
version: latest
- name: Run artifacthub smoke
env:
AH_API_KEY_ID: ${{ secrets.AH_API_KEY_ID }}
AH_API_KEY_SECRET: ${{ secrets.AH_API_KEY_SECRET }}
TAG: ${{ github.event.inputs.tag || github.ref_name }}
EXPECTED_ARTIFACTHUB_REPOSITORY_URL: oci://ghcr.io/keiailab/charts/mongodb-operator
EXPECTED_CHART_VERSION: ${{ github.event.inputs.tag || github.ref_name }}
ARTIFACTHUB_SMOKE_ATTEMPTS: "30"
ARTIFACTHUB_SMOKE_SLEEP_SECONDS: "30"
run: bash hack/artifacthub_smoke.sh
16 changes: 15 additions & 1 deletion .github/workflows/helm-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ permissions:
pages: read

concurrency:
group: helm-publish-${{ github.event.release.tag_name || github.event.inputs.tag || github.ref }}
group: helm-publish-${{ github.event.release.tag_name || github.event.inputs.tag || github.ref_name }}
cancel-in-progress: false

jobs:
Expand Down Expand Up @@ -79,9 +79,23 @@ jobs:
run: |
set -euo pipefail
VERSION=$(grep '^version:' charts/mongodb-operator/Chart.yaml | awk '{print $2}' | tr -d '"')
APP_VERSION=$(grep '^appVersion:' charts/mongodb-operator/Chart.yaml | awk '{print $2}' | tr -d '"')
CHART_REF="oci://ghcr.io/keiailab/charts/mongodb-operator"
echo "$GHCR_TOKEN" | helm registry login ghcr.io -u "$GHCR_USER" --password-stdin
if helm show chart "$CHART_REF" --version "$VERSION" > /tmp/existing-chart.yaml 2>/tmp/existing-chart.err; then
EXISTING_VERSION=$(awk -F': *' '/^version:/ {gsub(/"/, "", $2); print $2; exit}' /tmp/existing-chart.yaml)
EXISTING_APP_VERSION=$(awk -F': *' '/^appVersion:/ {gsub(/"/, "", $2); print $2; exit}' /tmp/existing-chart.yaml)
if [ "$EXISTING_VERSION" = "$VERSION" ] && [ "$EXISTING_APP_VERSION" = "$APP_VERSION" ]; then
echo "✓ OCI chart ${VERSION} already exists with appVersion ${APP_VERSION}; skipping push"
helm registry logout ghcr.io || true
exit 0
fi
echo "::error::OCI chart ${VERSION} exists with unexpected metadata: version=${EXISTING_VERSION}, appVersion=${EXISTING_APP_VERSION}"
exit 1
fi
mkdir -p out
helm package charts/mongodb-operator -d out/
helm push "out/mongodb-operator-${VERSION}.tgz" oci://ghcr.io/keiailab/charts
helm show chart "$CHART_REF" --version "$VERSION" >/dev/null
helm registry logout ghcr.io || true
echo "✓ pushed chart ${VERSION} to oci://ghcr.io/keiailab/charts/mongodb-operator"
43 changes: 37 additions & 6 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@ jobs:
runs-on: ubuntu-latest
outputs:
version: ${{ steps.derive.outputs.version }}
app_version: ${{ steps.derive.outputs.app_version }}
tag: ${{ steps.derive.outputs.tag }}
image_release: ${{ steps.derive.outputs.image_release }}
steps:
- uses: actions/checkout@v6
with:
Expand All @@ -45,16 +47,41 @@ jobs:
if [ "$CHART_VER" != "$VERSION" ]; then
echo "::error::Chart.yaml version=$CHART_VER does not match tag=$VERSION"; exit 1
fi
if [ "$APP_VER" != "$VERSION" ]; then
echo "::error::Chart.yaml appVersion=$APP_VER does not match tag=$VERSION"; exit 1
if [ "$APP_VER" = "$VERSION" ]; then
IMAGE_RELEASE=true
echo "✓ image release: tag=$TAG / version=$VERSION / appVersion=$APP_VER all aligned"
else
IMAGE_RELEASE=false
echo "✓ chart-only release: tag=$TAG / chart=$VERSION / appVersion=$APP_VER"
echo " image/GitHub Release jobs will be skipped; Helm chart publish handles the chart artifact."
fi
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
echo "✓ tag=$TAG / version=$VERSION / appVersion=$APP_VER all aligned"
{
echo "tag=$TAG"
echo "version=$VERSION"
echo "app_version=$APP_VER"
echo "image_release=$IMAGE_RELEASE"
} >> "$GITHUB_OUTPUT"

chart-only-summary:
name: Chart-only release summary
needs: preflight
if: needs.preflight.outputs.image_release == 'false'
runs-on: ubuntu-latest
steps:
- run: |
{
echo "### Chart-only release"
echo ""
echo "- tag: ${{ needs.preflight.outputs.tag }}"
echo "- chart version: ${{ needs.preflight.outputs.version }}"
echo "- appVersion/image: ${{ needs.preflight.outputs.app_version }}"
echo "- result: image, SBOM, and GitHub Release jobs intentionally skipped"
} >> "$GITHUB_STEP_SUMMARY"

image:
name: Build & push multi-arch image
name: Build & push linux/amd64 image
needs: preflight
if: needs.preflight.outputs.image_release == 'true'
runs-on: ubuntu-latest
outputs:
digest: ${{ steps.push.outputs.digest }}
Expand Down Expand Up @@ -93,6 +120,7 @@ jobs:
sbom:
name: SBOM (syft)
needs: [preflight, image]
if: needs.preflight.outputs.image_release == 'true'
runs-on: ubuntu-latest
steps:
- uses: anchore/sbom-action@v0
Expand All @@ -108,6 +136,7 @@ jobs:
notes:
name: Release notes (git-cliff)
needs: preflight
if: needs.preflight.outputs.image_release == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
Expand All @@ -128,6 +157,7 @@ jobs:
chart-tgz:
name: Package helm chart
needs: preflight
if: needs.preflight.outputs.image_release == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
Expand All @@ -146,6 +176,7 @@ jobs:
github-release:
name: Create GitHub Release
needs: [preflight, image, sbom, notes, chart-tgz]
if: needs.preflight.outputs.image_release == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@v8
Expand Down
162 changes: 121 additions & 41 deletions hack/artifacthub_smoke.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,14 @@ artifacthub_api_url="${ARTIFACTHUB_API_URL:-https://artifacthub.io/api/v1}"
artifacthub_org="${ARTIFACTHUB_ORG:-keiailab}"
artifacthub_package_name="${ARTIFACTHUB_PACKAGE_NAME:-mongodb-operator}"
artifacthub_repository_name="${ARTIFACTHUB_REPOSITORY_NAME:-keiailab-mongodb-operator}"
artifacthub_repository_url="${EXPECTED_ARTIFACTHUB_REPOSITORY_URL:-${ARTIFACTHUB_REPOSITORY_URL:-oci://ghcr.io/keiailab/charts/mongodb-operator}}"
helm_repo_url="${HELM_REPO_URL:-https://keiailab.github.io/mongodb-operator}"

curl_bin="${CURL_BIN:-curl}"
helm_bin="${HELM_BIN:-helm}"
jq_bin="${JQ_BIN:-jq}"
smoke_attempts="${ARTIFACTHUB_SMOKE_ATTEMPTS:-1}"
smoke_sleep_seconds="${ARTIFACTHUB_SMOKE_SLEEP_SECONDS:-30}"

tmpdir="$(mktemp -d "${TMPDIR:-/tmp}/mongodb-operator-artifacthub.XXXXXX")"
trap 'rm -rf "$tmpdir"' EXIT
Expand Down Expand Up @@ -44,41 +47,70 @@ fetch_json() {
"$curl_bin" -fsSL "$url" -o "$out"
}

echo "=== Helm repository reachability ==="
"$curl_bin" -fsSL "${helm_repo_url%/}/index.yaml" -o "$tmpdir/index.yaml"
"$curl_bin" -fsSL "${helm_repo_url%/}/artifacthub-repo.yml" -o "$tmpdir/artifacthub-repo.yml"
grep -q '^repositoryID:' "$tmpdir/artifacthub-repo.yml"
chart_yaml="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)/charts/${artifacthub_package_name}/Chart.yaml"
expected_chart_version="${EXPECTED_CHART_VERSION:-${TAG:-}}"
expected_chart_version="${expected_chart_version##refs/tags/}"
expected_chart_version="${expected_chart_version#v}"
if [[ -z "$expected_chart_version" && -f "$chart_yaml" ]]; then
expected_chart_version="$(awk -F': *' '/^version:/ {gsub(/"/, "", $2); print $2; exit}' "$chart_yaml")"
fi

echo "Helm repository OK: ${helm_repo_url%/}"
expected_app_version="${EXPECTED_APP_VERSION:-${APP_VERSION:-}}"
if [[ -z "$expected_app_version" && -f "$chart_yaml" ]]; then
expected_app_version="$(awk -F': *' '/^appVersion:/ {gsub(/"/, "", $2); print $2; exit}' "$chart_yaml")"
fi

if command -v "$helm_bin" >/dev/null 2>&1; then
"$helm_bin" repo add "$artifacthub_repository_name" "$helm_repo_url" >/dev/null 2>&1 || true
"$helm_bin" repo update "$artifacthub_repository_name" >/dev/null
"$helm_bin" search repo "${artifacthub_repository_name}/${artifacthub_package_name}" --versions --devel \
| grep -q "${artifacthub_repository_name}/${artifacthub_package_name}"
echo "Helm index package OK: ${artifacthub_repository_name}/${artifacthub_package_name}"
if [[ -z "$expected_chart_version" ]]; then
echo "ERROR: expected chart version is unknown. Set EXPECTED_CHART_VERSION or TAG." >&2
exit 1
fi
if [[ -z "$expected_app_version" ]]; then
echo "ERROR: expected appVersion is unknown. Set EXPECTED_APP_VERSION or keep Chart.yaml available." >&2
exit 1
fi

echo "=== Expected release contract ==="
echo "Chart version: ${expected_chart_version}"
echo "App version: ${expected_app_version}"
echo "Artifact Hub repository URL: ${artifacthub_repository_url%/}"
echo "Smoke attempts: ${smoke_attempts} (sleep ${smoke_sleep_seconds}s)"

echo "=== Legacy Helm repository reachability (warning-only) ==="
if "$curl_bin" -fsSL "${helm_repo_url%/}/index.yaml" -o "$tmpdir/index.yaml" 2>/dev/null; then
echo "Legacy Helm repository reachable: ${helm_repo_url%/}"
if command -v "$helm_bin" >/dev/null 2>&1; then
"$helm_bin" repo add "$artifacthub_repository_name" "$helm_repo_url" >/dev/null 2>&1 || true
"$helm_bin" repo update "$artifacthub_repository_name" >/dev/null
if "$helm_bin" search repo "${artifacthub_repository_name}/${artifacthub_package_name}" --versions --devel \
| grep -q "${artifacthub_repository_name}/${artifacthub_package_name}"; then
echo "Legacy Helm index package visible: ${artifacthub_repository_name}/${artifacthub_package_name}"
else
echo "::warning::legacy Helm index package not visible; Artifact Hub tracks OCI, so this is not a gate."
fi
fi
else
echo "WARN: helm not found; local Helm index search skipped" >&2
echo "::warning::legacy Helm repository is unreachable; Artifact Hub tracks OCI, so this is not a gate."
fi

require_tool "$curl_bin"
require_tool "$jq_bin"

echo "=== Artifact Hub repository registration ==="
org_query="$(urlencode "$artifacthub_org")"
fetch_json "${artifacthub_api_url%/}/repositories/search?org=${org_query}&kind=0&limit=60" "$tmpdir/repositories.json"

normalized_helm_url="$(normalize_url "$helm_repo_url")"
normalized_artifacthub_repository_url="$(normalize_url "$artifacthub_repository_url")"
repo_filter='
.[]?
| select((.url // "" | sub("/$"; "")) == $url or .name == $name)
| select(.name == $name and ((.url // "" | sub("/$"; "")) == $url))
'
repo_json="$("$jq_bin" -e -c --arg url "$normalized_helm_url" --arg name "$artifacthub_repository_name" "$repo_filter" "$tmpdir/repositories.json" 2>/dev/null || true)"
repo_json="$("$jq_bin" -e -c --arg url "$normalized_artifacthub_repository_url" --arg name "$artifacthub_repository_name" "$repo_filter" "$tmpdir/repositories.json" 2>/dev/null || true)"

if [[ -z "$repo_json" ]]; then
echo "ERROR: Artifact Hub repository is not registered." >&2
echo " org: ${artifacthub_org}" >&2
echo " expected name: ${artifacthub_repository_name}" >&2
echo " expected url: ${normalized_helm_url}" >&2
echo " expected url: ${normalized_artifacthub_repository_url}" >&2
echo " fix: make artifacthub-register ARTIFACTHUB_API_KEY_ID=... ARTIFACTHUB_API_KEY_SECRET=..." >&2
exit 2
fi
Expand All @@ -93,40 +125,88 @@ if [[ -n "$tracking_errors" ]]; then
exit 3
fi

echo "=== GHCR OCI chart availability ==="
if command -v "$helm_bin" >/dev/null 2>&1; then
oci_chart_ready=false
for attempt in $(seq 1 "$smoke_attempts"); do
if "$helm_bin" show chart "$normalized_artifacthub_repository_url" --version "$expected_chart_version" >"$tmpdir/oci-chart.yaml" 2>"$tmpdir/oci-chart.err"; then
oci_chart_ready=true
break
fi
if [[ "$attempt" -lt "$smoke_attempts" ]]; then
echo "GHCR OCI chart not visible yet (${attempt}/${smoke_attempts}); waiting ${smoke_sleep_seconds}s..."
sleep "$smoke_sleep_seconds"
fi
done
if [[ "$oci_chart_ready" != "true" ]]; then
cat "$tmpdir/oci-chart.err" >&2 || true
echo "ERROR: GHCR OCI chart is not published yet." >&2
echo " chart: ${normalized_artifacthub_repository_url}" >&2
echo " version: ${expected_chart_version}" >&2
exit 4
fi
oci_chart_version="$(awk -F': *' '/^version:/ {gsub(/"/, "", $2); print $2; exit}' "$tmpdir/oci-chart.yaml")"
oci_app_version="$(awk -F': *' '/^appVersion:/ {gsub(/"/, "", $2); print $2; exit}' "$tmpdir/oci-chart.yaml")"
if [[ "$oci_chart_version" != "$expected_chart_version" || "$oci_app_version" != "$expected_app_version" ]]; then
echo "ERROR: GHCR OCI chart metadata mismatch." >&2
echo " expected chart/app: ${expected_chart_version}/${expected_app_version}" >&2
echo " actual chart/app: ${oci_chart_version}/${oci_app_version}" >&2
exit 5
fi
echo "GHCR OCI chart OK: ${normalized_artifacthub_repository_url}:${expected_chart_version}"
else
echo "ERROR: helm not found; cannot verify GHCR OCI chart." >&2
exit 5
fi

echo "=== Artifact Hub package registration ==="
package_url="${artifacthub_api_url%/}/packages/helm/${artifacthub_repository_name}/${artifacthub_package_name}"
if ! "$curl_bin" -fsSL "$package_url" -o "$tmpdir/package.json"; then
echo "ERROR: Artifact Hub repository exists but package is not indexed yet." >&2
echo " package API: $package_url" >&2
package_version_url="${package_url}/${expected_chart_version}"
artifacthub_package_ready=false
for attempt in $(seq 1 "$smoke_attempts"); do
if "$curl_bin" -fsSL "$package_version_url" -o "$tmpdir/package.json" 2>"$tmpdir/package.err"; then
artifacthub_package_ready=true
break
fi
if [[ "$attempt" -lt "$smoke_attempts" ]]; then
echo "Artifact Hub target version not indexed yet (${attempt}/${smoke_attempts}); waiting ${smoke_sleep_seconds}s..."
sleep "$smoke_sleep_seconds"
fi
done
if [[ "$artifacthub_package_ready" != "true" ]]; then
cat "$tmpdir/package.err" >&2 || true
echo "ERROR: Artifact Hub repository exists but target chart version is not indexed yet." >&2
echo " package API: $package_version_url" >&2
echo " retry after Artifact Hub tracker runs, or push a new chart version to force reprocessing." >&2
exit 4
exit 6
fi

"$jq_bin" -e --arg name "$artifacthub_package_name" '.name == $name' "$tmpdir/package.json" >/dev/null
echo "Artifact Hub package OK: https://artifacthub.io/packages/helm/${artifacthub_repository_name}/${artifacthub_package_name}"

echo "=== Provenance (.prov) 도달성 ==="
# VERSION: Chart.yaml에서 추출 (TAG 환경변수가 없을 때 fallback)
VERSION="${TAG:-}"
if [[ -z "$VERSION" ]]; then
chart_yaml="$(dirname "$0")/../charts/${artifacthub_package_name}/Chart.yaml"
if [[ -f "$chart_yaml" ]]; then
VERSION="$(grep '^version:' "$chart_yaml" | awk '{print $2}' | tr -d '"')"
fi
fi
"$jq_bin" -e \
--arg name "$artifacthub_package_name" \
--arg version "$expected_chart_version" \
--arg app_version "$expected_app_version" \
--arg repository_url "$normalized_artifacthub_repository_url" \
'(.name // $name) == $name
and .version == $version
and .app_version == $app_version
and ((.repository.url // "") | sub("/$"; "")) == $repository_url
and .signed == true' \
"$tmpdir/package.json" >/dev/null || {
echo "ERROR: Artifact Hub package metadata mismatch." >&2
"$jq_bin" '{name, version, app_version, signed, repository: .repository.url}' "$tmpdir/package.json" >&2
exit 7
}
echo "Artifact Hub package OK: https://artifacthub.io/packages/helm/${artifacthub_repository_name}/${artifacthub_package_name}?modal=version-${expected_chart_version}"

verify_provenance() {
local prov="${helm_repo_url%/}/${artifacthub_package_name}-${VERSION}.tgz.prov"
echo "=== Legacy HTTP provenance (.prov) reachability (warning-only) ==="
verify_legacy_provenance() {
local prov="${helm_repo_url%/}/${artifacthub_package_name}-${expected_chart_version}.tgz.prov"
echo "→ provenance 확인: ${prov}"
if "$curl_bin" -fsSL -o "$tmpdir/chart.tgz.prov" "${prov}" 2>/dev/null; then
echo "✓ .prov 도달 가능 (Signed badge 전제 충족)"
echo "✓ legacy HTTP .prov 도달 가능"
else
echo "::warning::.prov 부재 — Signed badge 미달성(로컬 helm-publish.sh --sign 필요). warn-only, 통과."
echo "::warning::legacy HTTP .prov 부재. Artifact Hub OCI signed 상태는 API(.signed=true)로 검증 완료."
fi
}

if [[ -n "$VERSION" ]]; then
verify_provenance
else
echo "WARN: VERSION 미확인 — .prov 검증 건너뜀" >&2
fi
verify_legacy_provenance
2 changes: 1 addition & 1 deletion internal/resources/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ func buildTLSPEMMount() corev1.VolumeMount {
// cluster-internal CA chain + preferTLS 환경에서 hostname 검증은 의미 적음 (CA 로 ID
// 검증 충분), short/long hostname mix 흡수 의무.
//
//nolint:unused // PVC template builder kept for future StatefulSet integration
//lint:ignore U1000 StatefulSet PVC template 통합 예정 helper 보존
func buildDataVolumeClaimTemplate(storage mongodbv1alpha1.StorageSpec) corev1.PersistentVolumeClaim {
accessModes := storage.AccessModes
if len(accessModes) == 0 {
Expand Down
Loading