feat(dashboard): initial support for studio & pg-meta services

This commit is contained in:
Peter 2025-01-11 16:51:05 +01:00
parent 7d9e518f86
commit 0b551325b9
Signed by: prskr
GPG key ID: F56BED6903BC5E37
31 changed files with 2151 additions and 492 deletions

View file

@ -178,7 +178,7 @@ func (r *APIGatewayReconciler) reconcileEnvoyConfig(
}
_, err = controllerutil.CreateOrUpdate(ctx, r.Client, configMap, func() error {
configMap.Labels = MergeLabels(objectLabels(gateway, "envoy", "api-gateway", supabase.Images.Postgrest.Tag), gateway.Labels)
configMap.Labels = MergeLabels(objectLabels(gateway, "envoy", "api-gateway", supabase.Images.Envoy.Tag), gateway.Labels)
type nodeSpec struct {
Cluster string
@ -245,58 +245,15 @@ func (r *APIGatewayReconciler) reconileEnvoyDeployment(
},
}
envoySpec := gateway.Spec.Envoy
if envoySpec == nil {
envoySpec = new(supabasev1alpha1.EnvoySpec)
}
if envoySpec.WorkloadTemplate == nil {
envoySpec.WorkloadTemplate = new(supabasev1alpha1.WorkloadTemplate)
}
if envoySpec.WorkloadTemplate.Workload == nil {
envoySpec.WorkloadTemplate.Workload = new(supabasev1alpha1.ContainerTemplate)
}
var (
image = supabase.Images.Envoy.String()
podSecurityContext = envoySpec.WorkloadTemplate.SecurityContext
pullPolicy = envoySpec.WorkloadTemplate.Workload.PullPolicy
containerSecurityContext = envoySpec.WorkloadTemplate.Workload.SecurityContext
envoySpec = gateway.Spec.Envoy
serviceCfg = supabase.ServiceConfig.Envoy
)
if img := envoySpec.WorkloadTemplate.Workload.Image; img != "" {
image = img
}
if podSecurityContext == nil {
podSecurityContext = &corev1.PodSecurityContext{
RunAsNonRoot: ptrOf(true),
}
}
if containerSecurityContext == nil {
containerSecurityContext = &corev1.SecurityContext{
Privileged: ptrOf(false),
RunAsUser: ptrOf(int64(65532)),
RunAsGroup: ptrOf(int64(65532)),
RunAsNonRoot: ptrOf(true),
AllowPrivilegeEscalation: ptrOf(false),
ReadOnlyRootFilesystem: ptrOf(true),
Capabilities: &corev1.Capabilities{
Drop: []corev1.Capability{
"ALL",
},
},
}
}
_, err := controllerutil.CreateOrUpdate(ctx, r.Client, envoyDeployment, func() error {
envoyDeployment.Labels = MergeLabels(
objectLabels(gateway, "envoy", "api-gateway", supabase.Images.Postgrest.Tag),
envoyDeployment.Labels = envoySpec.WorkloadTemplate.MergeLabels(
objectLabels(gateway, "envoy", "api-gateway", supabase.Images.Envoy.Tag),
gateway.Labels,
envoySpec.WorkloadTemplate.AdditionalLabels,
)
if envoyDeployment.CreationTimestamp.IsZero() {
@ -305,7 +262,7 @@ func (r *APIGatewayReconciler) reconileEnvoyDeployment(
}
}
envoyDeployment.Spec.Replicas = envoySpec.WorkloadTemplate.Replicas
envoyDeployment.Spec.Replicas = envoySpec.WorkloadTemplate.ReplicaCount()
envoyDeployment.Spec.Template = corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
@ -313,19 +270,16 @@ func (r *APIGatewayReconciler) reconileEnvoyDeployment(
fmt.Sprintf("%s/%s", supabasev1alpha1.GroupVersion.Group, "config-hash"): configHash,
fmt.Sprintf("%s/%s", supabasev1alpha1.GroupVersion.Group, "jwks-hash"): jwksHash,
},
Labels: MergeLabels(
objectLabels(gateway, "envoy", "api-gateway", supabase.Images.Envoy.Tag),
envoySpec.WorkloadTemplate.AdditionalLabels,
),
Labels: objectLabels(gateway, "envoy", "api-gateway", supabase.Images.Envoy.Tag),
},
Spec: corev1.PodSpec{
ImagePullSecrets: envoySpec.WorkloadTemplate.Workload.ImagePullSecrets,
ImagePullSecrets: envoySpec.WorkloadTemplate.PullSecrets(),
AutomountServiceAccountToken: ptrOf(false),
Containers: []corev1.Container{
{
Name: "envoy-proxy",
Image: image,
ImagePullPolicy: pullPolicy,
Image: envoySpec.WorkloadTemplate.Image(supabase.Images.Envoy.String()),
ImagePullPolicy: envoySpec.WorkloadTemplate.ImagePullPolicy(),
Args: []string{"-c /etc/envoy/config.yaml"},
Ports: []corev1.ContainerPort{
{
@ -362,18 +316,18 @@ func (r *APIGatewayReconciler) reconileEnvoyDeployment(
},
},
},
SecurityContext: containerSecurityContext,
Resources: envoySpec.WorkloadTemplate.Workload.Resources,
VolumeMounts: []corev1.VolumeMount{
{
SecurityContext: envoySpec.WorkloadTemplate.ContainerSecurityContext(serviceCfg.Defaults.UID, serviceCfg.Defaults.GID),
Resources: envoySpec.WorkloadTemplate.Resources(),
VolumeMounts: envoySpec.WorkloadTemplate.AdditionalVolumeMounts(
corev1.VolumeMount{
Name: "config",
ReadOnly: true,
MountPath: "/etc/envoy",
},
},
),
},
},
SecurityContext: podSecurityContext,
SecurityContext: envoySpec.WorkloadTemplate.PodSecurityContext(),
Volumes: []corev1.Volume{
{
Name: "config",
@ -432,10 +386,10 @@ func (r *APIGatewayReconciler) reconcileEnvoyService(
}
_, err := controllerutil.CreateOrUpdate(ctx, r.Client, envoyService, func() error {
envoyService.Labels = MergeLabels(objectLabels(gateway, "envoy", "api-gateway", supabase.Images.Postgrest.Tag), gateway.Labels)
envoyService.Labels = MergeLabels(objectLabels(gateway, "envoy", "api-gateway", supabase.Images.Envoy.Tag), gateway.Labels)
envoyService.Spec = corev1.ServiceSpec{
Selector: selectorLabels(gateway, "postgrest"),
Selector: selectorLabels(gateway, "envoy"),
Ports: []corev1.ServicePort{
{
Name: "rest",

View file

@ -72,52 +72,11 @@ func (r *CoreAuthReconciler) reconcileAuthDeployment(
authDeployment = &appsv1.Deployment{
ObjectMeta: supabase.ServiceConfig.Auth.ObjectMeta(core),
}
authSpec = core.Spec.Auth
svcCfg = supabase.ServiceConfig.Auth
authSpec = core.Spec.Auth
svcCfg = supabase.ServiceConfig.Auth
namespacedClient = client.NewNamespacedClient(r.Client, core.Namespace)
)
if authSpec.WorkloadTemplate == nil {
authSpec.WorkloadTemplate = new(supabasev1alpha1.WorkloadTemplate)
}
if authSpec.WorkloadTemplate.Workload == nil {
authSpec.WorkloadTemplate.Workload = new(supabasev1alpha1.ContainerTemplate)
}
var (
image = supabase.Images.Gotrue.String()
podSecurityContext = authSpec.WorkloadTemplate.SecurityContext
pullPolicy = authSpec.WorkloadTemplate.Workload.PullPolicy
containerSecurityContext = authSpec.WorkloadTemplate.Workload.SecurityContext
namespacedClient = client.NewNamespacedClient(r.Client, core.Namespace)
)
if img := authSpec.WorkloadTemplate.Workload.Image; img != "" {
image = img
}
if podSecurityContext == nil {
podSecurityContext = &corev1.PodSecurityContext{
RunAsNonRoot: ptrOf(true),
}
}
if containerSecurityContext == nil {
containerSecurityContext = &corev1.SecurityContext{
Privileged: ptrOf(false),
RunAsUser: ptrOf(int64(1000)),
RunAsGroup: ptrOf(int64(1000)),
RunAsNonRoot: ptrOf(true),
AllowPrivilegeEscalation: ptrOf(false),
ReadOnlyRootFilesystem: ptrOf(true),
Capabilities: &corev1.Capabilities{
Drop: []corev1.Capability{
"ALL",
},
},
}
}
databaseDSN, err := core.Spec.Database.GetDSN(ctx, namespacedClient)
if err != nil {
return err
@ -129,7 +88,7 @@ func (r *CoreAuthReconciler) reconcileAuthDeployment(
}
_, err = controllerutil.CreateOrUpdate(ctx, r.Client, authDeployment, func() error {
authDeployment.Labels = MergeLabels(
authDeployment.Labels = authSpec.WorkloadTemplate.MergeLabels(
objectLabels(core, "auth", "core", supabase.Images.Gotrue.Tag),
core.Labels,
)
@ -153,24 +112,24 @@ func (r *CoreAuthReconciler) reconcileAuthDeployment(
}
authEnv := append(authDbEnv,
svcCfg.EnvKeys.ApiHost.Var(svcCfg.Defaults.ApiHost),
svcCfg.EnvKeys.ApiPort.Var(svcCfg.Defaults.ApiPort),
svcCfg.EnvKeys.ApiExternalUrl.Var(authSpec.APIExternalURL),
svcCfg.EnvKeys.DBDriver.Var(svcCfg.Defaults.DbDriver),
svcCfg.EnvKeys.SiteUrl.Var(authSpec.SiteURL),
svcCfg.EnvKeys.ApiHost.Var(),
svcCfg.EnvKeys.ApiPort.Var(),
svcCfg.EnvKeys.ApiExternalUrl.Var(core.Spec.APIExternalURL),
svcCfg.EnvKeys.DBDriver.Var(),
svcCfg.EnvKeys.SiteUrl.Var(core.Spec.SiteURL),
svcCfg.EnvKeys.AdditionalRedirectURLs.Var(authSpec.AdditionalRedirectUrls),
svcCfg.EnvKeys.DisableSignup.Var(boolValueOf(authSpec.DisableSignup)),
svcCfg.EnvKeys.JWTIssuer.Var(svcCfg.Defaults.JwtIssuer),
svcCfg.EnvKeys.JWTAdminRoles.Var(svcCfg.Defaults.JwtAdminRoles),
svcCfg.EnvKeys.JWTAudience.Var(svcCfg.Defaults.JwtAudience),
svcCfg.EnvKeys.JwtDefaultGroup.Var(svcCfg.Defaults.JwtDefaultGroupName),
svcCfg.EnvKeys.JWTIssuer.Var(),
svcCfg.EnvKeys.JWTAdminRoles.Var(),
svcCfg.EnvKeys.JWTAudience.Var(),
svcCfg.EnvKeys.JwtDefaultGroup.Var(),
svcCfg.EnvKeys.JwtExpiry.Var(ValueOrFallback(core.Spec.JWT.Expiry, supabase.ServiceConfig.JWT.Defaults.Expiry)),
svcCfg.EnvKeys.JwtSecret.Var(core.Spec.JWT.SecretKeySelector()),
svcCfg.EnvKeys.EmailSignupDisabled.Var(boolValueOf(authSpec.EmailSignupDisabled)),
svcCfg.EnvKeys.AnonymousUsersEnabled.Var(boolValueOf(authSpec.AnonymousUsersEnabled)),
)
authEnv = append(authEnv, authSpec.Providers.Vars(authSpec.APIExternalURL)...)
authEnv = append(authEnv, authSpec.Providers.Vars(core.Spec.APIExternalURL)...)
if authDeployment.CreationTimestamp.IsZero() {
authDeployment.Spec.Selector = &metav1.LabelSelector{
@ -178,38 +137,38 @@ func (r *CoreAuthReconciler) reconcileAuthDeployment(
}
}
authDeployment.Spec.Replicas = authSpec.WorkloadTemplate.Replicas
authDeployment.Spec.Replicas = authSpec.WorkloadTemplate.ReplicaCount()
authDeployment.Spec.Template = corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: objectLabels(core, "auth", "core", supabase.Images.Gotrue.Tag),
},
Spec: corev1.PodSpec{
ImagePullSecrets: authSpec.WorkloadTemplate.Workload.ImagePullSecrets,
ImagePullSecrets: authSpec.WorkloadTemplate.PullSecrets(),
InitContainers: []corev1.Container{{
Name: "migrations",
Image: image,
ImagePullPolicy: pullPolicy,
Name: "supabase-auth-migrations",
Image: authSpec.WorkloadTemplate.Image(supabase.Images.Gotrue.String()),
ImagePullPolicy: authSpec.WorkloadTemplate.ImagePullPolicy(),
Command: []string{"/usr/local/bin/auth"},
Args: []string{"migrate"},
Env: authEnv,
SecurityContext: containerSecurityContext,
Env: authSpec.WorkloadTemplate.MergeEnv(authEnv),
SecurityContext: authSpec.WorkloadTemplate.ContainerSecurityContext(svcCfg.Defaults.UID, svcCfg.Defaults.GID),
}},
Containers: []corev1.Container{{
Name: "supabase-auth",
Image: image,
ImagePullPolicy: pullPolicy,
Image: authSpec.WorkloadTemplate.Image(supabase.Images.Gotrue.String()),
ImagePullPolicy: authSpec.WorkloadTemplate.ImagePullPolicy(),
Command: []string{"/usr/local/bin/auth"},
Args: []string{"serve"},
Env: MergeEnv(authEnv, authSpec.WorkloadTemplate.Workload.AdditionalEnv...),
Env: authSpec.WorkloadTemplate.MergeEnv(authEnv),
Ports: []corev1.ContainerPort{{
Name: "api",
ContainerPort: 9999,
ContainerPort: svcCfg.Defaults.APIPort,
Protocol: corev1.ProtocolTCP,
}},
SecurityContext: containerSecurityContext,
Resources: authSpec.WorkloadTemplate.Workload.Resources,
VolumeMounts: authSpec.WorkloadTemplate.Workload.VolumeMounts,
SecurityContext: authSpec.WorkloadTemplate.ContainerSecurityContext(svcCfg.Defaults.UID, svcCfg.Defaults.GID),
Resources: authSpec.WorkloadTemplate.Resources(),
VolumeMounts: authSpec.WorkloadTemplate.AdditionalVolumeMounts(),
ReadinessProbe: &corev1.Probe{
InitialDelaySeconds: 5,
PeriodSeconds: 3,
@ -218,7 +177,7 @@ func (r *CoreAuthReconciler) reconcileAuthDeployment(
ProbeHandler: corev1.ProbeHandler{
HTTPGet: &corev1.HTTPGetAction{
Path: "/health",
Port: intstr.IntOrString{IntVal: 9999},
Port: intstr.IntOrString{IntVal: svcCfg.Defaults.APIPort},
},
},
},
@ -229,12 +188,12 @@ func (r *CoreAuthReconciler) reconcileAuthDeployment(
ProbeHandler: corev1.ProbeHandler{
HTTPGet: &corev1.HTTPGetAction{
Path: "/health",
Port: intstr.IntOrString{IntVal: 9999},
Port: intstr.IntOrString{IntVal: svcCfg.Defaults.APIPort},
},
},
},
}},
SecurityContext: podSecurityContext,
SecurityContext: authSpec.WorkloadTemplate.PodSecurityContext(),
},
}
@ -257,12 +216,14 @@ func (r *CoreAuthReconciler) reconcileAuthService(
}
_, err := controllerutil.CreateOrUpdate(ctx, r.Client, authService, func() error {
authService.Labels = MergeLabels(
authService.Labels = core.Spec.Postgrest.WorkloadTemplate.MergeLabels(
objectLabels(core, "auth", "core", supabase.Images.Gotrue.Tag),
core.Labels,
)
authService.Labels[meta.SupabaseLabel.EnvoyCluster] = core.Name
if _, ok := authService.Labels[meta.SupabaseLabel.EnvoyCluster]; !ok {
authService.Labels[meta.SupabaseLabel.EnvoyCluster] = core.Name
}
authService.Spec = corev1.ServiceSpec{
Selector: selectorLabels(core, "auth"),

View file

@ -78,51 +78,13 @@ func (r *CorePostgrestReconiler) reconilePostgrestDeployment(
postgrestSpec = core.Spec.Postgrest
)
if postgrestSpec.WorkloadTemplate == nil {
postgrestSpec.WorkloadTemplate = new(supabasev1alpha1.WorkloadTemplate)
}
if postgrestSpec.WorkloadTemplate.Workload == nil {
postgrestSpec.WorkloadTemplate.Workload = new(supabasev1alpha1.ContainerTemplate)
}
var (
image = supabase.Images.Postgrest.String()
podSecurityContext = postgrestSpec.WorkloadTemplate.SecurityContext
pullPolicy = postgrestSpec.WorkloadTemplate.Workload.PullPolicy
containerSecurityContext = postgrestSpec.WorkloadTemplate.Workload.SecurityContext
anonRole = ValueOrFallback(postgrestSpec.AnonRole, serviceCfg.Defaults.AnonRole)
postgrestSchemas = ValueOrFallback(postgrestSpec.Schemas, serviceCfg.Defaults.Schemas)
jwtSecretHash string
namespacedClient = client.NewNamespacedClient(r.Client, core.Namespace)
anonRole = ValueOrFallback(postgrestSpec.AnonRole, serviceCfg.Defaults.AnonRole)
postgrestSchemas = ValueOrFallback(postgrestSpec.Schemas, serviceCfg.Defaults.Schemas)
jwtSecretHash string
namespacedClient = client.NewNamespacedClient(r.Client, core.Namespace)
)
if img := postgrestSpec.WorkloadTemplate.Workload.Image; img != "" {
image = img
}
if podSecurityContext == nil {
podSecurityContext = &corev1.PodSecurityContext{
RunAsNonRoot: ptrOf(true),
}
}
if containerSecurityContext == nil {
containerSecurityContext = &corev1.SecurityContext{
Privileged: ptrOf(false),
RunAsUser: ptrOf(int64(1000)),
RunAsGroup: ptrOf(int64(1000)),
RunAsNonRoot: ptrOf(true),
AllowPrivilegeEscalation: ptrOf(false),
ReadOnlyRootFilesystem: ptrOf(true),
Capabilities: &corev1.Capabilities{
Drop: []corev1.Capability{
"ALL",
},
},
}
}
databaseDSN, err := core.Spec.Database.GetDSN(ctx, namespacedClient)
if err != nil {
return err
@ -140,7 +102,7 @@ func (r *CorePostgrestReconiler) reconilePostgrestDeployment(
}
_, err = controllerutil.CreateOrUpdate(ctx, r.Client, postgrestDeployment, func() error {
postgrestDeployment.Labels = MergeLabels(
postgrestDeployment.Labels = postgrestSpec.WorkloadTemplate.MergeLabels(
objectLabels(core, serviceCfg.Name, "core", supabase.Images.Postgrest.Tag),
core.Labels,
)
@ -161,6 +123,7 @@ func (r *CorePostgrestReconiler) reconilePostgrestDeployment(
Name: serviceCfg.EnvKeys.DBUri,
Value: strings.TrimSuffix(fmt.Sprintf("postgres://%s:$(DB_CREDENTIALS_PASSWORD)@%s%s?%s", supabase.DBRoleAuthenticator, parsedDSN.Host, parsedDSN.Path, parsedDSN.Query().Encode()), "?"),
},
serviceCfg.EnvKeys.Host.Var(),
serviceCfg.EnvKeys.JWTSecret.Var(core.Spec.JWT.JwksKeySelector()),
serviceCfg.EnvKeys.Schemas.Var(postgrestSchemas),
serviceCfg.EnvKeys.AnonRole.Var(anonRole),
@ -168,7 +131,9 @@ func (r *CorePostgrestReconiler) reconilePostgrestDeployment(
serviceCfg.EnvKeys.ExtraSearchPath.Var(serviceCfg.Defaults.ExtraSearchPath),
serviceCfg.EnvKeys.AppSettingsJWTSecret.Var(core.Spec.JWT.SecretKeySelector()),
serviceCfg.EnvKeys.AppSettingsJWTExpiry.Var(ValueOrFallback(core.Spec.JWT.Expiry, supabase.ServiceConfig.JWT.Defaults.Expiry)),
serviceCfg.EnvKeys.AdminServerPort.Var(3001),
serviceCfg.EnvKeys.AdminServerPort.Var((serviceCfg.Defaults.AdminPort)),
serviceCfg.EnvKeys.MaxRows.Var(postgrestSpec.MaxRows),
serviceCfg.EnvKeys.OpenAPIProxyURI.Var(fmt.Sprintf("%s/rest/v1", strings.TrimSuffix(core.Spec.APIExternalURL, "/"))),
}
if postgrestDeployment.CreationTimestamp.IsZero() {
@ -177,7 +142,7 @@ func (r *CorePostgrestReconiler) reconilePostgrestDeployment(
}
}
postgrestDeployment.Spec.Replicas = postgrestSpec.WorkloadTemplate.Replicas
postgrestDeployment.Spec.Replicas = postgrestSpec.WorkloadTemplate.ReplicaCount()
postgrestDeployment.Spec.Template = corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
@ -187,29 +152,29 @@ func (r *CorePostgrestReconiler) reconilePostgrestDeployment(
Labels: objectLabels(core, serviceCfg.Name, "core", supabase.Images.Postgrest.Tag),
},
Spec: corev1.PodSpec{
ImagePullSecrets: postgrestSpec.WorkloadTemplate.Workload.ImagePullSecrets,
ImagePullSecrets: postgrestSpec.WorkloadTemplate.PullSecrets(),
Containers: []corev1.Container{
{
Name: "supabase-rest",
Image: image,
ImagePullPolicy: pullPolicy,
Image: postgrestSpec.WorkloadTemplate.Image(supabase.Images.Postgrest.String()),
ImagePullPolicy: postgrestSpec.WorkloadTemplate.ImagePullPolicy(),
Args: []string{"postgrest"},
Env: MergeEnv(postgrestEnv, postgrestSpec.WorkloadTemplate.Workload.AdditionalEnv...),
Env: postgrestSpec.WorkloadTemplate.MergeEnv(postgrestEnv),
Ports: []corev1.ContainerPort{
{
Name: "rest",
ContainerPort: 3000,
ContainerPort: serviceCfg.Defaults.ServerPort,
Protocol: corev1.ProtocolTCP,
},
{
Name: "admin",
ContainerPort: 3001,
ContainerPort: serviceCfg.Defaults.AdminPort,
Protocol: corev1.ProtocolTCP,
},
},
SecurityContext: containerSecurityContext,
Resources: postgrestSpec.WorkloadTemplate.Workload.Resources,
VolumeMounts: postgrestSpec.WorkloadTemplate.Workload.VolumeMounts,
SecurityContext: postgrestSpec.WorkloadTemplate.ContainerSecurityContext(serviceCfg.Defaults.UID, serviceCfg.Defaults.GID),
Resources: postgrestSpec.WorkloadTemplate.Resources(),
VolumeMounts: postgrestSpec.WorkloadTemplate.AdditionalVolumeMounts(),
ReadinessProbe: &corev1.Probe{
InitialDelaySeconds: 5,
PeriodSeconds: 3,
@ -218,7 +183,7 @@ func (r *CorePostgrestReconiler) reconilePostgrestDeployment(
ProbeHandler: corev1.ProbeHandler{
HTTPGet: &corev1.HTTPGetAction{
Path: "/ready",
Port: intstr.IntOrString{IntVal: 3001},
Port: intstr.IntOrString{IntVal: serviceCfg.Defaults.AdminPort},
},
},
},
@ -229,13 +194,13 @@ func (r *CorePostgrestReconiler) reconilePostgrestDeployment(
ProbeHandler: corev1.ProbeHandler{
HTTPGet: &corev1.HTTPGetAction{
Path: "/live",
Port: intstr.IntOrString{IntVal: 3001},
Port: intstr.IntOrString{IntVal: serviceCfg.Defaults.AdminPort},
},
},
},
},
},
SecurityContext: podSecurityContext,
SecurityContext: postgrestSpec.WorkloadTemplate.PodSecurityContext(),
},
}
@ -258,12 +223,14 @@ func (r *CorePostgrestReconiler) reconcilePostgrestService(
}
_, err := controllerutil.CreateOrUpdate(ctx, r.Client, postgrestService, func() error {
postgrestService.Labels = MergeLabels(
postgrestService.Labels = core.Spec.Postgrest.WorkloadTemplate.MergeLabels(
objectLabels(core, supabase.ServiceConfig.Postgrest.Name, "core", supabase.Images.Postgrest.Tag),
core.Labels,
)
postgrestService.Labels[meta.SupabaseLabel.EnvoyCluster] = core.Name
if _, ok := postgrestService.Labels[meta.SupabaseLabel.EnvoyCluster]; !ok {
postgrestService.Labels[meta.SupabaseLabel.EnvoyCluster] = core.Name
}
postgrestService.Spec = corev1.ServiceSpec{
Selector: selectorLabels(core, supabase.ServiceConfig.Postgrest.Name),

View file

@ -40,19 +40,6 @@ type DashboardPGMetaReconciler struct {
Scheme *runtime.Scheme
}
// +kubebuilder:rbac:groups=supabase.k8s.icb4dc0.de,resources=dashboards,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=supabase.k8s.icb4dc0.de,resources=dashboards/status,verbs=get;update;patch
// +kubebuilder:rbac:groups=supabase.k8s.icb4dc0.de,resources=dashboards/finalizers,verbs=update
// Reconcile is part of the main kubernetes reconciliation loop which aims to
// move the current state of the cluster closer to the desired state.
// TODO(user): Modify the Reconcile function to compare the state specified by
// the Dashboard object against the actual cluster state, and then
// perform operations to make the cluster state reflect the state specified by
// the user.
//
// For more details, check Reconcile and its Result here:
// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.19.1/pkg/reconcile
func (r *DashboardPGMetaReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
var (
dashboard supabasev1alpha1.Dashboard
@ -101,47 +88,6 @@ func (r *DashboardPGMetaReconciler) reconcilePGMetaDeployment(
pgMetaSpec = new(supabasev1alpha1.PGMetaSpec)
}
if pgMetaSpec.WorkloadTemplate == nil {
pgMetaSpec.WorkloadTemplate = new(supabasev1alpha1.WorkloadTemplate)
}
if pgMetaSpec.WorkloadTemplate.Workload == nil {
pgMetaSpec.WorkloadTemplate.Workload = new(supabasev1alpha1.ContainerTemplate)
}
var (
image = supabase.Images.PostgresMeta.String()
podSecurityContext = pgMetaSpec.WorkloadTemplate.SecurityContext
pullPolicy = pgMetaSpec.WorkloadTemplate.Workload.PullPolicy
containerSecurityContext = pgMetaSpec.WorkloadTemplate.Workload.SecurityContext
)
if img := pgMetaSpec.WorkloadTemplate.Workload.Image; img != "" {
image = img
}
if podSecurityContext == nil {
podSecurityContext = &corev1.PodSecurityContext{
RunAsNonRoot: ptrOf(true),
}
}
if containerSecurityContext == nil {
containerSecurityContext = &corev1.SecurityContext{
Privileged: ptrOf(false),
RunAsUser: ptrOf(int64(1000)),
RunAsGroup: ptrOf(int64(1000)),
RunAsNonRoot: ptrOf(true),
AllowPrivilegeEscalation: ptrOf(false),
ReadOnlyRootFilesystem: ptrOf(true),
Capabilities: &corev1.Capabilities{
Drop: []corev1.Capability{
"ALL",
},
},
}
}
dsnSecret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: dashboard.Spec.DBSpec.DBCredentialsRef.Name,
@ -153,7 +99,7 @@ func (r *DashboardPGMetaReconciler) reconcilePGMetaDeployment(
}
_, err := controllerutil.CreateOrUpdate(ctx, r.Client, pgMetaDeployment, func() error {
pgMetaDeployment.Labels = MergeLabels(
pgMetaDeployment.Labels = pgMetaSpec.WorkloadTemplate.MergeLabels(
objectLabels(dashboard, serviceCfg.Name, "dashboard", supabase.Images.PostgresMeta.Tag),
dashboard.Labels,
)
@ -164,11 +110,12 @@ func (r *DashboardPGMetaReconciler) reconcilePGMetaDeployment(
}
}
pgMetaDeployment.Spec.Replicas = pgMetaSpec.WorkloadTemplate.Replicas
pgMetaDeployment.Spec.Replicas = pgMetaSpec.WorkloadTemplate.ReplicaCount()
pgMetaEnv := []corev1.EnvVar{
serviceCfg.EnvKeys.APIPort.Var(serviceCfg.Defaults.APIPort),
serviceCfg.EnvKeys.DBHost.Var(dashboard.Spec.DBSpec.Host),
serviceCfg.EnvKeys.DBName.Var(dashboard.Spec.DBSpec.DBName),
serviceCfg.EnvKeys.DBPort.Var(dashboard.Spec.DBSpec.Port),
serviceCfg.EnvKeys.DBUser.Var(dashboard.Spec.DBSpec.UserRef()),
serviceCfg.EnvKeys.DBPassword.Var(dashboard.Spec.DBSpec.PasswordRef()),
@ -179,20 +126,20 @@ func (r *DashboardPGMetaReconciler) reconcilePGMetaDeployment(
Labels: objectLabels(dashboard, serviceCfg.Name, "dashboard", supabase.Images.PostgresMeta.Tag),
},
Spec: corev1.PodSpec{
ImagePullSecrets: pgMetaSpec.WorkloadTemplate.Workload.ImagePullSecrets,
ImagePullSecrets: pgMetaSpec.WorkloadTemplate.PullSecrets(),
Containers: []corev1.Container{{
Name: "supabase-meta",
Image: image,
ImagePullPolicy: pullPolicy,
Env: MergeEnv(pgMetaEnv, pgMetaSpec.WorkloadTemplate.Workload.AdditionalEnv...),
Image: pgMetaSpec.WorkloadTemplate.Image(supabase.Images.PostgresMeta.String()),
ImagePullPolicy: pgMetaSpec.WorkloadTemplate.ImagePullPolicy(),
Env: pgMetaSpec.WorkloadTemplate.MergeEnv(pgMetaEnv),
Ports: []corev1.ContainerPort{{
Name: "api",
ContainerPort: int32(serviceCfg.Defaults.APIPort),
ContainerPort: serviceCfg.Defaults.APIPort,
Protocol: corev1.ProtocolTCP,
}},
SecurityContext: containerSecurityContext,
Resources: pgMetaSpec.WorkloadTemplate.Workload.Resources,
VolumeMounts: pgMetaSpec.WorkloadTemplate.Workload.VolumeMounts,
SecurityContext: pgMetaSpec.WorkloadTemplate.ContainerSecurityContext(serviceCfg.Defaults.NodeUID, serviceCfg.Defaults.NodeGID),
Resources: pgMetaSpec.WorkloadTemplate.Resources(),
VolumeMounts: pgMetaSpec.WorkloadTemplate.AdditionalVolumeMounts(),
ReadinessProbe: &corev1.Probe{
InitialDelaySeconds: 5,
PeriodSeconds: 3,
@ -201,7 +148,7 @@ func (r *DashboardPGMetaReconciler) reconcilePGMetaDeployment(
ProbeHandler: corev1.ProbeHandler{
HTTPGet: &corev1.HTTPGetAction{
Path: "/health",
Port: intstr.IntOrString{IntVal: int32(serviceCfg.Defaults.APIPort)},
Port: intstr.IntOrString{IntVal: serviceCfg.Defaults.APIPort},
},
},
},
@ -212,12 +159,12 @@ func (r *DashboardPGMetaReconciler) reconcilePGMetaDeployment(
ProbeHandler: corev1.ProbeHandler{
HTTPGet: &corev1.HTTPGetAction{
Path: "/health",
Port: intstr.IntOrString{IntVal: int32(serviceCfg.Defaults.APIPort)},
Port: intstr.IntOrString{IntVal: serviceCfg.Defaults.APIPort},
},
},
},
}},
SecurityContext: podSecurityContext,
SecurityContext: pgMetaSpec.WorkloadTemplate.PodSecurityContext(),
},
}
@ -239,15 +186,19 @@ func (r *DashboardPGMetaReconciler) reconcilePGMetaService(
ObjectMeta: supabase.ServiceConfig.PGMeta.ObjectMeta(dashboard),
}
if dashboard.Spec.PGMeta == nil {
dashboard.Spec.PGMeta = new(supabasev1alpha1.PGMetaSpec)
}
_, err := controllerutil.CreateOrPatch(ctx, r.Client, pgMetaService, func() error {
pgMetaService.Labels = MergeLabels(
pgMetaService.Labels = dashboard.Spec.PGMeta.WorkloadTemplate.MergeLabels(
objectLabels(dashboard, supabase.ServiceConfig.PGMeta.Name, "dashboard", supabase.Images.PostgresMeta.Tag),
dashboard.Labels,
)
pgMetaService.Labels[meta.SupabaseLabel.EnvoyCluster] = dashboard.Name
apiPort := int32(supabase.ServiceConfig.PGMeta.Defaults.APIPort)
if _, ok := pgMetaService.Labels[meta.SupabaseLabel.EnvoyCluster]; !ok {
pgMetaService.Labels[meta.SupabaseLabel.EnvoyCluster] = dashboard.Name
}
pgMetaService.Spec = corev1.ServiceSpec{
Selector: selectorLabels(dashboard, supabase.ServiceConfig.PGMeta.Name),
@ -256,8 +207,8 @@ func (r *DashboardPGMetaReconciler) reconcilePGMetaService(
Name: "api",
Protocol: corev1.ProtocolTCP,
AppProtocol: ptrOf("http"),
Port: apiPort,
TargetPort: intstr.IntOrString{IntVal: apiPort},
Port: supabase.ServiceConfig.PGMeta.Defaults.APIPort,
TargetPort: intstr.IntOrString{IntVal: supabase.ServiceConfig.PGMeta.Defaults.APIPort},
},
},
}

View file

@ -0,0 +1,246 @@
/*
Copyright 2024 Peter Kurfer.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package controller
import (
"context"
"fmt"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/intstr"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
"sigs.k8s.io/controller-runtime/pkg/log"
supabasev1alpha1 "code.icb4dc0.de/prskr/supabase-operator/api/v1alpha1"
"code.icb4dc0.de/prskr/supabase-operator/internal/meta"
"code.icb4dc0.de/prskr/supabase-operator/internal/supabase"
)
type DashboardStudioReconciler struct {
client.Client
Scheme *runtime.Scheme
}
func (r *DashboardStudioReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
var (
dashboard supabasev1alpha1.Dashboard
logger = log.FromContext(ctx)
)
if err := r.Get(ctx, req.NamespacedName, &dashboard); client.IgnoreNotFound(err) != nil {
logger.Error(err, "unable to fetch Dashboard")
return ctrl.Result{}, err
}
if err := r.reconcileStudioDeployment(ctx, &dashboard); err != nil {
return ctrl.Result{}, err
}
if err := r.reconcileStudioService(ctx, &dashboard); err != nil {
return ctrl.Result{}, err
}
return ctrl.Result{}, nil
}
// SetupWithManager sets up the controller with the Manager.
func (r *DashboardStudioReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&supabasev1alpha1.Dashboard{}).
Owns(new(appsv1.Deployment)).
Owns(new(corev1.Service)).
Named("dashboard-studio").
Complete(r)
}
func (r *DashboardStudioReconciler) reconcileStudioDeployment(
ctx context.Context,
dashboard *supabasev1alpha1.Dashboard,
) error {
var (
serviceCfg = supabase.ServiceConfig.Studio
studioDeployment = &appsv1.Deployment{
ObjectMeta: serviceCfg.ObjectMeta(dashboard),
}
studioSpec = dashboard.Spec.Studio
gatewayServiceList corev1.ServiceList
)
if studioSpec == nil {
studioSpec = new(supabasev1alpha1.StudioSpec)
}
err := r.List(
ctx,
&gatewayServiceList,
client.InNamespace(dashboard.Namespace),
client.MatchingLabels(studioSpec.GatewayServiceMatchLabels),
)
if err != nil {
return fmt.Errorf("selecting gateway service: %w", err)
}
if itemCount := len(gatewayServiceList.Items); itemCount != 1 {
return fmt.Errorf("unexpected matches for gateway service: %d", itemCount)
}
gatewayService := gatewayServiceList.Items[0]
_, err = controllerutil.CreateOrUpdate(ctx, r.Client, studioDeployment, func() error {
studioDeployment.Labels = studioSpec.WorkloadTemplate.MergeLabels(
objectLabels(dashboard, serviceCfg.Name, "dashboard", supabase.Images.Studio.Tag),
dashboard.Labels,
)
if studioDeployment.CreationTimestamp.IsZero() {
studioDeployment.Spec.Selector = &metav1.LabelSelector{
MatchLabels: selectorLabels(dashboard, serviceCfg.Name),
}
}
studioDeployment.Spec.Replicas = studioSpec.WorkloadTemplate.ReplicaCount()
studioEnv := []corev1.EnvVar{
serviceCfg.EnvKeys.PGMetaURL.Var(fmt.Sprintf("http://%s.%s.svc:%d", supabase.ServiceConfig.PGMeta.ObjectName(dashboard), dashboard.Namespace, supabase.ServiceConfig.PGMeta.Defaults.APIPort)),
serviceCfg.EnvKeys.Host.Var(),
serviceCfg.EnvKeys.ApiUrl.Var(fmt.Sprintf("http://%s.%s.svc:8000", gatewayService.Name, gatewayService.Namespace)),
serviceCfg.EnvKeys.DBPassword.Var(dashboard.Spec.DBSpec.PasswordRef()),
serviceCfg.EnvKeys.APIExternalURL.Var(studioSpec.APIExternalURL),
serviceCfg.EnvKeys.JwtSecret.Var(studioSpec.JWT.SecretKeySelector()),
serviceCfg.EnvKeys.AnonKey.Var(studioSpec.JWT.AnonKeySelector()),
serviceCfg.EnvKeys.ServiceKey.Var(studioSpec.JWT.ServiceKeySelector()),
}
studioDeployment.Spec.Template = corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: objectLabels(dashboard, serviceCfg.Name, "dashboard", supabase.Images.Studio.Tag),
},
Spec: corev1.PodSpec{
ImagePullSecrets: studioSpec.WorkloadTemplate.PullSecrets(),
Containers: []corev1.Container{{
Name: "supabase-studio",
Image: studioSpec.WorkloadTemplate.Image(supabase.Images.Studio.String()),
ImagePullPolicy: studioSpec.WorkloadTemplate.ImagePullPolicy(),
Env: studioSpec.WorkloadTemplate.MergeEnv(studioEnv),
Ports: []corev1.ContainerPort{{
Name: "studio",
ContainerPort: serviceCfg.Defaults.APIPort,
Protocol: corev1.ProtocolTCP,
}},
SecurityContext: studioSpec.WorkloadTemplate.ContainerSecurityContext(serviceCfg.Defaults.NodeUID, serviceCfg.Defaults.NodeGID),
Resources: studioSpec.WorkloadTemplate.Resources(),
VolumeMounts: studioSpec.WorkloadTemplate.AdditionalVolumeMounts(corev1.VolumeMount{
Name: "next-cache",
MountPath: "/app/apps/studio/.next/cache",
}),
ReadinessProbe: &corev1.Probe{
InitialDelaySeconds: 5,
PeriodSeconds: 3,
TimeoutSeconds: 1,
SuccessThreshold: 2,
ProbeHandler: corev1.ProbeHandler{
HTTPGet: &corev1.HTTPGetAction{
Path: "/api/profile",
Port: intstr.IntOrString{IntVal: serviceCfg.Defaults.APIPort},
},
},
},
LivenessProbe: &corev1.Probe{
InitialDelaySeconds: 10,
PeriodSeconds: 5,
TimeoutSeconds: 3,
ProbeHandler: corev1.ProbeHandler{
HTTPGet: &corev1.HTTPGetAction{
Path: "/api/profile",
Port: intstr.IntOrString{IntVal: serviceCfg.Defaults.APIPort},
},
},
},
}},
SecurityContext: studioSpec.WorkloadTemplate.PodSecurityContext(),
Volumes: []corev1.Volume{{
Name: "next-cache",
VolumeSource: corev1.VolumeSource{
EmptyDir: &corev1.EmptyDirVolumeSource{
Medium: corev1.StorageMediumDefault,
SizeLimit: ptrOf(resource.MustParse("300Mi")),
},
},
}},
},
}
if err := controllerutil.SetControllerReference(dashboard, studioDeployment, r.Scheme); err != nil {
return err
}
return nil
})
return err
}
func (r *DashboardStudioReconciler) reconcileStudioService(
ctx context.Context,
dashboard *supabasev1alpha1.Dashboard,
) error {
studioService := &corev1.Service{
ObjectMeta: supabase.ServiceConfig.Studio.ObjectMeta(dashboard),
}
if dashboard.Spec.Studio == nil {
dashboard.Spec.Studio = new(supabasev1alpha1.StudioSpec)
}
_, err := controllerutil.CreateOrPatch(ctx, r.Client, studioService, func() error {
studioService.Labels = dashboard.Spec.Studio.WorkloadTemplate.MergeLabels(
objectLabels(dashboard, supabase.ServiceConfig.Studio.Name, "dashboard", supabase.Images.Studio.Tag),
dashboard.Labels,
)
if _, ok := studioService.Labels[meta.SupabaseLabel.EnvoyCluster]; !ok {
studioService.Labels[meta.SupabaseLabel.EnvoyCluster] = dashboard.Name
}
studioService.Spec = corev1.ServiceSpec{
Selector: selectorLabels(dashboard, supabase.ServiceConfig.Studio.Name),
Ports: []corev1.ServicePort{
{
Name: "studio",
Protocol: corev1.ProtocolTCP,
AppProtocol: ptrOf("http"),
Port: supabase.ServiceConfig.Studio.Defaults.APIPort,
TargetPort: intstr.IntOrString{IntVal: supabase.ServiceConfig.Studio.Defaults.APIPort},
},
},
}
if err := controllerutil.SetControllerReference(dashboard, studioService, r.Scheme); err != nil {
return err
}
return nil
})
return err
}

View file

@ -6,6 +6,9 @@ package controller
// +kubebuilder:rbac:groups=supabase.k8s.icb4dc0.de,resources=apigateways,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=supabase.k8s.icb4dc0.de,resources=apigateways/status,verbs=get;update;patch
// +kubebuilder:rbac:groups=supabase.k8s.icb4dc0.de,resources=apigateways/finalizers,verbs=update
// +kubebuilder:rbac:groups=supabase.k8s.icb4dc0.de,resources=dashboards,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=supabase.k8s.icb4dc0.de,resources=dashboards/status,verbs=get;update;patch
// +kubebuilder:rbac:groups=supabase.k8s.icb4dc0.de,resources=dashboards/finalizers,verbs=update
// +kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups="",resources=secrets;configmaps;services,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups="",resources=events,verbs=create