feat(wik-webservice): improve support for ListenerSet #49
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Release Charts | |
| on: | |
| push: | |
| branches: | |
| - main | |
| paths: | |
| - 'charts/**' | |
| workflow_dispatch: | |
| inputs: | |
| chart: | |
| description: 'Chart to release (e.g., wik-webservice). Leave empty for auto-detect.' | |
| required: false | |
| type: string | |
| force: | |
| description: 'Force release even if version exists' | |
| required: false | |
| type: boolean | |
| default: false | |
| concurrency: | |
| group: release-${{ github.ref }} | |
| cancel-in-progress: false | |
| env: | |
| GHCR_REGISTRY: ghcr.io | |
| GHCR_REPO: wikodit/charts | |
| jobs: | |
| detect-changes: | |
| runs-on: ubuntu-latest | |
| outputs: | |
| matrix: ${{ steps.set-matrix.outputs.matrix }} | |
| has_changes: ${{ steps.set-matrix.outputs.has_changes }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Set up Helm | |
| uses: azure/setup-helm@v5 | |
| with: | |
| version: v3.19.4 | |
| - name: Set up chart-testing | |
| uses: helm/chart-testing-action@v2.8.0 | |
| - name: Detect charts to release | |
| id: set-matrix | |
| run: | | |
| if [[ -n "${{ inputs.chart }}" ]]; then | |
| # Manual trigger with specific chart | |
| CHARTS='["${{ inputs.chart }}"]' | |
| else | |
| # For push events, compare with the commit before the push | |
| if [[ "${{ github.event_name }}" == "push" ]]; then | |
| BEFORE_SHA="${{ github.event.before }}" | |
| if [[ "$BEFORE_SHA" == "0000000000000000000000000000000000000000" ]]; then | |
| # Initial push, check all charts | |
| changed=$(ls -d charts/*/ 2>/dev/null | xargs -I{} basename {} || echo "") | |
| else | |
| changed=$(git diff --name-only "$BEFORE_SHA" HEAD -- charts/ | grep -oP 'charts/\K[^/]+' | sort -u || echo "") | |
| fi | |
| else | |
| # For other events (workflow_dispatch without chart input) | |
| changed=$(git diff --name-only origin/main HEAD -- charts/ 2>/dev/null | grep -oP 'charts/\K[^/]+' | sort -u || echo "") | |
| fi | |
| if [[ -n "$changed" ]]; then | |
| CHARTS=$(echo "$changed" | jq -R -s -c 'split("\n") | map(select(. != ""))') | |
| else | |
| CHARTS='[]' | |
| fi | |
| fi | |
| echo "Detected charts: $CHARTS" | |
| if [[ "$CHARTS" == "[]" ]]; then | |
| echo "has_changes=false" >> "$GITHUB_OUTPUT" | |
| echo "matrix={\"chart\":[]}" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "has_changes=true" >> "$GITHUB_OUTPUT" | |
| echo "matrix={\"chart\":$CHARTS}" >> "$GITHUB_OUTPUT" | |
| fi | |
| release: | |
| runs-on: ubuntu-latest | |
| needs: detect-changes | |
| if: needs.detect-changes.outputs.has_changes == 'true' | |
| permissions: | |
| contents: write | |
| packages: write | |
| id-token: write # Required for cosign keyless signing | |
| strategy: | |
| fail-fast: false | |
| max-parallel: 1 # Serialize releases to avoid conflicts | |
| matrix: ${{ fromJson(needs.detect-changes.outputs.matrix) }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| token: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Set up Helm | |
| uses: azure/setup-helm@v5 | |
| with: | |
| version: v3.19.4 | |
| - name: Install cosign | |
| uses: sigstore/cosign-installer@v4.1.0 | |
| - name: Install yq | |
| uses: mikefarah/yq@v4 | |
| - name: Determine version bump from conventional commits | |
| id: version-bump | |
| run: | | |
| CHART_PATH="charts/${{ matrix.chart }}" | |
| CHART_NAME=$(yq '.name' "${CHART_PATH}/Chart.yaml") | |
| # Get the last release tag for this chart | |
| LAST_TAG=$(git tag --list "${CHART_NAME}-v*" --sort=-version:refname | head -n1 || echo "") | |
| if [[ -z "$LAST_TAG" ]]; then | |
| # No previous release, get all commits for this chart | |
| echo "No previous release found for ${CHART_NAME}" | |
| COMMITS=$(git log --oneline --no-merges -- "${CHART_PATH}") | |
| CURRENT_VERSION="0.0.0" | |
| else | |
| echo "Last release: ${LAST_TAG}" | |
| COMMITS=$(git log --oneline --no-merges "${LAST_TAG}..HEAD" -- "${CHART_PATH}") | |
| # Extract version from tag (chart-name-vX.Y.Z -> X.Y.Z) | |
| CURRENT_VERSION=$(echo "$LAST_TAG" | sed "s/${CHART_NAME}-v//") | |
| fi | |
| echo "Current version: ${CURRENT_VERSION}" | |
| echo "Commits since last release:" | |
| echo "$COMMITS" | |
| # Determine bump type from conventional commits | |
| BUMP_TYPE="none" | |
| # Check for breaking changes (BREAKING CHANGE in body or ! after type) | |
| if echo "$COMMITS" | grep -qiE '(BREAKING CHANGE|^[a-z]+!:|!)'; then | |
| BUMP_TYPE="major" | |
| # Check for features | |
| elif echo "$COMMITS" | grep -qiE '^[a-f0-9]+ feat'; then | |
| BUMP_TYPE="minor" | |
| # Check for fixes or performance improvements | |
| elif echo "$COMMITS" | grep -qiE '^[a-f0-9]+ (fix|perf)'; then | |
| BUMP_TYPE="patch" | |
| fi | |
| echo "Bump type: ${BUMP_TYPE}" | |
| if [[ "$BUMP_TYPE" == "none" ]]; then | |
| echo "::notice::No releasable commits found (need feat, fix, or perf). Skipping automatic release." | |
| echo "skip=true" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| echo "skip=false" >> "$GITHUB_OUTPUT" | |
| # Calculate new version | |
| IFS='.' read -r MAJOR MINOR PATCH <<< "$CURRENT_VERSION" | |
| case "$BUMP_TYPE" in | |
| major) | |
| NEW_VERSION="$((MAJOR + 1)).0.0" | |
| ;; | |
| minor) | |
| NEW_VERSION="${MAJOR}.$((MINOR + 1)).0" | |
| ;; | |
| patch) | |
| NEW_VERSION="${MAJOR}.${MINOR}.$((PATCH + 1))" | |
| ;; | |
| esac | |
| echo "New version: ${NEW_VERSION}" | |
| echo "bump_type=${BUMP_TYPE}" >> "$GITHUB_OUTPUT" | |
| echo "current_version=${CURRENT_VERSION}" >> "$GITHUB_OUTPUT" | |
| echo "new_version=${NEW_VERSION}" >> "$GITHUB_OUTPUT" | |
| - name: Update Chart.yaml version | |
| if: steps.version-bump.outputs.skip != 'true' | |
| id: update-version | |
| run: | | |
| CHART_PATH="charts/${{ matrix.chart }}" | |
| NEW_VERSION="${{ steps.version-bump.outputs.new_version }}" | |
| # Update version and appVersion in Chart.yaml | |
| yq -i ".version = \"${NEW_VERSION}\"" "${CHART_PATH}/Chart.yaml" | |
| yq -i ".appVersion = \"${NEW_VERSION}\"" "${CHART_PATH}/Chart.yaml" | |
| echo "Updated Chart.yaml to version ${NEW_VERSION}" | |
| cat "${CHART_PATH}/Chart.yaml" | |
| - name: Get chart info | |
| if: steps.version-bump.outputs.skip != 'true' | |
| id: chart-info | |
| run: | | |
| CHART_PATH="charts/${{ matrix.chart }}" | |
| if [[ ! -f "${CHART_PATH}/Chart.yaml" ]]; then | |
| echo "::error::Chart.yaml not found at ${CHART_PATH}" | |
| exit 1 | |
| fi | |
| VERSION=$(yq '.version' "${CHART_PATH}/Chart.yaml") | |
| NAME=$(yq '.name' "${CHART_PATH}/Chart.yaml") | |
| APP_VERSION=$(yq '.appVersion // "unknown"' "${CHART_PATH}/Chart.yaml") | |
| echo "name=${NAME}" >> "$GITHUB_OUTPUT" | |
| echo "version=${VERSION}" >> "$GITHUB_OUTPUT" | |
| echo "app_version=${APP_VERSION}" >> "$GITHUB_OUTPUT" | |
| echo "path=${CHART_PATH}" >> "$GITHUB_OUTPUT" | |
| echo "tag=${NAME}-v${VERSION}" >> "$GITHUB_OUTPUT" | |
| echo "Chart: ${NAME}" | |
| echo "Version: ${VERSION}" | |
| echo "App Version: ${APP_VERSION}" | |
| - name: Check if version already released | |
| if: steps.version-bump.outputs.skip != 'true' | |
| id: check-version | |
| run: | | |
| TAG="${{ steps.chart-info.outputs.tag }}" | |
| if git rev-parse "refs/tags/${TAG}" >/dev/null 2>&1; then | |
| if [[ "${{ inputs.force }}" == "true" ]]; then | |
| echo "WARNING: Tag ${TAG} exists but force=true, continuing..." | |
| echo "exists=true" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "::error::Tag ${TAG} already exists. Bump the version in Chart.yaml or use force=true." | |
| exit 1 | |
| fi | |
| else | |
| echo "exists=false" >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: Update dependencies | |
| if: steps.version-bump.outputs.skip != 'true' | |
| run: | | |
| helm dependency update "${{ steps.chart-info.outputs.path }}" || true | |
| - name: Regenerate README | |
| if: steps.version-bump.outputs.skip != 'true' | |
| run: | | |
| # Install helm-docs | |
| HELM_DOCS_VERSION="1.14.2" | |
| wget -O helm-docs.tar.gz "https://github.com/norwoodj/helm-docs/releases/download/v${HELM_DOCS_VERSION}/helm-docs_${HELM_DOCS_VERSION}_Linux_x86_64.tar.gz" | |
| tar xzf helm-docs.tar.gz | |
| sudo mv helm-docs /usr/local/bin/ | |
| # Regenerate README for this chart | |
| helm-docs --chart-search-root "${{ steps.chart-info.outputs.path }}" --template-files README.md.gotmpl | |
| echo "Regenerated README for ${{ steps.chart-info.outputs.name }}" | |
| - name: Generate CHANGELOG | |
| if: steps.version-bump.outputs.skip != 'true' | |
| id: changelog | |
| run: | | |
| # Install git-chglog | |
| CHGLOG_VERSION="0.15.4" | |
| wget -O git-chglog.tar.gz "https://github.com/git-chglog/git-chglog/releases/download/v${CHGLOG_VERSION}/git-chglog_${CHGLOG_VERSION}_linux_amd64.tar.gz" | |
| tar xzf git-chglog.tar.gz | |
| sudo mv git-chglog /usr/local/bin/ | |
| CHANGELOG_PATH="${{ steps.chart-info.outputs.path }}/CHANGELOG.md" | |
| NEW_TAG="${{ steps.chart-info.outputs.name }}-v${{ steps.chart-info.outputs.version }}" | |
| # Create temporary tag for git-chglog | |
| git tag "$NEW_TAG" | |
| # Generate full changelog for this chart (all versions, only this chart's commits) | |
| git-chglog --config .chglog/config.yml --output "$CHANGELOG_PATH" --tag-filter-pattern "${{ steps.chart-info.outputs.name }}-v.*" --path "${{ steps.chart-info.outputs.path }}" | |
| # Remove temporary tag (GitHub release will create it) | |
| git tag -d "$NEW_TAG" | |
| echo "Generated CHANGELOG for ${{ steps.chart-info.outputs.name }}" | |
| # Extract changelog for release body | |
| CHANGELOG_CONTENT=$(cat "$CHANGELOG_PATH" | head -100) | |
| echo "changelog<<EOF" >> "$GITHUB_OUTPUT" | |
| echo "$CHANGELOG_CONTENT" >> "$GITHUB_OUTPUT" | |
| echo "EOF" >> "$GITHUB_OUTPUT" | |
| - name: Package chart | |
| if: steps.version-bump.outputs.skip != 'true' | |
| run: | | |
| mkdir -p .packaged | |
| helm package "${{ steps.chart-info.outputs.path }}" -d .packaged/ | |
| echo "Packaged: .packaged/${{ steps.chart-info.outputs.name }}-${{ steps.chart-info.outputs.version }}.tgz" | |
| - name: Login to GHCR | |
| if: steps.version-bump.outputs.skip != 'true' | |
| run: | | |
| echo "${{ secrets.GITHUB_TOKEN }}" | helm registry login ${{ env.GHCR_REGISTRY }} \ | |
| -u ${{ github.actor }} --password-stdin | |
| - name: Push to GHCR | |
| if: steps.version-bump.outputs.skip != 'true' | |
| id: push-ghcr | |
| run: | | |
| PACKAGE=".packaged/${{ steps.chart-info.outputs.name }}-${{ steps.chart-info.outputs.version }}.tgz" | |
| helm push "${PACKAGE}" oci://${{ env.GHCR_REGISTRY }}/${{ env.GHCR_REPO }} | |
| # Get the digest | |
| DIGEST=$(crane digest ${{ env.GHCR_REGISTRY }}/${{ env.GHCR_REPO }}/${{ steps.chart-info.outputs.name }}:${{ steps.chart-info.outputs.version }} 2>/dev/null || echo "") | |
| echo "digest=${DIGEST}" >> "$GITHUB_OUTPUT" | |
| echo "Pushed to GHCR" | |
| env: | |
| COSIGN_EXPERIMENTAL: "true" | |
| - name: Sign chart in GHCR (keyless) | |
| if: steps.version-bump.outputs.skip != 'true' | |
| env: | |
| COSIGN_EXPERIMENTAL: "1" | |
| run: | | |
| IMAGE="${{ env.GHCR_REGISTRY }}/${{ env.GHCR_REPO }}/${{ steps.chart-info.outputs.name }}:${{ steps.chart-info.outputs.version }}" | |
| # Authenticate with GHCR for Cosign | |
| echo "${{ secrets.GITHUB_TOKEN }}" | cosign login ${{ env.GHCR_REGISTRY }} \ | |
| -u ${{ github.actor }} --password-stdin | |
| cosign sign --yes "${IMAGE}" | |
| echo "Signed ${IMAGE}" | |
| - name: Login to Harbor | |
| if: steps.version-bump.outputs.skip != 'true' && vars.HARBOR_REGISTRY != '' | |
| run: | | |
| echo "${{ secrets.HARBOR_PASSWORD }}" | helm registry login ${{ vars.HARBOR_REGISTRY }} \ | |
| -u "${{ secrets.HARBOR_USERNAME }}" --password-stdin | |
| - name: Push to Harbor | |
| if: steps.version-bump.outputs.skip != 'true' && vars.HARBOR_REGISTRY != '' | |
| run: | | |
| PACKAGE=".packaged/${{ steps.chart-info.outputs.name }}-${{ steps.chart-info.outputs.version }}.tgz" | |
| HARBOR_REPO="${{ vars.HARBOR_REGISTRY }}/${{ vars.HARBOR_PROJECT || 'library' }}" | |
| helm push "${PACKAGE}" oci://${HARBOR_REPO} | |
| echo "Pushed to Harbor" | |
| - name: Sign chart in Harbor (keyless) | |
| if: steps.version-bump.outputs.skip != 'true' && vars.HARBOR_REGISTRY != '' | |
| continue-on-error: true | |
| env: | |
| COSIGN_EXPERIMENTAL: "1" | |
| run: | | |
| IMAGE="${{ vars.HARBOR_REGISTRY }}/${{ vars.HARBOR_PROJECT || 'library' }}/${{ steps.chart-info.outputs.name }}:${{ steps.chart-info.outputs.version }}" | |
| # Authenticate with Harbor for Cosign | |
| echo "${{ secrets.HARBOR_PASSWORD }}" | cosign login ${{ vars.HARBOR_REGISTRY }} \ | |
| -u "${{ secrets.HARBOR_USERNAME }}" --password-stdin | |
| cosign sign --yes "${IMAGE}" | |
| echo "Signed ${IMAGE}" | |
| - name: Generate SBOM | |
| if: steps.version-bump.outputs.skip != 'true' | |
| run: | | |
| # Generate SBOM for the chart | |
| PACKAGE=".packaged/${{ steps.chart-info.outputs.name }}-${{ steps.chart-info.outputs.version }}.tgz" | |
| # Create a simple SBOM (for more comprehensive SBOM, use syft) | |
| cat > ".packaged/${{ steps.chart-info.outputs.name }}-${{ steps.chart-info.outputs.version }}.sbom.json" <<EOF | |
| { | |
| "bomFormat": "CycloneDX", | |
| "specVersion": "1.4", | |
| "version": 1, | |
| "metadata": { | |
| "timestamp": "$(date -u +%Y-%m-%dT%H:%M:%SZ)", | |
| "component": { | |
| "type": "application", | |
| "name": "${{ steps.chart-info.outputs.name }}", | |
| "version": "${{ steps.chart-info.outputs.version }}", | |
| "purl": "pkg:helm/${{ env.GHCR_REPO }}/${{ steps.chart-info.outputs.name }}@${{ steps.chart-info.outputs.version }}" | |
| } | |
| }, | |
| "components": [] | |
| } | |
| EOF | |
| echo "Generated SBOM" | |
| - name: Create GitHub Release | |
| if: steps.version-bump.outputs.skip != 'true' | |
| uses: softprops/action-gh-release@v2 | |
| with: | |
| tag_name: ${{ steps.chart-info.outputs.tag }} | |
| name: "${{ steps.chart-info.outputs.name }} v${{ steps.chart-info.outputs.version }}" | |
| body: | | |
| ## Helm Chart Release | |
| **Chart**: `${{ steps.chart-info.outputs.name }}` | |
| **Version**: `${{ steps.chart-info.outputs.version }}` | |
| **App Version**: `${{ steps.chart-info.outputs.app_version }}` | |
| ### Installation | |
| ```bash | |
| # From GHCR (OCI) | |
| helm install ${{ steps.chart-info.outputs.name }} oci://${{ env.GHCR_REGISTRY }}/${{ env.GHCR_REPO }}/${{ steps.chart-info.outputs.name }} --version ${{ steps.chart-info.outputs.version }} | |
| ``` | |
| ### Signature Verification | |
| ```bash | |
| cosign verify ${{ env.GHCR_REGISTRY }}/${{ env.GHCR_REPO }}/${{ steps.chart-info.outputs.name }}:${{ steps.chart-info.outputs.version }} \ | |
| --certificate-identity-regexp='https://github.com/${{ github.repository }}' \ | |
| --certificate-oidc-issuer='https://token.actions.githubusercontent.com' | |
| ``` | |
| --- | |
| ${{ steps.changelog.outputs.changelog }} | |
| files: | | |
| .packaged/${{ steps.chart-info.outputs.name }}-${{ steps.chart-info.outputs.version }}.tgz | |
| .packaged/${{ steps.chart-info.outputs.name }}-${{ steps.chart-info.outputs.version }}.sbom.json | |
| generate_release_notes: false | |
| make_latest: true | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Commit CHANGELOG | |
| if: success() && steps.version-bump.outputs.skip != 'true' | |
| run: | | |
| # Configure git | |
| git config --local user.email "github-actions[bot]@users.noreply.github.com" | |
| git config --local user.name "github-actions[bot]" | |
| # Show what files were modified | |
| echo "Modified files:" | |
| git status --short | |
| # Add all changes from this chart (including Chart.lock from dependency update) | |
| git add "${{ steps.chart-info.outputs.path }}/" | |
| # Check if there are changes to commit | |
| if git diff --staged --quiet; then | |
| echo "No changes to commit" | |
| else | |
| git commit -m "chore(${{ steps.chart-info.outputs.name }}): release v${{ steps.chart-info.outputs.version }} [skip ci]" \ | |
| -m "Bump version from ${{ steps.version-bump.outputs.current_version }} to ${{ steps.version-bump.outputs.new_version }}" \ | |
| -m "Update CHANGELOG.md" | |
| # Fetch and rebase (discard any untracked/modified files outside chart) | |
| git checkout -- . || true | |
| git clean -fd || true | |
| git fetch origin main | |
| git rebase origin/main | |
| # Push changes | |
| git push https://github-actions:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.git main | |
| echo "Version bump and CHANGELOG committed and pushed" | |
| fi | |
| - name: Summary | |
| if: steps.version-bump.outputs.skip != 'true' | |
| run: | | |
| cat >> "$GITHUB_STEP_SUMMARY" <<EOF | |
| ## Release Summary | |
| | Property | Value | | |
| |----------|-------| | |
| | Chart | \`${{ steps.chart-info.outputs.name }}\` | | |
| | Previous Version | \`${{ steps.version-bump.outputs.current_version }}\` | | |
| | New Version | \`${{ steps.chart-info.outputs.version }}\` | | |
| | Bump Type | \`${{ steps.version-bump.outputs.bump_type }}\` | | |
| | GHCR | \`${{ env.GHCR_REGISTRY }}/${{ env.GHCR_REPO }}/${{ steps.chart-info.outputs.name }}:${{ steps.chart-info.outputs.version }}\` | | |
| | GitHub Release | [${{ steps.chart-info.outputs.tag }}](${{ github.server_url }}/${{ github.repository }}/releases/tag/${{ steps.chart-info.outputs.tag }}) | | |
| | Signed | Keyless (Sigstore) | | |
| EOF |