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
88 changes: 88 additions & 0 deletions .github/workflows/helm.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
name: Helm Chart

on:
push:
branches: [main]
paths:
- "charts/**"
- "Dockerfile"
- "docker-entrypoint.sh"
- "cmd/**"
- "web/**"
- ".github/workflows/helm.yml"
pull_request:
branches: [main]
paths:
- "charts/**"
- "Dockerfile"
- "docker-entrypoint.sh"
- "cmd/**"
- "web/**"
- ".github/workflows/helm.yml"

permissions:
contents: read

jobs:
lint-and-template:
name: Lint & Template
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7

- uses: azure/setup-helm@v4
with:
version: "v3.18.4"

- name: helm lint
run: helm lint charts/spectogram

- name: helm template (default values)
run: helm template test charts/spectogram

- name: helm template (all features enabled)
run: |
helm template test charts/spectogram \
--set ingress.enabled=true \
--set autoscaling.enabled=true \
--set generateJob.enabled=true \
--set generateJob.audioConfigMap=my-audio

kind-install:
name: Deploy to kind
runs-on: ubuntu-latest
needs: lint-and-template
steps:
- uses: actions/checkout@v7

- uses: azure/setup-helm@v4
with:
version: "v3.18.4"

- name: Build image
run: docker build -t spectogram:ci .

- name: Create kind cluster
uses: helm/kind-action@v1
with:
cluster_name: spectogram-ci

- name: Load image into kind
run: kind load docker-image spectogram:ci --name spectogram-ci

- name: helm install
run: |
helm install spectogram charts/spectogram \
--set image.repository=spectogram \
--set image.tag=ci \
--wait --timeout 2m

- name: helm test
run: helm test spectogram --timeout 1m

- name: Describe resources on failure
if: failure()
run: |
kubectl get all
kubectl describe pod -l app.kubernetes.io/instance=spectogram
kubectl logs -l app.kubernetes.io/instance=spectogram --all-containers --tail=200 || true
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,10 @@ docker run --rm -p 8000:8000 -v "$(pwd)":/app/data spectogram serve

Then open `http://localhost:8000/web` in your browser, same as the local workflow above.

## ☸️ Running on Kubernetes

A Helm chart at [`charts/spectogram`](charts/spectogram) deploys the containerized viewer to any Kubernetes cluster — a local `kind` cluster, a managed cloud cluster, or on-prem — using the same image built by the `Dockerfile` above. See [`charts/spectogram/README.md`](charts/spectogram/README.md) for the full quickstart (kind), generating data via an in-cluster Job, and cloud/on-prem deployment options (registry, ingress, storage class, scaling).

## 🧠 How It Works?

The `Go` script:
Expand Down Expand Up @@ -132,12 +136,13 @@ Unit and integration tests live alongside the code in `cmd/spectogram/main_test.
## 📁 Folder Structure

```
.github/workflows/ - CI pipeline (build, vet, test, Docker image build)
.github/workflows/ - CI pipeline (build, vet, test, Docker image build, Helm chart lint/kind test)
cmd/spectogram/ - main Go application and tests (main.go, main_test.go)
web/ - HTML viewer with Plotly
data/ - generated spectrogram.json output (created automatically, not tracked in git)
Dockerfile - containerized build (Go binary + Python static file server)
docker-entrypoint.sh - dispatches `generate` and `serve` container commands
charts/spectogram/ - Helm chart for deploying to kind / cloud / on-prem Kubernetes
README.md - this file
go.mod - Go module info
```
Expand Down
7 changes: 7 additions & 0 deletions charts/spectogram/.helmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.git/
.gitignore
*.swp
*.bak
*.tmp
*.orig
.DS_Store
11 changes: 11 additions & 0 deletions charts/spectogram/Chart.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
apiVersion: v2
name: spectogram
description: Helm chart for the Spectogram viewer (Go generator + Plotly web viewer)
type: application
version: 0.1.0
appVersion: "0.1.0"
home: https://github.com/landscape82/spectogram
sources:
- https://github.com/landscape82/spectogram
maintainers:
- name: landscape82
116 changes: 116 additions & 0 deletions charts/spectogram/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
# spectogram Helm chart

Deploys the Spectogram web viewer (Deployment + Service, plus optional
Ingress and a one-off "generate" Job) using the image built from the
repo root [`Dockerfile`](../../Dockerfile).

The chart is written so that moving from a local `kind` cluster to a
cloud or on-prem cluster is purely a matter of `--set`/values overrides
— no template changes are needed.

## Quickstart on `kind`

```bash
# from the repo root
docker build -t spectogram:local .
kind create cluster --name spectogram
kind load docker-image spectogram:local --name spectogram

helm install spectogram charts/spectogram \
--set image.repository=spectogram \
--set image.tag=local \
--wait

helm test spectogram

kubectl port-forward svc/spectogram 8000:8000
# open http://localhost:8000/web
```

There's no spectrogram data yet at this point (see "Generating data"
below).

## Generating data

The chart can run the Go CLI's `generate` step as a one-off Job before
the viewer starts, sharing the same PersistentVolumeClaim as the
`serve` Deployment:

```bash
kubectl create configmap my-audio --from-file=audio.wav=./audio.wav

helm upgrade spectogram charts/spectogram \
--set image.repository=spectogram \
--set image.tag=local \
--set generateJob.enabled=true \
--set generateJob.audioConfigMap=my-audio \
--set generateJob.audioKey=audio.wav \
--set generateJob.inputPath=/app/audio-input/audio.wav \
--wait
```

`generateJob.audioKey` and the trailing path segment of
`generateJob.inputPath` must match (the ConfigMap is mounted at
`/app/audio-input`, so key `audio.wav` appears at
`/app/audio-input/audio.wav`). The audio format is detected from the
file extension (`.mp3` or `.wav`), so the key/path extension must match
the actual file content.

Alternatively, `kubectl cp` an audio file into a running pod and run
`spectogram generate` manually, or `kubectl cp` pre-generated
`spectrogram.png`/`data/spectrogram.json` directly onto the volume.

## Deploying to a cloud or on-prem cluster

The defaults (`ClusterIP` Service, chart-managed PVC using the
cluster's default StorageClass, no Ingress) work unmodified on `kind`.
For a real cluster:

```bash
helm install spectogram charts/spectogram \
--set image.repository=ghcr.io/landscape82/spectogram \
--set image.tag=v0.1.0 \
--set ingress.enabled=true \
--set ingress.className=nginx \
--set ingress.host=spectogram.example.com \
--set persistence.storageClassName=<your-storage-class> \
--set resources.requests.cpu=50m \
--set resources.requests.memory=64Mi \
--set resources.limits.cpu=250m \
--set resources.limits.memory=256Mi
```

- **Image**: push the image built from the repo `Dockerfile` to your
registry and set `image.repository`/`image.tag` (and
`imagePullSecrets` if the registry is private).
- **Exposure**: set `service.type=LoadBalancer` on a cloud provider
that supports it, `service.type=NodePort` for bare on-prem clusters
without a load balancer, or enable `ingress` with your ingress
controller's class.
- **Storage**: set `persistence.storageClassName` to a StorageClass
available in your cluster, or set `persistence.existingClaim` to
reuse an existing PVC. Leave it empty to use the cluster's default
StorageClass (this is what makes the chart work unmodified on
`kind`, which registers a default StorageClass out of the box).
- **Scaling**: enable `autoscaling.enabled=true` if your cluster has
the metrics-server needed for CPU-based HPA scaling. Note the
`generateJob`/PVC model here assumes `ReadWriteOnce`, single-writer
access — it's not designed for multi-replica concurrent writes.

## Values reference

See [`values.yaml`](values.yaml) for the full list of configurable
values and inline documentation for each one.

## Testing

- `helm lint charts/spectogram`
- `helm template spectogram charts/spectogram` to review rendered
manifests
- `helm install ... --wait && helm test spectogram` runs a `helm test`
hook Pod that fetches `/web/index.html` from the Service and asserts
the Plotly viewer page is served

CI (`.github/workflows/helm.yml`) lints and templates the chart on
every PR touching it, and additionally spins up a real `kind` cluster
to `helm install` and `helm test` the chart end-to-end.
26 changes: 26 additions & 0 deletions charts/spectogram/templates/NOTES.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
Spectogram has been deployed as {{ include "spectogram.fullname" . }} in namespace {{ .Release.Namespace }}.

{{- if .Values.ingress.enabled }}
Ingress is enabled — once DNS/hosts resolve {{ .Values.ingress.host }}, visit:
http://{{ .Values.ingress.host }}{{ .Values.ingress.path }}web
{{- else if contains "NodePort" .Values.service.type }}
Get the NodePort and visit the app:
export NODE_PORT=$(kubectl get -n {{ .Release.Namespace }} svc {{ include "spectogram.fullname" . }} -o jsonpath="{.spec.ports[0].nodePort}")
export NODE_IP=$(kubectl get nodes -o jsonpath="{.items[0].status.addresses[0].address}")
echo "http://$NODE_IP:$NODE_PORT/web"
{{- else if contains "LoadBalancer" .Values.service.type }}
Get the LoadBalancer IP (may take a few minutes to be assigned):
kubectl get -n {{ .Release.Namespace }} svc -w {{ include "spectogram.fullname" . }}
{{- else }}
Service is ClusterIP — port-forward to reach it locally (works on kind and any cluster):
kubectl port-forward -n {{ .Release.Namespace }} svc/{{ include "spectogram.fullname" . }} 8000:{{ .Values.service.port }}
Then open http://localhost:8000/web
{{- end }}

{{- if not .Values.generateJob.enabled }}

No spectrogram data has been generated yet. Either:
- enable generateJob in values (see charts/spectogram/README.md), or
- `kubectl cp` an audio file into the pod and run `spectogram generate` manually, or
- `kubectl cp` a pre-generated spectrogram.png/data/spectrogram.json onto the persistent volume.
{{- end }}
53 changes: 53 additions & 0 deletions charts/spectogram/templates/_helpers.tpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
{{/*
Expand the name of the chart.
*/}}
{{- define "spectogram.name" -}}
{{- .Chart.Name | trunc 63 | trimSuffix "-" }}
{{- end }}

{{/*
Create a default fully qualified app name.
*/}}
{{- define "spectogram.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- if contains $name .Release.Name }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{- end }}

{{- define "spectogram.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}

{{- define "spectogram.labels" -}}
helm.sh/chart: {{ include "spectogram.chart" . }}
{{ include "spectogram.selectorLabels" . }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}

{{- define "spectogram.selectorLabels" -}}
app.kubernetes.io/name: {{ include "spectogram.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}

{{- define "spectogram.serviceAccountName" -}}
{{- if .Values.serviceAccount.create }}
{{- default (include "spectogram.fullname" .) .Values.serviceAccount.name }}
{{- else }}
{{- default "default" .Values.serviceAccount.name }}
{{- end }}
{{- end }}

{{- define "spectogram.pvcName" -}}
{{- if .Values.persistence.existingClaim }}
{{- .Values.persistence.existingClaim }}
{{- else }}
{{- include "spectogram.fullname" . }}-data
{{- end }}
{{- end }}
Loading
Loading