refactor: implement control plane as controller-runtime manager
This commit is contained in:
parent
a5c170a478
commit
3104f50c58
67 changed files with 3693 additions and 261 deletions
|
@ -27,6 +27,7 @@ linters:
|
|||
- gocyclo
|
||||
- gofmt
|
||||
- goimports
|
||||
- goheader
|
||||
- gosimple
|
||||
- godox
|
||||
- govet
|
||||
|
@ -56,6 +57,12 @@ linters-settings:
|
|||
- dot
|
||||
goimports:
|
||||
local-prefixes: code.icb4dc0.de/prskr/supabase-operator
|
||||
goheader:
|
||||
values:
|
||||
const:
|
||||
AUTHOR: Peter Kurfer
|
||||
template-path: hack/header.tmpl
|
||||
|
||||
importas:
|
||||
no-unaliased: true
|
||||
no-extra-aliases: true
|
||||
|
|
9
PROJECT
9
PROJECT
|
@ -47,4 +47,13 @@ resources:
|
|||
defaulting: true
|
||||
validation: true
|
||||
webhookVersion: v1
|
||||
- api:
|
||||
crdVersion: v1
|
||||
namespaced: true
|
||||
controller: true
|
||||
domain: k8s.icb4dc0.de
|
||||
group: supabase
|
||||
kind: Storage
|
||||
path: code.icb4dc0.de/prskr/supabase-operator/api/v1alpha1
|
||||
version: v1alpha1
|
||||
version: "3"
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2024 Peter Kurfer.
|
||||
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.
|
||||
|
@ -54,15 +54,30 @@ type APIGatewaySpec struct {
|
|||
Envoy *EnvoySpec `json:"envoy"`
|
||||
// JWKSSelector - selector where the JWKS can be retrieved from to enable the API gateway to validate JWTs
|
||||
JWKSSelector *corev1.SecretKeySelector `json:"jwks"`
|
||||
// ServiceSelector - selector to match all Supabase services (or in fact EndpointSlices) that should be considered for this APIGateway
|
||||
// +kubebuilder:default={"matchExpressions":{{"key": "app.kubernetes.io/part-of", "operator":"In", "values":{"supabase"}},{"key":"supabase.k8s.icb4dc0.de/api-gateway-target","operator":"Exists"}}}
|
||||
ServiceSelector *metav1.LabelSelector `json:"serviceSelector"`
|
||||
// ComponentTypeLabel - Label to identify which Supabase component a Service represents (e.g. auth, postgrest, ...)
|
||||
// +kubebuilder:default="app.kubernetes.io/name"
|
||||
ComponentTypeLabel string `json:"componentTypeLabel,omitempty"`
|
||||
}
|
||||
|
||||
type EnvoyStatus struct {
|
||||
ConfigVersion string `json:"configVersion,omitempty"`
|
||||
ResourceHash []byte `json:"resourceHash,omitempty"`
|
||||
}
|
||||
|
||||
// APIGatewayStatus defines the observed state of APIGateway.
|
||||
type APIGatewayStatus struct{}
|
||||
type APIGatewayStatus struct {
|
||||
Envoy EnvoyStatus `json:"envoy,omitempty"`
|
||||
ServiceTargets map[string][]string `json:"serviceTargets,omitempty"`
|
||||
}
|
||||
|
||||
// +kubebuilder:object:root=true
|
||||
// +kubebuilder:subresource:status
|
||||
|
||||
// APIGateway is the Schema for the apigateways API.
|
||||
// +kubebuilder:printcolumn:name="EnvoyConfigVersion",type=string,JSONPath=`.status.envoy.configVersion`
|
||||
type APIGateway struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||
|
|
64
api/v1alpha1/storage_types.go
Normal file
64
api/v1alpha1/storage_types.go
Normal file
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN!
|
||||
// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized.
|
||||
|
||||
// StorageSpec defines the desired state of Storage.
|
||||
type StorageSpec struct {
|
||||
// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
|
||||
// Important: Run "make" to regenerate code after modifying this file
|
||||
|
||||
// Foo is an example field of Storage. Edit storage_types.go to remove/update
|
||||
Foo string `json:"foo,omitempty"`
|
||||
}
|
||||
|
||||
// StorageStatus defines the observed state of Storage.
|
||||
type StorageStatus struct {
|
||||
// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
|
||||
// Important: Run "make" to regenerate code after modifying this file
|
||||
}
|
||||
|
||||
// +kubebuilder:object:root=true
|
||||
// +kubebuilder:subresource:status
|
||||
|
||||
// Storage is the Schema for the storages API.
|
||||
type Storage struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||
|
||||
Spec StorageSpec `json:"spec,omitempty"`
|
||||
Status StorageStatus `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
// +kubebuilder:object:root=true
|
||||
|
||||
// StorageList contains a list of Storage.
|
||||
type StorageList struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ListMeta `json:"metadata,omitempty"`
|
||||
Items []Storage `json:"items"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
SchemeBuilder.Register(&Storage{}, &StorageList{})
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
//go:build !ignore_autogenerated
|
||||
|
||||
/*
|
||||
Copyright 2024 Peter Kurfer.
|
||||
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.
|
||||
|
@ -22,6 +22,7 @@ package v1alpha1
|
|||
|
||||
import (
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
|
@ -31,7 +32,7 @@ func (in *APIGateway) DeepCopyInto(out *APIGateway) {
|
|||
out.TypeMeta = in.TypeMeta
|
||||
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
|
||||
in.Spec.DeepCopyInto(&out.Spec)
|
||||
out.Status = in.Status
|
||||
in.Status.DeepCopyInto(&out.Status)
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new APIGateway.
|
||||
|
@ -97,6 +98,11 @@ func (in *APIGatewaySpec) DeepCopyInto(out *APIGatewaySpec) {
|
|||
*out = new(v1.SecretKeySelector)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.ServiceSelector != nil {
|
||||
in, out := &in.ServiceSelector, &out.ServiceSelector
|
||||
*out = new(metav1.LabelSelector)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new APIGatewaySpec.
|
||||
|
@ -112,6 +118,23 @@ func (in *APIGatewaySpec) DeepCopy() *APIGatewaySpec {
|
|||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *APIGatewayStatus) DeepCopyInto(out *APIGatewayStatus) {
|
||||
*out = *in
|
||||
in.Envoy.DeepCopyInto(&out.Envoy)
|
||||
if in.ServiceTargets != nil {
|
||||
in, out := &in.ServiceTargets, &out.ServiceTargets
|
||||
*out = make(map[string][]string, len(*in))
|
||||
for key, val := range *in {
|
||||
var outVal []string
|
||||
if val == nil {
|
||||
(*out)[key] = nil
|
||||
} else {
|
||||
inVal := (*in)[key]
|
||||
in, out := &inVal, &outVal
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
(*out)[key] = outVal
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new APIGatewayStatus.
|
||||
|
@ -764,6 +787,26 @@ func (in *EnvoySpec) DeepCopy() *EnvoySpec {
|
|||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *EnvoyStatus) DeepCopyInto(out *EnvoyStatus) {
|
||||
*out = *in
|
||||
if in.ResourceHash != nil {
|
||||
in, out := &in.ResourceHash, &out.ResourceHash
|
||||
*out = make([]byte, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EnvoyStatus.
|
||||
func (in *EnvoyStatus) DeepCopy() *EnvoyStatus {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(EnvoyStatus)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *GithubAuthProvider) DeepCopyInto(out *GithubAuthProvider) {
|
||||
*out = *in
|
||||
|
@ -903,6 +946,95 @@ func (in *PostgrestSpec) DeepCopy() *PostgrestSpec {
|
|||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *Storage) DeepCopyInto(out *Storage) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
|
||||
out.Spec = in.Spec
|
||||
out.Status = in.Status
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Storage.
|
||||
func (in *Storage) DeepCopy() *Storage {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(Storage)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *Storage) DeepCopyObject() runtime.Object {
|
||||
if c := in.DeepCopy(); c != nil {
|
||||
return c
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *StorageList) DeepCopyInto(out *StorageList) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ListMeta.DeepCopyInto(&out.ListMeta)
|
||||
if in.Items != nil {
|
||||
in, out := &in.Items, &out.Items
|
||||
*out = make([]Storage, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StorageList.
|
||||
func (in *StorageList) DeepCopy() *StorageList {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(StorageList)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *StorageList) DeepCopyObject() runtime.Object {
|
||||
if c := in.DeepCopy(); c != nil {
|
||||
return c
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *StorageSpec) DeepCopyInto(out *StorageSpec) {
|
||||
*out = *in
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StorageSpec.
|
||||
func (in *StorageSpec) DeepCopy() *StorageSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(StorageSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *StorageStatus) DeepCopyInto(out *StorageStatus) {
|
||||
*out = *in
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StorageStatus.
|
||||
func (in *StorageStatus) DeepCopy() *StorageStatus {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(StorageStatus)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// 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
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2024 Peter Kurfer.
|
||||
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.
|
||||
|
@ -18,12 +18,11 @@ package main
|
|||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/alecthomas/kong"
|
||||
clusterservice "github.com/envoyproxy/go-control-plane/envoy/service/cluster/v3"
|
||||
discoverygrpc "github.com/envoyproxy/go-control-plane/envoy/service/discovery/v3"
|
||||
endpointservice "github.com/envoyproxy/go-control-plane/envoy/service/endpoint/v3"
|
||||
|
@ -31,7 +30,7 @@ import (
|
|||
routeservice "github.com/envoyproxy/go-control-plane/envoy/service/route/v3"
|
||||
runtimeservice "github.com/envoyproxy/go-control-plane/envoy/service/runtime/v3"
|
||||
secretservice "github.com/envoyproxy/go-control-plane/envoy/service/secret/v3"
|
||||
"github.com/envoyproxy/go-control-plane/pkg/cache/v3"
|
||||
cachev3 "github.com/envoyproxy/go-control-plane/pkg/cache/v3"
|
||||
"github.com/envoyproxy/go-control-plane/pkg/server/v3"
|
||||
"google.golang.org/grpc"
|
||||
grpchealth "google.golang.org/grpc/health"
|
||||
|
@ -39,16 +38,103 @@ import (
|
|||
"google.golang.org/grpc/keepalive"
|
||||
"google.golang.org/grpc/reflection"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
mgr "sigs.k8s.io/controller-runtime/pkg/manager"
|
||||
"sigs.k8s.io/controller-runtime/pkg/metrics/filters"
|
||||
metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server"
|
||||
|
||||
"code.icb4dc0.de/prskr/supabase-operator/internal/controlplane"
|
||||
)
|
||||
|
||||
//nolint:lll // flag declaration with struct tags is as long as it is
|
||||
type controlPlane struct {
|
||||
ListenAddr string `name:"listen-address" default:":18000" help:"The address the control plane binds to."`
|
||||
ListenAddr string `name:"listen-address" default:":18000" help:"The address the control plane binds to."`
|
||||
MetricsAddr string `name:"metrics-bind-address" default:"0" help:"The address the metrics endpoint binds to. Use :8443 for HTTPS or :8080 for HTTP, or leave as 0 to disable the metrics service."`
|
||||
EnableLeaderElection bool `name:"leader-elect" default:"false" help:"Enable leader election for controller manager. Enabling this will ensure there is only one active controller manager."`
|
||||
ProbeAddr string `name:"health-probe-bind-address" default:":8081" help:"The address the probe endpoint binds to."`
|
||||
SecureMetrics bool `name:"metrics-secure" default:"true" help:"If set, the metrics endpoint is served securely via HTTPS. Use --metrics-secure=false to use HTTP instead."`
|
||||
EnableHTTP2 bool `name:"enable-http2" default:"false" help:"If set, HTTP/2 will be enabled for the metrics and webhook servers"`
|
||||
}
|
||||
|
||||
func (p controlPlane) Run(ctx context.Context, cache cache.SnapshotCache) (err error) {
|
||||
func (cp controlPlane) Run(ctx context.Context) error {
|
||||
var tlsOpts []func(*tls.Config)
|
||||
|
||||
// if the enable-http2 flag is false (the default), http/2 should be disabled
|
||||
// due to its vulnerabilities. More specifically, disabling http/2 will
|
||||
// prevent from being vulnerable to the HTTP/2 Stream Cancellation and
|
||||
// Rapid Reset CVEs. For more information see:
|
||||
// - https://github.com/advisories/GHSA-qppj-fm5r-hxr3
|
||||
// - https://github.com/advisories/GHSA-4374-p667-p6c8
|
||||
disableHTTP2 := func(c *tls.Config) {
|
||||
setupLog.Info("disabling http/2")
|
||||
c.NextProtos = []string{"http/1.1"}
|
||||
}
|
||||
|
||||
if !cp.EnableHTTP2 {
|
||||
tlsOpts = append(tlsOpts, disableHTTP2)
|
||||
}
|
||||
|
||||
// Metrics endpoint is enabled in 'config/default/kustomization.yaml'. The Metrics options configure the server.
|
||||
// More info:
|
||||
// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.19.1/pkg/metrics/server
|
||||
// - https://book.kubebuilder.io/reference/metrics.html
|
||||
metricsServerOptions := metricsserver.Options{
|
||||
BindAddress: cp.MetricsAddr,
|
||||
SecureServing: cp.SecureMetrics,
|
||||
TLSOpts: tlsOpts,
|
||||
}
|
||||
|
||||
if cp.SecureMetrics {
|
||||
// FilterProvider is used to protect the metrics endpoint with authn/authz.
|
||||
// These configurations ensure that only authorized users and service accounts
|
||||
// can access the metrics endpoint. The RBAC are configured in 'config/rbac/kustomization.yaml'. More info:
|
||||
// https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.19.1/pkg/metrics/filters#WithAuthenticationAndAuthorization
|
||||
metricsServerOptions.FilterProvider = filters.WithAuthenticationAndAuthorization
|
||||
}
|
||||
|
||||
mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
|
||||
Scheme: scheme,
|
||||
Metrics: metricsServerOptions,
|
||||
HealthProbeBindAddress: cp.ProbeAddr,
|
||||
LeaderElection: cp.EnableLeaderElection,
|
||||
BaseContext: func() context.Context { return ctx },
|
||||
LeaderElectionID: "30f6fafb.k8s.icb4dc0.de",
|
||||
LeaderElectionReleaseOnCancel: true,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to start control plane: %w", err)
|
||||
}
|
||||
|
||||
envoySnapshotCache := cachev3.NewSnapshotCache(false, cachev3.IDHash{}, nil)
|
||||
|
||||
envoySrv, err := cp.envoyServer(ctx, envoySnapshotCache)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := mgr.Add(envoySrv); err != nil {
|
||||
return fmt.Errorf("failed to add enovy server to manager: %w", err)
|
||||
}
|
||||
|
||||
if err = (&controlplane.APIGatewayReconciler{
|
||||
Client: mgr.GetClient(),
|
||||
Scheme: mgr.GetScheme(),
|
||||
Cache: envoySnapshotCache,
|
||||
}).SetupWithManager(mgr); err != nil {
|
||||
return fmt.Errorf("unable to create controller Core DB: %w", err)
|
||||
}
|
||||
|
||||
setupLog.Info("starting manager")
|
||||
if err := mgr.Start(ctx); err != nil {
|
||||
return fmt.Errorf("problem running manager: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cp controlPlane) envoyServer(
|
||||
ctx context.Context,
|
||||
cache cachev3.SnapshotCache,
|
||||
) (runnable mgr.Runnable, err error) {
|
||||
const (
|
||||
grpcKeepaliveTime = 30 * time.Second
|
||||
grpcKeepaliveTimeout = 5 * time.Second
|
||||
|
@ -56,19 +142,10 @@ func (p controlPlane) Run(ctx context.Context, cache cache.SnapshotCache) (err e
|
|||
grpcMaxConcurrentStreams = 1000000
|
||||
)
|
||||
|
||||
logger := ctrl.Log.WithName("control-plane")
|
||||
|
||||
clientOpts := client.Options{
|
||||
Scheme: scheme,
|
||||
}
|
||||
|
||||
logger.Info("Creating client")
|
||||
watcherClient, err := client.NewWithWatch(ctrl.GetConfigOrDie(), clientOpts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
srv := server.NewServer(ctx, cache, nil)
|
||||
var (
|
||||
logger = ctrl.Log.WithName("control-plane")
|
||||
srv = server.NewServer(ctx, cache, nil)
|
||||
)
|
||||
|
||||
// gRPC golang library sets a very small upper bound for the number gRPC/h2
|
||||
// streams over a single TCP connection. If a proxy multiplexes requests over
|
||||
|
@ -89,13 +166,14 @@ func (p controlPlane) Run(ctx context.Context, cache cache.SnapshotCache) (err e
|
|||
)
|
||||
grpcServer := grpc.NewServer(grpcOptions...)
|
||||
|
||||
logger.Info("Opening listener", "addr", p.ListenAddr)
|
||||
lis, err := net.Listen("tcp", p.ListenAddr)
|
||||
logger.Info("Opening listener", "addr", cp.ListenAddr)
|
||||
lis, err := net.Listen("tcp", cp.ListenAddr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("opening listener: %w", err)
|
||||
return nil, fmt.Errorf("opening listener: %w", err)
|
||||
}
|
||||
|
||||
logger.Info("Preparing health endpoints")
|
||||
|
||||
healthService := grpchealth.NewServer()
|
||||
healthService.SetServingStatus("", grpc_health_v1.HealthCheckResponse_SERVING)
|
||||
|
||||
|
@ -109,39 +187,11 @@ func (p controlPlane) Run(ctx context.Context, cache cache.SnapshotCache) (err e
|
|||
runtimeservice.RegisterRuntimeDiscoveryServiceServer(grpcServer, srv)
|
||||
grpc_health_v1.RegisterHealthServer(grpcServer, healthService)
|
||||
|
||||
// discoverygrpc.AggregatedDiscoveryService_ServiceDesc.ServiceName
|
||||
|
||||
endpointsController := controlplane.EndpointsController{
|
||||
Client: watcherClient,
|
||||
Cache: cache,
|
||||
}
|
||||
|
||||
errOut := make(chan error)
|
||||
|
||||
go func(errOut chan<- error) {
|
||||
logger.Info("Starting gRPC server")
|
||||
errOut <- grpcServer.Serve(lis)
|
||||
}(errOut)
|
||||
|
||||
go func(errOut chan<- error) {
|
||||
logger.Info("Staring endpoints controller")
|
||||
errOut <- endpointsController.Run(ctx)
|
||||
}(errOut)
|
||||
|
||||
go func(errOut chan error) {
|
||||
for out := range errOut {
|
||||
err = errors.Join(err, out)
|
||||
}
|
||||
}(errOut)
|
||||
|
||||
<-ctx.Done()
|
||||
grpcServer.Stop()
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
//nolint:unparam // signature required by kong
|
||||
func (p controlPlane) AfterApply(kongctx *kong.Context) error {
|
||||
kongctx.BindTo(cache.NewSnapshotCache(false, cache.IDHash{}, nil), (*cache.SnapshotCache)(nil))
|
||||
return nil
|
||||
return mgr.RunnableFunc(func(ctx context.Context) error {
|
||||
go func(ctx context.Context) {
|
||||
<-ctx.Done()
|
||||
grpcServer.GracefulStop()
|
||||
}(ctx)
|
||||
return grpcServer.Serve(lis)
|
||||
}), nil
|
||||
}
|
||||
|
|
|
@ -87,23 +87,14 @@ func (m manager) Run(ctx context.Context) error {
|
|||
}
|
||||
|
||||
mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
|
||||
Scheme: scheme,
|
||||
Metrics: metricsServerOptions,
|
||||
WebhookServer: webhookServer,
|
||||
HealthProbeBindAddress: m.ProbeAddr,
|
||||
LeaderElection: m.EnableLeaderElection,
|
||||
LeaderElectionID: "05f9463f.k8s.icb4dc0.de",
|
||||
// LeaderElectionReleaseOnCancel defines if the leader should step down voluntarily
|
||||
// when the Manager ends. This requires the binary to immediately end when the
|
||||
// Manager is stopped, otherwise, this setting is unsafe. Setting this significantly
|
||||
// speeds up voluntary leader transitions as the new leader don't have to wait
|
||||
// LeaseDuration time first.
|
||||
//
|
||||
// In the default scaffold provided, the program ends immediately after
|
||||
// the manager stops, so would be fine to enable this option. However,
|
||||
// if you are doing or is intended to do any operation such as perform cleanups
|
||||
// after the manager stops then its usage might be unsafe.
|
||||
// LeaderElectionReleaseOnCancel: true,
|
||||
Scheme: scheme,
|
||||
Metrics: metricsServerOptions,
|
||||
WebhookServer: webhookServer,
|
||||
HealthProbeBindAddress: m.ProbeAddr,
|
||||
LeaderElection: m.EnableLeaderElection,
|
||||
BaseContext: func() context.Context { return ctx },
|
||||
LeaderElectionID: "05f9463f.k8s.icb4dc0.de",
|
||||
LeaderElectionReleaseOnCancel: true,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to start manager: %w", err)
|
||||
|
|
|
@ -14,7 +14,11 @@ spec:
|
|||
singular: apigateway
|
||||
scope: Namespaced
|
||||
versions:
|
||||
- name: v1alpha1
|
||||
- additionalPrinterColumns:
|
||||
- jsonPath: .status.envoy.configVersion
|
||||
name: EnvoyConfigVersion
|
||||
type: string
|
||||
name: v1alpha1
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
description: APIGateway is the Schema for the apigateways API.
|
||||
|
@ -39,6 +43,11 @@ spec:
|
|||
spec:
|
||||
description: APIGatewaySpec defines the desired state of APIGateway.
|
||||
properties:
|
||||
componentTypeLabel:
|
||||
default: app.kubernetes.io/name
|
||||
description: ComponentTypeLabel - Label to identify which Supabase
|
||||
component a Service represents (e.g. auth, postgrest, ...)
|
||||
type: string
|
||||
envoy:
|
||||
description: Envoy - configure the envoy instance and most importantly
|
||||
the control-plane
|
||||
|
@ -61,6 +70,12 @@ spec:
|
|||
- host
|
||||
- port
|
||||
type: object
|
||||
nodeName:
|
||||
description: |-
|
||||
NodeName - identifies the Envoy cluster within the current namespace
|
||||
if not set, the name of the APIGateway resource will be used
|
||||
The primary use case is to make the assignment of multiple supabase instances in a single namespace explicit.
|
||||
type: string
|
||||
workloadTemplate:
|
||||
description: WorkloadTemplate - customize the Envoy deployment
|
||||
properties:
|
||||
|
@ -776,12 +791,83 @@ spec:
|
|||
- key
|
||||
type: object
|
||||
x-kubernetes-map-type: atomic
|
||||
serviceSelector:
|
||||
default:
|
||||
matchExpressions:
|
||||
- key: app.kubernetes.io/part-of
|
||||
operator: In
|
||||
values:
|
||||
- supabase
|
||||
- key: supabase.k8s.icb4dc0.de/api-gateway-target
|
||||
operator: Exists
|
||||
description: ServiceSelector - selector to match all Supabase services
|
||||
(or in fact EndpointSlices) that should be considered for this APIGateway
|
||||
properties:
|
||||
matchExpressions:
|
||||
description: matchExpressions is a list of label selector requirements.
|
||||
The requirements are ANDed.
|
||||
items:
|
||||
description: |-
|
||||
A label selector requirement is a selector that contains values, a key, and an operator that
|
||||
relates the key and values.
|
||||
properties:
|
||||
key:
|
||||
description: key is the label key that the selector applies
|
||||
to.
|
||||
type: string
|
||||
operator:
|
||||
description: |-
|
||||
operator represents a key's relationship to a set of values.
|
||||
Valid operators are In, NotIn, Exists and DoesNotExist.
|
||||
type: string
|
||||
values:
|
||||
description: |-
|
||||
values is an array of string values. If the operator is In or NotIn,
|
||||
the values array must be non-empty. If the operator is Exists or DoesNotExist,
|
||||
the values array must be empty. This array is replaced during a strategic
|
||||
merge patch.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
x-kubernetes-list-type: atomic
|
||||
required:
|
||||
- key
|
||||
- operator
|
||||
type: object
|
||||
type: array
|
||||
x-kubernetes-list-type: atomic
|
||||
matchLabels:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: |-
|
||||
matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels
|
||||
map is equivalent to an element of matchExpressions, whose key field is "key", the
|
||||
operator is "In", and the values array contains only "value". The requirements are ANDed.
|
||||
type: object
|
||||
type: object
|
||||
x-kubernetes-map-type: atomic
|
||||
required:
|
||||
- envoy
|
||||
- jwks
|
||||
- serviceSelector
|
||||
type: object
|
||||
status:
|
||||
description: APIGatewayStatus defines the observed state of APIGateway.
|
||||
properties:
|
||||
envoy:
|
||||
properties:
|
||||
configVersion:
|
||||
type: string
|
||||
resourceHash:
|
||||
format: byte
|
||||
type: string
|
||||
type: object
|
||||
serviceTargets:
|
||||
additionalProperties:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
type: object
|
||||
type: object
|
||||
type: object
|
||||
served: true
|
||||
|
|
54
config/crd/bases/supabase.k8s.icb4dc0.de_storages.yaml
Normal file
54
config/crd/bases/supabase.k8s.icb4dc0.de_storages.yaml
Normal file
|
@ -0,0 +1,54 @@
|
|||
---
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.16.5
|
||||
name: storages.supabase.k8s.icb4dc0.de
|
||||
spec:
|
||||
group: supabase.k8s.icb4dc0.de
|
||||
names:
|
||||
kind: Storage
|
||||
listKind: StorageList
|
||||
plural: storages
|
||||
singular: storage
|
||||
scope: Namespaced
|
||||
versions:
|
||||
- name: v1alpha1
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
description: Storage is the Schema for the storages API.
|
||||
properties:
|
||||
apiVersion:
|
||||
description: |-
|
||||
APIVersion defines the versioned schema of this representation of an object.
|
||||
Servers should convert recognized schemas to the latest internal value, and
|
||||
may reject unrecognized values.
|
||||
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
|
||||
type: string
|
||||
kind:
|
||||
description: |-
|
||||
Kind is a string value representing the REST resource this object represents.
|
||||
Servers may infer this from the endpoint the client submits requests to.
|
||||
Cannot be updated.
|
||||
In CamelCase.
|
||||
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
|
||||
type: string
|
||||
metadata:
|
||||
type: object
|
||||
spec:
|
||||
description: StorageSpec defines the desired state of Storage.
|
||||
properties:
|
||||
foo:
|
||||
description: Foo is an example field of Storage. Edit storage_types.go
|
||||
to remove/update
|
||||
type: string
|
||||
type: object
|
||||
status:
|
||||
description: StorageStatus defines the observed state of Storage.
|
||||
type: object
|
||||
type: object
|
||||
served: true
|
||||
storage: true
|
||||
subresources:
|
||||
status: {}
|
|
@ -5,6 +5,7 @@ resources:
|
|||
- bases/supabase.k8s.icb4dc0.de_cores.yaml
|
||||
- bases/supabase.k8s.icb4dc0.de_apigateways.yaml
|
||||
- bases/supabase.k8s.icb4dc0.de_dashboards.yaml
|
||||
- bases/supabase.k8s.icb4dc0.de_storages.yaml
|
||||
# +kubebuilder:scaffold:crdkustomizeresource
|
||||
|
||||
patches:
|
||||
|
|
|
@ -4,6 +4,23 @@ kind: ClusterRole
|
|||
metadata:
|
||||
name: control-plane-role
|
||||
rules:
|
||||
- apiGroups:
|
||||
- supabase.k8s.icb4dc0.de
|
||||
resources:
|
||||
- apigateways
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- supabase.k8s.icb4dc0.de
|
||||
resources:
|
||||
- apigateways/status
|
||||
verbs:
|
||||
- get
|
||||
- patch
|
||||
- update
|
||||
|
||||
- apiGroups:
|
||||
- discovery.k8s.io
|
||||
resources:
|
||||
|
|
|
@ -36,3 +36,10 @@ resources:
|
|||
# if you do not want those helpers be installed with your Project.
|
||||
- dashboard_editor_role.yaml
|
||||
- dashboard_viewer_role.yaml
|
||||
# For each CRD, "Admin", "Editor" and "Viewer" roles are scaffolded by
|
||||
# default, aiding admins in cluster management. Those roles are
|
||||
# not used by the {{ .ProjectName }} itself. You can comment the following lines
|
||||
# if you do not want those helpers be installed with your Project.
|
||||
- storage_admin_role.yaml
|
||||
- storage_editor_role.yaml
|
||||
- storage_viewer_role.yaml
|
||||
|
|
|
@ -13,3 +13,6 @@ subjects:
|
|||
- kind: ServiceAccount
|
||||
name: controller-manager
|
||||
namespace: supabase-system
|
||||
- kind: ServiceAccount
|
||||
name: control-plane
|
||||
namespace: supabase-system
|
||||
|
|
|
@ -42,6 +42,7 @@ rules:
|
|||
- apigateways
|
||||
- cores
|
||||
- dashboards
|
||||
- storages
|
||||
verbs:
|
||||
- create
|
||||
- delete
|
||||
|
@ -56,6 +57,7 @@ rules:
|
|||
- apigateways/finalizers
|
||||
- cores/finalizers
|
||||
- dashboards/finalizers
|
||||
- storages/finalizers
|
||||
verbs:
|
||||
- update
|
||||
- apiGroups:
|
||||
|
@ -64,6 +66,7 @@ rules:
|
|||
- apigateways/status
|
||||
- cores/status
|
||||
- dashboards/status
|
||||
- storages/status
|
||||
verbs:
|
||||
- get
|
||||
- patch
|
||||
|
|
27
config/rbac/storage_admin_role.yaml
Normal file
27
config/rbac/storage_admin_role.yaml
Normal file
|
@ -0,0 +1,27 @@
|
|||
# This rule is not used by the project supabase-operator itself.
|
||||
# It is provided to allow the cluster admin to help manage permissions for users.
|
||||
#
|
||||
# Grants full permissions ('*') over supabase.k8s.icb4dc0.de.
|
||||
# This role is intended for users authorized to modify roles and bindings within the cluster,
|
||||
# enabling them to delegate specific permissions to other users or groups as needed.
|
||||
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: supabase-operator
|
||||
app.kubernetes.io/managed-by: kustomize
|
||||
name: storage-admin-role
|
||||
rules:
|
||||
- apiGroups:
|
||||
- supabase.k8s.icb4dc0.de
|
||||
resources:
|
||||
- storages
|
||||
verbs:
|
||||
- '*'
|
||||
- apiGroups:
|
||||
- supabase.k8s.icb4dc0.de
|
||||
resources:
|
||||
- storages/status
|
||||
verbs:
|
||||
- get
|
33
config/rbac/storage_editor_role.yaml
Normal file
33
config/rbac/storage_editor_role.yaml
Normal file
|
@ -0,0 +1,33 @@
|
|||
# This rule is not used by the project supabase-operator itself.
|
||||
# It is provided to allow the cluster admin to help manage permissions for users.
|
||||
#
|
||||
# Grants permissions to create, update, and delete resources within the supabase.k8s.icb4dc0.de.
|
||||
# This role is intended for users who need to manage these resources
|
||||
# but should not control RBAC or manage permissions for others.
|
||||
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: supabase-operator
|
||||
app.kubernetes.io/managed-by: kustomize
|
||||
name: storage-editor-role
|
||||
rules:
|
||||
- apiGroups:
|
||||
- supabase.k8s.icb4dc0.de
|
||||
resources:
|
||||
- storages
|
||||
verbs:
|
||||
- create
|
||||
- delete
|
||||
- get
|
||||
- list
|
||||
- patch
|
||||
- update
|
||||
- watch
|
||||
- apiGroups:
|
||||
- supabase.k8s.icb4dc0.de
|
||||
resources:
|
||||
- storages/status
|
||||
verbs:
|
||||
- get
|
29
config/rbac/storage_viewer_role.yaml
Normal file
29
config/rbac/storage_viewer_role.yaml
Normal file
|
@ -0,0 +1,29 @@
|
|||
# This rule is not used by the project supabase-operator itself.
|
||||
# It is provided to allow the cluster admin to help manage permissions for users.
|
||||
#
|
||||
# Grants read-only access to supabase.k8s.icb4dc0.de resources.
|
||||
# This role is intended for users who need visibility into these resources
|
||||
# without permissions to modify them. It is ideal for monitoring purposes and limited-access viewing.
|
||||
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: supabase-operator
|
||||
app.kubernetes.io/managed-by: kustomize
|
||||
name: storage-viewer-role
|
||||
rules:
|
||||
- apiGroups:
|
||||
- supabase.k8s.icb4dc0.de
|
||||
resources:
|
||||
- storages
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- supabase.k8s.icb4dc0.de
|
||||
resources:
|
||||
- storages/status
|
||||
verbs:
|
||||
- get
|
|
@ -8,4 +8,5 @@ resources:
|
|||
- supabase_v1alpha1_core.yaml
|
||||
- supabase_v1alpha1_apigateway.yaml
|
||||
- supabase_v1alpha1_dashboard.yaml
|
||||
- supabase_v1alpha1_storage.yaml
|
||||
# +kubebuilder:scaffold:manifestskustomizesamples
|
||||
|
|
9
config/samples/supabase_v1alpha1_storage.yaml
Normal file
9
config/samples/supabase_v1alpha1_storage.yaml
Normal file
|
@ -0,0 +1,9 @@
|
|||
apiVersion: supabase.k8s.icb4dc0.de/v1alpha1
|
||||
kind: Storage
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: supabase-operator
|
||||
app.kubernetes.io/managed-by: kustomize
|
||||
name: storage-sample
|
||||
spec:
|
||||
# TODO(user): Add fields here
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2024 Peter Kurfer.
|
||||
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.
|
||||
|
|
13
hack/header.tmpl
Normal file
13
hack/header.tmpl
Normal file
|
@ -0,0 +1,13 @@
|
|||
Copyright {{ YEAR }} {{ AUTHOR }}.
|
||||
|
||||
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.
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2024 Peter Kurfer.
|
||||
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.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2024 Peter Kurfer.
|
||||
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.
|
||||
|
|
|
@ -221,8 +221,8 @@ func (r *CoreAuthReconciler) reconcileAuthService(
|
|||
core.Labels,
|
||||
)
|
||||
|
||||
if _, ok := authService.Labels[meta.SupabaseLabel.EnvoyCluster]; !ok {
|
||||
authService.Labels[meta.SupabaseLabel.EnvoyCluster] = core.Name
|
||||
if _, ok := authService.Labels[meta.SupabaseLabel.ApiGatewayTarget]; !ok {
|
||||
authService.Labels[meta.SupabaseLabel.ApiGatewayTarget] = core.Name
|
||||
}
|
||||
|
||||
authService.Spec = corev1.ServiceSpec{
|
||||
|
|
|
@ -228,8 +228,8 @@ func (r *CorePostgrestReconiler) reconcilePostgrestService(
|
|||
core.Labels,
|
||||
)
|
||||
|
||||
if _, ok := postgrestService.Labels[meta.SupabaseLabel.EnvoyCluster]; !ok {
|
||||
postgrestService.Labels[meta.SupabaseLabel.EnvoyCluster] = core.Name
|
||||
if _, ok := postgrestService.Labels[meta.SupabaseLabel.ApiGatewayTarget]; !ok {
|
||||
postgrestService.Labels[meta.SupabaseLabel.ApiGatewayTarget] = ""
|
||||
}
|
||||
|
||||
postgrestService.Spec = corev1.ServiceSpec{
|
||||
|
|
|
@ -196,8 +196,8 @@ func (r *DashboardPGMetaReconciler) reconcilePGMetaService(
|
|||
dashboard.Labels,
|
||||
)
|
||||
|
||||
if _, ok := pgMetaService.Labels[meta.SupabaseLabel.EnvoyCluster]; !ok {
|
||||
pgMetaService.Labels[meta.SupabaseLabel.EnvoyCluster] = dashboard.Name
|
||||
if _, ok := pgMetaService.Labels[meta.SupabaseLabel.ApiGatewayTarget]; !ok {
|
||||
pgMetaService.Labels[meta.SupabaseLabel.ApiGatewayTarget] = ""
|
||||
}
|
||||
|
||||
pgMetaService.Spec = corev1.ServiceSpec{
|
||||
|
|
|
@ -218,8 +218,8 @@ func (r *DashboardStudioReconciler) reconcileStudioService(
|
|||
dashboard.Labels,
|
||||
)
|
||||
|
||||
if _, ok := studioService.Labels[meta.SupabaseLabel.EnvoyCluster]; !ok {
|
||||
studioService.Labels[meta.SupabaseLabel.EnvoyCluster] = dashboard.Name
|
||||
if _, ok := studioService.Labels[meta.SupabaseLabel.ApiGatewayTarget]; !ok {
|
||||
studioService.Labels[meta.SupabaseLabel.ApiGatewayTarget] = ""
|
||||
}
|
||||
|
||||
studioService.Spec = corev1.ServiceSpec{
|
||||
|
|
|
@ -1,3 +1,19 @@
|
|||
// /*
|
||||
// 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.
|
||||
// */
|
||||
|
||||
package controller
|
||||
|
||||
import (
|
||||
|
|
63
internal/controller/storage_controller.go
Normal file
63
internal/controller/storage_controller.go
Normal file
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
package controller
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/log"
|
||||
|
||||
supabasev1alpha1 "code.icb4dc0.de/prskr/supabase-operator/api/v1alpha1"
|
||||
)
|
||||
|
||||
// StorageReconciler reconciles a Storage object
|
||||
type StorageReconciler struct {
|
||||
client.Client
|
||||
Scheme *runtime.Scheme
|
||||
}
|
||||
|
||||
// +kubebuilder:rbac:groups=supabase.k8s.icb4dc0.de,resources=storages,verbs=get;list;watch;create;update;patch;delete
|
||||
// +kubebuilder:rbac:groups=supabase.k8s.icb4dc0.de,resources=storages/status,verbs=get;update;patch
|
||||
// +kubebuilder:rbac:groups=supabase.k8s.icb4dc0.de,resources=storages/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 Storage 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.4/pkg/reconcile
|
||||
func (r *StorageReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
|
||||
_ = log.FromContext(ctx)
|
||||
|
||||
// TODO(user): your logic here
|
||||
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
|
||||
// SetupWithManager sets up the controller with the Manager.
|
||||
func (r *StorageReconciler) SetupWithManager(mgr ctrl.Manager) error {
|
||||
return ctrl.NewControllerManagedBy(mgr).
|
||||
For(&supabasev1alpha1.Storage{}).
|
||||
Named("storage").
|
||||
Complete(r)
|
||||
}
|
84
internal/controller/storage_controller_test.go
Normal file
84
internal/controller/storage_controller_test.go
Normal file
|
@ -0,0 +1,84 @@
|
|||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
package controller
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"sigs.k8s.io/controller-runtime/pkg/reconcile"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
supabasev1alpha1 "code.icb4dc0.de/prskr/supabase-operator/api/v1alpha1"
|
||||
)
|
||||
|
||||
var _ = Describe("Storage Controller", func() {
|
||||
Context("When reconciling a resource", func() {
|
||||
const resourceName = "test-resource"
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
typeNamespacedName := types.NamespacedName{
|
||||
Name: resourceName,
|
||||
Namespace: "default", // TODO(user):Modify as needed
|
||||
}
|
||||
storage := &supabasev1alpha1.Storage{}
|
||||
|
||||
BeforeEach(func() {
|
||||
By("creating the custom resource for the Kind Storage")
|
||||
err := k8sClient.Get(ctx, typeNamespacedName, storage)
|
||||
if err != nil && errors.IsNotFound(err) {
|
||||
resource := &supabasev1alpha1.Storage{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: resourceName,
|
||||
Namespace: "default",
|
||||
},
|
||||
// TODO(user): Specify other spec details if needed.
|
||||
}
|
||||
Expect(k8sClient.Create(ctx, resource)).To(Succeed())
|
||||
}
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
// TODO(user): Cleanup logic after each test, like removing the resource instance.
|
||||
resource := &supabasev1alpha1.Storage{}
|
||||
err := k8sClient.Get(ctx, typeNamespacedName, resource)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
By("Cleanup the specific resource instance Storage")
|
||||
Expect(k8sClient.Delete(ctx, resource)).To(Succeed())
|
||||
})
|
||||
It("should successfully reconcile the resource", func() {
|
||||
By("Reconciling the created resource")
|
||||
controllerReconciler := &StorageReconciler{
|
||||
Client: k8sClient,
|
||||
Scheme: k8sClient.Scheme(),
|
||||
}
|
||||
|
||||
_, err := controllerReconciler.Reconcile(ctx, reconcile.Request{
|
||||
NamespacedName: typeNamespacedName,
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
// TODO(user): Add more specific assertions depending on your controller's reconciliation logic.
|
||||
// Example: If you expect a certain status condition after reconciliation, verify it here.
|
||||
})
|
||||
})
|
||||
})
|
197
internal/controlplane/apigateway_controller.go
Normal file
197
internal/controlplane/apigateway_controller.go
Normal file
|
@ -0,0 +1,197 @@
|
|||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
package controlplane
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
cachev3 "github.com/envoyproxy/go-control-plane/pkg/cache/v3"
|
||||
discoveryv1 "k8s.io/api/discovery/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/builder"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
|
||||
"sigs.k8s.io/controller-runtime/pkg/handler"
|
||||
"sigs.k8s.io/controller-runtime/pkg/log"
|
||||
"sigs.k8s.io/controller-runtime/pkg/predicate"
|
||||
"sigs.k8s.io/controller-runtime/pkg/reconcile"
|
||||
|
||||
supabasev1alpha1 "code.icb4dc0.de/prskr/supabase-operator/api/v1alpha1"
|
||||
"code.icb4dc0.de/prskr/supabase-operator/internal/meta"
|
||||
)
|
||||
|
||||
// APIGatewayReconciler reconciles a APIGateway object
|
||||
type APIGatewayReconciler struct {
|
||||
client.Client
|
||||
Scheme *runtime.Scheme
|
||||
Cache cachev3.SnapshotCache
|
||||
}
|
||||
|
||||
// Reconcile is part of the main kubernetes reconciliation loop which aims to
|
||||
// move the current state of the cluster closer to the desired state.
|
||||
//
|
||||
// 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 *APIGatewayReconciler) Reconcile(ctx context.Context, req ctrl.Request) (res ctrl.Result, err error) {
|
||||
var (
|
||||
gateway supabasev1alpha1.APIGateway
|
||||
logger = log.FromContext(ctx)
|
||||
endpointSliceList discoveryv1.EndpointSliceList
|
||||
)
|
||||
|
||||
logger.Info("Reconciling APIGateway")
|
||||
|
||||
if err := r.Get(ctx, req.NamespacedName, &gateway); client.IgnoreNotFound(err) != nil {
|
||||
logger.Error(err, "unable to fetch Gateway")
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
|
||||
selector, err := metav1.LabelSelectorAsSelector(gateway.Spec.ServiceSelector)
|
||||
if err != nil {
|
||||
return ctrl.Result{}, fmt.Errorf("failed to create selector for EndpointSlices: %w", err)
|
||||
}
|
||||
|
||||
if err := r.List(ctx, &endpointSliceList, client.MatchingLabelsSelector{Selector: selector}); err != nil {
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
|
||||
services := EnvoyServices{ServiceLabelKey: gateway.Spec.ComponentTypeLabel}
|
||||
services.UpsertEndpointSlices(endpointSliceList.Items...)
|
||||
|
||||
rawServices, err := json.Marshal(services)
|
||||
if err != nil {
|
||||
return ctrl.Result{}, fmt.Errorf("failed to prepare config hash: %w", err)
|
||||
}
|
||||
|
||||
serviceHash := sha256.New().Sum(rawServices)
|
||||
if bytes.Equal(serviceHash, gateway.Status.Envoy.ResourceHash) {
|
||||
logger.Info("Resource hash did not change - skipping reconciliation")
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
|
||||
logger.Info("Updating service targets")
|
||||
_, err = controllerutil.CreateOrPatch(ctx, r.Client, &gateway, func() error {
|
||||
gateway.Status.ServiceTargets = services.Targets()
|
||||
gateway.Status.Envoy.ConfigVersion = strconv.FormatInt(time.Now().UTC().UnixMilli(), 10)
|
||||
gateway.Status.Envoy.ResourceHash = serviceHash
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
|
||||
instance := fmt.Sprintf("%s:%s", gateway.Spec.Envoy.NodeName, gateway.Namespace)
|
||||
|
||||
logger.Info("Computing Envoy snapshot for current service targets", "version", gateway.Status.Envoy.ConfigVersion)
|
||||
snapshot, err := services.snapshot(ctx, instance, gateway.Status.Envoy.ConfigVersion)
|
||||
if err != nil {
|
||||
return ctrl.Result{}, fmt.Errorf("failed to prepare snapshot: %w", err)
|
||||
}
|
||||
|
||||
logger.Info("Propagating Envoy snapshot", "version", gateway.Status.Envoy.ConfigVersion)
|
||||
if err := r.Cache.SetSnapshot(ctx, instance, snapshot); err != nil {
|
||||
return ctrl.Result{}, fmt.Errorf("failed to propagate snapshot: %w", err)
|
||||
}
|
||||
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
|
||||
func (r *APIGatewayReconciler) SetupWithManager(mgr ctrl.Manager) error {
|
||||
gatewayTargetLabelSelector, err := predicate.LabelSelectorPredicate(metav1.LabelSelector{
|
||||
MatchExpressions: []metav1.LabelSelectorRequirement{{
|
||||
Key: meta.SupabaseLabel.ApiGatewayTarget,
|
||||
Operator: metav1.LabelSelectorOpExists,
|
||||
}},
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to build gateway target predicate: %w", err)
|
||||
}
|
||||
|
||||
return ctrl.NewControllerManagedBy(mgr).
|
||||
For(new(supabasev1alpha1.APIGateway)).
|
||||
Watches(
|
||||
new(discoveryv1.EndpointSlice),
|
||||
r.endpointSliceEventHandler(),
|
||||
builder.WithPredicates(gatewayTargetLabelSelector)).
|
||||
Complete(r)
|
||||
}
|
||||
|
||||
// endpointSliceEventHandler - prepares an event handler that checks whether the EndpointSlice has a specific target
|
||||
// or if it is targeting the only APIGateway in its namespace (default behavior for the operator)
|
||||
func (r *APIGatewayReconciler) endpointSliceEventHandler() handler.TypedEventHandler[client.Object, reconcile.Request] {
|
||||
return handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, obj client.Object) []reconcile.Request {
|
||||
var (
|
||||
logger = log.FromContext(ctx)
|
||||
apiGatewayList supabasev1alpha1.APIGatewayList
|
||||
)
|
||||
|
||||
endpointSlice, ok := obj.(*discoveryv1.EndpointSlice)
|
||||
if !ok {
|
||||
logger.Info("Cannot map event to reconcile request, because object has unexpected type", "type", fmt.Sprintf("%T", obj))
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := r.Client.List(ctx, &apiGatewayList, client.InNamespace(endpointSlice.Namespace)); err != nil {
|
||||
logger.Error(err, "failed to list APIGateways to determine reconcile targets")
|
||||
return nil
|
||||
}
|
||||
|
||||
target, ok := endpointSlice.Labels[meta.SupabaseLabel.ApiGatewayTarget]
|
||||
if !ok {
|
||||
// should not happen, just to be sure
|
||||
return nil
|
||||
}
|
||||
|
||||
var reconcileRequests []reconcile.Request
|
||||
|
||||
if target != "" {
|
||||
for _, gw := range apiGatewayList.Items {
|
||||
if strings.EqualFold(gw.Spec.Envoy.NodeName, target) {
|
||||
reconcileRequests = append(reconcileRequests, reconcile.Request{
|
||||
NamespacedName: types.NamespacedName{
|
||||
Name: gw.Name,
|
||||
Namespace: gw.Namespace,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
} else {
|
||||
reconcileRequests = make([]reconcile.Request, 0, len(apiGatewayList.Items))
|
||||
for _, gw := range apiGatewayList.Items {
|
||||
reconcileRequests = append(reconcileRequests, reconcile.Request{
|
||||
NamespacedName: types.NamespacedName{
|
||||
Name: gw.Name,
|
||||
Namespace: gw.Namespace,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return reconcileRequests
|
||||
})
|
||||
}
|
|
@ -1,6 +1,10 @@
|
|||
package controlplane
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
clusterv3 "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3"
|
||||
|
@ -10,11 +14,28 @@ import (
|
|||
discoveryv1 "k8s.io/api/discovery/v1"
|
||||
)
|
||||
|
||||
var _ json.Marshaler = (*ServiceCluster)(nil)
|
||||
|
||||
type ServiceCluster struct {
|
||||
ServiceEndpoints map[string]Endpoints
|
||||
}
|
||||
|
||||
func (c *ServiceCluster) AddOrUpdateEndpoints(eps *discoveryv1.EndpointSlice) {
|
||||
// MarshalJSON implements json.Marshaler.
|
||||
func (c *ServiceCluster) MarshalJSON() ([]byte, error) {
|
||||
tmp := struct {
|
||||
Endpoints []string `json:"endpoints"`
|
||||
}{}
|
||||
|
||||
for _, endpoints := range c.ServiceEndpoints {
|
||||
tmp.Endpoints = append(tmp.Endpoints, endpoints.Targets...)
|
||||
}
|
||||
|
||||
slices.Sort(tmp.Endpoints)
|
||||
|
||||
return json.Marshal(tmp)
|
||||
}
|
||||
|
||||
func (c *ServiceCluster) AddOrUpdateEndpoints(eps discoveryv1.EndpointSlice) {
|
||||
if c.ServiceEndpoints == nil {
|
||||
c.ServiceEndpoints = make(map[string]Endpoints)
|
||||
}
|
||||
|
@ -22,6 +43,16 @@ func (c *ServiceCluster) AddOrUpdateEndpoints(eps *discoveryv1.EndpointSlice) {
|
|||
c.ServiceEndpoints[eps.Name] = newEndpointsFromSlice(eps)
|
||||
}
|
||||
|
||||
func (c ServiceCluster) Targets() []string {
|
||||
var targets []string
|
||||
|
||||
for _, ep := range c.ServiceEndpoints {
|
||||
targets = append(targets, ep.Targets...)
|
||||
}
|
||||
|
||||
return targets
|
||||
}
|
||||
|
||||
func (c ServiceCluster) Cluster(name string, port uint32) *clusterv3.Cluster {
|
||||
return &clusterv3.Cluster{
|
||||
Name: name,
|
||||
|
@ -47,12 +78,13 @@ func (c ServiceCluster) endpoints(port uint32) []*endpointv3.LocalityLbEndpoints
|
|||
return eps
|
||||
}
|
||||
|
||||
func newEndpointsFromSlice(eps *discoveryv1.EndpointSlice) Endpoints {
|
||||
func newEndpointsFromSlice(eps discoveryv1.EndpointSlice) Endpoints {
|
||||
var result Endpoints
|
||||
|
||||
for _, ep := range eps.Endpoints {
|
||||
if ep.Conditions.Ready != nil && *ep.Conditions.Ready {
|
||||
result.Addresses = append(result.Addresses, ep.Addresses...)
|
||||
result.Targets = append(result.Targets, strings.ToLower(fmt.Sprintf("%s/%s", ep.TargetRef.Kind, ep.TargetRef.Name)))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -61,6 +93,7 @@ func newEndpointsFromSlice(eps *discoveryv1.EndpointSlice) Endpoints {
|
|||
|
||||
type Endpoints struct {
|
||||
Addresses []string
|
||||
Targets []string
|
||||
}
|
||||
|
||||
func (e Endpoints) LBEndpoints(port uint32) []*endpointv3.LbEndpoint {
|
||||
|
|
|
@ -1,29 +1,8 @@
|
|||
/*
|
||||
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 controlplane
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"slices"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
corev3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
|
||||
listenerv3 "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3"
|
||||
|
@ -35,171 +14,64 @@ import (
|
|||
"github.com/envoyproxy/go-control-plane/pkg/resource/v3"
|
||||
"google.golang.org/protobuf/types/known/anypb"
|
||||
discoveryv1 "k8s.io/api/discovery/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/selection"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/log"
|
||||
|
||||
"code.icb4dc0.de/prskr/supabase-operator/internal/meta"
|
||||
"code.icb4dc0.de/prskr/supabase-operator/internal/supabase"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrUnexpectedObject = errors.New("unexpected object")
|
||||
ErrNoEnvoyClusterLabel = errors.New("no Envoy cluster label set")
|
||||
|
||||
supabaseServices = []string{
|
||||
supabase.ServiceConfig.Postgrest.Name,
|
||||
supabase.ServiceConfig.Auth.Name,
|
||||
supabase.ServiceConfig.PGMeta.Name,
|
||||
}
|
||||
)
|
||||
|
||||
type EndpointsController struct {
|
||||
lock sync.Mutex
|
||||
Client client.WithWatch
|
||||
Cache cache.SnapshotCache
|
||||
envoyClusters map[string]*envoyClusterServices
|
||||
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"`
|
||||
}
|
||||
|
||||
func (c *EndpointsController) Run(ctx context.Context) error {
|
||||
var (
|
||||
logger = ctrl.Log.WithName("endpoints-controller")
|
||||
endpointSlices discoveryv1.EndpointSliceList
|
||||
)
|
||||
|
||||
selector := labels.NewSelector()
|
||||
|
||||
partOfRequirement, err := labels.NewRequirement(meta.WellKnownLabel.PartOf, selection.Equals, []string{"supabase"})
|
||||
if err != nil {
|
||||
return fmt.Errorf("preparing watcher selectors: %w", err)
|
||||
}
|
||||
|
||||
nameRequirement, err := labels.NewRequirement(meta.WellKnownLabel.Name, selection.In, supabaseServices)
|
||||
if err != nil {
|
||||
return fmt.Errorf("preparing watcher selectors: %w", err)
|
||||
}
|
||||
|
||||
envoyClusterRequirement, err := labels.NewRequirement(meta.SupabaseLabel.EnvoyCluster, selection.Exists, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("preparing watcher selectors: %w", err)
|
||||
}
|
||||
|
||||
selector.Add(*partOfRequirement, *nameRequirement, *envoyClusterRequirement)
|
||||
|
||||
watcher, err := c.Client.Watch(
|
||||
ctx,
|
||||
&endpointSlices,
|
||||
client.MatchingLabelsSelector{
|
||||
Selector: selector.Add(*partOfRequirement, *nameRequirement, *envoyClusterRequirement),
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer watcher.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case ev, more := <-watcher.ResultChan():
|
||||
if !more {
|
||||
return nil
|
||||
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)
|
||||
}
|
||||
eventLogger := logger.WithValues("event_type", ev.Type)
|
||||
switch ev.Type {
|
||||
case watch.Added, watch.Modified:
|
||||
eps, ok := ev.Object.(*discoveryv1.EndpointSlice)
|
||||
if !ok {
|
||||
logger.Error(fmt.Errorf("%w: %T", ErrUnexpectedObject, ev.Object), "expected EndpointSlice but got a different object type")
|
||||
continue
|
||||
}
|
||||
|
||||
if err := c.handleModificationEvent(log.IntoContext(ctx, eventLogger), eps); err != nil {
|
||||
logger.Error(err, "error occurred during event handling")
|
||||
}
|
||||
s.Postgrest.AddOrUpdateEndpoints(eps)
|
||||
case supabase.ServiceConfig.Auth.Name:
|
||||
if s.GoTrue == nil {
|
||||
s.GoTrue = new(GoTrueCluster)
|
||||
}
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
s.GoTrue.AddOrUpdateEndpoints(eps)
|
||||
case supabase.ServiceConfig.PGMeta.Name:
|
||||
if s.PGMeta == nil {
|
||||
s.PGMeta = new(PGMetaCluster)
|
||||
}
|
||||
s.PGMeta.AddOrUpdateEndpoints(eps)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *EndpointsController) handleModificationEvent(ctx context.Context, epSlice *discoveryv1.EndpointSlice) error {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
func (s EnvoyServices) Targets() map[string][]string {
|
||||
targets := make(map[string][]string)
|
||||
|
||||
var (
|
||||
logger = log.FromContext(ctx)
|
||||
instanceKey string
|
||||
svc *envoyClusterServices
|
||||
)
|
||||
|
||||
logger.Info("Observed endpoint slice", "name", epSlice.Name)
|
||||
|
||||
if c.envoyClusters == nil {
|
||||
c.envoyClusters = make(map[string]*envoyClusterServices)
|
||||
if s.Postgrest != nil {
|
||||
targets[supabase.ServiceConfig.Postgrest.Name] = s.Postgrest.Targets()
|
||||
}
|
||||
|
||||
envoyNodeName, ok := epSlice.Labels[meta.SupabaseLabel.EnvoyCluster]
|
||||
if !ok {
|
||||
return fmt.Errorf("%w: at object %s", ErrNoEnvoyClusterLabel, epSlice.Name)
|
||||
if s.GoTrue != nil {
|
||||
targets[supabase.ServiceConfig.Auth.Name] = s.GoTrue.Targets()
|
||||
}
|
||||
|
||||
instanceKey = fmt.Sprintf("%s:%s", envoyNodeName, epSlice.Namespace)
|
||||
|
||||
if svc, ok = c.envoyClusters[instanceKey]; !ok {
|
||||
svc = new(envoyClusterServices)
|
||||
if s.PGMeta != nil {
|
||||
targets[supabase.ServiceConfig.PGMeta.Name] = s.PGMeta.Targets()
|
||||
}
|
||||
|
||||
svc.UpsertEndpoints(epSlice)
|
||||
if s.Studio != nil {
|
||||
targets[supabase.ServiceConfig.Studio.Name] = s.Studio.Targets()
|
||||
}
|
||||
|
||||
c.envoyClusters[instanceKey] = svc
|
||||
|
||||
return c.updateSnapshot(ctx, instanceKey)
|
||||
return targets
|
||||
}
|
||||
|
||||
func (c *EndpointsController) updateSnapshot(ctx context.Context, instance string) error {
|
||||
latestVersion := strconv.FormatInt(time.Now().UTC().UnixMilli(), 10)
|
||||
|
||||
snapshot, err := c.envoyClusters[instance].snapshot(instance, latestVersion)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.Cache.SetSnapshot(ctx, instance, snapshot)
|
||||
}
|
||||
|
||||
type envoyClusterServices struct {
|
||||
Postgrest *PostgrestCluster
|
||||
GoTrue *GoTrueCluster
|
||||
PGMeta *PGMetaCluster
|
||||
Studio *StudioCluster
|
||||
}
|
||||
|
||||
func (s *envoyClusterServices) UpsertEndpoints(eps *discoveryv1.EndpointSlice) {
|
||||
switch eps.Labels[meta.WellKnownLabel.Name] {
|
||||
case supabase.ServiceConfig.Postgrest.Name:
|
||||
if s.Postgrest == nil {
|
||||
s.Postgrest = new(PostgrestCluster)
|
||||
}
|
||||
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)
|
||||
}
|
||||
s.PGMeta.AddOrUpdateEndpoints(eps)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *envoyClusterServices) snapshot(instance, version string) (*cache.Snapshot, error) {
|
||||
func (s *EnvoyServices) snapshot(ctx context.Context, instance, version string) (*cache.Snapshot, error) {
|
||||
const (
|
||||
apiRouteName = "supabase"
|
||||
studioRouteName = "supabas-studio"
|
||||
|
@ -207,6 +79,8 @@ func (s *envoyClusterServices) snapshot(instance, version string) (*cache.Snapsh
|
|||
listenerName = "supabase"
|
||||
)
|
||||
|
||||
logger := log.FromContext(ctx)
|
||||
|
||||
apiConnectionManager := &hcm.HttpConnectionManager{
|
||||
CodecType: hcm.HttpConnectionManager_AUTO,
|
||||
StatPrefix: "http",
|
||||
|
@ -327,6 +201,8 @@ func (s *envoyClusterServices) snapshot(instance, version string) (*cache.Snapsh
|
|||
}}
|
||||
|
||||
if s.Studio != nil {
|
||||
logger.Info("Adding studio listener")
|
||||
|
||||
listeners = append(listeners, &listenerv3.Listener{
|
||||
Name: "studio",
|
||||
Address: &corev3.Address{
|
|
@ -39,9 +39,9 @@ var WellKnownLabel = struct {
|
|||
}
|
||||
|
||||
var SupabaseLabel = struct {
|
||||
Reload string
|
||||
EnvoyCluster string
|
||||
Reload string
|
||||
ApiGatewayTarget string
|
||||
}{
|
||||
Reload: supabasev1alpha1.GroupVersion.Group + "/reload",
|
||||
EnvoyCluster: supabasev1alpha1.GroupVersion.Group + "/envoy-cluster",
|
||||
Reload: supabasev1alpha1.GroupVersion.Group + "/reload",
|
||||
ApiGatewayTarget: supabasev1alpha1.GroupVersion.Group + "/api-gateway-target",
|
||||
}
|
||||
|
|
27
testdata/dotnet-client/supabase-integration.sln
vendored
Normal file
27
testdata/dotnet-client/supabase-integration.sln
vendored
Normal file
|
@ -0,0 +1,27 @@
|
|||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.0.31903.59
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{1A6ACECB-D8D9-4613-B56E-8A4FEB6A78A0}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "supabase-integration.api-test", "test\supabase-integration.api-test\supabase-integration.api-test.csproj", "{8C0E9D7E-5331-4AFB-919A-F00967971025}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{8C0E9D7E-5331-4AFB-919A-F00967971025}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{8C0E9D7E-5331-4AFB-919A-F00967971025}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{8C0E9D7E-5331-4AFB-919A-F00967971025}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{8C0E9D7E-5331-4AFB-919A-F00967971025}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(NestedProjects) = preSolution
|
||||
{8C0E9D7E-5331-4AFB-919A-F00967971025} = {1A6ACECB-D8D9-4613-B56E-8A4FEB6A78A0}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
6
testdata/dotnet-client/supabase-integration.sln.DotSettings.user
vendored
Normal file
6
testdata/dotnet-client/supabase-integration.sln.DotSettings.user
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||
<s:String x:Key="/Default/Environment/UnitTesting/UnitTestSessionStore/Sessions/=e8b9e660_002Dfb4d_002D48ab_002D99f4_002D8949fff0e981/@EntryIndexedValue"><SessionState ContinuousTestingMode="0" IsActive="True" Name="TestListTasks(SupabaseClientFixture)" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session">
|
||||
<TestAncestor>
|
||||
<TestId>Testing Platform::8C0E9D7E-5331-4AFB-919A-F00967971025::net9.0::ServiceKeyTest</TestId>
|
||||
</TestAncestor>
|
||||
</SessionState></s:String></wpf:ResourceDictionary>
|
27
testdata/dotnet-client/test/supabase-integration.api-test/ServiceKeyTest.cs
vendored
Normal file
27
testdata/dotnet-client/test/supabase-integration.api-test/ServiceKeyTest.cs
vendored
Normal file
|
@ -0,0 +1,27 @@
|
|||
using Supabase.Postgrest.Attributes;
|
||||
using Supabase.Postgrest.Models;
|
||||
|
||||
namespace supabase_integration.api_test;
|
||||
|
||||
public class ServiceKeyTest
|
||||
{
|
||||
[Test]
|
||||
[ClassDataSource<SupabaseClientFixture>(Shared = SharedType.PerAssembly)]
|
||||
public async Task TestListTasks(SupabaseClientFixture fixture)
|
||||
{
|
||||
var resp = await fixture.ApiClient.Postgrest.Table<TaskList>().Get().ConfigureAwait(false);
|
||||
|
||||
await Assert.That(resp.Models.Count).IsGreaterThan(0);
|
||||
}
|
||||
}
|
||||
|
||||
[Table("lists")]
|
||||
public class TaskList : BaseModel
|
||||
{
|
||||
[PrimaryKey("id")]
|
||||
public int Id { get; set; }
|
||||
[Column("user_id")]
|
||||
public int UserId { get; set; }
|
||||
[Column("name")]
|
||||
public string Name { get; set; }
|
||||
}
|
22
testdata/dotnet-client/test/supabase-integration.api-test/SupabaseClientFixture.cs
vendored
Normal file
22
testdata/dotnet-client/test/supabase-integration.api-test/SupabaseClientFixture.cs
vendored
Normal file
|
@ -0,0 +1,22 @@
|
|||
using Supabase;
|
||||
using TUnit.Core.Interfaces;
|
||||
|
||||
namespace supabase_integration.api_test;
|
||||
|
||||
public class SupabaseClientFixture : IAsyncInitializer
|
||||
{
|
||||
public Task InitializeAsync()
|
||||
{
|
||||
ApiClient = new Client(
|
||||
Environment.GetEnvironmentVariable("SUPBASE_URL") ?? "http://localhost:8000",
|
||||
Environment.GetEnvironmentVariable("SUPBASE_ACCESS_KEY") ?? throw new ArgumentException("Supabase access key is missing."),
|
||||
new SupabaseOptions
|
||||
{
|
||||
AutoConnectRealtime = false
|
||||
}
|
||||
);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Client ApiClient { get; private set; }
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
// <autogenerated />
|
||||
using System;
|
||||
using System.Reflection;
|
||||
[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETCoreApp,Version=v9.0", FrameworkDisplayName = ".NET 9.0")]
|
|
@ -0,0 +1,16 @@
|
|||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by Microsoft.Testing.Platform.MSBuild
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
|
||||
internal static class SelfRegisteredExtensions
|
||||
{
|
||||
public static void AddSelfRegisteredExtensions(this global::Microsoft.Testing.Platform.Builder.ITestApplicationBuilder builder, string[] args)
|
||||
{
|
||||
Microsoft.Testing.Platform.MSBuild.TestingPlatformBuilderHook.AddExtensions(builder, args);
|
||||
TUnit.Engine.Framework.TestingPlatformBuilderHook.AddExtensions(builder, args);
|
||||
Microsoft.Testing.Extensions.CodeCoverage.TestingPlatformBuilderHook.AddExtensions(builder, args);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by Microsoft.Testing.Platform.MSBuild
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
|
||||
internal sealed class TestingPlatformEntryPoint
|
||||
{
|
||||
public static async global::System.Threading.Tasks.Task<int> Main(string[] args)
|
||||
{
|
||||
global::Microsoft.Testing.Platform.Builder.ITestApplicationBuilder builder = await global::Microsoft.Testing.Platform.Builder.TestApplication.CreateBuilderAsync(args);
|
||||
SelfRegisteredExtensions.AddSelfRegisteredExtensions(builder, args);
|
||||
using (global::Microsoft.Testing.Platform.Builder.ITestApplication app = await builder.BuildAsync())
|
||||
{
|
||||
return await app.RunAsync();
|
||||
}
|
||||
}
|
||||
}
|
BIN
testdata/dotnet-client/test/supabase-integration.api-test/obj/Debug/net9.0/apphost
vendored
Executable file
BIN
testdata/dotnet-client/test/supabase-integration.api-test/obj/Debug/net9.0/apphost
vendored
Executable file
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,23 @@
|
|||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.Reflection;
|
||||
|
||||
[assembly: System.Reflection.AssemblyCompanyAttribute("supabase-integration.api-test")]
|
||||
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
|
||||
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
|
||||
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+a5c170a47816e50bce4b39b7d7b54764d92acb71")]
|
||||
[assembly: System.Reflection.AssemblyProductAttribute("supabase-integration.api-test")]
|
||||
[assembly: System.Reflection.AssemblyTitleAttribute("supabase-integration.api-test")]
|
||||
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
|
||||
[assembly: System.Reflection.AssemblyMetadata("Microsoft.Testing.Platform.Application", "True")]
|
||||
|
||||
// Generated by the MSBuild WriteCodeFragment class.
|
||||
|
|
@ -0,0 +1 @@
|
|||
c30ee9d790c053bb091f7b65a0618bb1bd275cc4b6492d6624dc720874d75514
|
|
@ -0,0 +1,15 @@
|
|||
is_global = true
|
||||
build_property.TargetFramework = net9.0
|
||||
build_property.TargetPlatformMinVersion =
|
||||
build_property.UsingMicrosoftNETSdkWeb =
|
||||
build_property.ProjectTypeGuids =
|
||||
build_property.InvariantGlobalization =
|
||||
build_property.PlatformNeutralAssembly =
|
||||
build_property.EnforceExtendedAnalyzerRules =
|
||||
build_property._SupportedPlatformList = Linux,macOS,Windows
|
||||
build_property.RootNamespace = supabase_integration.api_test
|
||||
build_property.ProjectDir = /Users/baez/sources/code.icb4dc0.de/prskr/supabase-operator/testdata/dotnet-client/test/supabase-integration.api-test/
|
||||
build_property.EnableComHosting =
|
||||
build_property.EnableGeneratedComInterfaceComImportInterop =
|
||||
build_property.EffectiveAnalysisLevelStyle = 9.0
|
||||
build_property.EnableCodeStyleSeverity =
|
|
@ -0,0 +1,12 @@
|
|||
// <auto-generated/>
|
||||
global using global::System;
|
||||
global using global::System.Collections.Generic;
|
||||
global using global::System.IO;
|
||||
global using global::System.Linq;
|
||||
global using global::System.Net.Http;
|
||||
global using global::System.Threading;
|
||||
global using global::System.Threading.Tasks;
|
||||
global using global::TUnit.Assertions;
|
||||
global using global::TUnit.Assertions.Extensions;
|
||||
global using global::TUnit.Core;
|
||||
global using static global::TUnit.Core.HookType;
|
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1 @@
|
|||
18cd98c3087cd64be3c7db21ec9acf602d7e3393abf67ab6c148e18f5288dcac
|
|
@ -0,0 +1,73 @@
|
|||
/Users/baez/sources/code.icb4dc0.de/prskr/supabase-operator/testdata/dotnet-client/test/supabase-integration.api-test/obj/Debug/net9.0/supabase-integration.api-test.csproj.AssemblyReference.cache
|
||||
/Users/baez/sources/code.icb4dc0.de/prskr/supabase-operator/testdata/dotnet-client/test/supabase-integration.api-test/obj/Debug/net9.0/supabase-integration.api-test.GeneratedMSBuildEditorConfig.editorconfig
|
||||
/Users/baez/sources/code.icb4dc0.de/prskr/supabase-operator/testdata/dotnet-client/test/supabase-integration.api-test/obj/Debug/net9.0/supabase-integration.api-test.AssemblyInfoInputs.cache
|
||||
/Users/baez/sources/code.icb4dc0.de/prskr/supabase-operator/testdata/dotnet-client/test/supabase-integration.api-test/obj/Debug/net9.0/supabase-integration.api-test.AssemblyInfo.cs
|
||||
/Users/baez/sources/code.icb4dc0.de/prskr/supabase-operator/testdata/dotnet-client/test/supabase-integration.api-test/obj/Debug/net9.0/supabase-integration.api-test.csproj.CoreCompileInputs.cache
|
||||
/Users/baez/sources/code.icb4dc0.de/prskr/supabase-operator/testdata/dotnet-client/test/supabase-integration.api-test/obj/Debug/net9.0/supabase-integration.api-test.gentestingplatformentrypointinputcache.cache
|
||||
/Users/baez/sources/code.icb4dc0.de/prskr/supabase-operator/testdata/dotnet-client/test/supabase-integration.api-test/obj/Debug/net9.0/supabase-integration.api-test.genautoregisteredextensionsinputcache.cache
|
||||
/Users/baez/sources/code.icb4dc0.de/prskr/supabase-operator/testdata/dotnet-client/test/supabase-integration.api-test/obj/Debug/net9.0/AutoRegisteredExtensions.cs
|
||||
/Users/baez/sources/code.icb4dc0.de/prskr/supabase-operator/testdata/dotnet-client/test/supabase-integration.api-test/obj/Debug/net9.0/TestPlatformEntryPoint.cs
|
||||
/Users/baez/sources/code.icb4dc0.de/prskr/supabase-operator/testdata/dotnet-client/test/supabase-integration.api-test/bin/Debug/net9.0/supabase-integration.api-test
|
||||
/Users/baez/sources/code.icb4dc0.de/prskr/supabase-operator/testdata/dotnet-client/test/supabase-integration.api-test/bin/Debug/net9.0/supabase-integration.api-test.deps.json
|
||||
/Users/baez/sources/code.icb4dc0.de/prskr/supabase-operator/testdata/dotnet-client/test/supabase-integration.api-test/bin/Debug/net9.0/supabase-integration.api-test.runtimeconfig.json
|
||||
/Users/baez/sources/code.icb4dc0.de/prskr/supabase-operator/testdata/dotnet-client/test/supabase-integration.api-test/bin/Debug/net9.0/supabase-integration.api-test.dll
|
||||
/Users/baez/sources/code.icb4dc0.de/prskr/supabase-operator/testdata/dotnet-client/test/supabase-integration.api-test/bin/Debug/net9.0/supabase-integration.api-test.pdb
|
||||
/Users/baez/sources/code.icb4dc0.de/prskr/supabase-operator/testdata/dotnet-client/test/supabase-integration.api-test/bin/Debug/net9.0/EnumerableAsyncProcessor.dll
|
||||
/Users/baez/sources/code.icb4dc0.de/prskr/supabase-operator/testdata/dotnet-client/test/supabase-integration.api-test/bin/Debug/net9.0/Microsoft.Extensions.DependencyInjection.Abstractions.dll
|
||||
/Users/baez/sources/code.icb4dc0.de/prskr/supabase-operator/testdata/dotnet-client/test/supabase-integration.api-test/bin/Debug/net9.0/Microsoft.Extensions.Logging.Abstractions.dll
|
||||
/Users/baez/sources/code.icb4dc0.de/prskr/supabase-operator/testdata/dotnet-client/test/supabase-integration.api-test/bin/Debug/net9.0/Microsoft.IdentityModel.Abstractions.dll
|
||||
/Users/baez/sources/code.icb4dc0.de/prskr/supabase-operator/testdata/dotnet-client/test/supabase-integration.api-test/bin/Debug/net9.0/Microsoft.IdentityModel.JsonWebTokens.dll
|
||||
/Users/baez/sources/code.icb4dc0.de/prskr/supabase-operator/testdata/dotnet-client/test/supabase-integration.api-test/bin/Debug/net9.0/Microsoft.IdentityModel.Logging.dll
|
||||
/Users/baez/sources/code.icb4dc0.de/prskr/supabase-operator/testdata/dotnet-client/test/supabase-integration.api-test/bin/Debug/net9.0/Microsoft.IdentityModel.Tokens.dll
|
||||
/Users/baez/sources/code.icb4dc0.de/prskr/supabase-operator/testdata/dotnet-client/test/supabase-integration.api-test/bin/Debug/net9.0/Microsoft.IO.RecyclableMemoryStream.dll
|
||||
/Users/baez/sources/code.icb4dc0.de/prskr/supabase-operator/testdata/dotnet-client/test/supabase-integration.api-test/bin/Debug/net9.0/Microsoft.Testing.Extensions.TrxReport.Abstractions.dll
|
||||
/Users/baez/sources/code.icb4dc0.de/prskr/supabase-operator/testdata/dotnet-client/test/supabase-integration.api-test/bin/Debug/net9.0/Microsoft.Testing.Platform.dll
|
||||
/Users/baez/sources/code.icb4dc0.de/prskr/supabase-operator/testdata/dotnet-client/test/supabase-integration.api-test/bin/Debug/net9.0/Microsoft.Testing.Platform.MSBuild.dll
|
||||
/Users/baez/sources/code.icb4dc0.de/prskr/supabase-operator/testdata/dotnet-client/test/supabase-integration.api-test/bin/Debug/net9.0/MimeMapping.dll
|
||||
/Users/baez/sources/code.icb4dc0.de/prskr/supabase-operator/testdata/dotnet-client/test/supabase-integration.api-test/bin/Debug/net9.0/Newtonsoft.Json.dll
|
||||
/Users/baez/sources/code.icb4dc0.de/prskr/supabase-operator/testdata/dotnet-client/test/supabase-integration.api-test/bin/Debug/net9.0/Supabase.dll
|
||||
/Users/baez/sources/code.icb4dc0.de/prskr/supabase-operator/testdata/dotnet-client/test/supabase-integration.api-test/bin/Debug/net9.0/Supabase.Core.dll
|
||||
/Users/baez/sources/code.icb4dc0.de/prskr/supabase-operator/testdata/dotnet-client/test/supabase-integration.api-test/bin/Debug/net9.0/Supabase.Functions.dll
|
||||
/Users/baez/sources/code.icb4dc0.de/prskr/supabase-operator/testdata/dotnet-client/test/supabase-integration.api-test/bin/Debug/net9.0/Supabase.Gotrue.dll
|
||||
/Users/baez/sources/code.icb4dc0.de/prskr/supabase-operator/testdata/dotnet-client/test/supabase-integration.api-test/bin/Debug/net9.0/Supabase.Postgrest.dll
|
||||
/Users/baez/sources/code.icb4dc0.de/prskr/supabase-operator/testdata/dotnet-client/test/supabase-integration.api-test/bin/Debug/net9.0/Supabase.Realtime.dll
|
||||
/Users/baez/sources/code.icb4dc0.de/prskr/supabase-operator/testdata/dotnet-client/test/supabase-integration.api-test/bin/Debug/net9.0/Supabase.Storage.dll
|
||||
/Users/baez/sources/code.icb4dc0.de/prskr/supabase-operator/testdata/dotnet-client/test/supabase-integration.api-test/bin/Debug/net9.0/System.IdentityModel.Tokens.Jwt.dll
|
||||
/Users/baez/sources/code.icb4dc0.de/prskr/supabase-operator/testdata/dotnet-client/test/supabase-integration.api-test/bin/Debug/net9.0/System.Reactive.dll
|
||||
/Users/baez/sources/code.icb4dc0.de/prskr/supabase-operator/testdata/dotnet-client/test/supabase-integration.api-test/bin/Debug/net9.0/TUnit.dll
|
||||
/Users/baez/sources/code.icb4dc0.de/prskr/supabase-operator/testdata/dotnet-client/test/supabase-integration.api-test/bin/Debug/net9.0/TUnit.Assertions.dll
|
||||
/Users/baez/sources/code.icb4dc0.de/prskr/supabase-operator/testdata/dotnet-client/test/supabase-integration.api-test/bin/Debug/net9.0/TUnit.Core.dll
|
||||
/Users/baez/sources/code.icb4dc0.de/prskr/supabase-operator/testdata/dotnet-client/test/supabase-integration.api-test/bin/Debug/net9.0/TUnit.Engine.dll
|
||||
/Users/baez/sources/code.icb4dc0.de/prskr/supabase-operator/testdata/dotnet-client/test/supabase-integration.api-test/bin/Debug/net9.0/Websocket.Client.dll
|
||||
/Users/baez/sources/code.icb4dc0.de/prskr/supabase-operator/testdata/dotnet-client/test/supabase-integration.api-test/bin/Debug/net9.0/cs/Microsoft.Testing.Platform.resources.dll
|
||||
/Users/baez/sources/code.icb4dc0.de/prskr/supabase-operator/testdata/dotnet-client/test/supabase-integration.api-test/bin/Debug/net9.0/de/Microsoft.Testing.Platform.resources.dll
|
||||
/Users/baez/sources/code.icb4dc0.de/prskr/supabase-operator/testdata/dotnet-client/test/supabase-integration.api-test/bin/Debug/net9.0/es/Microsoft.Testing.Platform.resources.dll
|
||||
/Users/baez/sources/code.icb4dc0.de/prskr/supabase-operator/testdata/dotnet-client/test/supabase-integration.api-test/bin/Debug/net9.0/fr/Microsoft.Testing.Platform.resources.dll
|
||||
/Users/baez/sources/code.icb4dc0.de/prskr/supabase-operator/testdata/dotnet-client/test/supabase-integration.api-test/bin/Debug/net9.0/it/Microsoft.Testing.Platform.resources.dll
|
||||
/Users/baez/sources/code.icb4dc0.de/prskr/supabase-operator/testdata/dotnet-client/test/supabase-integration.api-test/bin/Debug/net9.0/ja/Microsoft.Testing.Platform.resources.dll
|
||||
/Users/baez/sources/code.icb4dc0.de/prskr/supabase-operator/testdata/dotnet-client/test/supabase-integration.api-test/bin/Debug/net9.0/ko/Microsoft.Testing.Platform.resources.dll
|
||||
/Users/baez/sources/code.icb4dc0.de/prskr/supabase-operator/testdata/dotnet-client/test/supabase-integration.api-test/bin/Debug/net9.0/pl/Microsoft.Testing.Platform.resources.dll
|
||||
/Users/baez/sources/code.icb4dc0.de/prskr/supabase-operator/testdata/dotnet-client/test/supabase-integration.api-test/bin/Debug/net9.0/pt-BR/Microsoft.Testing.Platform.resources.dll
|
||||
/Users/baez/sources/code.icb4dc0.de/prskr/supabase-operator/testdata/dotnet-client/test/supabase-integration.api-test/bin/Debug/net9.0/ru/Microsoft.Testing.Platform.resources.dll
|
||||
/Users/baez/sources/code.icb4dc0.de/prskr/supabase-operator/testdata/dotnet-client/test/supabase-integration.api-test/bin/Debug/net9.0/tr/Microsoft.Testing.Platform.resources.dll
|
||||
/Users/baez/sources/code.icb4dc0.de/prskr/supabase-operator/testdata/dotnet-client/test/supabase-integration.api-test/bin/Debug/net9.0/zh-Hans/Microsoft.Testing.Platform.resources.dll
|
||||
/Users/baez/sources/code.icb4dc0.de/prskr/supabase-operator/testdata/dotnet-client/test/supabase-integration.api-test/bin/Debug/net9.0/zh-Hant/Microsoft.Testing.Platform.resources.dll
|
||||
/Users/baez/sources/code.icb4dc0.de/prskr/supabase-operator/testdata/dotnet-client/test/supabase-integration.api-test/bin/Debug/net9.0/cs/Microsoft.Testing.Platform.MSBuild.resources.dll
|
||||
/Users/baez/sources/code.icb4dc0.de/prskr/supabase-operator/testdata/dotnet-client/test/supabase-integration.api-test/bin/Debug/net9.0/de/Microsoft.Testing.Platform.MSBuild.resources.dll
|
||||
/Users/baez/sources/code.icb4dc0.de/prskr/supabase-operator/testdata/dotnet-client/test/supabase-integration.api-test/bin/Debug/net9.0/es/Microsoft.Testing.Platform.MSBuild.resources.dll
|
||||
/Users/baez/sources/code.icb4dc0.de/prskr/supabase-operator/testdata/dotnet-client/test/supabase-integration.api-test/bin/Debug/net9.0/fr/Microsoft.Testing.Platform.MSBuild.resources.dll
|
||||
/Users/baez/sources/code.icb4dc0.de/prskr/supabase-operator/testdata/dotnet-client/test/supabase-integration.api-test/bin/Debug/net9.0/it/Microsoft.Testing.Platform.MSBuild.resources.dll
|
||||
/Users/baez/sources/code.icb4dc0.de/prskr/supabase-operator/testdata/dotnet-client/test/supabase-integration.api-test/bin/Debug/net9.0/ja/Microsoft.Testing.Platform.MSBuild.resources.dll
|
||||
/Users/baez/sources/code.icb4dc0.de/prskr/supabase-operator/testdata/dotnet-client/test/supabase-integration.api-test/bin/Debug/net9.0/ko/Microsoft.Testing.Platform.MSBuild.resources.dll
|
||||
/Users/baez/sources/code.icb4dc0.de/prskr/supabase-operator/testdata/dotnet-client/test/supabase-integration.api-test/bin/Debug/net9.0/pl/Microsoft.Testing.Platform.MSBuild.resources.dll
|
||||
/Users/baez/sources/code.icb4dc0.de/prskr/supabase-operator/testdata/dotnet-client/test/supabase-integration.api-test/bin/Debug/net9.0/pt-BR/Microsoft.Testing.Platform.MSBuild.resources.dll
|
||||
/Users/baez/sources/code.icb4dc0.de/prskr/supabase-operator/testdata/dotnet-client/test/supabase-integration.api-test/bin/Debug/net9.0/ru/Microsoft.Testing.Platform.MSBuild.resources.dll
|
||||
/Users/baez/sources/code.icb4dc0.de/prskr/supabase-operator/testdata/dotnet-client/test/supabase-integration.api-test/bin/Debug/net9.0/tr/Microsoft.Testing.Platform.MSBuild.resources.dll
|
||||
/Users/baez/sources/code.icb4dc0.de/prskr/supabase-operator/testdata/dotnet-client/test/supabase-integration.api-test/bin/Debug/net9.0/zh-Hans/Microsoft.Testing.Platform.MSBuild.resources.dll
|
||||
/Users/baez/sources/code.icb4dc0.de/prskr/supabase-operator/testdata/dotnet-client/test/supabase-integration.api-test/bin/Debug/net9.0/zh-Hant/Microsoft.Testing.Platform.MSBuild.resources.dll
|
||||
/Users/baez/sources/code.icb4dc0.de/prskr/supabase-operator/testdata/dotnet-client/test/supabase-integration.api-test/obj/Debug/net9.0/supabase.88E5058B.Up2Date
|
||||
/Users/baez/sources/code.icb4dc0.de/prskr/supabase-operator/testdata/dotnet-client/test/supabase-integration.api-test/obj/Debug/net9.0/supabase-integration.api-test.dll
|
||||
/Users/baez/sources/code.icb4dc0.de/prskr/supabase-operator/testdata/dotnet-client/test/supabase-integration.api-test/obj/Debug/net9.0/refint/supabase-integration.api-test.dll
|
||||
/Users/baez/sources/code.icb4dc0.de/prskr/supabase-operator/testdata/dotnet-client/test/supabase-integration.api-test/obj/Debug/net9.0/supabase-integration.api-test.pdb
|
||||
/Users/baez/sources/code.icb4dc0.de/prskr/supabase-operator/testdata/dotnet-client/test/supabase-integration.api-test/obj/Debug/net9.0/supabase-integration.api-test.genruntimeconfig.cache
|
||||
/Users/baez/sources/code.icb4dc0.de/prskr/supabase-operator/testdata/dotnet-client/test/supabase-integration.api-test/obj/Debug/net9.0/ref/supabase-integration.api-test.dll
|
Binary file not shown.
|
@ -0,0 +1 @@
|
|||
ac7cea3191876b400be0355099ee75cb19980b90abdf6abdaa74befcf3d99901
|
|
@ -0,0 +1 @@
|
|||
24f9af8c04e70f6b6181eaa2f0ed1638767c3450d7387a035086bd4dfd85d781
|
|
@ -0,0 +1 @@
|
|||
ac7cea3191876b400be0355099ee75cb19980b90abdf6abdaa74befcf3d99901
|
Binary file not shown.
2176
testdata/dotnet-client/test/supabase-integration.api-test/obj/project.assets.json
vendored
Normal file
2176
testdata/dotnet-client/test/supabase-integration.api-test/obj/project.assets.json
vendored
Normal file
File diff suppressed because it is too large
Load diff
47
testdata/dotnet-client/test/supabase-integration.api-test/obj/project.nuget.cache
vendored
Normal file
47
testdata/dotnet-client/test/supabase-integration.api-test/obj/project.nuget.cache
vendored
Normal file
|
@ -0,0 +1,47 @@
|
|||
{
|
||||
"version": 2,
|
||||
"dgSpecHash": "R14E93Erk28=",
|
||||
"success": true,
|
||||
"projectFilePath": "/Users/baez/sources/code.icb4dc0.de/prskr/supabase-operator/testdata/dotnet-client/test/supabase-integration.api-test/supabase-integration.api-test.csproj",
|
||||
"expectedPackageFiles": [
|
||||
"/Users/baez/.nuget/packages/enumerableasyncprocessor/2.0.6/enumerableasyncprocessor.2.0.6.nupkg.sha512",
|
||||
"/Users/baez/.nuget/packages/microsoft.diasymreader/2.0.0/microsoft.diasymreader.2.0.0.nupkg.sha512",
|
||||
"/Users/baez/.nuget/packages/microsoft.extensions.dependencyinjection.abstractions/8.0.0/microsoft.extensions.dependencyinjection.abstractions.8.0.0.nupkg.sha512",
|
||||
"/Users/baez/.nuget/packages/microsoft.extensions.dependencymodel/6.0.1/microsoft.extensions.dependencymodel.6.0.1.nupkg.sha512",
|
||||
"/Users/baez/.nuget/packages/microsoft.extensions.logging.abstractions/8.0.0/microsoft.extensions.logging.abstractions.8.0.0.nupkg.sha512",
|
||||
"/Users/baez/.nuget/packages/microsoft.identitymodel.abstractions/7.5.1/microsoft.identitymodel.abstractions.7.5.1.nupkg.sha512",
|
||||
"/Users/baez/.nuget/packages/microsoft.identitymodel.jsonwebtokens/7.5.1/microsoft.identitymodel.jsonwebtokens.7.5.1.nupkg.sha512",
|
||||
"/Users/baez/.nuget/packages/microsoft.identitymodel.logging/7.5.1/microsoft.identitymodel.logging.7.5.1.nupkg.sha512",
|
||||
"/Users/baez/.nuget/packages/microsoft.identitymodel.tokens/7.5.1/microsoft.identitymodel.tokens.7.5.1.nupkg.sha512",
|
||||
"/Users/baez/.nuget/packages/microsoft.io.recyclablememorystream/3.0.0/microsoft.io.recyclablememorystream.3.0.0.nupkg.sha512",
|
||||
"/Users/baez/.nuget/packages/microsoft.testing.extensions.codecoverage/17.13.1/microsoft.testing.extensions.codecoverage.17.13.1.nupkg.sha512",
|
||||
"/Users/baez/.nuget/packages/microsoft.testing.extensions.trxreport.abstractions/1.4.3/microsoft.testing.extensions.trxreport.abstractions.1.4.3.nupkg.sha512",
|
||||
"/Users/baez/.nuget/packages/microsoft.testing.platform/1.4.3/microsoft.testing.platform.1.4.3.nupkg.sha512",
|
||||
"/Users/baez/.nuget/packages/microsoft.testing.platform.msbuild/1.4.3/microsoft.testing.platform.msbuild.1.4.3.nupkg.sha512",
|
||||
"/Users/baez/.nuget/packages/mimemapping/3.0.1/mimemapping.3.0.1.nupkg.sha512",
|
||||
"/Users/baez/.nuget/packages/newtonsoft.json/13.0.3/newtonsoft.json.13.0.3.nupkg.sha512",
|
||||
"/Users/baez/.nuget/packages/supabase/1.1.1/supabase.1.1.1.nupkg.sha512",
|
||||
"/Users/baez/.nuget/packages/supabase.core/1.0.0/supabase.core.1.0.0.nupkg.sha512",
|
||||
"/Users/baez/.nuget/packages/supabase.functions/2.0.0/supabase.functions.2.0.0.nupkg.sha512",
|
||||
"/Users/baez/.nuget/packages/supabase.gotrue/6.0.3/supabase.gotrue.6.0.3.nupkg.sha512",
|
||||
"/Users/baez/.nuget/packages/supabase.postgrest/4.0.3/supabase.postgrest.4.0.3.nupkg.sha512",
|
||||
"/Users/baez/.nuget/packages/supabase.realtime/7.0.2/supabase.realtime.7.0.2.nupkg.sha512",
|
||||
"/Users/baez/.nuget/packages/supabase.storage/2.0.2/supabase.storage.2.0.2.nupkg.sha512",
|
||||
"/Users/baez/.nuget/packages/system.buffers/4.5.1/system.buffers.4.5.1.nupkg.sha512",
|
||||
"/Users/baez/.nuget/packages/system.collections.immutable/8.0.0/system.collections.immutable.8.0.0.nupkg.sha512",
|
||||
"/Users/baez/.nuget/packages/system.identitymodel.tokens.jwt/7.5.1/system.identitymodel.tokens.jwt.7.5.1.nupkg.sha512",
|
||||
"/Users/baez/.nuget/packages/system.memory/4.5.4/system.memory.4.5.4.nupkg.sha512",
|
||||
"/Users/baez/.nuget/packages/system.reactive/6.0.0/system.reactive.6.0.0.nupkg.sha512",
|
||||
"/Users/baez/.nuget/packages/system.reflection.metadata/8.0.0/system.reflection.metadata.8.0.0.nupkg.sha512",
|
||||
"/Users/baez/.nuget/packages/system.runtime.compilerservices.unsafe/6.0.0/system.runtime.compilerservices.unsafe.6.0.0.nupkg.sha512",
|
||||
"/Users/baez/.nuget/packages/system.text.encodings.web/6.0.0/system.text.encodings.web.6.0.0.nupkg.sha512",
|
||||
"/Users/baez/.nuget/packages/system.text.json/6.0.10/system.text.json.6.0.10.nupkg.sha512",
|
||||
"/Users/baez/.nuget/packages/system.threading.channels/8.0.0/system.threading.channels.8.0.0.nupkg.sha512",
|
||||
"/Users/baez/.nuget/packages/tunit/0.6.123/tunit.0.6.123.nupkg.sha512",
|
||||
"/Users/baez/.nuget/packages/tunit.assertions/0.6.123/tunit.assertions.0.6.123.nupkg.sha512",
|
||||
"/Users/baez/.nuget/packages/tunit.core/0.6.123/tunit.core.0.6.123.nupkg.sha512",
|
||||
"/Users/baez/.nuget/packages/tunit.engine/0.6.123/tunit.engine.0.6.123.nupkg.sha512",
|
||||
"/Users/baez/.nuget/packages/websocket.client/5.1.1/websocket.client.5.1.1.nupkg.sha512"
|
||||
],
|
||||
"logs": []
|
||||
}
|
1
testdata/dotnet-client/test/supabase-integration.api-test/obj/project.packagespec.json
vendored
Normal file
1
testdata/dotnet-client/test/supabase-integration.api-test/obj/project.packagespec.json
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
"restore":{"projectUniqueName":"/Users/baez/sources/code.icb4dc0.de/prskr/supabase-operator/testdata/dotnet-client/test/supabase-integration.api-test/supabase-integration.api-test.csproj","projectName":"supabase-integration.api-test","projectPath":"/Users/baez/sources/code.icb4dc0.de/prskr/supabase-operator/testdata/dotnet-client/test/supabase-integration.api-test/supabase-integration.api-test.csproj","outputPath":"/Users/baez/sources/code.icb4dc0.de/prskr/supabase-operator/testdata/dotnet-client/test/supabase-integration.api-test/obj/","projectStyle":"PackageReference","originalTargetFrameworks":["net9.0"],"sources":{"https://api.nuget.org/v3/index.json":{}},"frameworks":{"net9.0":{"targetAlias":"net9.0","projectReferences":{}}},"warningProperties":{"warnAsError":["NU1605"]},"restoreAuditProperties":{"enableAudit":"true","auditLevel":"low","auditMode":"direct"},"SdkAnalysisLevel":"9.0.100"}"frameworks":{"net9.0":{"targetAlias":"net9.0","dependencies":{"Microsoft.Testing.Extensions.CodeCoverage":{"target":"Package","version":"[17.13.1, )"},"Supabase":{"target":"Package","version":"[1.1.1, )"},"TUnit":{"target":"Package","version":"[0.6.123, )"}},"imports":["net461","net462","net47","net471","net472","net48","net481"],"assetTargetFallback":true,"warn":true,"frameworkReferences":{"Microsoft.NETCore.App":{"privateAssets":"all"}},"runtimeIdentifierGraphPath":"/usr/local/share/dotnet/sdk/9.0.102/PortableRuntimeIdentifierGraph.json"}}
|
1
testdata/dotnet-client/test/supabase-integration.api-test/obj/rider.project.model.nuget.info
vendored
Normal file
1
testdata/dotnet-client/test/supabase-integration.api-test/obj/rider.project.model.nuget.info
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
17372014475364470
|
1
testdata/dotnet-client/test/supabase-integration.api-test/obj/rider.project.restore.info
vendored
Normal file
1
testdata/dotnet-client/test/supabase-integration.api-test/obj/rider.project.restore.info
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
17372014475364470
|
|
@ -0,0 +1,81 @@
|
|||
{
|
||||
"format": 1,
|
||||
"restore": {
|
||||
"/Users/baez/sources/code.icb4dc0.de/prskr/supabase-operator/testdata/dotnet-client/test/supabase-integration.api-test/supabase-integration.api-test.csproj": {}
|
||||
},
|
||||
"projects": {
|
||||
"/Users/baez/sources/code.icb4dc0.de/prskr/supabase-operator/testdata/dotnet-client/test/supabase-integration.api-test/supabase-integration.api-test.csproj": {
|
||||
"version": "1.0.0",
|
||||
"restore": {
|
||||
"projectUniqueName": "/Users/baez/sources/code.icb4dc0.de/prskr/supabase-operator/testdata/dotnet-client/test/supabase-integration.api-test/supabase-integration.api-test.csproj",
|
||||
"projectName": "supabase-integration.api-test",
|
||||
"projectPath": "/Users/baez/sources/code.icb4dc0.de/prskr/supabase-operator/testdata/dotnet-client/test/supabase-integration.api-test/supabase-integration.api-test.csproj",
|
||||
"packagesPath": "/Users/baez/.nuget/packages/",
|
||||
"outputPath": "/Users/baez/sources/code.icb4dc0.de/prskr/supabase-operator/testdata/dotnet-client/test/supabase-integration.api-test/obj/",
|
||||
"projectStyle": "PackageReference",
|
||||
"configFilePaths": [
|
||||
"/Users/baez/.nuget/NuGet/NuGet.Config"
|
||||
],
|
||||
"originalTargetFrameworks": [
|
||||
"net9.0"
|
||||
],
|
||||
"sources": {
|
||||
"https://api.nuget.org/v3/index.json": {}
|
||||
},
|
||||
"frameworks": {
|
||||
"net9.0": {
|
||||
"targetAlias": "net9.0",
|
||||
"projectReferences": {}
|
||||
}
|
||||
},
|
||||
"warningProperties": {
|
||||
"warnAsError": [
|
||||
"NU1605"
|
||||
]
|
||||
},
|
||||
"restoreAuditProperties": {
|
||||
"enableAudit": "true",
|
||||
"auditLevel": "low",
|
||||
"auditMode": "direct"
|
||||
},
|
||||
"SdkAnalysisLevel": "9.0.100"
|
||||
},
|
||||
"frameworks": {
|
||||
"net9.0": {
|
||||
"targetAlias": "net9.0",
|
||||
"dependencies": {
|
||||
"Microsoft.Testing.Extensions.CodeCoverage": {
|
||||
"target": "Package",
|
||||
"version": "[17.13.1, )"
|
||||
},
|
||||
"Supabase": {
|
||||
"target": "Package",
|
||||
"version": "[1.1.1, )"
|
||||
},
|
||||
"TUnit": {
|
||||
"target": "Package",
|
||||
"version": "[0.6.123, )"
|
||||
}
|
||||
},
|
||||
"imports": [
|
||||
"net461",
|
||||
"net462",
|
||||
"net47",
|
||||
"net471",
|
||||
"net472",
|
||||
"net48",
|
||||
"net481"
|
||||
],
|
||||
"assetTargetFallback": true,
|
||||
"warn": true,
|
||||
"frameworkReferences": {
|
||||
"Microsoft.NETCore.App": {
|
||||
"privateAssets": "all"
|
||||
}
|
||||
},
|
||||
"runtimeIdentifierGraphPath": "/usr/local/share/dotnet/sdk/9.0.102/PortableRuntimeIdentifierGraph.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
<?xml version="1.0" encoding="utf-8" standalone="no"?>
|
||||
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
|
||||
<RestoreSuccess Condition=" '$(RestoreSuccess)' == '' ">True</RestoreSuccess>
|
||||
<RestoreTool Condition=" '$(RestoreTool)' == '' ">NuGet</RestoreTool>
|
||||
<ProjectAssetsFile Condition=" '$(ProjectAssetsFile)' == '' ">$(MSBuildThisFileDirectory)project.assets.json</ProjectAssetsFile>
|
||||
<NuGetPackageRoot Condition=" '$(NuGetPackageRoot)' == '' ">/Users/baez/.nuget/packages/</NuGetPackageRoot>
|
||||
<NuGetPackageFolders Condition=" '$(NuGetPackageFolders)' == '' ">/Users/baez/.nuget/packages/</NuGetPackageFolders>
|
||||
<NuGetProjectStyle Condition=" '$(NuGetProjectStyle)' == '' ">PackageReference</NuGetProjectStyle>
|
||||
<NuGetToolVersion Condition=" '$(NuGetToolVersion)' == '' ">6.12.2</NuGetToolVersion>
|
||||
</PropertyGroup>
|
||||
<ItemGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
|
||||
<SourceRoot Include="/Users/baez/.nuget/packages/" />
|
||||
</ItemGroup>
|
||||
<ImportGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
|
||||
<Import Project="$(NuGetPackageRoot)tunit.core/0.6.123/buildTransitive/net9.0/TUnit.Core.props" Condition="Exists('$(NuGetPackageRoot)tunit.core/0.6.123/buildTransitive/net9.0/TUnit.Core.props')" />
|
||||
<Import Project="$(NuGetPackageRoot)microsoft.testing.platform/1.4.3/buildTransitive/net8.0/Microsoft.Testing.Platform.props" Condition="Exists('$(NuGetPackageRoot)microsoft.testing.platform/1.4.3/buildTransitive/net8.0/Microsoft.Testing.Platform.props')" />
|
||||
<Import Project="$(NuGetPackageRoot)microsoft.testing.platform.msbuild/1.4.3/buildTransitive/net8.0/Microsoft.Testing.Platform.MSBuild.props" Condition="Exists('$(NuGetPackageRoot)microsoft.testing.platform.msbuild/1.4.3/buildTransitive/net8.0/Microsoft.Testing.Platform.MSBuild.props')" />
|
||||
<Import Project="$(NuGetPackageRoot)tunit.engine/0.6.123/buildTransitive/net9.0/TUnit.Engine.props" Condition="Exists('$(NuGetPackageRoot)tunit.engine/0.6.123/buildTransitive/net9.0/TUnit.Engine.props')" />
|
||||
<Import Project="$(NuGetPackageRoot)tunit.assertions/0.6.123/buildTransitive/net9.0/TUnit.Assertions.props" Condition="Exists('$(NuGetPackageRoot)tunit.assertions/0.6.123/buildTransitive/net9.0/TUnit.Assertions.props')" />
|
||||
<Import Project="$(NuGetPackageRoot)microsoft.testing.extensions.codecoverage/17.13.1/buildTransitive/net6.0/Microsoft.Testing.Extensions.CodeCoverage.props" Condition="Exists('$(NuGetPackageRoot)microsoft.testing.extensions.codecoverage/17.13.1/buildTransitive/net6.0/Microsoft.Testing.Extensions.CodeCoverage.props')" />
|
||||
</ImportGroup>
|
||||
</Project>
|
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="utf-8" standalone="no"?>
|
||||
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ImportGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
|
||||
<Import Project="$(NuGetPackageRoot)microsoft.extensions.logging.abstractions/8.0.0/buildTransitive/net6.0/Microsoft.Extensions.Logging.Abstractions.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.extensions.logging.abstractions/8.0.0/buildTransitive/net6.0/Microsoft.Extensions.Logging.Abstractions.targets')" />
|
||||
<Import Project="$(NuGetPackageRoot)tunit.core/0.6.123/buildTransitive/net9.0/TUnit.Core.targets" Condition="Exists('$(NuGetPackageRoot)tunit.core/0.6.123/buildTransitive/net9.0/TUnit.Core.targets')" />
|
||||
<Import Project="$(NuGetPackageRoot)microsoft.testing.platform.msbuild/1.4.3/buildTransitive/net8.0/Microsoft.Testing.Platform.MSBuild.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.testing.platform.msbuild/1.4.3/buildTransitive/net8.0/Microsoft.Testing.Platform.MSBuild.targets')" />
|
||||
<Import Project="$(NuGetPackageRoot)tunit.assertions/0.6.123/buildTransitive/net9.0/TUnit.Assertions.targets" Condition="Exists('$(NuGetPackageRoot)tunit.assertions/0.6.123/buildTransitive/net9.0/TUnit.Assertions.targets')" />
|
||||
<Import Project="$(NuGetPackageRoot)system.text.json/6.0.10/buildTransitive/netcoreapp3.1/System.Text.Json.targets" Condition="Exists('$(NuGetPackageRoot)system.text.json/6.0.10/buildTransitive/netcoreapp3.1/System.Text.Json.targets')" />
|
||||
<Import Project="$(NuGetPackageRoot)microsoft.testing.extensions.codecoverage/17.13.1/buildTransitive/net6.0/Microsoft.Testing.Extensions.CodeCoverage.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.testing.extensions.codecoverage/17.13.1/buildTransitive/net6.0/Microsoft.Testing.Extensions.CodeCoverage.targets')" />
|
||||
</ImportGroup>
|
||||
</Project>
|
22
testdata/dotnet-client/test/supabase-integration.api-test/supabase-integration.api-test.csproj
vendored
Normal file
22
testdata/dotnet-client/test/supabase-integration.api-test/supabase-integration.api-test.csproj
vendored
Normal file
|
@ -0,0 +1,22 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<OutputType>Exe</OutputType>
|
||||
<RootNamespace>supabase_integration.api_test</RootNamespace>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Supabase" Version="1.1.1" />
|
||||
<PackageReference Include="TUnit" Version="0.6.123" />
|
||||
<PackageReference Include="Microsoft.Testing.Extensions.CodeCoverage" Version="17.13.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Using Include="TUnit.Core" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
Loading…
Add table
Reference in a new issue