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
|
- gocyclo
|
||||||
- gofmt
|
- gofmt
|
||||||
- goimports
|
- goimports
|
||||||
|
- goheader
|
||||||
- gosimple
|
- gosimple
|
||||||
- godox
|
- godox
|
||||||
- govet
|
- govet
|
||||||
|
@ -56,6 +57,12 @@ linters-settings:
|
||||||
- dot
|
- dot
|
||||||
goimports:
|
goimports:
|
||||||
local-prefixes: code.icb4dc0.de/prskr/supabase-operator
|
local-prefixes: code.icb4dc0.de/prskr/supabase-operator
|
||||||
|
goheader:
|
||||||
|
values:
|
||||||
|
const:
|
||||||
|
AUTHOR: Peter Kurfer
|
||||||
|
template-path: hack/header.tmpl
|
||||||
|
|
||||||
importas:
|
importas:
|
||||||
no-unaliased: true
|
no-unaliased: true
|
||||||
no-extra-aliases: true
|
no-extra-aliases: true
|
||||||
|
|
9
PROJECT
9
PROJECT
|
@ -47,4 +47,13 @@ resources:
|
||||||
defaulting: true
|
defaulting: true
|
||||||
validation: true
|
validation: true
|
||||||
webhookVersion: v1
|
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"
|
version: "3"
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2024 Peter Kurfer.
|
Copyright 2025 Peter Kurfer.
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with 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"`
|
Envoy *EnvoySpec `json:"envoy"`
|
||||||
// JWKSSelector - selector where the JWKS can be retrieved from to enable the API gateway to validate JWTs
|
// JWKSSelector - selector where the JWKS can be retrieved from to enable the API gateway to validate JWTs
|
||||||
JWKSSelector *corev1.SecretKeySelector `json:"jwks"`
|
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.
|
// 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:object:root=true
|
||||||
// +kubebuilder:subresource:status
|
// +kubebuilder:subresource:status
|
||||||
|
|
||||||
// APIGateway is the Schema for the apigateways API.
|
// APIGateway is the Schema for the apigateways API.
|
||||||
|
// +kubebuilder:printcolumn:name="EnvoyConfigVersion",type=string,JSONPath=`.status.envoy.configVersion`
|
||||||
type APIGateway struct {
|
type APIGateway struct {
|
||||||
metav1.TypeMeta `json:",inline"`
|
metav1.TypeMeta `json:",inline"`
|
||||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
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
|
//go:build !ignore_autogenerated
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Copyright 2024 Peter Kurfer.
|
Copyright 2025 Peter Kurfer.
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -22,6 +22,7 @@ package v1alpha1
|
||||||
|
|
||||||
import (
|
import (
|
||||||
v1 "k8s.io/api/core/v1"
|
v1 "k8s.io/api/core/v1"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
runtime "k8s.io/apimachinery/pkg/runtime"
|
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -31,7 +32,7 @@ func (in *APIGateway) DeepCopyInto(out *APIGateway) {
|
||||||
out.TypeMeta = in.TypeMeta
|
out.TypeMeta = in.TypeMeta
|
||||||
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
|
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
|
||||||
in.Spec.DeepCopyInto(&out.Spec)
|
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.
|
// 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)
|
*out = new(v1.SecretKeySelector)
|
||||||
(*in).DeepCopyInto(*out)
|
(*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.
|
// 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.
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
func (in *APIGatewayStatus) DeepCopyInto(out *APIGatewayStatus) {
|
func (in *APIGatewayStatus) DeepCopyInto(out *APIGatewayStatus) {
|
||||||
*out = *in
|
*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.
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new APIGatewayStatus.
|
||||||
|
@ -764,6 +787,26 @@ func (in *EnvoySpec) DeepCopy() *EnvoySpec {
|
||||||
return out
|
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.
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
func (in *GithubAuthProvider) DeepCopyInto(out *GithubAuthProvider) {
|
func (in *GithubAuthProvider) DeepCopyInto(out *GithubAuthProvider) {
|
||||||
*out = *in
|
*out = *in
|
||||||
|
@ -903,6 +946,95 @@ func (in *PostgrestSpec) DeepCopy() *PostgrestSpec {
|
||||||
return out
|
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.
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
func (in *StudioSpec) DeepCopyInto(out *StudioSpec) {
|
func (in *StudioSpec) DeepCopyInto(out *StudioSpec) {
|
||||||
*out = *in
|
*out = *in
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2024 Peter Kurfer.
|
Copyright 2025 Peter Kurfer.
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -18,12 +18,11 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"crypto/tls"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/alecthomas/kong"
|
|
||||||
clusterservice "github.com/envoyproxy/go-control-plane/envoy/service/cluster/v3"
|
clusterservice "github.com/envoyproxy/go-control-plane/envoy/service/cluster/v3"
|
||||||
discoverygrpc "github.com/envoyproxy/go-control-plane/envoy/service/discovery/v3"
|
discoverygrpc "github.com/envoyproxy/go-control-plane/envoy/service/discovery/v3"
|
||||||
endpointservice "github.com/envoyproxy/go-control-plane/envoy/service/endpoint/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"
|
routeservice "github.com/envoyproxy/go-control-plane/envoy/service/route/v3"
|
||||||
runtimeservice "github.com/envoyproxy/go-control-plane/envoy/service/runtime/v3"
|
runtimeservice "github.com/envoyproxy/go-control-plane/envoy/service/runtime/v3"
|
||||||
secretservice "github.com/envoyproxy/go-control-plane/envoy/service/secret/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"
|
"github.com/envoyproxy/go-control-plane/pkg/server/v3"
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
grpchealth "google.golang.org/grpc/health"
|
grpchealth "google.golang.org/grpc/health"
|
||||||
|
@ -39,16 +38,103 @@ import (
|
||||||
"google.golang.org/grpc/keepalive"
|
"google.golang.org/grpc/keepalive"
|
||||||
"google.golang.org/grpc/reflection"
|
"google.golang.org/grpc/reflection"
|
||||||
ctrl "sigs.k8s.io/controller-runtime"
|
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"
|
"code.icb4dc0.de/prskr/supabase-operator/internal/controlplane"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
//nolint:lll // flag declaration with struct tags is as long as it is
|
||||||
type controlPlane struct {
|
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 (
|
const (
|
||||||
grpcKeepaliveTime = 30 * time.Second
|
grpcKeepaliveTime = 30 * time.Second
|
||||||
grpcKeepaliveTimeout = 5 * time.Second
|
grpcKeepaliveTimeout = 5 * time.Second
|
||||||
|
@ -56,19 +142,10 @@ func (p controlPlane) Run(ctx context.Context, cache cache.SnapshotCache) (err e
|
||||||
grpcMaxConcurrentStreams = 1000000
|
grpcMaxConcurrentStreams = 1000000
|
||||||
)
|
)
|
||||||
|
|
||||||
logger := ctrl.Log.WithName("control-plane")
|
var (
|
||||||
|
logger = ctrl.Log.WithName("control-plane")
|
||||||
clientOpts := client.Options{
|
srv = server.NewServer(ctx, cache, nil)
|
||||||
Scheme: scheme,
|
)
|
||||||
}
|
|
||||||
|
|
||||||
logger.Info("Creating client")
|
|
||||||
watcherClient, err := client.NewWithWatch(ctrl.GetConfigOrDie(), clientOpts)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
srv := server.NewServer(ctx, cache, nil)
|
|
||||||
|
|
||||||
// gRPC golang library sets a very small upper bound for the number gRPC/h2
|
// 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
|
// 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...)
|
grpcServer := grpc.NewServer(grpcOptions...)
|
||||||
|
|
||||||
logger.Info("Opening listener", "addr", p.ListenAddr)
|
logger.Info("Opening listener", "addr", cp.ListenAddr)
|
||||||
lis, err := net.Listen("tcp", p.ListenAddr)
|
lis, err := net.Listen("tcp", cp.ListenAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("opening listener: %w", err)
|
return nil, fmt.Errorf("opening listener: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Info("Preparing health endpoints")
|
logger.Info("Preparing health endpoints")
|
||||||
|
|
||||||
healthService := grpchealth.NewServer()
|
healthService := grpchealth.NewServer()
|
||||||
healthService.SetServingStatus("", grpc_health_v1.HealthCheckResponse_SERVING)
|
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)
|
runtimeservice.RegisterRuntimeDiscoveryServiceServer(grpcServer, srv)
|
||||||
grpc_health_v1.RegisterHealthServer(grpcServer, healthService)
|
grpc_health_v1.RegisterHealthServer(grpcServer, healthService)
|
||||||
|
|
||||||
// discoverygrpc.AggregatedDiscoveryService_ServiceDesc.ServiceName
|
return mgr.RunnableFunc(func(ctx context.Context) error {
|
||||||
|
go func(ctx context.Context) {
|
||||||
endpointsController := controlplane.EndpointsController{
|
<-ctx.Done()
|
||||||
Client: watcherClient,
|
grpcServer.GracefulStop()
|
||||||
Cache: cache,
|
}(ctx)
|
||||||
}
|
return grpcServer.Serve(lis)
|
||||||
|
}), nil
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -87,23 +87,14 @@ func (m manager) Run(ctx context.Context) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
|
mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
|
||||||
Scheme: scheme,
|
Scheme: scheme,
|
||||||
Metrics: metricsServerOptions,
|
Metrics: metricsServerOptions,
|
||||||
WebhookServer: webhookServer,
|
WebhookServer: webhookServer,
|
||||||
HealthProbeBindAddress: m.ProbeAddr,
|
HealthProbeBindAddress: m.ProbeAddr,
|
||||||
LeaderElection: m.EnableLeaderElection,
|
LeaderElection: m.EnableLeaderElection,
|
||||||
LeaderElectionID: "05f9463f.k8s.icb4dc0.de",
|
BaseContext: func() context.Context { return ctx },
|
||||||
// LeaderElectionReleaseOnCancel defines if the leader should step down voluntarily
|
LeaderElectionID: "05f9463f.k8s.icb4dc0.de",
|
||||||
// when the Manager ends. This requires the binary to immediately end when the
|
LeaderElectionReleaseOnCancel: true,
|
||||||
// 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,
|
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("unable to start manager: %w", err)
|
return fmt.Errorf("unable to start manager: %w", err)
|
||||||
|
|
|
@ -14,7 +14,11 @@ spec:
|
||||||
singular: apigateway
|
singular: apigateway
|
||||||
scope: Namespaced
|
scope: Namespaced
|
||||||
versions:
|
versions:
|
||||||
- name: v1alpha1
|
- additionalPrinterColumns:
|
||||||
|
- jsonPath: .status.envoy.configVersion
|
||||||
|
name: EnvoyConfigVersion
|
||||||
|
type: string
|
||||||
|
name: v1alpha1
|
||||||
schema:
|
schema:
|
||||||
openAPIV3Schema:
|
openAPIV3Schema:
|
||||||
description: APIGateway is the Schema for the apigateways API.
|
description: APIGateway is the Schema for the apigateways API.
|
||||||
|
@ -39,6 +43,11 @@ spec:
|
||||||
spec:
|
spec:
|
||||||
description: APIGatewaySpec defines the desired state of APIGateway.
|
description: APIGatewaySpec defines the desired state of APIGateway.
|
||||||
properties:
|
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:
|
envoy:
|
||||||
description: Envoy - configure the envoy instance and most importantly
|
description: Envoy - configure the envoy instance and most importantly
|
||||||
the control-plane
|
the control-plane
|
||||||
|
@ -61,6 +70,12 @@ spec:
|
||||||
- host
|
- host
|
||||||
- port
|
- port
|
||||||
type: object
|
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:
|
workloadTemplate:
|
||||||
description: WorkloadTemplate - customize the Envoy deployment
|
description: WorkloadTemplate - customize the Envoy deployment
|
||||||
properties:
|
properties:
|
||||||
|
@ -776,12 +791,83 @@ spec:
|
||||||
- key
|
- key
|
||||||
type: object
|
type: object
|
||||||
x-kubernetes-map-type: atomic
|
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:
|
required:
|
||||||
- envoy
|
- envoy
|
||||||
- jwks
|
- jwks
|
||||||
|
- serviceSelector
|
||||||
type: object
|
type: object
|
||||||
status:
|
status:
|
||||||
description: APIGatewayStatus defines the observed state of APIGateway.
|
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
|
||||||
type: object
|
type: object
|
||||||
served: true
|
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_cores.yaml
|
||||||
- bases/supabase.k8s.icb4dc0.de_apigateways.yaml
|
- bases/supabase.k8s.icb4dc0.de_apigateways.yaml
|
||||||
- bases/supabase.k8s.icb4dc0.de_dashboards.yaml
|
- bases/supabase.k8s.icb4dc0.de_dashboards.yaml
|
||||||
|
- bases/supabase.k8s.icb4dc0.de_storages.yaml
|
||||||
# +kubebuilder:scaffold:crdkustomizeresource
|
# +kubebuilder:scaffold:crdkustomizeresource
|
||||||
|
|
||||||
patches:
|
patches:
|
||||||
|
|
|
@ -4,6 +4,23 @@ kind: ClusterRole
|
||||||
metadata:
|
metadata:
|
||||||
name: control-plane-role
|
name: control-plane-role
|
||||||
rules:
|
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:
|
- apiGroups:
|
||||||
- discovery.k8s.io
|
- discovery.k8s.io
|
||||||
resources:
|
resources:
|
||||||
|
|
|
@ -36,3 +36,10 @@ resources:
|
||||||
# if you do not want those helpers be installed with your Project.
|
# if you do not want those helpers be installed with your Project.
|
||||||
- dashboard_editor_role.yaml
|
- dashboard_editor_role.yaml
|
||||||
- dashboard_viewer_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
|
- kind: ServiceAccount
|
||||||
name: controller-manager
|
name: controller-manager
|
||||||
namespace: supabase-system
|
namespace: supabase-system
|
||||||
|
- kind: ServiceAccount
|
||||||
|
name: control-plane
|
||||||
|
namespace: supabase-system
|
||||||
|
|
|
@ -42,6 +42,7 @@ rules:
|
||||||
- apigateways
|
- apigateways
|
||||||
- cores
|
- cores
|
||||||
- dashboards
|
- dashboards
|
||||||
|
- storages
|
||||||
verbs:
|
verbs:
|
||||||
- create
|
- create
|
||||||
- delete
|
- delete
|
||||||
|
@ -56,6 +57,7 @@ rules:
|
||||||
- apigateways/finalizers
|
- apigateways/finalizers
|
||||||
- cores/finalizers
|
- cores/finalizers
|
||||||
- dashboards/finalizers
|
- dashboards/finalizers
|
||||||
|
- storages/finalizers
|
||||||
verbs:
|
verbs:
|
||||||
- update
|
- update
|
||||||
- apiGroups:
|
- apiGroups:
|
||||||
|
@ -64,6 +66,7 @@ rules:
|
||||||
- apigateways/status
|
- apigateways/status
|
||||||
- cores/status
|
- cores/status
|
||||||
- dashboards/status
|
- dashboards/status
|
||||||
|
- storages/status
|
||||||
verbs:
|
verbs:
|
||||||
- get
|
- get
|
||||||
- patch
|
- 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_core.yaml
|
||||||
- supabase_v1alpha1_apigateway.yaml
|
- supabase_v1alpha1_apigateway.yaml
|
||||||
- supabase_v1alpha1_dashboard.yaml
|
- supabase_v1alpha1_dashboard.yaml
|
||||||
|
- supabase_v1alpha1_storage.yaml
|
||||||
# +kubebuilder:scaffold:manifestskustomizesamples
|
# +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");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -12,4 +12,4 @@ distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
See the License for the specific language governing permissions and
|
See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under 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");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with 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");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
|
|
@ -221,8 +221,8 @@ func (r *CoreAuthReconciler) reconcileAuthService(
|
||||||
core.Labels,
|
core.Labels,
|
||||||
)
|
)
|
||||||
|
|
||||||
if _, ok := authService.Labels[meta.SupabaseLabel.EnvoyCluster]; !ok {
|
if _, ok := authService.Labels[meta.SupabaseLabel.ApiGatewayTarget]; !ok {
|
||||||
authService.Labels[meta.SupabaseLabel.EnvoyCluster] = core.Name
|
authService.Labels[meta.SupabaseLabel.ApiGatewayTarget] = core.Name
|
||||||
}
|
}
|
||||||
|
|
||||||
authService.Spec = corev1.ServiceSpec{
|
authService.Spec = corev1.ServiceSpec{
|
||||||
|
|
|
@ -228,8 +228,8 @@ func (r *CorePostgrestReconiler) reconcilePostgrestService(
|
||||||
core.Labels,
|
core.Labels,
|
||||||
)
|
)
|
||||||
|
|
||||||
if _, ok := postgrestService.Labels[meta.SupabaseLabel.EnvoyCluster]; !ok {
|
if _, ok := postgrestService.Labels[meta.SupabaseLabel.ApiGatewayTarget]; !ok {
|
||||||
postgrestService.Labels[meta.SupabaseLabel.EnvoyCluster] = core.Name
|
postgrestService.Labels[meta.SupabaseLabel.ApiGatewayTarget] = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
postgrestService.Spec = corev1.ServiceSpec{
|
postgrestService.Spec = corev1.ServiceSpec{
|
||||||
|
|
|
@ -196,8 +196,8 @@ func (r *DashboardPGMetaReconciler) reconcilePGMetaService(
|
||||||
dashboard.Labels,
|
dashboard.Labels,
|
||||||
)
|
)
|
||||||
|
|
||||||
if _, ok := pgMetaService.Labels[meta.SupabaseLabel.EnvoyCluster]; !ok {
|
if _, ok := pgMetaService.Labels[meta.SupabaseLabel.ApiGatewayTarget]; !ok {
|
||||||
pgMetaService.Labels[meta.SupabaseLabel.EnvoyCluster] = dashboard.Name
|
pgMetaService.Labels[meta.SupabaseLabel.ApiGatewayTarget] = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
pgMetaService.Spec = corev1.ServiceSpec{
|
pgMetaService.Spec = corev1.ServiceSpec{
|
||||||
|
|
|
@ -218,8 +218,8 @@ func (r *DashboardStudioReconciler) reconcileStudioService(
|
||||||
dashboard.Labels,
|
dashboard.Labels,
|
||||||
)
|
)
|
||||||
|
|
||||||
if _, ok := studioService.Labels[meta.SupabaseLabel.EnvoyCluster]; !ok {
|
if _, ok := studioService.Labels[meta.SupabaseLabel.ApiGatewayTarget]; !ok {
|
||||||
studioService.Labels[meta.SupabaseLabel.EnvoyCluster] = dashboard.Name
|
studioService.Labels[meta.SupabaseLabel.ApiGatewayTarget] = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
studioService.Spec = corev1.ServiceSpec{
|
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
|
package controller
|
||||||
|
|
||||||
import (
|
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
|
package controlplane
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"slices"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
clusterv3 "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3"
|
clusterv3 "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3"
|
||||||
|
@ -10,11 +14,28 @@ import (
|
||||||
discoveryv1 "k8s.io/api/discovery/v1"
|
discoveryv1 "k8s.io/api/discovery/v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var _ json.Marshaler = (*ServiceCluster)(nil)
|
||||||
|
|
||||||
type ServiceCluster struct {
|
type ServiceCluster struct {
|
||||||
ServiceEndpoints map[string]Endpoints
|
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 {
|
if c.ServiceEndpoints == nil {
|
||||||
c.ServiceEndpoints = make(map[string]Endpoints)
|
c.ServiceEndpoints = make(map[string]Endpoints)
|
||||||
}
|
}
|
||||||
|
@ -22,6 +43,16 @@ func (c *ServiceCluster) AddOrUpdateEndpoints(eps *discoveryv1.EndpointSlice) {
|
||||||
c.ServiceEndpoints[eps.Name] = newEndpointsFromSlice(eps)
|
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 {
|
func (c ServiceCluster) Cluster(name string, port uint32) *clusterv3.Cluster {
|
||||||
return &clusterv3.Cluster{
|
return &clusterv3.Cluster{
|
||||||
Name: name,
|
Name: name,
|
||||||
|
@ -47,12 +78,13 @@ func (c ServiceCluster) endpoints(port uint32) []*endpointv3.LocalityLbEndpoints
|
||||||
return eps
|
return eps
|
||||||
}
|
}
|
||||||
|
|
||||||
func newEndpointsFromSlice(eps *discoveryv1.EndpointSlice) Endpoints {
|
func newEndpointsFromSlice(eps discoveryv1.EndpointSlice) Endpoints {
|
||||||
var result Endpoints
|
var result Endpoints
|
||||||
|
|
||||||
for _, ep := range eps.Endpoints {
|
for _, ep := range eps.Endpoints {
|
||||||
if ep.Conditions.Ready != nil && *ep.Conditions.Ready {
|
if ep.Conditions.Ready != nil && *ep.Conditions.Ready {
|
||||||
result.Addresses = append(result.Addresses, ep.Addresses...)
|
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 {
|
type Endpoints struct {
|
||||||
Addresses []string
|
Addresses []string
|
||||||
|
Targets []string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e Endpoints) LBEndpoints(port uint32) []*endpointv3.LbEndpoint {
|
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
|
package controlplane
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"slices"
|
"slices"
|
||||||
"strconv"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
corev3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
|
corev3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
|
||||||
listenerv3 "github.com/envoyproxy/go-control-plane/envoy/config/listener/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"
|
"github.com/envoyproxy/go-control-plane/pkg/resource/v3"
|
||||||
"google.golang.org/protobuf/types/known/anypb"
|
"google.golang.org/protobuf/types/known/anypb"
|
||||||
discoveryv1 "k8s.io/api/discovery/v1"
|
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"
|
"sigs.k8s.io/controller-runtime/pkg/log"
|
||||||
|
|
||||||
"code.icb4dc0.de/prskr/supabase-operator/internal/meta"
|
|
||||||
"code.icb4dc0.de/prskr/supabase-operator/internal/supabase"
|
"code.icb4dc0.de/prskr/supabase-operator/internal/supabase"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
type EnvoyServices struct {
|
||||||
ErrUnexpectedObject = errors.New("unexpected object")
|
ServiceLabelKey string `json:"-"`
|
||||||
ErrNoEnvoyClusterLabel = errors.New("no Envoy cluster label set")
|
Postgrest *PostgrestCluster `json:"postgrest,omitempty"`
|
||||||
|
GoTrue *GoTrueCluster `json:"auth,omitempty"`
|
||||||
supabaseServices = []string{
|
PGMeta *PGMetaCluster `json:"pgmeta,omitempty"`
|
||||||
supabase.ServiceConfig.Postgrest.Name,
|
Studio *StudioCluster `json:"studio,omitempty"`
|
||||||
supabase.ServiceConfig.Auth.Name,
|
|
||||||
supabase.ServiceConfig.PGMeta.Name,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
type EndpointsController struct {
|
|
||||||
lock sync.Mutex
|
|
||||||
Client client.WithWatch
|
|
||||||
Cache cache.SnapshotCache
|
|
||||||
envoyClusters map[string]*envoyClusterServices
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *EndpointsController) Run(ctx context.Context) error {
|
func (s *EnvoyServices) UpsertEndpointSlices(endpointSlices ...discoveryv1.EndpointSlice) {
|
||||||
var (
|
for _, eps := range endpointSlices {
|
||||||
logger = ctrl.Log.WithName("endpoints-controller")
|
switch eps.Labels[s.ServiceLabelKey] {
|
||||||
endpointSlices discoveryv1.EndpointSliceList
|
case supabase.ServiceConfig.Postgrest.Name:
|
||||||
)
|
if s.Postgrest == nil {
|
||||||
|
s.Postgrest = new(PostgrestCluster)
|
||||||
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
|
|
||||||
}
|
}
|
||||||
eventLogger := logger.WithValues("event_type", ev.Type)
|
s.Postgrest.AddOrUpdateEndpoints(eps)
|
||||||
switch ev.Type {
|
case supabase.ServiceConfig.Auth.Name:
|
||||||
case watch.Added, watch.Modified:
|
if s.GoTrue == nil {
|
||||||
eps, ok := ev.Object.(*discoveryv1.EndpointSlice)
|
s.GoTrue = new(GoTrueCluster)
|
||||||
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")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
case <-ctx.Done():
|
s.GoTrue.AddOrUpdateEndpoints(eps)
|
||||||
return ctx.Err()
|
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 {
|
func (s EnvoyServices) Targets() map[string][]string {
|
||||||
c.lock.Lock()
|
targets := make(map[string][]string)
|
||||||
defer c.lock.Unlock()
|
|
||||||
|
|
||||||
var (
|
if s.Postgrest != nil {
|
||||||
logger = log.FromContext(ctx)
|
targets[supabase.ServiceConfig.Postgrest.Name] = s.Postgrest.Targets()
|
||||||
instanceKey string
|
|
||||||
svc *envoyClusterServices
|
|
||||||
)
|
|
||||||
|
|
||||||
logger.Info("Observed endpoint slice", "name", epSlice.Name)
|
|
||||||
|
|
||||||
if c.envoyClusters == nil {
|
|
||||||
c.envoyClusters = make(map[string]*envoyClusterServices)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
envoyNodeName, ok := epSlice.Labels[meta.SupabaseLabel.EnvoyCluster]
|
if s.GoTrue != nil {
|
||||||
if !ok {
|
targets[supabase.ServiceConfig.Auth.Name] = s.GoTrue.Targets()
|
||||||
return fmt.Errorf("%w: at object %s", ErrNoEnvoyClusterLabel, epSlice.Name)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
instanceKey = fmt.Sprintf("%s:%s", envoyNodeName, epSlice.Namespace)
|
if s.PGMeta != nil {
|
||||||
|
targets[supabase.ServiceConfig.PGMeta.Name] = s.PGMeta.Targets()
|
||||||
if svc, ok = c.envoyClusters[instanceKey]; !ok {
|
|
||||||
svc = new(envoyClusterServices)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
svc.UpsertEndpoints(epSlice)
|
if s.Studio != nil {
|
||||||
|
targets[supabase.ServiceConfig.Studio.Name] = s.Studio.Targets()
|
||||||
|
}
|
||||||
|
|
||||||
c.envoyClusters[instanceKey] = svc
|
return targets
|
||||||
|
|
||||||
return c.updateSnapshot(ctx, instanceKey)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *EndpointsController) updateSnapshot(ctx context.Context, instance string) error {
|
func (s *EnvoyServices) snapshot(ctx context.Context, instance, version string) (*cache.Snapshot, 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) {
|
|
||||||
const (
|
const (
|
||||||
apiRouteName = "supabase"
|
apiRouteName = "supabase"
|
||||||
studioRouteName = "supabas-studio"
|
studioRouteName = "supabas-studio"
|
||||||
|
@ -207,6 +79,8 @@ func (s *envoyClusterServices) snapshot(instance, version string) (*cache.Snapsh
|
||||||
listenerName = "supabase"
|
listenerName = "supabase"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
logger := log.FromContext(ctx)
|
||||||
|
|
||||||
apiConnectionManager := &hcm.HttpConnectionManager{
|
apiConnectionManager := &hcm.HttpConnectionManager{
|
||||||
CodecType: hcm.HttpConnectionManager_AUTO,
|
CodecType: hcm.HttpConnectionManager_AUTO,
|
||||||
StatPrefix: "http",
|
StatPrefix: "http",
|
||||||
|
@ -327,6 +201,8 @@ func (s *envoyClusterServices) snapshot(instance, version string) (*cache.Snapsh
|
||||||
}}
|
}}
|
||||||
|
|
||||||
if s.Studio != nil {
|
if s.Studio != nil {
|
||||||
|
logger.Info("Adding studio listener")
|
||||||
|
|
||||||
listeners = append(listeners, &listenerv3.Listener{
|
listeners = append(listeners, &listenerv3.Listener{
|
||||||
Name: "studio",
|
Name: "studio",
|
||||||
Address: &corev3.Address{
|
Address: &corev3.Address{
|
|
@ -39,9 +39,9 @@ var WellKnownLabel = struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
var SupabaseLabel = struct {
|
var SupabaseLabel = struct {
|
||||||
Reload string
|
Reload string
|
||||||
EnvoyCluster string
|
ApiGatewayTarget string
|
||||||
}{
|
}{
|
||||||
Reload: supabasev1alpha1.GroupVersion.Group + "/reload",
|
Reload: supabasev1alpha1.GroupVersion.Group + "/reload",
|
||||||
EnvoyCluster: supabasev1alpha1.GroupVersion.Group + "/envoy-cluster",
|
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