Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/publish-oci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ jobs:

- name: Set up Helm
uses: azure/setup-helm@v4
with:
version: v3.16.4

- name: Add dependency repos
run: |
Expand Down
2 changes: 1 addition & 1 deletion drupal/Chart.lock
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,4 @@ dependencies:
repository: file://../silta-release
version: 1.0.1
digest: sha256:213c0b37cfaeb7c5416d7390912e735ad4eef9d2c798431034b492f2e2b377c8
generated: "2025-08-19T09:50:46.780411246+03:00"
generated: "2026-05-18T13:59:59.0730673+03:00"
84 changes: 1 addition & 83 deletions drupal/templates/NOTES.txt
Original file line number Diff line number Diff line change
@@ -1,83 +1 @@
{{ $protocol := .Values.ingress.default.tls | ternary "https" "http" -}}
Your site is available at:

{{ $protocol }}://{{- template "drupal.domain" . }}
{{- range $index, $prefix := .Values.domainPrefixes }}
{{- $params := dict "prefix" $prefix }}
{{ $protocol}}://{{ template "drupal.domain" (merge $params $ ) }}
{{- end }}
{{- range $index, $domain := .Values.exposeDomains }}
{{- if $domain.ssl }}
{{- if $domain.ssl.enabled }}
https://{{ $domain.hostname }}
{{- end }}
{{- else }}
http://{{ $domain.hostname }}
{{- end }}
{{- end }}

{{- if .Values.mailhog.enabled }}

Mailhog available at:

http://{{- template "drupal.domain" . }}/mailhog
{{- range $index, $domain := .Values.exposeDomains }}
http://{{ $domain.hostname }}/mailhog
{{- end }}
⚠️ **DEPRECATED** mailhog is deprecated and will be removed in the future, use mailpit instead
See: https://wunderio.github.io/silta/docs/silta-examples#sending-e-mail
{{- end }}

{{- if .Values.mailpit.enabled }}

Mailpit available at:

http://{{- template "drupal.domain" . }}/mailpit
{{- range $index, $domain := .Values.exposeDomains }}
http://{{ $domain.hostname }}/mailpit
{{- end }}
{{- end }}

{{- if .Values.nginx.basicauth.enabled }}

Basic access authentication credentials:

Username: {{ .Values.nginx.basicauth.credentials.username }}
Password: {{ .Values.nginx.basicauth.credentials.password }}
{{- end }}

{{- if .Values.shell.enabled }}

SSH connection (limited access through VPN):

ssh {{ include "drupal.shellHost" . }} -J {{ include "drupal.jumphost" . }}

Downloading data from server

Downloading database:
ssh {{ include "drupal.shellHost" . }} -J {{ include "drupal.jumphost" . }} "drush sql-dump" > {{ .Release.Namespace }}-{{ .Release.Name }}.sql

{{ range $index, $mount := .Values.mounts -}}
{{ if eq $mount.enabled true -}}
{{/* Ensure that the mount path is suffixed with a slash to download contents of the mount not the folder itself. */}}
{{- $mountPath := ternary $mount.mountPath (printf "%s/" $mount.mountPath) (hasSuffix "/" $mount.mountPath) -}}
Downloading files from {{ $index }}:
rsync -azv -e 'ssh -A -J {{ include "drupal.jumphost" $ }}' {{ include "drupal.shellHost" $ }}:{{ $mountPath }} {{ $.Release.Namespace }}-mounts/{{ $index }}
{{ end }}
{{ end -}}

Downloading any file or folder:
rsync -chavzP -e "ssh -A -J {{ include "drupal.jumphost" . }}" {{ include "drupal.shellHost" . }}:/app/remote-filename ./

Importing data into server (use this with caution!)

Importing database:
ssh {{ include "drupal.shellHost" . }} -J {{ include "drupal.jumphost" . }} "drush sql-cli" < {{ .Release.Namespace }}-{{ .Release.Name }}.sql

{{ range $index, $mount := .Values.mounts -}}
{{ if eq $mount.enabled true -}}
Uploading files to {{ $index }}:
rsync -azv --temp-dir=/tmp/ -e 'ssh -A -J {{ include "drupal.jumphost" $ }}' {{ $.Release.Namespace }}-mounts/{{ $index }}/ {{ include "drupal.shellHost" $ }}:{{ $mount.mountPath }}
{{ end }}
{{ end -}}
{{- end -}}
{{ include "drupal.deployment-notes" . }}
96 changes: 96 additions & 0 deletions drupal/templates/_helpers.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -849,3 +849,99 @@ autoscaling/v2beta1
{{- .Release.Name }}-sa
{{- end }}
{{- end }}

{{/*
Deployment notes — shared between NOTES.txt and the PR comment Job.
Outputs Markdown-formatted environment details.
*/}}
{{- define "drupal.deployment-notes" -}}
{{ $protocol := .Values.ingress.default.tls | ternary "https" "http" -}}
**Your site is available at:**

{{ $protocol }}://{{- template "drupal.domain" . }}
{{- range $index, $prefix := .Values.domainPrefixes }}
{{- $params := dict "prefix" $prefix }}
{{ $protocol}}://{{ template "drupal.domain" (merge $params $ ) }}
{{- end }}
{{- range $index, $domain := .Values.exposeDomains }}
{{- if $domain.ssl }}
{{- if $domain.ssl.enabled }}
https://{{ $domain.hostname }}
{{- end }}
{{- else }}
http://{{ $domain.hostname }}
{{- end }}
{{- end }}
{{- if .Values.mailhog.enabled }}

**Mailhog available at:**

http://{{- template "drupal.domain" . }}/mailhog
{{- range $index, $domain := .Values.exposeDomains }}
http://{{ $domain.hostname }}/mailhog
{{- end }}
> ⚠️ mailhog is deprecated — use mailpit instead.
> See: https://wunderio.github.io/silta/docs/silta-examples#sending-e-mail
{{- end }}
{{- if .Values.mailpit.enabled }}

**Mailpit available at:**

http://{{- template "drupal.domain" . }}/mailpit
{{- range $index, $domain := .Values.exposeDomains }}
http://{{ $domain.hostname }}/mailpit
{{- end }}
{{- end }}
{{- if .Values.nginx.basicauth.enabled }}

**Basic Auth:**

| | |
|---|---|
| Username | `{{ .Values.nginx.basicauth.credentials.username }}` |
| Password | `{{ .Values.nginx.basicauth.credentials.password }}` |
{{- end }}
{{- if .Values.shell.enabled }}

**SSH connection** (limited access through VPN):

```
ssh {{ include "drupal.shellHost" . }} -J {{ include "drupal.jumphost" . }}
```

<details>
<summary>Data transfer commands</summary>

**Downloading database:**
```
ssh {{ include "drupal.shellHost" . }} -J {{ include "drupal.jumphost" . }} "drush sql-dump" > {{ .Release.Namespace }}-{{ .Release.Name }}.sql
```
{{ range $index, $mount := .Values.mounts -}}
{{ if eq $mount.enabled true -}}
{{- $mountPath := ternary $mount.mountPath (printf "%s/" $mount.mountPath) (hasSuffix "/" $mount.mountPath) -}}
**Downloading files from {{ $index }}:**
```
rsync -azv -e 'ssh -A -J {{ include "drupal.jumphost" $ }}' {{ include "drupal.shellHost" $ }}:{{ $mountPath }} {{ $.Release.Namespace }}-mounts/{{ $index }}
```
{{ end }}
{{ end -}}
**Downloading any file or folder:**
```
rsync -chavzP -e "ssh -A -J {{ include "drupal.jumphost" . }}" {{ include "drupal.shellHost" . }}:/app/remote-filename ./
```

**Importing database** (use with caution!):
```
ssh {{ include "drupal.shellHost" . }} -J {{ include "drupal.jumphost" . }} "drush sql-cli" < {{ .Release.Namespace }}-{{ .Release.Name }}.sql
```
{{ range $index, $mount := .Values.mounts -}}
{{ if eq $mount.enabled true -}}
**Uploading files to {{ $index }}:**
```
rsync -azv --temp-dir=/tmp/ -e 'ssh -A -J {{ include "drupal.jumphost" $ }}' {{ $.Release.Namespace }}-mounts/{{ $index }}/ {{ include "drupal.shellHost" $ }}:{{ $mount.mountPath }}
```
{{ end }}
{{ end -}}
</details>
{{- end -}}
{{- end -}}
145 changes: 145 additions & 0 deletions drupal/templates/github-pr-comment.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
{{- if .Values.github.prComment.prNumber }}
---
# PostSync Job that posts deployment details as a GitHub PR comment.
# Only rendered for PR environments (prNumber is set by the platform chart).
# Best-effort: the script exits 0 even if the GitHub API call fails,
# so a comment failure does not mark the sync as failed.
apiVersion: batch/v1
kind: Job
metadata:
name: "{{ .Release.Name }}-pr-comment"
labels:
{{- include "drupal.release_labels" . | nindent 4 }}
annotations:
argocd.argoproj.io/hook: PostSync
argocd.argoproj.io/hook-delete-policy: BeforeHookCreation
spec:
activeDeadlineSeconds: 120
backoffLimit: 0
ttlSecondsAfterFinished: 300
template:
metadata:
labels:
{{- include "drupal.release_labels" . | nindent 8 }}
spec:
restartPolicy: Never
enableServiceLinks: false
containers:
- name: pr-comment
image: alpine:3.20
command: ["/bin/sh", "-c"]
args:
- |
set -e
apk add --no-cache curl openssl jq >/dev/null 2>&1

# --- GitHub App authentication ---
PRIVATE_KEY_FILE="/github-app/githubAppPrivateKey"

# Build JWT (RS256, valid 5 minutes)
NOW=$(date +%s)
IAT=$((NOW - 60))
EXP=$((NOW + 300))
HEADER=$(printf '{"alg":"RS256","typ":"JWT"}' | openssl base64 -e -A | tr '+/' '-_' | tr -d '=')
PAYLOAD=$(printf '{"iat":%d,"exp":%d,"iss":"%s"}' "$IAT" "$EXP" "$GITHUB_APP_ID" | openssl base64 -e -A | tr '+/' '-_' | tr -d '=')
SIGNATURE=$(printf '%s.%s' "$HEADER" "$PAYLOAD" | openssl dgst -sha256 -sign "$PRIVATE_KEY_FILE" | openssl base64 -e -A | tr '+/' '-_' | tr -d '=')
JWT="${HEADER}.${PAYLOAD}.${SIGNATURE}"

# Exchange JWT for installation access token
TOKEN=$(curl -sf -X POST \
-H "Authorization: Bearer ${JWT}" \
-H "Accept: application/vnd.github+json" \
"https://api.github.com/app/installations/${GITHUB_APP_INSTALLATION_ID}/access_tokens" \
| jq -r '.token') || {
echo "WARNING: Failed to get GitHub installation token. Skipping PR comment."
exit 0
}

if [ -z "$TOKEN" ] || [ "$TOKEN" = "null" ]; then
echo "WARNING: GitHub token is empty. Skipping PR comment."
exit 0
fi

# --- Post or update PR comment ---
REPO="{{ .Values.github.prComment.repository }}"
PR_NUMBER="{{ .Values.github.prComment.prNumber }}"
COMMENT_TAG="{{ .Values.github.prComment.commentTag }}-{{ .Release.Name }}"
MARKER="<!-- ${COMMENT_TAG} -->"

# Find existing comment by marker
EXISTING_ID=$(curl -sf \
-H "Authorization: token ${TOKEN}" \
-H "Accept: application/vnd.github+json" \
"https://api.github.com/repos/${REPO}/issues/${PR_NUMBER}/comments?per_page=100" \
| jq -r ".[] | select(.body | contains(\"${MARKER}\")) | .id" \
| head -1) || true

if [ -n "$EXISTING_ID" ] && [ "$EXISTING_ID" != "null" ]; then
curl -sf -X PATCH \
-H "Authorization: token ${TOKEN}" \
-H "Accept: application/vnd.github+json" \
-d "$(jq -n --rawfile body /comment/comment-body.txt '{body: $body}')" \
"https://api.github.com/repos/${REPO}/issues/${PR_NUMBER}/comments/${EXISTING_ID}" \
>/dev/null && echo "Updated existing PR comment." || echo "WARNING: Failed to update PR comment."
else
curl -sf -X POST \
-H "Authorization: token ${TOKEN}" \
-H "Accept: application/vnd.github+json" \
-d "$(jq -n --rawfile body /comment/comment-body.txt '{body: $body}')" \
"https://api.github.com/repos/${REPO}/issues/${PR_NUMBER}/comments" \
>/dev/null && echo "Posted new PR comment." || echo "WARNING: Failed to post PR comment."
fi
exit 0
env:
- name: GITHUB_APP_ID
valueFrom:
secretKeyRef:
name: {{ .Values.github.prComment.secretName }}
key: githubAppID
- name: GITHUB_APP_INSTALLATION_ID
valueFrom:
secretKeyRef:
name: {{ .Values.github.prComment.secretName }}
key: githubAppInstallationID
volumeMounts:
- name: github-app-key
mountPath: /github-app
readOnly: true
- name: comment-body
mountPath: /comment
readOnly: true
resources:
requests:
cpu: 50m
memory: 32Mi
limits:
memory: 64Mi
volumes:
- name: github-app-key
secret:
secretName: {{ .Values.github.prComment.secretName }}
items:
- key: githubAppPrivateKey
path: githubAppPrivateKey
- name: comment-body
configMap:
name: {{ .Release.Name }}-pr-comment-body
---
# ConfigMap holding the Helm-rendered comment body.
# Separating it from the Job args avoids shell escaping issues with
# the rendered Markdown content.
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Release.Name }}-pr-comment-body
labels:
{{- include "drupal.release_labels" . | nindent 4 }}
annotations:
argocd.argoproj.io/hook: PostSync
argocd.argoproj.io/hook-delete-policy: BeforeHookCreation
data:
comment-body.txt: |
### :rocket: Deployment — `{{ .Release.Name }}`
{{ include "drupal.deployment-notes" . | nindent 4 }}
<!-- {{ .Values.github.prComment.commentTag }}-{{ .Release.Name }} -->
{{- end }}
14 changes: 14 additions & 0 deletions drupal/values.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,20 @@
"clusterDomain": { "type": "string" },
"projectName": { "type": "string" },
"environmentName": { "type": "string" },
"github": {
"type": "object",
"properties": {
"prComment": {
"type": "object",
"properties": {
"prNumber": { "type": "string" },
"repository": { "type": "string" },
"secretName": { "type": "string" },
"commentTag": { "type": "string" }
}
}
}
},
"branchName": { "type": "string" },
"cleanup": {
"type": "object",
Expand Down
17 changes: 17 additions & 0 deletions drupal/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,23 @@ projectName: ""
# This name is mainly used to create nice subdomains for each environment.
environmentName: ""

# --- GitHub PR Comment
# When prNumber is set (injected by the platform chart for PR environments),
# a PostSync Job posts deployment details as a PR comment via the GitHub API.
# Requires the argocd-github-app secret in the namespace (synced by Kyverno).
github:
prComment:
# Set automatically by the platform chart for PR environments.
prNumber: ""
# GitHub repository owner/name, e.g. "wunderio/client-fi-mysite".
# Set automatically by the platform chart from tenant.repoURL.
repository: ""
# Name of the Kubernetes secret containing GitHub App credentials.
# Must have keys: githubAppID, githubAppInstallationID, githubAppPrivateKey.
secretName: argocd-github-app
# Marker string used to upsert (update-or-create) the comment.
commentTag: "silta-deploy"

# Configure image pull secrets for the containers. This is not needed on GKE.
# See https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/
imagePullSecrets: []
Expand Down
Loading