Add maintained Asciinema Helm chart
This commit is contained in:
1 parent
bf9f7c3041
commit
16c65f8bf2
14 files changed
+611
-194
No files matched your search
@@ -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
|
||||
@@ -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
|
||||
@@ -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"
|
||||
@@ -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 }}
|
||||
@@ -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 }}
|
||||
@@ -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 }}
|
||||
@@ -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 }}
|
||||
@@ -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"}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
@@ -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
|
||||
|
||||
Executable
+13
@@ -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"
|
||||
@@ -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."
|
||||
Reference in new issue
Block a user