From 0c9d0df2fc884f967f661443982f8c21e34aefbc Mon Sep 17 00:00:00 2001 From: Evgeny Snitko Date: Wed, 22 Apr 2026 01:51:41 +0400 Subject: [PATCH 1/6] chart 1 --- charts/cattery/.helmignore | 20 +++ charts/cattery/Chart.yaml | 17 ++ charts/cattery/README.md | 144 +++++++++++++++++ charts/cattery/ci/smoke-values.yaml | 61 +++++++ charts/cattery/templates/NOTES.txt | 41 +++++ charts/cattery/templates/_helpers.tpl | 60 +++++++ charts/cattery/templates/configmap.yaml | 9 ++ charts/cattery/templates/deployment.yaml | 116 ++++++++++++++ charts/cattery/templates/ingress.yaml | 41 +++++ charts/cattery/templates/secrets.yaml | 29 ++++ charts/cattery/templates/service.yaml | 21 +++ charts/cattery/templates/serviceaccount.yaml | 12 ++ charts/cattery/templates/servicemonitor.yaml | 20 +++ charts/cattery/values.yaml | 157 +++++++++++++++++++ 14 files changed, 748 insertions(+) create mode 100644 charts/cattery/.helmignore create mode 100644 charts/cattery/Chart.yaml create mode 100644 charts/cattery/README.md create mode 100644 charts/cattery/ci/smoke-values.yaml create mode 100644 charts/cattery/templates/NOTES.txt create mode 100644 charts/cattery/templates/_helpers.tpl create mode 100644 charts/cattery/templates/configmap.yaml create mode 100644 charts/cattery/templates/deployment.yaml create mode 100644 charts/cattery/templates/ingress.yaml create mode 100644 charts/cattery/templates/secrets.yaml create mode 100644 charts/cattery/templates/service.yaml create mode 100644 charts/cattery/templates/serviceaccount.yaml create mode 100644 charts/cattery/templates/servicemonitor.yaml create mode 100644 charts/cattery/values.yaml diff --git a/charts/cattery/.helmignore b/charts/cattery/.helmignore new file mode 100644 index 0000000..3fa04d1 --- /dev/null +++ b/charts/cattery/.helmignore @@ -0,0 +1,20 @@ +# Patterns to ignore when building packages. +.DS_Store +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +*.swp +*.bak +*.tmp +*.orig +*~ +.project +.idea/ +*.tmproj +.vscode/ +OWNERS +ci/ diff --git a/charts/cattery/Chart.yaml b/charts/cattery/Chart.yaml new file mode 100644 index 0000000..350f27f --- /dev/null +++ b/charts/cattery/Chart.yaml @@ -0,0 +1,17 @@ +apiVersion: v2 +name: cattery +description: Scheduler and lifecycle manager for GitHub Actions self-hosted runners +type: application +version: 0.1.0 +appVersion: "0.0.1" +home: https://github.com/paritytech/cattery +sources: + - https://github.com/paritytech/cattery +keywords: + - github-actions + - self-hosted-runners + - ci + - runner-scale-set +maintainers: + - name: paritytech + url: https://github.com/paritytech/cattery diff --git a/charts/cattery/README.md b/charts/cattery/README.md new file mode 100644 index 0000000..1a8ee5a --- /dev/null +++ b/charts/cattery/README.md @@ -0,0 +1,144 @@ +# cattery + +Helm chart for [Cattery](https://github.com/paritytech/cattery), a scheduler +and lifecycle manager for GitHub Actions self-hosted runners. + +## TL;DR + +```bash +helm install cattery ./charts/cattery -f my-values.yaml +``` + +## Prerequisites + +- Kubernetes 1.24+ +- Helm 3.8+ +- A reachable MongoDB instance (this chart does **not** bundle one) +- A [GitHub App](https://docs.github.com/en/apps/creating-github-apps) with + Actions read/write permissions, installed on your organization +- Credentials for at least one provider (Docker socket access, or a GCP + service account for the `google` provider) + +## Installing + +Minimal `values.yaml`: + +```yaml +image: + tag: "0.0.1" + +config: + server: + advertiseUrl: https://cattery.example.com + + database: + uri: mongodb://mongodb.default.svc:27017/ + database: cattery + + github: + - name: my-org + appId: 123456 + appClientId: Iv123abC + installationId: 987654321 + privateKeyPath: /cattery/secrets/my-org/private-key.pem + + providers: + - name: gce + type: google + project: my-gcp-project + + trayTypes: + - name: my-runner + provider: gce + githubOrg: my-org + runnerGroupId: 1 + maxTrays: 5 + shutdown: true + config: + project: my-gcp-project + zones: [europe-west1-b, europe-west1-c] + machineType: e2-standard-2 + instanceTemplate: global/instanceTemplates/my-runner + +secretFiles: + my-org-private-key: + mountPath: /cattery/secrets/my-org/private-key.pem + existingSecret: my-org-github-app + existingSecretKey: private-key.pem +``` + +```bash +helm install cattery ./charts/cattery -f values.yaml +``` + +## Configuration + +All fields under `config` are rendered verbatim into `/etc/cattery/config.yaml` +inside the pod. See +[docs/configuration.md](https://github.com/paritytech/cattery/blob/main/docs/configuration.md) +for the full reference. + +### Secrets + +`secretFiles` mounts arbitrary files (typically GitHub App private keys) at +the path `config.github[*].privateKeyPath` expects. Two modes per entry: + +- **Inline** — set `value` with the file content. A Secret is created by + this chart. Convenient for bootstrapping; don't commit production values. +- **External** — set `existingSecret` (and optionally `existingSecretKey`, + default `content`) to reference a Secret you manage elsewhere (sealed + secrets, external-secrets-operator, vault, etc). + +Webhook secrets are handled the same way: inline `webhookSecret` on a +`config.github[*]` entry renders a Secret, or reference an existing one via +the top-level `webhookSecrets` map. + +### Status port + +`config.server.statusListenAddress` can be set to serve `/status` and +`/metrics` on a separate port. When set, the chart opens a second Service +port and the `ServiceMonitor` scrapes that port. Probes always target +whichever port `/status` is on. + +### ServiceMonitor + +Enable with `serviceMonitor.enabled: true` (requires the Prometheus Operator +CRDs installed in the cluster). + +## Values + +| Key | Default | Description | +| ------------------------------------- | ------------------------------ | ----------------------------------------------- | +| `replicaCount` | `1` | Runs as a singleton; don't raise without care. | +| `image.repository` | `docker.io/paritytech/cattery` | Container image. | +| `image.tag` | `""` (uses `.Chart.AppVersion`)| Image tag. | +| `image.pullPolicy` | `IfNotPresent` | | +| `config.server.listenAddress` | `0.0.0.0:5137` | Agent/API listen address. | +| `config.server.statusListenAddress` | `0.0.0.0:3925` | Status/metrics listen address. Empty to share. | +| `config.server.advertiseUrl` | `http://cattery.example.com` | URL runners use to reach the server. | +| `config.database.uri` | `mongodb://mongodb:27017/` | MongoDB connection URI. | +| `config.github` | `[]` | List of GitHub App configs. | +| `config.providers` | `[]` | List of provider configs. | +| `config.trayTypes` | `[]` | List of tray type configs. | +| `secretFiles` | `{}` | Files mounted into the container from Secrets. | +| `webhookSecrets` | `{}` | References to existing webhook Secrets. | +| `service.type` | `ClusterIP` | | +| `service.port` | `5137` | Service port mapped to `http`. | +| `service.statusPort` | `3925` | Service port for status/metrics (when enabled). | +| `ingress.enabled` | `false` | | +| `serviceMonitor.enabled` | `false` | Requires Prometheus Operator. | +| `serviceAccount.create` | `true` | | +| `serviceAccount.annotations` | `{}` | e.g. GKE Workload Identity binding. | +| `resources` | `{}` | | +| `livenessProbe.enabled` | `true` | | +| `readinessProbe.enabled` | `true` | | +| `nodeSelector` / `tolerations` / `affinity` | `{}` / `[]` / `{}` | | + +## Upgrading + +The Deployment uses `strategy: Recreate` — cattery is a singleton and the +pod holds long-running leases against GitHub, so rolling updates would +produce a double-poll window. + +Config changes are picked up automatically: the pod template carries a +checksum of the rendered `config` and restarts when it changes. diff --git a/charts/cattery/ci/smoke-values.yaml b/charts/cattery/ci/smoke-values.yaml new file mode 100644 index 0000000..b0d4818 --- /dev/null +++ b/charts/cattery/ci/smoke-values.yaml @@ -0,0 +1,61 @@ +config: + server: + advertiseUrl: http://cattery.test.local + github: + - name: testorg + appId: 123 + appClientId: Iv1abc + installationId: 456 + privateKeyPath: /cattery/secrets/testorg/pk.pem + webhookSecret: hunter2 + providers: + - name: docker-local + type: docker + trayTypes: + - name: basic + provider: docker-local + githubOrg: testorg + runnerGroupId: 1 + maxTrays: 2 + config: + image: ghcr.io/actions/actions-runner:latest + +secretFiles: + testorg-pk: + mountPath: /cattery/secrets/testorg/pk.pem + value: | + -----BEGIN RSA PRIVATE KEY----- + MIIBOgIBAAJBAKj34GkxFhD90vcNLYLInFEX6Ppy1tPf9Cnzj4p4WGeKLs1Pt8Qu + -----END RSA PRIVATE KEY----- + other-pk: + mountPath: /cattery/secrets/other/pk.pem + existingSecret: other-github-pk + existingSecretKey: key.pem + +ingress: + enabled: true + className: nginx + hosts: + - host: cattery.test.local + paths: + - path: / + pathType: Prefix + tls: + - secretName: cattery-tls + hosts: + - cattery.test.local + +serviceMonitor: + enabled: true + +resources: + requests: + cpu: 100m + memory: 128Mi + limits: + cpu: 500m + memory: 512Mi + +serviceAccount: + annotations: + iam.gke.io/gcp-service-account: cattery@example.iam.gserviceaccount.com diff --git a/charts/cattery/templates/NOTES.txt b/charts/cattery/templates/NOTES.txt new file mode 100644 index 0000000..5b1d1eb --- /dev/null +++ b/charts/cattery/templates/NOTES.txt @@ -0,0 +1,41 @@ +Cattery {{ .Chart.AppVersion }} installed as release "{{ .Release.Name }}" in namespace "{{ .Release.Namespace }}". + +Service: + {{ include "cattery.fullname" . }}.{{ .Release.Namespace }}.svc:{{ .Values.service.port }} + +{{- if .Values.config.server.statusListenAddress }} + +Status / metrics port: + {{ include "cattery.fullname" . }}.{{ .Release.Namespace }}.svc:{{ .Values.service.statusPort }} + - /status status page + - /metrics Prometheus metrics +{{- else }} + +Status / metrics are served on the main port (/status, /metrics). +{{- end }} + +{{- if .Values.ingress.enabled }} + +Ingress enabled on: +{{- range .Values.ingress.hosts }} + - http{{ if $.Values.ingress.tls }}s{{ end }}://{{ .host }} +{{- end }} +{{- else }} + +To reach the status page from your laptop: + kubectl -n {{ .Release.Namespace }} port-forward svc/{{ include "cattery.fullname" . }} \ + {{- if .Values.config.server.statusListenAddress }} {{ .Values.service.statusPort }}:{{ .Values.service.statusPort }}{{ else }} {{ .Values.service.port }}:{{ .Values.service.port }}{{ end }} + Then open http://localhost:{{ if .Values.config.server.statusListenAddress }}{{ .Values.service.statusPort }}{{ else }}{{ .Values.service.port }}{{ end }}/status +{{- end }} + +{{- if not .Values.config.github }} + +WARNING: no GitHub apps configured. Add entries under `config.github` before +cattery can poll for jobs. See docs/configuration.md in the cattery repo. +{{- end }} + +{{- if not .Values.config.trayTypes }} + +WARNING: no trayTypes configured. Cattery will run but won't provision any +runners until you add at least one trayType under `config.trayTypes`. +{{- end }} diff --git a/charts/cattery/templates/_helpers.tpl b/charts/cattery/templates/_helpers.tpl new file mode 100644 index 0000000..2315930 --- /dev/null +++ b/charts/cattery/templates/_helpers.tpl @@ -0,0 +1,60 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "cattery.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +Truncated at 63 chars to stay within Kubernetes DNS name limits. +*/}} +{{- define "cattery.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 "cattery.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{- define "cattery.labels" -}} +helm.sh/chart: {{ include "cattery.chart" . }} +{{ include "cattery.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{- define "cattery.selectorLabels" -}} +app.kubernetes.io/name: {{ include "cattery.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{- define "cattery.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "cattery.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} + +{{/* +Probe target port: status port if configured separately, otherwise http. +*/}} +{{- define "cattery.probePort" -}} +{{- if .Values.config.server.statusListenAddress -}} +status +{{- else -}} +http +{{- end -}} +{{- end }} diff --git a/charts/cattery/templates/configmap.yaml b/charts/cattery/templates/configmap.yaml new file mode 100644 index 0000000..dfe27df --- /dev/null +++ b/charts/cattery/templates/configmap.yaml @@ -0,0 +1,9 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "cattery.fullname" . }}-config + labels: + {{- include "cattery.labels" . | nindent 4 }} +data: + config.yaml: | + {{- toYaml .Values.config | nindent 4 }} diff --git a/charts/cattery/templates/deployment.yaml b/charts/cattery/templates/deployment.yaml new file mode 100644 index 0000000..57023fb --- /dev/null +++ b/charts/cattery/templates/deployment.yaml @@ -0,0 +1,116 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "cattery.fullname" . }} + labels: + {{- include "cattery.labels" . | nindent 4 }} +spec: + replicas: {{ .Values.replicaCount }} + strategy: + type: Recreate + selector: + matchLabels: + {{- include "cattery.selectorLabels" . | nindent 6 }} + template: + metadata: + annotations: + cattery/config-checksum: {{ .Values.config | toYaml | sha256sum }} + {{- with .Values.podAnnotations }} + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "cattery.labels" . | nindent 8 }} + {{- with .Values.podLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + serviceAccountName: {{ include "cattery.serviceAccountName" . }} + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.podSecurityContext }} + securityContext: + {{- toYaml . | nindent 8 }} + {{- end }} + containers: + - name: {{ .Chart.Name }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + {{- with .Values.securityContext }} + securityContext: + {{- toYaml . | nindent 12 }} + {{- end }} + args: + - server + - -c + - /etc/cattery/config.yaml + ports: + - name: http + containerPort: {{ regexReplaceAll ".*:" .Values.config.server.listenAddress "" | int }} + protocol: TCP + {{- if .Values.config.server.statusListenAddress }} + - name: status + containerPort: {{ regexReplaceAll ".*:" .Values.config.server.statusListenAddress "" | int }} + protocol: TCP + {{- end }} + {{- if .Values.livenessProbe.enabled }} + livenessProbe: + httpGet: + path: /status + port: {{ include "cattery.probePort" . }} + initialDelaySeconds: {{ .Values.livenessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.livenessProbe.periodSeconds }} + timeoutSeconds: {{ .Values.livenessProbe.timeoutSeconds }} + failureThreshold: {{ .Values.livenessProbe.failureThreshold }} + {{- end }} + {{- if .Values.readinessProbe.enabled }} + readinessProbe: + httpGet: + path: /status + port: {{ include "cattery.probePort" . }} + initialDelaySeconds: {{ .Values.readinessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.readinessProbe.periodSeconds }} + timeoutSeconds: {{ .Values.readinessProbe.timeoutSeconds }} + failureThreshold: {{ .Values.readinessProbe.failureThreshold }} + {{- end }} + {{- with .Values.resources }} + resources: + {{- toYaml . | nindent 12 }} + {{- end }} + volumeMounts: + - name: config + mountPath: /etc/cattery/config.yaml + subPath: config.yaml + readOnly: true + {{- range $name, $spec := .Values.secretFiles }} + - name: secret-file-{{ $name }} + mountPath: {{ $spec.mountPath }} + subPath: {{ default "content" $spec.existingSecretKey }} + readOnly: true + {{- end }} + volumes: + - name: config + configMap: + name: {{ include "cattery.fullname" . }}-config + {{- range $name, $spec := .Values.secretFiles }} + - name: secret-file-{{ $name }} + secret: + {{- if $spec.existingSecret }} + secretName: {{ $spec.existingSecret }} + {{- else }} + secretName: {{ include "cattery.fullname" $ }}-file-{{ $name }} + {{- end }} + {{- end }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/charts/cattery/templates/ingress.yaml b/charts/cattery/templates/ingress.yaml new file mode 100644 index 0000000..d51ec6b --- /dev/null +++ b/charts/cattery/templates/ingress.yaml @@ -0,0 +1,41 @@ +{{- if .Values.ingress.enabled -}} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ include "cattery.fullname" . }} + labels: + {{- include "cattery.labels" . | nindent 4 }} + {{- with .Values.ingress.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- with .Values.ingress.className }} + ingressClassName: {{ . }} + {{- end }} + {{- with .Values.ingress.tls }} + tls: + {{- range . }} + - hosts: + {{- range .hosts }} + - {{ . | quote }} + {{- end }} + secretName: {{ .secretName }} + {{- end }} + {{- end }} + rules: + {{- range .Values.ingress.hosts }} + - host: {{ .host | quote }} + http: + paths: + {{- range .paths }} + - path: {{ .path }} + pathType: {{ default "Prefix" .pathType }} + backend: + service: + name: {{ include "cattery.fullname" $ }} + port: + number: {{ $.Values.service.port }} + {{- end }} + {{- end }} +{{- end }} diff --git a/charts/cattery/templates/secrets.yaml b/charts/cattery/templates/secrets.yaml new file mode 100644 index 0000000..c7433cb --- /dev/null +++ b/charts/cattery/templates/secrets.yaml @@ -0,0 +1,29 @@ +{{- range $name, $spec := .Values.secretFiles }} +{{- if $spec.value }} +--- +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "cattery.fullname" $ }}-file-{{ $name }} + labels: + {{- include "cattery.labels" $ | nindent 4 }} +type: Opaque +data: + content: {{ $spec.value | toString | b64enc }} +{{- end }} +{{- end }} + +{{- range $gh := .Values.config.github }} +{{- if $gh.webhookSecret }} +--- +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "cattery.fullname" $ }}-webhook-{{ $gh.name }} + labels: + {{- include "cattery.labels" $ | nindent 4 }} +type: Opaque +data: + secret: {{ $gh.webhookSecret | toString | b64enc }} +{{- end }} +{{- end }} diff --git a/charts/cattery/templates/service.yaml b/charts/cattery/templates/service.yaml new file mode 100644 index 0000000..a6f6d32 --- /dev/null +++ b/charts/cattery/templates/service.yaml @@ -0,0 +1,21 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "cattery.fullname" . }} + labels: + {{- include "cattery.labels" . | nindent 4 }} +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.port }} + targetPort: http + protocol: TCP + name: http + {{- if .Values.config.server.statusListenAddress }} + - port: {{ .Values.service.statusPort }} + targetPort: status + protocol: TCP + name: status + {{- end }} + selector: + {{- include "cattery.selectorLabels" . | nindent 4 }} diff --git a/charts/cattery/templates/serviceaccount.yaml b/charts/cattery/templates/serviceaccount.yaml new file mode 100644 index 0000000..d1c2a25 --- /dev/null +++ b/charts/cattery/templates/serviceaccount.yaml @@ -0,0 +1,12 @@ +{{- if .Values.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "cattery.serviceAccountName" . }} + labels: + {{- include "cattery.labels" . | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end -}} diff --git a/charts/cattery/templates/servicemonitor.yaml b/charts/cattery/templates/servicemonitor.yaml new file mode 100644 index 0000000..eaf1469 --- /dev/null +++ b/charts/cattery/templates/servicemonitor.yaml @@ -0,0 +1,20 @@ +{{- if .Values.serviceMonitor.enabled -}} +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +metadata: + name: {{ include "cattery.fullname" . }} + labels: + {{- include "cattery.labels" . | nindent 4 }} + {{- with .Values.serviceMonitor.labels }} + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + endpoints: + - port: {{ include "cattery.probePort" . }} + path: /metrics + interval: {{ .Values.serviceMonitor.interval }} + scrapeTimeout: {{ .Values.serviceMonitor.scrapeTimeout }} + selector: + matchLabels: + {{- include "cattery.selectorLabels" . | nindent 6 }} +{{- end -}} diff --git a/charts/cattery/values.yaml b/charts/cattery/values.yaml new file mode 100644 index 0000000..8e552c8 --- /dev/null +++ b/charts/cattery/values.yaml @@ -0,0 +1,157 @@ +replicaCount: 1 + +image: + repository: docker.io/paritytech/cattery + pullPolicy: IfNotPresent + # Defaults to .Chart.AppVersion when unset. + tag: "" + +imagePullSecrets: [] +nameOverride: "" +fullnameOverride: "" + +# Cattery config rendered into /etc/cattery/config.yaml. +# See https://github.com/paritytech/cattery/blob/main/docs/configuration.md +config: + server: + listenAddress: "0.0.0.0:5137" + # Optionally serve /status and /metrics on a separate port. Leave empty + # to serve them on listenAddress. + statusListenAddress: "0.0.0.0:3925" + # URL agents use to reach the server. Must be reachable from runners. + advertiseUrl: http://cattery.example.com + + database: + uri: mongodb://mongodb:27017/ + database: cattery + + # One entry per GitHub App / organization. + github: [] + # - name: my-org + # appId: 123456 + # appClientId: Iv123abC + # installationId: 987654321 + # privateKeyPath: /cattery/secrets/my-org/private-key.pem + + providers: [] + # - name: docker-local + # type: docker + # - name: gce + # type: google + # project: my-gcp-project + + trayTypes: [] + # - name: my-runner + # provider: docker-local + # githubOrg: my-org + # runnerGroupId: 1 + # maxTrays: 5 + # shutdown: true + # config: + # image: my-runner-image:latest + +# Extra arbitrary files mounted into the container. +# +# Use this for GitHub App private keys and any other sensitive file the +# config.yaml references by path. +# +# Two modes per entry: +# 1. `value` — inline content, rendered into a Secret managed by this chart +# (convenient, but puts the secret in your values file). +# 2. `existingSecret` / `existingSecretKey` — reference an externally +# managed Secret (recommended for production). +secretFiles: {} +# secretFiles: +# my-org-private-key: +# mountPath: /cattery/secrets/my-org/private-key.pem +# value: | +# -----BEGIN RSA PRIVATE KEY----- +# ... +# -----END RSA PRIVATE KEY----- +# other-org-private-key: +# mountPath: /cattery/secrets/other-org/private-key.pem +# existingSecret: other-org-github-app +# existingSecretKey: private-key.pem + +# Webhook secrets per GitHub App entry. +# +# If you set `webhookSecret` inline on a `config.github[*]` entry it will be +# rendered into a Secret. To reference an existing Secret instead, omit +# `webhookSecret` in config and add an entry here keyed by the org name. +webhookSecrets: {} +# webhookSecrets: +# my-org: +# existingSecret: my-org-webhook +# existingSecretKey: secret + +serviceAccount: + create: true + name: "" + annotations: {} + +podAnnotations: {} +podLabels: {} + +podSecurityContext: {} + # fsGroup: 65532 + +securityContext: {} + # allowPrivilegeEscalation: false + # readOnlyRootFilesystem: true + # runAsNonRoot: true + # runAsUser: 65532 + # capabilities: + # drop: ["ALL"] + +service: + type: ClusterIP + port: 5137 + # Exposed only when config.server.statusListenAddress is set. + statusPort: 3925 + +ingress: + enabled: false + className: "" + annotations: {} + hosts: [] + # - host: cattery.example.com + # paths: + # - path: / + # pathType: Prefix + tls: [] + # - secretName: cattery-tls + # hosts: + # - cattery.example.com + +resources: {} + # limits: + # cpu: 500m + # memory: 512Mi + # requests: + # cpu: 100m + # memory: 128Mi + +# Probes hit /status. They target the status port when +# config.server.statusListenAddress is set, otherwise the main http port. +livenessProbe: + enabled: true + initialDelaySeconds: 15 + periodSeconds: 20 + timeoutSeconds: 3 + failureThreshold: 3 +readinessProbe: + enabled: true + initialDelaySeconds: 5 + periodSeconds: 10 + timeoutSeconds: 3 + failureThreshold: 3 + +nodeSelector: {} +tolerations: [] +affinity: {} + +serviceMonitor: + enabled: false + interval: 30s + scrapeTimeout: 10s + labels: {} From fb03a8abbffde8c95fefbfd53eaf6040c4f0dd89 Mon Sep 17 00:00:00 2001 From: Evgeny Snitko Date: Wed, 22 Apr 2026 02:43:19 +0400 Subject: [PATCH 2/6] annotations --- charts/cattery/ci/smoke-values.yaml | 9 +++++++++ charts/cattery/templates/deployment.yaml | 4 ++++ charts/cattery/templates/service.yaml | 4 ++++ charts/cattery/templates/servicemonitor.yaml | 4 ++++ charts/cattery/values.yaml | 3 +++ 5 files changed, 24 insertions(+) diff --git a/charts/cattery/ci/smoke-values.yaml b/charts/cattery/ci/smoke-values.yaml index b0d4818..fc99997 100644 --- a/charts/cattery/ci/smoke-values.yaml +++ b/charts/cattery/ci/smoke-values.yaml @@ -47,6 +47,15 @@ ingress: serviceMonitor: enabled: true + annotations: + example.com/owner: team-a + +deploymentAnnotations: + example.com/owner: team-a + +service: + annotations: + cloud.google.com/load-balancer-type: Internal resources: requests: diff --git a/charts/cattery/templates/deployment.yaml b/charts/cattery/templates/deployment.yaml index 57023fb..50235f2 100644 --- a/charts/cattery/templates/deployment.yaml +++ b/charts/cattery/templates/deployment.yaml @@ -4,6 +4,10 @@ metadata: name: {{ include "cattery.fullname" . }} labels: {{- include "cattery.labels" . | nindent 4 }} + {{- with .Values.deploymentAnnotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} spec: replicas: {{ .Values.replicaCount }} strategy: diff --git a/charts/cattery/templates/service.yaml b/charts/cattery/templates/service.yaml index a6f6d32..6a4f580 100644 --- a/charts/cattery/templates/service.yaml +++ b/charts/cattery/templates/service.yaml @@ -4,6 +4,10 @@ metadata: name: {{ include "cattery.fullname" . }} labels: {{- include "cattery.labels" . | nindent 4 }} + {{- with .Values.service.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} spec: type: {{ .Values.service.type }} ports: diff --git a/charts/cattery/templates/servicemonitor.yaml b/charts/cattery/templates/servicemonitor.yaml index eaf1469..b3a2ee6 100644 --- a/charts/cattery/templates/servicemonitor.yaml +++ b/charts/cattery/templates/servicemonitor.yaml @@ -8,6 +8,10 @@ metadata: {{- with .Values.serviceMonitor.labels }} {{- toYaml . | nindent 4 }} {{- end }} + {{- with .Values.serviceMonitor.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} spec: endpoints: - port: {{ include "cattery.probePort" . }} diff --git a/charts/cattery/values.yaml b/charts/cattery/values.yaml index 8e552c8..f8c5f66 100644 --- a/charts/cattery/values.yaml +++ b/charts/cattery/values.yaml @@ -89,6 +89,7 @@ serviceAccount: name: "" annotations: {} +deploymentAnnotations: {} podAnnotations: {} podLabels: {} @@ -108,6 +109,7 @@ service: port: 5137 # Exposed only when config.server.statusListenAddress is set. statusPort: 3925 + annotations: {} ingress: enabled: false @@ -155,3 +157,4 @@ serviceMonitor: interval: 30s scrapeTimeout: 10s labels: {} + annotations: {} From eea2e4eabe8d49d05229dc9ccd1ef795312e41e2 Mon Sep 17 00:00:00 2001 From: Evgeny Snitko Date: Wed, 22 Apr 2026 04:14:12 +0400 Subject: [PATCH 3/6] dockerfile rework --- Dockerfile | 42 +++++++++++++++----- charts/cattery/README.md | 50 ++++++++++++++++++++---- charts/cattery/ci/smoke-values.yaml | 21 +++++++++- charts/cattery/templates/NOTES.txt | 7 ++++ charts/cattery/templates/deployment.yaml | 26 +++++++++--- charts/cattery/templates/secrets.yaml | 15 ------- charts/cattery/values.yaml | 42 ++++++++++++++------ 7 files changed, 155 insertions(+), 48 deletions(-) diff --git a/Dockerfile b/Dockerfile index 84ee267..7b511e8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,21 +1,45 @@ -FROM golang:1.25.5-alpine AS builder +ARG GO_VERSION=1.25.5 +ARG ALPINE_VERSION=3.20 + +FROM golang:${GO_VERSION}-alpine AS builder ARG CATTERY_VERSION="0.0.0" -WORKDIR /app +WORKDIR /src + +COPY src/go.mod src/go.sum ./ +RUN go mod download COPY src/ . -RUN go build -o bin/cattery -ldflags="-X cattery/cmd.Version=$CATTERY_VERSION" +ENV CGO_ENABLED=0 GOOS=linux + +RUN go build \ + -trimpath \ + -ldflags="-s -w -X cattery/cmd.Version=${CATTERY_VERSION}" \ + -o /out/cattery \ + && /out/cattery --version -FROM alpine:latest +FROM alpine:${ALPINE_VERSION} + +ARG CATTERY_VERSION="0.0.0" -ARG CONFIG_PATH="/etc/cattery/config.yaml" +LABEL org.opencontainers.image.title="cattery" \ + org.opencontainers.image.description="Scheduler and lifecycle manager for GitHub Actions self-hosted runners" \ + org.opencontainers.image.source="https://github.com/paritytech/cattery" \ + org.opencontainers.image.url="https://github.com/paritytech/cattery" \ + org.opencontainers.image.licenses="MIT" \ + org.opencontainers.image.version="${CATTERY_VERSION}" -WORKDIR /cattery +# ca-certificates: required for outbound HTTPS (api.github.com, googleapis.com) +# tzdata: lets users override timezone with TZ env var +RUN apk add --no-cache ca-certificates tzdata \ + && addgroup -S -g 65532 cattery \ + && adduser -S -u 65532 -G cattery -H -h /nonexistent cattery -COPY --from=builder /app/bin/cattery . +COPY --from=builder /out/cattery /usr/local/bin/cattery -RUN ./cattery --version +USER 65532:65532 -ENTRYPOINT ["./cattery", "server", "-c", "/etc/cattery/config.yaml"] \ No newline at end of file +ENTRYPOINT ["/usr/local/bin/cattery"] +CMD ["server", "-c", "/etc/cattery/config.yaml"] diff --git a/charts/cattery/README.md b/charts/cattery/README.md index 1a8ee5a..e904d17 100644 --- a/charts/cattery/README.md +++ b/charts/cattery/README.md @@ -67,8 +67,43 @@ secretFiles: existingSecretKey: private-key.pem ``` -```bash -helm install cattery ./charts/cattery -f values.yaml +### GCP provider + +When using the `google` provider outside of GKE Workload Identity, mount the +service account key JSON as a file and point `credentialsFile` at it: + +```yaml +secretFiles: + gcp-sa: + mountPath: /cattery/secrets/gcp-sa.json + existingSecret: my-gcp-sa + existingSecretKey: key.json + +config: + providers: + - name: gce + type: google + project: my-gcp-project + credentialsFile: /cattery/secrets/gcp-sa.json +``` + +On GKE, skip the file and use Workload Identity via `serviceAccount.annotations`. + +### Docker provider + +The `docker` provider needs access to a Docker daemon. Typical pattern is a +hostPath mount of the socket (requires a privileged node configuration — not +recommended for multi-tenant clusters): + +```yaml +extraVolumes: + - name: docker-sock + hostPath: + path: /var/run/docker.sock + type: Socket +extraVolumeMounts: + - name: docker-sock + mountPath: /var/run/docker.sock ``` ## Configuration @@ -89,10 +124,6 @@ the path `config.github[*].privateKeyPath` expects. Two modes per entry: default `content`) to reference a Secret you manage elsewhere (sealed secrets, external-secrets-operator, vault, etc). -Webhook secrets are handled the same way: inline `webhookSecret` on a -`config.github[*]` entry renders a Secret, or reference an existing one via -the top-level `webhookSecrets` map. - ### Status port `config.server.statusListenAddress` can be set to serve `/status` and @@ -121,7 +152,8 @@ CRDs installed in the cluster). | `config.providers` | `[]` | List of provider configs. | | `config.trayTypes` | `[]` | List of tray type configs. | | `secretFiles` | `{}` | Files mounted into the container from Secrets. | -| `webhookSecrets` | `{}` | References to existing webhook Secrets. | +| `env` / `envFrom` | `[]` / `[]` | Extra env vars on the container. | +| `extraVolumes` / `extraVolumeMounts` | `[]` / `[]` | Escape hatch for arbitrary volume mounts. | | `service.type` | `ClusterIP` | | | `service.port` | `5137` | Service port mapped to `http`. | | `service.statusPort` | `3925` | Service port for status/metrics (when enabled). | @@ -129,10 +161,14 @@ CRDs installed in the cluster). | `serviceMonitor.enabled` | `false` | Requires Prometheus Operator. | | `serviceAccount.create` | `true` | | | `serviceAccount.annotations` | `{}` | e.g. GKE Workload Identity binding. | +| `serviceAccount.automountServiceAccountToken` | `false` | Cattery doesn't call the k8s API. | | `resources` | `{}` | | | `livenessProbe.enabled` | `true` | | | `readinessProbe.enabled` | `true` | | | `nodeSelector` / `tolerations` / `affinity` | `{}` / `[]` / `{}` | | +| `priorityClassName` | `""` | | +| `revisionHistoryLimit` | `5` | | +| `terminationGracePeriodSeconds` | `30` | | ## Upgrading diff --git a/charts/cattery/ci/smoke-values.yaml b/charts/cattery/ci/smoke-values.yaml index fc99997..fc5a878 100644 --- a/charts/cattery/ci/smoke-values.yaml +++ b/charts/cattery/ci/smoke-values.yaml @@ -7,7 +7,6 @@ config: appClientId: Iv1abc installationId: 456 privateKeyPath: /cattery/secrets/testorg/pk.pem - webhookSecret: hunter2 providers: - name: docker-local type: docker @@ -68,3 +67,23 @@ resources: serviceAccount: annotations: iam.gke.io/gcp-service-account: cattery@example.iam.gserviceaccount.com + +env: + - name: TZ + value: UTC + +envFrom: + - secretRef: + name: cattery-extra-env + +extraVolumes: + - name: docker-sock + hostPath: + path: /var/run/docker.sock + type: Socket +extraVolumeMounts: + - name: docker-sock + mountPath: /var/run/docker.sock + +priorityClassName: system-cluster-critical +terminationGracePeriodSeconds: 60 diff --git a/charts/cattery/templates/NOTES.txt b/charts/cattery/templates/NOTES.txt index 5b1d1eb..6bb639d 100644 --- a/charts/cattery/templates/NOTES.txt +++ b/charts/cattery/templates/NOTES.txt @@ -39,3 +39,10 @@ cattery can poll for jobs. See docs/configuration.md in the cattery repo. WARNING: no trayTypes configured. Cattery will run but won't provision any runners until you add at least one trayType under `config.trayTypes`. {{- end }} + +{{- if gt (int .Values.replicaCount) 1 }} + +WARNING: replicaCount={{ .Values.replicaCount }} but cattery is a singleton. +Multiple replicas will double-poll GitHub and race on provider calls. +Set replicaCount back to 1. +{{- end }} diff --git a/charts/cattery/templates/deployment.yaml b/charts/cattery/templates/deployment.yaml index 50235f2..b9e97b7 100644 --- a/charts/cattery/templates/deployment.yaml +++ b/charts/cattery/templates/deployment.yaml @@ -10,6 +10,7 @@ metadata: {{- end }} spec: replicas: {{ .Values.replicaCount }} + revisionHistoryLimit: {{ .Values.revisionHistoryLimit }} strategy: type: Recreate selector: @@ -18,7 +19,7 @@ spec: template: metadata: annotations: - cattery/config-checksum: {{ .Values.config | toYaml | sha256sum }} + checksum/config: {{ .Values.config | toYaml | sha256sum }} {{- with .Values.podAnnotations }} {{- toYaml . | nindent 8 }} {{- end }} @@ -29,6 +30,11 @@ spec: {{- end }} spec: serviceAccountName: {{ include "cattery.serviceAccountName" . }} + automountServiceAccountToken: {{ .Values.serviceAccount.automountServiceAccountToken }} + terminationGracePeriodSeconds: {{ .Values.terminationGracePeriodSeconds }} + {{- with .Values.priorityClassName }} + priorityClassName: {{ . }} + {{- end }} {{- with .Values.imagePullSecrets }} imagePullSecrets: {{- toYaml . | nindent 8 }} @@ -45,10 +51,14 @@ spec: securityContext: {{- toYaml . | nindent 12 }} {{- end }} - args: - - server - - -c - - /etc/cattery/config.yaml + {{- with .Values.env }} + env: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.envFrom }} + envFrom: + {{- toYaml . | nindent 12 }} + {{- end }} ports: - name: http containerPort: {{ regexReplaceAll ".*:" .Values.config.server.listenAddress "" | int }} @@ -93,6 +103,9 @@ spec: subPath: {{ default "content" $spec.existingSecretKey }} readOnly: true {{- end }} + {{- with .Values.extraVolumeMounts }} + {{- toYaml . | nindent 12 }} + {{- end }} volumes: - name: config configMap: @@ -106,6 +119,9 @@ spec: secretName: {{ include "cattery.fullname" $ }}-file-{{ $name }} {{- end }} {{- end }} + {{- with .Values.extraVolumes }} + {{- toYaml . | nindent 8 }} + {{- end }} {{- with .Values.nodeSelector }} nodeSelector: {{- toYaml . | nindent 8 }} diff --git a/charts/cattery/templates/secrets.yaml b/charts/cattery/templates/secrets.yaml index c7433cb..575534e 100644 --- a/charts/cattery/templates/secrets.yaml +++ b/charts/cattery/templates/secrets.yaml @@ -12,18 +12,3 @@ data: content: {{ $spec.value | toString | b64enc }} {{- end }} {{- end }} - -{{- range $gh := .Values.config.github }} -{{- if $gh.webhookSecret }} ---- -apiVersion: v1 -kind: Secret -metadata: - name: {{ include "cattery.fullname" $ }}-webhook-{{ $gh.name }} - labels: - {{- include "cattery.labels" $ | nindent 4 }} -type: Opaque -data: - secret: {{ $gh.webhookSecret | toString | b64enc }} -{{- end }} -{{- end }} diff --git a/charts/cattery/values.yaml b/charts/cattery/values.yaml index f8c5f66..e78cc58 100644 --- a/charts/cattery/values.yaml +++ b/charts/cattery/values.yaml @@ -1,3 +1,4 @@ +# Cattery is a singleton — do not raise replicaCount. replicaCount: 1 image: @@ -10,6 +11,10 @@ imagePullSecrets: [] nameOverride: "" fullnameOverride: "" +revisionHistoryLimit: 5 +terminationGracePeriodSeconds: 30 +priorityClassName: "" + # Cattery config rendered into /etc/cattery/config.yaml. # See https://github.com/paritytech/cattery/blob/main/docs/configuration.md config: @@ -73,21 +78,12 @@ secretFiles: {} # existingSecret: other-org-github-app # existingSecretKey: private-key.pem -# Webhook secrets per GitHub App entry. -# -# If you set `webhookSecret` inline on a `config.github[*]` entry it will be -# rendered into a Secret. To reference an existing Secret instead, omit -# `webhookSecret` in config and add an entry here keyed by the org name. -webhookSecrets: {} -# webhookSecrets: -# my-org: -# existingSecret: my-org-webhook -# existingSecretKey: secret - serviceAccount: create: true name: "" annotations: {} + # Cattery does not call the Kubernetes API. Keep disabled unless you need it. + automountServiceAccountToken: false deploymentAnnotations: {} podAnnotations: {} @@ -96,13 +92,37 @@ podLabels: {} podSecurityContext: {} # fsGroup: 65532 +# The upstream image runs as the non-root `cattery` user (uid 65532). +# These are recommended hardening flags — enable them if your providers +# don't need any extra capabilities. securityContext: {} # allowPrivilegeEscalation: false # readOnlyRootFilesystem: true # runAsNonRoot: true # runAsUser: 65532 + # runAsGroup: 65532 # capabilities: # drop: ["ALL"] + # seccompProfile: + # type: RuntimeDefault + +# Extra environment variables (e.g. to override config via env, for debugging). +env: [] +# env: +# - name: TZ +# value: UTC + +# envFrom entries — handy for pulling MongoDB URIs or provider credentials +# out of existing ConfigMaps / Secrets without embedding them in values. +envFrom: [] +# envFrom: +# - secretRef: +# name: cattery-runtime + +# Extra volumes/volumeMounts for anything `secretFiles` does not cover +# (e.g. hostPath for Docker socket, projected tokens, extra ConfigMaps). +extraVolumes: [] +extraVolumeMounts: [] service: type: ClusterIP From 30148eb836c1315f194088afb01bbe1448a79d68 Mon Sep 17 00:00:00 2001 From: Evgeny Snitko Date: Wed, 22 Apr 2026 13:37:15 +0400 Subject: [PATCH 4/6] chart release --- .github/workflows/chart-release.yml | 128 ++++++++++++++++++++++++++++ charts/cattery/Chart.yaml | 4 +- 2 files changed, 130 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/chart-release.yml diff --git a/.github/workflows/chart-release.yml b/.github/workflows/chart-release.yml new file mode 100644 index 0000000..cae79f5 --- /dev/null +++ b/.github/workflows/chart-release.yml @@ -0,0 +1,128 @@ +name: chart-release + +on: + pull_request: + paths: + - 'charts/**' + - '.github/workflows/chart-release.yml' + push: + branches: + - main + paths: + - 'charts/**' + - '.github/workflows/chart-release.yml' + +permissions: + contents: read + packages: write + +env: + CHART_PATH: charts/cattery + CHART_NAME: cattery + REGISTRY: ghcr.io + # Chart lands at: oci://ghcr.io//charts/cattery + OCI_NAMESPACE: ${{ github.repository_owner }}/charts + HELM_VERSION: v3.16.3 + +jobs: + lint: + name: lint & template + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: azure/setup-helm@v4 + with: + version: ${{ env.HELM_VERSION }} + + - name: helm lint + run: helm lint "${CHART_PATH}" + + - name: helm template (defaults) + run: helm template smoke "${CHART_PATH}" > /dev/null + + - name: helm template (smoke values) + run: helm template smoke "${CHART_PATH}" -f "${CHART_PATH}/ci/smoke-values.yaml" > /dev/null + + check-version: + name: check for version bump + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + needs: lint + runs-on: ubuntu-latest + outputs: + bumped: ${{ steps.v.outputs.bumped }} + version: ${{ steps.v.outputs.version }} + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 2 + + - uses: azure/setup-helm@v4 + with: + version: ${{ env.HELM_VERSION }} + + - name: compare Chart.yaml version with previous commit + id: v + run: | + set -euo pipefail + CURRENT=$(helm show chart "${CHART_PATH}" | awk '/^version:/ {print $2}') + PREVIOUS=$(git show "HEAD^:${CHART_PATH}/Chart.yaml" 2>/dev/null | awk '/^version:/ {print $2}' || true) + + echo "current=${CURRENT}" + echo "previous=${PREVIOUS:-}" + + if [ -z "${CURRENT}" ]; then + echo "::error::Could not read version from ${CHART_PATH}/Chart.yaml" + exit 1 + fi + + if [ "${CURRENT}" = "${PREVIOUS}" ]; then + echo "Chart version unchanged (${CURRENT}); skipping publish." + echo "bumped=false" >> "${GITHUB_OUTPUT}" + else + echo "Chart version bumped: ${PREVIOUS:-} -> ${CURRENT}" + echo "bumped=true" >> "${GITHUB_OUTPUT}" + fi + echo "version=${CURRENT}" >> "${GITHUB_OUTPUT}" + + publish: + name: package & push to ghcr.io + needs: check-version + if: needs.check-version.outputs.bumped == 'true' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: azure/setup-helm@v4 + with: + version: ${{ env.HELM_VERSION }} + + - name: helm package + run: | + mkdir -p dist + helm package "${CHART_PATH}" --destination dist/ + + - name: helm registry login + run: | + echo "${{ secrets.GITHUB_TOKEN }}" | \ + helm registry login "${REGISTRY}" \ + --username "${{ github.actor }}" \ + --password-stdin + + - name: helm push + run: | + helm push \ + "dist/${CHART_NAME}-${{ needs.check-version.outputs.version }}.tgz" \ + "oci://${REGISTRY}/${OCI_NAMESPACE}" + + - name: summary + run: | + cat <> "${GITHUB_STEP_SUMMARY}" + Published \`${CHART_NAME}\` chart \`${{ needs.check-version.outputs.version }}\` to: + + oci://${REGISTRY}/${OCI_NAMESPACE}/${CHART_NAME} + + Install with: + + helm install ${CHART_NAME} oci://${REGISTRY}/${OCI_NAMESPACE}/${CHART_NAME} --version ${{ needs.check-version.outputs.version }} + EOF diff --git a/charts/cattery/Chart.yaml b/charts/cattery/Chart.yaml index 350f27f..72923bd 100644 --- a/charts/cattery/Chart.yaml +++ b/charts/cattery/Chart.yaml @@ -2,8 +2,8 @@ apiVersion: v2 name: cattery description: Scheduler and lifecycle manager for GitHub Actions self-hosted runners type: application -version: 0.1.0 -appVersion: "0.0.1" +version: 0.2.0 +appVersion: "0.2.0" home: https://github.com/paritytech/cattery sources: - https://github.com/paritytech/cattery From 73daaa51c4d3c53b814d96dcfb338642fb74dd93 Mon Sep 17 00:00:00 2001 From: Evgeny Snitko Date: Wed, 22 Apr 2026 13:48:30 +0400 Subject: [PATCH 5/6] build on pr --- .github/workflows/build.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 43ed065..1b8b82b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -86,17 +86,18 @@ jobs: docker-build: needs: [tests] - if: github.event_name == 'workflow_dispatch' || contains(github.event.pull_request.labels.*.name, 'image-push') || github.event_name == 'push' runs-on: parity-ubuntu environment: ${{ github.event_name == 'workflow_dispatch' && 'main' || null }} env: REGISTRY_PATH: docker.io/${{ github.event_name == 'workflow_dispatch' && 'paritytech' || 'paritypr' }}/cattery VERSION: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.tag_name || (github.event.pull_request.head.sha || github.sha) }} EXTRA_TAG: ${{ github.event_name == 'workflow_dispatch' && 'latest' || 'main' }} + PUSH: ${{ github.event_name == 'workflow_dispatch' || contains(github.event.pull_request.labels.*.name, 'image-push') || github.event_name == 'push' }} steps: - uses: actions/checkout@v4 - name: Docker login + if: env.PUSH == 'true' uses: docker/login-action@v3 with: username: ${{ secrets.REGISTRY_USER }} @@ -110,6 +111,6 @@ jobs: tags: | ${{ env.REGISTRY_PATH }}:${{ env.VERSION }} ${{ env.REGISTRY_PATH }}:${{ env.EXTRA_TAG }} - push: true + push: ${{ env.PUSH }} build-args: | CATTERY_VERSION=${{ env.VERSION }} From 634e100bf98b480b6da0a2a2af22be9c85b1015d Mon Sep 17 00:00:00 2001 From: Evgeny Snitko Date: Wed, 22 Apr 2026 13:56:00 +0400 Subject: [PATCH 6/6] dockerignore --- .dockerignore | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/.dockerignore b/.dockerignore index 31d11c5..d9b2942 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,11 +1,12 @@ -Dockerfile -bin/ -examples/ -README.MD -LICENCE -.github/ -.git/ -docs -tests -.dockerignore -.gitignore \ No newline at end of file +# Deny-by-default. Ship only what the Dockerfile actually reads. + +* + +# Allow the Go source tree +!src +!src/** + +# Trim cruft that sneaks into src/ +**/coverage.out +**/*.test +**/.DS_Store