feat(dashboard): initial support for studio & pg-meta services
This commit is contained in:
parent
7d9e518f86
commit
0b551325b9
31 changed files with 2151 additions and 492 deletions
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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"`
|
||||
|
|
|
@ -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"`
|
||||
}
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue