2025-01-20 17:20:04 +01:00
|
|
|
/*
|
|
|
|
Copyright 2025 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.
|
|
|
|
*/
|
|
|
|
|
2025-01-04 17:07:49 +01:00
|
|
|
package controlplane
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"slices"
|
|
|
|
|
|
|
|
corev3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
|
2025-01-11 16:51:05 +01:00
|
|
|
listenerv3 "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3"
|
2025-01-04 17:07:49 +01:00
|
|
|
route "github.com/envoyproxy/go-control-plane/envoy/config/route/v3"
|
|
|
|
router "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/router/v3"
|
|
|
|
hcm "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3"
|
|
|
|
"github.com/envoyproxy/go-control-plane/pkg/cache/types"
|
|
|
|
"github.com/envoyproxy/go-control-plane/pkg/cache/v3"
|
|
|
|
"github.com/envoyproxy/go-control-plane/pkg/resource/v3"
|
|
|
|
"google.golang.org/protobuf/types/known/anypb"
|
|
|
|
discoveryv1 "k8s.io/api/discovery/v1"
|
|
|
|
"sigs.k8s.io/controller-runtime/pkg/log"
|
|
|
|
|
|
|
|
"code.icb4dc0.de/prskr/supabase-operator/internal/supabase"
|
|
|
|
)
|
|
|
|
|
2025-01-20 17:06:41 +01:00
|
|
|
type EnvoyServices struct {
|
|
|
|
ServiceLabelKey string `json:"-"`
|
|
|
|
Postgrest *PostgrestCluster `json:"postgrest,omitempty"`
|
|
|
|
GoTrue *GoTrueCluster `json:"auth,omitempty"`
|
|
|
|
PGMeta *PGMetaCluster `json:"pgmeta,omitempty"`
|
|
|
|
Studio *StudioCluster `json:"studio,omitempty"`
|
2025-01-04 17:07:49 +01:00
|
|
|
}
|
|
|
|
|
2025-01-20 17:06:41 +01:00
|
|
|
func (s *EnvoyServices) UpsertEndpointSlices(endpointSlices ...discoveryv1.EndpointSlice) {
|
|
|
|
for _, eps := range endpointSlices {
|
|
|
|
switch eps.Labels[s.ServiceLabelKey] {
|
|
|
|
case supabase.ServiceConfig.Postgrest.Name:
|
|
|
|
if s.Postgrest == nil {
|
|
|
|
s.Postgrest = new(PostgrestCluster)
|
2025-01-04 17:07:49 +01:00
|
|
|
}
|
2025-01-20 17:06:41 +01:00
|
|
|
s.Postgrest.AddOrUpdateEndpoints(eps)
|
|
|
|
case supabase.ServiceConfig.Auth.Name:
|
|
|
|
if s.GoTrue == nil {
|
|
|
|
s.GoTrue = new(GoTrueCluster)
|
|
|
|
}
|
|
|
|
s.GoTrue.AddOrUpdateEndpoints(eps)
|
|
|
|
case supabase.ServiceConfig.PGMeta.Name:
|
|
|
|
if s.PGMeta == nil {
|
|
|
|
s.PGMeta = new(PGMetaCluster)
|
2025-01-04 17:07:49 +01:00
|
|
|
}
|
2025-01-20 17:06:41 +01:00
|
|
|
s.PGMeta.AddOrUpdateEndpoints(eps)
|
2025-01-04 17:07:49 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-01-20 17:06:41 +01:00
|
|
|
func (s EnvoyServices) Targets() map[string][]string {
|
|
|
|
targets := make(map[string][]string)
|
2025-01-04 17:07:49 +01:00
|
|
|
|
2025-01-20 17:06:41 +01:00
|
|
|
if s.Postgrest != nil {
|
|
|
|
targets[supabase.ServiceConfig.Postgrest.Name] = s.Postgrest.Targets()
|
2025-01-04 17:07:49 +01:00
|
|
|
}
|
|
|
|
|
2025-01-20 17:06:41 +01:00
|
|
|
if s.GoTrue != nil {
|
|
|
|
targets[supabase.ServiceConfig.Auth.Name] = s.GoTrue.Targets()
|
2025-01-04 17:07:49 +01:00
|
|
|
}
|
|
|
|
|
2025-01-20 17:06:41 +01:00
|
|
|
if s.PGMeta != nil {
|
|
|
|
targets[supabase.ServiceConfig.PGMeta.Name] = s.PGMeta.Targets()
|
2025-01-04 17:07:49 +01:00
|
|
|
}
|
|
|
|
|
2025-01-20 17:06:41 +01:00
|
|
|
if s.Studio != nil {
|
|
|
|
targets[supabase.ServiceConfig.Studio.Name] = s.Studio.Targets()
|
2025-01-04 17:07:49 +01:00
|
|
|
}
|
|
|
|
|
2025-01-20 17:06:41 +01:00
|
|
|
return targets
|
2025-01-04 17:07:49 +01:00
|
|
|
}
|
|
|
|
|
2025-01-20 17:06:41 +01:00
|
|
|
func (s *EnvoyServices) snapshot(ctx context.Context, instance, version string) (*cache.Snapshot, error) {
|
2025-01-04 17:07:49 +01:00
|
|
|
const (
|
2025-01-11 16:51:05 +01:00
|
|
|
apiRouteName = "supabase"
|
|
|
|
studioRouteName = "supabas-studio"
|
|
|
|
vHostName = "supabase"
|
|
|
|
listenerName = "supabase"
|
2025-01-04 17:07:49 +01:00
|
|
|
)
|
|
|
|
|
2025-01-20 17:06:41 +01:00
|
|
|
logger := log.FromContext(ctx)
|
|
|
|
|
2025-01-11 16:51:05 +01:00
|
|
|
apiConnectionManager := &hcm.HttpConnectionManager{
|
2025-01-04 17:07:49 +01:00
|
|
|
CodecType: hcm.HttpConnectionManager_AUTO,
|
|
|
|
StatPrefix: "http",
|
|
|
|
RouteSpecifier: &hcm.HttpConnectionManager_Rds{
|
|
|
|
Rds: &hcm.Rds{
|
|
|
|
ConfigSource: &corev3.ConfigSource{
|
|
|
|
ResourceApiVersion: resource.DefaultAPIVersion,
|
|
|
|
ConfigSourceSpecifier: &corev3.ConfigSource_ApiConfigSource{
|
|
|
|
ApiConfigSource: &corev3.ApiConfigSource{
|
|
|
|
TransportApiVersion: resource.DefaultAPIVersion,
|
|
|
|
ApiType: corev3.ApiConfigSource_GRPC,
|
|
|
|
SetNodeOnFirstMessageOnly: true,
|
|
|
|
GrpcServices: []*corev3.GrpcService{{
|
|
|
|
TargetSpecifier: &corev3.GrpcService_EnvoyGrpc_{
|
|
|
|
EnvoyGrpc: &corev3.GrpcService_EnvoyGrpc{ClusterName: "supabase-control-plane"},
|
|
|
|
},
|
|
|
|
}},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2025-01-11 16:51:05 +01:00
|
|
|
RouteConfigName: apiRouteName,
|
2025-01-04 17:07:49 +01:00
|
|
|
},
|
|
|
|
},
|
|
|
|
HttpFilters: []*hcm.HttpFilter{
|
|
|
|
{
|
|
|
|
Name: FilterNameJwtAuthn,
|
|
|
|
ConfigType: &hcm.HttpFilter_TypedConfig{TypedConfig: MustAny(JWTFilterConfig())},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Name: FilterNameCORS,
|
|
|
|
ConfigType: &hcm.HttpFilter_TypedConfig{TypedConfig: MustAny(Cors())},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Name: FilterNameHttpRouter,
|
|
|
|
ConfigType: &hcm.HttpFilter_TypedConfig{TypedConfig: MustAny(new(router.Router))},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2025-01-11 16:51:05 +01:00
|
|
|
studioConnetionManager := &hcm.HttpConnectionManager{
|
|
|
|
CodecType: hcm.HttpConnectionManager_AUTO,
|
|
|
|
StatPrefix: "http",
|
|
|
|
RouteSpecifier: &hcm.HttpConnectionManager_Rds{
|
|
|
|
Rds: &hcm.Rds{
|
|
|
|
ConfigSource: &corev3.ConfigSource{
|
|
|
|
ResourceApiVersion: resource.DefaultAPIVersion,
|
|
|
|
ConfigSourceSpecifier: &corev3.ConfigSource_ApiConfigSource{
|
|
|
|
ApiConfigSource: &corev3.ApiConfigSource{
|
|
|
|
TransportApiVersion: resource.DefaultAPIVersion,
|
|
|
|
ApiType: corev3.ApiConfigSource_GRPC,
|
|
|
|
SetNodeOnFirstMessageOnly: true,
|
|
|
|
GrpcServices: []*corev3.GrpcService{{
|
|
|
|
TargetSpecifier: &corev3.GrpcService_EnvoyGrpc_{
|
|
|
|
EnvoyGrpc: &corev3.GrpcService_EnvoyGrpc{ClusterName: "supabase-control-plane"},
|
|
|
|
},
|
|
|
|
}},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
RouteConfigName: studioRouteName,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
HttpFilters: []*hcm.HttpFilter{
|
|
|
|
{
|
|
|
|
Name: FilterNameHttpRouter,
|
|
|
|
ConfigType: &hcm.HttpFilter_TypedConfig{TypedConfig: MustAny(new(router.Router))},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
apiRouteCfg := &route.RouteConfiguration{
|
|
|
|
Name: apiRouteName,
|
2025-01-04 17:07:49 +01:00
|
|
|
VirtualHosts: []*route.VirtualHost{{
|
|
|
|
Name: "supabase",
|
|
|
|
Domains: []string{"*"},
|
|
|
|
TypedPerFilterConfig: map[string]*anypb.Any{
|
|
|
|
FilterNameJwtAuthn: MustAny(JWTPerRouteConfig()),
|
|
|
|
FilterNameRBAC: MustAny(RBACPerRoute(RBACRequireAuthConfig())),
|
|
|
|
},
|
|
|
|
Routes: slices.Concat(
|
|
|
|
s.Postgrest.Routes(instance),
|
|
|
|
s.GoTrue.Routes(instance),
|
|
|
|
s.PGMeta.Routes(instance),
|
|
|
|
),
|
|
|
|
}},
|
|
|
|
TypedPerFilterConfig: map[string]*anypb.Any{
|
|
|
|
FilterNameCORS: MustAny(CorsPolicy()),
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2025-01-11 16:51:05 +01:00
|
|
|
// TODO add studio route config
|
|
|
|
|
|
|
|
listeners := []*listenerv3.Listener{{
|
2025-01-04 17:07:49 +01:00
|
|
|
Name: listenerName,
|
|
|
|
Address: &corev3.Address{
|
|
|
|
Address: &corev3.Address_SocketAddress{
|
|
|
|
SocketAddress: &corev3.SocketAddress{
|
|
|
|
Protocol: corev3.SocketAddress_TCP,
|
|
|
|
Address: "0.0.0.0",
|
|
|
|
PortSpecifier: &corev3.SocketAddress_PortValue{
|
|
|
|
PortValue: 8000,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2025-01-11 16:51:05 +01:00
|
|
|
FilterChains: []*listenerv3.FilterChain{
|
2025-01-04 17:07:49 +01:00
|
|
|
{
|
2025-01-11 16:51:05 +01:00
|
|
|
Filters: []*listenerv3.Filter{
|
2025-01-04 17:07:49 +01:00
|
|
|
{
|
|
|
|
Name: FilterNameHttpConnectionManager,
|
2025-01-11 16:51:05 +01:00
|
|
|
ConfigType: &listenerv3.Filter_TypedConfig{
|
|
|
|
TypedConfig: MustAny(apiConnectionManager),
|
2025-01-04 17:07:49 +01:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2025-01-11 16:51:05 +01:00
|
|
|
}}
|
|
|
|
|
|
|
|
if s.Studio != nil {
|
2025-01-20 17:06:41 +01:00
|
|
|
logger.Info("Adding studio listener")
|
|
|
|
|
2025-01-11 16:51:05 +01:00
|
|
|
listeners = append(listeners, &listenerv3.Listener{
|
|
|
|
Name: "studio",
|
|
|
|
Address: &corev3.Address{
|
|
|
|
Address: &corev3.Address_SocketAddress{
|
|
|
|
SocketAddress: &corev3.SocketAddress{
|
|
|
|
Protocol: corev3.SocketAddress_TCP,
|
|
|
|
Address: "0.0.0.0",
|
|
|
|
PortSpecifier: &corev3.SocketAddress_PortValue{
|
|
|
|
PortValue: 3000,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
FilterChains: []*listenerv3.FilterChain{
|
|
|
|
{
|
|
|
|
Filters: []*listenerv3.Filter{
|
|
|
|
{
|
|
|
|
Name: FilterNameHttpConnectionManager,
|
|
|
|
ConfigType: &listenerv3.Filter_TypedConfig{
|
|
|
|
TypedConfig: MustAny(studioConnetionManager),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
})
|
2025-01-04 17:07:49 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
rawSnapshot := map[resource.Type][]types.Resource{
|
|
|
|
resource.ClusterType: castResources(
|
|
|
|
slices.Concat(
|
|
|
|
s.Postgrest.Cluster(instance),
|
|
|
|
s.GoTrue.Cluster(instance),
|
|
|
|
s.PGMeta.Cluster(instance),
|
|
|
|
)...),
|
2025-01-11 16:51:05 +01:00
|
|
|
resource.RouteType: {apiRouteCfg},
|
|
|
|
resource.ListenerType: castResources(listeners...),
|
2025-01-04 17:07:49 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
snapshot, err := cache.NewSnapshot(
|
|
|
|
version,
|
|
|
|
rawSnapshot,
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := snapshot.Consistent(); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return snapshot, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func castResources[T types.Resource](from ...T) []types.Resource {
|
|
|
|
result := make([]types.Resource, len(from))
|
|
|
|
for idx := range from {
|
|
|
|
result[idx] = from[idx]
|
|
|
|
}
|
|
|
|
|
|
|
|
return result
|
|
|
|
}
|