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

@ -17,6 +17,8 @@ limitations under the License.
package v1alpha1
import (
"maps"
corev1 "k8s.io/api/core/v1"
)
@ -42,3 +44,133 @@ type WorkloadTemplate struct {
// Workload - customize the container template of the workload
Workload *ContainerTemplate `json:"workload,omitempty"`
}
func (t *WorkloadTemplate) ReplicaCount() *int32 {
if t != nil && t.Replicas != nil {
return t.Replicas
}
return nil
}
func (t *WorkloadTemplate) MergeEnv(basicEnv []corev1.EnvVar) []corev1.EnvVar {
if t == nil || t.Workload == nil || len(t.Workload.AdditionalEnv) == 0 {
return basicEnv
}
existingKeys := make(map[string]bool, len(basicEnv)+len(t.Workload.AdditionalEnv))
merged := append(make([]corev1.EnvVar, 0, len(basicEnv)+len(t.Workload.AdditionalEnv)), basicEnv...)
for _, v := range basicEnv {
existingKeys[v.Name] = true
}
for _, v := range t.Workload.AdditionalEnv {
if _, alreadyPresent := existingKeys[v.Name]; alreadyPresent {
continue
}
merged = append(merged, v)
existingKeys[v.Name] = true
}
return merged
}
func (t *WorkloadTemplate) MergeLabels(initial map[string]string, toAppend ...map[string]string) map[string]string {
result := make(map[string]string)
maps.Copy(result, initial)
var labelSets []map[string]string
if t != nil && len(t.AdditionalLabels) > 0 {
labelSets = append(labelSets, t.AdditionalLabels)
}
labelSets = append(labelSets, toAppend...)
for _, lbls := range labelSets {
for k, v := range lbls {
if _, ok := result[k]; !ok {
result[k] = v
}
}
}
return result
}
func (t *WorkloadTemplate) Image(defaultImage string) string {
if t != nil && t.Workload != nil && t.Workload.Image != "" {
return t.Workload.Image
}
return defaultImage
}
func (t *WorkloadTemplate) ImagePullPolicy() corev1.PullPolicy {
if t != nil && t.Workload != nil && t.Workload.PullPolicy != "" {
return t.Workload.PullPolicy
}
return corev1.PullIfNotPresent
}
func (t *WorkloadTemplate) PullSecrets() []corev1.LocalObjectReference {
if t != nil && t.Workload != nil && len(t.Workload.ImagePullSecrets) > 0 {
return t.Workload.ImagePullSecrets
}
return nil
}
func (t *WorkloadTemplate) Resources() corev1.ResourceRequirements {
if t != nil && t.Workload != nil {
return t.Workload.Resources
}
return corev1.ResourceRequirements{}
}
func (t *WorkloadTemplate) AdditionalVolumeMounts(defaultMounts ...corev1.VolumeMount) []corev1.VolumeMount {
if t != nil && t.Workload != nil {
return append(defaultMounts, t.Workload.VolumeMounts...)
}
return defaultMounts
}
func (t *WorkloadTemplate) PodSecurityContext() *corev1.PodSecurityContext {
if t != nil && t.SecurityContext != nil {
return t.SecurityContext
}
return &corev1.PodSecurityContext{
RunAsNonRoot: ptrOf(true),
}
}
func (t *WorkloadTemplate) ContainerSecurityContext(uid, gid int64) *corev1.SecurityContext {
if t != nil && t.Workload != nil && t.Workload.SecurityContext != nil {
return t.Workload.SecurityContext
}
return &corev1.SecurityContext{
Privileged: ptrOf(false),
RunAsUser: ptrOf(uid),
RunAsGroup: ptrOf(gid),
RunAsNonRoot: ptrOf(true),
AllowPrivilegeEscalation: ptrOf(false),
ReadOnlyRootFilesystem: ptrOf(true),
Capabilities: &corev1.Capabilities{
Drop: []corev1.Capability{
"ALL",
},
},
}
}
func ptrOf[T any](val T) *T {
return &val
}

View file

@ -90,7 +90,7 @@ func (d Database) DSNEnv(key string) corev1.EnvVar {
}
}
type JwtSpec struct {
type CoreJwtSpec struct {
// Secret - JWT HMAC secret in plain text
// This is WRITE-ONLY and will be copied to the SecretRef by the defaulter
Secret *string `json:"secret,omitempty"`
@ -113,7 +113,7 @@ type JwtSpec struct {
Expiry int `json:"expiry,omitempty"`
}
func (s JwtSpec) GetJWTSecret(ctx context.Context, client client.Client) ([]byte, error) {
func (s CoreJwtSpec) GetJWTSecret(ctx context.Context, client client.Client) ([]byte, error) {
var secret corev1.Secret
if err := client.Get(ctx, types.NamespacedName{Name: s.SecretRef.Name}, &secret); err != nil {
return nil, nil
@ -127,21 +127,21 @@ func (s JwtSpec) GetJWTSecret(ctx context.Context, client client.Client) ([]byte
return value, nil
}
func (s JwtSpec) SecretKeySelector() *corev1.SecretKeySelector {
func (s CoreJwtSpec) SecretKeySelector() *corev1.SecretKeySelector {
return &corev1.SecretKeySelector{
LocalObjectReference: *s.SecretRef,
Key: s.SecretKey,
}
}
func (s JwtSpec) JwksKeySelector() *corev1.SecretKeySelector {
func (s CoreJwtSpec) JwksKeySelector() *corev1.SecretKeySelector {
return &corev1.SecretKeySelector{
LocalObjectReference: *s.SecretRef,
Key: s.JwksKey,
}
}
func (s JwtSpec) SecretAsEnv(key string) corev1.EnvVar {
func (s CoreJwtSpec) SecretAsEnv(key string) corev1.EnvVar {
return corev1.EnvVar{
Name: key,
ValueFrom: &corev1.EnvVarSource{
@ -153,7 +153,7 @@ func (s JwtSpec) SecretAsEnv(key string) corev1.EnvVar {
}
}
func (s JwtSpec) ExpiryAsEnv(key string) corev1.EnvVar {
func (s CoreJwtSpec) ExpiryAsEnv(key string) corev1.EnvVar {
return corev1.EnvVar{
Name: key,
Value: strconv.Itoa(s.Expiry),
@ -358,12 +358,6 @@ func (p *AuthProviders) Vars(apiExternalURL string) []corev1.EnvVar {
}
type AuthSpec struct {
// APIExternalURL is referring to the URL where Supabase API will be available
// Typically this is the ingress of the API gateway
APIExternalURL string `json:"externalUrl"`
// SiteURL is referring to the URL of the (frontend) application
// In most Kubernetes scenarios this is the same as the APIExternalURL with a different path handler in the ingress
SiteURL string `json:"siteUrl"`
AdditionalRedirectUrls []string `json:"additionalRedirectUrls,omitempty"`
DisableSignup *bool `json:"disableSignup,omitempty"`
AnonymousUsersEnabled *bool `json:"anonymousUsersEnabled,omitempty"`
@ -374,7 +368,13 @@ type AuthSpec struct {
// CoreSpec defines the desired state of Core.
type CoreSpec struct {
JWT *JwtSpec `json:"jwt,omitempty"`
// APIExternalURL is referring to the URL where Supabase API will be available
// Typically this is the ingress of the API gateway
APIExternalURL string `json:"externalUrl"`
// SiteURL is referring to the URL of the (frontend) application
// In most Kubernetes scenarios this is the same as the APIExternalURL with a different path handler in the ingress
SiteURL string `json:"siteUrl"`
JWT *CoreJwtSpec `json:"jwt,omitempty"`
Database Database `json:"database,omitempty"`
Postgrest PostgrestSpec `json:"postgrest,omitempty"`
Auth *AuthSpec `json:"auth,omitempty"`

View file

@ -21,9 +21,53 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
type DashboardJwtSpec struct {
// SecretRef - object reference to the Secret where JWT values are stored
SecretRef *corev1.LocalObjectReference `json:"secretRef,omitempty"`
// SecretKey - key in secret where to read the JWT HMAC secret from
// +kubebuilder:default=secret
SecretKey string `json:"secretKey,omitempty"`
// AnonKey - key in secret where to read the anon JWT from
// +kubebuilder:default=anon_key
AnonKey string `json:"anonKey,omitempty"`
// ServiceKey - key in secret where to read the service JWT from
// +kubebuilder:default=service_key
ServiceKey string `json:"serviceKey,omitempty"`
}
func (s DashboardJwtSpec) SecretKeySelector() *corev1.SecretKeySelector {
return &corev1.SecretKeySelector{
LocalObjectReference: *s.SecretRef,
Key: s.SecretKey,
}
}
func (s DashboardJwtSpec) AnonKeySelector() *corev1.SecretKeySelector {
return &corev1.SecretKeySelector{
LocalObjectReference: *s.SecretRef,
Key: s.AnonKey,
}
}
func (s DashboardJwtSpec) ServiceKeySelector() *corev1.SecretKeySelector {
return &corev1.SecretKeySelector{
LocalObjectReference: *s.SecretRef,
Key: s.ServiceKey,
}
}
type StudioSpec struct {
JWT *DashboardJwtSpec `json:"jwt,omitempty"`
// WorkloadTemplate - customize the studio deployment
WorkloadTemplate *WorkloadTemplate `json:"workloadTemplate,omitempty"`
// GatewayServiceSelector - selector to find the service for the API gateway
// Required to configure the API URL in the studio deployment
// If you don't run multiple APIGateway instances in the same namespaces, the default will be fine
// +kubebuilder:default={"app.kubernetes.io/name":"envoy","app.kubernetes.io/component":"api-gateway"}
GatewayServiceMatchLabels map[string]string `json:"gatewayServiceSelector,omitempty"`
// APIExternalURL is referring to the URL where Supabase API will be available
// Typically this is the ingress of the API gateway
APIExternalURL string `json:"externalUrl"`
}
type PGMetaSpec struct {
@ -60,10 +104,8 @@ func (s DashboardDbSpec) PasswordRef() *corev1.SecretKeySelector {
type DashboardSpec struct {
DBSpec *DashboardDbSpec `json:"db"`
// PGMeta
// +kubebuilder:default={}
PGMeta *PGMetaSpec `json:"pgMeta,omitempty"`
// Studio
// +kubebuilder:default={}
Studio *StudioSpec `json:"studio,omitempty"`
}

View file

@ -319,6 +319,31 @@ func (in *Core) DeepCopyObject() runtime.Object {
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *CoreJwtSpec) DeepCopyInto(out *CoreJwtSpec) {
*out = *in
if in.Secret != nil {
in, out := &in.Secret, &out.Secret
*out = new(string)
**out = **in
}
if in.SecretRef != nil {
in, out := &in.SecretRef, &out.SecretRef
*out = new(v1.LocalObjectReference)
**out = **in
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CoreJwtSpec.
func (in *CoreJwtSpec) DeepCopy() *CoreJwtSpec {
if in == nil {
return nil
}
out := new(CoreJwtSpec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *CoreList) DeepCopyInto(out *CoreList) {
*out = *in
@ -356,7 +381,7 @@ func (in *CoreSpec) DeepCopyInto(out *CoreSpec) {
*out = *in
if in.JWT != nil {
in, out := &in.JWT, &out.JWT
*out = new(JwtSpec)
*out = new(CoreJwtSpec)
(*in).DeepCopyInto(*out)
}
in.Database.DeepCopyInto(&out.Database)
@ -441,6 +466,26 @@ func (in *DashboardDbSpec) DeepCopy() *DashboardDbSpec {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *DashboardJwtSpec) DeepCopyInto(out *DashboardJwtSpec) {
*out = *in
if in.SecretRef != nil {
in, out := &in.SecretRef, &out.SecretRef
*out = new(v1.LocalObjectReference)
**out = **in
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DashboardJwtSpec.
func (in *DashboardJwtSpec) DeepCopy() *DashboardJwtSpec {
if in == nil {
return nil
}
out := new(DashboardJwtSpec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *DashboardList) DeepCopyInto(out *DashboardList) {
*out = *in
@ -751,31 +796,6 @@ func (in *ImageSpec) DeepCopy() *ImageSpec {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *JwtSpec) DeepCopyInto(out *JwtSpec) {
*out = *in
if in.Secret != nil {
in, out := &in.Secret, &out.Secret
*out = new(string)
**out = **in
}
if in.SecretRef != nil {
in, out := &in.SecretRef, &out.SecretRef
*out = new(v1.LocalObjectReference)
**out = **in
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JwtSpec.
func (in *JwtSpec) DeepCopy() *JwtSpec {
if in == nil {
return nil
}
out := new(JwtSpec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in MigrationStatus) DeepCopyInto(out *MigrationStatus) {
{
@ -886,11 +906,23 @@ func (in *PostgrestSpec) DeepCopy() *PostgrestSpec {
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *StudioSpec) DeepCopyInto(out *StudioSpec) {
*out = *in
if in.JWT != nil {
in, out := &in.JWT, &out.JWT
*out = new(DashboardJwtSpec)
(*in).DeepCopyInto(*out)
}
if in.WorkloadTemplate != nil {
in, out := &in.WorkloadTemplate, &out.WorkloadTemplate
*out = new(WorkloadTemplate)
(*in).DeepCopyInto(*out)
}
if in.GatewayServiceMatchLabels != nil {
in, out := &in.GatewayServiceMatchLabels, &out.GatewayServiceMatchLabels
*out = make(map[string]string, len(*in))
for key, val := range *in {
(*out)[key] = val
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StudioSpec.