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
14 changes: 14 additions & 0 deletions helm/supabase-operator/templates/_helpers.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,17 @@ Create the name of the service account to use
{{- default "default" .Values.serviceAccount.name }}
{{- end }}
{{- end }}

{{/*
Webhook service name
*/}}
{{- define "supabase-operator.webhookServiceName" -}}
{{- printf "%s-webhook" (include "supabase-operator.fullname" .) | trunc 63 | trimSuffix "-" }}
{{- end }}

{{/*
Webhook certificate secret name
*/}}
{{- define "supabase-operator.webhookCertSecret" -}}
{{- printf "%s-webhook-cert" (include "supabase-operator.fullname" .) | trunc 63 | trimSuffix "-" }}
{{- end }}
31 changes: 27 additions & 4 deletions helm/supabase-operator/templates/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,12 @@ spec:
{{- tpl (toYaml .) $ | nindent 12 }}
{{- end }}
{{- end }}
{{- if .Values.webhook.enabled }}
ports:
- containerPort: {{ .Values.webhook.targetPort }}
name: webhook-server
protocol: TCP
{{- end }}
livenessProbe:
httpGet:
path: /healthz
Expand All @@ -70,14 +76,31 @@ spec:
periodSeconds: 10
resources:
{{- toYaml .Values.resources | nindent 12 }}
{{- with .Values.extraVolumeMounts }}
{{- $extraMounts := .Values.extraVolumeMounts }}
{{- if or .Values.webhook.enabled (and $extraMounts (not (empty $extraMounts))) }}
volumeMounts:
{{- toYaml . | nindent 12 }}
{{- if .Values.webhook.enabled }}
- mountPath: /tmp/k8s-webhook-server/serving-certs
name: webhook-cert
readOnly: true
{{- end }}
{{- if $extraMounts }}
{{- toYaml $extraMounts | nindent 12 }}
{{- end }}
{{- end }}
terminationGracePeriodSeconds: 10
{{- with .Values.extraVolumes }}
{{- $extraVolumes := .Values.extraVolumes }}
{{- if or .Values.webhook.enabled (and $extraVolumes (not (empty $extraVolumes))) }}
volumes:
{{- toYaml . | nindent 8 }}
{{- if .Values.webhook.enabled }}
- name: webhook-cert
secret:
secretName: {{ include "supabase-operator.webhookCertSecret" . }}
defaultMode: 420
{{- end }}
{{- if $extraVolumes }}
{{- toYaml $extraVolumes | nindent 8 }}
{{- end }}
{{- end }}
{{- with .Values.nodeSelector }}
nodeSelector:
Expand Down
27 changes: 27 additions & 0 deletions helm/supabase-operator/templates/webhook-certmanager.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{{- if .Values.webhook.enabled }}
apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
name: {{ include "supabase-operator.fullname" . }}-selfsigned-issuer
namespace: {{ .Release.Namespace }}
labels:
{{- include "supabase-operator.labels" . | nindent 4 }}
spec:
selfSigned: {}
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: {{ include "supabase-operator.webhookCertSecret" . }}
namespace: {{ .Release.Namespace }}
labels:
{{- include "supabase-operator.labels" . | nindent 4 }}
spec:
dnsNames:
- {{ include "supabase-operator.webhookServiceName" . }}.{{ .Release.Namespace }}.svc
- {{ include "supabase-operator.webhookServiceName" . }}.{{ .Release.Namespace }}.svc.cluster.local
issuerRef:
kind: Issuer
name: {{ include "supabase-operator.fullname" . }}-selfsigned-issuer
secretName: {{ include "supabase-operator.webhookCertSecret" . }}
{{- end }}
49 changes: 49 additions & 0 deletions helm/supabase-operator/templates/webhook-configurations.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
{{- if .Values.webhook.enabled }}
apiVersion: admissionregistration.k8s.io/v1
kind: MutatingWebhookConfiguration
metadata:
name: {{ include "supabase-operator.fullname" . }}-mutating-webhook
labels:
{{- include "supabase-operator.labels" . | nindent 4 }}
annotations:
cert-manager.io/inject-ca-from: {{ printf "%s/%s" .Release.Namespace (include "supabase-operator.webhookCertSecret" .) }}
webhooks:
- name: msupabaseproject.kb.io
admissionReviewVersions: ["v1"]
failurePolicy: {{ .Values.webhook.failurePolicy }}
sideEffects: None
clientConfig:
service:
name: {{ include "supabase-operator.webhookServiceName" . }}
namespace: {{ .Release.Namespace }}
path: /mutate-supabase-strrl-dev-v1alpha1-supabaseproject
rules:
- apiGroups: ["supabase.strrl.dev"]
apiVersions: ["v1alpha1"]
operations: ["CREATE", "UPDATE"]
resources: ["supabaseprojects"]
---
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
name: {{ include "supabase-operator.fullname" . }}-validating-webhook
labels:
{{- include "supabase-operator.labels" . | nindent 4 }}
annotations:
cert-manager.io/inject-ca-from: {{ printf "%s/%s" .Release.Namespace (include "supabase-operator.webhookCertSecret" .) }}
webhooks:
- name: vsupabaseproject.kb.io
admissionReviewVersions: ["v1"]
failurePolicy: {{ .Values.webhook.failurePolicy }}
sideEffects: None
clientConfig:
service:
name: {{ include "supabase-operator.webhookServiceName" . }}
namespace: {{ .Release.Namespace }}
path: /validate-supabase-strrl-dev-v1alpha1-supabaseproject
rules:
- apiGroups: ["supabase.strrl.dev"]
apiVersions: ["v1alpha1"]
operations: ["CREATE", "UPDATE"]
resources: ["supabaseprojects"]
{{- end }}
17 changes: 17 additions & 0 deletions helm/supabase-operator/templates/webhook-service.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{{- if .Values.webhook.enabled }}
apiVersion: v1
kind: Service
metadata:
name: {{ include "supabase-operator.webhookServiceName" . }}
labels:
{{- include "supabase-operator.labels" . | nindent 4 }}
spec:
type: ClusterIP
selector:
{{- include "supabase-operator.selectorLabels" . | nindent 4 }}
ports:
- name: webhook
port: {{ .Values.webhook.servicePort }}
targetPort: {{ .Values.webhook.targetPort }}
protocol: TCP
{{- end }}
6 changes: 6 additions & 0 deletions helm/supabase-operator/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,9 @@ tolerations: []
affinity: {}

topologySpreadConstraints: []

webhook:
enabled: true
servicePort: 443
targetPort: 9443
failurePolicy: Fail
69 changes: 66 additions & 3 deletions test/e2e/e2e_suite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@
package e2e

import (
"bytes"
"fmt"
"os"
"os/exec"
"strings"
"testing"
"time"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
Expand All @@ -24,9 +27,11 @@ var (
// isCertManagerAlreadyInstalled will be set true when CertManager CRDs be found on the cluster
isCertManagerAlreadyInstalled = false

// projectImage is the name of the image which will be build and loaded
// with the code source changes to be tested.
projectImage = "example.com/supabase-operator:v0.0.1"
// projectImage* describe the local image built for e2e; keep repo/tag split
// so we can feed them into Helm values easily.
projectImageRepository = firstNonEmpty(os.Getenv("E2E_IMG_REPO"), "example.com/supabase-operator")
projectImageTag = ""
projectImage = ""
)

// TestE2E runs the end-to-end (e2e) test suite for the project. These tests execute in an isolated,
Expand All @@ -40,6 +45,9 @@ func TestE2E(t *testing.T) {
}

var _ = BeforeSuite(func() {
projectImageTag = resolveImageTag()
projectImage = fmt.Sprintf("%s:%s", projectImageRepository, projectImageTag)

By("building the manager(Operator) image")
cmd := exec.Command("make", "docker-build", fmt.Sprintf("IMG=%s", projectImage))
_, err := utils.Run(cmd)
Expand Down Expand Up @@ -67,6 +75,61 @@ var _ = BeforeSuite(func() {
}
})

// resolveImageTag picks an image tag for e2e image builds.
// Priority: explicit env E2E_IMG_TAG -> git short SHA (+ -dirty if needed) -> timestamp.
func resolveImageTag() string {
if envTag := os.Getenv("E2E_IMG_TAG"); envTag != "" {
return envTag
}

shortSHA, err := gitRevParseShort()
if err == nil && shortSHA != "" {
if dirty, derr := gitIsDirty(); derr == nil && dirty {
return shortSHA + "-dirty"
}
return shortSHA
}

return fmt.Sprintf("dev-%d", time.Now().Unix())
}

func gitRevParseShort() (string, error) {
root, err := utils.GetProjectDir()
if err != nil {
return "", err
}
cmd := exec.Command("git", "rev-parse", "--short", "HEAD")
cmd.Dir = root
out, err := cmd.Output()
if err != nil {
return "", err
}
return strings.TrimSpace(string(out)), nil
}

func gitIsDirty() (bool, error) {
root, err := utils.GetProjectDir()
if err != nil {
return false, err
}
cmd := exec.Command("git", "status", "--porcelain")
cmd.Dir = root
out, err := cmd.Output()
if err != nil {
return false, err
}
return len(bytes.TrimSpace(out)) > 0, nil
}

func firstNonEmpty(values ...string) string {
for _, v := range values {
if strings.TrimSpace(v) != "" {
return v
}
}
return ""
}

var _ = AfterSuite(func() {
// Teardown CertManager after the suite if not skipped and if it was not already installed
if !skipCertManagerInstall && !isCertManagerAlreadyInstalled {
Expand Down
45 changes: 27 additions & 18 deletions test/e2e/e2e_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,13 @@ import (

// namespace where the project is deployed in
const namespace = "supabase-operator-system"
const helmReleaseName = "supabase-operator"

var _ = Describe("Manager", Ordered, func() {
var controllerPodName string

// Before running the tests, set up the environment by creating the namespace,
// enforce the restricted security policy to the namespace, installing CRDs,
// and deploying the controller.
// enforce the restricted security policy to the namespace, and installing the controller via Helm.
BeforeAll(func() {
By("creating manager namespace")
cmd := exec.Command("kubectl", "create", "ns", namespace)
Expand All @@ -44,30 +44,33 @@ var _ = Describe("Manager", Ordered, func() {
_, err = utils.Run(cmd)
Expect(err).NotTo(HaveOccurred(), "Failed to label namespace with restricted policy")

By("installing CRDs")
cmd = exec.Command("make", "install")
_, err = utils.Run(cmd)
Expect(err).NotTo(HaveOccurred(), "Failed to install CRDs")

By("deploying the controller-manager")
cmd = exec.Command("make", "deploy", fmt.Sprintf("IMG=%s", projectImage))
By("installing the operator via Helm chart")
cmd = exec.Command("helm", "upgrade", "--install", helmReleaseName, "helm/supabase-operator",
"--namespace", namespace,
"--create-namespace",
"--wait",
"--timeout", "5m",
"--set", fmt.Sprintf("image.repository=%s", projectImageRepository),
"--set", fmt.Sprintf("image.tag=%s", projectImageTag),
"--set", "image.pullPolicy=IfNotPresent",
)
_, err = utils.Run(cmd)
Expect(err).NotTo(HaveOccurred(), "Failed to deploy the controller-manager")
Expect(err).NotTo(HaveOccurred(), "Failed to install the operator Helm release")
})

// After all tests have been executed, clean up by undeploying the controller, uninstalling CRDs,
// After all tests have been executed, clean up by uninstalling the Helm release, removing CRDs,
// and deleting the namespace.
AfterAll(func() {
By("undeploying the controller-manager")
cmd := exec.Command("make", "undeploy")
By("uninstalling the operator Helm release")
cmd := exec.Command("helm", "uninstall", helmReleaseName, "-n", namespace, "--wait")
_, _ = utils.Run(cmd)

By("uninstalling CRDs")
cmd = exec.Command("make", "uninstall")
By("cleaning up Supabase CRDs")
cmd = exec.Command("kubectl", "delete", "crd", "supabaseprojects.supabase.strrl.dev", "--ignore-not-found=true")
_, _ = utils.Run(cmd)

By("removing manager namespace")
cmd = exec.Command("kubectl", "delete", "ns", namespace)
cmd = exec.Command("kubectl", "delete", "ns", namespace, "--ignore-not-found=true")
_, _ = utils.Run(cmd)
})

Expand All @@ -76,6 +79,10 @@ var _ = Describe("Manager", Ordered, func() {
AfterEach(func() {
specReport := CurrentSpecReport()
if specReport.Failed() {
if controllerPodName == "" {
_, _ = fmt.Fprintf(GinkgoWriter, "controllerPodName not set; skipping pod log/describe collection\n")
return
}
By("Fetching controller manager pod logs")
cmd := exec.Command("kubectl", "logs", controllerPodName, "-n", namespace)
controllerLogs, err := utils.Run(cmd)
Expand Down Expand Up @@ -114,7 +121,9 @@ var _ = Describe("Manager", Ordered, func() {
verifyControllerUp := func(g Gomega) {
// Get the name of the controller-manager pod
cmd := exec.Command("kubectl", "get",
"pods", "-l", "control-plane=controller-manager",
"pods",
"-l", "app.kubernetes.io/name=supabase-operator",
"-l", fmt.Sprintf("app.kubernetes.io/instance=%s", helmReleaseName),
"-o", "go-template={{ range .items }}"+
"{{ if not .metadata.deletionTimestamp }}"+
"{{ .metadata.name }}"+
Expand All @@ -127,7 +136,7 @@ var _ = Describe("Manager", Ordered, func() {
podNames := utils.GetNonEmptyLines(podOutput)
g.Expect(podNames).To(HaveLen(1), "expected 1 controller pod running")
controllerPodName = podNames[0]
g.Expect(controllerPodName).To(ContainSubstring("controller-manager"))
g.Expect(controllerPodName).To(ContainSubstring(helmReleaseName))

// Validate the pod's status
cmd = exec.Command("kubectl", "get",
Expand Down
Loading