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
161 changes: 77 additions & 84 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ env:
GO_VERSION: '1.26'

jobs:
# ── Quality gates (run on every push and PR) ───────────────────────────────

lint:
name: Lint
runs-on: ubuntu-latest
Expand Down Expand Up @@ -77,123 +79,114 @@ jobs:
go install golang.org/x/vuln/cmd/govulncheck@latest
govulncheck ./...

build:
name: Build (${{ matrix.os }}_${{ matrix.arch }})
# ── Release (v* tags only) ─────────────────────────────────────────────────
#
# Responsibilities are split between GoReleaser and a post-release step:
#
# GoReleaser:
# - Cross-compiles all 5 platform binaries (linux/amd64, linux/arm64,
# darwin/amd64, darwin/arm64, windows/amd64)
# - Packages each binary into a helm-oci-<os>-<arch>.tgz archive whose
# directory layout (helm-oci/bin/, helm-oci/plugin.yaml, …) is exactly
# what install-binary.sh expects
# - Generates a sha256 checksum file
# - GPG-signs the checksum file (--detach-sign --armor → .asc)
# - Creates the GitHub Release and uploads all artifacts
#
# Post-GoReleaser step:
# - Uses `helm plugin package` to produce a Helm provenance file (.prov)
# so users can run `helm verify` against the plugin package
# - Exports the public signing key as helm-oci-signing-key.asc
# - Uploads both files to the release created by GoReleaser

release:
name: Release
runs-on: ubuntu-latest
needs: [lint, security-scan, test]
strategy:
matrix:
include:
- os: linux
arch: amd64
- os: linux
arch: arm64
- os: darwin
arch: amd64
- os: darwin
arch: arm64
- os: windows
arch: amd64
needs: [lint, test, security-scan]
if: startsWith(github.ref, 'refs/tags/v')
permissions:
contents: write # create GitHub releases and upload assets

steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0 # GoReleaser needs full history for changelog

- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: ${{ env.GO_VERSION }}

- name: Build binary
# Import the GPG key before GoReleaser runs so the `signs:` block can
# invoke gpg with the key already present in the agent's keyring.
- name: Import GPG key
env:
GOOS: ${{ matrix.os }}
GOARCH: ${{ matrix.arch }}
CGO_ENABLED: '0'
run: |
EXT=""
if [ "$GOOS" = "windows" ]; then EXT=".exe"; fi
mkdir -p dist
go build -trimpath -ldflags="-X main.version=$(sed -n 's/version:.*"\(.*\)"/\1/p' plugin.yaml)" \
-o "dist/helm-oci${EXT}" .

- name: Package archive
env:
MATRIX_OS: ${{ matrix.os }}
MATRIX_ARCH: ${{ matrix.arch }}
GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
run: |
mkdir -p release/helm-oci/bin
cp plugin.yaml install-binary.sh release/helm-oci/
EXT=""
if [ "$MATRIX_OS" = "windows" ]; then EXT=".exe"; fi
cp "dist/helm-oci${EXT}" release/helm-oci/bin/
tar -C release -zcvf "helm-oci-${MATRIX_OS}-${MATRIX_ARCH}.tgz" helm-oci/

- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: helm-oci-${{ matrix.os }}-${{ matrix.arch }}
path: helm-oci-${{ matrix.os }}-${{ matrix.arch }}.tgz

release:
name: Release
runs-on: ubuntu-latest
needs: [build]
if: startsWith(github.ref, 'refs/tags/v')
permissions:
contents: write
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Download all build artifacts
uses: actions/download-artifact@v4
echo "$GPG_PRIVATE_KEY" | gpg --batch --import
# Export the secret key to a temporary keyring so helm can use it
# in the plugin-package step below (helm requires a legacy keyring).
gpg --batch --pinentry-mode loopback \
--passphrase "$GPG_PASSPHRASE" \
--export-secret-keys "${{ secrets.GPG_KEY_ID }}" \
> /tmp/secring.gpg

- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v6
with:
path: dist
pattern: helm-oci-*
merge-multiple: true
distribution: goreleaser
version: "~> v2"
args: release --clean
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
GPG_KEY_ID: ${{ secrets.GPG_KEY_ID }}

# Produce the Helm plugin provenance file so users can run:
# helm verify helm-oci-<version>.tgz
# This step uses the same GPG key that GoReleaser used for the checksum
# signature above, so a single key covers all release artifacts.
- name: Install Helm
uses: azure/setup-helm@v4
with:
version: 'latest'

- name: Package and sign plugin
- name: Build Helm plugin provenance (.prov)
env:
GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
GPG_KEY_ID: ${{ secrets.GPG_KEY_ID }}
TAG: ${{ github.ref_name }}
run: |
echo "$GPG_PRIVATE_KEY" | gpg --batch --import
gpg --batch --pinentry-mode loopback --passphrase "$GPG_PASSPHRASE" \
--export-secret-keys "$GPG_KEY_ID" > /tmp/secring.gpg

mkdir -p staging/oci
cp plugin.yaml install-binary.sh staging/oci/
# Assemble a minimal plugin source directory that `helm plugin package`
# understands (it only needs plugin.yaml and install-binary.sh).
mkdir -p staging/helm-oci
cp plugin.yaml install-binary.sh staging/helm-oci/

echo "$GPG_PASSPHRASE" | helm plugin package \
staging/oci/ \
staging/helm-oci/ \
--destination dist/ \
--key "$GPG_KEY_ID" \
--keyring /tmp/secring.gpg \
--passphrase-file -

gpg --batch --yes --armor --export "$GPG_KEY_ID" > dist/helm-oci-signing-key.asc
# Export the public key so release consumers can import it easily.
gpg --batch --yes --armor \
--export "$GPG_KEY_ID" \
> dist/helm-oci-signing-key.asc

# Scrub the secret keyring from disk.
rm -f /tmp/secring.gpg

- name: Get version
id: version
run: echo "VERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT

- name: Create release
# Attach the provenance file and public key to the GitHub Release that
# GoReleaser already created in the previous step.
- name: Upload Helm provenance to release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAG: ${{ steps.version.outputs.VERSION }}
TAG: ${{ github.ref_name }}
run: |
TITLE=$(git tag -l --format='%(contents:subject)' "$TAG")
gh release create "$TAG" \
--title "${TITLE:-Release $TAG}" \
--generate-notes \
dist/helm-oci-*.tgz \
dist/oci-*.tgz \
dist/oci-*.tgz.prov \
dist/helm-oci-signing-key.asc
gh release upload "$TAG" \
dist/helm-oci-*.tgz.prov \
dist/helm-oci-signing-key.asc \
--clobber
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
bin/
build/
release/
dist
160 changes: 160 additions & 0 deletions .goreleaser.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
version: 2

# ── Project ───────────────────────────────────────────────────────────────────
project_name: helm-oci

before:
hooks:
- go mod tidy
- go mod verify

# ── Build ─────────────────────────────────────────────────────────────────────
builds:
- id: helm-oci
main: .
binary: helm-oci
env:
- CGO_ENABLED=0
goos:
- linux
- darwin
- windows
goarch:
- amd64
- arm64
# Exclude unsupported combinations.
ignore:
- goos: windows
goarch: arm64
ldflags:
- -s -w
- -X main.version={{.Version}}
flags:
- -trimpath
mod_timestamp: "{{ .CommitTimestamp }}"

# ── Archives ──────────────────────────────────────────────────────────────────
# Each archive must unpack as helm-oci/<files> so that install-binary.sh can do:
# tar xzf helm-oci-linux-amd64.tgz
# and find helm-oci/bin/helm-oci, helm-oci/plugin.yaml, helm-oci/install-binary.sh.
# wrap_in_directory names the top-level directory after the archive name_template;
# since we want "helm-oci" (not the versioned archive name) we set it explicitly.
archives:
- id: helm-oci
ids:
- helm-oci
formats: ["tgz"]
name_template: "helm-oci-{{ .Os }}-{{ .Arch }}"
# Wraps all archive contents inside a "helm-oci/" directory, which is what
# install-binary.sh expects when it untars the downloaded archive.
wrap_in_directory: "helm-oci"
# Windows gets a zip so PowerShell users can expand it natively; the inner
# directory layout remains the same.
format_overrides:
- goos: windows
formats: ["zip"]
# Include the plugin manifest and install hook alongside the binary so the
# archive is a self-contained Helm plugin directory.
files:
- plugin.yaml
- install-binary.sh
- README.md
- LICENSE.md

# ── Checksums ─────────────────────────────────────────────────────────────────
checksum:
name_template: "{{ .ProjectName }}_{{ .Version }}_checksums.txt"
algorithm: sha256

# ── Signing ───────────────────────────────────────────────────────────────────
# GoReleaser signs the checksum file (and therefore transitively covers all
# platform archives) using the GPG key supplied via CI secrets.
# The helm plugin provenance (.prov) file is produced by a separate CI step
# using `helm plugin package --key` because that format is specific to Helm's
# verify workflow and cannot be generated here.
signs:
- id: gpg-sign
# Sign only the checksum file; individual archives are covered by it.
artifacts: checksum
args:
- "--batch"
- "--yes"
- "--pinentry-mode"
- "loopback"
- "--passphrase"
- "{{ .Env.GPG_PASSPHRASE }}"
- "--local-user"
- "{{ .Env.GPG_KEY_ID }}"
- "--output"
- "${signature}"
- "--detach-sign"
- "--armor"
- "${artifact}"

# ── GitHub Release ────────────────────────────────────────────────────────────
release:
github:
owner: esnet
name: helm-oci
draft: false
prerelease: auto
name_template: "{{ .ProjectName }} {{ .Tag }}"
header: |
## helm-oci {{ .Tag }}

Helm plugin that lets you bookmark OCI chart URLs and use them by name.

### Install / upgrade

```bash
helm plugin install https://github.com/esnet/helm-oci/releases/download/{{ .Tag }}/helm-oci-linux-amd64.tgz
```

Or let Helm fetch the latest release automatically:

```bash
helm plugin install https://github.com/esnet/helm-oci
```
footer: |
**Full Changelog**: https://github.com/esnet/helm-oci/compare/{{ .PreviousTag }}...{{ .Tag }}

### Verify

Import the signing key and verify the checksum file:

```bash
gpg --import helm-oci-signing-key.asc
gpg --verify {{ .ProjectName }}_{{ .Version }}_checksums.txt.asc {{ .ProjectName }}_{{ .Version }}_checksums.txt
sha256sum -c {{ .ProjectName }}_{{ .Version }}_checksums.txt
```

Verify the Helm plugin package provenance:

```bash
helm verify helm-oci-{{ .Version }}.tgz
```

# ── Changelog ─────────────────────────────────────────────────────────────────
changelog:
sort: asc
use: github
filters:
exclude:
- "^docs:"
- "^test:"
- "^chore:"
- "^ci:"
- Merge pull request
- Merge branch
groups:
- title: Features
regexp: "^.*feat.*:.*$"
order: 0
- title: Bug Fixes
regexp: "^.*fix.*:.*$"
order: 1
- title: Performance
regexp: "^.*perf.*:.*$"
order: 2
- title: Other
order: 999
4 changes: 4 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ uninstall:
clean:
rm -rf bin/

.PHONY: snapshot
snapshot:
goreleaser release --snapshot --clean --skip=sign,publish

.PHONY: dist
dist: export CGO_ENABLED=0
dist:
Expand Down
Loading