Add maintained Asciinema Helm chart
Validate, publish and promote Asciinema chart / publish (push) Successful in 5m0s
Validate, publish and promote Asciinema chart / validate (push) Successful in 2m53s

This commit is contained in:
feedc0de committed 2026-10-18 23:42:36 +02:00
1 parent bf9f7c3041
commit 16c65f8bf2
14 files changed
+611 -194

No files matched your search

+86
View File
@@ -0,0 +1,86 @@
name: Validate, publish and promote Asciinema chart
on:
push:
pull_request:
env:
HELM_VERSION: v4.2.2
KUBECONFORM_VERSION: v0.7.0
GITOPS_REPOSITORY: feedc0de/homelab-gitops
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: azure/setup-helm@v4
with:
version: ${{ env.HELM_VERSION }}
- name: Lint, render and validate the chart
run: ./test.sh
publish:
if: gitea.ref == 'refs/heads/main'
needs: validate
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: azure/setup-helm@v4
with:
version: ${{ env.HELM_VERSION }}
- name: Select immutable chart version
run: printf 'CHART_VERSION=%s\n' "$(./scripts/chart-version.sh)" >> "$GITHUB_ENV"
- name: Package and publish chart
env:
PACKAGE_USERNAME: ${{ secrets.PACKAGE_USERNAME }}
PACKAGE_TOKEN: ${{ secrets.PACKAGE_TOKEN }}
run: |
set -euo pipefail
test -n "$PACKAGE_USERNAME"
test -n "$PACKAGE_TOKEN"
mkdir -p .chart-packages
helm package chart/asciinema --destination .chart-packages --version "$CHART_VERSION"
curl --fail --silent --show-error --user "$PACKAGE_USERNAME:$PACKAGE_TOKEN" \
--request POST --upload-file ".chart-packages/asciinema-$CHART_VERSION.tgz" \
https://code.brunner.ninja/api/packages/feedc0de/helm/api/charts
- name: Check that this is still current main
id: current-main
env:
COMMIT_SHA: ${{ gitea.sha }}
run: |
git fetch --no-tags origin main
if [ "$(git rev-parse FETCH_HEAD)" = "$COMMIT_SHA" ]; then
echo 'promote=true' >> "$GITEA_OUTPUT"
else
echo 'promote=false' >> "$GITEA_OUTPUT"
fi
- name: Check out GitOps repository
if: steps.current-main.outputs.promote == 'true'
uses: actions/checkout@v4
with:
repository: ${{ env.GITOPS_REPOSITORY }}
ref: main
token: ${{ secrets.GITOPS_TOKEN }}
path: homelab-gitops
- name: Commit chart promotion
if: steps.current-main.outputs.promote == 'true'
working-directory: homelab-gitops
run: |
set -euo pipefail
./scripts/update-chart-version.sh asciinema "$CHART_VERSION"
git config user.name feedc0de
git config user.email feedc0de@users.noreply.code.brunner.ninja
git add bootstrap/asciinema.yaml
if git diff --cached --quiet; then exit 0; fi
git commit -m "Deploy asciinema chart $CHART_VERSION"
for attempt in 1 2 3; do
git push origin HEAD:main && exit 0
[ "$attempt" = 3 ] && exit 1
git fetch origin main
git rebase origin/main
done
-139
View File
@@ -1,139 +0,0 @@
name: Validate and deploy asciinema
on:
push:
pull_request:
env:
KUBECTL_VERSION: v1.36.3
KUBERNETES_API: https://host.containers.internal:6443
KUBERNETES_TLS_SERVER_NAME: 192.168.0.2
NAMESPACE: asciinema
jobs:
validate:
name: Validate manifests
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- name: Check out source
uses: actions/checkout@v4
- name: Validate scripts and Kubernetes resources
run: bash test.sh
deploy:
name: Deploy to Kubernetes
if: gitea.event_name == 'push' && gitea.ref == 'refs/heads/main'
needs: validate
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- name: Check out source
uses: actions/checkout@v4
- name: Install kubectl
run: |
curl --fail --silent --show-error --location \
--output "${RUNNER_TEMP}/kubectl" \
"https://dl.k8s.io/release/${KUBECTL_VERSION}/bin/linux/amd64/kubectl"
chmod 0700 "${RUNNER_TEMP}/kubectl"
- name: Configure Kubernetes access
env:
KUBE_CONFIG_BASE64: ${{ secrets.KUBE_CONFIG_BASE64 }}
run: |
test -n "${KUBE_CONFIG_BASE64}"
printf '%s' "${KUBE_CONFIG_BASE64}" | base64 --decode > "${RUNNER_TEMP}/kubeconfig"
chmod 0600 "${RUNNER_TEMP}/kubeconfig"
export KUBECONFIG="${RUNNER_TEMP}/kubeconfig"
"${RUNNER_TEMP}/kubectl" config set-cluster cluster \
--server="${KUBERNETES_API}" \
--tls-server-name="${KUBERNETES_TLS_SERVER_NAME}"
- name: Validate desired state on the server
run: |
export KUBECONFIG="${RUNNER_TEMP}/kubeconfig"
"${RUNNER_TEMP}/kubectl" apply \
--dry-run=server \
--validate=false \
--filename infrastructure.yaml
"${RUNNER_TEMP}/kubectl" apply \
--dry-run=server \
--validate=false \
--filename asciinema.yaml
- name: Apply and verify
run: |
set -euo pipefail
export KUBECONFIG="${RUNNER_TEMP}/kubeconfig"
"${RUNNER_TEMP}/kubectl" apply \
--validate=false \
--filename infrastructure.yaml
for attempt in {1..120}; do
IFS='|' read -r generation observed desired current ready updated <<< "$(
"${RUNNER_TEMP}/kubectl" --namespace "${NAMESPACE}" \
get statefulset/postgresql \
--output=jsonpath='{.metadata.generation}|{.status.observedGeneration}|{.spec.replicas}|{.status.currentReplicas}|{.status.readyReplicas}|{.status.updatedReplicas}'
)"
echo "PostgreSQL ${attempt}/120: generation ${observed}/${generation}, replicas ${updated}/${desired} updated, ${current}/${desired} current, ${ready}/${desired} ready"
if [[ "${observed}" == "${generation}" \
&& "${updated}" == "${desired}" \
&& "${current}" == "${desired}" \
&& "${ready}" == "${desired}" ]]; then
break
fi
if [[ "${attempt}" == 120 ]]; then
echo "PostgreSQL rollout did not complete within ten minutes" >&2
exit 1
fi
sleep 5
done
"${RUNNER_TEMP}/kubectl" apply \
--validate=false \
--filename asciinema.yaml
# ConfigMap and Secret-backed environment variables are read only at
# process start, so every successful deployment recreates the sole pod.
"${RUNNER_TEMP}/kubectl" --namespace "${NAMESPACE}" \
rollout restart deployment/asciinema
for attempt in {1..120}; do
IFS='|' read -r generation observed desired updated ready available unavailable image <<< "$(
"${RUNNER_TEMP}/kubectl" --namespace "${NAMESPACE}" \
get deployment/asciinema \
--output=jsonpath='{.metadata.generation}|{.status.observedGeneration}|{.spec.replicas}|{.status.updatedReplicas}|{.status.readyReplicas}|{.status.availableReplicas}|{.status.unavailableReplicas}|{.spec.template.spec.containers[0].image}'
)"
echo "asciinema ${attempt}/120: generation ${observed}/${generation}, replicas ${updated}/${desired} updated, ${ready}/${desired} ready, ${available}/${desired} available, image ${image}"
if [[ "${observed}" == "${generation}" \
&& "${updated}" == "${desired}" \
&& "${ready}" == "${desired}" \
&& "${available}" == "${desired}" \
&& ( -z "${unavailable}" || "${unavailable}" == "0" ) ]]; then
break
fi
if [[ "${attempt}" == 120 ]]; then
echo "asciinema rollout did not complete within ten minutes" >&2
exit 1
fi
sleep 5
done
for attempt in {1..30}; do
if curl --fail --silent --show-error --max-time 15 \
https://asciinema.brunner.ninja/ >/dev/null; then
echo "asciinema deployment is healthy"
exit 0
fi
echo "Public health check ${attempt}/30 failed; retrying"
sleep 5
done
echo "Public health check did not pass" >&2
exit 1
+6
View File
@@ -0,0 +1,6 @@
apiVersion: v2
name: asciinema
description: Maintained Kubernetes adapter chart for the asciinema server image
type: application
version: 0.0.0
appVersion: "20260626"
+20
View File
@@ -0,0 +1,20 @@
{{- define "asciinema.name" -}}
{{- default .Chart.Name .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- define "asciinema.appName" -}}
{{- include "asciinema.name" . }}
{{- end }}
{{- define "asciinema.postgresqlName" -}}
{{- .Values.postgresql.name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- define "asciinema.labels" -}}
app.kubernetes.io/name: {{ include "asciinema.appName" . }}
{{- end }}
{{- define "asciinema.postgresqlLabels" -}}
app.kubernetes.io/name: postgresql
app.kubernetes.io/part-of: {{ include "asciinema.appName" . }}
{{- end }}
+147
View File
@@ -0,0 +1,147 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "asciinema.appName" . }}
labels:
{{- include "asciinema.labels" . | nindent 4 }}
spec:
replicas: {{ .Values.replicaCount }}
strategy:
type: Recreate
selector:
matchLabels:
{{- include "asciinema.labels" . | nindent 6 }}
template:
metadata:
labels:
{{- include "asciinema.labels" . | nindent 8 }}
spec:
automountServiceAccountToken: false
terminationGracePeriodSeconds: 30
securityContext:
runAsNonRoot: true
runAsUser: 10000
runAsGroup: 0
fsGroup: 0
fsGroupChangePolicy: OnRootMismatch
seccompProfile:
type: RuntimeDefault
containers:
- name: asciinema
image: {{ printf "%s:%s" .Values.image.repository (default .Chart.AppVersion .Values.image.tag) | quote }}
imagePullPolicy: {{ .Values.image.pullPolicy }}
envFrom:
- configMapRef:
name: {{ include "asciinema.appName" . }}
env:
- name: HOME
value: /tmp
- name: CACHE_PATH
value: /var/cache/asciinema
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: {{ .Values.secret.existingSecret }}
key: {{ .Values.secret.keys.databaseUrl }}
- name: SECRET_KEY_BASE
valueFrom:
secretKeyRef:
name: {{ .Values.secret.existingSecret }}
key: {{ .Values.secret.keys.secretKeyBase }}
- name: RELEASE_COOKIE
valueFrom:
secretKeyRef:
name: {{ .Values.secret.existingSecret }}
key: {{ .Values.secret.keys.releaseCookie }}
- name: S3_ACCESS_KEY_ID
valueFrom:
secretKeyRef:
name: {{ .Values.secret.existingSecret }}
key: {{ .Values.secret.keys.s3AccessKeyId }}
- name: S3_SECRET_ACCESS_KEY
valueFrom:
secretKeyRef:
name: {{ .Values.secret.existingSecret }}
key: {{ .Values.secret.keys.s3SecretAccessKey }}
ports:
- name: http
containerPort: 4000
protocol: TCP
startupProbe:
httpGet: {path: /, port: http}
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 60
readinessProbe:
httpGet: {path: /, port: http}
periodSeconds: 10
timeoutSeconds: 3
failureThreshold: 3
livenessProbe:
tcpSocket: {port: http}
initialDelaySeconds: 30
periodSeconds: 30
timeoutSeconds: 3
failureThreshold: 3
resources:
{{- toYaml .Values.resources | nindent 12 }}
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop: [ALL]
readOnlyRootFilesystem: true
volumeMounts:
- name: cache
mountPath: /var/cache/asciinema
- name: tmp
mountPath: /tmp
volumes:
- name: cache
emptyDir:
sizeLimit: {{ .Values.cache.sizeLimit }}
- name: tmp
emptyDir:
sizeLimit: {{ .Values.tmp.sizeLimit }}
---
apiVersion: v1
kind: Service
metadata:
name: {{ .Values.service.name }}
labels:
{{- include "asciinema.labels" . | nindent 4 }}
spec:
selector:
{{- include "asciinema.labels" . | nindent 4 }}
ports:
- name: http
port: {{ .Values.service.port }}
targetPort: http
protocol: TCP
{{- if .Values.ingress.enabled }}
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: {{ include "asciinema.appName" . }}
labels:
{{- include "asciinema.labels" . | nindent 4 }}
{{- with .Values.ingress.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
{{- with .Values.ingress.className }}
ingressClassName: {{ . }}
{{- end }}
rules:
- host: {{ required "ingress.host is required when ingress is enabled" .Values.ingress.host }}
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: {{ .Values.service.name }}
port:
name: http
{{- end }}
@@ -0,0 +1,43 @@
{{- if .Values.bucketBootstrap.enabled }}
apiVersion: batch/v1
kind: Job
metadata:
name: {{ include "asciinema.appName" . }}-bucket-bootstrap
labels:
{{- include "asciinema.labels" . | nindent 4 }}
annotations:
helm.sh/hook: post-install,post-upgrade
helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded
spec:
backoffLimit: 5
template:
spec:
restartPolicy: OnFailure
automountServiceAccountToken: false
containers:
- name: bucket
image: {{ .Values.bucketBootstrap.image }}
command:
- /bin/sh
- -ec
- >-
aws --endpoint-url "$S3_ENDPOINT" s3api head-bucket --bucket "$S3_BUCKET" >/dev/null 2>&1 ||
aws --endpoint-url "$S3_ENDPOINT" s3api create-bucket --bucket "$S3_BUCKET" >/dev/null
env:
- name: S3_ENDPOINT
value: {{ .Values.config.s3.endpoint | quote }}
- name: S3_BUCKET
value: {{ .Values.config.s3.bucket | quote }}
- name: AWS_DEFAULT_REGION
value: {{ .Values.config.s3.region | quote }}
- name: AWS_ACCESS_KEY_ID
valueFrom:
secretKeyRef:
name: {{ .Values.secret.existingSecret }}
key: {{ .Values.secret.keys.s3AccessKeyId }}
- name: AWS_SECRET_ACCESS_KEY
valueFrom:
secretKeyRef:
name: {{ .Values.secret.existingSecret }}
key: {{ .Values.secret.keys.s3SecretAccessKey }}
{{- end }}
+27
View File
@@ -0,0 +1,27 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ include "asciinema.appName" . }}
labels:
{{- include "asciinema.labels" . | nindent 4 }}
data:
URL_HOST: {{ .Values.config.urlHost | quote }}
URL_SCHEME: {{ .Values.config.urlScheme | quote }}
TZ: {{ .Values.config.timezone | quote }}
DB_POOL_SIZE: {{ .Values.config.dbPoolSize | quote }}
S3_BUCKET: {{ .Values.config.s3.bucket | quote }}
S3_ENDPOINT: {{ .Values.config.s3.endpoint | quote }}
S3_REGION: {{ .Values.config.s3.region | quote }}
SMTP_HOST: {{ .Values.config.smtp.host | quote }}
SMTP_PORT: {{ .Values.config.smtp.port | quote }}
SMTP_TLS: {{ .Values.config.smtp.tls | quote }}
MAIL_FROM_ADDRESS: {{ .Values.config.smtp.fromAddress | quote }}
MAIL_REPLY_TO_ADDRESS: {{ .Values.config.smtp.replyToAddress | quote }}
CONTACT_EMAIL_ADDRESS: {{ .Values.config.contactEmailAddress | quote }}
ADMIN_PANEL_ON_MAIN_ENDPOINT: {{ .Values.config.adminPanelOnMainEndpoint | quote }}
ADMIN_BIND_ALL: {{ .Values.config.adminBindAll | quote }}
UPLOAD_AUTH_REQUIRED: {{ .Values.config.uploadAuthRequired | quote }}
UNREGISTERED_UPLOAD_COUNT_LIMIT: {{ .Values.config.unregisteredUploadCountLimit | quote }}
DEFAULT_RECORDING_VISIBILITY: {{ .Values.config.defaultRecordingVisibility | quote }}
DEFAULT_STREAM_VISIBILITY: {{ .Values.config.defaultStreamVisibility | quote }}
SIGN_UP_DISABLED: {{ .Values.config.signUpDisabled | quote }}
+126
View File
@@ -0,0 +1,126 @@
apiVersion: v1
kind: Service
metadata:
name: {{ include "asciinema.postgresqlName" . }}
labels:
{{- include "asciinema.postgresqlLabels" . | nindent 4 }}
spec:
clusterIP: None
selector:
{{- include "asciinema.postgresqlLabels" . | nindent 4 }}
ports:
- name: postgresql
port: 5432
targetPort: postgresql
protocol: TCP
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: {{ include "asciinema.postgresqlName" . }}
labels:
{{- include "asciinema.postgresqlLabels" . | nindent 4 }}
spec:
serviceName: {{ include "asciinema.postgresqlName" . }}
replicas: 1
selector:
matchLabels:
{{- include "asciinema.postgresqlLabels" . | nindent 6 }}
template:
metadata:
labels:
{{- include "asciinema.postgresqlLabels" . | nindent 8 }}
spec:
automountServiceAccountToken: false
terminationGracePeriodSeconds: 120
securityContext:
runAsNonRoot: true
runAsUser: 70
runAsGroup: 70
fsGroup: 70
fsGroupChangePolicy: OnRootMismatch
seccompProfile:
type: RuntimeDefault
containers:
- name: postgresql
image: {{ printf "%s:%s" .Values.postgresql.image.repository .Values.postgresql.image.tag | quote }}
imagePullPolicy: {{ .Values.postgresql.image.pullPolicy }}
env:
- name: POSTGRES_DB
value: {{ .Values.postgresql.database | quote }}
- name: POSTGRES_USER
value: {{ .Values.postgresql.username | quote }}
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: {{ .Values.secret.existingSecret }}
key: {{ .Values.secret.keys.databasePassword }}
- name: POSTGRES_INITDB_ARGS
value: --auth-host=scram-sha-256
- name: PGDATA
value: /var/lib/postgresql/data/pgdata
ports:
- name: postgresql
containerPort: 5432
protocol: TCP
startupProbe:
exec:
command: [pg_isready, --username={{ .Values.postgresql.username }}, --dbname={{ .Values.postgresql.database }}]
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 60
readinessProbe:
exec:
command: [pg_isready, --username={{ .Values.postgresql.username }}, --dbname={{ .Values.postgresql.database }}]
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 3
livenessProbe:
exec:
command: [pg_isready, --username={{ .Values.postgresql.username }}, --dbname={{ .Values.postgresql.database }}]
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 3
failureThreshold: 6
resources:
{{- toYaml .Values.postgresql.resources | nindent 12 }}
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop: [ALL]
readOnlyRootFilesystem: false
volumeMounts:
- name: data
mountPath: /var/lib/postgresql/data
- name: run
mountPath: /var/run/postgresql
- name: tmp
mountPath: /tmp
volumes:
- name: run
emptyDir:
medium: Memory
sizeLimit: 16Mi
- name: tmp
emptyDir:
sizeLimit: 64Mi
{{- if .Values.postgresql.persistence.existingClaim }}
- name: data
persistentVolumeClaim:
claimName: {{ .Values.postgresql.persistence.existingClaim }}
{{- end }}
{{- if not .Values.postgresql.persistence.existingClaim }}
volumeClaimTemplates:
- metadata:
name: data
labels:
{{- include "asciinema.postgresqlLabels" . | nindent 10 }}
spec:
accessModes: [ReadWriteOnce]
{{- with .Values.postgresql.persistence.storageClass }}
storageClassName: {{ . }}
{{- end }}
resources:
requests:
storage: {{ .Values.postgresql.persistence.size }}
{{- end }}
+20
View File
@@ -0,0 +1,20 @@
{
"$schema": "https://json-schema.org/draft-07/schema#",
"type": "object",
"additionalProperties": false,
"properties": {
"fullnameOverride": {"type": "string"},
"image": {"type": "object"},
"replicaCount": {"type": "integer", "minimum": 1},
"secret": {"type": "object"},
"config": {"type": "object"},
"service": {"type": "object"},
"ingress": {"type": "object"},
"postgresql": {"type": "object"},
"resources": {"type": "object"},
"cache": {"type": "object"},
"tmp": {"type": "object"},
"bucketBootstrap": {"type": "object"},
"global": {"type": "object"}
}
}
+93
View File
@@ -0,0 +1,93 @@
fullnameOverride: ""
image:
repository: ghcr.io/asciinema/asciinema-server
tag: ""
pullPolicy: IfNotPresent
replicaCount: 1
secret:
existingSecret: asciinema-secrets
keys:
databaseUrl: database-url
databasePassword: database-password
secretKeyBase: secret-key-base
releaseCookie: release-cookie
s3AccessKeyId: s3-access-key-id
s3SecretAccessKey: s3-secret-access-key
config:
urlHost: asciinema.example.com
urlScheme: https
timezone: UTC
dbPoolSize: "5"
s3:
bucket: asciinema
endpoint: ""
region: us-east-1
smtp:
host: ""
port: "587"
tls: if_available
fromAddress: ""
replyToAddress: ""
contactEmailAddress: ""
adminPanelOnMainEndpoint: true
adminBindAll: false
uploadAuthRequired: true
unregisteredUploadCountLimit: "0"
defaultRecordingVisibility: unlisted
defaultStreamVisibility: unlisted
signUpDisabled: false
service:
name: asciinema
port: 80
ingress:
enabled: false
className: ""
annotations: {}
host: ""
postgresql:
name: postgresql
image:
repository: postgres
tag: 17.11-alpine
pullPolicy: IfNotPresent
database: asciinema
username: asciinema
persistence:
storageClass: ""
size: 8Gi
existingClaim: ""
resources:
requests:
cpu: 50m
memory: 128Mi
ephemeral-storage: 64Mi
limits:
cpu: "1"
memory: 1Gi
ephemeral-storage: 1Gi
resources:
requests:
cpu: 50m
memory: 256Mi
ephemeral-storage: 128Mi
limits:
cpu: "1"
memory: 1Gi
ephemeral-storage: 3Gi
cache:
sizeLimit: 2Gi
tmp:
sizeLimit: 512Mi
bucketBootstrap:
enabled: false
image: amazon/aws-cli:2.28.22
+16
View File
@@ -0,0 +1,16 @@
fullnameOverride: asciinema
config:
urlHost: asciinema.example.com
s3:
endpoint: http://object-store.example.svc.cluster.local
smtp:
host: smtp.example.com
fromAddress: asciinema@example.com
replyToAddress: admin@example.com
contactEmailAddress: admin@example.com
ingress:
enabled: true
className: traefik
host: asciinema.example.com
+1 -1
View File
@@ -50,7 +50,7 @@ spec:
type: RuntimeDefault
containers:
- name: postgresql
image: postgres:17.6-alpine
image: postgres:17.11-alpine
imagePullPolicy: IfNotPresent
env:
- name: POSTGRES_DB
+13
View File
@@ -0,0 +1,13 @@
#!/usr/bin/env bash
set -euo pipefail
description=$(git describe --tags --long --match 'v[0-9]*' --dirty)
case "$description" in
*-dirty) echo 'Refusing to package a dirty tree.' >&2; exit 1 ;;
esac
version=${description#v}
case "$version" in
*-0-g*) version=${version%%-0-g*} ;;
esac
printf '%s\n' "$version"
+13 -54
View File
@@ -1,61 +1,20 @@
#!/usr/bin/env bash
set -euo pipefail
project_dir=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)
chart="$project_dir/chart/asciinema"
bash -n "${project_dir}/install.sh"
bash -n "${project_dir}/create-ci-kubeconfig.sh"
bash -n "${project_dir}/test.sh"
bash -n "$project_dir/test.sh" "$project_dir/scripts/chart-version.sh"
helm lint "$chart"
helm template asciinema "$chart" --namespace asciinema --values "$project_dir/examples/values.yaml" > /tmp/asciinema-rendered.yaml
if ! grep -Fq 'storageClassName: rook-ceph-block-ec' "${project_dir}/infrastructure.yaml"; then
echo "The dedicated PostgreSQL claim must use retained Ceph block storage." >&2
exit 1
grep -Fq 'name: data' /tmp/asciinema-rendered.yaml
grep -Fq 'name: asciinema-secrets' /tmp/asciinema-rendered.yaml
grep -Fq 'kind: StatefulSet' /tmp/asciinema-rendered.yaml
grep -Fq 'kind: Ingress' /tmp/asciinema-rendered.yaml
if command -v kubeconform >/dev/null 2>&1; then
kubeconform -strict -summary /tmp/asciinema-rendered.yaml
elif command -v docker >/dev/null 2>&1; then
docker run --rm --interactive ghcr.io/yannh/kubeconform:v0.7.0 -strict -summary < /tmp/asciinema-rendered.yaml
fi
if grep -Eqi 'redis|valkey' \
"${project_dir}/asciinema.yaml" "${project_dir}/bootstrap.yaml"; then
echo "asciinema should not use Redis/Valkey." >&2
exit 1
fi
if grep -Eq 'S3_PUBLIC_ENDPOINT|SplitEndpointS3|asciinema-custom-config' \
"${project_dir}/asciinema.yaml"; then
echo "asciinema must use the cluster-local RGW endpoint for server-side reads." >&2
exit 1
fi
if ! grep -Fq 'S3_ENDPOINT: http://rook-ceph-rgw-my-store.rook-ceph.svc.cluster.local' \
"${project_dir}/asciinema.yaml"; then
echo "asciinema must use the cluster-local RGW Service." >&2
exit 1
fi
validate_file() {
local manifest=$1
shift
if command -v kubeconform >/dev/null 2>&1; then
kubeconform -strict -summary "$@" "${manifest}"
elif command -v docker >/dev/null 2>&1; then
docker run --rm --interactive \
ghcr.io/yannh/kubeconform:v0.7.0 \
-strict -summary "$@" < "${manifest}"
elif command -v podman >/dev/null 2>&1; then
podman run --rm --interactive \
ghcr.io/yannh/kubeconform:v0.7.0 \
-strict -summary "$@" < "${manifest}"
else
echo "Required command not found: install kubeconform, Docker, or Podman" >&2
exit 1
fi
}
validate_file "${project_dir}/asciinema.yaml"
validate_file "${project_dir}/bootstrap.yaml"
validate_file "${project_dir}/infrastructure.yaml"
validate_file "${project_dir}/namespace.yaml"
validate_file "${project_dir}/ceph-object-user.yaml" -ignore-missing-schemas
validate_file "${project_dir}/ci-deployer.yaml"
echo "asciinema scripts and Kubernetes resources are valid."