Compare commits
19 commits
Author | SHA1 | Date | |
---|---|---|---|
87a06dac66 | |||
366ceece24 | |||
8b2425d16d | |||
101bf971a7 | |||
c4a43b82d3 | |||
1f285f6492 | |||
2d71a4a132 | |||
264b30e8a2 | |||
7f56a3db56 | |||
9d02a2d90b | |||
3c13eb0d6b | |||
e9302c51be | |||
45630f7326 | |||
6c61adb1c7 | |||
8066c34eb5 | |||
867daaa375 | |||
0fccef973f | |||
89b682935b | |||
c0cbd22bb0 |
151 changed files with 6550 additions and 6215 deletions
.github/workflows
.golangci.yml.husky.toml.zed
DockerfileMakefileREADME.mdTiltfileapi/v1alpha1
apigateway_types.gocommon_types.gocore_types.godashboard_types.gostorage_types.gozz_generated.deepcopy.go
assets/migrations
migrations.go
migrations
10000000000000_demote-postgres.sql20221207154255_create_pgsodium_and_vault.sql20221207154255_create_vault.sql20230529180330_alter_api_roles_for_inherit.sql20250205060043_disable_log_statement_on_internal_roles.sql
setup
cmd
config
control-plane
crd/bases
supabase.k8s.icb4dc0.de_apigateways.yamlsupabase.k8s.icb4dc0.de_cores.yamlsupabase.k8s.icb4dc0.de_dashboards.yamlsupabase.k8s.icb4dc0.de_storages.yaml
default
dev
.gitignoreapigateway.yamlca.yamlcnpg-cluster.yamlcore.yamldashboard.yamlkustomization.yaml
kustomizeconfig
minio-operator.yamlminio.yamlnamespace.yamlstorage.yamlstudio-credentials-secret.yamlstudio-plaintext-users.yamlmanager
rbac
samples
dev
docs
go.modgo.suminternal
certs
controller
apigateway_controller.gocore_db_controller.gocore_gotrue_controller.gocore_postgrest_controller.godashboard_pg-meta_controller.godashboard_studio_controller.gostorage_api_controller.gostorage_api_controller_test.gostorage_imgproxy_controller.gostorage_s3_creds_controller.go
templates
controlplane
db
health
oidc
pw
supabase
3
.github/workflows/docs.yml
vendored
3
.github/workflows/docs.yml
vendored
|
@ -35,7 +35,8 @@ jobs:
|
|||
run: mkdocs build
|
||||
|
||||
- name: Copy files to the s3 website content bucket
|
||||
if: ${{ startsWith(github.ref, 'refs/tags/v') }}
|
||||
# for the time being, let's just always deploy the docs
|
||||
# if: ${{ startsWith(github.ref, 'refs/tags/v') }}
|
||||
run: rclone sync site/ HCLOUD:/1661580-supabase-operator-docs/
|
||||
env:
|
||||
RCLONE_CONFIG_HCLOUD_TYPE: s3
|
||||
|
|
2
.github/workflows/lint.yml
vendored
2
.github/workflows/lint.yml
vendored
|
@ -28,4 +28,4 @@ jobs:
|
|||
- name: Run linter
|
||||
uses: golangci/golangci-lint-action@v6
|
||||
with:
|
||||
version: v1.61
|
||||
version: v1.63.4
|
||||
|
|
80
.github/workflows/postgres.yml
vendored
Normal file
80
.github/workflows/postgres.yml
vendored
Normal file
|
@ -0,0 +1,80 @@
|
|||
name: Postgres image
|
||||
on:
|
||||
schedule:
|
||||
# every Thursday 2:30am
|
||||
- cron: "30 2 * * 2"
|
||||
push:
|
||||
paths:
|
||||
- .github/workflows/postgres.yml
|
||||
- postgres/**
|
||||
branches:
|
||||
- main
|
||||
tags:
|
||||
- "v*"
|
||||
|
||||
env:
|
||||
MINOR_VERSIONS: '{"15": "12","17": "4"}'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
strategy:
|
||||
matrix:
|
||||
arch:
|
||||
- arm64
|
||||
- amd64
|
||||
postgres_major:
|
||||
- "15"
|
||||
- "17"
|
||||
runs-on: ubuntu-latest-${{ matrix.arch }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Login to container registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: registry.icb4dc0.de
|
||||
username: ${{ secrets.HARBOR_USER }}
|
||||
password: ${{ secrets.HARBOR_TOKEN }}
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
file: postgres/Dockerfile
|
||||
push: true
|
||||
tags: registry.icb4dc0.de/supabase-operator/postgres:${{ matrix.postgres_major }}.${{ fromJSON(env.MINOR_VERSIONS)[matrix.postgres_major] }}.${{ github.run_number }}-${{ matrix.arch }}
|
||||
build-args: |
|
||||
POSTGRES_MAJOR=${{ matrix.postgres_major }}
|
||||
POSTGRES_MINOR=${{ fromJSON(env.MINOR_VERSIONS)[matrix.postgres_major] }}
|
||||
|
||||
manifest:
|
||||
strategy:
|
||||
matrix:
|
||||
postgres_major:
|
||||
- "15"
|
||||
- "17"
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- build
|
||||
steps:
|
||||
- name: Login to container registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: registry.icb4dc0.de
|
||||
username: ${{ secrets.HARBOR_USER }}
|
||||
password: ${{ secrets.HARBOR_TOKEN }}
|
||||
|
||||
- name: Install skopeo
|
||||
run: |
|
||||
apt-get update
|
||||
apt-get install -y skopeo
|
||||
|
||||
- name: Create manifest
|
||||
run: |
|
||||
docker buildx imagetools create \
|
||||
-t registry.icb4dc0.de/supabase-operator/postgres:${{ matrix.postgres_major }}.${{ fromJSON(env.MINOR_VERSIONS)[matrix.postgres_major] }} \
|
||||
-t registry.icb4dc0.de/supabase-operator/postgres:${{ matrix.postgres_major }}.${{ fromJSON(env.MINOR_VERSIONS)[matrix.postgres_major] }}.${{ github.run_number }} \
|
||||
registry.icb4dc0.de/supabase-operator/postgres:${{ matrix.postgres_major }}.${{ fromJSON(env.MINOR_VERSIONS)[matrix.postgres_major] }}.${{ github.run_number }}-arm64 \
|
||||
registry.icb4dc0.de/supabase-operator/postgres:${{ matrix.postgres_major }}.${{ fromJSON(env.MINOR_VERSIONS)[matrix.postgres_major] }}.${{ github.run_number }}-amd64
|
||||
|
||||
skopeo delete docker://registry.icb4dc0.de/supabase-operator/postgres:${{ matrix.postgres_major }}.${{ fromJSON(env.MINOR_VERSIONS)[matrix.postgres_major] }}.${{ github.run_number }}-arm64
|
||||
skopeo delete docker://registry.icb4dc0.de/supabase-operator/postgres:${{ matrix.postgres_major }}.${{ fromJSON(env.MINOR_VERSIONS)[matrix.postgres_major] }}.${{ github.run_number }}-amd64
|
1
.github/workflows/release.yml
vendored
1
.github/workflows/release.yml
vendored
|
@ -36,7 +36,6 @@ jobs:
|
|||
- name: Init go
|
||||
run: |
|
||||
go mod download
|
||||
go mod download -modfile tools/go.mod
|
||||
|
||||
- name: Snapshot release
|
||||
uses: goreleaser/goreleaser-action@v6
|
||||
|
|
5
.github/workflows/test-e2e.yml
vendored
5
.github/workflows/test-e2e.yml
vendored
|
@ -43,3 +43,8 @@ jobs:
|
|||
run: |
|
||||
go mod tidy
|
||||
make test-e2e
|
||||
|
||||
- name: Cleanup kind cluster
|
||||
if: always()
|
||||
run: |
|
||||
kind delete cluster
|
||||
|
|
110
.golangci.yml
110
.golangci.yml
|
@ -1,36 +1,18 @@
|
|||
version: "2"
|
||||
run:
|
||||
timeout: 5m
|
||||
allow-parallel-runners: true
|
||||
|
||||
issues:
|
||||
# don't skip warning about doc comments
|
||||
# don't exclude the default set of lint
|
||||
exclude-use-default: false
|
||||
# restore some of the defaults
|
||||
# (fill in the rest as needed)
|
||||
exclude-rules:
|
||||
- path: "api/*"
|
||||
linters:
|
||||
- lll
|
||||
- path: "internal/*"
|
||||
linters:
|
||||
- dupl
|
||||
- lll
|
||||
linters:
|
||||
disable-all: true
|
||||
default: none
|
||||
enable:
|
||||
- copyloopvar
|
||||
- dupl
|
||||
- errcheck
|
||||
- copyloopvar
|
||||
- ginkgolinter
|
||||
- goconst
|
||||
- gocyclo
|
||||
- gofmt
|
||||
- goimports
|
||||
- goheader
|
||||
- gosimple
|
||||
# enable when the TODOs are fixed
|
||||
# - godox
|
||||
- goheader
|
||||
- govet
|
||||
- ineffassign
|
||||
- lll
|
||||
|
@ -39,43 +21,65 @@ linters:
|
|||
- prealloc
|
||||
- revive
|
||||
- staticcheck
|
||||
- typecheck
|
||||
- unconvert
|
||||
- unparam
|
||||
- unused
|
||||
|
||||
linters-settings:
|
||||
revive:
|
||||
settings:
|
||||
goheader:
|
||||
values:
|
||||
const:
|
||||
AUTHOR: Peter Kurfer
|
||||
template-path: hack/header.tmpl
|
||||
importas:
|
||||
alias:
|
||||
- pkg: k8s.io/api/(\w+)/(v[\w\d]+)
|
||||
alias: $1$2
|
||||
- pkg: k8s.io/apimachinery/pkg/apis/meta/v1
|
||||
alias: metav1
|
||||
no-unaliased: true
|
||||
no-extra-aliases: true
|
||||
revive:
|
||||
rules:
|
||||
- name: comment-spacings
|
||||
exclusions:
|
||||
generated: lax
|
||||
rules:
|
||||
- name: comment-spacings
|
||||
gci:
|
||||
sections:
|
||||
- standard
|
||||
- default
|
||||
- prefix(code.icb4dc0.de/prskr/supabase-operator)
|
||||
- alias
|
||||
- blank
|
||||
- dot
|
||||
goimports:
|
||||
local-prefixes: code.icb4dc0.de/prskr/supabase-operator
|
||||
goheader:
|
||||
values:
|
||||
const:
|
||||
AUTHOR: Peter Kurfer
|
||||
template-path: hack/header.tmpl
|
||||
|
||||
importas:
|
||||
no-unaliased: true
|
||||
no-extra-aliases: true
|
||||
alias:
|
||||
- pkg: k8s.io/api/(\w+)/(v[\w\d]+)
|
||||
alias: $1$2
|
||||
- pkg: "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
alias: metav1
|
||||
|
||||
- linters:
|
||||
- lll
|
||||
path: api/*
|
||||
- linters:
|
||||
- dupl
|
||||
- lll
|
||||
path: internal/*
|
||||
paths:
|
||||
- third_party$
|
||||
- builtin$
|
||||
- examples$
|
||||
severity:
|
||||
default-severity: error
|
||||
default: error
|
||||
rules:
|
||||
- linters:
|
||||
- godox
|
||||
severity: info
|
||||
formatters:
|
||||
enable:
|
||||
- gofmt
|
||||
- goimports
|
||||
settings:
|
||||
gci:
|
||||
sections:
|
||||
- standard
|
||||
- default
|
||||
- prefix(code.icb4dc0.de/prskr/supabase-operator)
|
||||
- alias
|
||||
- blank
|
||||
- dot
|
||||
goimports:
|
||||
local-prefixes:
|
||||
- code.icb4dc0.de/prskr/supabase-operator
|
||||
exclusions:
|
||||
generated: lax
|
||||
paths:
|
||||
- third_party$
|
||||
- builtin$
|
||||
- examples$
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
# git hook pre commit
|
||||
pre-commit = [
|
||||
"go mod tidy -go=1.23.5",
|
||||
"go mod tidy -go=1.24",
|
||||
"go run mage.go GenerateAll",
|
||||
"husky lint-staged",
|
||||
# "golangci-lint run",
|
||||
|
|
|
@ -4,6 +4,19 @@
|
|||
"initialization_options": {
|
||||
"local": "code.icb4dc0.de/prskr/supabase-operator"
|
||||
}
|
||||
},
|
||||
"golangci-lint": {
|
||||
"initialization_options": {
|
||||
"command": [
|
||||
"go",
|
||||
"tool",
|
||||
"golangci-lint",
|
||||
"run",
|
||||
"--output.json.path",
|
||||
"stderr",
|
||||
"--issues-exit-code=1"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# Build the manager binary
|
||||
FROM golang:1.23.4 AS builder
|
||||
FROM golang:1.24-alpine AS builder
|
||||
ARG TARGETOS
|
||||
ARG TARGETARCH
|
||||
|
||||
|
@ -16,10 +16,7 @@ COPY [ "go.*", "./" ]
|
|||
COPY [ "api", "api" ]
|
||||
COPY [ "assets/migrations", "assets/migrations" ]
|
||||
COPY [ "cmd", "cmd" ]
|
||||
COPY [ "infrastructure", "infrastructure" ]
|
||||
COPY [ "internal", "internal" ]
|
||||
COPY [ "magefiles", "magefiles" ]
|
||||
COPY [ "tools", "tools" ]
|
||||
|
||||
# Build
|
||||
# the GOARCH has not a default value to allow the binary be built according to the host where the command
|
||||
|
|
64
Makefile
64
Makefile
|
@ -44,11 +44,11 @@ help: ## Display this help.
|
|||
##@ Development
|
||||
|
||||
.PHONY: manifests
|
||||
manifests: controller-gen ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects.
|
||||
manifests: ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects.
|
||||
$(CONTROLLER_GEN) rbac:roleName=manager-role crd webhook paths="./..." output:crd:artifacts:config=config/crd/bases
|
||||
|
||||
.PHONY: generate
|
||||
generate: controller-gen ## Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations.
|
||||
generate: ## Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations.
|
||||
$(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" paths="./..."
|
||||
|
||||
.PHONY: fmt
|
||||
|
@ -60,7 +60,7 @@ vet: ## Run go vet against code.
|
|||
go vet ./...
|
||||
|
||||
.PHONY: test
|
||||
test: manifests generate fmt vet envtest ## Run tests.
|
||||
test: manifests generate fmt vet ## Run tests.
|
||||
KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) --bin-dir $(LOCALBIN) -p path)" go test $$(go list ./... | grep -v /e2e) -coverprofile cover.out
|
||||
|
||||
# TODO(user): To use a different vendor for e2e tests, modify the setup under 'tests/e2e'.
|
||||
|
@ -81,11 +81,11 @@ test-e2e: manifests generate fmt vet ## Run the e2e tests. Expected an isolated
|
|||
go test ./test/e2e/ -v -ginkgo.v
|
||||
|
||||
.PHONY: lint
|
||||
lint: golangci-lint ## Run golangci-lint linter
|
||||
lint: ## Run golangci-lint linter
|
||||
$(GOLANGCI_LINT) run
|
||||
|
||||
.PHONY: lint-fix
|
||||
lint-fix: golangci-lint ## Run golangci-lint linter and perform fixes
|
||||
lint-fix: ## Run golangci-lint linter and perform fixes
|
||||
$(GOLANGCI_LINT) run --fix
|
||||
|
||||
##@ Build
|
||||
|
@ -127,7 +127,7 @@ docker-buildx: ## Build and push docker image for the manager for cross-platform
|
|||
rm Dockerfile.cross
|
||||
|
||||
.PHONY: build-installer
|
||||
build-installer: manifests generate kustomize ## Generate a consolidated YAML with CRDs and deployment.
|
||||
build-installer: manifests generate ## Generate a consolidated YAML with CRDs and deployment.
|
||||
mkdir -p dist
|
||||
cd config/manager && $(KUSTOMIZE) edit set image controller=${IMG}
|
||||
$(KUSTOMIZE) build config/default > dist/install.yaml
|
||||
|
@ -148,7 +148,7 @@ uninstall: manifests kustomize ## Uninstall CRDs from the K8s cluster specified
|
|||
|
||||
.PHONY: deploy
|
||||
deploy: manifests kustomize ## Deploy controller to the K8s cluster specified in ~/.kube/config.
|
||||
cd config/manager && $(KUSTOMIZE) edit set image controller=${IMG}
|
||||
cd config/manager && $(KUSTOMIZE) edit set image supabase-operator=${IMG}
|
||||
$(KUSTOMIZE) build config/default | $(KUBECTL) apply -f -
|
||||
|
||||
.PHONY: undeploy
|
||||
|
@ -164,49 +164,7 @@ $(LOCALBIN):
|
|||
|
||||
## Tool Binaries
|
||||
KUBECTL ?= kubectl
|
||||
KUSTOMIZE ?= $(LOCALBIN)/kustomize
|
||||
CONTROLLER_GEN ?= $(LOCALBIN)/controller-gen
|
||||
ENVTEST ?= $(LOCALBIN)/setup-envtest
|
||||
GOLANGCI_LINT = $(LOCALBIN)/golangci-lint
|
||||
|
||||
## Tool Versions
|
||||
KUSTOMIZE_VERSION ?= v5.5.0
|
||||
CONTROLLER_TOOLS_VERSION ?= v0.16.4
|
||||
ENVTEST_VERSION ?= release-0.19
|
||||
GOLANGCI_LINT_VERSION ?= v1.61.0
|
||||
|
||||
.PHONY: kustomize
|
||||
kustomize: $(KUSTOMIZE) ## Download kustomize locally if necessary.
|
||||
$(KUSTOMIZE): $(LOCALBIN)
|
||||
$(call go-install-tool,$(KUSTOMIZE),sigs.k8s.io/kustomize/kustomize/v5,$(KUSTOMIZE_VERSION))
|
||||
|
||||
.PHONY: controller-gen
|
||||
controller-gen: $(CONTROLLER_GEN) ## Download controller-gen locally if necessary.
|
||||
$(CONTROLLER_GEN): $(LOCALBIN)
|
||||
$(call go-install-tool,$(CONTROLLER_GEN),sigs.k8s.io/controller-tools/cmd/controller-gen,$(CONTROLLER_TOOLS_VERSION))
|
||||
|
||||
.PHONY: envtest
|
||||
envtest: $(ENVTEST) ## Download setup-envtest locally if necessary.
|
||||
$(ENVTEST): $(LOCALBIN)
|
||||
$(call go-install-tool,$(ENVTEST),sigs.k8s.io/controller-runtime/tools/setup-envtest,$(ENVTEST_VERSION))
|
||||
|
||||
.PHONY: golangci-lint
|
||||
golangci-lint: $(GOLANGCI_LINT) ## Download golangci-lint locally if necessary.
|
||||
$(GOLANGCI_LINT): $(LOCALBIN)
|
||||
$(call go-install-tool,$(GOLANGCI_LINT),github.com/golangci/golangci-lint/cmd/golangci-lint,$(GOLANGCI_LINT_VERSION))
|
||||
|
||||
# go-install-tool will 'go install' any package with custom target and name of binary, if it doesn't exist
|
||||
# $1 - target path with name of binary
|
||||
# $2 - package url which can be installed
|
||||
# $3 - specific version of package
|
||||
define go-install-tool
|
||||
@[ -f "$(1)-$(3)" ] || { \
|
||||
set -e; \
|
||||
package=$(2)@$(3) ;\
|
||||
echo "Downloading $${package}" ;\
|
||||
rm -f $(1) || true ;\
|
||||
GOBIN=$(LOCALBIN) go install $${package} ;\
|
||||
mv $(1) $(1)-$(3) ;\
|
||||
} ;\
|
||||
ln -sf $(1)-$(3) $(1)
|
||||
endef
|
||||
KUSTOMIZE ?= go tool kustomize
|
||||
CONTROLLER_GEN ?= go tool controller-gen
|
||||
ENVTEST ?= go tool setup-envtest
|
||||
GOLANGCI_LINT = go tool golangci-lint
|
||||
|
|
|
@ -37,7 +37,7 @@ This operator tries to be as un-opionionated as possible and thereofore does not
|
|||
## Getting Started
|
||||
|
||||
### Prerequisites
|
||||
- go version v1.23.x+
|
||||
- go version v1.24.x+
|
||||
- docker version 27.+.
|
||||
- kubectl version v1.30.0+.
|
||||
- Access to a Kubernetes v1.30.+ cluster.
|
||||
|
|
14
Tiltfile
14
Tiltfile
|
@ -4,7 +4,6 @@ load('ext://restart_process', 'docker_build_with_restart')
|
|||
allow_k8s_contexts('kind-kind')
|
||||
|
||||
k8s_yaml(kustomize('config/dev'))
|
||||
k8s_yaml(kustomize('config/samples'))
|
||||
|
||||
compile_cmd = 'CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o out/supabase-operator ./cmd/'
|
||||
|
||||
|
@ -23,6 +22,8 @@ local_resource(
|
|||
resource_deps=[]
|
||||
)
|
||||
|
||||
k8s_kind('Cluster', api_version='postgresql.cnpg.io/v1')
|
||||
|
||||
docker_build_with_restart(
|
||||
'supabase-operator',
|
||||
'.',
|
||||
|
@ -40,10 +41,11 @@ k8s_resource('supabase-controller-manager')
|
|||
k8s_resource(
|
||||
workload='supabase-control-plane',
|
||||
port_forwards=18000,
|
||||
resource_deps=[]
|
||||
)
|
||||
|
||||
k8s_resource(
|
||||
objects=["cluster-example:Cluster:supabase-demo"],
|
||||
workload='cluster-example',
|
||||
new_name='Postgres cluster',
|
||||
port_forwards=5432
|
||||
)
|
||||
|
@ -62,7 +64,12 @@ k8s_resource(
|
|||
k8s_resource(
|
||||
objects=["gateway-sample:APIGateway:supabase-demo"],
|
||||
extra_pod_selectors={"app.kubernetes.io/component": "api-gateway"},
|
||||
port_forwards=[8000, 19000],
|
||||
port_forwards=[3000, 8000, 19000],
|
||||
links=[
|
||||
link("https://localhost:3000", "Studio"),
|
||||
link("http://localhost:8000", "API"),
|
||||
link("http://localhost:19000", "Envoy Admin Interface")
|
||||
],
|
||||
new_name='API Gateway',
|
||||
resource_deps=[
|
||||
'supabase-controller-manager'
|
||||
|
@ -73,7 +80,6 @@ k8s_resource(
|
|||
objects=["dashboard-sample:Dashboard:supabase-demo"],
|
||||
extra_pod_selectors={"app.kubernetes.io/component": "dashboard", "app.kubernetes.io/name": "studio"},
|
||||
discovery_strategy="selectors-only",
|
||||
port_forwards=[3000],
|
||||
new_name='Dashboard',
|
||||
resource_deps=[
|
||||
'supabase-controller-manager'
|
||||
|
|
|
@ -19,6 +19,7 @@ package v1alpha1
|
|||
import (
|
||||
"iter"
|
||||
"maps"
|
||||
"strings"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
@ -37,6 +38,40 @@ type ControlPlaneSpec struct {
|
|||
Port uint16 `json:"port"`
|
||||
}
|
||||
|
||||
type EnvoyLogLevel string
|
||||
|
||||
type EnvoyComponentLogLevel struct {
|
||||
// Component - the component to set the log level for
|
||||
// the component IDs can be found [here](https://github.com/envoyproxy/envoy/blob/main/source/common/common/logger.h#L36)
|
||||
Component string `json:"component"`
|
||||
// Level - the log level to set for the component
|
||||
// +kubebuilder:validation:Enum=trace;debug;info;warning;error;critical;off
|
||||
Level EnvoyLogLevel `json:"level"`
|
||||
}
|
||||
|
||||
type EnvoyDebuggingOptions struct {
|
||||
ComponentLogLevels []EnvoyComponentLogLevel `json:"componentLogLevels,omitempty"`
|
||||
}
|
||||
|
||||
func (o *EnvoyDebuggingOptions) DebugLogging() string {
|
||||
if o == nil || len(o.ComponentLogLevels) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
var builder strings.Builder
|
||||
for i, lvl := range o.ComponentLogLevels {
|
||||
if i > 0 {
|
||||
builder.WriteString(",")
|
||||
}
|
||||
|
||||
builder.WriteString(lvl.Component)
|
||||
builder.WriteRune(':')
|
||||
builder.WriteString(string(lvl.Level))
|
||||
}
|
||||
|
||||
return builder.String()
|
||||
}
|
||||
|
||||
type EnvoySpec struct {
|
||||
// NodeName - identifies the Envoy cluster within the current namespace
|
||||
// if not set, the name of the APIGateway resource will be used
|
||||
|
@ -45,15 +80,148 @@ type EnvoySpec struct {
|
|||
// ControlPlane - configure the control plane where Envoy will retrieve its configuration from
|
||||
ControlPlane *ControlPlaneSpec `json:"controlPlane"`
|
||||
// WorkloadTemplate - customize the Envoy deployment
|
||||
WorkloadTemplate *WorkloadTemplate `json:"workloadTemplate,omitempty"`
|
||||
WorkloadSpec *WorkloadSpec `json:"workloadSpec,omitempty"`
|
||||
// DisableIPv6 - disable IPv6 for the Envoy instance
|
||||
// this will force Envoy to use IPv4 for upstream hosts (mostly for the OAuth2 token endpoint)
|
||||
DisableIPv6 bool `json:"disableIPv6,omitempty"`
|
||||
Debugging *EnvoyDebuggingOptions `json:"debugging,omitempty"`
|
||||
}
|
||||
|
||||
type TlsCertRef struct {
|
||||
SecretName string `json:"secretName"`
|
||||
// ServerCertKey - key in the secret that contains the server certificate
|
||||
// +kubebuilder:default="tls.crt"
|
||||
ServerCertKey string `json:"serverCertKey"`
|
||||
// ServerKeyKey - key in the secret that contains the server private key
|
||||
// +kubebuilder:default="tls.key"
|
||||
ServerKeyKey string `json:"serverKeyKey"`
|
||||
// CaCertKey - key in the secret that contains the CA certificate
|
||||
// +kubebuilder:default="ca.crt"
|
||||
CaCertKey string `json:"caCertKey,omitempty"`
|
||||
}
|
||||
|
||||
type EndpointTlsSpec struct {
|
||||
Cert *TlsCertRef `json:"cert"`
|
||||
}
|
||||
|
||||
type ApiEndpointSpec struct {
|
||||
// JWKSSelector - selector where the JWKS can be retrieved from to enable the API gateway to validate JWTs
|
||||
JWKSSelector *corev1.SecretKeySelector `json:"jwks"`
|
||||
// TLS - enable and configure TLS for the API endpoint
|
||||
TLS *EndpointTlsSpec `json:"tls,omitempty"`
|
||||
}
|
||||
|
||||
func (s *ApiEndpointSpec) TLSSpec() *EndpointTlsSpec {
|
||||
if s == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return s.TLS
|
||||
}
|
||||
|
||||
type DashboardAuthType string
|
||||
|
||||
const (
|
||||
DashboardAuthTypeNone DashboardAuthType = "none"
|
||||
DashboardAuthTypeOAuth2 DashboardAuthType = "oauth2"
|
||||
DashboardAuthTypeBasic DashboardAuthType = "basic"
|
||||
)
|
||||
|
||||
type DashboardOAuth2Spec struct {
|
||||
// OpenIDIssuer - if set the defaulter will fetch the discovery document and fill
|
||||
// TokenEndpoint and AuthorizationEndpoint based on the discovery document
|
||||
OpenIDIssuer string `json:"openIdIssuer,omitempty"`
|
||||
// TokenEndpoint - endpoint where Envoy will retrieve the OAuth2 access and identity token from
|
||||
TokenEndpoint string `json:"tokenEndpoint,omitempty"`
|
||||
// AuthorizationEndpoint - endpoint where the user will be redirected to authenticate
|
||||
AuthorizationEndpoint string `json:"authorizationEndpoint,omitempty"`
|
||||
// ClientID - client ID to authenticate with the OAuth2 provider
|
||||
ClientID string `json:"clientId"`
|
||||
// Scopes - scopes to request from the OAuth2 provider (e.g. "openid", "profile", ...) - optional
|
||||
Scopes []string `json:"scopes,omitempty"`
|
||||
// Resources - resources to request from the OAuth2 provider (e.g. "user", "email", ...) - optional
|
||||
Resources []string `json:"resources,omitempty"`
|
||||
// ClientSecretRef - reference to the secret that contains the client secret
|
||||
ClientSecretRef *corev1.SecretKeySelector `json:"clientSecretRef"`
|
||||
}
|
||||
|
||||
type DashboardBasicAuthSpec struct {
|
||||
// UsersInline - [htpasswd format](https://httpd.apache.org/docs/2.4/programs/htpasswd.html)
|
||||
// +kubebuilder:validation:items:Pattern="^[\\w_.]+:\\{SHA\\}[A-z0-9]+=*$"
|
||||
UsersInline []string `json:"usersInline,omitempty"`
|
||||
// PlaintextUsersSecretRef - name of a secret that contains plaintext credentials in key-value form
|
||||
// if not empty, credentials will be merged with inline users
|
||||
PlaintextUsersSecretRef string `json:"plaintextUsersSecretRef,omitempty"`
|
||||
}
|
||||
|
||||
type DashboardAuthSpec struct {
|
||||
// OAuth2 - configure oauth2 authentication for the dashhboard listener
|
||||
// if configured, will be preferred over Basic authentication configuration
|
||||
// effectively disabling basic auth
|
||||
OAuth2 *DashboardOAuth2Spec `json:"oauth2,omitempty"`
|
||||
// Basic - HTTP basic auth configuration, this should only be used in exceptions
|
||||
// e.g. during evaluations or for local development
|
||||
// only used if no other authentication is configured
|
||||
Basic *DashboardBasicAuthSpec `json:"basic,omitempty"`
|
||||
}
|
||||
|
||||
type DashboardEndpointSpec struct {
|
||||
// Auth - configure authentication for the dashboard endpoint
|
||||
Auth *DashboardAuthSpec `json:"auth,omitempty"`
|
||||
// TLS - enable and configure TLS for the Dashboard endpoint
|
||||
TLS *EndpointTlsSpec `json:"tls,omitempty"`
|
||||
}
|
||||
|
||||
func (s *DashboardEndpointSpec) TLSSpec() *EndpointTlsSpec {
|
||||
if s == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return s.TLS
|
||||
}
|
||||
|
||||
func (s *DashboardEndpointSpec) AuthType() DashboardAuthType {
|
||||
if s == nil || s.Auth == nil {
|
||||
return DashboardAuthTypeNone
|
||||
}
|
||||
|
||||
if s.Auth.OAuth2 != nil {
|
||||
return DashboardAuthTypeOAuth2
|
||||
}
|
||||
|
||||
if s.Auth.Basic != nil {
|
||||
return DashboardAuthTypeBasic
|
||||
}
|
||||
|
||||
return DashboardAuthTypeNone
|
||||
}
|
||||
|
||||
func (s *DashboardEndpointSpec) OAuth2() *DashboardOAuth2Spec {
|
||||
if s == nil || s.Auth == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return s.Auth.OAuth2
|
||||
}
|
||||
|
||||
func (s *DashboardEndpointSpec) Basic() *DashboardBasicAuthSpec {
|
||||
if s == nil || s.Auth == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return s.Auth.Basic
|
||||
}
|
||||
|
||||
// APIGatewaySpec defines the desired state of APIGateway.
|
||||
type APIGatewaySpec struct {
|
||||
// Envoy - configure the envoy instance and most importantly the control-plane
|
||||
Envoy *EnvoySpec `json:"envoy"`
|
||||
// JWKSSelector - selector where the JWKS can be retrieved from to enable the API gateway to validate JWTs
|
||||
JWKSSelector *corev1.SecretKeySelector `json:"jwks"`
|
||||
// ApiEndpoint - Configure the endpoint for all API routes
|
||||
// this includes the JWT configuration
|
||||
ApiEndpoint *ApiEndpointSpec `json:"apiEndpoint,omitempty"`
|
||||
// DashboardEndpoint - Configure the endpoint for the Supabase dashboard (studio)
|
||||
// this includes optional authentication (basic or Oauth2) for the dashboard
|
||||
DashboardEndpoint *DashboardEndpointSpec `json:"dashboardEndpoint,omitempty"`
|
||||
// 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"`
|
||||
|
@ -63,8 +231,7 @@ type APIGatewaySpec struct {
|
|||
}
|
||||
|
||||
type EnvoyStatus struct {
|
||||
ConfigVersion string `json:"configVersion,omitempty"`
|
||||
ResourceHash []byte `json:"resourceHash,omitempty"`
|
||||
ResourceHash []byte `json:"resourceHash,omitempty"`
|
||||
}
|
||||
|
||||
// APIGatewayStatus defines the observed state of APIGateway.
|
||||
|
@ -77,7 +244,7 @@ type APIGatewayStatus struct {
|
|||
// +kubebuilder:subresource:status
|
||||
|
||||
// APIGateway is the Schema for the apigateways API.
|
||||
// +kubebuilder:printcolumn:name="EnvoyConfigVersion",type=string,JSONPath=`.status.envoy.configVersion`
|
||||
// +kubebuilder:printcolumn:name="EnvoyConfigVersion",type=string,JSONPath=`.status.envoy.resourceHash`
|
||||
type APIGateway struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||
|
@ -88,7 +255,7 @@ type APIGateway struct {
|
|||
|
||||
func (g APIGateway) JwksSecretMeta() metav1.ObjectMeta {
|
||||
return metav1.ObjectMeta{
|
||||
Name: g.Spec.JWKSSelector.Name,
|
||||
Name: g.Spec.ApiEndpoint.JWKSSelector.Name,
|
||||
Namespace: g.Namespace,
|
||||
Labels: maps.Clone(g.Labels),
|
||||
}
|
||||
|
|
|
@ -91,16 +91,16 @@ type ContainerTemplate struct {
|
|||
AdditionalEnv []corev1.EnvVar `json:"additionalEnv,omitempty"`
|
||||
}
|
||||
|
||||
type WorkloadTemplate struct {
|
||||
type WorkloadSpec struct {
|
||||
Replicas *int32 `json:"replicas,omitempty"`
|
||||
SecurityContext *corev1.PodSecurityContext `json:"securityContext,omitempty"`
|
||||
AdditionalLabels map[string]string `json:"additionalLabels,omitempty"`
|
||||
// Workload - customize the container template of the workload
|
||||
Workload *ContainerTemplate `json:"workload,omitempty"`
|
||||
// ContainerSpec - customize the container template of the workload
|
||||
ContainerSpec *ContainerTemplate `json:"container,omitempty"`
|
||||
AdditionalVolumes []corev1.Volume `json:"additionalVolumes,omitempty"`
|
||||
}
|
||||
|
||||
func (t *WorkloadTemplate) ReplicaCount() *int32 {
|
||||
func (t *WorkloadSpec) ReplicaCount() *int32 {
|
||||
if t != nil && t.Replicas != nil {
|
||||
return t.Replicas
|
||||
}
|
||||
|
@ -108,20 +108,20 @@ func (t *WorkloadTemplate) ReplicaCount() *int32 {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (t *WorkloadTemplate) MergeEnv(basicEnv []corev1.EnvVar) []corev1.EnvVar {
|
||||
if t == nil || t.Workload == nil || len(t.Workload.AdditionalEnv) == 0 {
|
||||
func (t *WorkloadSpec) MergeEnv(basicEnv []corev1.EnvVar) []corev1.EnvVar {
|
||||
if t == nil || t.ContainerSpec == nil || len(t.ContainerSpec.AdditionalEnv) == 0 {
|
||||
return basicEnv
|
||||
}
|
||||
|
||||
existingKeys := make(map[string]bool, len(basicEnv)+len(t.Workload.AdditionalEnv))
|
||||
existingKeys := make(map[string]bool, len(basicEnv)+len(t.ContainerSpec.AdditionalEnv))
|
||||
|
||||
merged := append(make([]corev1.EnvVar, 0, len(basicEnv)+len(t.Workload.AdditionalEnv)), basicEnv...)
|
||||
merged := append(make([]corev1.EnvVar, 0, len(basicEnv)+len(t.ContainerSpec.AdditionalEnv)), basicEnv...)
|
||||
|
||||
for _, v := range basicEnv {
|
||||
existingKeys[v.Name] = true
|
||||
}
|
||||
|
||||
for _, v := range t.Workload.AdditionalEnv {
|
||||
for _, v := range t.ContainerSpec.AdditionalEnv {
|
||||
if _, alreadyPresent := existingKeys[v.Name]; alreadyPresent {
|
||||
continue
|
||||
}
|
||||
|
@ -132,7 +132,7 @@ func (t *WorkloadTemplate) MergeEnv(basicEnv []corev1.EnvVar) []corev1.EnvVar {
|
|||
return merged
|
||||
}
|
||||
|
||||
func (t *WorkloadTemplate) MergeLabels(initial map[string]string, toAppend ...map[string]string) map[string]string {
|
||||
func (t *WorkloadSpec) MergeLabels(initial map[string]string, toAppend ...map[string]string) map[string]string {
|
||||
result := make(map[string]string)
|
||||
|
||||
maps.Copy(result, initial)
|
||||
|
@ -156,47 +156,47 @@ func (t *WorkloadTemplate) MergeLabels(initial map[string]string, toAppend ...ma
|
|||
return result
|
||||
}
|
||||
|
||||
func (t *WorkloadTemplate) Image(defaultImage string) string {
|
||||
if t != nil && t.Workload != nil && t.Workload.Image != "" {
|
||||
return t.Workload.Image
|
||||
func (t *WorkloadSpec) Image(defaultImage string) string {
|
||||
if t != nil && t.ContainerSpec != nil && t.ContainerSpec.Image != "" {
|
||||
return t.ContainerSpec.Image
|
||||
}
|
||||
|
||||
return defaultImage
|
||||
}
|
||||
|
||||
func (t *WorkloadTemplate) ImagePullPolicy() corev1.PullPolicy {
|
||||
if t != nil && t.Workload != nil && t.Workload.PullPolicy != "" {
|
||||
return t.Workload.PullPolicy
|
||||
func (t *WorkloadSpec) ImagePullPolicy() corev1.PullPolicy {
|
||||
if t != nil && t.ContainerSpec != nil && t.ContainerSpec.PullPolicy != "" {
|
||||
return t.ContainerSpec.PullPolicy
|
||||
}
|
||||
|
||||
return corev1.PullIfNotPresent
|
||||
}
|
||||
|
||||
func (t *WorkloadTemplate) PullSecrets() []corev1.LocalObjectReference {
|
||||
if t != nil && t.Workload != nil && len(t.Workload.ImagePullSecrets) > 0 {
|
||||
return t.Workload.ImagePullSecrets
|
||||
func (t *WorkloadSpec) PullSecrets() []corev1.LocalObjectReference {
|
||||
if t != nil && t.ContainerSpec != nil && len(t.ContainerSpec.ImagePullSecrets) > 0 {
|
||||
return t.ContainerSpec.ImagePullSecrets
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *WorkloadTemplate) Resources() corev1.ResourceRequirements {
|
||||
if t != nil && t.Workload != nil {
|
||||
return t.Workload.Resources
|
||||
func (t *WorkloadSpec) Resources() corev1.ResourceRequirements {
|
||||
if t != nil && t.ContainerSpec != nil {
|
||||
return t.ContainerSpec.Resources
|
||||
}
|
||||
|
||||
return corev1.ResourceRequirements{}
|
||||
}
|
||||
|
||||
func (t *WorkloadTemplate) AdditionalVolumeMounts(defaultMounts ...corev1.VolumeMount) []corev1.VolumeMount {
|
||||
if t != nil && t.Workload != nil {
|
||||
return append(defaultMounts, t.Workload.VolumeMounts...)
|
||||
func (t *WorkloadSpec) AdditionalVolumeMounts(defaultMounts ...corev1.VolumeMount) []corev1.VolumeMount {
|
||||
if t != nil && t.ContainerSpec != nil {
|
||||
return append(defaultMounts, t.ContainerSpec.VolumeMounts...)
|
||||
}
|
||||
|
||||
return defaultMounts
|
||||
}
|
||||
|
||||
func (t *WorkloadTemplate) Volumes(defaultVolumes ...corev1.Volume) []corev1.Volume {
|
||||
func (t *WorkloadSpec) Volumes(defaultVolumes ...corev1.Volume) []corev1.Volume {
|
||||
if t == nil {
|
||||
return defaultVolumes
|
||||
}
|
||||
|
@ -204,7 +204,7 @@ func (t *WorkloadTemplate) Volumes(defaultVolumes ...corev1.Volume) []corev1.Vol
|
|||
return append(defaultVolumes, t.AdditionalVolumes...)
|
||||
}
|
||||
|
||||
func (t *WorkloadTemplate) PodSecurityContext() *corev1.PodSecurityContext {
|
||||
func (t *WorkloadSpec) PodSecurityContext() *corev1.PodSecurityContext {
|
||||
if t != nil && t.SecurityContext != nil {
|
||||
return t.SecurityContext
|
||||
}
|
||||
|
@ -214,9 +214,9 @@ func (t *WorkloadTemplate) PodSecurityContext() *corev1.PodSecurityContext {
|
|||
}
|
||||
}
|
||||
|
||||
func (t *WorkloadTemplate) ContainerSecurityContext(uid, gid int64) *corev1.SecurityContext {
|
||||
if t != nil && t.Workload != nil && t.Workload.SecurityContext != nil {
|
||||
return t.Workload.SecurityContext
|
||||
func (t *WorkloadSpec) ContainerSecurityContext(uid, gid int64) *corev1.SecurityContext {
|
||||
if t != nil && t.ContainerSpec != nil && t.ContainerSpec.SecurityContext != nil {
|
||||
return t.ContainerSpec.SecurityContext
|
||||
}
|
||||
|
||||
return &corev1.SecurityContext{
|
||||
|
|
|
@ -17,6 +17,7 @@ limitations under the License.
|
|||
package v1alpha1
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
@ -25,6 +26,7 @@ import (
|
|||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
@ -59,7 +61,7 @@ type DatabaseRoles struct {
|
|||
|
||||
type Database struct {
|
||||
DSN *string `json:"dsn,omitempty"`
|
||||
DSNSecretRef *corev1.SecretKeySelector `json:"dsnSecretRef,omitempty"`
|
||||
DSNSecretRef *corev1.SecretKeySelector `json:"dsnSecretRef"`
|
||||
Roles DatabaseRoles `json:"roles,omitempty"`
|
||||
}
|
||||
|
||||
|
@ -167,8 +169,8 @@ type PostgrestSpec struct {
|
|||
// MaxRows - maximum number of rows PostgREST will load at a time
|
||||
// +kubebuilder:default=1000
|
||||
MaxRows int `json:"maxRows,omitempty"`
|
||||
// WorkloadTemplate - customize the PostgREST workload
|
||||
WorkloadTemplate *WorkloadTemplate `json:"workloadTemplate,omitempty"`
|
||||
// WorkloadSpec - customize the PostgREST workload
|
||||
WorkloadSpec *WorkloadSpec `json:"workloadSpec,omitempty"`
|
||||
}
|
||||
|
||||
type AuthProviderMeta struct {
|
||||
|
@ -365,12 +367,12 @@ func (p *AuthProviders) Vars(apiExternalURL string) []corev1.EnvVar {
|
|||
}
|
||||
|
||||
type AuthSpec struct {
|
||||
AdditionalRedirectUrls []string `json:"additionalRedirectUrls,omitempty"`
|
||||
DisableSignup *bool `json:"disableSignup,omitempty"`
|
||||
AnonymousUsersEnabled *bool `json:"anonymousUsersEnabled,omitempty"`
|
||||
Providers *AuthProviders `json:"providers,omitempty"`
|
||||
WorkloadTemplate *WorkloadTemplate `json:"workloadTemplate,omitempty"`
|
||||
EmailSignupDisabled *bool `json:"emailSignupDisabled,omitempty"`
|
||||
AdditionalRedirectUrls []string `json:"additionalRedirectUrls,omitempty"`
|
||||
DisableSignup *bool `json:"disableSignup,omitempty"`
|
||||
AnonymousUsersEnabled *bool `json:"anonymousUsersEnabled,omitempty"`
|
||||
Providers *AuthProviders `json:"providers,omitempty"`
|
||||
WorkloadTemplate *WorkloadSpec `json:"workloadTemplate,omitempty"`
|
||||
EmailSignupDisabled *bool `json:"emailSignupDisabled,omitempty"`
|
||||
}
|
||||
|
||||
// CoreSpec defines the desired state of Core.
|
||||
|
@ -387,20 +389,89 @@ type CoreSpec struct {
|
|||
Auth *AuthSpec `json:"auth,omitempty"`
|
||||
}
|
||||
|
||||
type MigrationStatus map[string]metav1.Time
|
||||
type MigrationConditionStatus string
|
||||
|
||||
func (s MigrationStatus) IsApplied(name string) bool {
|
||||
_, ok := s[name]
|
||||
return ok
|
||||
}
|
||||
const (
|
||||
MigrationConditionStatusApplied MigrationConditionStatus = "Applied"
|
||||
MigrationConditionStatusFailed MigrationConditionStatus = "Failed"
|
||||
)
|
||||
|
||||
func (s MigrationStatus) Record(name string) {
|
||||
s[name] = metav1.Now()
|
||||
type MigrationScriptCondition struct {
|
||||
// Name - file name of the migration script
|
||||
Name string `json:"name"`
|
||||
// Hash - SHA256 hash of the script when it was last successfully applied
|
||||
Hash []byte `json:"hash"`
|
||||
// Status - whether the migration was applied or not
|
||||
// +kubebuilder:validation:Enum=Applied;Failed
|
||||
Status MigrationConditionStatus `json:"status"`
|
||||
// LastProbeTime - last time the operator tried to execute the migration script
|
||||
LastProbeTime metav1.Time `json:"lastProbeTime,omitempty"`
|
||||
// LastTransitionTime - last time the condition transitioned from one status to another
|
||||
LastTransitionTime metav1.Time `json:"lastTransitionTime,omitempty"`
|
||||
// Reason - one-word, CamcelCase reason for the condition's last transition
|
||||
Reason string `json:"reason,omitempty"`
|
||||
// Message - human-readable message indicating details about the last transition
|
||||
Message string `json:"message,omitempty"`
|
||||
}
|
||||
|
||||
type DatabaseStatus struct {
|
||||
AppliedMigrations MigrationStatus `json:"appliedMigrations,omitempty"`
|
||||
Roles map[string][]byte `json:"roles,omitempty"`
|
||||
MigrationConditions []MigrationScriptCondition `json:"migrationConditions,omitempty" patchStrategy:"merge" patchMergeKey:"name"`
|
||||
Roles map[string][]byte `json:"roles,omitempty"`
|
||||
}
|
||||
|
||||
func (s DatabaseStatus) IsMigrationUpToDate(name string, hash []byte) (found bool, upToDate bool) {
|
||||
for _, cond := range s.MigrationConditions {
|
||||
if cond.Name == name {
|
||||
return true, bytes.Equal(cond.Hash, hash)
|
||||
}
|
||||
}
|
||||
|
||||
return false, false
|
||||
}
|
||||
|
||||
func (s *DatabaseStatus) RecordMigrationCondition(name string, hash []byte, err error) error {
|
||||
var (
|
||||
now = time.Now()
|
||||
newStatus = MigrationConditionStatusApplied
|
||||
lastProbeTime = metav1.NewTime(now)
|
||||
lastTransitionTime = metav1.NewTime(now)
|
||||
message string
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
newStatus = MigrationConditionStatusFailed
|
||||
message = err.Error()
|
||||
}
|
||||
|
||||
for idx, cond := range s.MigrationConditions {
|
||||
if cond.Name == name {
|
||||
lastTransitionTime = cond.LastTransitionTime
|
||||
if cond.Status != newStatus {
|
||||
lastTransitionTime = metav1.NewTime(now)
|
||||
}
|
||||
|
||||
cond.Hash = hash
|
||||
cond.Status = newStatus
|
||||
cond.LastProbeTime = lastProbeTime
|
||||
cond.LastTransitionTime = lastTransitionTime
|
||||
cond.Reason = "Outdated"
|
||||
cond.Message = message
|
||||
|
||||
s.MigrationConditions[idx] = cond
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
s.MigrationConditions = append(s.MigrationConditions, MigrationScriptCondition{
|
||||
Name: name,
|
||||
Hash: hash,
|
||||
Status: newStatus,
|
||||
LastProbeTime: lastProbeTime,
|
||||
LastTransitionTime: lastTransitionTime,
|
||||
Message: message,
|
||||
})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
type CoreConditionType string
|
||||
|
|
|
@ -24,7 +24,7 @@ import (
|
|||
type StudioSpec struct {
|
||||
JWT *JwtSpec `json:"jwt,omitempty"`
|
||||
// WorkloadTemplate - customize the studio deployment
|
||||
WorkloadTemplate *WorkloadTemplate `json:"workloadTemplate,omitempty"`
|
||||
WorkloadSpec *WorkloadSpec `json:"workloadSpec,omitempty"`
|
||||
// GatewayServiceSelector - selector to find the service for the API gateway
|
||||
// Required to configure the API URL in the studio deployment
|
||||
// If you don't run multiple APIGateway instances in the same namespaces, the default will be fine
|
||||
|
@ -37,7 +37,7 @@ type StudioSpec struct {
|
|||
|
||||
type PGMetaSpec struct {
|
||||
// WorkloadTemplate - customize the pg-meta deployment
|
||||
WorkloadTemplate *WorkloadTemplate `json:"workloadTemplate,omitempty"`
|
||||
WorkloadSpec *WorkloadSpec `json:"workloadSpec,omitempty"`
|
||||
}
|
||||
|
||||
type DbCredentialsReference struct {
|
||||
|
|
|
@ -191,7 +191,7 @@ type StorageApiSpec struct {
|
|||
// UploadTemp - configure the emptyDir for storing intermediate files during uploads
|
||||
UploadTemp *UploadTempSpec `json:"uploadTemp,omitempty"`
|
||||
// WorkloadTemplate - customize the Storage API workload
|
||||
WorkloadTemplate *WorkloadTemplate `json:"workloadTemplate,omitempty"`
|
||||
WorkloadSpec *WorkloadSpec `json:"workloadSpec,omitempty"`
|
||||
}
|
||||
|
||||
type ImageProxySpec struct {
|
||||
|
@ -199,7 +199,7 @@ type ImageProxySpec struct {
|
|||
Enable bool `json:"enable,omitempty"`
|
||||
EnabledWebPDetection bool `json:"enableWebPDetection,omitempty"`
|
||||
// WorkloadTemplate - customize the image proxy workload
|
||||
WorkloadTemplate *WorkloadTemplate `json:"workloadTemplate,omitempty"`
|
||||
WorkloadSpec *WorkloadSpec `json:"workloadSpec,omitempty"`
|
||||
}
|
||||
|
||||
// StorageSpec defines the desired state of Storage.
|
||||
|
|
|
@ -93,9 +93,14 @@ func (in *APIGatewaySpec) DeepCopyInto(out *APIGatewaySpec) {
|
|||
*out = new(EnvoySpec)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.JWKSSelector != nil {
|
||||
in, out := &in.JWKSSelector, &out.JWKSSelector
|
||||
*out = new(v1.SecretKeySelector)
|
||||
if in.ApiEndpoint != nil {
|
||||
in, out := &in.ApiEndpoint, &out.ApiEndpoint
|
||||
*out = new(ApiEndpointSpec)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.DashboardEndpoint != nil {
|
||||
in, out := &in.DashboardEndpoint, &out.DashboardEndpoint
|
||||
*out = new(DashboardEndpointSpec)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.ServiceSelector != nil {
|
||||
|
@ -147,6 +152,31 @@ func (in *APIGatewayStatus) DeepCopy() *APIGatewayStatus {
|
|||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ApiEndpointSpec) DeepCopyInto(out *ApiEndpointSpec) {
|
||||
*out = *in
|
||||
if in.JWKSSelector != nil {
|
||||
in, out := &in.JWKSSelector, &out.JWKSSelector
|
||||
*out = new(v1.SecretKeySelector)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.TLS != nil {
|
||||
in, out := &in.TLS, &out.TLS
|
||||
*out = new(EndpointTlsSpec)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ApiEndpointSpec.
|
||||
func (in *ApiEndpointSpec) DeepCopy() *ApiEndpointSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ApiEndpointSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *AuthProviderMeta) DeepCopyInto(out *AuthProviderMeta) {
|
||||
*out = *in
|
||||
|
@ -222,7 +252,7 @@ func (in *AuthSpec) DeepCopyInto(out *AuthSpec) {
|
|||
}
|
||||
if in.WorkloadTemplate != nil {
|
||||
in, out := &in.WorkloadTemplate, &out.WorkloadTemplate
|
||||
*out = new(WorkloadTemplate)
|
||||
*out = new(WorkloadSpec)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.EmailSignupDisabled != nil {
|
||||
|
@ -465,6 +495,51 @@ func (in *Dashboard) DeepCopyObject() runtime.Object {
|
|||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *DashboardAuthSpec) DeepCopyInto(out *DashboardAuthSpec) {
|
||||
*out = *in
|
||||
if in.OAuth2 != nil {
|
||||
in, out := &in.OAuth2, &out.OAuth2
|
||||
*out = new(DashboardOAuth2Spec)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.Basic != nil {
|
||||
in, out := &in.Basic, &out.Basic
|
||||
*out = new(DashboardBasicAuthSpec)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DashboardAuthSpec.
|
||||
func (in *DashboardAuthSpec) DeepCopy() *DashboardAuthSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(DashboardAuthSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *DashboardBasicAuthSpec) DeepCopyInto(out *DashboardBasicAuthSpec) {
|
||||
*out = *in
|
||||
if in.UsersInline != nil {
|
||||
in, out := &in.UsersInline, &out.UsersInline
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DashboardBasicAuthSpec.
|
||||
func (in *DashboardBasicAuthSpec) DeepCopy() *DashboardBasicAuthSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(DashboardBasicAuthSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *DashboardDbSpec) DeepCopyInto(out *DashboardDbSpec) {
|
||||
*out = *in
|
||||
|
@ -485,6 +560,31 @@ func (in *DashboardDbSpec) DeepCopy() *DashboardDbSpec {
|
|||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *DashboardEndpointSpec) DeepCopyInto(out *DashboardEndpointSpec) {
|
||||
*out = *in
|
||||
if in.Auth != nil {
|
||||
in, out := &in.Auth, &out.Auth
|
||||
*out = new(DashboardAuthSpec)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.TLS != nil {
|
||||
in, out := &in.TLS, &out.TLS
|
||||
*out = new(EndpointTlsSpec)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DashboardEndpointSpec.
|
||||
func (in *DashboardEndpointSpec) DeepCopy() *DashboardEndpointSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(DashboardEndpointSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *DashboardList) DeepCopyInto(out *DashboardList) {
|
||||
*out = *in
|
||||
|
@ -517,6 +617,36 @@ func (in *DashboardList) DeepCopyObject() runtime.Object {
|
|||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *DashboardOAuth2Spec) DeepCopyInto(out *DashboardOAuth2Spec) {
|
||||
*out = *in
|
||||
if in.Scopes != nil {
|
||||
in, out := &in.Scopes, &out.Scopes
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.Resources != nil {
|
||||
in, out := &in.Resources, &out.Resources
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.ClientSecretRef != nil {
|
||||
in, out := &in.ClientSecretRef, &out.ClientSecretRef
|
||||
*out = new(v1.SecretKeySelector)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DashboardOAuth2Spec.
|
||||
func (in *DashboardOAuth2Spec) DeepCopy() *DashboardOAuth2Spec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(DashboardOAuth2Spec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *DashboardSpec) DeepCopyInto(out *DashboardSpec) {
|
||||
*out = *in
|
||||
|
@ -622,11 +752,11 @@ func (in *DatabaseRolesSecrets) DeepCopy() *DatabaseRolesSecrets {
|
|||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *DatabaseStatus) DeepCopyInto(out *DatabaseStatus) {
|
||||
*out = *in
|
||||
if in.AppliedMigrations != nil {
|
||||
in, out := &in.AppliedMigrations, &out.AppliedMigrations
|
||||
*out = make(MigrationStatus, len(*in))
|
||||
for key, val := range *in {
|
||||
(*out)[key] = *val.DeepCopy()
|
||||
if in.MigrationConditions != nil {
|
||||
in, out := &in.MigrationConditions, &out.MigrationConditions
|
||||
*out = make([]MigrationScriptCondition, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
if in.Roles != nil {
|
||||
|
@ -728,6 +858,61 @@ func (in *EmailAuthSmtpSpec) DeepCopy() *EmailAuthSmtpSpec {
|
|||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *EndpointTlsSpec) DeepCopyInto(out *EndpointTlsSpec) {
|
||||
*out = *in
|
||||
if in.Cert != nil {
|
||||
in, out := &in.Cert, &out.Cert
|
||||
*out = new(TlsCertRef)
|
||||
**out = **in
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EndpointTlsSpec.
|
||||
func (in *EndpointTlsSpec) DeepCopy() *EndpointTlsSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(EndpointTlsSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *EnvoyComponentLogLevel) DeepCopyInto(out *EnvoyComponentLogLevel) {
|
||||
*out = *in
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EnvoyComponentLogLevel.
|
||||
func (in *EnvoyComponentLogLevel) DeepCopy() *EnvoyComponentLogLevel {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(EnvoyComponentLogLevel)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *EnvoyDebuggingOptions) DeepCopyInto(out *EnvoyDebuggingOptions) {
|
||||
*out = *in
|
||||
if in.ComponentLogLevels != nil {
|
||||
in, out := &in.ComponentLogLevels, &out.ComponentLogLevels
|
||||
*out = make([]EnvoyComponentLogLevel, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EnvoyDebuggingOptions.
|
||||
func (in *EnvoyDebuggingOptions) DeepCopy() *EnvoyDebuggingOptions {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(EnvoyDebuggingOptions)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *EnvoySpec) DeepCopyInto(out *EnvoySpec) {
|
||||
*out = *in
|
||||
|
@ -736,9 +921,14 @@ func (in *EnvoySpec) DeepCopyInto(out *EnvoySpec) {
|
|||
*out = new(ControlPlaneSpec)
|
||||
**out = **in
|
||||
}
|
||||
if in.WorkloadTemplate != nil {
|
||||
in, out := &in.WorkloadTemplate, &out.WorkloadTemplate
|
||||
*out = new(WorkloadTemplate)
|
||||
if in.WorkloadSpec != nil {
|
||||
in, out := &in.WorkloadSpec, &out.WorkloadSpec
|
||||
*out = new(WorkloadSpec)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.Debugging != nil {
|
||||
in, out := &in.Debugging, &out.Debugging
|
||||
*out = new(EnvoyDebuggingOptions)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
|
@ -808,9 +998,9 @@ func (in *GithubAuthProvider) DeepCopy() *GithubAuthProvider {
|
|||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ImageProxySpec) DeepCopyInto(out *ImageProxySpec) {
|
||||
*out = *in
|
||||
if in.WorkloadTemplate != nil {
|
||||
in, out := &in.WorkloadTemplate, &out.WorkloadTemplate
|
||||
*out = new(WorkloadTemplate)
|
||||
if in.WorkloadSpec != nil {
|
||||
in, out := &in.WorkloadSpec, &out.WorkloadSpec
|
||||
*out = new(WorkloadSpec)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
|
@ -856,24 +1046,25 @@ func (in *JwtSpec) DeepCopy() *JwtSpec {
|
|||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in MigrationStatus) DeepCopyInto(out *MigrationStatus) {
|
||||
{
|
||||
in := &in
|
||||
*out = make(MigrationStatus, len(*in))
|
||||
for key, val := range *in {
|
||||
(*out)[key] = *val.DeepCopy()
|
||||
}
|
||||
func (in *MigrationScriptCondition) DeepCopyInto(out *MigrationScriptCondition) {
|
||||
*out = *in
|
||||
if in.Hash != nil {
|
||||
in, out := &in.Hash, &out.Hash
|
||||
*out = make([]byte, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
in.LastProbeTime.DeepCopyInto(&out.LastProbeTime)
|
||||
in.LastTransitionTime.DeepCopyInto(&out.LastTransitionTime)
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MigrationStatus.
|
||||
func (in MigrationStatus) DeepCopy() MigrationStatus {
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MigrationScriptCondition.
|
||||
func (in *MigrationScriptCondition) DeepCopy() *MigrationScriptCondition {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(MigrationStatus)
|
||||
out := new(MigrationScriptCondition)
|
||||
in.DeepCopyInto(out)
|
||||
return *out
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
|
@ -899,9 +1090,9 @@ func (in *OAuthProvider) DeepCopy() *OAuthProvider {
|
|||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *PGMetaSpec) DeepCopyInto(out *PGMetaSpec) {
|
||||
*out = *in
|
||||
if in.WorkloadTemplate != nil {
|
||||
in, out := &in.WorkloadTemplate, &out.WorkloadTemplate
|
||||
*out = new(WorkloadTemplate)
|
||||
if in.WorkloadSpec != nil {
|
||||
in, out := &in.WorkloadSpec, &out.WorkloadSpec
|
||||
*out = new(WorkloadSpec)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
|
@ -945,9 +1136,9 @@ func (in *PostgrestSpec) DeepCopyInto(out *PostgrestSpec) {
|
|||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.WorkloadTemplate != nil {
|
||||
in, out := &in.WorkloadTemplate, &out.WorkloadTemplate
|
||||
*out = new(WorkloadTemplate)
|
||||
if in.WorkloadSpec != nil {
|
||||
in, out := &in.WorkloadSpec, &out.WorkloadSpec
|
||||
*out = new(WorkloadSpec)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
|
@ -1104,9 +1295,9 @@ func (in *StorageApiSpec) DeepCopyInto(out *StorageApiSpec) {
|
|||
*out = new(UploadTempSpec)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.WorkloadTemplate != nil {
|
||||
in, out := &in.WorkloadTemplate, &out.WorkloadTemplate
|
||||
*out = new(WorkloadTemplate)
|
||||
if in.WorkloadSpec != nil {
|
||||
in, out := &in.WorkloadSpec, &out.WorkloadSpec
|
||||
*out = new(WorkloadSpec)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
|
@ -1197,9 +1388,9 @@ func (in *StudioSpec) DeepCopyInto(out *StudioSpec) {
|
|||
*out = new(JwtSpec)
|
||||
**out = **in
|
||||
}
|
||||
if in.WorkloadTemplate != nil {
|
||||
in, out := &in.WorkloadTemplate, &out.WorkloadTemplate
|
||||
*out = new(WorkloadTemplate)
|
||||
if in.WorkloadSpec != nil {
|
||||
in, out := &in.WorkloadSpec, &out.WorkloadSpec
|
||||
*out = new(WorkloadSpec)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.GatewayServiceMatchLabels != nil {
|
||||
|
@ -1221,6 +1412,21 @@ func (in *StudioSpec) DeepCopy() *StudioSpec {
|
|||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *TlsCertRef) DeepCopyInto(out *TlsCertRef) {
|
||||
*out = *in
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TlsCertRef.
|
||||
func (in *TlsCertRef) DeepCopy() *TlsCertRef {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(TlsCertRef)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *UploadTempSpec) DeepCopyInto(out *UploadTempSpec) {
|
||||
*out = *in
|
||||
|
@ -1242,7 +1448,7 @@ func (in *UploadTempSpec) DeepCopy() *UploadTempSpec {
|
|||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *WorkloadTemplate) DeepCopyInto(out *WorkloadTemplate) {
|
||||
func (in *WorkloadSpec) DeepCopyInto(out *WorkloadSpec) {
|
||||
*out = *in
|
||||
if in.Replicas != nil {
|
||||
in, out := &in.Replicas, &out.Replicas
|
||||
|
@ -1261,8 +1467,8 @@ func (in *WorkloadTemplate) DeepCopyInto(out *WorkloadTemplate) {
|
|||
(*out)[key] = val
|
||||
}
|
||||
}
|
||||
if in.Workload != nil {
|
||||
in, out := &in.Workload, &out.Workload
|
||||
if in.ContainerSpec != nil {
|
||||
in, out := &in.ContainerSpec, &out.ContainerSpec
|
||||
*out = new(ContainerTemplate)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
|
@ -1275,12 +1481,12 @@ func (in *WorkloadTemplate) DeepCopyInto(out *WorkloadTemplate) {
|
|||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkloadTemplate.
|
||||
func (in *WorkloadTemplate) DeepCopy() *WorkloadTemplate {
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkloadSpec.
|
||||
func (in *WorkloadSpec) DeepCopy() *WorkloadSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(WorkloadTemplate)
|
||||
out := new(WorkloadSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ limitations under the License.
|
|||
package migrations
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"embed"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
|
@ -32,6 +33,7 @@ var migrationsFS embed.FS
|
|||
type Script struct {
|
||||
FileName string
|
||||
Content string
|
||||
Hash []byte
|
||||
}
|
||||
|
||||
func InitScripts() iter.Seq2[Script, error] {
|
||||
|
@ -49,10 +51,18 @@ func RoleCreationScript(roleName string) (Script, error) {
|
|||
return Script{}, err
|
||||
}
|
||||
|
||||
return Script{fileName, string(content)}, nil
|
||||
hash := sha256.New()
|
||||
_, _ = hash.Write(content)
|
||||
|
||||
return Script{
|
||||
FileName: fileName,
|
||||
Content: string(content),
|
||||
Hash: hash.Sum(nil),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func readScripts(dir string) iter.Seq2[Script, error] {
|
||||
hash := sha256.New()
|
||||
return func(yield func(Script, error) bool) {
|
||||
files, err := migrationsFS.ReadDir(dir)
|
||||
if err != nil {
|
||||
|
@ -76,11 +86,16 @@ func readScripts(dir string) iter.Seq2[Script, error] {
|
|||
}
|
||||
}
|
||||
|
||||
_, _ = hash.Write(content)
|
||||
|
||||
s := Script{
|
||||
FileName: file.Name(),
|
||||
Content: string(content),
|
||||
Hash: hash.Sum(nil),
|
||||
}
|
||||
|
||||
hash.Reset()
|
||||
|
||||
if !yield(s, nil) {
|
||||
return
|
||||
}
|
||||
|
|
|
@ -1,19 +0,0 @@
|
|||
-- migrate:up
|
||||
|
||||
-- demote postgres user
|
||||
GRANT ALL ON DATABASE postgres TO postgres;
|
||||
GRANT ALL ON SCHEMA auth TO postgres;
|
||||
GRANT ALL ON SCHEMA extensions TO postgres;
|
||||
GRANT ALL ON SCHEMA storage TO postgres;
|
||||
GRANT ALL ON ALL TABLES IN SCHEMA auth TO postgres;
|
||||
GRANT ALL ON ALL TABLES IN SCHEMA storage TO postgres;
|
||||
GRANT ALL ON ALL TABLES IN SCHEMA extensions TO postgres;
|
||||
GRANT ALL ON ALL SEQUENCES IN SCHEMA auth TO postgres;
|
||||
GRANT ALL ON ALL SEQUENCES IN SCHEMA storage TO postgres;
|
||||
GRANT ALL ON ALL SEQUENCES IN SCHEMA extensions TO postgres;
|
||||
GRANT ALL ON ALL ROUTINES IN SCHEMA auth TO postgres;
|
||||
GRANT ALL ON ALL ROUTINES IN SCHEMA storage TO postgres;
|
||||
GRANT ALL ON ALL ROUTINES IN SCHEMA extensions TO postgres;
|
||||
ALTER ROLE postgres NOSUPERUSER CREATEDB CREATEROLE LOGIN REPLICATION BYPASSRLS;
|
||||
|
||||
-- migrate:down
|
|
@ -5,34 +5,44 @@ DECLARE
|
|||
pgsodium_exists boolean;
|
||||
vault_exists boolean;
|
||||
BEGIN
|
||||
pgsodium_exists = (
|
||||
select count(*) = 1
|
||||
from pg_available_extensions
|
||||
where name = 'pgsodium'
|
||||
and default_version in ('3.1.6', '3.1.7', '3.1.8', '3.1.9')
|
||||
);
|
||||
|
||||
vault_exists = (
|
||||
IF EXISTS (SELECT FROM pg_available_extensions WHERE name = 'supabase_vault' AND default_version != '0.2.8') THEN
|
||||
CREATE EXTENSION IF NOT EXISTS supabase_vault;
|
||||
|
||||
-- for some reason extension custom scripts aren't run during AMI build, so
|
||||
-- we manually run it here
|
||||
GRANT USAGE ON SCHEMA vault TO postgres WITH GRANT OPTION;
|
||||
GRANT SELECT, DELETE ON vault.secrets, vault.decrypted_secrets TO postgres WITH GRANT OPTION;
|
||||
GRANT EXECUTE ON FUNCTION vault.create_secret, vault.update_secret, vault._crypto_aead_det_decrypt TO postgres WITH GRANT OPTION;
|
||||
ELSE
|
||||
pgsodium_exists = (
|
||||
select count(*) = 1
|
||||
from pg_available_extensions
|
||||
where name = 'supabase_vault'
|
||||
);
|
||||
|
||||
IF pgsodium_exists
|
||||
THEN
|
||||
create extension if not exists pgsodium;
|
||||
|
||||
grant pgsodium_keyiduser to postgres with admin option;
|
||||
grant pgsodium_keyholder to postgres with admin option;
|
||||
grant pgsodium_keymaker to postgres with admin option;
|
||||
|
||||
grant execute on function pgsodium.crypto_aead_det_decrypt(bytea, bytea, uuid, bytea) to service_role;
|
||||
grant execute on function pgsodium.crypto_aead_det_encrypt(bytea, bytea, uuid, bytea) to service_role;
|
||||
grant execute on function pgsodium.crypto_aead_det_keygen to service_role;
|
||||
|
||||
IF vault_exists
|
||||
where name = 'pgsodium'
|
||||
and default_version in ('3.1.6', '3.1.7', '3.1.8', '3.1.9')
|
||||
);
|
||||
|
||||
vault_exists = (
|
||||
select count(*) = 1
|
||||
from pg_available_extensions
|
||||
where name = 'supabase_vault'
|
||||
);
|
||||
|
||||
IF pgsodium_exists
|
||||
THEN
|
||||
create extension if not exists supabase_vault;
|
||||
create extension if not exists pgsodium;
|
||||
|
||||
grant pgsodium_keyiduser to postgres with admin option;
|
||||
grant pgsodium_keyholder to postgres with admin option;
|
||||
grant pgsodium_keymaker to postgres with admin option;
|
||||
|
||||
grant execute on function pgsodium.crypto_aead_det_decrypt(bytea, bytea, uuid, bytea) to service_role;
|
||||
grant execute on function pgsodium.crypto_aead_det_encrypt(bytea, bytea, uuid, bytea) to service_role;
|
||||
grant execute on function pgsodium.crypto_aead_det_keygen to service_role;
|
||||
|
||||
IF vault_exists
|
||||
THEN
|
||||
create extension if not exists supabase_vault;
|
||||
END IF;
|
||||
END IF;
|
||||
END IF;
|
||||
END $$;
|
||||
|
|
17
assets/migrations/migrations/20221207154255_create_vault.sql
Normal file
17
assets/migrations/migrations/20221207154255_create_vault.sql
Normal file
|
@ -0,0 +1,17 @@
|
|||
-- migrate:up
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
IF EXISTS (select from pg_available_extensions where name = 'supabase_vault')
|
||||
THEN
|
||||
create extension if not exists supabase_vault;
|
||||
|
||||
-- for some reason extension custom scripts aren't run during AMI build, so
|
||||
-- we manually run it here
|
||||
grant usage on schema vault to postgres with grant option;
|
||||
grant select, delete on vault.secrets, vault.decrypted_secrets to postgres with grant option;
|
||||
grant execute on function vault.create_secret, vault.update_secret, vault._crypto_aead_det_decrypt to postgres with grant option;
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
-- migrate:down
|
|
@ -4,7 +4,12 @@ ALTER ROLE authenticated inherit;
|
|||
ALTER ROLE anon inherit;
|
||||
ALTER ROLE service_role inherit;
|
||||
|
||||
GRANT pgsodium_keyholder to service_role;
|
||||
DO $$
|
||||
BEGIN
|
||||
IF EXISTS (SELECT FROM pg_roles WHERE rolname = 'pgsodium_keyholder') THEN
|
||||
GRANT pgsodium_keyholder to service_role;
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
-- migrate:down
|
||||
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
-- migrate:up
|
||||
alter role supabase_admin set log_statement = none;
|
||||
alter role supabase_auth_admin set log_statement = none;
|
||||
alter role supabase_storage_admin set log_statement = none;
|
||||
|
||||
-- migrate:down
|
3
assets/migrations/setup/realtime.sql
Normal file
3
assets/migrations/setup/realtime.sql
Normal file
|
@ -0,0 +1,3 @@
|
|||
create schema if not exists _realtime;
|
||||
|
||||
alter schema _realtime owner to supabase_admin;
|
|
@ -19,10 +19,13 @@ package main
|
|||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
corev3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
|
||||
clusterservice "github.com/envoyproxy/go-control-plane/envoy/service/cluster/v3"
|
||||
discoverygrpc "github.com/envoyproxy/go-control-plane/envoy/service/discovery/v3"
|
||||
endpointservice "github.com/envoyproxy/go-control-plane/envoy/service/endpoint/v3"
|
||||
|
@ -31,31 +34,53 @@ import (
|
|||
runtimeservice "github.com/envoyproxy/go-control-plane/envoy/service/runtime/v3"
|
||||
secretservice "github.com/envoyproxy/go-control-plane/envoy/service/secret/v3"
|
||||
cachev3 "github.com/envoyproxy/go-control-plane/pkg/cache/v3"
|
||||
"github.com/envoyproxy/go-control-plane/pkg/log"
|
||||
"github.com/envoyproxy/go-control-plane/pkg/server/v3"
|
||||
"google.golang.org/grpc/credentials"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
"google.golang.org/grpc"
|
||||
grpchealth "google.golang.org/grpc/health"
|
||||
"google.golang.org/grpc/health/grpc_health_v1"
|
||||
"google.golang.org/grpc/keepalive"
|
||||
"google.golang.org/grpc/reflection"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
|
||||
"sigs.k8s.io/controller-runtime/pkg/healthz"
|
||||
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/certs"
|
||||
"code.icb4dc0.de/prskr/supabase-operator/internal/controlplane"
|
||||
"code.icb4dc0.de/prskr/supabase-operator/internal/health"
|
||||
)
|
||||
|
||||
//nolint:lll // flag declaration with struct tags is as long as it is
|
||||
type controlPlane struct {
|
||||
ListenAddr string `name:"listen-address" default:":18000" help:"The address the control plane binds to."`
|
||||
caCert tls.Certificate `kong:"-"`
|
||||
|
||||
ListenAddr string `name:"listen-address" default:":18000" help:"The address the control plane binds to."`
|
||||
Tls struct {
|
||||
CA struct {
|
||||
Cert FileContent `env:"CERT" name:"server-cert" required:"" help:"The path to the server certificate file."`
|
||||
Key FileContent `env:"KEY" name:"server-key" required:"" help:"The path to the server key file."`
|
||||
} `embed:"" prefix:"ca." envprefix:"CA_"`
|
||||
ServerSecretName string `name:"server-secret-name" help:"The name of the secret containing the server certificate and key." default:"control-plane-xds-tls"`
|
||||
} `embed:"" prefix:"tls." envprefix:"TLS_"`
|
||||
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"`
|
||||
ServiceName string `name:"service-name" env:"CONTROL_PLANE_SERVICE_NAME" default:"" required:"" help:"The name of the control plane service."`
|
||||
Namespace string `name:"namespace" env:"CONTROL_PLANE_NAMESPACE" default:"" required:"" help:"Namespace where the controller is running, ideally set via downward API"`
|
||||
}
|
||||
|
||||
func (cp controlPlane) Run(ctx context.Context) error {
|
||||
func (cp *controlPlane) Run(ctx context.Context, logger logr.Logger) error {
|
||||
var tlsOpts []func(*tls.Config)
|
||||
|
||||
// if the enable-http2 flag is false (the default), http/2 should be disabled
|
||||
|
@ -91,6 +116,11 @@ func (cp controlPlane) Run(ctx context.Context) error {
|
|||
metricsServerOptions.FilterProvider = filters.WithAuthenticationAndAuthorization
|
||||
}
|
||||
|
||||
bootstrapClient, err := client.New(ctrl.GetConfigOrDie(), client.Options{Scheme: scheme})
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to create bootstrap client: %w", err)
|
||||
}
|
||||
|
||||
mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
|
||||
Scheme: scheme,
|
||||
Metrics: metricsServerOptions,
|
||||
|
@ -104,9 +134,15 @@ func (cp controlPlane) Run(ctx context.Context) error {
|
|||
return fmt.Errorf("unable to start control plane: %w", err)
|
||||
}
|
||||
|
||||
envoySnapshotCache := cachev3.NewSnapshotCache(false, cachev3.IDHash{}, nil)
|
||||
cacheLoggerInst := cacheLogger(logger.WithName("envoy-snapshot-cache"))
|
||||
envoySnapshotCache := cachev3.NewSnapshotCache(false, cachev3.IDHash{}, cacheLoggerInst)
|
||||
|
||||
envoySrv, err := cp.envoyServer(ctx, envoySnapshotCache)
|
||||
serverCert, err := cp.ensureControlPlaneTlsCert(ctx, bootstrapClient)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to ensure control plane TLS cert: %w", err)
|
||||
}
|
||||
|
||||
envoySrv, err := cp.envoyServer(ctx, logger, envoySnapshotCache, serverCert)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -123,6 +159,18 @@ func (cp controlPlane) Run(ctx context.Context) error {
|
|||
return fmt.Errorf("unable to create controller Core DB: %w", err)
|
||||
}
|
||||
|
||||
if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil {
|
||||
return fmt.Errorf("unable to set up health check: %w", err)
|
||||
}
|
||||
|
||||
if err := mgr.AddHealthzCheck("server-cert", health.CertValidCheck(serverCert)); err != nil {
|
||||
return fmt.Errorf("unable to set up health check: %w", err)
|
||||
}
|
||||
|
||||
if err := mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil {
|
||||
return fmt.Errorf("unable to set up ready check: %w", err)
|
||||
}
|
||||
|
||||
setupLog.Info("starting manager")
|
||||
if err := mgr.Start(ctx); err != nil {
|
||||
return fmt.Errorf("problem running manager: %w", err)
|
||||
|
@ -131,9 +179,20 @@ func (cp controlPlane) Run(ctx context.Context) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (cp controlPlane) envoyServer(
|
||||
func (cp *controlPlane) AfterApply() (err error) {
|
||||
cp.caCert, err = tls.X509KeyPair(cp.Tls.CA.Cert, cp.Tls.CA.Key)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse server certificate: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cp *controlPlane) envoyServer(
|
||||
ctx context.Context,
|
||||
logger logr.Logger,
|
||||
cache cachev3.SnapshotCache,
|
||||
serverCert tls.Certificate,
|
||||
) (runnable mgr.Runnable, err error) {
|
||||
const (
|
||||
grpcKeepaliveTime = 30 * time.Second
|
||||
|
@ -141,11 +200,8 @@ func (cp controlPlane) envoyServer(
|
|||
grpcKeepaliveMinTime = 30 * time.Second
|
||||
grpcMaxConcurrentStreams = 1000000
|
||||
)
|
||||
|
||||
var (
|
||||
logger = ctrl.Log.WithName("control-plane")
|
||||
srv = server.NewServer(ctx, cache, nil)
|
||||
)
|
||||
srv := server.NewServer(ctx, cache, xdsServerCallbacks(logger))
|
||||
logger = logger.WithName("control-plane")
|
||||
|
||||
// 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
|
||||
|
@ -153,7 +209,13 @@ func (cp controlPlane) envoyServer(
|
|||
// availability problems. Keepalive timeouts based on connection_keepalive parameter
|
||||
// https://www.envoyproxy.io/docs/envoy/latest/configuration/overview/examples#dynamic
|
||||
|
||||
tlsCfg, err := cp.tlsConfig(serverCert)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create TLS config: %w", err)
|
||||
}
|
||||
|
||||
grpcOptions := append(make([]grpc.ServerOption, 0, 4),
|
||||
grpc.Creds(credentials.NewTLS(tlsCfg)),
|
||||
grpc.MaxConcurrentStreams(grpcMaxConcurrentStreams),
|
||||
grpc.KeepaliveParams(keepalive.ServerParameters{
|
||||
Time: grpcKeepaliveTime,
|
||||
|
@ -195,3 +257,169 @@ func (cp controlPlane) envoyServer(
|
|||
return grpcServer.Serve(lis)
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (cp *controlPlane) ensureControlPlaneTlsCert(
|
||||
ctx context.Context,
|
||||
k8sClient client.Client,
|
||||
) (tls.Certificate, error) {
|
||||
var (
|
||||
controlPlaneServerCert = &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: cp.Tls.ServerSecretName,
|
||||
Namespace: cp.Namespace,
|
||||
},
|
||||
}
|
||||
serverCert tls.Certificate
|
||||
)
|
||||
|
||||
_, err := controllerutil.CreateOrUpdate(ctx, k8sClient, controlPlaneServerCert, func() (err error) {
|
||||
controlPlaneServerCert.Type = corev1.SecretTypeTLS
|
||||
|
||||
if controlPlaneServerCert.Data == nil {
|
||||
controlPlaneServerCert.Data = make(map[string][]byte, 3)
|
||||
}
|
||||
|
||||
var (
|
||||
cert = controlPlaneServerCert.Data[corev1.TLSCertKey]
|
||||
privateKey = controlPlaneServerCert.Data[corev1.TLSPrivateKeyKey]
|
||||
)
|
||||
|
||||
var requireRenewal bool
|
||||
if cert != nil && privateKey != nil {
|
||||
if serverCert, err = tls.X509KeyPair(cert, privateKey); err != nil {
|
||||
return fmt.Errorf("failed to parse server certificate: %w", err)
|
||||
}
|
||||
|
||||
renewGracePeriod := time.Duration(float64(serverCert.Leaf.NotAfter.Sub(serverCert.Leaf.NotBefore)) * 0.1)
|
||||
if serverCert.Leaf.NotAfter.Before(time.Now().Add(-renewGracePeriod)) {
|
||||
requireRenewal = true
|
||||
}
|
||||
} else {
|
||||
requireRenewal = true
|
||||
}
|
||||
|
||||
if requireRenewal {
|
||||
dnsNames := []string{
|
||||
strings.Join([]string{cp.ServiceName, cp.Namespace, "svc"}, "."),
|
||||
strings.Join([]string{cp.ServiceName, cp.Namespace, "svc", "cluster", "local"}, "."),
|
||||
}
|
||||
if certResult, err := certs.ServerCert("supabase-control-plane", dnsNames, cp.caCert); err != nil {
|
||||
return fmt.Errorf("failed to generate server certificate: %w", err)
|
||||
} else {
|
||||
serverCert = certResult.ServerCert
|
||||
controlPlaneServerCert.Data[corev1.TLSCertKey] = certResult.PublicKey
|
||||
controlPlaneServerCert.Data[corev1.TLSPrivateKeyKey] = certResult.PrivateKey
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return tls.Certificate{}, fmt.Errorf("failed to create or update control plane server certificate: %w", err)
|
||||
}
|
||||
|
||||
return serverCert, nil
|
||||
}
|
||||
|
||||
func (cp *controlPlane) tlsConfig(serverCert tls.Certificate) (*tls.Config, error) {
|
||||
tlsCfg := &tls.Config{
|
||||
RootCAs: x509.NewCertPool(),
|
||||
ClientCAs: x509.NewCertPool(),
|
||||
ClientAuth: tls.RequireAndVerifyClientCert,
|
||||
}
|
||||
|
||||
tlsCfg.Certificates = append(tlsCfg.Certificates, serverCert)
|
||||
if !tlsCfg.RootCAs.AppendCertsFromPEM(cp.Tls.CA.Cert) {
|
||||
return nil, fmt.Errorf("failed to parse CA certificate")
|
||||
}
|
||||
|
||||
if !tlsCfg.ClientCAs.AppendCertsFromPEM(cp.Tls.CA.Cert) {
|
||||
return nil, fmt.Errorf("failed to parse client CA certificate")
|
||||
}
|
||||
|
||||
return tlsCfg, nil
|
||||
}
|
||||
|
||||
func xdsServerCallbacks(logger logr.Logger) server.Callbacks {
|
||||
return server.CallbackFuncs{
|
||||
StreamOpenFunc: func(ctx context.Context, streamId int64, nodeId string) error {
|
||||
logger.Info("Stream opened", "stream-id", streamId, "node-id", nodeId)
|
||||
return nil
|
||||
},
|
||||
StreamClosedFunc: func(streamId int64, node *corev3.Node) {
|
||||
logger.Info("Stream closed", "stream-id", streamId,
|
||||
"node.id", node.Id,
|
||||
"node.cluster", node.Cluster,
|
||||
)
|
||||
},
|
||||
StreamRequestFunc: func(streamId int64, request *discoverygrpc.DiscoveryRequest) error {
|
||||
logger.Info("Stream request",
|
||||
"stream-id", streamId,
|
||||
"request.node.id", request.Node.Id,
|
||||
"request.node.cluster", request.Node.Cluster,
|
||||
"request.version", request.VersionInfo,
|
||||
"request.error", request.ErrorDetail,
|
||||
)
|
||||
return nil
|
||||
},
|
||||
StreamResponseFunc: func(
|
||||
ctx context.Context,
|
||||
streamId int64,
|
||||
request *discoverygrpc.DiscoveryRequest,
|
||||
response *discoverygrpc.DiscoveryResponse,
|
||||
) {
|
||||
logger.Info("Stream delta response",
|
||||
"stream-id", streamId,
|
||||
"request.node.id", request.Node.Id,
|
||||
"request.node.cluster", request.Node.Cluster,
|
||||
)
|
||||
},
|
||||
DeltaStreamOpenFunc: func(ctx context.Context, streamId int64, nodeId string) error {
|
||||
logger.Info("Delta stream opened", "stream-id", streamId, "node-id", nodeId)
|
||||
return nil
|
||||
},
|
||||
|
||||
DeltaStreamClosedFunc: func(streamId int64, node *corev3.Node) {
|
||||
logger.Info("Delta stream closed",
|
||||
"stream-id", streamId,
|
||||
"node.id", node.Id,
|
||||
"node.cluster", node.Cluster,
|
||||
)
|
||||
},
|
||||
StreamDeltaRequestFunc: func(i int64, request *discoverygrpc.DeltaDiscoveryRequest) error {
|
||||
logger.Info("Stream delta request",
|
||||
"stream-id", i,
|
||||
"request.node.id", request.Node.Id,
|
||||
"request.node.cluster", request.Node.Cluster,
|
||||
"request.error", request.ErrorDetail,
|
||||
)
|
||||
return nil
|
||||
},
|
||||
StreamDeltaResponseFunc: func(
|
||||
i int64,
|
||||
request *discoverygrpc.DeltaDiscoveryRequest,
|
||||
response *discoverygrpc.DeltaDiscoveryResponse,
|
||||
) {
|
||||
logger.Info("Stream delta response",
|
||||
"stream-id", i,
|
||||
"request.node", request.Node,
|
||||
"response.resources", response.Resources,
|
||||
)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func cacheLogger(logger logr.Logger) log.Logger {
|
||||
wrapper := func(delegate func(msg string, keysAndValues ...any)) func(string, ...any) {
|
||||
return func(s string, i ...any) {
|
||||
delegate(fmt.Sprintf(s, i...))
|
||||
}
|
||||
}
|
||||
|
||||
return log.LoggerFuncs{
|
||||
DebugFunc: nil, // enable for debug info
|
||||
InfoFunc: wrapper(logger.Info),
|
||||
WarnFunc: wrapper(logger.Info),
|
||||
ErrorFunc: wrapper(logger.Info),
|
||||
}
|
||||
}
|
||||
|
|
41
cmd/flags.go
Normal file
41
cmd/flags.go
Normal file
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
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 main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/alecthomas/kong"
|
||||
)
|
||||
|
||||
var _ kong.MapperValue = (*FileContent)(nil)
|
||||
|
||||
type FileContent []byte
|
||||
|
||||
func (f *FileContent) Decode(ctx *kong.DecodeContext) (err error) {
|
||||
var filePath string
|
||||
if err := ctx.Scan.PopValueInto("file-content", &filePath); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if *f, err = os.ReadFile(filePath); err != nil {
|
||||
return fmt.Errorf("failed to read file: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -40,6 +40,10 @@ type manager struct {
|
|||
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"`
|
||||
Namespace string `name:"controller-namespace" env:"CONTROLLER_NAMESPACE" default:"" help:"Namespace where the controller is running, ideally set via downward API"`
|
||||
Tls struct {
|
||||
CACert FileContent `env:"CA_CERT" name:"ca-cert" required:"" help:"The path to the CA certificate file."`
|
||||
CAKey FileContent `env:"CA_KEY" name:"ca-key" required:"" help:"The path to the CA key file."`
|
||||
} `embed:"" prefix:"tls." envprefix:"TLS_"`
|
||||
}
|
||||
|
||||
func (m manager) Run(ctx context.Context) error {
|
||||
|
@ -68,6 +72,11 @@ func (m manager) Run(ctx context.Context) error {
|
|||
TLSOpts: tlsOpts,
|
||||
})
|
||||
|
||||
caCert, err := tls.X509KeyPair(m.Tls.CACert, m.Tls.CAKey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to load CA cert: %w", err)
|
||||
}
|
||||
|
||||
// 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
|
||||
|
@ -145,6 +154,7 @@ func (m manager) Run(ctx context.Context) error {
|
|||
if err = (&controller.APIGatewayReconciler{
|
||||
Client: mgr.GetClient(),
|
||||
Scheme: mgr.GetScheme(),
|
||||
CACert: caCert,
|
||||
}).SetupWithManager(ctx, mgr); err != nil {
|
||||
return fmt.Errorf("unable to create controller APIGateway: %w", err)
|
||||
}
|
||||
|
|
33
config/control-plane/cert-ca.yaml
Normal file
33
config/control-plane/cert-ca.yaml
Normal file
|
@ -0,0 +1,33 @@
|
|||
---
|
||||
apiVersion: cert-manager.io/v1
|
||||
kind: Issuer
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: supabase-operator
|
||||
app.kubernetes.io/managed-by: kustomize
|
||||
name: cp-selfsigned-issuer
|
||||
namespace: system
|
||||
spec:
|
||||
selfSigned: {}
|
||||
---
|
||||
apiVersion: cert-manager.io/v1
|
||||
kind: Certificate
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: certificate
|
||||
app.kubernetes.io/instance: serving-cert
|
||||
app.kubernetes.io/component: certificate
|
||||
app.kubernetes.io/created-by: supabase-operator
|
||||
app.kubernetes.io/part-of: supabase-operator
|
||||
app.kubernetes.io/managed-by: kustomize
|
||||
name: cp-ca-cert
|
||||
namespace: supabase-system
|
||||
spec:
|
||||
commonName: control-plane-ca
|
||||
privateKey:
|
||||
algorithm: ECDSA
|
||||
issuerRef:
|
||||
kind: Issuer
|
||||
name: selfsigned-issuer
|
||||
secretName: control-plane-ca-cert-tls
|
||||
isCA: true
|
|
@ -18,40 +18,26 @@ spec:
|
|||
labels:
|
||||
app.kubernetes.io/name: control-plane
|
||||
spec:
|
||||
# TODO(user): Uncomment the following code to configure the nodeAffinity expression
|
||||
# according to the platforms which are supported by your solution.
|
||||
# It is considered best practice to support multiple architectures. You can
|
||||
# build your manager image using the makefile target docker-buildx.
|
||||
# affinity:
|
||||
# nodeAffinity:
|
||||
# requiredDuringSchedulingIgnoredDuringExecution:
|
||||
# nodeSelectorTerms:
|
||||
# - matchExpressions:
|
||||
# - key: kubernetes.io/arch
|
||||
# operator: In
|
||||
# values:
|
||||
# - amd64
|
||||
# - arm64
|
||||
# - ppc64le
|
||||
# - s390x
|
||||
# - key: kubernetes.io/os
|
||||
# operator: In
|
||||
# values:
|
||||
# - linux
|
||||
securityContext:
|
||||
runAsNonRoot: true
|
||||
# TODO(user): For common cases that do not require escalating privileges
|
||||
# it is recommended to ensure that all your Pods/Containers are restrictive.
|
||||
# More info: https://kubernetes.io/docs/concepts/security/pod-security-standards/#restricted
|
||||
# Please uncomment the following code if your project does NOT have to work on old Kubernetes
|
||||
# versions < 1.19 or on vendors versions which do NOT support this field by default (i.e. Openshift < 4.11 ).
|
||||
# seccompProfile:
|
||||
# type: RuntimeDefault
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
containers:
|
||||
- args:
|
||||
- control-plane
|
||||
image: supabase-operator:latest
|
||||
name: control-plane
|
||||
env:
|
||||
- name: CONTROL_PLANE_NAMESPACE
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: metadata.namespace
|
||||
- name: CONTROL_PLANE_SERVICE_NAME
|
||||
value: control-plane
|
||||
- name: TLS_CA_CERT
|
||||
value: /etc/supabase/control-plane/certs/tls.crt
|
||||
- name: TLS_CA_KEY
|
||||
value: /etc/supabase/control-plane/certs/tls.key
|
||||
ports:
|
||||
- containerPort: 18000
|
||||
name: grpc
|
||||
|
@ -62,17 +48,17 @@ spec:
|
|||
drop:
|
||||
- "ALL"
|
||||
livenessProbe:
|
||||
grpc:
|
||||
port: 18000
|
||||
httpGet:
|
||||
path: /healthz
|
||||
port: 8081
|
||||
initialDelaySeconds: 15
|
||||
periodSeconds: 20
|
||||
readinessProbe:
|
||||
grpc:
|
||||
port: 18000
|
||||
httpGet:
|
||||
path: /readyz
|
||||
port: 8081
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 10
|
||||
# TODO(user): Configure the resources accordingly based on the project requirements.
|
||||
# More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/
|
||||
resources:
|
||||
limits:
|
||||
cpu: 150m
|
||||
|
@ -80,5 +66,12 @@ spec:
|
|||
requests:
|
||||
cpu: 50m
|
||||
memory: 64Mi
|
||||
volumeMounts:
|
||||
- name: tls-certs
|
||||
mountPath: /etc/supabase/control-plane/certs
|
||||
volumes:
|
||||
- name: tls-certs
|
||||
secret:
|
||||
secretName: control-plane-ca-cert-tls
|
||||
serviceAccountName: control-plane
|
||||
terminationGracePeriodSeconds: 10
|
||||
|
|
|
@ -2,5 +2,9 @@ apiVersion: kustomize.config.k8s.io/v1beta1
|
|||
kind: Kustomization
|
||||
|
||||
resources:
|
||||
- cert-ca.yaml
|
||||
- control-plane.yaml
|
||||
- service.yaml
|
||||
|
||||
configurations:
|
||||
- kustomizeconfig.yaml
|
||||
|
|
8
config/control-plane/kustomizeconfig.yaml
Normal file
8
config/control-plane/kustomizeconfig.yaml
Normal file
|
@ -0,0 +1,8 @@
|
|||
# This configuration is for teaching kustomize how to update name ref substitution
|
||||
nameReference:
|
||||
- kind: Issuer
|
||||
group: cert-manager.io
|
||||
fieldSpecs:
|
||||
- kind: Certificate
|
||||
group: cert-manager.io
|
||||
path: spec/issuerRef/name
|
|
@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1
|
|||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.17.1
|
||||
controller-gen.kubebuilder.io/version: v0.17.2
|
||||
name: apigateways.supabase.k8s.icb4dc0.de
|
||||
spec:
|
||||
group: supabase.k8s.icb4dc0.de
|
||||
|
@ -15,7 +15,7 @@ spec:
|
|||
scope: Namespaced
|
||||
versions:
|
||||
- additionalPrinterColumns:
|
||||
- jsonPath: .status.envoy.configVersion
|
||||
- jsonPath: .status.envoy.resourceHash
|
||||
name: EnvoyConfigVersion
|
||||
type: string
|
||||
name: v1alpha1
|
||||
|
@ -43,11 +43,198 @@ spec:
|
|||
spec:
|
||||
description: APIGatewaySpec defines the desired state of APIGateway.
|
||||
properties:
|
||||
apiEndpoint:
|
||||
description: |-
|
||||
ApiEndpoint - Configure the endpoint for all API routes
|
||||
this includes the JWT configuration
|
||||
properties:
|
||||
jwks:
|
||||
description: JWKSSelector - selector where the JWKS can be retrieved
|
||||
from to enable the API gateway to validate JWTs
|
||||
properties:
|
||||
key:
|
||||
description: The key of the secret to select from. Must be
|
||||
a valid secret key.
|
||||
type: string
|
||||
name:
|
||||
default: ""
|
||||
description: |-
|
||||
Name of the referent.
|
||||
This field is effectively required, but due to backwards compatibility is
|
||||
allowed to be empty. Instances of this type with an empty value here are
|
||||
almost certainly wrong.
|
||||
More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
|
||||
type: string
|
||||
optional:
|
||||
description: Specify whether the Secret or its key must be
|
||||
defined
|
||||
type: boolean
|
||||
required:
|
||||
- key
|
||||
type: object
|
||||
x-kubernetes-map-type: atomic
|
||||
tls:
|
||||
description: TLS - enable and configure TLS for the API endpoint
|
||||
properties:
|
||||
cert:
|
||||
properties:
|
||||
caCertKey:
|
||||
default: ca.crt
|
||||
description: CaCertKey - key in the secret that contains
|
||||
the CA certificate
|
||||
type: string
|
||||
secretName:
|
||||
type: string
|
||||
serverCertKey:
|
||||
default: tls.crt
|
||||
description: ServerCertKey - key in the secret that contains
|
||||
the server certificate
|
||||
type: string
|
||||
serverKeyKey:
|
||||
default: tls.key
|
||||
description: ServerKeyKey - key in the secret that contains
|
||||
the server private key
|
||||
type: string
|
||||
required:
|
||||
- secretName
|
||||
- serverCertKey
|
||||
- serverKeyKey
|
||||
type: object
|
||||
required:
|
||||
- cert
|
||||
type: object
|
||||
required:
|
||||
- jwks
|
||||
type: object
|
||||
componentTypeLabel:
|
||||
default: app.kubernetes.io/name
|
||||
description: ComponentTypeLabel - Label to identify which Supabase
|
||||
component a Service represents (e.g. auth, postgrest, ...)
|
||||
type: string
|
||||
dashboardEndpoint:
|
||||
description: |-
|
||||
DashboardEndpoint - Configure the endpoint for the Supabase dashboard (studio)
|
||||
this includes optional authentication (basic or Oauth2) for the dashboard
|
||||
properties:
|
||||
auth:
|
||||
description: Auth - configure authentication for the dashboard
|
||||
endpoint
|
||||
properties:
|
||||
basic:
|
||||
description: |-
|
||||
Basic - HTTP basic auth configuration, this should only be used in exceptions
|
||||
e.g. during evaluations or for local development
|
||||
only used if no other authentication is configured
|
||||
properties:
|
||||
plaintextUsersSecretRef:
|
||||
description: |-
|
||||
PlaintextUsersSecretRef - name of a secret that contains plaintext credentials in key-value form
|
||||
if not empty, credentials will be merged with inline users
|
||||
type: string
|
||||
usersInline:
|
||||
description: UsersInline - [htpasswd format](https://httpd.apache.org/docs/2.4/programs/htpasswd.html)
|
||||
items:
|
||||
pattern: ^[\w_.]+:\{SHA\}[A-z0-9]+=*$
|
||||
type: string
|
||||
type: array
|
||||
type: object
|
||||
oauth2:
|
||||
description: |-
|
||||
OAuth2 - configure oauth2 authentication for the dashhboard listener
|
||||
if configured, will be preferred over Basic authentication configuration
|
||||
effectively disabling basic auth
|
||||
properties:
|
||||
authorizationEndpoint:
|
||||
description: AuthorizationEndpoint - endpoint where the
|
||||
user will be redirected to authenticate
|
||||
type: string
|
||||
clientId:
|
||||
description: ClientID - client ID to authenticate with
|
||||
the OAuth2 provider
|
||||
type: string
|
||||
clientSecretRef:
|
||||
description: ClientSecretRef - reference to the secret
|
||||
that contains the client secret
|
||||
properties:
|
||||
key:
|
||||
description: The key of the secret to select from. Must
|
||||
be a valid secret key.
|
||||
type: string
|
||||
name:
|
||||
default: ""
|
||||
description: |-
|
||||
Name of the referent.
|
||||
This field is effectively required, but due to backwards compatibility is
|
||||
allowed to be empty. Instances of this type with an empty value here are
|
||||
almost certainly wrong.
|
||||
More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
|
||||
type: string
|
||||
optional:
|
||||
description: Specify whether the Secret or its key
|
||||
must be defined
|
||||
type: boolean
|
||||
required:
|
||||
- key
|
||||
type: object
|
||||
x-kubernetes-map-type: atomic
|
||||
openIdIssuer:
|
||||
description: |-
|
||||
OpenIDIssuer - if set the defaulter will fetch the discovery document and fill
|
||||
TokenEndpoint and AuthorizationEndpoint based on the discovery document
|
||||
type: string
|
||||
resources:
|
||||
description: Resources - resources to request from the
|
||||
OAuth2 provider (e.g. "user", "email", ...) - optional
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
scopes:
|
||||
description: Scopes - scopes to request from the OAuth2
|
||||
provider (e.g. "openid", "profile", ...) - optional
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
tokenEndpoint:
|
||||
description: TokenEndpoint - endpoint where Envoy will
|
||||
retrieve the OAuth2 access and identity token from
|
||||
type: string
|
||||
required:
|
||||
- clientId
|
||||
- clientSecretRef
|
||||
type: object
|
||||
type: object
|
||||
tls:
|
||||
description: TLS - enable and configure TLS for the Dashboard
|
||||
endpoint
|
||||
properties:
|
||||
cert:
|
||||
properties:
|
||||
caCertKey:
|
||||
default: ca.crt
|
||||
description: CaCertKey - key in the secret that contains
|
||||
the CA certificate
|
||||
type: string
|
||||
secretName:
|
||||
type: string
|
||||
serverCertKey:
|
||||
default: tls.crt
|
||||
description: ServerCertKey - key in the secret that contains
|
||||
the server certificate
|
||||
type: string
|
||||
serverKeyKey:
|
||||
default: tls.key
|
||||
description: ServerKeyKey - key in the secret that contains
|
||||
the server private key
|
||||
type: string
|
||||
required:
|
||||
- secretName
|
||||
- serverCertKey
|
||||
- serverKeyKey
|
||||
type: object
|
||||
required:
|
||||
- cert
|
||||
type: object
|
||||
type: object
|
||||
envoy:
|
||||
description: Envoy - configure the envoy instance and most importantly
|
||||
the control-plane
|
||||
|
@ -70,13 +257,45 @@ spec:
|
|||
- host
|
||||
- port
|
||||
type: object
|
||||
debugging:
|
||||
properties:
|
||||
componentLogLevels:
|
||||
items:
|
||||
properties:
|
||||
component:
|
||||
description: |-
|
||||
Component - the component to set the log level for
|
||||
the component IDs can be found [here](https://github.com/envoyproxy/envoy/blob/main/source/common/common/logger.h#L36)
|
||||
type: string
|
||||
level:
|
||||
description: Level - the log level to set for the component
|
||||
enum:
|
||||
- trace
|
||||
- debug
|
||||
- info
|
||||
- warning
|
||||
- error
|
||||
- critical
|
||||
- "off"
|
||||
type: string
|
||||
required:
|
||||
- component
|
||||
- level
|
||||
type: object
|
||||
type: array
|
||||
type: object
|
||||
disableIPv6:
|
||||
description: |-
|
||||
DisableIPv6 - disable IPv6 for the Envoy instance
|
||||
this will force Envoy to use IPv4 for upstream hosts (mostly for the OAuth2 token endpoint)
|
||||
type: boolean
|
||||
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:
|
||||
workloadSpec:
|
||||
description: WorkloadTemplate - customize the Envoy deployment
|
||||
properties:
|
||||
additionalLabels:
|
||||
|
@ -1883,248 +2102,9 @@ spec:
|
|||
- name
|
||||
type: object
|
||||
type: array
|
||||
replicas:
|
||||
format: int32
|
||||
type: integer
|
||||
securityContext:
|
||||
description: |-
|
||||
PodSecurityContext holds pod-level security attributes and common container settings.
|
||||
Some fields are also present in container.securityContext. Field values of
|
||||
container.securityContext take precedence over field values of PodSecurityContext.
|
||||
properties:
|
||||
appArmorProfile:
|
||||
description: |-
|
||||
appArmorProfile is the AppArmor options to use by the containers in this pod.
|
||||
Note that this field cannot be set when spec.os.name is windows.
|
||||
properties:
|
||||
localhostProfile:
|
||||
description: |-
|
||||
localhostProfile indicates a profile loaded on the node that should be used.
|
||||
The profile must be preconfigured on the node to work.
|
||||
Must match the loaded name of the profile.
|
||||
Must be set if and only if type is "Localhost".
|
||||
type: string
|
||||
type:
|
||||
description: |-
|
||||
type indicates which kind of AppArmor profile will be applied.
|
||||
Valid options are:
|
||||
Localhost - a profile pre-loaded on the node.
|
||||
RuntimeDefault - the container runtime's default profile.
|
||||
Unconfined - no AppArmor enforcement.
|
||||
type: string
|
||||
required:
|
||||
- type
|
||||
type: object
|
||||
fsGroup:
|
||||
description: |-
|
||||
A special supplemental group that applies to all containers in a pod.
|
||||
Some volume types allow the Kubelet to change the ownership of that volume
|
||||
to be owned by the pod:
|
||||
|
||||
1. The owning GID will be the FSGroup
|
||||
2. The setgid bit is set (new files created in the volume will be owned by FSGroup)
|
||||
3. The permission bits are OR'd with rw-rw----
|
||||
|
||||
If unset, the Kubelet will not modify the ownership and permissions of any volume.
|
||||
Note that this field cannot be set when spec.os.name is windows.
|
||||
format: int64
|
||||
type: integer
|
||||
fsGroupChangePolicy:
|
||||
description: |-
|
||||
fsGroupChangePolicy defines behavior of changing ownership and permission of the volume
|
||||
before being exposed inside Pod. This field will only apply to
|
||||
volume types which support fsGroup based ownership(and permissions).
|
||||
It will have no effect on ephemeral volume types such as: secret, configmaps
|
||||
and emptydir.
|
||||
Valid values are "OnRootMismatch" and "Always". If not specified, "Always" is used.
|
||||
Note that this field cannot be set when spec.os.name is windows.
|
||||
type: string
|
||||
runAsGroup:
|
||||
description: |-
|
||||
The GID to run the entrypoint of the container process.
|
||||
Uses runtime default if unset.
|
||||
May also be set in SecurityContext. If set in both SecurityContext and
|
||||
PodSecurityContext, the value specified in SecurityContext takes precedence
|
||||
for that container.
|
||||
Note that this field cannot be set when spec.os.name is windows.
|
||||
format: int64
|
||||
type: integer
|
||||
runAsNonRoot:
|
||||
description: |-
|
||||
Indicates that the container must run as a non-root user.
|
||||
If true, the Kubelet will validate the image at runtime to ensure that it
|
||||
does not run as UID 0 (root) and fail to start the container if it does.
|
||||
If unset or false, no such validation will be performed.
|
||||
May also be set in SecurityContext. If set in both SecurityContext and
|
||||
PodSecurityContext, the value specified in SecurityContext takes precedence.
|
||||
type: boolean
|
||||
runAsUser:
|
||||
description: |-
|
||||
The UID to run the entrypoint of the container process.
|
||||
Defaults to user specified in image metadata if unspecified.
|
||||
May also be set in SecurityContext. If set in both SecurityContext and
|
||||
PodSecurityContext, the value specified in SecurityContext takes precedence
|
||||
for that container.
|
||||
Note that this field cannot be set when spec.os.name is windows.
|
||||
format: int64
|
||||
type: integer
|
||||
seLinuxChangePolicy:
|
||||
description: |-
|
||||
seLinuxChangePolicy defines how the container's SELinux label is applied to all volumes used by the Pod.
|
||||
It has no effect on nodes that do not support SELinux or to volumes does not support SELinux.
|
||||
Valid values are "MountOption" and "Recursive".
|
||||
|
||||
"Recursive" means relabeling of all files on all Pod volumes by the container runtime.
|
||||
This may be slow for large volumes, but allows mixing privileged and unprivileged Pods sharing the same volume on the same node.
|
||||
|
||||
"MountOption" mounts all eligible Pod volumes with `-o context` mount option.
|
||||
This requires all Pods that share the same volume to use the same SELinux label.
|
||||
It is not possible to share the same volume among privileged and unprivileged Pods.
|
||||
Eligible volumes are in-tree FibreChannel and iSCSI volumes, and all CSI volumes
|
||||
whose CSI driver announces SELinux support by setting spec.seLinuxMount: true in their
|
||||
CSIDriver instance. Other volumes are always re-labelled recursively.
|
||||
"MountOption" value is allowed only when SELinuxMount feature gate is enabled.
|
||||
|
||||
If not specified and SELinuxMount feature gate is enabled, "MountOption" is used.
|
||||
If not specified and SELinuxMount feature gate is disabled, "MountOption" is used for ReadWriteOncePod volumes
|
||||
and "Recursive" for all other volumes.
|
||||
|
||||
This field affects only Pods that have SELinux label set, either in PodSecurityContext or in SecurityContext of all containers.
|
||||
|
||||
All Pods that use the same volume should use the same seLinuxChangePolicy, otherwise some pods can get stuck in ContainerCreating state.
|
||||
Note that this field cannot be set when spec.os.name is windows.
|
||||
type: string
|
||||
seLinuxOptions:
|
||||
description: |-
|
||||
The SELinux context to be applied to all containers.
|
||||
If unspecified, the container runtime will allocate a random SELinux context for each
|
||||
container. May also be set in SecurityContext. If set in
|
||||
both SecurityContext and PodSecurityContext, the value specified in SecurityContext
|
||||
takes precedence for that container.
|
||||
Note that this field cannot be set when spec.os.name is windows.
|
||||
properties:
|
||||
level:
|
||||
description: Level is SELinux level label that applies
|
||||
to the container.
|
||||
type: string
|
||||
role:
|
||||
description: Role is a SELinux role label that applies
|
||||
to the container.
|
||||
type: string
|
||||
type:
|
||||
description: Type is a SELinux type label that applies
|
||||
to the container.
|
||||
type: string
|
||||
user:
|
||||
description: User is a SELinux user label that applies
|
||||
to the container.
|
||||
type: string
|
||||
type: object
|
||||
seccompProfile:
|
||||
description: |-
|
||||
The seccomp options to use by the containers in this pod.
|
||||
Note that this field cannot be set when spec.os.name is windows.
|
||||
properties:
|
||||
localhostProfile:
|
||||
description: |-
|
||||
localhostProfile indicates a profile defined in a file on the node should be used.
|
||||
The profile must be preconfigured on the node to work.
|
||||
Must be a descending path, relative to the kubelet's configured seccomp profile location.
|
||||
Must be set if type is "Localhost". Must NOT be set for any other type.
|
||||
type: string
|
||||
type:
|
||||
description: |-
|
||||
type indicates which kind of seccomp profile will be applied.
|
||||
Valid options are:
|
||||
|
||||
Localhost - a profile defined in a file on the node should be used.
|
||||
RuntimeDefault - the container runtime default profile should be used.
|
||||
Unconfined - no profile should be applied.
|
||||
type: string
|
||||
required:
|
||||
- type
|
||||
type: object
|
||||
supplementalGroups:
|
||||
description: |-
|
||||
A list of groups applied to the first process run in each container, in
|
||||
addition to the container's primary GID and fsGroup (if specified). If
|
||||
the SupplementalGroupsPolicy feature is enabled, the
|
||||
supplementalGroupsPolicy field determines whether these are in addition
|
||||
to or instead of any group memberships defined in the container image.
|
||||
If unspecified, no additional groups are added, though group memberships
|
||||
defined in the container image may still be used, depending on the
|
||||
supplementalGroupsPolicy field.
|
||||
Note that this field cannot be set when spec.os.name is windows.
|
||||
items:
|
||||
format: int64
|
||||
type: integer
|
||||
type: array
|
||||
x-kubernetes-list-type: atomic
|
||||
supplementalGroupsPolicy:
|
||||
description: |-
|
||||
Defines how supplemental groups of the first container processes are calculated.
|
||||
Valid values are "Merge" and "Strict". If not specified, "Merge" is used.
|
||||
(Alpha) Using the field requires the SupplementalGroupsPolicy feature gate to be enabled
|
||||
and the container runtime must implement support for this feature.
|
||||
Note that this field cannot be set when spec.os.name is windows.
|
||||
type: string
|
||||
sysctls:
|
||||
description: |-
|
||||
Sysctls hold a list of namespaced sysctls used for the pod. Pods with unsupported
|
||||
sysctls (by the container runtime) might fail to launch.
|
||||
Note that this field cannot be set when spec.os.name is windows.
|
||||
items:
|
||||
description: Sysctl defines a kernel parameter to be
|
||||
set
|
||||
properties:
|
||||
name:
|
||||
description: Name of a property to set
|
||||
type: string
|
||||
value:
|
||||
description: Value of a property to set
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
- value
|
||||
type: object
|
||||
type: array
|
||||
x-kubernetes-list-type: atomic
|
||||
windowsOptions:
|
||||
description: |-
|
||||
The Windows specific settings applied to all containers.
|
||||
If unspecified, the options within a container's SecurityContext will be used.
|
||||
If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence.
|
||||
Note that this field cannot be set when spec.os.name is linux.
|
||||
properties:
|
||||
gmsaCredentialSpec:
|
||||
description: |-
|
||||
GMSACredentialSpec is where the GMSA admission webhook
|
||||
(https://github.com/kubernetes-sigs/windows-gmsa) inlines the contents of the
|
||||
GMSA credential spec named by the GMSACredentialSpecName field.
|
||||
type: string
|
||||
gmsaCredentialSpecName:
|
||||
description: GMSACredentialSpecName is the name of
|
||||
the GMSA credential spec to use.
|
||||
type: string
|
||||
hostProcess:
|
||||
description: |-
|
||||
HostProcess determines if a container should be run as a 'Host Process' container.
|
||||
All of a Pod's containers must have the same effective HostProcess value
|
||||
(it is not allowed to have a mix of HostProcess containers and non-HostProcess containers).
|
||||
In addition, if HostProcess is true then HostNetwork must also be set to true.
|
||||
type: boolean
|
||||
runAsUserName:
|
||||
description: |-
|
||||
The UserName in Windows to run the entrypoint of the container process.
|
||||
Defaults to the user specified in image metadata if unspecified.
|
||||
May also be set in PodSecurityContext. If set in both SecurityContext and
|
||||
PodSecurityContext, the value specified in SecurityContext takes precedence.
|
||||
type: string
|
||||
type: object
|
||||
type: object
|
||||
workload:
|
||||
description: Workload - customize the container template of
|
||||
the workload
|
||||
container:
|
||||
description: ContainerSpec - customize the container template
|
||||
of the workload
|
||||
properties:
|
||||
additionalEnv:
|
||||
items:
|
||||
|
@ -2589,34 +2569,249 @@ spec:
|
|||
type: object
|
||||
type: array
|
||||
type: object
|
||||
replicas:
|
||||
format: int32
|
||||
type: integer
|
||||
securityContext:
|
||||
description: |-
|
||||
PodSecurityContext holds pod-level security attributes and common container settings.
|
||||
Some fields are also present in container.securityContext. Field values of
|
||||
container.securityContext take precedence over field values of PodSecurityContext.
|
||||
properties:
|
||||
appArmorProfile:
|
||||
description: |-
|
||||
appArmorProfile is the AppArmor options to use by the containers in this pod.
|
||||
Note that this field cannot be set when spec.os.name is windows.
|
||||
properties:
|
||||
localhostProfile:
|
||||
description: |-
|
||||
localhostProfile indicates a profile loaded on the node that should be used.
|
||||
The profile must be preconfigured on the node to work.
|
||||
Must match the loaded name of the profile.
|
||||
Must be set if and only if type is "Localhost".
|
||||
type: string
|
||||
type:
|
||||
description: |-
|
||||
type indicates which kind of AppArmor profile will be applied.
|
||||
Valid options are:
|
||||
Localhost - a profile pre-loaded on the node.
|
||||
RuntimeDefault - the container runtime's default profile.
|
||||
Unconfined - no AppArmor enforcement.
|
||||
type: string
|
||||
required:
|
||||
- type
|
||||
type: object
|
||||
fsGroup:
|
||||
description: |-
|
||||
A special supplemental group that applies to all containers in a pod.
|
||||
Some volume types allow the Kubelet to change the ownership of that volume
|
||||
to be owned by the pod:
|
||||
|
||||
1. The owning GID will be the FSGroup
|
||||
2. The setgid bit is set (new files created in the volume will be owned by FSGroup)
|
||||
3. The permission bits are OR'd with rw-rw----
|
||||
|
||||
If unset, the Kubelet will not modify the ownership and permissions of any volume.
|
||||
Note that this field cannot be set when spec.os.name is windows.
|
||||
format: int64
|
||||
type: integer
|
||||
fsGroupChangePolicy:
|
||||
description: |-
|
||||
fsGroupChangePolicy defines behavior of changing ownership and permission of the volume
|
||||
before being exposed inside Pod. This field will only apply to
|
||||
volume types which support fsGroup based ownership(and permissions).
|
||||
It will have no effect on ephemeral volume types such as: secret, configmaps
|
||||
and emptydir.
|
||||
Valid values are "OnRootMismatch" and "Always". If not specified, "Always" is used.
|
||||
Note that this field cannot be set when spec.os.name is windows.
|
||||
type: string
|
||||
runAsGroup:
|
||||
description: |-
|
||||
The GID to run the entrypoint of the container process.
|
||||
Uses runtime default if unset.
|
||||
May also be set in SecurityContext. If set in both SecurityContext and
|
||||
PodSecurityContext, the value specified in SecurityContext takes precedence
|
||||
for that container.
|
||||
Note that this field cannot be set when spec.os.name is windows.
|
||||
format: int64
|
||||
type: integer
|
||||
runAsNonRoot:
|
||||
description: |-
|
||||
Indicates that the container must run as a non-root user.
|
||||
If true, the Kubelet will validate the image at runtime to ensure that it
|
||||
does not run as UID 0 (root) and fail to start the container if it does.
|
||||
If unset or false, no such validation will be performed.
|
||||
May also be set in SecurityContext. If set in both SecurityContext and
|
||||
PodSecurityContext, the value specified in SecurityContext takes precedence.
|
||||
type: boolean
|
||||
runAsUser:
|
||||
description: |-
|
||||
The UID to run the entrypoint of the container process.
|
||||
Defaults to user specified in image metadata if unspecified.
|
||||
May also be set in SecurityContext. If set in both SecurityContext and
|
||||
PodSecurityContext, the value specified in SecurityContext takes precedence
|
||||
for that container.
|
||||
Note that this field cannot be set when spec.os.name is windows.
|
||||
format: int64
|
||||
type: integer
|
||||
seLinuxChangePolicy:
|
||||
description: |-
|
||||
seLinuxChangePolicy defines how the container's SELinux label is applied to all volumes used by the Pod.
|
||||
It has no effect on nodes that do not support SELinux or to volumes does not support SELinux.
|
||||
Valid values are "MountOption" and "Recursive".
|
||||
|
||||
"Recursive" means relabeling of all files on all Pod volumes by the container runtime.
|
||||
This may be slow for large volumes, but allows mixing privileged and unprivileged Pods sharing the same volume on the same node.
|
||||
|
||||
"MountOption" mounts all eligible Pod volumes with `-o context` mount option.
|
||||
This requires all Pods that share the same volume to use the same SELinux label.
|
||||
It is not possible to share the same volume among privileged and unprivileged Pods.
|
||||
Eligible volumes are in-tree FibreChannel and iSCSI volumes, and all CSI volumes
|
||||
whose CSI driver announces SELinux support by setting spec.seLinuxMount: true in their
|
||||
CSIDriver instance. Other volumes are always re-labelled recursively.
|
||||
"MountOption" value is allowed only when SELinuxMount feature gate is enabled.
|
||||
|
||||
If not specified and SELinuxMount feature gate is enabled, "MountOption" is used.
|
||||
If not specified and SELinuxMount feature gate is disabled, "MountOption" is used for ReadWriteOncePod volumes
|
||||
and "Recursive" for all other volumes.
|
||||
|
||||
This field affects only Pods that have SELinux label set, either in PodSecurityContext or in SecurityContext of all containers.
|
||||
|
||||
All Pods that use the same volume should use the same seLinuxChangePolicy, otherwise some pods can get stuck in ContainerCreating state.
|
||||
Note that this field cannot be set when spec.os.name is windows.
|
||||
type: string
|
||||
seLinuxOptions:
|
||||
description: |-
|
||||
The SELinux context to be applied to all containers.
|
||||
If unspecified, the container runtime will allocate a random SELinux context for each
|
||||
container. May also be set in SecurityContext. If set in
|
||||
both SecurityContext and PodSecurityContext, the value specified in SecurityContext
|
||||
takes precedence for that container.
|
||||
Note that this field cannot be set when spec.os.name is windows.
|
||||
properties:
|
||||
level:
|
||||
description: Level is SELinux level label that applies
|
||||
to the container.
|
||||
type: string
|
||||
role:
|
||||
description: Role is a SELinux role label that applies
|
||||
to the container.
|
||||
type: string
|
||||
type:
|
||||
description: Type is a SELinux type label that applies
|
||||
to the container.
|
||||
type: string
|
||||
user:
|
||||
description: User is a SELinux user label that applies
|
||||
to the container.
|
||||
type: string
|
||||
type: object
|
||||
seccompProfile:
|
||||
description: |-
|
||||
The seccomp options to use by the containers in this pod.
|
||||
Note that this field cannot be set when spec.os.name is windows.
|
||||
properties:
|
||||
localhostProfile:
|
||||
description: |-
|
||||
localhostProfile indicates a profile defined in a file on the node should be used.
|
||||
The profile must be preconfigured on the node to work.
|
||||
Must be a descending path, relative to the kubelet's configured seccomp profile location.
|
||||
Must be set if type is "Localhost". Must NOT be set for any other type.
|
||||
type: string
|
||||
type:
|
||||
description: |-
|
||||
type indicates which kind of seccomp profile will be applied.
|
||||
Valid options are:
|
||||
|
||||
Localhost - a profile defined in a file on the node should be used.
|
||||
RuntimeDefault - the container runtime default profile should be used.
|
||||
Unconfined - no profile should be applied.
|
||||
type: string
|
||||
required:
|
||||
- type
|
||||
type: object
|
||||
supplementalGroups:
|
||||
description: |-
|
||||
A list of groups applied to the first process run in each container, in
|
||||
addition to the container's primary GID and fsGroup (if specified). If
|
||||
the SupplementalGroupsPolicy feature is enabled, the
|
||||
supplementalGroupsPolicy field determines whether these are in addition
|
||||
to or instead of any group memberships defined in the container image.
|
||||
If unspecified, no additional groups are added, though group memberships
|
||||
defined in the container image may still be used, depending on the
|
||||
supplementalGroupsPolicy field.
|
||||
Note that this field cannot be set when spec.os.name is windows.
|
||||
items:
|
||||
format: int64
|
||||
type: integer
|
||||
type: array
|
||||
x-kubernetes-list-type: atomic
|
||||
supplementalGroupsPolicy:
|
||||
description: |-
|
||||
Defines how supplemental groups of the first container processes are calculated.
|
||||
Valid values are "Merge" and "Strict". If not specified, "Merge" is used.
|
||||
(Alpha) Using the field requires the SupplementalGroupsPolicy feature gate to be enabled
|
||||
and the container runtime must implement support for this feature.
|
||||
Note that this field cannot be set when spec.os.name is windows.
|
||||
type: string
|
||||
sysctls:
|
||||
description: |-
|
||||
Sysctls hold a list of namespaced sysctls used for the pod. Pods with unsupported
|
||||
sysctls (by the container runtime) might fail to launch.
|
||||
Note that this field cannot be set when spec.os.name is windows.
|
||||
items:
|
||||
description: Sysctl defines a kernel parameter to be
|
||||
set
|
||||
properties:
|
||||
name:
|
||||
description: Name of a property to set
|
||||
type: string
|
||||
value:
|
||||
description: Value of a property to set
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
- value
|
||||
type: object
|
||||
type: array
|
||||
x-kubernetes-list-type: atomic
|
||||
windowsOptions:
|
||||
description: |-
|
||||
The Windows specific settings applied to all containers.
|
||||
If unspecified, the options within a container's SecurityContext will be used.
|
||||
If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence.
|
||||
Note that this field cannot be set when spec.os.name is linux.
|
||||
properties:
|
||||
gmsaCredentialSpec:
|
||||
description: |-
|
||||
GMSACredentialSpec is where the GMSA admission webhook
|
||||
(https://github.com/kubernetes-sigs/windows-gmsa) inlines the contents of the
|
||||
GMSA credential spec named by the GMSACredentialSpecName field.
|
||||
type: string
|
||||
gmsaCredentialSpecName:
|
||||
description: GMSACredentialSpecName is the name of
|
||||
the GMSA credential spec to use.
|
||||
type: string
|
||||
hostProcess:
|
||||
description: |-
|
||||
HostProcess determines if a container should be run as a 'Host Process' container.
|
||||
All of a Pod's containers must have the same effective HostProcess value
|
||||
(it is not allowed to have a mix of HostProcess containers and non-HostProcess containers).
|
||||
In addition, if HostProcess is true then HostNetwork must also be set to true.
|
||||
type: boolean
|
||||
runAsUserName:
|
||||
description: |-
|
||||
The UserName in Windows to run the entrypoint of the container process.
|
||||
Defaults to the user specified in image metadata if unspecified.
|
||||
May also be set in PodSecurityContext. If set in both SecurityContext and
|
||||
PodSecurityContext, the value specified in SecurityContext takes precedence.
|
||||
type: string
|
||||
type: object
|
||||
type: object
|
||||
type: object
|
||||
required:
|
||||
- controlPlane
|
||||
type: object
|
||||
jwks:
|
||||
description: JWKSSelector - selector where the JWKS can be retrieved
|
||||
from to enable the API gateway to validate JWTs
|
||||
properties:
|
||||
key:
|
||||
description: The key of the secret to select from. Must be a
|
||||
valid secret key.
|
||||
type: string
|
||||
name:
|
||||
default: ""
|
||||
description: |-
|
||||
Name of the referent.
|
||||
This field is effectively required, but due to backwards compatibility is
|
||||
allowed to be empty. Instances of this type with an empty value here are
|
||||
almost certainly wrong.
|
||||
More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
|
||||
type: string
|
||||
optional:
|
||||
description: Specify whether the Secret or its key must be defined
|
||||
type: boolean
|
||||
required:
|
||||
- key
|
||||
type: object
|
||||
x-kubernetes-map-type: atomic
|
||||
serviceSelector:
|
||||
default:
|
||||
matchExpressions:
|
||||
|
@ -2674,7 +2869,6 @@ spec:
|
|||
x-kubernetes-map-type: atomic
|
||||
required:
|
||||
- envoy
|
||||
- jwks
|
||||
- serviceSelector
|
||||
type: object
|
||||
status:
|
||||
|
@ -2682,8 +2876,6 @@ spec:
|
|||
properties:
|
||||
envoy:
|
||||
properties:
|
||||
configVersion:
|
||||
type: string
|
||||
resourceHash:
|
||||
format: byte
|
||||
type: string
|
||||
|
|
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
@ -23,7 +23,6 @@ resources:
|
|||
- ../certmanager
|
||||
# [PROMETHEUS] To enable prometheus monitor, uncomment all sections with 'PROMETHEUS'.
|
||||
#- ../prometheus
|
||||
# [METRICS] Expose the controller manager metrics service.
|
||||
- metrics_service.yaml
|
||||
# [NETWORK POLICY] Protect the /metrics endpoint and Webhook Server with NetworkPolicy.
|
||||
# Only Pod(s) running a namespace labeled with 'metrics: enabled' will be able to gather the metrics.
|
||||
|
@ -44,19 +43,31 @@ patches:
|
|||
# crd/kustomization.yaml
|
||||
- path: manager_webhook_patch.yaml
|
||||
|
||||
# [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER' prefix.
|
||||
# Uncomment the following replacements to add the cert-manager CA injection annotations
|
||||
replacements:
|
||||
- source: # Uncomment the following block if you have any webhook
|
||||
- source:
|
||||
kind: Service
|
||||
version: v1
|
||||
name: control-plane
|
||||
fieldPath: .metadata.name
|
||||
targets:
|
||||
- select:
|
||||
kind: Deployment
|
||||
group: apps
|
||||
version: v1
|
||||
name: control-plane
|
||||
fieldPaths:
|
||||
- .spec.template.spec.containers.*.env.[name=CONTROL_PLANE_SERVICE_NAME].value
|
||||
- source:
|
||||
kind: Service
|
||||
version: v1
|
||||
name: webhook-service
|
||||
fieldPath: .metadata.name # Name of the service
|
||||
fieldPath: .metadata.name
|
||||
targets:
|
||||
- select:
|
||||
kind: Certificate
|
||||
group: cert-manager.io
|
||||
version: v1
|
||||
name: serving-cert
|
||||
fieldPaths:
|
||||
- .spec.dnsNames.0
|
||||
- .spec.dnsNames.1
|
||||
|
@ -74,6 +85,7 @@ replacements:
|
|||
kind: Certificate
|
||||
group: cert-manager.io
|
||||
version: v1
|
||||
name: serving-cert
|
||||
fieldPaths:
|
||||
- .spec.dnsNames.0
|
||||
- .spec.dnsNames.1
|
||||
|
|
1
config/dev/.gitignore
vendored
Normal file
1
config/dev/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
studio-credentials-secret.yaml
|
55
config/dev/apigateway.yaml
Normal file
55
config/dev/apigateway.yaml
Normal file
|
@ -0,0 +1,55 @@
|
|||
---
|
||||
apiVersion: supabase.k8s.icb4dc0.de/v1alpha1
|
||||
kind: APIGateway
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: supabase-operator
|
||||
app.kubernetes.io/managed-by: kustomize
|
||||
name: gateway-sample
|
||||
namespace: supabase-demo
|
||||
spec:
|
||||
envoy:
|
||||
disableIPv6: true
|
||||
workloadSpec:
|
||||
replicas: 2
|
||||
apiEndpoint:
|
||||
jwks:
|
||||
name: core-sample-jwt
|
||||
key: jwks.json
|
||||
dashboardEndpoint:
|
||||
tls:
|
||||
cert:
|
||||
secretName: dashboard-tls-cert
|
||||
auth:
|
||||
oauth2:
|
||||
openIdIssuer: "https://login.microsoftonline.com/f4e80111-1571-477a-b56d-c5fe517676b7/"
|
||||
clientId: 3528016b-f6e3-49be-8fb3-f9a9a2ab6c3f
|
||||
scopes:
|
||||
- openid
|
||||
- profile
|
||||
- email
|
||||
clientSecretRef:
|
||||
name: studio-sample-oauth2
|
||||
key: clientSecret
|
||||
---
|
||||
apiVersion: cert-manager.io/v1
|
||||
kind: Certificate
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: certificate
|
||||
app.kubernetes.io/instance: dashboard-tls
|
||||
app.kubernetes.io/component: certificate
|
||||
app.kubernetes.io/created-by: supabase-operator
|
||||
app.kubernetes.io/part-of: supabase-operator
|
||||
app.kubernetes.io/managed-by: kustomize
|
||||
name: dashboard-tls
|
||||
namespace: supabase-demo
|
||||
spec:
|
||||
dnsNames:
|
||||
- gateway-sample-envoy.supabase-demo.svc
|
||||
- gateway-sample-envoy.supabase-demo.svc.cluster.local
|
||||
- localhost:3000
|
||||
issuerRef:
|
||||
kind: ClusterIssuer
|
||||
name: cluster-pki
|
||||
secretName: dashboard-tls-cert
|
36
config/dev/ca.yaml
Normal file
36
config/dev/ca.yaml
Normal file
|
@ -0,0 +1,36 @@
|
|||
---
|
||||
apiVersion: cert-manager.io/v1
|
||||
kind: Issuer
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: supabase-operator
|
||||
app.kubernetes.io/managed-by: kustomize
|
||||
name: cluster-pki-bootstrapper
|
||||
namespace: cert-manager
|
||||
spec:
|
||||
selfSigned: {}
|
||||
---
|
||||
apiVersion: cert-manager.io/v1
|
||||
kind: Certificate
|
||||
metadata:
|
||||
name: cluster-pki-ca
|
||||
namespace: cert-manager
|
||||
spec:
|
||||
commonName: cluster-pki
|
||||
isCA: true
|
||||
issuerRef:
|
||||
kind: Issuer
|
||||
name: cluster-pki-bootstrapper
|
||||
secretName: cluster-pki-ca-cert
|
||||
---
|
||||
apiVersion: cert-manager.io/v1
|
||||
kind: ClusterIssuer
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: supabase-operator
|
||||
app.kubernetes.io/managed-by: kustomize
|
||||
name: cluster-pki
|
||||
namespace: cert-manager
|
||||
spec:
|
||||
ca:
|
||||
secretName: cluster-pki-ca-cert
|
|
@ -3,6 +3,7 @@ apiVersion: v1
|
|||
kind: ConfigMap
|
||||
metadata:
|
||||
name: pgsodium-config
|
||||
namespace: supabase-demo
|
||||
data:
|
||||
pgsodium_getkey.sh: |
|
||||
#!/bin/bash
|
||||
|
@ -18,6 +19,7 @@ apiVersion: v1
|
|||
kind: Secret
|
||||
metadata:
|
||||
name: pgsodium-key
|
||||
namespace: supabase-demo
|
||||
data:
|
||||
# Generate a 32-byte key
|
||||
# head -c 32 /dev/urandom | od -A n -t x1 | tr -d ' \n' | base64
|
||||
|
@ -27,6 +29,7 @@ apiVersion: v1
|
|||
kind: Secret
|
||||
metadata:
|
||||
name: supabase-admin-credentials
|
||||
namespace: supabase-demo
|
||||
labels:
|
||||
cnpg.io/reload: "true"
|
||||
type: kubernetes.io/basic-auth
|
||||
|
@ -38,16 +41,18 @@ apiVersion: postgresql.cnpg.io/v1
|
|||
kind: Cluster
|
||||
metadata:
|
||||
name: cluster-example
|
||||
namespace: supabase-demo
|
||||
spec:
|
||||
instances: 1
|
||||
imageName: ghcr.io/supabase/postgres:15.8.1.021
|
||||
postgresUID: 105
|
||||
postgresGID: 106
|
||||
imageName: code.icb4dc0.de/prskr/supabase-operator/postgres:17.2
|
||||
imagePullPolicy: Always
|
||||
postgresUID: 26
|
||||
postgresGID: 102
|
||||
|
||||
bootstrap:
|
||||
initdb:
|
||||
database: app
|
||||
owner: setup
|
||||
owner: supabase_admin
|
||||
postInitSQL:
|
||||
- drop publication if exists supabase_realtime;
|
||||
|
44
config/dev/core.yaml
Normal file
44
config/dev/core.yaml
Normal file
|
@ -0,0 +1,44 @@
|
|||
---
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: supabase-demo-credentials
|
||||
namespace: supabase-demo
|
||||
stringData:
|
||||
url: postgresql://supabase_admin:1n1t-R00t!@cluster-example-rw.supabase-demo:5432/app
|
||||
---
|
||||
apiVersion: supabase.k8s.icb4dc0.de/v1alpha1
|
||||
kind: Core
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: supabase-operator
|
||||
app.kubernetes.io/managed-by: kustomize
|
||||
name: core-sample
|
||||
namespace: supabase-demo
|
||||
spec:
|
||||
# public URL of the Supabase instance (API)
|
||||
# normally the Ingress/HTTPRoute endpoint
|
||||
externalUrl: http://localhost:8000/
|
||||
|
||||
# public URL of the frontend
|
||||
# could be the same as the externalUrl if you're using one Ingress/HTTPRoute for both
|
||||
# or a different one if you prefer to separate API and frontend URLs
|
||||
# will be used by Supabase Auth to redirect users after login
|
||||
siteUrl: http://localhost:3000/
|
||||
database:
|
||||
# this field is write-only, it can be used to configure the DSN without having to create the secret manually
|
||||
# dsn: postgresql://supabase_admin:1n1t-R00t!@cluster-example-rw.supabase-demo:5432/app
|
||||
dsnSecretRef:
|
||||
name: supabase-demo-credentials
|
||||
key: url
|
||||
auth:
|
||||
disableSignup: false
|
||||
enableEmailAutoconfirm: true
|
||||
providers: {}
|
||||
postgrest:
|
||||
maxRows: 1000
|
||||
jwt:
|
||||
expiry: 3600
|
||||
# name of the secret containing the JWT secret
|
||||
# will be created if not found, make sure to refernce this secret in the APIGateway, Dashboard and Storage
|
||||
secretName: core-sample-jwt
|
19
config/dev/dashboard.yaml
Normal file
19
config/dev/dashboard.yaml
Normal file
|
@ -0,0 +1,19 @@
|
|||
---
|
||||
apiVersion: supabase.k8s.icb4dc0.de/v1alpha1
|
||||
kind: Dashboard
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: supabase-operator
|
||||
app.kubernetes.io/managed-by: kustomize
|
||||
name: dashboard-sample
|
||||
namespace: supabase-demo
|
||||
spec:
|
||||
db:
|
||||
host: cluster-example-rw.supabase-demo.svc
|
||||
dbName: app
|
||||
dbCredentialsRef:
|
||||
secretName: core-sample-db-creds-supabase-admin
|
||||
studio:
|
||||
externalUrl: http://localhost:8000
|
||||
jwt:
|
||||
secretName: core-sample-jwt
|
|
@ -2,10 +2,19 @@ apiVersion: kustomize.config.k8s.io/v1beta1
|
|||
kind: Kustomization
|
||||
|
||||
resources:
|
||||
- https://github.com/cert-manager/cert-manager/releases/download/v1.16.3/cert-manager.yaml
|
||||
- https://github.com/cert-manager/cert-manager/releases/download/v1.17.1/cert-manager.yaml
|
||||
- https://github.com/cloudnative-pg/cloudnative-pg/releases/download/v1.25.0/cnpg-1.25.0.yaml
|
||||
- resources/minio.yaml
|
||||
- ca.yaml
|
||||
- namespace.yaml
|
||||
- cnpg-cluster.yaml
|
||||
- minio.yaml
|
||||
- ../default
|
||||
- studio-plaintext-users.yaml
|
||||
- studio-credentials-secret.yaml
|
||||
- core.yaml
|
||||
- apigateway.yaml
|
||||
- dashboard.yaml
|
||||
- storage.yaml
|
||||
|
||||
patches:
|
||||
- path: manager_dev_settings.yaml
|
||||
|
@ -16,3 +25,6 @@ patches:
|
|||
target:
|
||||
kind: Deployment
|
||||
labelSelector: app.kubernetes.io/name=control-plane
|
||||
|
||||
configurations:
|
||||
- kustomizeconfig/cnpg-cluster.yaml
|
||||
|
|
4
config/dev/kustomizeconfig/cnpg-cluster.yaml
Normal file
4
config/dev/kustomizeconfig/cnpg-cluster.yaml
Normal file
|
@ -0,0 +1,4 @@
|
|||
images:
|
||||
- kind: Cluster
|
||||
group: postgresql.cnpg.io
|
||||
path: spec/imageName
|
|
@ -1,3 +0,0 @@
|
|||
- op: replace
|
||||
path: /spec/replicas
|
||||
value: 1
|
|
@ -1,4 +1,4 @@
|
|||
# Deploys a new Namespace for the MinIO Pod
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
4
config/dev/namespace.yaml
Normal file
4
config/dev/namespace.yaml
Normal file
|
@ -0,0 +1,4 @@
|
|||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: supabase-demo
|
43
config/dev/storage.yaml
Normal file
43
config/dev/storage.yaml
Normal file
|
@ -0,0 +1,43 @@
|
|||
---
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: storage-s3-credentials
|
||||
namespace: supabase-demo
|
||||
stringData:
|
||||
accessKeyId: FPxTAFL7NaubjPgIGBo3
|
||||
secretAccessKey: 7F437pPe84QcoocD3MWdAIVBU3oXonhVHxK645tm
|
||||
---
|
||||
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
|
||||
namespace: supabase-demo
|
||||
spec:
|
||||
api:
|
||||
s3Backend:
|
||||
endpoint: http://minio.minio-dev.svc:9000
|
||||
region: us-east-1
|
||||
forcePathStyle: true
|
||||
bucket: test
|
||||
credentialsSecretRef:
|
||||
secretName: storage-s3-credentials
|
||||
s3: {}
|
||||
db:
|
||||
host: cluster-example-rw.supabase-demo.svc
|
||||
dbName: app
|
||||
dbCredentialsRef:
|
||||
# will be created by Core resource operator if not present
|
||||
# just make sure the secret name is either based on the name of the core resource or explicitly set
|
||||
# format <core-resource-name>-db-creds-supabase-storage-admin
|
||||
secretName: core-sample-db-creds-supabase-storage-admin
|
||||
enableImageTransformation: true
|
||||
jwtAuth:
|
||||
# will be created by Core resource operator if not present
|
||||
# just make sure the secret name is either based on the name of the core resource or explicitly set
|
||||
secretName: core-sample-jwt
|
||||
imageProxy:
|
||||
enable: true
|
8
config/dev/studio-credentials-secret.yaml
Normal file
8
config/dev/studio-credentials-secret.yaml
Normal file
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: studio-sample-oauth2
|
||||
namespace: supabase-demo
|
||||
stringData:
|
||||
clientSecret: "G9r8Q~o4LJRlTQwPpdCBaZLsWdhUxM_02Y_XBcEr"
|
8
config/dev/studio-plaintext-users.yaml
Normal file
8
config/dev/studio-plaintext-users.yaml
Normal file
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: studio-sample-basic-auth
|
||||
namespace: supabase-demo
|
||||
stringData:
|
||||
ted: not_admin
|
|
@ -20,26 +20,6 @@ spec:
|
|||
labels:
|
||||
control-plane: controller-manager
|
||||
spec:
|
||||
# TODO(user): Uncomment the following code to configure the nodeAffinity expression
|
||||
# according to the platforms which are supported by your solution.
|
||||
# It is considered best practice to support multiple architectures. You can
|
||||
# build your manager image using the makefile target docker-buildx.
|
||||
# affinity:
|
||||
# nodeAffinity:
|
||||
# requiredDuringSchedulingIgnoredDuringExecution:
|
||||
# nodeSelectorTerms:
|
||||
# - matchExpressions:
|
||||
# - key: kubernetes.io/arch
|
||||
# operator: In
|
||||
# values:
|
||||
# - amd64
|
||||
# - arm64
|
||||
# - ppc64le
|
||||
# - s390x
|
||||
# - key: kubernetes.io/os
|
||||
# operator: In
|
||||
# values:
|
||||
# - linux
|
||||
securityContext:
|
||||
runAsNonRoot: true
|
||||
seccompProfile:
|
||||
|
@ -56,6 +36,10 @@ spec:
|
|||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: metadata.namespace
|
||||
- name: TLS_CA_CERT
|
||||
value: /etc/supabase/operator/certs/tls.crt
|
||||
- name: TLS_CA_KEY
|
||||
value: /etc/supabase/operator/certs/tls.key
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
|
@ -73,8 +57,6 @@ spec:
|
|||
port: 8081
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 10
|
||||
# TODO(user): Configure the resources accordingly based on the project requirements.
|
||||
# More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/
|
||||
resources:
|
||||
limits:
|
||||
cpu: 150m
|
||||
|
@ -82,5 +64,12 @@ spec:
|
|||
requests:
|
||||
cpu: 10m
|
||||
memory: 64Mi
|
||||
volumeMounts:
|
||||
- name: tls-certs
|
||||
mountPath: /etc/supabase/operator/certs
|
||||
volumes:
|
||||
- name: tls-certs
|
||||
secret:
|
||||
secretName: control-plane-ca-cert-tls
|
||||
serviceAccountName: controller-manager
|
||||
terminationGracePeriodSeconds: 10
|
||||
|
|
|
@ -4,6 +4,17 @@ kind: ClusterRole
|
|||
metadata:
|
||||
name: control-plane-role
|
||||
rules:
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- secrets
|
||||
verbs:
|
||||
- create
|
||||
- update
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
|
||||
- apiGroups:
|
||||
- supabase.k8s.icb4dc0.de
|
||||
resources:
|
||||
|
@ -12,6 +23,8 @@ rules:
|
|||
- get
|
||||
- list
|
||||
- watch
|
||||
- update
|
||||
|
||||
- apiGroups:
|
||||
- supabase.k8s.icb4dc0.de
|
||||
resources:
|
||||
|
|
|
@ -4,7 +4,6 @@ namespace: supabase-demo
|
|||
|
||||
resources:
|
||||
- namespace.yaml
|
||||
- cnpg-cluster.yaml
|
||||
- supabase_v1alpha1_core.yaml
|
||||
- supabase_v1alpha1_apigateway.yaml
|
||||
- supabase_v1alpha1_dashboard.yaml
|
||||
|
|
|
@ -6,8 +6,9 @@ metadata:
|
|||
app.kubernetes.io/managed-by: kustomize
|
||||
name: gateway-sample
|
||||
spec:
|
||||
jwks:
|
||||
# will be created by Core resource operator if not present
|
||||
# just make sure the secret name is either based on the name of the core resource or explicitly set
|
||||
name: core-sample-jwt
|
||||
key: jwks.json
|
||||
apiEndpoint:
|
||||
jwks:
|
||||
# will be created by Core resource operator if not present
|
||||
# just make sure the secret name is either based on the name of the core resource or explicitly set
|
||||
name: core-sample-jwt
|
||||
key: jwks.json
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
---
|
||||
apiVersion: supabase.k8s.icb4dc0.de/v1alpha1
|
||||
kind: APIGateway
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: supabase-operator
|
||||
app.kubernetes.io/managed-by: kustomize
|
||||
name: gateway-sample
|
||||
spec:
|
||||
apiEndpoint:
|
||||
jwks:
|
||||
# will be created by Core resource operator if not present
|
||||
# just make sure the secret name is either based on the name of the core resource or explicitly set
|
||||
name: core-sample-jwt
|
||||
key: jwks.json
|
||||
dashboardEndpoint:
|
||||
auth:
|
||||
basic:
|
||||
usersInline:
|
||||
# admin:admin
|
||||
- admin:{SHA}0DPiKuNIrrVmD8IUCuw1hQxNqZc=
|
||||
plaintextUsersSecretRef: studio-sample-basic-auth
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: studio-sample-basic-auth
|
||||
namespace: supabase-demo
|
||||
stringData:
|
||||
ted: not_admin
|
|
@ -0,0 +1,36 @@
|
|||
---
|
||||
apiVersion: supabase.k8s.icb4dc0.de/v1alpha1
|
||||
kind: APIGateway
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: supabase-operator
|
||||
app.kubernetes.io/managed-by: kustomize
|
||||
name: gateway-sample
|
||||
spec:
|
||||
apiEndpoint:
|
||||
jwks:
|
||||
# will be created by Core resource operator if not present
|
||||
# just make sure the secret name is either based on the name of the core resource or explicitly set
|
||||
name: core-sample-jwt
|
||||
key: jwks.json
|
||||
dashboardEndpoint:
|
||||
auth:
|
||||
oauth2:
|
||||
openIdIssuer: "https://idp.your-domain.com/"
|
||||
clientId: "<your-client-id>"
|
||||
# if not set, 'user' will be used
|
||||
scopes:
|
||||
- openid
|
||||
- profile
|
||||
- email
|
||||
clientSecretRef:
|
||||
name: studio-sample-oauth2
|
||||
key: clientSecret
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: studio-sample-oauth2
|
||||
namespace: supabase-demo
|
||||
stringData:
|
||||
clientSecret: "<your-client-secret>"
|
|
@ -1,17 +1,11 @@
|
|||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: supabase-demo-credentials
|
||||
stringData:
|
||||
url: postgresql://supabase_admin:1n1t-R00t!@cluster-example-rw.supabase-demo:5432/app
|
||||
---
|
||||
apiVersion: supabase.k8s.icb4dc0.de/v1alpha1
|
||||
kind: Core
|
||||
metadata:
|
||||
name: core-sample
|
||||
labels:
|
||||
app.kubernetes.io/name: supabase-operator
|
||||
app.kubernetes.io/managed-by: kustomize
|
||||
name: core-sample
|
||||
spec:
|
||||
# public URL of the Supabase instance (API)
|
||||
# normally the Ingress/HTTPRoute endpoint
|
||||
|
@ -24,10 +18,10 @@ spec:
|
|||
siteUrl: http://localhost:3000/
|
||||
database:
|
||||
dsnSecretRef:
|
||||
# name of the secret containing the database DSN
|
||||
name: supabase-demo-credentials
|
||||
key: url
|
||||
auth:
|
||||
disableSignup: false
|
||||
enableEmailAutoconfirm: true
|
||||
providers: {}
|
||||
postgrest:
|
||||
|
@ -35,5 +29,8 @@ spec:
|
|||
jwt:
|
||||
expiry: 3600
|
||||
# name of the secret containing the JWT secret
|
||||
# will be created if not found, make sure to refernce this secret in the APIGateway, Dashboard and Storage
|
||||
# will be created if not found, make sure to refernce this secret in:
|
||||
# - APIGateway
|
||||
# - Dashboard-
|
||||
# - Storage
|
||||
secretName: core-sample-jwt
|
||||
|
|
|
@ -1,12 +1,4 @@
|
|||
---
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: storage-s3-credentials
|
||||
stringData:
|
||||
accessKeyId: FPxTAFL7NaubjPgIGBo3
|
||||
secretAccessKey: 7F437pPe84QcoocD3MWdAIVBU3oXonhVHxK645tm
|
||||
---
|
||||
apiVersion: supabase.k8s.icb4dc0.de/v1alpha1
|
||||
kind: Storage
|
||||
metadata:
|
||||
|
@ -23,7 +15,7 @@ spec:
|
|||
bucket: test
|
||||
credentialsSecretRef:
|
||||
secretName: storage-s3-credentials
|
||||
s3Protocol: {}
|
||||
s3: {}
|
||||
db:
|
||||
host: cluster-example-rw.supabase-demo.svc
|
||||
dbName: app
|
||||
|
|
|
@ -1,9 +1,13 @@
|
|||
apiVersion: ctlptl.dev/v1alpha1
|
||||
kind: Registry
|
||||
name: ctlptl-registry
|
||||
name: supabase-operator-registry
|
||||
port: 5005
|
||||
---
|
||||
apiVersion: ctlptl.dev/v1alpha1
|
||||
kind: Cluster
|
||||
product: kind
|
||||
registry: ctlptl-registry
|
||||
registry: supabase-operator-registry
|
||||
kindV1Alpha4Cluster:
|
||||
name: supabase-operator-debug
|
||||
networking:
|
||||
ipFamily: dual
|
||||
|
|
|
@ -71,13 +71,31 @@ _Appears in:_
|
|||
| Field | Description | Default | Validation |
|
||||
| --- | --- | --- | --- |
|
||||
| `envoy` _[EnvoySpec](#envoyspec)_ | Envoy - configure the envoy instance and most importantly the control-plane | | |
|
||||
| `jwks` _[SecretKeySelector](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#secretkeyselector-v1-core)_ | JWKSSelector - selector where the JWKS can be retrieved from to enable the API gateway to validate JWTs | | |
|
||||
| `apiEndpoint` _[ApiEndpointSpec](#apiendpointspec)_ | ApiEndpoint - Configure the endpoint for all API routes<br />this includes the JWT configuration | | |
|
||||
| `dashboardEndpoint` _[DashboardEndpointSpec](#dashboardendpointspec)_ | DashboardEndpoint - Configure the endpoint for the Supabase dashboard (studio)<br />this includes optional authentication (basic or Oauth2) for the dashboard | | |
|
||||
| `serviceSelector` _[LabelSelector](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#labelselector-v1-meta)_ | ServiceSelector - selector to match all Supabase services (or in fact EndpointSlices) that should be considered for this APIGateway | \{ matchExpressions:[map[key:app.kubernetes.io/part-of operator:In values:[supabase]] map[key:supabase.k8s.icb4dc0.de/api-gateway-target operator:Exists]] \} | |
|
||||
| `componentTypeLabel` _string_ | ComponentTypeLabel - Label to identify which Supabase component a Service represents (e.g. auth, postgrest, ...) | app.kubernetes.io/name | |
|
||||
|
||||
|
||||
|
||||
|
||||
#### ApiEndpointSpec
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
_Appears in:_
|
||||
- [APIGatewaySpec](#apigatewayspec)
|
||||
|
||||
| Field | Description | Default | Validation |
|
||||
| --- | --- | --- | --- |
|
||||
| `jwks` _[SecretKeySelector](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#secretkeyselector-v1-core)_ | JWKSSelector - selector where the JWKS can be retrieved from to enable the API gateway to validate JWTs | | |
|
||||
| `tls` _[EndpointTlsSpec](#endpointtlsspec)_ | TLS - enable and configure TLS for the API endpoint | | |
|
||||
|
||||
|
||||
#### AuthProviderMeta
|
||||
|
||||
|
||||
|
@ -133,7 +151,7 @@ _Appears in:_
|
|||
| `disableSignup` _boolean_ | | | |
|
||||
| `anonymousUsersEnabled` _boolean_ | | | |
|
||||
| `providers` _[AuthProviders](#authproviders)_ | | | |
|
||||
| `workloadTemplate` _[WorkloadTemplate](#workloadtemplate)_ | | | |
|
||||
| `workloadTemplate` _[WorkloadSpec](#workloadspec)_ | | | |
|
||||
| `emailSignupDisabled` _boolean_ | | | |
|
||||
|
||||
|
||||
|
@ -167,7 +185,7 @@ _Appears in:_
|
|||
|
||||
|
||||
_Appears in:_
|
||||
- [WorkloadTemplate](#workloadtemplate)
|
||||
- [WorkloadSpec](#workloadspec)
|
||||
|
||||
| Field | Description | Default | Validation |
|
||||
| --- | --- | --- | --- |
|
||||
|
@ -300,6 +318,42 @@ _Appears in:_
|
|||
| `spec` _[DashboardSpec](#dashboardspec)_ | | | |
|
||||
|
||||
|
||||
#### DashboardAuthSpec
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
_Appears in:_
|
||||
- [DashboardEndpointSpec](#dashboardendpointspec)
|
||||
|
||||
| Field | Description | Default | Validation |
|
||||
| --- | --- | --- | --- |
|
||||
| `oauth2` _[DashboardOAuth2Spec](#dashboardoauth2spec)_ | OAuth2 - configure oauth2 authentication for the dashhboard listener<br />if configured, will be preferred over Basic authentication configuration<br />effectively disabling basic auth | | |
|
||||
| `basic` _[DashboardBasicAuthSpec](#dashboardbasicauthspec)_ | Basic - HTTP basic auth configuration, this should only be used in exceptions<br />e.g. during evaluations or for local development<br />only used if no other authentication is configured | | |
|
||||
|
||||
|
||||
|
||||
|
||||
#### DashboardBasicAuthSpec
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
_Appears in:_
|
||||
- [DashboardAuthSpec](#dashboardauthspec)
|
||||
|
||||
| Field | Description | Default | Validation |
|
||||
| --- | --- | --- | --- |
|
||||
| `usersInline` _string array_ | UsersInline - [htpasswd format](https://httpd.apache.org/docs/2.4/programs/htpasswd.html) | | items:Pattern: ^[\w_.]+:\\{SHA\\}[A-z0-9]+=*$ <br /> |
|
||||
| `plaintextUsersSecretRef` _string_ | PlaintextUsersSecretRef - name of a secret that contains plaintext credentials in key-value form<br />if not empty, credentials will be merged with inline users | | |
|
||||
|
||||
|
||||
#### DashboardDbSpec
|
||||
|
||||
|
||||
|
@ -319,6 +373,23 @@ _Appears in:_
|
|||
| `dbCredentialsRef` _[DbCredentialsReference](#dbcredentialsreference)_ | DBCredentialsRef - reference to a Secret key where the DB credentials can be retrieved from<br />Credentials need to be stored in basic auth form | | |
|
||||
|
||||
|
||||
#### DashboardEndpointSpec
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
_Appears in:_
|
||||
- [APIGatewaySpec](#apigatewayspec)
|
||||
|
||||
| Field | Description | Default | Validation |
|
||||
| --- | --- | --- | --- |
|
||||
| `auth` _[DashboardAuthSpec](#dashboardauthspec)_ | Auth - configure authentication for the dashboard endpoint | | |
|
||||
| `tls` _[EndpointTlsSpec](#endpointtlsspec)_ | TLS - enable and configure TLS for the Dashboard endpoint | | |
|
||||
|
||||
|
||||
#### DashboardList
|
||||
|
||||
|
||||
|
@ -337,6 +408,28 @@ DashboardList contains a list of Dashboard.
|
|||
| `items` _[Dashboard](#dashboard) array_ | | | |
|
||||
|
||||
|
||||
#### DashboardOAuth2Spec
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
_Appears in:_
|
||||
- [DashboardAuthSpec](#dashboardauthspec)
|
||||
|
||||
| Field | Description | Default | Validation |
|
||||
| --- | --- | --- | --- |
|
||||
| `openIdIssuer` _string_ | OpenIDIssuer - if set the defaulter will fetch the discovery document and fill<br />TokenEndpoint and AuthorizationEndpoint based on the discovery document | | |
|
||||
| `tokenEndpoint` _string_ | TokenEndpoint - endpoint where Envoy will retrieve the OAuth2 access and identity token from | | |
|
||||
| `authorizationEndpoint` _string_ | AuthorizationEndpoint - endpoint where the user will be redirected to authenticate | | |
|
||||
| `clientId` _string_ | ClientID - client ID to authenticate with the OAuth2 provider | | |
|
||||
| `scopes` _string array_ | Scopes - scopes to request from the OAuth2 provider (e.g. "openid", "profile", ...) - optional | | |
|
||||
| `resources` _string array_ | Resources - resources to request from the OAuth2 provider (e.g. "user", "email", ...) - optional | | |
|
||||
| `clientSecretRef` _[SecretKeySelector](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#secretkeyselector-v1-core)_ | ClientSecretRef - reference to the secret that contains the client secret | | |
|
||||
|
||||
|
||||
#### DashboardSpec
|
||||
|
||||
|
||||
|
@ -425,7 +518,7 @@ _Appears in:_
|
|||
|
||||
| Field | Description | Default | Validation |
|
||||
| --- | --- | --- | --- |
|
||||
| `appliedMigrations` _[MigrationStatus](#migrationstatus)_ | | | |
|
||||
| `migrationConditions` _[MigrationScriptCondition](#migrationscriptcondition) array_ | | | |
|
||||
| `roles` _object (keys:string, values:integer array)_ | | | |
|
||||
|
||||
|
||||
|
@ -489,6 +582,69 @@ _Appears in:_
|
|||
| `credentialsRef` _[SmtpCredentialsReference](#smtpcredentialsreference)_ | | | |
|
||||
|
||||
|
||||
#### EndpointTlsSpec
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
_Appears in:_
|
||||
- [ApiEndpointSpec](#apiendpointspec)
|
||||
- [DashboardEndpointSpec](#dashboardendpointspec)
|
||||
|
||||
| Field | Description | Default | Validation |
|
||||
| --- | --- | --- | --- |
|
||||
| `cert` _[TlsCertRef](#tlscertref)_ | | | |
|
||||
|
||||
|
||||
#### EnvoyComponentLogLevel
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
_Appears in:_
|
||||
- [EnvoyDebuggingOptions](#envoydebuggingoptions)
|
||||
|
||||
| Field | Description | Default | Validation |
|
||||
| --- | --- | --- | --- |
|
||||
| `component` _string_ | Component - the component to set the log level for<br />the component IDs can be found [here](https://github.com/envoyproxy/envoy/blob/main/source/common/common/logger.h#L36) | | |
|
||||
| `level` _[EnvoyLogLevel](#envoyloglevel)_ | Level - the log level to set for the component | | Enum: [trace debug info warning error critical off] <br /> |
|
||||
|
||||
|
||||
#### EnvoyDebuggingOptions
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
_Appears in:_
|
||||
- [EnvoySpec](#envoyspec)
|
||||
|
||||
| Field | Description | Default | Validation |
|
||||
| --- | --- | --- | --- |
|
||||
| `componentLogLevels` _[EnvoyComponentLogLevel](#envoycomponentloglevel) array_ | | | |
|
||||
|
||||
|
||||
#### EnvoyLogLevel
|
||||
|
||||
_Underlying type:_ _string_
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
_Appears in:_
|
||||
- [EnvoyComponentLogLevel](#envoycomponentloglevel)
|
||||
|
||||
|
||||
|
||||
#### EnvoySpec
|
||||
|
||||
|
||||
|
@ -504,7 +660,9 @@ _Appears in:_
|
|||
| --- | --- | --- | --- |
|
||||
| `nodeName` _string_ | NodeName - identifies the Envoy cluster within the current namespace<br />if not set, the name of the APIGateway resource will be used<br />The primary use case is to make the assignment of multiple supabase instances in a single namespace explicit. | | |
|
||||
| `controlPlane` _[ControlPlaneSpec](#controlplanespec)_ | ControlPlane - configure the control plane where Envoy will retrieve its configuration from | | |
|
||||
| `workloadTemplate` _[WorkloadTemplate](#workloadtemplate)_ | WorkloadTemplate - customize the Envoy deployment | | |
|
||||
| `workloadSpec` _[WorkloadSpec](#workloadspec)_ | WorkloadTemplate - customize the Envoy deployment | | |
|
||||
| `disableIPv6` _boolean_ | DisableIPv6 - disable IPv6 for the Envoy instance<br />this will force Envoy to use IPv4 for upstream hosts (mostly for the OAuth2 token endpoint) | | |
|
||||
| `debugging` _[EnvoyDebuggingOptions](#envoydebuggingoptions)_ | | | |
|
||||
|
||||
|
||||
#### EnvoyStatus
|
||||
|
@ -520,7 +678,6 @@ _Appears in:_
|
|||
|
||||
| Field | Description | Default | Validation |
|
||||
| --- | --- | --- | --- |
|
||||
| `configVersion` _string_ | | | |
|
||||
| `resourceHash` _integer array_ | | | |
|
||||
|
||||
|
||||
|
@ -574,7 +731,7 @@ _Appears in:_
|
|||
| --- | --- | --- | --- |
|
||||
| `enable` _boolean_ | Enable - whether to deploy the image proxy or not | | |
|
||||
| `enableWebPDetection` _boolean_ | | | |
|
||||
| `workloadTemplate` _[WorkloadTemplate](#workloadtemplate)_ | WorkloadTemplate - customize the image proxy workload | | |
|
||||
| `workloadSpec` _[WorkloadSpec](#workloadspec)_ | WorkloadTemplate - customize the image proxy workload | | |
|
||||
|
||||
|
||||
#### ImageSpec
|
||||
|
@ -616,9 +773,11 @@ _Appears in:_
|
|||
| `serviceKey` _string_ | ServiceKey - key in secret where to read the service JWT from | service_key | |
|
||||
|
||||
|
||||
#### MigrationStatus
|
||||
|
||||
_Underlying type:_ _[Time](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#time-v1-meta)_
|
||||
|
||||
#### MigrationScriptCondition
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -627,6 +786,14 @@ _Underlying type:_ _[Time](https://kubernetes.io/docs/reference/generated/kubern
|
|||
_Appears in:_
|
||||
- [DatabaseStatus](#databasestatus)
|
||||
|
||||
| Field | Description | Default | Validation |
|
||||
| --- | --- | --- | --- |
|
||||
| `name` _string_ | Name - file name of the migration script | | |
|
||||
| `hash` _integer array_ | Hash - SHA256 hash of the script when it was last successfully applied | | |
|
||||
| `lastProbeTime` _[Time](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#time-v1-meta)_ | LastProbeTime - last time the operator tried to execute the migration script | | |
|
||||
| `lastTransitionTime` _[Time](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#time-v1-meta)_ | LastTransitionTime - last time the condition transitioned from one status to another | | |
|
||||
| `reason` _string_ | Reason - one-word, CamcelCase reason for the condition's last transition | | |
|
||||
| `message` _string_ | Message - human-readable message indicating details about the last transition | | |
|
||||
|
||||
|
||||
#### OAuthProvider
|
||||
|
@ -661,7 +828,7 @@ _Appears in:_
|
|||
|
||||
| Field | Description | Default | Validation |
|
||||
| --- | --- | --- | --- |
|
||||
| `workloadTemplate` _[WorkloadTemplate](#workloadtemplate)_ | WorkloadTemplate - customize the pg-meta deployment | | |
|
||||
| `workloadSpec` _[WorkloadSpec](#workloadspec)_ | WorkloadTemplate - customize the pg-meta deployment | | |
|
||||
|
||||
|
||||
#### PhoneAuthProvider
|
||||
|
@ -697,7 +864,7 @@ _Appears in:_
|
|||
| `extraSearchPath` _string array_ | ExtraSearchPath - Extra schemas to add to the search_path of every request.<br />These schemas tables, views and functions don’t get API endpoints, they can only be referred from the database objects inside your db-schemas. | [public extensions] | |
|
||||
| `anonRole` _string_ | AnonRole - name of the anon role | anon | |
|
||||
| `maxRows` _integer_ | MaxRows - maximum number of rows PostgREST will load at a time | 1000 | |
|
||||
| `workloadTemplate` _[WorkloadTemplate](#workloadtemplate)_ | WorkloadTemplate - customize the PostgREST workload | | |
|
||||
| `workloadSpec` _[WorkloadSpec](#workloadspec)_ | WorkloadSpec - customize the PostgREST workload | | |
|
||||
|
||||
|
||||
#### S3BackendSpec
|
||||
|
@ -832,7 +999,7 @@ _Appears in:_
|
|||
| `db` _[StorageApiDbSpec](#storageapidbspec)_ | DBSpec - Configure access to the Postgres database<br />In most cases this will reference the supabase-storage-admin credentials secret provided by the Core resource | | |
|
||||
| `s3` _[S3ProtocolSpec](#s3protocolspec)_ | S3Protocol - Configure S3 access to the Storage API allowing clients to use any S3 client | | |
|
||||
| `uploadTemp` _[UploadTempSpec](#uploadtempspec)_ | UploadTemp - configure the emptyDir for storing intermediate files during uploads | | |
|
||||
| `workloadTemplate` _[WorkloadTemplate](#workloadtemplate)_ | WorkloadTemplate - customize the Storage API workload | | |
|
||||
| `workloadSpec` _[WorkloadSpec](#workloadspec)_ | WorkloadTemplate - customize the Storage API workload | | |
|
||||
|
||||
|
||||
#### StorageList
|
||||
|
@ -886,11 +1053,30 @@ _Appears in:_
|
|||
| Field | Description | Default | Validation |
|
||||
| --- | --- | --- | --- |
|
||||
| `jwt` _[JwtSpec](#jwtspec)_ | | | |
|
||||
| `workloadTemplate` _[WorkloadTemplate](#workloadtemplate)_ | WorkloadTemplate - customize the studio deployment | | |
|
||||
| `workloadSpec` _[WorkloadSpec](#workloadspec)_ | WorkloadTemplate - customize the studio deployment | | |
|
||||
| `gatewayServiceSelector` _object (keys:string, values:string)_ | GatewayServiceSelector - selector to find the service for the API gateway<br />Required to configure the API URL in the studio deployment<br />If you don't run multiple APIGateway instances in the same namespaces, the default will be fine | \{ app.kubernetes.io/component:api-gateway app.kubernetes.io/name:envoy \} | |
|
||||
| `externalUrl` _string_ | APIExternalURL is referring to the URL where Supabase API will be available<br />Typically this is the ingress of the API gateway | | |
|
||||
|
||||
|
||||
#### TlsCertRef
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
_Appears in:_
|
||||
- [EndpointTlsSpec](#endpointtlsspec)
|
||||
|
||||
| Field | Description | Default | Validation |
|
||||
| --- | --- | --- | --- |
|
||||
| `secretName` _string_ | | | |
|
||||
| `serverCertKey` _string_ | ServerCertKey - key in the secret that contains the server certificate | tls.crt | |
|
||||
| `serverKeyKey` _string_ | ServerKeyKey - key in the secret that contains the server private key | tls.key | |
|
||||
| `caCertKey` _string_ | CaCertKey - key in the secret that contains the CA certificate | ca.crt | |
|
||||
|
||||
|
||||
#### UploadTempSpec
|
||||
|
||||
|
||||
|
@ -908,7 +1094,7 @@ _Appears in:_
|
|||
| `sizeLimit` _[Quantity](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#quantity-resource-api)_ | | | |
|
||||
|
||||
|
||||
#### WorkloadTemplate
|
||||
#### WorkloadSpec
|
||||
|
||||
|
||||
|
||||
|
@ -930,7 +1116,7 @@ _Appears in:_
|
|||
| `replicas` _integer_ | | | |
|
||||
| `securityContext` _[PodSecurityContext](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#podsecuritycontext-v1-core)_ | | | |
|
||||
| `additionalLabels` _object (keys:string, values:string)_ | | | |
|
||||
| `workload` _[ContainerTemplate](#containertemplate)_ | Workload - customize the container template of the workload | | |
|
||||
| `container` _[ContainerTemplate](#containertemplate)_ | ContainerSpec - customize the container template of the workload | | |
|
||||
| `additionalVolumes` _[Volume](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#volume-v1-core) array_ | | | |
|
||||
|
||||
|
||||
|
|
1
docs/auth/email.md
Normal file
1
docs/auth/email.md
Normal file
|
@ -0,0 +1 @@
|
|||
# Email
|
1
docs/auth/overview.md
Normal file
1
docs/auth/overview.md
Normal file
|
@ -0,0 +1 @@
|
|||
# Overview
|
1
docs/auth/providers/azure.md
Normal file
1
docs/auth/providers/azure.md
Normal file
|
@ -0,0 +1 @@
|
|||
# Providers
|
1
docs/components/apigateway.md
Normal file
1
docs/components/apigateway.md
Normal file
|
@ -0,0 +1 @@
|
|||
# APIGateway
|
|
@ -0,0 +1,14 @@
|
|||
# Core
|
||||
|
||||
The `Core` resource configures the essential Supabase services:
|
||||
|
||||
- PostgREST
|
||||
- Auth
|
||||
|
||||
and it manages the initial DB migrations that are not done by the services themselves and are part of the [supabase/postgres](https://github.com/supabase/postgres) repository.
|
||||
|
||||
To deploy a basic `Core` instance, you can use the following snippet as a 'template':
|
||||
|
||||
```yaml title="Basic 'Core' example" linenums="1"
|
||||
--8<-- "config/samples/supabase_v1alpha1_core.yaml"
|
||||
```
|
19
docs/development/tools.md
Normal file
19
docs/development/tools.md
Normal file
|
@ -0,0 +1,19 @@
|
|||
# Tools
|
||||
|
||||
## Essentials
|
||||
|
||||
- Current Go version (see `go.mod`)
|
||||
- Any Docker daemon[^1]
|
||||
- kubectl (v1.30.0+.)
|
||||
- Access to a Kubernetes v1.30.+ cluster[^2]
|
||||
|
||||
[^1]: Docker Desktop, Orbstack, Podman, ...
|
||||
[^2]: kind, Orbstack, Docker Desktop, Rancher Desktop, k3s, microk8s, ...
|
||||
|
||||
## Recommended
|
||||
|
||||
- [husky](https://github.com/go-courier/husky)
|
||||
- [tilt](https://tilt.dev/)
|
||||
- [ctlptl](https://github.com/tilt-dev/ctlptl)
|
||||
- [goreleaser](https://goreleaser.com/)
|
||||
- [ko](https://ko.build/)
|
|
@ -1,5 +1,11 @@
|
|||
# Getting Started
|
||||
|
||||
## Dependencies
|
||||
|
||||
The operator has the following dependencies:
|
||||
|
||||
- [cert-manager](https://cert-manager.io/) (to issue webhook certificates)
|
||||
|
||||
## Deploying the operator
|
||||
|
||||
The easiest way to deploy the operator is to fetch the manifest from the [releases](https://code.icb4dc0.de/prskr/supabase-operator/releases) and apply it like this:
|
||||
|
@ -12,3 +18,12 @@ The manifest is rendered as part of the release workflow and based on [kustomize
|
|||
If you want to customize the deployment, you can start from the [release/default](https://code.icb4dc0.de/prskr/supabase-operator/src/branch/main/config/release/default) layer and build your own manifest.
|
||||
|
||||
## Deploying a basic Supabase instance
|
||||
|
||||
As described in the [overview](./components/overview.md) the custom resources are 'grouping' the Supabase services into 'modules'.
|
||||
A common basic instance requires:
|
||||
|
||||
- a [`Core`](./components/core.md) instance (PostgREST, Auth & DB migrations)
|
||||
- an [`APIGateway`](./components/apigateway.md) instance (gateway to handle JWT auth and routing)
|
||||
|
||||
it is perfectly possible to deploy for instance only an `APIGateway` and a `Storage` instance as well if you don't need the API or you can also manage your own API gateway if you prefer it that way.
|
||||
The operator setup tries to be as 'unopinionated' as possible.
|
||||
|
|
|
@ -9,7 +9,7 @@ This project is not affiliated with the Supabase project or company in any way.
|
|||
This is currently a work-in-progress experiment to replace existing Helm charts for Supabase as they tend to be hard to deploy and to manage and the default Supabase stack - although working great as a single instance or in their SaaS instances - isn't a perfect fit for Kubernetes.
|
||||
This operator replaces tedious Helm values files with a small set of custom resources that allow an user to quickly deploy a Supabase instance without having to know much (if anything) of the Supabase internals.
|
||||
|
||||
## Targets
|
||||
## Goals
|
||||
|
||||
- Make it as easy as possible to deploy Supabase on a Kubernetes cluster
|
||||
- Manage updates of components
|
||||
|
@ -23,7 +23,7 @@ This operator replaces tedious Helm values files with a small set of custom reso
|
|||
- ConfigMaps
|
||||
- *soon*: NetworkPolicies
|
||||
|
||||
## Non-Targets
|
||||
## Non-Goals
|
||||
|
||||
- Manage **all** Kubernetes aspects, it does **not** create:
|
||||
- PodDisruptionBudgets
|
||||
|
|
301
go.mod
301
go.mod
|
@ -1,119 +1,326 @@
|
|||
module code.icb4dc0.de/prskr/supabase-operator
|
||||
|
||||
go 1.23.5
|
||||
go 1.24
|
||||
|
||||
toolchain go1.24.0
|
||||
|
||||
require (
|
||||
github.com/alecthomas/kong v1.6.0
|
||||
github.com/envoyproxy/go-control-plane v0.13.1
|
||||
github.com/jackc/pgx/v5 v5.7.1
|
||||
github.com/alecthomas/kong v1.7.0
|
||||
github.com/envoyproxy/go-control-plane v0.13.4
|
||||
github.com/envoyproxy/go-control-plane/envoy v1.32.4
|
||||
github.com/go-logr/logr v1.4.2
|
||||
github.com/jackc/pgx/v5 v5.7.2
|
||||
github.com/lestrrat-go/jwx/v2 v2.1.3
|
||||
github.com/magefile/mage v1.15.0
|
||||
github.com/onsi/ginkgo/v2 v2.21.0
|
||||
github.com/onsi/gomega v1.35.1
|
||||
github.com/onsi/ginkgo/v2 v2.22.2
|
||||
github.com/onsi/gomega v1.36.2
|
||||
github.com/stretchr/testify v1.10.0
|
||||
go.uber.org/zap v1.27.0
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56
|
||||
google.golang.org/grpc v1.65.0
|
||||
google.golang.org/protobuf v1.36.3
|
||||
golang.org/x/exp v0.0.0-20250128182459-e0ece0dbea4c
|
||||
google.golang.org/grpc v1.70.0
|
||||
google.golang.org/protobuf v1.36.6
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
k8s.io/api v0.32.1
|
||||
k8s.io/apimachinery v0.32.1
|
||||
k8s.io/client-go v0.32.1
|
||||
sigs.k8s.io/controller-runtime v0.20.0
|
||||
sigs.k8s.io/controller-runtime v0.20.1
|
||||
)
|
||||
|
||||
tool (
|
||||
github.com/elastic/crd-ref-docs
|
||||
github.com/golangci/golangci-lint/v2/cmd/golangci-lint
|
||||
gotest.tools/gotestsum
|
||||
mvdan.cc/gofumpt
|
||||
sigs.k8s.io/controller-runtime/tools/setup-envtest
|
||||
sigs.k8s.io/controller-tools/cmd/controller-gen
|
||||
sigs.k8s.io/kustomize/kustomize/v5
|
||||
)
|
||||
|
||||
require (
|
||||
cel.dev/expr v0.18.0 // indirect
|
||||
github.com/antlr4-go/antlr/v4 v4.13.0 // indirect
|
||||
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a // indirect
|
||||
4d63.com/gocheckcompilerdirectives v1.3.0 // indirect
|
||||
4d63.com/gochecknoglobals v0.2.2 // indirect
|
||||
cel.dev/expr v0.19.2 // indirect
|
||||
github.com/4meepo/tagalign v1.4.2 // indirect
|
||||
github.com/Abirdcfly/dupword v0.1.3 // indirect
|
||||
github.com/Antonboom/errname v1.1.0 // indirect
|
||||
github.com/Antonboom/nilnil v1.1.0 // indirect
|
||||
github.com/Antonboom/testifylint v1.6.0 // indirect
|
||||
github.com/BurntSushi/toml v1.5.0 // indirect
|
||||
github.com/Crocmagnon/fatcontext v0.7.1 // indirect
|
||||
github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 // indirect
|
||||
github.com/GaijinEntertainment/go-exhaustruct/v3 v3.3.1 // indirect
|
||||
github.com/Masterminds/goutils v1.1.1 // indirect
|
||||
github.com/Masterminds/semver v1.5.0 // indirect
|
||||
github.com/Masterminds/semver/v3 v3.3.1 // indirect
|
||||
github.com/Masterminds/sprig v2.22.0+incompatible // indirect
|
||||
github.com/OpenPeeDeeP/depguard/v2 v2.2.1 // indirect
|
||||
github.com/alecthomas/go-check-sumtype v0.3.1 // indirect
|
||||
github.com/alexkohler/nakedret/v2 v2.0.5 // indirect
|
||||
github.com/alexkohler/prealloc v1.0.0 // indirect
|
||||
github.com/alingse/asasalint v0.0.11 // indirect
|
||||
github.com/alingse/nilnesserr v0.1.2 // indirect
|
||||
github.com/antlr4-go/antlr/v4 v4.13.1 // indirect
|
||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
|
||||
github.com/ashanbrown/forbidigo v1.6.0 // indirect
|
||||
github.com/ashanbrown/makezero v1.2.0 // indirect
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/bitfield/gotestdox v0.2.2 // indirect
|
||||
github.com/bkielbasa/cyclop v1.2.3 // indirect
|
||||
github.com/blang/semver/v4 v4.0.0 // indirect
|
||||
github.com/blizzy78/varnamelen v0.8.0 // indirect
|
||||
github.com/bombsimon/wsl/v4 v4.6.0 // indirect
|
||||
github.com/breml/bidichk v0.3.3 // indirect
|
||||
github.com/breml/errchkjson v0.4.1 // indirect
|
||||
github.com/butuzov/ireturn v0.3.1 // indirect
|
||||
github.com/butuzov/mirror v1.3.0 // indirect
|
||||
github.com/catenacyber/perfsprint v0.9.1 // indirect
|
||||
github.com/ccojocar/zxcvbn-go v1.0.2 // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||
github.com/census-instrumentation/opencensus-proto v0.4.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/cncf/xds/go v0.0.0-20240423153145-555b57ec207b // indirect
|
||||
github.com/charithe/durationcheck v0.0.10 // indirect
|
||||
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect
|
||||
github.com/charmbracelet/lipgloss v1.1.0 // indirect
|
||||
github.com/charmbracelet/x/ansi v0.8.0 // indirect
|
||||
github.com/charmbracelet/x/cellbuf v0.0.13 // indirect
|
||||
github.com/charmbracelet/x/term v0.2.1 // indirect
|
||||
github.com/chavacava/garif v0.1.0 // indirect
|
||||
github.com/ckaznocha/intrange v0.3.1 // indirect
|
||||
github.com/cncf/xds/go v0.0.0-20250121191232-2f005788dc42 // indirect
|
||||
github.com/curioswitch/go-reassign v0.3.0 // indirect
|
||||
github.com/daixiang0/gci v0.13.6 // indirect
|
||||
github.com/dave/dst v0.27.3 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect
|
||||
github.com/denis-tingaikin/go-header v0.5.0 // indirect
|
||||
github.com/dnephin/pflag v1.0.7 // indirect
|
||||
github.com/elastic/crd-ref-docs v0.1.0 // indirect
|
||||
github.com/emicklei/go-restful/v3 v3.12.1 // indirect
|
||||
github.com/envoyproxy/protoc-gen-validate v1.1.0 // indirect
|
||||
github.com/evanphx/json-patch/v5 v5.9.0 // indirect
|
||||
github.com/envoyproxy/go-control-plane/ratelimit v0.1.0 // indirect
|
||||
github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect
|
||||
github.com/ettle/strcase v0.2.0 // indirect
|
||||
github.com/evanphx/json-patch/v5 v5.9.11 // indirect
|
||||
github.com/fatih/color v1.18.0 // indirect
|
||||
github.com/fatih/structtag v1.2.0 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/firefart/nonamedreturns v1.0.5 // indirect
|
||||
github.com/fsnotify/fsnotify v1.8.0 // indirect
|
||||
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
|
||||
github.com/go-logr/logr v1.4.2 // indirect
|
||||
github.com/fzipp/gocyclo v0.6.0 // indirect
|
||||
github.com/ghostiam/protogetter v0.3.12 // indirect
|
||||
github.com/go-critic/go-critic v0.13.0 // indirect
|
||||
github.com/go-errors/errors v1.4.2 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-logr/zapr v1.3.0 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.21.0 // indirect
|
||||
github.com/go-openapi/jsonreference v0.21.0 // indirect
|
||||
github.com/go-openapi/swag v0.23.0 // indirect
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
|
||||
github.com/goccy/go-json v0.10.3 // indirect
|
||||
github.com/go-toolsmith/astcast v1.1.0 // indirect
|
||||
github.com/go-toolsmith/astcopy v1.1.0 // indirect
|
||||
github.com/go-toolsmith/astequal v1.2.0 // indirect
|
||||
github.com/go-toolsmith/astfmt v1.1.0 // indirect
|
||||
github.com/go-toolsmith/astp v1.1.0 // indirect
|
||||
github.com/go-toolsmith/strparse v1.1.0 // indirect
|
||||
github.com/go-toolsmith/typep v1.1.0 // indirect
|
||||
github.com/go-viper/mapstructure/v2 v2.2.1 // indirect
|
||||
github.com/go-xmlfmt/xmlfmt v1.1.3 // indirect
|
||||
github.com/gobuffalo/flect v1.0.3 // indirect
|
||||
github.com/gobwas/glob v0.2.3 // indirect
|
||||
github.com/goccy/go-json v0.10.5 // indirect
|
||||
github.com/goccy/go-yaml v1.11.3 // indirect
|
||||
github.com/gofrs/flock v0.12.1 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang/protobuf v1.5.4 // indirect
|
||||
github.com/golangci/dupl v0.0.0-20250308024227-f665c8d69b32 // indirect
|
||||
github.com/golangci/go-printf-func-name v0.1.0 // indirect
|
||||
github.com/golangci/gofmt v0.0.0-20250106114630-d62b90e6713d // indirect
|
||||
github.com/golangci/golangci-lint/v2 v2.0.1 // indirect
|
||||
github.com/golangci/golines v0.0.0-20250217232252-b35a6149b587 // indirect
|
||||
github.com/golangci/misspell v0.6.0 // indirect
|
||||
github.com/golangci/plugin-module-register v0.1.1 // indirect
|
||||
github.com/golangci/revgrep v0.8.0 // indirect
|
||||
github.com/golangci/unconvert v0.0.0-20240309020433-c5143eacb3ed // indirect
|
||||
github.com/google/btree v1.1.3 // indirect
|
||||
github.com/google/cel-go v0.22.0 // indirect
|
||||
github.com/google/gnostic-models v0.6.9 // indirect
|
||||
github.com/google/go-cmp v0.6.0 // indirect
|
||||
github.com/google/go-cmp v0.7.0 // indirect
|
||||
github.com/google/gofuzz v1.2.0 // indirect
|
||||
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db // indirect
|
||||
github.com/google/pprof v0.0.0-20250202011525-fc3143867406 // indirect
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect
|
||||
github.com/gordonklaus/ineffassign v0.1.0 // indirect
|
||||
github.com/gostaticanalysis/analysisutil v0.7.1 // indirect
|
||||
github.com/gostaticanalysis/comment v1.5.0 // indirect
|
||||
github.com/gostaticanalysis/forcetypeassert v0.2.0 // indirect
|
||||
github.com/gostaticanalysis/nilerr v0.1.1 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.0 // indirect
|
||||
github.com/hashicorp/go-immutable-radix/v2 v2.1.0 // indirect
|
||||
github.com/hashicorp/go-version v1.7.0 // indirect
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
|
||||
github.com/hexops/gotextdiff v1.0.3 // indirect
|
||||
github.com/huandu/xstrings v1.3.3 // indirect
|
||||
github.com/imdario/mergo v0.3.11 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||
github.com/jgautheron/goconst v1.7.1 // indirect
|
||||
github.com/jingyugao/rowserrcheck v1.1.1 // indirect
|
||||
github.com/jjti/go-spancheck v0.6.4 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/julz/importas v0.2.0 // indirect
|
||||
github.com/karamaru-alpha/copyloopvar v1.2.1 // indirect
|
||||
github.com/kisielk/errcheck v1.9.0 // indirect
|
||||
github.com/kkHAIKE/contextcheck v1.1.6 // indirect
|
||||
github.com/klauspost/compress v1.17.11 // indirect
|
||||
github.com/kulti/thelper v0.6.3 // indirect
|
||||
github.com/kunwardeep/paralleltest v1.0.12 // indirect
|
||||
github.com/lasiar/canonicalheader v1.1.2 // indirect
|
||||
github.com/ldez/exptostd v0.4.2 // indirect
|
||||
github.com/ldez/gomoddirectives v0.6.1 // indirect
|
||||
github.com/ldez/grignotin v0.9.0 // indirect
|
||||
github.com/ldez/tagliatelle v0.7.1 // indirect
|
||||
github.com/ldez/usetesting v0.4.2 // indirect
|
||||
github.com/leonklingele/grouper v1.1.2 // indirect
|
||||
github.com/lestrrat-go/blackmagic v1.0.2 // indirect
|
||||
github.com/lestrrat-go/httpcc v1.0.1 // indirect
|
||||
github.com/lestrrat-go/httprc v1.0.6 // indirect
|
||||
github.com/lestrrat-go/iter v1.0.2 // indirect
|
||||
github.com/lestrrat-go/option v1.0.1 // indirect
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||
github.com/macabu/inamedparam v0.2.0 // indirect
|
||||
github.com/mailru/easyjson v0.9.0 // indirect
|
||||
github.com/maratori/testableexamples v1.0.0 // indirect
|
||||
github.com/maratori/testpackage v1.1.1 // indirect
|
||||
github.com/matoous/godox v1.1.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.16 // indirect
|
||||
github.com/mgechev/revive v1.7.0 // indirect
|
||||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect
|
||||
github.com/moricho/tparallel v0.3.2 // indirect
|
||||
github.com/muesli/termenv v0.16.0 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/nakabonne/nestif v0.3.1 // indirect
|
||||
github.com/nishanths/exhaustive v0.12.0 // indirect
|
||||
github.com/nishanths/predeclared v0.2.2 // indirect
|
||||
github.com/nunnatsa/ginkgolinter v0.19.1 // indirect
|
||||
github.com/olekukonko/tablewriter v0.0.5 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
|
||||
github.com/prometheus/client_golang v1.20.5 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/polyfloyd/go-errorlint v1.7.1 // indirect
|
||||
github.com/prometheus/client_golang v1.21.1 // indirect
|
||||
github.com/prometheus/client_model v0.6.1 // indirect
|
||||
github.com/prometheus/common v0.62.0 // indirect
|
||||
github.com/prometheus/procfs v0.15.1 // indirect
|
||||
github.com/prometheus/common v0.63.0 // indirect
|
||||
github.com/prometheus/procfs v0.16.0 // indirect
|
||||
github.com/quasilyte/go-ruleguard v0.4.4 // indirect
|
||||
github.com/quasilyte/go-ruleguard/dsl v0.3.22 // indirect
|
||||
github.com/quasilyte/gogrep v0.5.0 // indirect
|
||||
github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 // indirect
|
||||
github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 // indirect
|
||||
github.com/raeperd/recvcheck v0.2.0 // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
github.com/rogpeppe/go-internal v1.14.1 // indirect
|
||||
github.com/ryancurrah/gomodguard v1.4.1 // indirect
|
||||
github.com/ryanrolds/sqlclosecheck v0.5.1 // indirect
|
||||
github.com/sagikazarmark/locafero v0.8.0 // indirect
|
||||
github.com/sanposhiho/wastedassign/v2 v2.1.0 // indirect
|
||||
github.com/santhosh-tekuri/jsonschema/v6 v6.0.1 // indirect
|
||||
github.com/sashamelentyev/interfacebloat v1.1.0 // indirect
|
||||
github.com/sashamelentyev/usestdlibvars v1.28.0 // indirect
|
||||
github.com/securego/gosec/v2 v2.22.2 // indirect
|
||||
github.com/segmentio/asm v1.2.0 // indirect
|
||||
github.com/spf13/cobra v1.8.1 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/sergi/go-diff v1.2.0 // indirect
|
||||
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||
github.com/sivchari/containedctx v1.0.3 // indirect
|
||||
github.com/sonatard/noctx v0.1.0 // indirect
|
||||
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||
github.com/sourcegraph/go-diff v0.7.0 // indirect
|
||||
github.com/spf13/afero v1.14.0 // indirect
|
||||
github.com/spf13/cast v1.7.1 // indirect
|
||||
github.com/spf13/cobra v1.9.1 // indirect
|
||||
github.com/spf13/pflag v1.0.6 // indirect
|
||||
github.com/spf13/viper v1.20.0 // indirect
|
||||
github.com/ssgreg/nlreturn/v2 v2.2.1 // indirect
|
||||
github.com/stbenjam/no-sprintf-host-port v0.2.0 // indirect
|
||||
github.com/stoewer/go-strcase v1.3.0 // indirect
|
||||
github.com/stretchr/objx v0.5.2 // indirect
|
||||
github.com/subosito/gotenv v1.6.0 // indirect
|
||||
github.com/tdakkota/asciicheck v0.4.1 // indirect
|
||||
github.com/tetafro/godot v1.5.0 // indirect
|
||||
github.com/timakin/bodyclose v0.0.0-20241222091800-1db5c5ca4d67 // indirect
|
||||
github.com/timonwong/loggercheck v0.10.1 // indirect
|
||||
github.com/tomarrell/wrapcheck/v2 v2.10.0 // indirect
|
||||
github.com/tommy-muehle/go-mnd/v2 v2.5.1 // indirect
|
||||
github.com/ultraware/funlen v0.2.0 // indirect
|
||||
github.com/ultraware/whitespace v0.2.0 // indirect
|
||||
github.com/uudashr/gocognit v1.2.0 // indirect
|
||||
github.com/uudashr/iface v1.3.1 // indirect
|
||||
github.com/x448/float16 v0.8.4 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 // indirect
|
||||
go.opentelemetry.io/otel v1.28.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.28.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.28.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.28.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v1.3.1 // indirect
|
||||
github.com/xen0n/gosmopolitan v1.3.0 // indirect
|
||||
github.com/xlab/treeprint v1.2.0 // indirect
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
||||
github.com/yagipy/maintidx v1.0.0 // indirect
|
||||
github.com/yeya24/promlinter v0.3.0 // indirect
|
||||
github.com/ykadowak/zerologlint v0.1.5 // indirect
|
||||
gitlab.com/bosi/decorder v0.4.2 // indirect
|
||||
go-simpler.org/musttag v0.13.0 // indirect
|
||||
go-simpler.org/sloglint v0.9.0 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 // indirect
|
||||
go.opentelemetry.io/otel v1.34.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.34.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.34.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.34.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v1.5.0 // indirect
|
||||
go.uber.org/automaxprocs v1.6.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
golang.org/x/crypto v0.32.0 // indirect
|
||||
golang.org/x/net v0.34.0 // indirect
|
||||
golang.org/x/oauth2 v0.25.0 // indirect
|
||||
golang.org/x/sync v0.10.0 // indirect
|
||||
golang.org/x/sys v0.29.0 // indirect
|
||||
golang.org/x/term v0.28.0 // indirect
|
||||
golang.org/x/text v0.21.0 // indirect
|
||||
golang.org/x/time v0.9.0 // indirect
|
||||
golang.org/x/tools v0.26.0 // indirect
|
||||
golang.org/x/crypto v0.36.0 // indirect
|
||||
golang.org/x/exp/typeparams v0.0.0-20250305212735-054e65f0b394 // indirect
|
||||
golang.org/x/mod v0.24.0 // indirect
|
||||
golang.org/x/net v0.37.0 // indirect
|
||||
golang.org/x/oauth2 v0.26.0 // indirect
|
||||
golang.org/x/sync v0.12.0 // indirect
|
||||
golang.org/x/sys v0.31.0 // indirect
|
||||
golang.org/x/term v0.30.0 // indirect
|
||||
golang.org/x/text v0.23.0 // indirect
|
||||
golang.org/x/time v0.10.0 // indirect
|
||||
golang.org/x/tools v0.31.0 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect
|
||||
gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240826202546-f6391c0de4c7 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240826202546-f6391c0de4c7 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250127172529-29210b9bc287 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250219182151-9fdb1cabc7b2 // indirect
|
||||
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gotest.tools/gotestsum v1.12.0 // indirect
|
||||
honnef.co/go/tools v0.6.1 // indirect
|
||||
k8s.io/apiextensions-apiserver v0.32.1 // indirect
|
||||
k8s.io/apiserver v0.32.1 // indirect
|
||||
k8s.io/component-base v0.32.1 // indirect
|
||||
k8s.io/klog/v2 v2.130.1 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20241212222426-2c72e554b1e7 // indirect
|
||||
k8s.io/utils v0.0.0-20241210054802-24370beab758 // indirect
|
||||
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.0 // indirect
|
||||
mvdan.cc/gofumpt v0.7.0 // indirect
|
||||
mvdan.cc/unparam v0.0.0-20250301125049-0df0534333a4 // indirect
|
||||
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.1 // indirect
|
||||
sigs.k8s.io/controller-runtime/tools/setup-envtest v0.0.0-20250205174817-cd644c0ad54f // indirect
|
||||
sigs.k8s.io/controller-tools v0.17.2 // indirect
|
||||
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect
|
||||
sigs.k8s.io/kustomize/api v0.19.0 // indirect
|
||||
sigs.k8s.io/kustomize/cmd/config v0.19.0 // indirect
|
||||
sigs.k8s.io/kustomize/kustomize/v5 v5.6.0 // indirect
|
||||
sigs.k8s.io/kustomize/kyaml v0.19.0 // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.5.0 // indirect
|
||||
sigs.k8s.io/yaml v1.4.0 // indirect
|
||||
)
|
||||
|
|
764
go.sum
764
go.sum
|
@ -1,50 +1,173 @@
|
|||
cel.dev/expr v0.18.0 h1:CJ6drgk+Hf96lkLikr4rFf19WrU0BOWEihyZnI2TAzo=
|
||||
cel.dev/expr v0.18.0/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw=
|
||||
4d63.com/gocheckcompilerdirectives v1.3.0 h1:Ew5y5CtcAAQeTVKUVFrE7EwHMrTO6BggtEj8BZSjZ3A=
|
||||
4d63.com/gocheckcompilerdirectives v1.3.0/go.mod h1:ofsJ4zx2QAuIP/NO/NAh1ig6R1Fb18/GI7RVMwz7kAY=
|
||||
4d63.com/gochecknoglobals v0.2.2 h1:H1vdnwnMaZdQW/N+NrkT1SZMTBmcwHe9Vq8lJcYYTtU=
|
||||
4d63.com/gochecknoglobals v0.2.2/go.mod h1:lLxwTQjL5eIesRbvnzIP3jZtG140FnTdz+AlMa+ogt0=
|
||||
cel.dev/expr v0.19.2 h1:V354PbqIXr9IQdwy4SYA4xa0HXaWq1BUPAGzugBY5V4=
|
||||
cel.dev/expr v0.19.2/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw=
|
||||
github.com/4meepo/tagalign v1.4.2 h1:0hcLHPGMjDyM1gHG58cS73aQF8J4TdVR96TZViorO9E=
|
||||
github.com/4meepo/tagalign v1.4.2/go.mod h1:+p4aMyFM+ra7nb41CnFG6aSDXqRxU/w1VQqScKqDARI=
|
||||
github.com/Abirdcfly/dupword v0.1.3 h1:9Pa1NuAsZvpFPi9Pqkd93I7LIYRURj+A//dFd5tgBeE=
|
||||
github.com/Abirdcfly/dupword v0.1.3/go.mod h1:8VbB2t7e10KRNdwTVoxdBaxla6avbhGzb8sCTygUMhw=
|
||||
github.com/Antonboom/errname v1.1.0 h1:A+ucvdpMwlo/myWrkHEUEBWc/xuXdud23S8tmTb/oAE=
|
||||
github.com/Antonboom/errname v1.1.0/go.mod h1:O1NMrzgUcVBGIfi3xlVuvX8Q/VP/73sseCaAppfjqZw=
|
||||
github.com/Antonboom/nilnil v1.1.0 h1:jGxJxjgYS3VUUtOTNk8Z1icwT5ESpLH/426fjmQG+ng=
|
||||
github.com/Antonboom/nilnil v1.1.0/go.mod h1:b7sAlogQjFa1wV8jUW3o4PMzDVFLbTux+xnQdvzdcIE=
|
||||
github.com/Antonboom/testifylint v1.6.0 h1:6rdILVPt4+rqcvhid8w9wJNynKLUgqHNpFyM67UeXyc=
|
||||
github.com/Antonboom/testifylint v1.6.0/go.mod h1:k+nEkathI2NFjKO6HvwmSrbzUcQ6FAnbZV+ZRrnXPLI=
|
||||
github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=
|
||||
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
||||
github.com/Crocmagnon/fatcontext v0.7.1 h1:SC/VIbRRZQeQWj/TcQBS6JmrXcfA+BU4OGSVUt54PjM=
|
||||
github.com/Crocmagnon/fatcontext v0.7.1/go.mod h1:1wMvv3NXEBJucFGfwOJBxSVWcoIO6emV215SMkW9MFU=
|
||||
github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 h1:sHglBQTwgx+rWPdisA5ynNEsoARbiCBOyGcJM4/OzsM=
|
||||
github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24/go.mod h1:4UJr5HIiMZrwgkSPdsjy2uOQExX/WEILpIrO9UPGuXs=
|
||||
github.com/GaijinEntertainment/go-exhaustruct/v3 v3.3.1 h1:Sz1JIXEcSfhz7fUi7xHnhpIE0thVASYjvosApmHuD2k=
|
||||
github.com/GaijinEntertainment/go-exhaustruct/v3 v3.3.1/go.mod h1:n/LSCXNuIYqVfBlVXyHfMQkZDdp1/mmxfSjADd3z1Zg=
|
||||
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
|
||||
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
|
||||
github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
|
||||
github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
|
||||
github.com/Masterminds/semver/v3 v3.3.1 h1:QtNSWtVZ3nBfk8mAOu/B6v7FMJ+NHTIgUPi7rj+4nv4=
|
||||
github.com/Masterminds/semver/v3 v3.3.1/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
|
||||
github.com/Masterminds/sprig v2.22.0+incompatible h1:z4yfnGrZ7netVz+0EDJ0Wi+5VZCSYp4Z0m2dk6cEM60=
|
||||
github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o=
|
||||
github.com/OpenPeeDeeP/depguard/v2 v2.2.1 h1:vckeWVESWp6Qog7UZSARNqfu/cZqvki8zsuj3piCMx4=
|
||||
github.com/OpenPeeDeeP/depguard/v2 v2.2.1/go.mod h1:q4DKzC4UcVaAvcfd41CZh0PWpGgzrVxUYBlgKNGquUo=
|
||||
github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0=
|
||||
github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
|
||||
github.com/alecthomas/kong v1.6.0 h1:mwOzbdMR7uv2vul9J0FU3GYxE7ls/iX1ieMg5WIM6gE=
|
||||
github.com/alecthomas/kong v1.6.0/go.mod h1:p2vqieVMeTAnaC83txKtXe8FLke2X07aruPWXyMPQrU=
|
||||
github.com/alecthomas/go-check-sumtype v0.3.1 h1:u9aUvbGINJxLVXiFvHUlPEaD7VDULsrxJb4Aq31NLkU=
|
||||
github.com/alecthomas/go-check-sumtype v0.3.1/go.mod h1:A8TSiN3UPRw3laIgWEUOHHLPa6/r9MtoigdlP5h3K/E=
|
||||
github.com/alecthomas/kong v1.7.0 h1:MnT8+5JxFDCvISeI6vgd/mFbAJwueJ/pqQNzZMsiqZE=
|
||||
github.com/alecthomas/kong v1.7.0/go.mod h1:p2vqieVMeTAnaC83txKtXe8FLke2X07aruPWXyMPQrU=
|
||||
github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc=
|
||||
github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
|
||||
github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI=
|
||||
github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g=
|
||||
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a h1:idn718Q4B6AGu/h5Sxe66HYVdqdGu2l9Iebqhi/AEoA=
|
||||
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
|
||||
github.com/alexkohler/nakedret/v2 v2.0.5 h1:fP5qLgtwbx9EJE8dGEERT02YwS8En4r9nnZ71RK+EVU=
|
||||
github.com/alexkohler/nakedret/v2 v2.0.5/go.mod h1:bF5i0zF2Wo2o4X4USt9ntUWve6JbFv02Ff4vlkmS/VU=
|
||||
github.com/alexkohler/prealloc v1.0.0 h1:Hbq0/3fJPQhNkN0dR95AVrr6R7tou91y0uHG5pOcUuw=
|
||||
github.com/alexkohler/prealloc v1.0.0/go.mod h1:VetnK3dIgFBBKmg0YnD9F9x6Icjd+9cvfHR56wJVlKE=
|
||||
github.com/alingse/asasalint v0.0.11 h1:SFwnQXJ49Kx/1GghOFz1XGqHYKp21Kq1nHad/0WQRnw=
|
||||
github.com/alingse/asasalint v0.0.11/go.mod h1:nCaoMhw7a9kSJObvQyVzNTPBDbNpdocqrSP7t/cW5+I=
|
||||
github.com/alingse/nilnesserr v0.1.2 h1:Yf8Iwm3z2hUUrP4muWfW83DF4nE3r1xZ26fGWUKCZlo=
|
||||
github.com/alingse/nilnesserr v0.1.2/go.mod h1:1xJPrXonEtX7wyTq8Dytns5P2hNzoWymVUIaKm4HNFg=
|
||||
github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ=
|
||||
github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw=
|
||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so=
|
||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
|
||||
github.com/ashanbrown/forbidigo v1.6.0 h1:D3aewfM37Yb3pxHujIPSpTf6oQk9sc9WZi8gerOIVIY=
|
||||
github.com/ashanbrown/forbidigo v1.6.0/go.mod h1:Y8j9jy9ZYAEHXdu723cUlraTqbzjKF1MUyfOKL+AjcU=
|
||||
github.com/ashanbrown/makezero v1.2.0 h1:/2Lp1bypdmK9wDIq7uWBlDF1iMUpIIS4A+pF6C9IEUU=
|
||||
github.com/ashanbrown/makezero v1.2.0/go.mod h1:dxlPhHbDMC6N6xICzFBSK+4njQDdK8euNO0qjQMtGY4=
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/bitfield/gotestdox v0.2.2 h1:x6RcPAbBbErKLnapz1QeAlf3ospg8efBsedU93CDsnE=
|
||||
github.com/bitfield/gotestdox v0.2.2/go.mod h1:D+gwtS0urjBrzguAkTM2wodsTQYFHdpx8eqRJ3N+9pY=
|
||||
github.com/bkielbasa/cyclop v1.2.3 h1:faIVMIGDIANuGPWH031CZJTi2ymOQBULs9H21HSMa5w=
|
||||
github.com/bkielbasa/cyclop v1.2.3/go.mod h1:kHTwA9Q0uZqOADdupvcFJQtp/ksSnytRMe8ztxG8Fuo=
|
||||
github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM=
|
||||
github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ=
|
||||
github.com/blizzy78/varnamelen v0.8.0 h1:oqSblyuQvFsW1hbBHh1zfwrKe3kcSj0rnXkKzsQ089M=
|
||||
github.com/blizzy78/varnamelen v0.8.0/go.mod h1:V9TzQZ4fLJ1DSrjVDfl89H7aMnTvKkApdHeyESmyR7k=
|
||||
github.com/bombsimon/wsl/v4 v4.6.0 h1:ew2R/N42su553DKTYqt3HSxaQN+uHQPv4xZ2MBmwaW4=
|
||||
github.com/bombsimon/wsl/v4 v4.6.0/go.mod h1:uV/+6BkffuzSAVYD+yGyld1AChO7/EuLrCF/8xTiapg=
|
||||
github.com/breml/bidichk v0.3.3 h1:WSM67ztRusf1sMoqH6/c4OBCUlRVTKq+CbSeo0R17sE=
|
||||
github.com/breml/bidichk v0.3.3/go.mod h1:ISbsut8OnjB367j5NseXEGGgO/th206dVa427kR8YTE=
|
||||
github.com/breml/errchkjson v0.4.1 h1:keFSS8D7A2T0haP9kzZTi7o26r7kE3vymjZNeNDRDwg=
|
||||
github.com/breml/errchkjson v0.4.1/go.mod h1:a23OvR6Qvcl7DG/Z4o0el6BRAjKnaReoPQFciAl9U3s=
|
||||
github.com/butuzov/ireturn v0.3.1 h1:mFgbEI6m+9W8oP/oDdfA34dLisRFCj2G6o/yiI1yZrY=
|
||||
github.com/butuzov/ireturn v0.3.1/go.mod h1:ZfRp+E7eJLC0NQmk1Nrm1LOrn/gQlOykv+cVPdiXH5M=
|
||||
github.com/butuzov/mirror v1.3.0 h1:HdWCXzmwlQHdVhwvsfBb2Au0r3HyINry3bDWLYXiKoc=
|
||||
github.com/butuzov/mirror v1.3.0/go.mod h1:AEij0Z8YMALaq4yQj9CPPVYOyJQyiexpQEQgihajRfI=
|
||||
github.com/catenacyber/perfsprint v0.9.1 h1:5LlTp4RwTooQjJCvGEFV6XksZvWE7wCOUvjD2z0vls0=
|
||||
github.com/catenacyber/perfsprint v0.9.1/go.mod h1:q//VWC2fWbcdSLEY1R3l8n0zQCDPdE4IjZwyY1HMunM=
|
||||
github.com/ccojocar/zxcvbn-go v1.0.2 h1:na/czXU8RrhXO4EZme6eQJLR4PzcGsahsBOAwU6I3Vg=
|
||||
github.com/ccojocar/zxcvbn-go v1.0.2/go.mod h1:g1qkXtUSvHP8lhHp5GrSmTz6uWALGRMQdw6Qnz/hi60=
|
||||
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
|
||||
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
||||
github.com/census-instrumentation/opencensus-proto v0.4.1 h1:iKLQ0xPNFxR/2hzXZMrBo8f1j86j5WHzznCCQxV/b8g=
|
||||
github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cncf/xds/go v0.0.0-20240423153145-555b57ec207b h1:ga8SEFjZ60pxLcmhnThWgvH2wg8376yUJmPhEH4H3kw=
|
||||
github.com/cncf/xds/go v0.0.0-20240423153145-555b57ec207b/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/charithe/durationcheck v0.0.10 h1:wgw73BiocdBDQPik+zcEoBG/ob8uyBHf2iyoHGPf5w4=
|
||||
github.com/charithe/durationcheck v0.0.10/go.mod h1:bCWXb7gYRysD1CU3C+u4ceO49LoGOY1C1L6uouGNreQ=
|
||||
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4pZI35227imm7yK2bGPcfpFEmuY1gc2YSTShr4iJBfs=
|
||||
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc/go.mod h1:X4/0JoqgTIPSFcRA/P6INZzIuyqdFY5rm8tb41s9okk=
|
||||
github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY=
|
||||
github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30=
|
||||
github.com/charmbracelet/x/ansi v0.8.0 h1:9GTq3xq9caJW8ZrBTe0LIe2fvfLR/bYXKTx2llXn7xE=
|
||||
github.com/charmbracelet/x/ansi v0.8.0/go.mod h1:wdYl/ONOLHLIVmQaxbIYEC/cRKOQyjTkowiI4blgS9Q=
|
||||
github.com/charmbracelet/x/cellbuf v0.0.13 h1:/KBBKHuVRbq1lYx5BzEHBAFBP8VcQzJejZ/IA3iR28k=
|
||||
github.com/charmbracelet/x/cellbuf v0.0.13/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs=
|
||||
github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=
|
||||
github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg=
|
||||
github.com/chavacava/garif v0.1.0 h1:2JHa3hbYf5D9dsgseMKAmc/MZ109otzgNFk5s87H9Pc=
|
||||
github.com/chavacava/garif v0.1.0/go.mod h1:XMyYCkEL58DF0oyW4qDjjnPWONs2HBqYKI+UIPD+Gww=
|
||||
github.com/ckaznocha/intrange v0.3.1 h1:j1onQyXvHUsPWujDH6WIjhyH26gkRt/txNlV7LspvJs=
|
||||
github.com/ckaznocha/intrange v0.3.1/go.mod h1:QVepyz1AkUoFQkpEqksSYpNpUo3c5W7nWh/s6SHIJJk=
|
||||
github.com/cncf/xds/go v0.0.0-20250121191232-2f005788dc42 h1:Om6kYQYDUk5wWbT0t0q6pvyM49i9XZAv9dDrkDA7gjk=
|
||||
github.com/cncf/xds/go v0.0.0-20250121191232-2f005788dc42/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||
github.com/curioswitch/go-reassign v0.3.0 h1:dh3kpQHuADL3cobV/sSGETA8DOv457dwl+fbBAhrQPs=
|
||||
github.com/curioswitch/go-reassign v0.3.0/go.mod h1:nApPCCTtqLJN/s8HfItCcKV0jIPwluBOvZP+dsJGA88=
|
||||
github.com/daixiang0/gci v0.13.6 h1:RKuEOSkGpSadkGbvZ6hJ4ddItT3cVZ9Vn9Rybk6xjl8=
|
||||
github.com/daixiang0/gci v0.13.6/go.mod h1:12etP2OniiIdP4q+kjUGrC/rUagga7ODbqsom5Eo5Yk=
|
||||
github.com/dave/dst v0.27.3 h1:P1HPoMza3cMEquVf9kKy8yXsFirry4zEnWOdYPOoIzY=
|
||||
github.com/dave/dst v0.27.3/go.mod h1:jHh6EOibnHgcUW3WjKHisiooEkYwqpHLBSX1iOBhEyc=
|
||||
github.com/dave/jennifer v1.7.1 h1:B4jJJDHelWcDhlRQxWeo0Npa/pYKBLrirAQoTN45txo=
|
||||
github.com/dave/jennifer v1.7.1/go.mod h1:nXbxhEmQfOZhWml3D1cDK5M1FLnMSozpbFN/m3RmGZc=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 h1:rpfIENRNNilwHwZeG5+P150SMrnNEcHYvcCuK6dPZSg=
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0=
|
||||
github.com/denis-tingaikin/go-header v0.5.0 h1:SRdnP5ZKvcO9KKRP1KJrhFR3RrlGuD+42t4429eC9k8=
|
||||
github.com/denis-tingaikin/go-header v0.5.0/go.mod h1:mMenU5bWrok6Wl2UsZjy+1okegmwQ3UgWl4V1D8gjlY=
|
||||
github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI=
|
||||
github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/dnephin/pflag v1.0.7 h1:oxONGlWxhmUct0YzKTgrpQv9AUA1wtPBn7zuSjJqptk=
|
||||
github.com/dnephin/pflag v1.0.7/go.mod h1:uxE91IoWURlOiTUIA8Mq5ZZkAv3dPUfZNaT80Zm7OQE=
|
||||
github.com/elastic/crd-ref-docs v0.1.0 h1:Cr5kz89QB3Iuuj7dhAfLMApCrChEGAaIBTxGk/xuRKw=
|
||||
github.com/elastic/crd-ref-docs v0.1.0/go.mod h1:X83mMBdJt05heJUYiS3T0yJ/JkCuliuhSUNav5Gjo/U=
|
||||
github.com/emicklei/go-restful/v3 v3.12.1 h1:PJMDIM/ak7btuL8Ex0iYET9hxM3CI2sjZtzpL63nKAU=
|
||||
github.com/emicklei/go-restful/v3 v3.12.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
|
||||
github.com/envoyproxy/go-control-plane v0.13.1 h1:vPfJZCkob6yTMEgS+0TwfTUfbHjfy/6vOJ8hUWX/uXE=
|
||||
github.com/envoyproxy/go-control-plane v0.13.1/go.mod h1:X45hY0mufo6Fd0KW3rqsGvQMw58jvjymeCzBU3mWyHw=
|
||||
github.com/envoyproxy/protoc-gen-validate v1.1.0 h1:tntQDh69XqOCOZsDz0lVJQez/2L6Uu2PdjCQwWCJ3bM=
|
||||
github.com/envoyproxy/protoc-gen-validate v1.1.0/go.mod h1:sXRDRVmzEbkM7CVcM06s9shE/m23dg3wzjl0UWqJ2q4=
|
||||
github.com/envoyproxy/go-control-plane v0.13.4 h1:zEqyPVyku6IvWCFwux4x9RxkLOMUL+1vC9xUFv5l2/M=
|
||||
github.com/envoyproxy/go-control-plane v0.13.4/go.mod h1:kDfuBlDVsSj2MjrLEtRWtHlsWIFcGyB2RMO44Dc5GZA=
|
||||
github.com/envoyproxy/go-control-plane/envoy v1.32.4 h1:jb83lalDRZSpPWW2Z7Mck/8kXZ5CQAFYVjQcdVIr83A=
|
||||
github.com/envoyproxy/go-control-plane/envoy v1.32.4/go.mod h1:Gzjc5k8JcJswLjAx1Zm+wSYE20UrLtt7JZMWiWQXQEw=
|
||||
github.com/envoyproxy/go-control-plane/ratelimit v0.1.0 h1:/G9QYbddjL25KvtKTv3an9lx6VBE2cnb8wp1vEGNYGI=
|
||||
github.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJPzVVHnPgRKdUdwW/KdbRt94AzgRee4=
|
||||
github.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8=
|
||||
github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU=
|
||||
github.com/ettle/strcase v0.2.0 h1:fGNiVF21fHXpX1niBgk0aROov1LagYsOwV/xqKDKR/Q=
|
||||
github.com/ettle/strcase v0.2.0/go.mod h1:DajmHElDSaX76ITe3/VHVyMin4LWSJN5Z909Wp+ED1A=
|
||||
github.com/evanphx/json-patch v0.5.2 h1:xVCHIVMUu1wtM/VkR9jVZ45N3FhZfYMMYGorLCR8P3k=
|
||||
github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ=
|
||||
github.com/evanphx/json-patch/v5 v5.9.0 h1:kcBlZQbplgElYIlo/n1hJbls2z/1awpXxpRi0/FOJfg=
|
||||
github.com/evanphx/json-patch/v5 v5.9.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ=
|
||||
github.com/evanphx/json-patch/v5 v5.9.11 h1:/8HVnzMq13/3x9TPvjG08wUGqBTmZBsCWzjTM0wiaDU=
|
||||
github.com/evanphx/json-patch/v5 v5.9.11/go.mod h1:3j+LviiESTElxA4p3EMKAB9HXj3/XEtnUf6OZxqIQTM=
|
||||
github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=
|
||||
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
|
||||
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
|
||||
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
|
||||
github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4=
|
||||
github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94=
|
||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/firefart/nonamedreturns v1.0.5 h1:tM+Me2ZaXs8tfdDw3X6DOX++wMCOqzYUho6tUTYIdRA=
|
||||
github.com/firefart/nonamedreturns v1.0.5/go.mod h1:gHJjDqhGM4WyPt639SOZs+G89Ko7QKH5R5BhnO6xJhw=
|
||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
||||
github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M=
|
||||
github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
|
||||
github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
|
||||
github.com/fzipp/gocyclo v0.6.0 h1:lsblElZG7d3ALtGMx9fmxeTKZaLLpU8mET09yN4BBLo=
|
||||
github.com/fzipp/gocyclo v0.6.0/go.mod h1:rXPyn8fnlpa0R2csP/31uerbiVBugk5whMdlyaLkLoA=
|
||||
github.com/ghostiam/protogetter v0.3.12 h1:xTPjH97iKph27vXRRKV0OCke5sAMoHPbVeVstdzmCLE=
|
||||
github.com/ghostiam/protogetter v0.3.12/go.mod h1:WZ0nw9pfzsgxuRsPOFQomgDVSWtDLJRfQJEhsGbmQMA=
|
||||
github.com/go-critic/go-critic v0.13.0 h1:kJzM7wzltQasSUXtYyTl6UaPVySO6GkaR1thFnJ6afY=
|
||||
github.com/go-critic/go-critic v0.13.0/go.mod h1:M/YeuJ3vOCQDnP2SU+ZhjgRzwzcBW87JqLpMJLrZDLI=
|
||||
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
|
||||
github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
|
@ -58,58 +181,188 @@ github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF
|
|||
github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4=
|
||||
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
|
||||
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
|
||||
github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
|
||||
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
|
||||
github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
|
||||
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
|
||||
github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE=
|
||||
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
|
||||
github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI=
|
||||
github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow=
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
|
||||
github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA=
|
||||
github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||
github.com/go-toolsmith/astcast v1.1.0 h1:+JN9xZV1A+Re+95pgnMgDboWNVnIMMQXwfBwLRPgSC8=
|
||||
github.com/go-toolsmith/astcast v1.1.0/go.mod h1:qdcuFWeGGS2xX5bLM/c3U9lewg7+Zu4mr+xPwZIB4ZU=
|
||||
github.com/go-toolsmith/astcopy v1.1.0 h1:YGwBN0WM+ekI/6SS6+52zLDEf8Yvp3n2seZITCUBt5s=
|
||||
github.com/go-toolsmith/astcopy v1.1.0/go.mod h1:hXM6gan18VA1T/daUEHCFcYiW8Ai1tIwIzHY6srfEAw=
|
||||
github.com/go-toolsmith/astequal v1.0.3/go.mod h1:9Ai4UglvtR+4up+bAD4+hCj7iTo4m/OXVTSLnCyTAx4=
|
||||
github.com/go-toolsmith/astequal v1.1.0/go.mod h1:sedf7VIdCL22LD8qIvv7Nn9MuWJruQA/ysswh64lffQ=
|
||||
github.com/go-toolsmith/astequal v1.2.0 h1:3Fs3CYZ1k9Vo4FzFhwwewC3CHISHDnVUPC4x0bI2+Cw=
|
||||
github.com/go-toolsmith/astequal v1.2.0/go.mod h1:c8NZ3+kSFtFY/8lPso4v8LuJjdJiUFVnSuU3s0qrrDY=
|
||||
github.com/go-toolsmith/astfmt v1.1.0 h1:iJVPDPp6/7AaeLJEruMsBUlOYCmvg0MoCfJprsOmcco=
|
||||
github.com/go-toolsmith/astfmt v1.1.0/go.mod h1:OrcLlRwu0CuiIBp/8b5PYF9ktGVZUjlNMV634mhwuQ4=
|
||||
github.com/go-toolsmith/astp v1.1.0 h1:dXPuCl6u2llURjdPLLDxJeZInAeZ0/eZwFJmqZMnpQA=
|
||||
github.com/go-toolsmith/astp v1.1.0/go.mod h1:0T1xFGz9hicKs8Z5MfAqSUitoUYS30pDMsRVIDHs8CA=
|
||||
github.com/go-toolsmith/pkgload v1.2.2 h1:0CtmHq/02QhxcF7E9N5LIFcYFsMR5rdovfqTtRKkgIk=
|
||||
github.com/go-toolsmith/pkgload v1.2.2/go.mod h1:R2hxLNRKuAsiXCo2i5J6ZQPhnPMOVtU+f0arbFPWCus=
|
||||
github.com/go-toolsmith/strparse v1.0.0/go.mod h1:YI2nUKP9YGZnL/L1/DLFBfixrcjslWct4wyljWhSRy8=
|
||||
github.com/go-toolsmith/strparse v1.1.0 h1:GAioeZUK9TGxnLS+qfdqNbA4z0SSm5zVNtCQiyP2Bvw=
|
||||
github.com/go-toolsmith/strparse v1.1.0/go.mod h1:7ksGy58fsaQkGQlY8WVoBFNyEPMGuJin1rfoPS4lBSQ=
|
||||
github.com/go-toolsmith/typep v1.1.0 h1:fIRYDyF+JywLfqzyhdiHzRop/GQDxxNhLGQ6gFUNHus=
|
||||
github.com/go-toolsmith/typep v1.1.0/go.mod h1:fVIw+7zjdsMxDA3ITWnH1yOiw1rnTQKCsF/sk2H/qig=
|
||||
github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss=
|
||||
github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||
github.com/go-xmlfmt/xmlfmt v1.1.3 h1:t8Ey3Uy7jDSEisW2K3somuMKIpzktkWptA0iFCnRUWY=
|
||||
github.com/go-xmlfmt/xmlfmt v1.1.3/go.mod h1:aUCEOzzezBEjDBbFBoSiya/gduyIiWYRP6CnSFIV8AM=
|
||||
github.com/gobuffalo/flect v1.0.3 h1:xeWBM2nui+qnVvNM4S3foBhCAL2XgPU+a7FdpelbTq4=
|
||||
github.com/gobuffalo/flect v1.0.3/go.mod h1:A5msMlrHtLqh9umBSnvabjsMrCcCpAyzglnDvkbYKHs=
|
||||
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
|
||||
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
|
||||
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
||||
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||
github.com/goccy/go-yaml v1.11.3 h1:B3W9IdWbvrUu2OYQGwvU1nZtvMQJPBKgBUuweJjLj6I=
|
||||
github.com/goccy/go-yaml v1.11.3/go.mod h1:wKnAMd44+9JAAnGQpWVEgBzGt3YuTaQ4uXoHvE4m7WU=
|
||||
github.com/gofrs/flock v0.12.1 h1:MTLVXXHf8ekldpJk3AKicLij9MdwOWkZ+a/jHHZby9E=
|
||||
github.com/gofrs/flock v0.12.1/go.mod h1:9zxTsyu5xtJ9DK+1tFZyibEV7y3uwDxPPfbxeeHCoD0=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/golangci/dupl v0.0.0-20250308024227-f665c8d69b32 h1:WUvBfQL6EW/40l6OmeSBYQJNSif4O11+bmWEz+C7FYw=
|
||||
github.com/golangci/dupl v0.0.0-20250308024227-f665c8d69b32/go.mod h1:NUw9Zr2Sy7+HxzdjIULge71wI6yEg1lWQr7Evcu8K0E=
|
||||
github.com/golangci/go-printf-func-name v0.1.0 h1:dVokQP+NMTO7jwO4bwsRwLWeudOVUPPyAKJuzv8pEJU=
|
||||
github.com/golangci/go-printf-func-name v0.1.0/go.mod h1:wqhWFH5mUdJQhweRnldEywnR5021wTdZSNgwYceV14s=
|
||||
github.com/golangci/gofmt v0.0.0-20250106114630-d62b90e6713d h1:viFft9sS/dxoYY0aiOTsLKO2aZQAPT4nlQCsimGcSGE=
|
||||
github.com/golangci/gofmt v0.0.0-20250106114630-d62b90e6713d/go.mod h1:ivJ9QDg0XucIkmwhzCDsqcnxxlDStoTl89jDMIoNxKY=
|
||||
github.com/golangci/golangci-lint/v2 v2.0.1 h1:fNxppSstN0Rl5eDNRqMqdoH+/c2cr02zM8gEbhU12XQ=
|
||||
github.com/golangci/golangci-lint/v2 v2.0.1/go.mod h1:ptNNMeGBQrbves0Qq38xvfdJg18PzxmT+7KRCOpm6i8=
|
||||
github.com/golangci/golines v0.0.0-20250217232252-b35a6149b587 h1:RXtAfHDBWAv49/t94l3j9Iqvy6eXL/nm56EejqrZuQc=
|
||||
github.com/golangci/golines v0.0.0-20250217232252-b35a6149b587/go.mod h1:k9mmcyWKSTMcPPvQUCfRWWQ9VHJ1U9Dc0R7kaXAgtnQ=
|
||||
github.com/golangci/misspell v0.6.0 h1:JCle2HUTNWirNlDIAUO44hUsKhOFqGPoC4LZxlaSXDs=
|
||||
github.com/golangci/misspell v0.6.0/go.mod h1:keMNyY6R9isGaSAu+4Q8NMBwMPkh15Gtc8UCVoDtAWo=
|
||||
github.com/golangci/plugin-module-register v0.1.1 h1:TCmesur25LnyJkpsVrupv1Cdzo+2f7zX0H6Jkw1Ol6c=
|
||||
github.com/golangci/plugin-module-register v0.1.1/go.mod h1:TTpqoB6KkwOJMV8u7+NyXMrkwwESJLOkfl9TxR1DGFc=
|
||||
github.com/golangci/revgrep v0.8.0 h1:EZBctwbVd0aMeRnNUsFogoyayvKHyxlV3CdUA46FX2s=
|
||||
github.com/golangci/revgrep v0.8.0/go.mod h1:U4R/s9dlXZsg8uJmaR1GrloUr14D7qDl8gi2iPXJH8k=
|
||||
github.com/golangci/unconvert v0.0.0-20240309020433-c5143eacb3ed h1:IURFTjxeTfNFP0hTEi1YKjB/ub8zkpaOqFFMApi2EAs=
|
||||
github.com/golangci/unconvert v0.0.0-20240309020433-c5143eacb3ed/go.mod h1:XLXN8bNw4CGRPaqgl3bv/lhz7bsGPh4/xSaMTbo2vkQ=
|
||||
github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
|
||||
github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
|
||||
github.com/google/cel-go v0.22.0 h1:b3FJZxpiv1vTMo2/5RDUqAHPxkT8mmMfJIrq1llbf7g=
|
||||
github.com/google/cel-go v0.22.0/go.mod h1:BuznPXXfQDpXKWQ9sPW3TzlAJN5zzFe+i9tIs0yC4s8=
|
||||
github.com/google/gnostic-models v0.6.9 h1:MU/8wDLif2qCXZmzncUQ/BOfxWfthHi63KqpoNbWqVw=
|
||||
github.com/google/gnostic-models v0.6.9/go.mod h1:CiWsm0s6BSQd1hRn8/QmxqB6BesYcbSZxsz9b0KuDBw=
|
||||
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo=
|
||||
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
|
||||
github.com/google/pprof v0.0.0-20250202011525-fc3143867406 h1:wlQI2cYY0BsWmmPPAnxfQ8SDW0S3Jasn+4B8kXFxprg=
|
||||
github.com/google/pprof v0.0.0-20250202011525-fc3143867406/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k=
|
||||
github.com/gordonklaus/ineffassign v0.1.0 h1:y2Gd/9I7MdY1oEIt+n+rowjBNDcLQq3RsH5hwJd0f9s=
|
||||
github.com/gordonklaus/ineffassign v0.1.0/go.mod h1:Qcp2HIAYhR7mNUVSIxZww3Guk4it82ghYcEXIAk+QT0=
|
||||
github.com/gostaticanalysis/analysisutil v0.7.1 h1:ZMCjoue3DtDWQ5WyU16YbjbQEQ3VuzwxALrpYd+HeKk=
|
||||
github.com/gostaticanalysis/analysisutil v0.7.1/go.mod h1:v21E3hY37WKMGSnbsw2S/ojApNWb6C1//mXO48CXbVc=
|
||||
github.com/gostaticanalysis/comment v1.4.1/go.mod h1:ih6ZxzTHLdadaiSnF5WY3dxUoXfXAlTaRzuaNDlSado=
|
||||
github.com/gostaticanalysis/comment v1.4.2/go.mod h1:KLUTGDv6HOCotCH8h2erHKmpci2ZoR8VPu34YA2uzdM=
|
||||
github.com/gostaticanalysis/comment v1.5.0 h1:X82FLl+TswsUMpMh17srGRuKaaXprTaytmEpgnKIDu8=
|
||||
github.com/gostaticanalysis/comment v1.5.0/go.mod h1:V6eb3gpCv9GNVqb6amXzEUX3jXLVK/AdA+IrAMSqvEc=
|
||||
github.com/gostaticanalysis/forcetypeassert v0.2.0 h1:uSnWrrUEYDr86OCxWa4/Tp2jeYDlogZiZHzGkWFefTk=
|
||||
github.com/gostaticanalysis/forcetypeassert v0.2.0/go.mod h1:M5iPavzE9pPqWyeiVXSFghQjljW1+l/Uke3PXHS6ILY=
|
||||
github.com/gostaticanalysis/nilerr v0.1.1 h1:ThE+hJP0fEp4zWLkWHWcRyI2Od0p7DlgYG3Uqrmrcpk=
|
||||
github.com/gostaticanalysis/nilerr v0.1.1/go.mod h1:wZYb6YI5YAxxq0i1+VJbY0s2YONW0HU0GPE3+5PWN4A=
|
||||
github.com/gostaticanalysis/testutil v0.3.1-0.20210208050101-bfb5c8eec0e4/go.mod h1:D+FIZ+7OahH3ePw/izIEeH5I06eKs1IKI4Xr64/Am3M=
|
||||
github.com/gostaticanalysis/testutil v0.5.0 h1:Dq4wT1DdTwTGCQQv3rl3IvD5Ld0E6HiY+3Zh0sUGqw8=
|
||||
github.com/gostaticanalysis/testutil v0.5.0/go.mod h1:OLQSbuM6zw2EvCcXTz1lVq5unyoNft372msDY0nY5Hs=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.0 h1:VD1gqscl4nYs1YxVuSdemTrSgTKrwOWDK0FVFMqm+Cg=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.0/go.mod h1:4EgsQoS4TOhJizV+JTFg40qx1Ofh3XmXEQNBpgvNT40=
|
||||
github.com/hashicorp/go-immutable-radix/v2 v2.1.0 h1:CUW5RYIcysz+D3B+l1mDeXrQ7fUvGGCwJfdASSzbrfo=
|
||||
github.com/hashicorp/go-immutable-radix/v2 v2.1.0/go.mod h1:hgdqLXA4f6NIjRVisM1TJ9aOJVNRqKZj+xDGF6m7PBw=
|
||||
github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
|
||||
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||
github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY=
|
||||
github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
|
||||
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
|
||||
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
|
||||
github.com/huandu/xstrings v1.3.3 h1:/Gcsuc1x8JVbJ9/rlye4xZnVAbEkGauT8lbebqcQws4=
|
||||
github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
||||
github.com/imdario/mergo v0.3.11 h1:3tnifQM4i+fbajXKBHXWEH+KvNHqojZ778UH75j3bGA=
|
||||
github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||
github.com/jackc/pgx/v5 v5.7.1 h1:x7SYsPBYDkHDksogeSmZZ5xzThcTgRz++I5E+ePFUcs=
|
||||
github.com/jackc/pgx/v5 v5.7.1/go.mod h1:e7O26IywZZ+naJtWWos6i6fvWK+29etgITqrqHLfoZA=
|
||||
github.com/jackc/pgx/v5 v5.7.2 h1:mLoDLV6sonKlvjIEsV56SkWNCnuNv531l94GaIzO+XI=
|
||||
github.com/jackc/pgx/v5 v5.7.2/go.mod h1:ncY89UGWxg82EykZUwSpUKEfccBGGYq1xjrOpsbsfGQ=
|
||||
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
|
||||
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||
github.com/jgautheron/goconst v1.7.1 h1:VpdAG7Ca7yvvJk5n8dMwQhfEZJh95kl/Hl9S1OI5Jkk=
|
||||
github.com/jgautheron/goconst v1.7.1/go.mod h1:aAosetZ5zaeC/2EfMeRswtxUFBpe2Hr7HzkgX4fanO4=
|
||||
github.com/jingyugao/rowserrcheck v1.1.1 h1:zibz55j/MJtLsjP1OF4bSdgXxwL1b+Vn7Tjzq7gFzUs=
|
||||
github.com/jingyugao/rowserrcheck v1.1.1/go.mod h1:4yvlZSDb3IyDTUZJUmpZfm2Hwok+Dtp+nu2qOq+er9c=
|
||||
github.com/jjti/go-spancheck v0.6.4 h1:Tl7gQpYf4/TMU7AT84MN83/6PutY21Nb9fuQjFTpRRc=
|
||||
github.com/jjti/go-spancheck v0.6.4/go.mod h1:yAEYdKJ2lRkDA8g7X+oKUHXOWVAXSBJRv04OhF+QUjk=
|
||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/julz/importas v0.2.0 h1:y+MJN/UdL63QbFJHws9BVC5RpA2iq0kpjrFajTGivjQ=
|
||||
github.com/julz/importas v0.2.0/go.mod h1:pThlt589EnCYtMnmhmRYY/qn9lCf/frPOK+WMx3xiJY=
|
||||
github.com/karamaru-alpha/copyloopvar v1.2.1 h1:wmZaZYIjnJ0b5UoKDjUHrikcV0zuPyyxI4SVplLd2CI=
|
||||
github.com/karamaru-alpha/copyloopvar v1.2.1/go.mod h1:nFmMlFNlClC2BPvNaHMdkirmTJxVCY0lhxBtlfOypMM=
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
github.com/kisielk/errcheck v1.9.0 h1:9xt1zI9EBfcYBvdU1nVrzMzzUPUtPKs9bVSIM3TAb3M=
|
||||
github.com/kisielk/errcheck v1.9.0/go.mod h1:kQxWMMVZgIkDq7U8xtG/n2juOjbLgZtedi0D+/VL/i8=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/kkHAIKE/contextcheck v1.1.6 h1:7HIyRcnyzxL9Lz06NGhiKvenXq7Zw6Q0UQu/ttjfJCE=
|
||||
github.com/kkHAIKE/contextcheck v1.1.6/go.mod h1:3dDbMRNBFaq8HFXWC1JyvDSPm43CmE6IuHam8Wr0rkg=
|
||||
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
|
||||
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/kulti/thelper v0.6.3 h1:ElhKf+AlItIu+xGnI990no4cE2+XaSu1ULymV2Yulxs=
|
||||
github.com/kulti/thelper v0.6.3/go.mod h1:DsqKShOvP40epevkFrvIwkCMNYxMeTNjdWL4dqWHZ6I=
|
||||
github.com/kunwardeep/paralleltest v1.0.12 h1:U+p1ghipXMbnjZDJP1/s/S8C4/hJxEzApM2p2nsSqzA=
|
||||
github.com/kunwardeep/paralleltest v1.0.12/go.mod h1:newCbtCAOPBiKMJMKV3SWv6DZC2jnf2I8ZtlH8LjVxc=
|
||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
github.com/lasiar/canonicalheader v1.1.2 h1:vZ5uqwvDbyJCnMhmFYimgMZnJMjwljN5VGY0VKbMXb4=
|
||||
github.com/lasiar/canonicalheader v1.1.2/go.mod h1:qJCeLFS0G/QlLQ506T+Fk/fWMa2VmBUiEI2cuMK4djI=
|
||||
github.com/ldez/exptostd v0.4.2 h1:l5pOzHBz8mFOlbcifTxzfyYbgEmoUqjxLFHZkjlbHXs=
|
||||
github.com/ldez/exptostd v0.4.2/go.mod h1:iZBRYaUmcW5jwCR3KROEZ1KivQQp6PHXbDPk9hqJKCQ=
|
||||
github.com/ldez/gomoddirectives v0.6.1 h1:Z+PxGAY+217f/bSGjNZr/b2KTXcyYLgiWI6geMBN2Qc=
|
||||
github.com/ldez/gomoddirectives v0.6.1/go.mod h1:cVBiu3AHR9V31em9u2kwfMKD43ayN5/XDgr+cdaFaKs=
|
||||
github.com/ldez/grignotin v0.9.0 h1:MgOEmjZIVNn6p5wPaGp/0OKWyvq42KnzAt/DAb8O4Ow=
|
||||
github.com/ldez/grignotin v0.9.0/go.mod h1:uaVTr0SoZ1KBii33c47O1M8Jp3OP3YDwhZCmzT9GHEk=
|
||||
github.com/ldez/tagliatelle v0.7.1 h1:bTgKjjc2sQcsgPiT902+aadvMjCeMHrY7ly2XKFORIk=
|
||||
github.com/ldez/tagliatelle v0.7.1/go.mod h1:3zjxUpsNB2aEZScWiZTHrAXOl1x25t3cRmzfK1mlo2I=
|
||||
github.com/ldez/usetesting v0.4.2 h1:J2WwbrFGk3wx4cZwSMiCQQ00kjGR0+tuuyW0Lqm4lwA=
|
||||
github.com/ldez/usetesting v0.4.2/go.mod h1:eEs46T3PpQ+9RgN9VjpY6qWdiw2/QmfiDeWmdZdrjIQ=
|
||||
github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
|
||||
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
|
||||
github.com/leonklingele/grouper v1.1.2 h1:o1ARBDLOmmasUaNDesWqWCIFH3u7hoFlM84YrjT3mIY=
|
||||
github.com/leonklingele/grouper v1.1.2/go.mod h1:6D0M/HVkhs2yRKRFZUoGjeDy7EZTfFBE9gl4kjmIGkA=
|
||||
github.com/lestrrat-go/blackmagic v1.0.2 h1:Cg2gVSc9h7sz9NOByczrbUvLopQmXrfFx//N+AkAr5k=
|
||||
github.com/lestrrat-go/blackmagic v1.0.2/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU=
|
||||
github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE=
|
||||
|
@ -122,21 +375,81 @@ github.com/lestrrat-go/jwx/v2 v2.1.3 h1:Ud4lb2QuxRClYAmRleF50KrbKIoM1TddXgBrneT5
|
|||
github.com/lestrrat-go/jwx/v2 v2.1.3/go.mod h1:q6uFgbgZfEmQrfJfrCo90QcQOcXFMfbI/fO0NqRtvZo=
|
||||
github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU=
|
||||
github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||
github.com/macabu/inamedparam v0.2.0 h1:VyPYpOc10nkhI2qeNUdh3Zket4fcZjEWe35poddBCpE=
|
||||
github.com/macabu/inamedparam v0.2.0/go.mod h1:+Pee9/YfGe5LJ62pYXqB89lJ+0k5bsR8Wgz/C0Zlq3U=
|
||||
github.com/magefile/mage v1.15.0 h1:BvGheCMAsG3bWUDbZ8AyXXpCNwU9u5CB6sM+HNb9HYg=
|
||||
github.com/magefile/mage v1.15.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
|
||||
github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4=
|
||||
github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
|
||||
github.com/maratori/testableexamples v1.0.0 h1:dU5alXRrD8WKSjOUnmJZuzdxWOEQ57+7s93SLMxb2vI=
|
||||
github.com/maratori/testableexamples v1.0.0/go.mod h1:4rhjL1n20TUTT4vdh3RDqSizKLyXp7K2u6HgraZCGzE=
|
||||
github.com/maratori/testpackage v1.1.1 h1:S58XVV5AD7HADMmD0fNnziNHqKvSdDuEKdPD1rNTU04=
|
||||
github.com/maratori/testpackage v1.1.1/go.mod h1:s4gRK/ym6AMrqpOa/kEbQTV4Q4jb7WeLZzVhVVVOQMc=
|
||||
github.com/matoous/godox v1.1.0 h1:W5mqwbyWrwZv6OQ5Z1a/DHGMOvXYCBP3+Ht7KMoJhq4=
|
||||
github.com/matoous/godox v1.1.0/go.mod h1:jgE/3fUXiTurkdHOLT5WEkThTSuE7yxHv5iWPa80afs=
|
||||
github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE=
|
||||
github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
||||
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mgechev/revive v1.7.0 h1:JyeQ4yO5K8aZhIKf5rec56u0376h8AlKNQEmjfkjKlY=
|
||||
github.com/mgechev/revive v1.7.0/go.mod h1:qZnwcNhoguE58dfi96IJeSTPeZQejNeoMQLUZGi4SW4=
|
||||
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
|
||||
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
|
||||
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
|
||||
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 h1:n6/2gBQ3RWajuToeY6ZtZTIKv2v7ThUy5KKusIT0yc0=
|
||||
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4=
|
||||
github.com/moricho/tparallel v0.3.2 h1:odr8aZVFA3NZrNybggMkYO3rgPRcqjeQUlBBFVxKHTI=
|
||||
github.com/moricho/tparallel v0.3.2/go.mod h1:OQ+K3b4Ln3l2TZveGCywybl68glfLEwFGqvnjok8b+U=
|
||||
github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=
|
||||
github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/onsi/ginkgo/v2 v2.21.0 h1:7rg/4f3rB88pb5obDgNZrNHrQ4e6WpjonchcpuBRnZM=
|
||||
github.com/onsi/ginkgo/v2 v2.21.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo=
|
||||
github.com/onsi/gomega v1.35.1 h1:Cwbd75ZBPxFSuZ6T+rN/WCb/gOc6YgFBXLlZLhC7Ds4=
|
||||
github.com/onsi/gomega v1.35.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog=
|
||||
github.com/nakabonne/nestif v0.3.1 h1:wm28nZjhQY5HyYPx+weN3Q65k6ilSBxDb8v5S81B81U=
|
||||
github.com/nakabonne/nestif v0.3.1/go.mod h1:9EtoZochLn5iUprVDmDjqGKPofoUEBL8U4Ngq6aY7OE=
|
||||
github.com/nishanths/exhaustive v0.12.0 h1:vIY9sALmw6T/yxiASewa4TQcFsVYZQQRUQJhKRf3Swg=
|
||||
github.com/nishanths/exhaustive v0.12.0/go.mod h1:mEZ95wPIZW+x8kC4TgC+9YCUgiST7ecevsVDTgc2obs=
|
||||
github.com/nishanths/predeclared v0.2.2 h1:V2EPdZPliZymNAn79T8RkNApBjMmVKh5XRpLm/w98Vk=
|
||||
github.com/nishanths/predeclared v0.2.2/go.mod h1:RROzoN6TnGQupbC+lqggsOlcgysk3LMK/HI84Mp280c=
|
||||
github.com/nunnatsa/ginkgolinter v0.19.1 h1:mjwbOlDQxZi9Cal+KfbEJTCz327OLNfwNvoZ70NJ+c4=
|
||||
github.com/nunnatsa/ginkgolinter v0.19.1/go.mod h1:jkQ3naZDmxaZMXPWaS9rblH+i+GWXQCaS/JFIWcOH2s=
|
||||
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
|
||||
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
|
||||
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
|
||||
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
|
||||
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
|
||||
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
|
||||
github.com/onsi/ginkgo/v2 v2.22.2 h1:/3X8Panh8/WwhU/3Ssa6rCKqPLuAkVY2I0RoyDLySlU=
|
||||
github.com/onsi/ginkgo/v2 v2.22.2/go.mod h1:oeMosUL+8LtarXBHu/c0bx2D/K9zyQ6uX3cTyztHwsk=
|
||||
github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8=
|
||||
github.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY=
|
||||
github.com/otiai10/copy v1.2.0/go.mod h1:rrF5dJ5F0t/EWSYODDu4j9/vEeYHMkc8jt0zJChqQWw=
|
||||
github.com/otiai10/copy v1.14.0 h1:dCI/t1iTdYGtkvCuBG2BgR6KZa83PTclw4U5n2wAllU=
|
||||
github.com/otiai10/copy v1.14.0/go.mod h1:ECfuL02W+/FkTWZWgQqXPWZgW9oeKCSQ5qVfSc4qc4w=
|
||||
github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE=
|
||||
github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs=
|
||||
github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo=
|
||||
github.com/otiai10/mint v1.3.1/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc=
|
||||
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
|
||||
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo=
|
||||
|
@ -144,56 +457,179 @@ github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1
|
|||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y=
|
||||
github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=
|
||||
github.com/polyfloyd/go-errorlint v1.7.1 h1:RyLVXIbosq1gBdk/pChWA8zWYLsq9UEw7a1L5TVMCnA=
|
||||
github.com/polyfloyd/go-errorlint v1.7.1/go.mod h1:aXjNb1x2TNhoLsk26iv1yl7a+zTnXPhwEMtEXukiLR8=
|
||||
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
|
||||
github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
|
||||
github.com/prometheus/client_golang v1.21.1 h1:DOvXXTqVzvkIewV/CDPFdejpMCGeMcbGCQ8YOmu+Ibk=
|
||||
github.com/prometheus/client_golang v1.21.1/go.mod h1:U9NM32ykUErtVBxdvD3zfi+EuFkkaBvMb09mIfe0Zgg=
|
||||
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
|
||||
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
|
||||
github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io=
|
||||
github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I=
|
||||
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
|
||||
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
|
||||
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
|
||||
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
|
||||
github.com/prometheus/common v0.63.0 h1:YR/EIY1o3mEFP/kZCD7iDMnLPlGyuU2Gb3HIcXnA98k=
|
||||
github.com/prometheus/common v0.63.0/go.mod h1:VVFF/fBIoToEnWRVkYoXEkq3R3paCoxG9PXP74SnV18=
|
||||
github.com/prometheus/procfs v0.16.0 h1:xh6oHhKwnOJKMYiYBDWmkHqQPyiY40sny36Cmx2bbsM=
|
||||
github.com/prometheus/procfs v0.16.0/go.mod h1:8veyXUu3nGP7oaCxhX6yeaM5u4stL2FeMXnCqhDthZg=
|
||||
github.com/quasilyte/go-ruleguard v0.4.4 h1:53DncefIeLX3qEpjzlS1lyUmQoUEeOWPFWqaTJq9eAQ=
|
||||
github.com/quasilyte/go-ruleguard v0.4.4/go.mod h1:Vl05zJ538vcEEwu16V/Hdu7IYZWyKSwIy4c88Ro1kRE=
|
||||
github.com/quasilyte/go-ruleguard/dsl v0.3.22 h1:wd8zkOhSNr+I+8Qeciml08ivDt1pSXe60+5DqOpCjPE=
|
||||
github.com/quasilyte/go-ruleguard/dsl v0.3.22/go.mod h1:KeCP03KrjuSO0H1kTuZQCWlQPulDV6YMIXmpQss17rU=
|
||||
github.com/quasilyte/gogrep v0.5.0 h1:eTKODPXbI8ffJMN+W2aE0+oL0z/nh8/5eNdiO34SOAo=
|
||||
github.com/quasilyte/gogrep v0.5.0/go.mod h1:Cm9lpz9NZjEoL1tgZ2OgeUKPIxL1meE7eo60Z6Sk+Ng=
|
||||
github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 h1:TCg2WBOl980XxGFEZSS6KlBGIV0diGdySzxATTWoqaU=
|
||||
github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727/go.mod h1:rlzQ04UMyJXu/aOvhd8qT+hvDrFpiwqp8MRXDY9szc0=
|
||||
github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 h1:M8mH9eK4OUR4lu7Gd+PU1fV2/qnDNfzT635KRSObncs=
|
||||
github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567/go.mod h1:DWNGW8A4Y+GyBgPuaQJuWiy0XYftx4Xm/y5Jqk9I6VQ=
|
||||
github.com/raeperd/recvcheck v0.2.0 h1:GnU+NsbiCqdC2XX5+vMZzP+jAJC5fht7rcVTAhX74UI=
|
||||
github.com/raeperd/recvcheck v0.2.0/go.mod h1:n04eYkwIR0JbgD73wT8wL4JjPC3wm0nFtzBnWNocnYU=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
|
||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/ryancurrah/gomodguard v1.4.1 h1:eWC8eUMNZ/wM/PWuZBv7JxxqT5fiIKSIyTvjb7Elr+g=
|
||||
github.com/ryancurrah/gomodguard v1.4.1/go.mod h1:qnMJwV1hX9m+YJseXEBhd2s90+1Xn6x9dLz11ualI1I=
|
||||
github.com/ryanrolds/sqlclosecheck v0.5.1 h1:dibWW826u0P8jNLsLN+En7+RqWWTYrjCB9fJfSfdyCU=
|
||||
github.com/ryanrolds/sqlclosecheck v0.5.1/go.mod h1:2g3dUjoS6AL4huFdv6wn55WpLIDjY7ZgUR4J8HOO/XQ=
|
||||
github.com/sagikazarmark/locafero v0.8.0 h1:mXaMVw7IqxNBxfv3LdWt9MDmcWDQ1fagDH918lOdVaQ=
|
||||
github.com/sagikazarmark/locafero v0.8.0/go.mod h1:UBUyz37V+EdMS3hDF3QWIiVr/2dPrx49OMO0Bn0hJqk=
|
||||
github.com/sanposhiho/wastedassign/v2 v2.1.0 h1:crurBF7fJKIORrV85u9UUpePDYGWnwvv3+A96WvwXT0=
|
||||
github.com/sanposhiho/wastedassign/v2 v2.1.0/go.mod h1:+oSmSC+9bQ+VUAxA66nBb0Z7N8CK7mscKTDYC6aIek4=
|
||||
github.com/santhosh-tekuri/jsonschema/v6 v6.0.1 h1:PKK9DyHxif4LZo+uQSgXNqs0jj5+xZwwfKHgph2lxBw=
|
||||
github.com/santhosh-tekuri/jsonschema/v6 v6.0.1/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU=
|
||||
github.com/sashamelentyev/interfacebloat v1.1.0 h1:xdRdJp0irL086OyW1H/RTZTr1h/tMEOsumirXcOJqAw=
|
||||
github.com/sashamelentyev/interfacebloat v1.1.0/go.mod h1:+Y9yU5YdTkrNvoX0xHc84dxiN1iBi9+G8zZIhPVoNjQ=
|
||||
github.com/sashamelentyev/usestdlibvars v1.28.0 h1:jZnudE2zKCtYlGzLVreNp5pmCdOxXUzwsMDBkR21cyQ=
|
||||
github.com/sashamelentyev/usestdlibvars v1.28.0/go.mod h1:9nl0jgOfHKWNFS43Ojw0i7aRoS4j6EBye3YBhmAIRF8=
|
||||
github.com/securego/gosec/v2 v2.22.2 h1:IXbuI7cJninj0nRpZSLCUlotsj8jGusohfONMrHoF6g=
|
||||
github.com/securego/gosec/v2 v2.22.2/go.mod h1:UEBGA+dSKb+VqM6TdehR7lnQtIIMorYJ4/9CW1KVQBE=
|
||||
github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys=
|
||||
github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs=
|
||||
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
|
||||
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ=
|
||||
github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
||||
github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=
|
||||
github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ=
|
||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/sivchari/containedctx v1.0.3 h1:x+etemjbsh2fB5ewm5FeLNi5bUjK0V8n0RB+Wwfd0XE=
|
||||
github.com/sivchari/containedctx v1.0.3/go.mod h1:c1RDvCbnJLtH4lLcYD/GqwiBSSf4F5Qk0xld2rBqzJ4=
|
||||
github.com/sonatard/noctx v0.1.0 h1:JjqOc2WN16ISWAjAk8M5ej0RfExEXtkEyExl2hLW+OM=
|
||||
github.com/sonatard/noctx v0.1.0/go.mod h1:0RvBxqY8D4j9cTTTWE8ylt2vqj2EPI8fHmrxHdsaZ2c=
|
||||
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
|
||||
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
|
||||
github.com/sourcegraph/go-diff v0.7.0 h1:9uLlrd5T46OXs5qpp8L/MTltk0zikUGi0sNNyCpA8G0=
|
||||
github.com/sourcegraph/go-diff v0.7.0/go.mod h1:iBszgVvyxdc8SFZ7gm69go2KDdt3ag071iBaWPF6cjs=
|
||||
github.com/spf13/afero v1.14.0 h1:9tH6MapGnn/j0eb0yIXiLjERO8RB6xIVZRDCX7PtqWA=
|
||||
github.com/spf13/afero v1.14.0/go.mod h1:acJQ8t0ohCGuMN3O+Pv0V0hgMxNYDlvdk+VTfyZmbYo=
|
||||
github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y=
|
||||
github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
||||
github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
|
||||
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
|
||||
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.20.0 h1:zrxIyR3RQIOsarIrgL8+sAvALXul9jeEPa06Y0Ph6vY=
|
||||
github.com/spf13/viper v1.20.0/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4=
|
||||
github.com/ssgreg/nlreturn/v2 v2.2.1 h1:X4XDI7jstt3ySqGU86YGAURbxw3oTDPK9sPEi6YEwQ0=
|
||||
github.com/ssgreg/nlreturn/v2 v2.2.1/go.mod h1:E/iiPB78hV7Szg2YfRgyIrk1AD6JVMTRkkxBiELzh2I=
|
||||
github.com/stbenjam/no-sprintf-host-port v0.2.0 h1:i8pxvGrt1+4G0czLr/WnmyH7zbZ8Bg8etvARQ1rpyl4=
|
||||
github.com/stbenjam/no-sprintf-host-port v0.2.0/go.mod h1:eL0bQ9PasS0hsyTyfTjjG+E80QIyPnBVQbYZyv20Jfk=
|
||||
github.com/stoewer/go-strcase v1.3.0 h1:g0eASXYtp+yvN9fK8sH94oCIk0fau9uV1/ZdJ0AVEzs=
|
||||
github.com/stoewer/go-strcase v1.3.0/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
||||
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
||||
github.com/tdakkota/asciicheck v0.4.1 h1:bm0tbcmi0jezRA2b5kg4ozmMuGAFotKI3RZfrhfovg8=
|
||||
github.com/tdakkota/asciicheck v0.4.1/go.mod h1:0k7M3rCfRXb0Z6bwgvkEIMleKH3kXNz9UqJ9Xuqopr8=
|
||||
github.com/tenntenn/modver v1.0.1 h1:2klLppGhDgzJrScMpkj9Ujy3rXPUspSjAcev9tSEBgA=
|
||||
github.com/tenntenn/modver v1.0.1/go.mod h1:bePIyQPb7UeioSRkw3Q0XeMhYZSMx9B8ePqg6SAMGH0=
|
||||
github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3 h1:f+jULpRQGxTSkNYKJ51yaw6ChIqO+Je8UqsTKN/cDag=
|
||||
github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3/go.mod h1:ON8b8w4BN/kE1EOhwT0o+d62W65a6aPw1nouo9LMgyY=
|
||||
github.com/tetafro/godot v1.5.0 h1:aNwfVI4I3+gdxjMgYPus9eHmoBeJIbnajOyqZYStzuw=
|
||||
github.com/tetafro/godot v1.5.0/go.mod h1:2oVxTBSftRTh4+MVfUaUXR6bn2GDXCaMcOG4Dk3rfio=
|
||||
github.com/timakin/bodyclose v0.0.0-20241222091800-1db5c5ca4d67 h1:9LPGD+jzxMlnk5r6+hJnar67cgpDIz/iyD+rfl5r2Vk=
|
||||
github.com/timakin/bodyclose v0.0.0-20241222091800-1db5c5ca4d67/go.mod h1:mkjARE7Yr8qU23YcGMSALbIxTQ9r9QBVahQOBRfU460=
|
||||
github.com/timonwong/loggercheck v0.10.1 h1:uVZYClxQFpw55eh+PIoqM7uAOHMrhVcDoWDery9R8Lg=
|
||||
github.com/timonwong/loggercheck v0.10.1/go.mod h1:HEAWU8djynujaAVX7QI65Myb8qgfcZ1uKbdpg3ZzKl8=
|
||||
github.com/tomarrell/wrapcheck/v2 v2.10.0 h1:SzRCryzy4IrAH7bVGG4cK40tNUhmVmMDuJujy4XwYDg=
|
||||
github.com/tomarrell/wrapcheck/v2 v2.10.0/go.mod h1:g9vNIyhb5/9TQgumxQyOEqDHsmGYcGsVMOx/xGkqdMo=
|
||||
github.com/tommy-muehle/go-mnd/v2 v2.5.1 h1:NowYhSdyE/1zwK9QCLeRb6USWdoif80Ie+v+yU8u1Zw=
|
||||
github.com/tommy-muehle/go-mnd/v2 v2.5.1/go.mod h1:WsUAkMJMYww6l/ufffCD3m+P7LEvr8TnZn9lwVDlgzw=
|
||||
github.com/ultraware/funlen v0.2.0 h1:gCHmCn+d2/1SemTdYMiKLAHFYxTYz7z9VIDRaTGyLkI=
|
||||
github.com/ultraware/funlen v0.2.0/go.mod h1:ZE0q4TsJ8T1SQcjmkhN/w+MceuatI6pBFSxxyteHIJA=
|
||||
github.com/ultraware/whitespace v0.2.0 h1:TYowo2m9Nfj1baEQBjuHzvMRbp19i+RCcRYrSWoFa+g=
|
||||
github.com/ultraware/whitespace v0.2.0/go.mod h1:XcP1RLD81eV4BW8UhQlpaR+SDc2givTvyI8a586WjW8=
|
||||
github.com/uudashr/gocognit v1.2.0 h1:3BU9aMr1xbhPlvJLSydKwdLN3tEUUrzPSSM8S4hDYRA=
|
||||
github.com/uudashr/gocognit v1.2.0/go.mod h1:k/DdKPI6XBZO1q7HgoV2juESI2/Ofj9AcHPZhBBdrTU=
|
||||
github.com/uudashr/iface v1.3.1 h1:bA51vmVx1UIhiIsQFSNq6GZ6VPTk3WNMZgRiCe9R29U=
|
||||
github.com/uudashr/iface v1.3.1/go.mod h1:4QvspiRd3JLPAEXBQ9AiZpLbJlrWWgRChOKDJEuQTdg=
|
||||
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
||||
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
|
||||
github.com/xen0n/gosmopolitan v1.3.0 h1:zAZI1zefvo7gcpbCOrPSHJZJYA9ZgLfJqtKzZ5pHqQM=
|
||||
github.com/xen0n/gosmopolitan v1.3.0/go.mod h1:rckfr5T6o4lBtM1ga7mLGKZmLxswUoH1zxHgNXOsEt4=
|
||||
github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ=
|
||||
github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0=
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
|
||||
github.com/yagipy/maintidx v1.0.0 h1:h5NvIsCz+nRDapQ0exNv4aJ0yXSI0420omVANTv3GJM=
|
||||
github.com/yagipy/maintidx v1.0.0/go.mod h1:0qNf/I/CCZXSMhsRsrEPDZ+DkekpKLXAJfsTACwgXLk=
|
||||
github.com/yeya24/promlinter v0.3.0 h1:JVDbMp08lVCP7Y6NP3qHroGAO6z2yGKQtS5JsjqtoFs=
|
||||
github.com/yeya24/promlinter v0.3.0/go.mod h1:cDfJQQYv9uYciW60QT0eeHlFodotkYZlL+YcPQN+mW4=
|
||||
github.com/ykadowak/zerologlint v0.1.5 h1:Gy/fMz1dFQN9JZTPjv1hxEk+sRWm05row04Yoolgdiw=
|
||||
github.com/ykadowak/zerologlint v0.1.5/go.mod h1:KaUskqF3e/v59oPmdq1U1DnKcuHokl2/K1U4pmIELKg=
|
||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 h1:4K4tsIXefpVJtvA/8srF4V4y0akAoPHkIslgAkjixJA=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0/go.mod h1:jjdQuTGVsXV4vSs+CJ2qYDeDPf9yIJV23qlIzBm73Vg=
|
||||
go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo=
|
||||
go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 h1:3Q/xZUyC1BBkualc9ROb4G8qkH90LXEIICcs5zv1OYY=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0/go.mod h1:s75jGIWA9OfCMzF0xr+ZgfrB5FEbbV7UuYo32ahUiFI=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0 h1:qFffATk0X+HD+f1Z8lswGiOQYKHRlzfmdJm0wEaVrFA=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0/go.mod h1:MOiCmryaYtc+V0Ei+Tx9o5S1ZjA7kzLucuVuyzBZloQ=
|
||||
go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q=
|
||||
go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s=
|
||||
go.opentelemetry.io/otel/sdk v1.28.0 h1:b9d7hIry8yZsgtbmM0DKyPWMMUMlK9NEKuIG4aBqWyE=
|
||||
go.opentelemetry.io/otel/sdk v1.28.0/go.mod h1:oYj7ClPUA7Iw3m+r7GeEjz0qckQRJK2B8zjcZEfu7Pg=
|
||||
go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g=
|
||||
go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI=
|
||||
go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0=
|
||||
go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
gitlab.com/bosi/decorder v0.4.2 h1:qbQaV3zgwnBZ4zPMhGLW4KZe7A7NwxEhJx39R3shffo=
|
||||
gitlab.com/bosi/decorder v0.4.2/go.mod h1:muuhHoaJkA9QLcYHq4Mj8FJUwDZ+EirSHRiaTcTf6T8=
|
||||
go-simpler.org/assert v0.9.0 h1:PfpmcSvL7yAnWyChSjOz6Sp6m9j5lyK8Ok9pEL31YkQ=
|
||||
go-simpler.org/assert v0.9.0/go.mod h1:74Eqh5eI6vCK6Y5l3PI8ZYFXG4Sa+tkr70OIPJAUr28=
|
||||
go-simpler.org/musttag v0.13.0 h1:Q/YAW0AHvaoaIbsPj3bvEI5/QFP7w696IMUpnKXQfCE=
|
||||
go-simpler.org/musttag v0.13.0/go.mod h1:FTzIGeK6OkKlUDVpj0iQUXZLUO1Js9+mvykDQy9C5yM=
|
||||
go-simpler.org/sloglint v0.9.0 h1:/40NQtjRx9txvsB/RN022KsUJU+zaaSb/9q9BSefSrE=
|
||||
go-simpler.org/sloglint v0.9.0/go.mod h1:G/OrAF6uxj48sHahCzrbarVMptL2kjWTaUeC8+fOGww=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 h1:CV7UdSGJt/Ao6Gp4CXckLxVRRsRgDHoI8XjbL3PDl8s=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0/go.mod h1:FRmFuRJfag1IZ2dPkHnEoSFVgTVPUd2qf5Vi69hLb8I=
|
||||
go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY=
|
||||
go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 h1:OeNbIYk/2C15ckl7glBlOBp5+WlYsOElzTNmiPW/x60=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0/go.mod h1:7Bept48yIeqxP2OZ9/AqIpYS94h2or0aB4FypJTc8ZM=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0 h1:tgJ0uaNS4c98WRNUEx5U3aDlrDOI5Rs+1Vifcw4DJ8U=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0/go.mod h1:U7HYyW0zt/a9x5J1Kjs+r1f/d4ZHnYFclhYY2+YbeoE=
|
||||
go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ=
|
||||
go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE=
|
||||
go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A=
|
||||
go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.32.0 h1:rZvFnvmvawYb0alrYkjraqJq0Z4ZUJAiyYCU9snn1CU=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.32.0/go.mod h1:PWeZlq0zt9YkYAp3gjKZ0eicRYvOh1Gd+X99x6GHpCQ=
|
||||
go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k=
|
||||
go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE=
|
||||
go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4=
|
||||
go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4=
|
||||
go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
|
||||
go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||
|
@ -203,68 +639,198 @@ go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
|||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
|
||||
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
|
||||
golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
|
||||
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
||||
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
|
||||
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
|
||||
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
|
||||
golang.org/x/exp v0.0.0-20250128182459-e0ece0dbea4c h1:KL/ZBHXgKGVmuZBZ01Lt57yE5ws8ZPSkkihmEyq7FXc=
|
||||
golang.org/x/exp v0.0.0-20250128182459-e0ece0dbea4c/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU=
|
||||
golang.org/x/exp/typeparams v0.0.0-20220428152302-39d4317da171/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
|
||||
golang.org/x/exp/typeparams v0.0.0-20230203172020-98cc5a0785f9/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
|
||||
golang.org/x/exp/typeparams v0.0.0-20250305212735-054e65f0b394 h1:VI4qDpTkfFaCXEPrbojidLgVQhj2x4nzTccG0hjaLlU=
|
||||
golang.org/x/exp/typeparams v0.0.0-20250305212735-054e65f0b394/go.mod h1:LKZHyeOpPuZcMgxeHjJp4p5yvxrCX1xDvH10zYHhjjQ=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI=
|
||||
golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
|
||||
golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
|
||||
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
|
||||
golang.org/x/oauth2 v0.25.0 h1:CY4y7XT9v0cRI9oupztF8AgiIu99L/ksR/Xp/6jrZ70=
|
||||
golang.org/x/oauth2 v0.25.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
|
||||
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
|
||||
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
||||
golang.org/x/net v0.16.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
|
||||
golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c=
|
||||
golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
|
||||
golang.org/x/oauth2 v0.26.0 h1:afQXWNNaeC4nvZ0Ed9XvCCzXM6UHJG7iCg0W4fPqSBE=
|
||||
golang.org/x/oauth2 v0.26.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
|
||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
|
||||
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
|
||||
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg=
|
||||
golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211105183446-c75c47738b0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
|
||||
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o=
|
||||
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
|
||||
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
|
||||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
|
||||
golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y=
|
||||
golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY=
|
||||
golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
|
||||
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
|
||||
golang.org/x/time v0.10.0 h1:3usCWA8tQn0L8+hFJQNgzpWbd89begxN66o1Ojdn5L4=
|
||||
golang.org/x/time v0.10.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200324003944-a576cf524670/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
|
||||
golang.org/x/tools v0.0.0-20200329025819-fd4102a86c65/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200724022722-7017fd6b1305/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200820010801-b793a1359eac/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20201023174141-c8cfbd0f21e6/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ=
|
||||
golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0=
|
||||
golang.org/x/tools v0.1.1-0.20210205202024-ef80cdb6ec6d/go.mod h1:9bzcO0MWcOuT0tm1iBGzDVPshzfwoVvREIui8C+MHqU=
|
||||
golang.org/x/tools v0.1.1-0.20210302220138-2ac05c832e1a/go.mod h1:9bzcO0MWcOuT0tm1iBGzDVPshzfwoVvREIui8C+MHqU=
|
||||
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA=
|
||||
golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s=
|
||||
golang.org/x/tools v0.11.0/go.mod h1:anzJrxPjNtfgiYQYirP2CPGzGLxrH2u2QBhn6Bf3qY8=
|
||||
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
||||
golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg=
|
||||
golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc=
|
||||
golang.org/x/tools v0.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU=
|
||||
golang.org/x/tools v0.31.0/go.mod h1:naFTU+Cev749tSJRXJlna0T3WxKvb1kWEx15xA4SdmQ=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU=
|
||||
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90=
|
||||
gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw=
|
||||
gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240826202546-f6391c0de4c7 h1:YcyjlL1PRr2Q17/I0dPk2JmYS5CDXfcdb2Z3YRioEbw=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240826202546-f6391c0de4c7/go.mod h1:OCdP9MfskevB/rbYvHTsXTtKC+3bHWajPdoKgjcYkfo=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240826202546-f6391c0de4c7 h1:2035KHhUv+EpyB+hWgJnaWKJOdX1E95w2S8Rr4uWKTs=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240826202546-f6391c0de4c7/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU=
|
||||
google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc=
|
||||
google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ=
|
||||
google.golang.org/protobuf v1.36.3 h1:82DV7MYdb8anAVi3qge1wSnMDrnKK7ebr+I0hHRN1BU=
|
||||
google.golang.org/protobuf v1.36.3/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250127172529-29210b9bc287 h1:A2ni10G3UlplFrWdCDJTl7D7mJ7GSRm37S+PDimaKRw=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250127172529-29210b9bc287/go.mod h1:iYONQfRdizDB8JJBybql13nArx91jcUk7zCXEsOofM4=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250219182151-9fdb1cabc7b2 h1:DMTIbak9GhdaSxEjvVzAeNZvyc03I61duqNbnm3SU0M=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250219182151-9fdb1cabc7b2/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I=
|
||||
google.golang.org/grpc v1.70.0 h1:pWFv03aZoHzlRKHWicjsZytKAiYCtNS0dHbXnIdq7jQ=
|
||||
google.golang.org/grpc v1.70.0/go.mod h1:ofIJqVKDXx/JiXrwr2IG4/zwdH9txy3IlF40RmcJSQw=
|
||||
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
|
||||
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4=
|
||||
gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M=
|
||||
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
|
||||
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gotest.tools/gotestsum v1.12.0 h1:CmwtaGDkHxrZm4Ib0Vob89MTfpc3GrEFMJKovliPwGk=
|
||||
gotest.tools/gotestsum v1.12.0/go.mod h1:fAvqkSptospfSbQw26CTYzNwnsE/ztqLeyhP0h67ARY=
|
||||
gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU=
|
||||
gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU=
|
||||
honnef.co/go/tools v0.6.1 h1:R094WgE8K4JirYjBaOpz/AvTyUu/3wbmAoskKN/pxTI=
|
||||
honnef.co/go/tools v0.6.1/go.mod h1:3puzxxljPCe8RGJX7BIy1plGbxEOZni5mR2aXe3/uk4=
|
||||
k8s.io/api v0.32.1 h1:f562zw9cy+GvXzXf0CKlVQ7yHJVYzLfL6JAS4kOAaOc=
|
||||
k8s.io/api v0.32.1/go.mod h1:/Yi/BqkuueW1BgpoePYBRdDYfjPF5sgTr5+YqDZra5k=
|
||||
k8s.io/apiextensions-apiserver v0.32.1 h1:hjkALhRUeCariC8DiVmb5jj0VjIc1N0DREP32+6UXZw=
|
||||
|
@ -283,12 +849,28 @@ k8s.io/kube-openapi v0.0.0-20241212222426-2c72e554b1e7 h1:hcha5B1kVACrLujCKLbr8X
|
|||
k8s.io/kube-openapi v0.0.0-20241212222426-2c72e554b1e7/go.mod h1:GewRfANuJ70iYzvn+i4lezLDAFzvjxZYK1gn1lWcfas=
|
||||
k8s.io/utils v0.0.0-20241210054802-24370beab758 h1:sdbE21q2nlQtFh65saZY+rRM6x6aJJI8IUa1AmH/qa0=
|
||||
k8s.io/utils v0.0.0-20241210054802-24370beab758/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
|
||||
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.0 h1:CPT0ExVicCzcpeN4baWEV2ko2Z/AsiZgEdwgcfwLgMo=
|
||||
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.0/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw=
|
||||
sigs.k8s.io/controller-runtime v0.20.0 h1:jjkMo29xEXH+02Md9qaVXfEIaMESSpy3TBWPrsfQkQs=
|
||||
sigs.k8s.io/controller-runtime v0.20.0/go.mod h1:BrP3w158MwvB3ZbNpaAcIKkHQ7YGpYnzpoSTZ8E14WU=
|
||||
mvdan.cc/gofumpt v0.7.0 h1:bg91ttqXmi9y2xawvkuMXyvAA/1ZGJqYAEGjXuP0JXU=
|
||||
mvdan.cc/gofumpt v0.7.0/go.mod h1:txVFJy/Sc/mvaycET54pV8SW8gWxTlUuGHVEcncmNUo=
|
||||
mvdan.cc/unparam v0.0.0-20250301125049-0df0534333a4 h1:WjUu4yQoT5BHT1w8Zu56SP8367OuBV5jvo+4Ulppyf8=
|
||||
mvdan.cc/unparam v0.0.0-20250301125049-0df0534333a4/go.mod h1:rthT7OuvRbaGcd5ginj6dA2oLE7YNlta9qhBNNdCaLE=
|
||||
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.1 h1:uOuSLOMBWkJH0TWa9X6l+mj5nZdm6Ay6Bli8HL8rNfk=
|
||||
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.1/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw=
|
||||
sigs.k8s.io/controller-runtime v0.20.1 h1:JbGMAG/X94NeM3xvjenVUaBjy6Ui4Ogd/J5ZtjZnHaE=
|
||||
sigs.k8s.io/controller-runtime v0.20.1/go.mod h1:BrP3w158MwvB3ZbNpaAcIKkHQ7YGpYnzpoSTZ8E14WU=
|
||||
sigs.k8s.io/controller-runtime/tools/setup-envtest v0.0.0-20250205174817-cd644c0ad54f h1:a3Zwd7yRlWRpB4j9sbh6WMZrAMgLrSBi0qQ/9YCVSkU=
|
||||
sigs.k8s.io/controller-runtime/tools/setup-envtest v0.0.0-20250205174817-cd644c0ad54f/go.mod h1:IaDsO8xSPRxRG1/rm9CP7+jPmj0nMNAuNi/yiHnLX8k=
|
||||
sigs.k8s.io/controller-tools v0.17.2 h1:jNFOKps8WnaRKZU2R+4vRCHnXyJanVmXBWqkuUPFyFg=
|
||||
sigs.k8s.io/controller-tools v0.17.2/go.mod h1:4q5tZG2JniS5M5bkiXY2/potOiXyhoZVw/U48vLkXk0=
|
||||
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE=
|
||||
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg=
|
||||
sigs.k8s.io/kustomize/api v0.19.0 h1:F+2HB2mU1MSiR9Hp1NEgoU2q9ItNOaBJl0I4Dlus5SQ=
|
||||
sigs.k8s.io/kustomize/api v0.19.0/go.mod h1:/BbwnivGVcBh1r+8m3tH1VNxJmHSk1PzP5fkP6lbL1o=
|
||||
sigs.k8s.io/kustomize/cmd/config v0.19.0 h1:D3uASwjHWHmNiEHu3pPJBJMBIsb+auFvHrHql3HAarU=
|
||||
sigs.k8s.io/kustomize/cmd/config v0.19.0/go.mod h1:29Vvdl26PidPLUDi7nfjYa/I0wHBkwCZp15Nlcc4y98=
|
||||
sigs.k8s.io/kustomize/kustomize/v5 v5.6.0 h1:MWtRRDWCwQEeW2rnJTqJMuV6Agy56P53SkbVoJpN7wA=
|
||||
sigs.k8s.io/kustomize/kustomize/v5 v5.6.0/go.mod h1:XuuZiQF7WdcvZzEYyNww9A0p3LazCKeJmCjeycN8e1I=
|
||||
sigs.k8s.io/kustomize/kyaml v0.19.0 h1:RFge5qsO1uHhwJsu3ipV7RNolC7Uozc0jUBC/61XSlA=
|
||||
sigs.k8s.io/kustomize/kyaml v0.19.0/go.mod h1:FeKD5jEOH+FbZPpqUghBP8mrLjJ3+zD3/rf9NNu1cwY=
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.5.0 h1:nbCitCK2hfnhyiKo6uf2HxUPTCodY6Qaf85SbDIaMBk=
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.5.0/go.mod h1:N8f93tFZh9U6vpxwRArLiikrE5/2tiu1w1AGfACIGE4=
|
||||
sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
|
||||
|
|
60
internal/certs/format.go
Normal file
60
internal/certs/format.go
Normal file
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
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 certs
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
const (
|
||||
certificateBlockType = "CERTIFICATE"
|
||||
privateKeyBlockType = "PRIVATE KEY"
|
||||
)
|
||||
|
||||
func EncodePublicKeyToPEM(derBytes []byte) []byte {
|
||||
return pem.EncodeToMemory(&pem.Block{Type: certificateBlockType, Bytes: derBytes})
|
||||
}
|
||||
|
||||
func EncodePrivateKeyToPEM(privateKeyBytes []byte) []byte {
|
||||
return pem.EncodeToMemory(&pem.Block{Type: privateKeyBlockType, Bytes: privateKeyBytes})
|
||||
}
|
||||
|
||||
func newCertResult(derBytes, privateKeyBytes []byte) (CertResult, error) {
|
||||
pemEncodedPublicKey := EncodePublicKeyToPEM(derBytes)
|
||||
pemEncodedPrivateKey := EncodePrivateKeyToPEM(privateKeyBytes)
|
||||
|
||||
cert, err := tls.X509KeyPair(pemEncodedPublicKey, pemEncodedPrivateKey)
|
||||
if err != nil {
|
||||
return CertResult{}, fmt.Errorf("failed to create TLS cert based on x509 key pair: %w", err)
|
||||
}
|
||||
|
||||
result := CertResult{
|
||||
ServerCert: cert,
|
||||
PublicKey: pemEncodedPublicKey,
|
||||
PrivateKey: pemEncodedPrivateKey,
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
type CertResult struct {
|
||||
ServerCert tls.Certificate
|
||||
PublicKey []byte
|
||||
PrivateKey []byte
|
||||
}
|
122
internal/certs/generate.go
Normal file
122
internal/certs/generate.go
Normal file
|
@ -0,0 +1,122 @@
|
|||
/*
|
||||
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 certs
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"time"
|
||||
)
|
||||
|
||||
func ServerCert(
|
||||
commonName string,
|
||||
dnsNames []string,
|
||||
ca tls.Certificate,
|
||||
) (result CertResult, err error) {
|
||||
serial, err := generateSerialNumber()
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
|
||||
privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
if err != nil {
|
||||
return result, fmt.Errorf("failed to generate private key: %w", err)
|
||||
}
|
||||
|
||||
certTemplate := x509.Certificate{
|
||||
SerialNumber: serial,
|
||||
Subject: pkix.Name{
|
||||
CommonName: commonName,
|
||||
},
|
||||
DNSNames: dnsNames,
|
||||
NotBefore: time.Now().Add(-1 * time.Hour),
|
||||
NotAfter: time.Now().AddDate(0, 1, 0),
|
||||
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageDataEncipherment | x509.KeyUsageKeyEncipherment | x509.KeyUsageContentCommitment,
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||
}
|
||||
|
||||
var caCrt *x509.Certificate
|
||||
if caCrt, err = x509.ParseCertificate(ca.Certificate[0]); err != nil {
|
||||
return result, fmt.Errorf("failed parse CA cert: %w", err)
|
||||
}
|
||||
|
||||
var derBytes []byte
|
||||
if derBytes, err = x509.CreateCertificate(rand.Reader, &certTemplate, caCrt, &privateKey.PublicKey, ca.PrivateKey); err != nil {
|
||||
return result, fmt.Errorf("failed to create signed certificate: %w", err)
|
||||
}
|
||||
|
||||
var privateKeyBytes []byte
|
||||
if privateKeyBytes, err = x509.MarshalPKCS8PrivateKey(privateKey); err != nil {
|
||||
return result, fmt.Errorf("failed to marshal private key: %w", err)
|
||||
}
|
||||
|
||||
return newCertResult(derBytes, privateKeyBytes)
|
||||
}
|
||||
|
||||
func ClientCert(
|
||||
commonName string,
|
||||
ca tls.Certificate,
|
||||
) (result CertResult, err error) {
|
||||
serial, err := generateSerialNumber()
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
|
||||
privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
if err != nil {
|
||||
return result, fmt.Errorf("failed to generate private key: %w", err)
|
||||
}
|
||||
|
||||
certTemplate := x509.Certificate{
|
||||
SerialNumber: serial,
|
||||
Subject: pkix.Name{
|
||||
CommonName: commonName,
|
||||
},
|
||||
NotBefore: time.Now().Add(-1 * time.Hour),
|
||||
NotAfter: time.Now().AddDate(0, 0, 7),
|
||||
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageDataEncipherment | x509.KeyUsageKeyEncipherment | x509.KeyUsageContentCommitment,
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
|
||||
}
|
||||
|
||||
var caCrt *x509.Certificate
|
||||
if caCrt, err = x509.ParseCertificate(ca.Certificate[0]); err != nil {
|
||||
return result, fmt.Errorf("failed parse CA cert: %w", err)
|
||||
}
|
||||
|
||||
var derBytes []byte
|
||||
if derBytes, err = x509.CreateCertificate(rand.Reader, &certTemplate, caCrt, &privateKey.PublicKey, ca.PrivateKey); err != nil {
|
||||
return result, fmt.Errorf("failed to create signed certificate: %w", err)
|
||||
}
|
||||
|
||||
var privateKeyBytes []byte
|
||||
if privateKeyBytes, err = x509.MarshalPKCS8PrivateKey(privateKey); err != nil {
|
||||
return result, fmt.Errorf("failed to marshal private key: %w", err)
|
||||
}
|
||||
|
||||
return newCertResult(derBytes, privateKeyBytes)
|
||||
}
|
||||
|
||||
func generateSerialNumber() (*big.Int, error) {
|
||||
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
|
||||
return rand.Int(rand.Reader, serialNumberLimit)
|
||||
}
|
|
@ -19,10 +19,13 @@ package controller
|
|||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"crypto/tls"
|
||||
"embed"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
|
@ -41,6 +44,7 @@ import (
|
|||
"sigs.k8s.io/controller-runtime/pkg/reconcile"
|
||||
|
||||
supabasev1alpha1 "code.icb4dc0.de/prskr/supabase-operator/api/v1alpha1"
|
||||
"code.icb4dc0.de/prskr/supabase-operator/internal/certs"
|
||||
"code.icb4dc0.de/prskr/supabase-operator/internal/meta"
|
||||
"code.icb4dc0.de/prskr/supabase-operator/internal/supabase"
|
||||
)
|
||||
|
@ -53,7 +57,7 @@ var (
|
|||
)
|
||||
|
||||
const (
|
||||
jwksSecretNameField = ".spec.jwks.name"
|
||||
jwksSecretNameField = ".spec.apiEndpoint.jwks.name"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
@ -64,6 +68,7 @@ func init() {
|
|||
type APIGatewayReconciler struct {
|
||||
client.Client
|
||||
Scheme *runtime.Scheme
|
||||
CACert tls.Certificate
|
||||
}
|
||||
|
||||
// Reconcile is part of the main kubernetes reconciliation loop which aims to
|
||||
|
@ -85,6 +90,14 @@ func (r *APIGatewayReconciler) Reconcile(ctx context.Context, req ctrl.Request)
|
|||
return ctrl.Result{}, err
|
||||
}
|
||||
|
||||
if err := r.reconcileHmacSecret(ctx, &gateway); err != nil {
|
||||
return ctrl.Result{}, fmt.Errorf("failed to ensure HMAC token secret: %w", err)
|
||||
}
|
||||
|
||||
if err := r.reconcileClientCertSecret(ctx, &gateway); err != nil {
|
||||
return ctrl.Result{}, fmt.Errorf("failed to ensure client certificate: %w", err)
|
||||
}
|
||||
|
||||
if jwksHash, err = r.reconcileJwksSecret(ctx, &gateway); err != nil {
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
|
@ -105,7 +118,8 @@ func (r *APIGatewayReconciler) Reconcile(ctx context.Context, req ctrl.Request)
|
|||
return ctrl.Result{}, err
|
||||
}
|
||||
|
||||
return ctrl.Result{}, nil
|
||||
// requeue after 15 minutes to watch for expiring certificates
|
||||
return ctrl.Result{RequeueAfter: 15 * time.Minute}, nil
|
||||
}
|
||||
|
||||
// SetupWithManager sets up the controller with the Manager.
|
||||
|
@ -116,7 +130,7 @@ func (r *APIGatewayReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Ma
|
|||
return nil
|
||||
}
|
||||
|
||||
return []string{gw.Spec.JWKSSelector.Name}
|
||||
return []string{gw.Spec.ApiEndpoint.JWKSSelector.Name}
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("setting up field index for JWKS secret name: %w", err)
|
||||
|
@ -144,6 +158,8 @@ func (r *APIGatewayReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Ma
|
|||
return ctrl.NewControllerManagedBy(mgr).
|
||||
For(&supabasev1alpha1.APIGateway{}).
|
||||
Named("apigateway").
|
||||
// watch for the HMAC & client cert secrets
|
||||
Owns(new(corev1.Secret)).
|
||||
Owns(new(corev1.ConfigMap)).
|
||||
Owns(new(appsv1.Deployment)).
|
||||
Owns(new(corev1.Service)).
|
||||
|
@ -179,7 +195,7 @@ func (r *APIGatewayReconciler) apiTargetServiceEventHandler() handler.TypedEvent
|
|||
return nil
|
||||
}
|
||||
|
||||
if err := r.Client.List(ctx, &list, client.InNamespace(obj.GetNamespace())); err != nil {
|
||||
if err := r.List(ctx, &list, client.InNamespace(obj.GetNamespace())); err != nil {
|
||||
logger.Error(err, "Failed to list Services to map updates to APIGateway reconciliation requests")
|
||||
return nil
|
||||
}
|
||||
|
@ -211,7 +227,7 @@ func (r *APIGatewayReconciler) reconcileJwksSecret(
|
|||
return "", err
|
||||
}
|
||||
|
||||
jwksRaw, ok := jwksSecret.Data[gateway.Spec.JWKSSelector.Key]
|
||||
jwksRaw, ok := jwksSecret.Data[gateway.Spec.ApiEndpoint.JWKSSelector.Key]
|
||||
if !ok {
|
||||
return "", fmt.Errorf("%w in secret %s", ErrNoJwksConfigured, jwksSecret.Name)
|
||||
}
|
||||
|
@ -219,16 +235,128 @@ func (r *APIGatewayReconciler) reconcileJwksSecret(
|
|||
return hex.EncodeToString(HashBytes(jwksRaw)), nil
|
||||
}
|
||||
|
||||
func (r *APIGatewayReconciler) reconcileHmacSecret(
|
||||
ctx context.Context,
|
||||
gateway *supabasev1alpha1.APIGateway,
|
||||
) error {
|
||||
const hmacSecretLength = 32
|
||||
|
||||
serviceCfg := supabase.ServiceConfig.Envoy
|
||||
|
||||
hmacSecret := &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: serviceCfg.HmacSecretName(gateway),
|
||||
Namespace: gateway.Namespace,
|
||||
},
|
||||
}
|
||||
|
||||
_, err := controllerutil.CreateOrUpdate(ctx, r.Client, hmacSecret, func() error {
|
||||
if hmacSecret.Data == nil {
|
||||
hmacSecret.Data = make(map[string][]byte)
|
||||
}
|
||||
|
||||
if _, ok := hmacSecret.Data[serviceCfg.Defaults.HmacSecretKey]; !ok {
|
||||
secret := make([]byte, hmacSecretLength)
|
||||
if n, err := rand.Read(secret); err != nil {
|
||||
return fmt.Errorf("failed to generate HMAC token secret: %w", err)
|
||||
} else if n != hmacSecretLength {
|
||||
return fmt.Errorf("failed to generate HMAC token secret: not enough bytes generated")
|
||||
}
|
||||
|
||||
hmacSecret.Data[serviceCfg.Defaults.HmacSecretKey] = secret
|
||||
}
|
||||
|
||||
if err := controllerutil.SetControllerReference(gateway, hmacSecret, r.Scheme); err != nil {
|
||||
return fmt.Errorf("failed to set controller reference: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *APIGatewayReconciler) reconcileClientCertSecret(
|
||||
ctx context.Context,
|
||||
gateway *supabasev1alpha1.APIGateway,
|
||||
) error {
|
||||
var (
|
||||
logger = log.FromContext(ctx)
|
||||
clientCertSecret = &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: supabase.ServiceConfig.Envoy.ControlPlaneClientCertSecretName(gateway),
|
||||
Namespace: gateway.Namespace,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
_, err := controllerutil.CreateOrUpdate(ctx, r.Client, clientCertSecret, func() (err error) {
|
||||
clientCertSecret.Type = corev1.SecretTypeTLS
|
||||
if clientCertSecret.Data == nil {
|
||||
clientCertSecret.Data = make(map[string][]byte, 3)
|
||||
}
|
||||
|
||||
caCertBytes := certs.EncodePublicKeyToPEM(r.CACert.Certificate[0])
|
||||
clientCertSecret.Data["ca.crt"] = caCertBytes
|
||||
|
||||
var (
|
||||
cert = clientCertSecret.Data[corev1.TLSCertKey]
|
||||
privateKey = clientCertSecret.Data[corev1.TLSPrivateKeyKey]
|
||||
clientCert tls.Certificate
|
||||
)
|
||||
|
||||
var requireRenewal bool
|
||||
if cert != nil && privateKey != nil {
|
||||
if clientCert, err = tls.X509KeyPair(cert, privateKey); err != nil {
|
||||
return fmt.Errorf("failed to parse server certificate: %w", err)
|
||||
}
|
||||
|
||||
renewGracePeriod := time.Duration(float64(clientCert.Leaf.NotAfter.Sub(clientCert.Leaf.NotBefore)) * 0.1)
|
||||
if clientCert.Leaf.NotAfter.Before(time.Now().Add(-renewGracePeriod)) {
|
||||
logger.Info("Envoy control-plane client certificate requires renewal",
|
||||
"not_after", clientCert.Leaf.NotAfter,
|
||||
"renew_grace_period", renewGracePeriod,
|
||||
)
|
||||
requireRenewal = true
|
||||
}
|
||||
} else {
|
||||
logger.Info("Client cert is not set creating a new one")
|
||||
requireRenewal = true
|
||||
}
|
||||
|
||||
if requireRenewal {
|
||||
if certResult, err := certs.ClientCert(strings.Join([]string{gateway.Name, gateway.Namespace}, ":"), r.CACert); err != nil {
|
||||
return fmt.Errorf("failed to generate server certificate: %w", err)
|
||||
} else {
|
||||
clientCert = certResult.ServerCert
|
||||
clientCertSecret.Data[corev1.TLSCertKey] = certResult.PublicKey
|
||||
clientCertSecret.Data[corev1.TLSPrivateKeyKey] = certResult.PrivateKey
|
||||
}
|
||||
}
|
||||
|
||||
if err := controllerutil.SetControllerReference(gateway, clientCertSecret, r.Scheme); err != nil {
|
||||
return fmt.Errorf("failed to set controller reference: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *APIGatewayReconciler) reconcileEnvoyConfig(
|
||||
ctx context.Context,
|
||||
gateway *supabasev1alpha1.APIGateway,
|
||||
) (configHash string, err error) {
|
||||
configMap := &corev1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: supabase.ServiceConfig.Envoy.ObjectName(gateway),
|
||||
Namespace: gateway.Namespace,
|
||||
},
|
||||
}
|
||||
var (
|
||||
envoySpec = gateway.Spec.Envoy
|
||||
configMap = &corev1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: supabase.ServiceConfig.Envoy.ObjectName(gateway),
|
||||
Namespace: gateway.Namespace,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
_, err = controllerutil.CreateOrUpdate(ctx, r.Client, configMap, func() error {
|
||||
configMap.Labels = MergeLabels(objectLabels(gateway, "envoy", "api-gateway", supabase.Images.Envoy.Tag), gateway.Labels)
|
||||
|
@ -244,7 +372,7 @@ func (r *APIGatewayReconciler) reconcileEnvoyConfig(
|
|||
Port uint16
|
||||
}
|
||||
|
||||
instance := fmt.Sprintf("%s:%s", gateway.Spec.Envoy.NodeName, gateway.Namespace)
|
||||
instance := fmt.Sprintf("%s:%s", envoySpec.NodeName, gateway.Namespace)
|
||||
|
||||
tmplData := struct {
|
||||
Node nodeSpec
|
||||
|
@ -256,8 +384,8 @@ func (r *APIGatewayReconciler) reconcileEnvoyConfig(
|
|||
},
|
||||
ControlPlane: controlPlaneSpec{
|
||||
Name: "supabase-control-plane",
|
||||
Host: gateway.Spec.Envoy.ControlPlane.Host,
|
||||
Port: gateway.Spec.Envoy.ControlPlane.Port,
|
||||
Host: envoySpec.ControlPlane.Host,
|
||||
Port: envoySpec.ControlPlane.Port,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -291,6 +419,10 @@ func (r *APIGatewayReconciler) reconileEnvoyDeployment(
|
|||
gateway *supabasev1alpha1.APIGateway,
|
||||
configHash, jwksHash string,
|
||||
) error {
|
||||
const (
|
||||
configVolumeName = "config"
|
||||
controlPlaneTlsVolumeName = "cp-tls"
|
||||
)
|
||||
envoyDeployment := &appsv1.Deployment{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: supabase.ServiceConfig.Envoy.ObjectName(gateway),
|
||||
|
@ -304,7 +436,7 @@ func (r *APIGatewayReconciler) reconileEnvoyDeployment(
|
|||
)
|
||||
|
||||
_, err := controllerutil.CreateOrUpdate(ctx, r.Client, envoyDeployment, func() error {
|
||||
envoyDeployment.Labels = envoySpec.WorkloadTemplate.MergeLabels(
|
||||
envoyDeployment.Labels = envoySpec.WorkloadSpec.MergeLabels(
|
||||
objectLabels(gateway, "envoy", "api-gateway", supabase.Images.Envoy.Tag),
|
||||
gateway.Labels,
|
||||
)
|
||||
|
@ -315,7 +447,13 @@ func (r *APIGatewayReconciler) reconileEnvoyDeployment(
|
|||
}
|
||||
}
|
||||
|
||||
envoyDeployment.Spec.Replicas = envoySpec.WorkloadTemplate.ReplicaCount()
|
||||
envoyDeployment.Spec.Replicas = envoySpec.WorkloadSpec.ReplicaCount()
|
||||
|
||||
envoyArgs := []string{"-c /etc/envoy/config.yaml"}
|
||||
|
||||
if componentLogLevels := envoySpec.Debugging.DebugLogging(); len(componentLogLevels) > 0 {
|
||||
envoyArgs = append(envoyArgs, "--component-log-level", componentLogLevels)
|
||||
}
|
||||
|
||||
envoyDeployment.Spec.Template = corev1.PodTemplateSpec{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
|
@ -326,23 +464,28 @@ func (r *APIGatewayReconciler) reconileEnvoyDeployment(
|
|||
Labels: objectLabels(gateway, "envoy", "api-gateway", supabase.Images.Envoy.Tag),
|
||||
},
|
||||
Spec: corev1.PodSpec{
|
||||
ImagePullSecrets: envoySpec.WorkloadTemplate.PullSecrets(),
|
||||
ImagePullSecrets: envoySpec.WorkloadSpec.PullSecrets(),
|
||||
AutomountServiceAccountToken: ptrOf(false),
|
||||
Containers: []corev1.Container{
|
||||
{
|
||||
Name: "envoy-proxy",
|
||||
Image: envoySpec.WorkloadTemplate.Image(supabase.Images.Envoy.String()),
|
||||
ImagePullPolicy: envoySpec.WorkloadTemplate.ImagePullPolicy(),
|
||||
Args: []string{"-c /etc/envoy/config.yaml"},
|
||||
Image: envoySpec.WorkloadSpec.Image(supabase.Images.Envoy.String()),
|
||||
ImagePullPolicy: envoySpec.WorkloadSpec.ImagePullPolicy(),
|
||||
Args: envoyArgs,
|
||||
Ports: []corev1.ContainerPort{
|
||||
{
|
||||
Name: "http",
|
||||
ContainerPort: 8000,
|
||||
Name: serviceCfg.Defaults.StudioPortName,
|
||||
ContainerPort: serviceCfg.Defaults.StudioPort,
|
||||
Protocol: corev1.ProtocolTCP,
|
||||
},
|
||||
{
|
||||
Name: serviceCfg.Defaults.ApiPortName,
|
||||
ContainerPort: serviceCfg.Defaults.ApiPort,
|
||||
Protocol: corev1.ProtocolTCP,
|
||||
},
|
||||
{
|
||||
Name: "admin",
|
||||
ContainerPort: 19000,
|
||||
ContainerPort: serviceCfg.Defaults.AdminPort,
|
||||
Protocol: corev1.ProtocolTCP,
|
||||
},
|
||||
},
|
||||
|
@ -369,21 +512,19 @@ func (r *APIGatewayReconciler) reconileEnvoyDeployment(
|
|||
},
|
||||
},
|
||||
},
|
||||
SecurityContext: envoySpec.WorkloadTemplate.ContainerSecurityContext(serviceCfg.Defaults.UID, serviceCfg.Defaults.GID),
|
||||
Resources: envoySpec.WorkloadTemplate.Resources(),
|
||||
VolumeMounts: envoySpec.WorkloadTemplate.AdditionalVolumeMounts(
|
||||
corev1.VolumeMount{
|
||||
Name: "config",
|
||||
ReadOnly: true,
|
||||
MountPath: "/etc/envoy",
|
||||
},
|
||||
),
|
||||
SecurityContext: envoySpec.WorkloadSpec.ContainerSecurityContext(serviceCfg.Defaults.UID, serviceCfg.Defaults.GID),
|
||||
Resources: envoySpec.WorkloadSpec.Resources(),
|
||||
VolumeMounts: envoySpec.WorkloadSpec.AdditionalVolumeMounts(corev1.VolumeMount{
|
||||
Name: configVolumeName,
|
||||
ReadOnly: true,
|
||||
MountPath: "/etc/envoy",
|
||||
}),
|
||||
},
|
||||
},
|
||||
SecurityContext: envoySpec.WorkloadTemplate.PodSecurityContext(),
|
||||
SecurityContext: envoySpec.WorkloadSpec.PodSecurityContext(),
|
||||
Volumes: []corev1.Volume{
|
||||
{
|
||||
Name: "config",
|
||||
Name: configVolumeName,
|
||||
VolumeSource: corev1.VolumeSource{
|
||||
Projected: &corev1.ProjectedVolumeSource{
|
||||
Sources: []corev1.VolumeProjection{
|
||||
|
@ -392,27 +533,58 @@ func (r *APIGatewayReconciler) reconileEnvoyDeployment(
|
|||
LocalObjectReference: corev1.LocalObjectReference{
|
||||
Name: supabase.ServiceConfig.Envoy.ObjectName(gateway),
|
||||
},
|
||||
Items: []corev1.KeyToPath{
|
||||
{
|
||||
Key: "config.yaml",
|
||||
Path: "config.yaml",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Secret: &corev1.SecretProjection{
|
||||
LocalObjectReference: corev1.LocalObjectReference{
|
||||
Name: gateway.Spec.ApiEndpoint.JWKSSelector.Name,
|
||||
},
|
||||
Items: []corev1.KeyToPath{{
|
||||
Key: "config.yaml",
|
||||
Path: "config.yaml",
|
||||
Key: gateway.Spec.ApiEndpoint.JWKSSelector.Key,
|
||||
Path: "jwks.json",
|
||||
}},
|
||||
},
|
||||
},
|
||||
{
|
||||
Secret: &corev1.SecretProjection{
|
||||
LocalObjectReference: corev1.LocalObjectReference{
|
||||
Name: gateway.Spec.JWKSSelector.Name,
|
||||
Name: serviceCfg.ControlPlaneClientCertSecretName(gateway),
|
||||
},
|
||||
Items: []corev1.KeyToPath{
|
||||
{
|
||||
Key: "ca.crt",
|
||||
Path: "certs/cp/ca.crt",
|
||||
},
|
||||
{
|
||||
Key: "tls.crt",
|
||||
Path: "certs/cp/tls.crt",
|
||||
},
|
||||
{
|
||||
Key: "tls.key",
|
||||
Path: "certs/cp/tls.key",
|
||||
},
|
||||
},
|
||||
Items: []corev1.KeyToPath{{
|
||||
Key: gateway.Spec.JWKSSelector.Key,
|
||||
Path: "jwks.json",
|
||||
}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: controlPlaneTlsVolumeName,
|
||||
VolumeSource: corev1.VolumeSource{
|
||||
Secret: &corev1.SecretVolumeSource{
|
||||
SecretName: serviceCfg.ControlPlaneClientCertSecretName(gateway),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@ -431,12 +603,15 @@ func (r *APIGatewayReconciler) reconcileEnvoyService(
|
|||
ctx context.Context,
|
||||
gateway *supabasev1alpha1.APIGateway,
|
||||
) error {
|
||||
envoyService := &corev1.Service{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: supabase.ServiceConfig.Envoy.ObjectName(gateway),
|
||||
Namespace: gateway.Namespace,
|
||||
},
|
||||
}
|
||||
var (
|
||||
serviceCfg = supabase.ServiceConfig.Envoy
|
||||
envoyService = &corev1.Service{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: supabase.ServiceConfig.Envoy.ObjectName(gateway),
|
||||
Namespace: gateway.Namespace,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
_, err := controllerutil.CreateOrUpdate(ctx, r.Client, envoyService, func() error {
|
||||
envoyService.Labels = MergeLabels(objectLabels(gateway, "envoy", "api-gateway", supabase.Images.Envoy.Tag), gateway.Labels)
|
||||
|
@ -445,11 +620,18 @@ func (r *APIGatewayReconciler) reconcileEnvoyService(
|
|||
Selector: selectorLabels(gateway, "envoy"),
|
||||
Ports: []corev1.ServicePort{
|
||||
{
|
||||
Name: "rest",
|
||||
Name: serviceCfg.Defaults.StudioPortName,
|
||||
Protocol: corev1.ProtocolTCP,
|
||||
AppProtocol: ptrOf("http"),
|
||||
Port: 8000,
|
||||
TargetPort: intstr.IntOrString{IntVal: 8000},
|
||||
Port: serviceCfg.Defaults.StudioPort,
|
||||
TargetPort: intstr.IntOrString{IntVal: serviceCfg.Defaults.StudioPort},
|
||||
},
|
||||
{
|
||||
Name: serviceCfg.Defaults.ApiPortName,
|
||||
Protocol: corev1.ProtocolTCP,
|
||||
AppProtocol: ptrOf("http"),
|
||||
Port: serviceCfg.Defaults.ApiPort,
|
||||
TargetPort: intstr.IntOrString{IntVal: serviceCfg.Defaults.ApiPort},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ package controller
|
|||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"hash/fnv"
|
||||
"crypto/rand"
|
||||
"maps"
|
||||
"net/url"
|
||||
"time"
|
||||
|
@ -38,7 +38,6 @@ import (
|
|||
"code.icb4dc0.de/prskr/supabase-operator/internal/db"
|
||||
"code.icb4dc0.de/prskr/supabase-operator/internal/errx"
|
||||
"code.icb4dc0.de/prskr/supabase-operator/internal/meta"
|
||||
"code.icb4dc0.de/prskr/supabase-operator/internal/pw"
|
||||
"code.icb4dc0.de/prskr/supabase-operator/internal/supabase"
|
||||
)
|
||||
|
||||
|
@ -105,11 +104,7 @@ func (r *CoreDbReconciler) applyMissingMigrations(
|
|||
|
||||
var appliedSomething bool
|
||||
|
||||
if core.Status.Database.AppliedMigrations == nil {
|
||||
core.Status.Database.AppliedMigrations = make(supabasev1alpha1.MigrationStatus)
|
||||
}
|
||||
|
||||
if appliedSomething, err = migrator.ApplyAll(ctx, core.Status.Database.AppliedMigrations, migrations.InitScripts()); err != nil {
|
||||
if appliedSomething, err = migrator.ApplyAll(ctx, &core.Status, migrations.InitScripts(), true); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -120,7 +115,7 @@ func (r *CoreDbReconciler) applyMissingMigrations(
|
|||
logger.Info("Init scripts were up to date - did not run any")
|
||||
}
|
||||
|
||||
if appliedSomething, err = migrator.ApplyAll(ctx, core.Status.Database.AppliedMigrations, migrations.MigrationScripts()); err != nil {
|
||||
if appliedSomething, err = migrator.ApplyAll(ctx, &core.Status, migrations.MigrationScripts(), false); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -173,8 +168,6 @@ func (r *CoreDbReconciler) ensureDbRolesSecrets(
|
|||
core.Status.Database.Roles = make(map[string][]byte)
|
||||
}
|
||||
|
||||
hash := fnv.New64a()
|
||||
|
||||
for secretName, role := range roles {
|
||||
secretLogger := logger.WithValues("secret_name", secretName, "role_name", role.String())
|
||||
|
||||
|
@ -207,21 +200,23 @@ func (r *CoreDbReconciler) ensureDbRolesSecrets(
|
|||
|
||||
var requireStatusUpdate bool
|
||||
|
||||
// if the current role is the same user as in the DSN, we're keeping the password as is
|
||||
if value := credentialsSecret.Data[corev1.BasicAuthPasswordKey]; len(value) == 0 || (role.String() == dsnUser && !bytes.Equal(credentialsSecret.Data[corev1.BasicAuthPasswordKey], []byte(dsnPW))) {
|
||||
if role.String() == dsnUser {
|
||||
credentialsSecret.Data[corev1.BasicAuthPasswordKey] = []byte(dsnPW)
|
||||
} else {
|
||||
credentialsSecret.Data[corev1.BasicAuthPasswordKey] = pw.GeneratePW(24, nil)
|
||||
credentialsSecret.Data[corev1.BasicAuthPasswordKey] = []byte(rand.Text())
|
||||
}
|
||||
|
||||
secretLogger.Info("Update database role to match secret credentials")
|
||||
if err := rolesMgr.UpdateRolePassword(ctx, role.String(), credentialsSecret.Data[corev1.BasicAuthPasswordKey]); err != nil {
|
||||
return err
|
||||
}
|
||||
core.Status.Database.Roles[role.String()] = hash.Sum(credentialsSecret.Data[corev1.BasicAuthPasswordKey])
|
||||
|
||||
core.Status.Database.Roles[role.String()] = HashBytes(credentialsSecret.Data[corev1.BasicAuthPasswordKey])
|
||||
requireStatusUpdate = true
|
||||
} else {
|
||||
if bytes.Equal(core.Status.Database.Roles[role.String()], hash.Sum(credentialsSecret.Data[corev1.BasicAuthPasswordKey])) {
|
||||
if bytes.Equal(core.Status.Database.Roles[role.String()], HashBytes(credentialsSecret.Data[corev1.BasicAuthPasswordKey])) {
|
||||
logger.Info("Role password is up to date", "role_name", role.String())
|
||||
} else {
|
||||
if err := rolesMgr.UpdateRolePassword(ctx, role.String(), credentialsSecret.Data[corev1.BasicAuthPasswordKey]); err != nil {
|
||||
|
@ -229,7 +224,7 @@ func (r *CoreDbReconciler) ensureDbRolesSecrets(
|
|||
}
|
||||
requireStatusUpdate = true
|
||||
}
|
||||
core.Status.Database.Roles[role.String()] = hash.Sum(credentialsSecret.Data[corev1.BasicAuthPasswordKey])
|
||||
core.Status.Database.Roles[role.String()] = HashBytes(credentialsSecret.Data[corev1.BasicAuthPasswordKey])
|
||||
}
|
||||
|
||||
credentialsSecret.Type = corev1.SecretTypeBasicAuth
|
||||
|
|
|
@ -160,7 +160,8 @@ func (r *CoreAuthReconciler) reconcileAuthDeployment(
|
|||
Labels: objectLabels(core, "auth", "core", supabase.Images.Gotrue.Tag),
|
||||
},
|
||||
Spec: corev1.PodSpec{
|
||||
ImagePullSecrets: authSpec.WorkloadTemplate.PullSecrets(),
|
||||
ImagePullSecrets: authSpec.WorkloadTemplate.PullSecrets(),
|
||||
AutomountServiceAccountToken: ptrOf(false),
|
||||
InitContainers: []corev1.Container{{
|
||||
Name: "supabase-auth-migrations",
|
||||
Image: authSpec.WorkloadTemplate.Image(supabase.Images.Gotrue.String()),
|
||||
|
@ -192,7 +193,7 @@ func (r *CoreAuthReconciler) reconcileAuthDeployment(
|
|||
SuccessThreshold: 2,
|
||||
ProbeHandler: corev1.ProbeHandler{
|
||||
HTTPGet: &corev1.HTTPGetAction{
|
||||
Path: "/health",
|
||||
Path: svcCfg.LivenessProbePath,
|
||||
Port: intstr.IntOrString{IntVal: svcCfg.Defaults.APIPort},
|
||||
},
|
||||
},
|
||||
|
@ -203,7 +204,7 @@ func (r *CoreAuthReconciler) reconcileAuthDeployment(
|
|||
TimeoutSeconds: 3,
|
||||
ProbeHandler: corev1.ProbeHandler{
|
||||
HTTPGet: &corev1.HTTPGetAction{
|
||||
Path: "/health",
|
||||
Path: svcCfg.LivenessProbePath,
|
||||
Port: intstr.IntOrString{IntVal: svcCfg.Defaults.APIPort},
|
||||
},
|
||||
},
|
||||
|
@ -232,7 +233,7 @@ func (r *CoreAuthReconciler) reconcileAuthService(
|
|||
}
|
||||
|
||||
_, err := controllerutil.CreateOrUpdate(ctx, r.Client, authService, func() error {
|
||||
authService.Labels = core.Spec.Postgrest.WorkloadTemplate.MergeLabels(
|
||||
authService.Labels = core.Spec.Postgrest.WorkloadSpec.MergeLabels(
|
||||
objectLabels(core, "auth", "core", supabase.Images.Gotrue.Tag),
|
||||
core.Labels,
|
||||
)
|
||||
|
|
|
@ -115,7 +115,7 @@ func (r *CorePostgrestReconiler) reconilePostgrestDeployment(
|
|||
}
|
||||
|
||||
_, err = controllerutil.CreateOrUpdate(ctx, r.Client, postgrestDeployment, func() error {
|
||||
postgrestDeployment.Labels = postgrestSpec.WorkloadTemplate.MergeLabels(
|
||||
postgrestDeployment.Labels = postgrestSpec.WorkloadSpec.MergeLabels(
|
||||
objectLabels(core, serviceCfg.Name, "core", supabase.Images.Postgrest.Tag),
|
||||
core.Labels,
|
||||
)
|
||||
|
@ -155,7 +155,7 @@ func (r *CorePostgrestReconiler) reconilePostgrestDeployment(
|
|||
}
|
||||
}
|
||||
|
||||
postgrestDeployment.Spec.Replicas = postgrestSpec.WorkloadTemplate.ReplicaCount()
|
||||
postgrestDeployment.Spec.Replicas = postgrestSpec.WorkloadSpec.ReplicaCount()
|
||||
|
||||
postgrestDeployment.Spec.Template = corev1.PodTemplateSpec{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
|
@ -165,14 +165,15 @@ func (r *CorePostgrestReconiler) reconilePostgrestDeployment(
|
|||
Labels: objectLabels(core, serviceCfg.Name, "core", supabase.Images.Postgrest.Tag),
|
||||
},
|
||||
Spec: corev1.PodSpec{
|
||||
ImagePullSecrets: postgrestSpec.WorkloadTemplate.PullSecrets(),
|
||||
ImagePullSecrets: postgrestSpec.WorkloadSpec.PullSecrets(),
|
||||
AutomountServiceAccountToken: ptrOf(false),
|
||||
Containers: []corev1.Container{
|
||||
{
|
||||
Name: "supabase-rest",
|
||||
Image: postgrestSpec.WorkloadTemplate.Image(supabase.Images.Postgrest.String()),
|
||||
ImagePullPolicy: postgrestSpec.WorkloadTemplate.ImagePullPolicy(),
|
||||
Image: postgrestSpec.WorkloadSpec.Image(supabase.Images.Postgrest.String()),
|
||||
ImagePullPolicy: postgrestSpec.WorkloadSpec.ImagePullPolicy(),
|
||||
Args: []string{"postgrest"},
|
||||
Env: postgrestSpec.WorkloadTemplate.MergeEnv(postgrestEnv),
|
||||
Env: postgrestSpec.WorkloadSpec.MergeEnv(postgrestEnv),
|
||||
Ports: []corev1.ContainerPort{
|
||||
{
|
||||
Name: serviceCfg.Defaults.ServerPortName,
|
||||
|
@ -185,9 +186,9 @@ func (r *CorePostgrestReconiler) reconilePostgrestDeployment(
|
|||
Protocol: corev1.ProtocolTCP,
|
||||
},
|
||||
},
|
||||
SecurityContext: postgrestSpec.WorkloadTemplate.ContainerSecurityContext(serviceCfg.Defaults.UID, serviceCfg.Defaults.GID),
|
||||
Resources: postgrestSpec.WorkloadTemplate.Resources(),
|
||||
VolumeMounts: postgrestSpec.WorkloadTemplate.AdditionalVolumeMounts(),
|
||||
SecurityContext: postgrestSpec.WorkloadSpec.ContainerSecurityContext(serviceCfg.Defaults.UID, serviceCfg.Defaults.GID),
|
||||
Resources: postgrestSpec.WorkloadSpec.Resources(),
|
||||
VolumeMounts: postgrestSpec.WorkloadSpec.AdditionalVolumeMounts(),
|
||||
ReadinessProbe: &corev1.Probe{
|
||||
InitialDelaySeconds: 5,
|
||||
PeriodSeconds: 3,
|
||||
|
@ -195,7 +196,7 @@ func (r *CorePostgrestReconiler) reconilePostgrestDeployment(
|
|||
SuccessThreshold: 2,
|
||||
ProbeHandler: corev1.ProbeHandler{
|
||||
HTTPGet: &corev1.HTTPGetAction{
|
||||
Path: "/ready",
|
||||
Path: serviceCfg.LivenessProbePath,
|
||||
Port: intstr.IntOrString{IntVal: serviceCfg.Defaults.AdminPort},
|
||||
},
|
||||
},
|
||||
|
@ -213,7 +214,7 @@ func (r *CorePostgrestReconiler) reconilePostgrestDeployment(
|
|||
},
|
||||
},
|
||||
},
|
||||
SecurityContext: postgrestSpec.WorkloadTemplate.PodSecurityContext(),
|
||||
SecurityContext: postgrestSpec.WorkloadSpec.PodSecurityContext(),
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -239,7 +240,7 @@ func (r *CorePostgrestReconiler) reconcilePostgrestService(
|
|||
)
|
||||
|
||||
_, err := controllerutil.CreateOrUpdate(ctx, r.Client, postgrestService, func() error {
|
||||
postgrestService.Labels = core.Spec.Postgrest.WorkloadTemplate.MergeLabels(
|
||||
postgrestService.Labels = core.Spec.Postgrest.WorkloadSpec.MergeLabels(
|
||||
objectLabels(core, serviceCfg.Name, "core", supabase.Images.Postgrest.Tag),
|
||||
core.Labels,
|
||||
)
|
||||
|
|
|
@ -99,7 +99,7 @@ func (r *DashboardPGMetaReconciler) reconcilePGMetaDeployment(
|
|||
}
|
||||
|
||||
_, err := controllerutil.CreateOrUpdate(ctx, r.Client, pgMetaDeployment, func() error {
|
||||
pgMetaDeployment.Labels = pgMetaSpec.WorkloadTemplate.MergeLabels(
|
||||
pgMetaDeployment.Labels = pgMetaSpec.WorkloadSpec.MergeLabels(
|
||||
objectLabels(dashboard, serviceCfg.Name, "dashboard", supabase.Images.PostgresMeta.Tag),
|
||||
dashboard.Labels,
|
||||
)
|
||||
|
@ -110,7 +110,7 @@ func (r *DashboardPGMetaReconciler) reconcilePGMetaDeployment(
|
|||
}
|
||||
}
|
||||
|
||||
pgMetaDeployment.Spec.Replicas = pgMetaSpec.WorkloadTemplate.ReplicaCount()
|
||||
pgMetaDeployment.Spec.Replicas = pgMetaSpec.WorkloadSpec.ReplicaCount()
|
||||
|
||||
pgMetaEnv := []corev1.EnvVar{
|
||||
serviceCfg.EnvKeys.APIPort.Var(serviceCfg.Defaults.APIPort),
|
||||
|
@ -126,20 +126,21 @@ func (r *DashboardPGMetaReconciler) reconcilePGMetaDeployment(
|
|||
Labels: objectLabels(dashboard, serviceCfg.Name, "dashboard", supabase.Images.PostgresMeta.Tag),
|
||||
},
|
||||
Spec: corev1.PodSpec{
|
||||
ImagePullSecrets: pgMetaSpec.WorkloadTemplate.PullSecrets(),
|
||||
ImagePullSecrets: pgMetaSpec.WorkloadSpec.PullSecrets(),
|
||||
AutomountServiceAccountToken: ptrOf(false),
|
||||
Containers: []corev1.Container{{
|
||||
Name: "supabase-meta",
|
||||
Image: pgMetaSpec.WorkloadTemplate.Image(supabase.Images.PostgresMeta.String()),
|
||||
ImagePullPolicy: pgMetaSpec.WorkloadTemplate.ImagePullPolicy(),
|
||||
Env: pgMetaSpec.WorkloadTemplate.MergeEnv(pgMetaEnv),
|
||||
Image: pgMetaSpec.WorkloadSpec.Image(supabase.Images.PostgresMeta.String()),
|
||||
ImagePullPolicy: pgMetaSpec.WorkloadSpec.ImagePullPolicy(),
|
||||
Env: pgMetaSpec.WorkloadSpec.MergeEnv(pgMetaEnv),
|
||||
Ports: []corev1.ContainerPort{{
|
||||
Name: "api",
|
||||
ContainerPort: serviceCfg.Defaults.APIPort,
|
||||
Protocol: corev1.ProtocolTCP,
|
||||
}},
|
||||
SecurityContext: pgMetaSpec.WorkloadTemplate.ContainerSecurityContext(serviceCfg.Defaults.NodeUID, serviceCfg.Defaults.NodeGID),
|
||||
Resources: pgMetaSpec.WorkloadTemplate.Resources(),
|
||||
VolumeMounts: pgMetaSpec.WorkloadTemplate.AdditionalVolumeMounts(),
|
||||
SecurityContext: pgMetaSpec.WorkloadSpec.ContainerSecurityContext(serviceCfg.Defaults.NodeUID, serviceCfg.Defaults.NodeGID),
|
||||
Resources: pgMetaSpec.WorkloadSpec.Resources(),
|
||||
VolumeMounts: pgMetaSpec.WorkloadSpec.AdditionalVolumeMounts(),
|
||||
ReadinessProbe: &corev1.Probe{
|
||||
InitialDelaySeconds: 5,
|
||||
PeriodSeconds: 3,
|
||||
|
@ -147,7 +148,7 @@ func (r *DashboardPGMetaReconciler) reconcilePGMetaDeployment(
|
|||
SuccessThreshold: 2,
|
||||
ProbeHandler: corev1.ProbeHandler{
|
||||
HTTPGet: &corev1.HTTPGetAction{
|
||||
Path: "/health",
|
||||
Path: serviceCfg.LivenessProbePath,
|
||||
Port: intstr.IntOrString{IntVal: serviceCfg.Defaults.APIPort},
|
||||
},
|
||||
},
|
||||
|
@ -158,13 +159,13 @@ func (r *DashboardPGMetaReconciler) reconcilePGMetaDeployment(
|
|||
TimeoutSeconds: 3,
|
||||
ProbeHandler: corev1.ProbeHandler{
|
||||
HTTPGet: &corev1.HTTPGetAction{
|
||||
Path: "/health",
|
||||
Path: serviceCfg.LivenessProbePath,
|
||||
Port: intstr.IntOrString{IntVal: serviceCfg.Defaults.APIPort},
|
||||
},
|
||||
},
|
||||
},
|
||||
}},
|
||||
SecurityContext: pgMetaSpec.WorkloadTemplate.PodSecurityContext(),
|
||||
SecurityContext: pgMetaSpec.WorkloadSpec.PodSecurityContext(),
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -191,7 +192,7 @@ func (r *DashboardPGMetaReconciler) reconcilePGMetaService(
|
|||
}
|
||||
|
||||
_, err := controllerutil.CreateOrPatch(ctx, r.Client, pgMetaService, func() error {
|
||||
pgMetaService.Labels = dashboard.Spec.PGMeta.WorkloadTemplate.MergeLabels(
|
||||
pgMetaService.Labels = dashboard.Spec.PGMeta.WorkloadSpec.MergeLabels(
|
||||
objectLabels(dashboard, supabase.ServiceConfig.PGMeta.Name, "dashboard", supabase.Images.PostgresMeta.Tag),
|
||||
dashboard.Labels,
|
||||
)
|
||||
|
|
|
@ -107,7 +107,7 @@ func (r *DashboardStudioReconciler) reconcileStudioDeployment(
|
|||
gatewayService := gatewayServiceList.Items[0]
|
||||
|
||||
_, err = controllerutil.CreateOrUpdate(ctx, r.Client, studioDeployment, func() error {
|
||||
studioDeployment.Labels = studioSpec.WorkloadTemplate.MergeLabels(
|
||||
studioDeployment.Labels = studioSpec.WorkloadSpec.MergeLabels(
|
||||
objectLabels(dashboard, serviceCfg.Name, "dashboard", supabase.Images.Studio.Tag),
|
||||
dashboard.Labels,
|
||||
)
|
||||
|
@ -118,7 +118,7 @@ func (r *DashboardStudioReconciler) reconcileStudioDeployment(
|
|||
}
|
||||
}
|
||||
|
||||
studioDeployment.Spec.Replicas = studioSpec.WorkloadTemplate.ReplicaCount()
|
||||
studioDeployment.Spec.Replicas = studioSpec.WorkloadSpec.ReplicaCount()
|
||||
|
||||
studioEnv := []corev1.EnvVar{
|
||||
serviceCfg.EnvKeys.PGMetaURL.Var(fmt.Sprintf("http://%s.%s.svc:%d", supabase.ServiceConfig.PGMeta.ObjectName(dashboard), dashboard.Namespace, supabase.ServiceConfig.PGMeta.Defaults.APIPort)),
|
||||
|
@ -137,20 +137,21 @@ func (r *DashboardStudioReconciler) reconcileStudioDeployment(
|
|||
Labels: objectLabels(dashboard, serviceCfg.Name, "dashboard", supabase.Images.Studio.Tag),
|
||||
},
|
||||
Spec: corev1.PodSpec{
|
||||
ImagePullSecrets: studioSpec.WorkloadTemplate.PullSecrets(),
|
||||
ImagePullSecrets: studioSpec.WorkloadSpec.PullSecrets(),
|
||||
AutomountServiceAccountToken: ptrOf(false),
|
||||
Containers: []corev1.Container{{
|
||||
Name: "supabase-studio",
|
||||
Image: studioSpec.WorkloadTemplate.Image(supabase.Images.Studio.String()),
|
||||
ImagePullPolicy: studioSpec.WorkloadTemplate.ImagePullPolicy(),
|
||||
Env: studioSpec.WorkloadTemplate.MergeEnv(studioEnv),
|
||||
Image: studioSpec.WorkloadSpec.Image(supabase.Images.Studio.String()),
|
||||
ImagePullPolicy: studioSpec.WorkloadSpec.ImagePullPolicy(),
|
||||
Env: studioSpec.WorkloadSpec.MergeEnv(studioEnv),
|
||||
Ports: []corev1.ContainerPort{{
|
||||
Name: "studio",
|
||||
ContainerPort: serviceCfg.Defaults.APIPort,
|
||||
Protocol: corev1.ProtocolTCP,
|
||||
}},
|
||||
SecurityContext: studioSpec.WorkloadTemplate.ContainerSecurityContext(serviceCfg.Defaults.NodeUID, serviceCfg.Defaults.NodeGID),
|
||||
Resources: studioSpec.WorkloadTemplate.Resources(),
|
||||
VolumeMounts: studioSpec.WorkloadTemplate.AdditionalVolumeMounts(corev1.VolumeMount{
|
||||
SecurityContext: studioSpec.WorkloadSpec.ContainerSecurityContext(serviceCfg.Defaults.NodeUID, serviceCfg.Defaults.NodeGID),
|
||||
Resources: studioSpec.WorkloadSpec.Resources(),
|
||||
VolumeMounts: studioSpec.WorkloadSpec.AdditionalVolumeMounts(corev1.VolumeMount{
|
||||
Name: "next-cache",
|
||||
MountPath: "/app/apps/studio/.next/cache",
|
||||
}),
|
||||
|
@ -161,7 +162,7 @@ func (r *DashboardStudioReconciler) reconcileStudioDeployment(
|
|||
SuccessThreshold: 2,
|
||||
ProbeHandler: corev1.ProbeHandler{
|
||||
HTTPGet: &corev1.HTTPGetAction{
|
||||
Path: "/api/profile",
|
||||
Path: serviceCfg.LivenessProbePath,
|
||||
Port: intstr.IntOrString{IntVal: serviceCfg.Defaults.APIPort},
|
||||
},
|
||||
},
|
||||
|
@ -172,13 +173,13 @@ func (r *DashboardStudioReconciler) reconcileStudioDeployment(
|
|||
TimeoutSeconds: 3,
|
||||
ProbeHandler: corev1.ProbeHandler{
|
||||
HTTPGet: &corev1.HTTPGetAction{
|
||||
Path: "/api/profile",
|
||||
Path: serviceCfg.LivenessProbePath,
|
||||
Port: intstr.IntOrString{IntVal: serviceCfg.Defaults.APIPort},
|
||||
},
|
||||
},
|
||||
},
|
||||
}},
|
||||
SecurityContext: studioSpec.WorkloadTemplate.PodSecurityContext(),
|
||||
SecurityContext: studioSpec.WorkloadSpec.PodSecurityContext(),
|
||||
Volumes: []corev1.Volume{{
|
||||
Name: "next-cache",
|
||||
VolumeSource: corev1.VolumeSource{
|
||||
|
@ -214,7 +215,7 @@ func (r *DashboardStudioReconciler) reconcileStudioService(
|
|||
}
|
||||
|
||||
_, err := controllerutil.CreateOrPatch(ctx, r.Client, studioService, func() error {
|
||||
studioService.Labels = dashboard.Spec.Studio.WorkloadTemplate.MergeLabels(
|
||||
studioService.Labels = dashboard.Spec.Studio.WorkloadSpec.MergeLabels(
|
||||
objectLabels(dashboard, supabase.ServiceConfig.Studio.Name, "dashboard", supabase.Images.Studio.Tag),
|
||||
dashboard.Labels,
|
||||
)
|
||||
|
|
|
@ -133,7 +133,7 @@ func (r *StorageApiReconciler) reconcileStorageApiDeployment(
|
|||
))
|
||||
|
||||
_, err := controllerutil.CreateOrUpdate(ctx, r.Client, storageApiDeployment, func() error {
|
||||
storageApiDeployment.Labels = apiSpec.WorkloadTemplate.MergeLabels(
|
||||
storageApiDeployment.Labels = apiSpec.WorkloadSpec.MergeLabels(
|
||||
objectLabels(storage, serviceCfg.Name, "storage", supabase.Images.Storage.Tag),
|
||||
storage.Labels,
|
||||
)
|
||||
|
@ -188,7 +188,7 @@ func (r *StorageApiReconciler) reconcileStorageApiDeployment(
|
|||
}
|
||||
}
|
||||
|
||||
storageApiDeployment.Spec.Replicas = apiSpec.WorkloadTemplate.ReplicaCount()
|
||||
storageApiDeployment.Spec.Replicas = apiSpec.WorkloadSpec.ReplicaCount()
|
||||
|
||||
storageApiDeployment.Spec.Template = corev1.PodTemplateSpec{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
|
@ -199,20 +199,21 @@ func (r *StorageApiReconciler) reconcileStorageApiDeployment(
|
|||
Labels: objectLabels(storage, serviceCfg.Name, "storage", supabase.Images.Storage.Tag),
|
||||
},
|
||||
Spec: corev1.PodSpec{
|
||||
ImagePullSecrets: apiSpec.WorkloadTemplate.PullSecrets(),
|
||||
ImagePullSecrets: apiSpec.WorkloadSpec.PullSecrets(),
|
||||
AutomountServiceAccountToken: ptrOf(false),
|
||||
Containers: []corev1.Container{{
|
||||
Name: "supabase-storage",
|
||||
Image: apiSpec.WorkloadTemplate.Image(supabase.Images.Storage.String()),
|
||||
ImagePullPolicy: apiSpec.WorkloadTemplate.ImagePullPolicy(),
|
||||
Env: apiSpec.WorkloadTemplate.MergeEnv(append(storagApiEnv, slices.Concat(apiSpec.FileBackend.Env(), apiSpec.S3Backend.Env())...)),
|
||||
Image: apiSpec.WorkloadSpec.Image(supabase.Images.Storage.String()),
|
||||
ImagePullPolicy: apiSpec.WorkloadSpec.ImagePullPolicy(),
|
||||
Env: apiSpec.WorkloadSpec.MergeEnv(append(storagApiEnv, slices.Concat(apiSpec.FileBackend.Env(), apiSpec.S3Backend.Env())...)),
|
||||
Ports: []corev1.ContainerPort{{
|
||||
Name: serviceCfg.Defaults.ApiPortName,
|
||||
ContainerPort: serviceCfg.Defaults.ApiPort,
|
||||
Protocol: corev1.ProtocolTCP,
|
||||
}},
|
||||
SecurityContext: apiSpec.WorkloadTemplate.ContainerSecurityContext(serviceCfg.Defaults.UID, serviceCfg.Defaults.GID),
|
||||
Resources: apiSpec.WorkloadTemplate.Resources(),
|
||||
VolumeMounts: apiSpec.WorkloadTemplate.AdditionalVolumeMounts(
|
||||
SecurityContext: apiSpec.WorkloadSpec.ContainerSecurityContext(serviceCfg.Defaults.UID, serviceCfg.Defaults.GID),
|
||||
Resources: apiSpec.WorkloadSpec.Resources(),
|
||||
VolumeMounts: apiSpec.WorkloadSpec.AdditionalVolumeMounts(
|
||||
corev1.VolumeMount{
|
||||
Name: "tmp",
|
||||
MountPath: "/tmp",
|
||||
|
@ -225,7 +226,7 @@ func (r *StorageApiReconciler) reconcileStorageApiDeployment(
|
|||
SuccessThreshold: 2,
|
||||
ProbeHandler: corev1.ProbeHandler{
|
||||
HTTPGet: &corev1.HTTPGetAction{
|
||||
Path: "/status",
|
||||
Path: serviceCfg.LivenessProbePath,
|
||||
Port: intstr.IntOrString{IntVal: serviceCfg.Defaults.ApiPort},
|
||||
},
|
||||
},
|
||||
|
@ -236,14 +237,14 @@ func (r *StorageApiReconciler) reconcileStorageApiDeployment(
|
|||
TimeoutSeconds: 3,
|
||||
ProbeHandler: corev1.ProbeHandler{
|
||||
HTTPGet: &corev1.HTTPGetAction{
|
||||
Path: "/status",
|
||||
Path: serviceCfg.LivenessProbePath,
|
||||
Port: intstr.IntOrString{IntVal: serviceCfg.Defaults.ApiPort},
|
||||
},
|
||||
},
|
||||
},
|
||||
}},
|
||||
SecurityContext: apiSpec.WorkloadTemplate.PodSecurityContext(),
|
||||
Volumes: apiSpec.WorkloadTemplate.Volumes(
|
||||
SecurityContext: apiSpec.WorkloadSpec.PodSecurityContext(),
|
||||
Volumes: apiSpec.WorkloadSpec.Volumes(
|
||||
corev1.Volume{
|
||||
Name: "tmp",
|
||||
VolumeSource: corev1.VolumeSource{
|
||||
|
@ -276,7 +277,7 @@ func (r *StorageApiReconciler) reconcileStorageApiService(
|
|||
)
|
||||
|
||||
_, err := controllerutil.CreateOrUpdate(ctx, r.Client, storageApiService, func() error {
|
||||
storageApiService.Labels = storage.Spec.Api.WorkloadTemplate.MergeLabels(
|
||||
storageApiService.Labels = storage.Spec.Api.WorkloadSpec.MergeLabels(
|
||||
objectLabels(storage, serviceCfg.Name, "storage", supabase.Images.Storage.Tag),
|
||||
storage.Labels,
|
||||
)
|
||||
|
|
|
@ -68,7 +68,7 @@ var _ = Describe("Storage Controller", func() {
|
|||
})
|
||||
It("should successfully reconcile the resource", func() {
|
||||
By("Reconciling the created resource")
|
||||
controllerReconciler := &StorageReconciler{
|
||||
controllerReconciler := &StorageApiReconciler{
|
||||
Client: k8sClient,
|
||||
Scheme: k8sClient.Scheme(),
|
||||
}
|
||||
|
|
|
@ -98,7 +98,7 @@ func (r *StorageImgProxyReconciler) reconcileImgProxyDeployment(
|
|||
)
|
||||
|
||||
_, err := controllerutil.CreateOrUpdate(ctx, r.Client, imgProxyDeployment, func() error {
|
||||
imgProxyDeployment.Labels = imgProxySpec.WorkloadTemplate.MergeLabels(
|
||||
imgProxyDeployment.Labels = imgProxySpec.WorkloadSpec.MergeLabels(
|
||||
objectLabels(storage, serviceCfg.Name, "storage", supabase.Images.ImgProxy.Tag),
|
||||
storage.Labels,
|
||||
)
|
||||
|
@ -119,27 +119,28 @@ func (r *StorageImgProxyReconciler) reconcileImgProxyDeployment(
|
|||
}
|
||||
}
|
||||
|
||||
imgProxyDeployment.Spec.Replicas = imgProxySpec.WorkloadTemplate.ReplicaCount()
|
||||
imgProxyDeployment.Spec.Replicas = imgProxySpec.WorkloadSpec.ReplicaCount()
|
||||
|
||||
imgProxyDeployment.Spec.Template = corev1.PodTemplateSpec{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Labels: objectLabels(storage, serviceCfg.Name, "storage", supabase.Images.ImgProxy.Tag),
|
||||
},
|
||||
Spec: corev1.PodSpec{
|
||||
ImagePullSecrets: imgProxySpec.WorkloadTemplate.PullSecrets(),
|
||||
ImagePullSecrets: imgProxySpec.WorkloadSpec.PullSecrets(),
|
||||
AutomountServiceAccountToken: ptrOf(false),
|
||||
Containers: []corev1.Container{{
|
||||
Name: "supabase-imgproxy",
|
||||
Image: imgProxySpec.WorkloadTemplate.Image(supabase.Images.ImgProxy.String()),
|
||||
ImagePullPolicy: imgProxySpec.WorkloadTemplate.ImagePullPolicy(),
|
||||
Env: imgProxySpec.WorkloadTemplate.MergeEnv(imgProxyEnv),
|
||||
Image: imgProxySpec.WorkloadSpec.Image(supabase.Images.ImgProxy.String()),
|
||||
ImagePullPolicy: imgProxySpec.WorkloadSpec.ImagePullPolicy(),
|
||||
Env: imgProxySpec.WorkloadSpec.MergeEnv(imgProxyEnv),
|
||||
Ports: []corev1.ContainerPort{{
|
||||
Name: serviceCfg.Defaults.ApiPortName,
|
||||
ContainerPort: serviceCfg.Defaults.ApiPort,
|
||||
Protocol: corev1.ProtocolTCP,
|
||||
}},
|
||||
SecurityContext: imgProxySpec.WorkloadTemplate.ContainerSecurityContext(serviceCfg.Defaults.UID, serviceCfg.Defaults.GID),
|
||||
Resources: imgProxySpec.WorkloadTemplate.Resources(),
|
||||
VolumeMounts: imgProxySpec.WorkloadTemplate.AdditionalVolumeMounts(),
|
||||
SecurityContext: imgProxySpec.WorkloadSpec.ContainerSecurityContext(serviceCfg.Defaults.UID, serviceCfg.Defaults.GID),
|
||||
Resources: imgProxySpec.WorkloadSpec.Resources(),
|
||||
VolumeMounts: imgProxySpec.WorkloadSpec.AdditionalVolumeMounts(),
|
||||
ReadinessProbe: &corev1.Probe{
|
||||
InitialDelaySeconds: 5,
|
||||
PeriodSeconds: 3,
|
||||
|
@ -162,8 +163,8 @@ func (r *StorageImgProxyReconciler) reconcileImgProxyDeployment(
|
|||
},
|
||||
},
|
||||
}},
|
||||
SecurityContext: imgProxySpec.WorkloadTemplate.PodSecurityContext(),
|
||||
Volumes: imgProxySpec.WorkloadTemplate.Volumes(),
|
||||
SecurityContext: imgProxySpec.WorkloadSpec.PodSecurityContext(),
|
||||
Volumes: imgProxySpec.WorkloadSpec.Volumes(),
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -184,12 +185,12 @@ func (r *StorageImgProxyReconciler) reconcileImgProxyService(
|
|||
var (
|
||||
serviceCfg = supabase.ServiceConfig.ImgProxy
|
||||
imgProxyService = &corev1.Service{
|
||||
ObjectMeta: supabase.ServiceConfig.Storage.ObjectMeta(storage),
|
||||
ObjectMeta: supabase.ServiceConfig.ImgProxy.ObjectMeta(storage),
|
||||
}
|
||||
)
|
||||
|
||||
_, err := controllerutil.CreateOrPatch(ctx, r.Client, imgProxyService, func() error {
|
||||
imgProxyService.Labels = storage.Spec.Api.WorkloadTemplate.MergeLabels(
|
||||
imgProxyService.Labels = storage.Spec.Api.WorkloadSpec.MergeLabels(
|
||||
objectLabels(storage, serviceCfg.Name, "storage", supabase.Images.ImgProxy.Tag),
|
||||
storage.Labels,
|
||||
)
|
||||
|
|
|
@ -91,15 +91,14 @@ func (r *StorageS3CredentialsReconciler) reconcileS3StorageSecret(
|
|||
},
|
||||
}
|
||||
|
||||
if err := r.Get(ctx, client.ObjectKeyFromObject(s3CredsSecret), s3CredsSecret); err != nil {
|
||||
_, err := controllerutil.CreateOrUpdate(ctx, r.Client, s3CredsSecret, func() error {
|
||||
return controllerutil.SetControllerReference(storage, s3CredsSecret, r.Scheme)
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := controllerutil.SetControllerReference(storage, s3CredsSecret, r.Scheme); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return r.Update(ctx, s3CredsSecret)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *StorageS3CredentialsReconciler) reconcileS3ProtoSecret(
|
||||
|
|
|
@ -15,13 +15,9 @@ dynamic_resources:
|
|||
|
||||
static_resources:
|
||||
clusters:
|
||||
- type: STRICT_DNS
|
||||
typed_extension_protocol_options:
|
||||
envoy.extensions.upstreams.http.v3.HttpProtocolOptions:
|
||||
"@type": type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions
|
||||
explicit_http_config:
|
||||
http2_protocol_options: {}
|
||||
name: {{ .ControlPlane.Name }}
|
||||
- name: {{ .ControlPlane.Name }}
|
||||
type: STRICT_DNS
|
||||
connect_timeout: 5s
|
||||
load_assignment:
|
||||
cluster_name: {{ .ControlPlane.Name }}
|
||||
endpoints:
|
||||
|
@ -31,9 +27,38 @@ static_resources:
|
|||
socket_address:
|
||||
address: {{ .ControlPlane.Host }}
|
||||
port_value: {{ .ControlPlane.Port }}
|
||||
typed_extension_protocol_options:
|
||||
envoy.extensions.upstreams.http.v3.HttpProtocolOptions:
|
||||
"@type": type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions
|
||||
explicit_http_config:
|
||||
http2_protocol_options: {}
|
||||
transport_socket:
|
||||
name: "envoy.transport_sockets.tls"
|
||||
typed_config:
|
||||
"@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext"
|
||||
sni: {{ .ControlPlane.Host }}
|
||||
common_tls_context:
|
||||
tls_certificates:
|
||||
- certificate_chain:
|
||||
filename: /etc/envoy/certs/cp/tls.crt
|
||||
private_key:
|
||||
filename: /etc/envoy/certs/cp/tls.key
|
||||
validation_context:
|
||||
trusted_ca:
|
||||
filename: /etc/envoy/certs/cp/ca.crt
|
||||
|
||||
admin:
|
||||
address:
|
||||
socket_address:
|
||||
address: 0.0.0.0
|
||||
port_value: 19000
|
||||
|
||||
|
||||
application_log_config:
|
||||
log_format:
|
||||
json_format:
|
||||
type: "app"
|
||||
name: "%n"
|
||||
timestamp: "%Y-%m-%dT%T.%F"
|
||||
level: "%l"
|
||||
message: "%j"
|
||||
|
|
|
@ -17,16 +17,14 @@ limitations under the License.
|
|||
package controlplane
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"hash/fnv"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
cachev3 "github.com/envoyproxy/go-control-plane/pkg/cache/v3"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
discoveryv1 "k8s.io/api/discovery/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
|
@ -63,7 +61,7 @@ func (r *APIGatewayReconciler) Reconcile(ctx context.Context, req ctrl.Request)
|
|||
endpointSliceList discoveryv1.EndpointSliceList
|
||||
)
|
||||
|
||||
logger.Info("Reconciling APIGateway")
|
||||
logger.Info("Reconciling Envoy control-plane config")
|
||||
|
||||
if err := r.Get(ctx, req.NamespacedName, &gateway); client.IgnoreNotFound(err) != nil {
|
||||
logger.Error(err, "unable to fetch Gateway")
|
||||
|
@ -79,25 +77,27 @@ func (r *APIGatewayReconciler) Reconcile(ctx context.Context, req ctrl.Request)
|
|||
return ctrl.Result{}, err
|
||||
}
|
||||
|
||||
services := EnvoyServices{ServiceLabelKey: gateway.Spec.ComponentTypeLabel}
|
||||
services := EnvoyServices{
|
||||
ServiceLabelKey: gateway.Spec.ComponentTypeLabel,
|
||||
Gateway: &gateway,
|
||||
Client: r.Client,
|
||||
}
|
||||
services.UpsertEndpointSlices(endpointSliceList.Items...)
|
||||
|
||||
rawServices, err := json.Marshal(services)
|
||||
var (
|
||||
instance = fmt.Sprintf("%s:%s", gateway.Spec.Envoy.NodeName, gateway.Namespace)
|
||||
snapshotVersion = strconv.FormatInt(time.Now().UTC().UnixMilli(), 10)
|
||||
)
|
||||
|
||||
logger.Info("Computing Envoy snapshot for current service targets", "version", snapshotVersion)
|
||||
snapshot, snapshotHash, err := services.snapshot(ctx, instance, snapshotVersion)
|
||||
if err != nil {
|
||||
return ctrl.Result{}, fmt.Errorf("failed to prepare config hash: %w", err)
|
||||
return ctrl.Result{}, fmt.Errorf("failed to prepare snapshot: %w", err)
|
||||
}
|
||||
|
||||
serviceHash := fnv.New64a().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 {
|
||||
opResult, err := controllerutil.CreateOrUpdate(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
|
||||
gateway.Status.Envoy.ResourceHash = snapshotHash
|
||||
|
||||
return nil
|
||||
})
|
||||
|
@ -105,19 +105,27 @@ func (r *APIGatewayReconciler) Reconcile(ctx context.Context, req ctrl.Request)
|
|||
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)
|
||||
if opResult == controllerutil.OperationResultNone {
|
||||
logger.Info("No changes detected in APIGateway object")
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
|
||||
logger.Info("Propagating Envoy snapshot", "version", gateway.Status.Envoy.ConfigVersion)
|
||||
logger.Info("Propagating Envoy snapshot", "version", snapshotVersion)
|
||||
if err := r.Cache.SetSnapshot(ctx, instance, snapshot); err != nil {
|
||||
return ctrl.Result{}, fmt.Errorf("failed to propagate snapshot: %w", err)
|
||||
}
|
||||
|
||||
if info := r.Cache.GetStatusInfo(instance); info != nil {
|
||||
node := info.GetNode()
|
||||
logger = logger.WithValues(
|
||||
"node.id", node.Id,
|
||||
"node.cluster", node.Cluster,
|
||||
"node.num_watches", info.GetNumWatches(),
|
||||
)
|
||||
}
|
||||
|
||||
logger.Info("Envoy snapshot propagated successfully")
|
||||
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
|
||||
|
@ -133,7 +141,8 @@ func (r *APIGatewayReconciler) SetupWithManager(mgr ctrl.Manager) error {
|
|||
}
|
||||
|
||||
return ctrl.NewControllerManagedBy(mgr).
|
||||
For(new(supabasev1alpha1.APIGateway)).
|
||||
For(new(supabasev1alpha1.APIGateway), builder.WithPredicates(predicate.GenerationChangedPredicate{})).
|
||||
Owns(new(corev1.Secret), builder.WithPredicates(predicate.GenerationChangedPredicate{})).
|
||||
Watches(
|
||||
new(discoveryv1.EndpointSlice),
|
||||
r.endpointSliceEventHandler(),
|
||||
|
@ -156,7 +165,8 @@ func (r *APIGatewayReconciler) endpointSliceEventHandler() handler.TypedEventHan
|
|||
return nil
|
||||
}
|
||||
|
||||
if err := r.Client.List(ctx, &apiGatewayList, client.InNamespace(endpointSlice.Namespace)); err != nil {
|
||||
logger.Info("Triggering APIGateway reconciliation", "obj_name", obj.GetName(), "obj_namespace", obj.GetNamespace())
|
||||
if err := r.List(ctx, &apiGatewayList, client.InNamespace(endpointSlice.Namespace)); err != nil {
|
||||
logger.Error(err, "failed to list APIGateways to determine reconcile targets")
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -161,48 +161,51 @@ func RBACRequireAuthConfig() *rbacv3.RBAC {
|
|||
OrIds: &rbacv3cfg.Principal_Set{
|
||||
Ids: []*rbacv3cfg.Principal{
|
||||
{
|
||||
Identifier: &rbacv3cfg.Principal_Metadata{
|
||||
Metadata: &matcherv3.MetadataMatcher{
|
||||
Filter: FilterNameJwtAuthn,
|
||||
Path: []*matcherv3.MetadataMatcher_PathSegment{
|
||||
{
|
||||
Segment: &matcherv3.MetadataMatcher_PathSegment_Key{
|
||||
Key: "jwt_payload",
|
||||
Identifier: &rbacv3cfg.Principal_SourcedMetadata{
|
||||
SourcedMetadata: &rbacv3cfg.SourcedMetadata{
|
||||
MetadataSource: rbacv3cfg.MetadataSource_DYNAMIC,
|
||||
MetadataMatcher: &matcherv3.MetadataMatcher{
|
||||
Filter: FilterNameJwtAuthn,
|
||||
Path: []*matcherv3.MetadataMatcher_PathSegment{
|
||||
{
|
||||
Segment: &matcherv3.MetadataMatcher_PathSegment_Key{
|
||||
Key: "jwt_payload",
|
||||
},
|
||||
},
|
||||
{
|
||||
Segment: &matcherv3.MetadataMatcher_PathSegment_Key{
|
||||
Key: "role",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Segment: &matcherv3.MetadataMatcher_PathSegment_Key{
|
||||
Key: "role",
|
||||
},
|
||||
},
|
||||
},
|
||||
Value: &matcherv3.ValueMatcher{
|
||||
MatchPattern: &matcherv3.ValueMatcher_OrMatch{
|
||||
OrMatch: &matcherv3.OrMatcher{
|
||||
ValueMatchers: []*matcherv3.ValueMatcher{
|
||||
{
|
||||
MatchPattern: &matcherv3.ValueMatcher_StringMatch{
|
||||
StringMatch: &matcherv3.StringMatcher{
|
||||
MatchPattern: &matcherv3.StringMatcher_Exact{
|
||||
Exact: "anon",
|
||||
Value: &matcherv3.ValueMatcher{
|
||||
MatchPattern: &matcherv3.ValueMatcher_OrMatch{
|
||||
OrMatch: &matcherv3.OrMatcher{
|
||||
ValueMatchers: []*matcherv3.ValueMatcher{
|
||||
{
|
||||
MatchPattern: &matcherv3.ValueMatcher_StringMatch{
|
||||
StringMatch: &matcherv3.StringMatcher{
|
||||
MatchPattern: &matcherv3.StringMatcher_Exact{
|
||||
Exact: "anon",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
MatchPattern: &matcherv3.ValueMatcher_StringMatch{
|
||||
StringMatch: &matcherv3.StringMatcher{
|
||||
MatchPattern: &matcherv3.StringMatcher_Exact{
|
||||
Exact: "authenticated",
|
||||
{
|
||||
MatchPattern: &matcherv3.ValueMatcher_StringMatch{
|
||||
StringMatch: &matcherv3.StringMatcher{
|
||||
MatchPattern: &matcherv3.StringMatcher_Exact{
|
||||
Exact: "authenticated",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
MatchPattern: &matcherv3.ValueMatcher_StringMatch{
|
||||
StringMatch: &matcherv3.StringMatcher{
|
||||
MatchPattern: &matcherv3.StringMatcher_Exact{
|
||||
Exact: "admin",
|
||||
{
|
||||
MatchPattern: &matcherv3.ValueMatcher_StringMatch{
|
||||
StringMatch: &matcherv3.StringMatcher{
|
||||
MatchPattern: &matcherv3.StringMatcher_Exact{
|
||||
Exact: "admin",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -17,9 +17,7 @@ limitations under the License.
|
|||
package controlplane
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
|
@ -30,27 +28,10 @@ import (
|
|||
discoveryv1 "k8s.io/api/discovery/v1"
|
||||
)
|
||||
|
||||
var _ json.Marshaler = (*ServiceCluster)(nil)
|
||||
|
||||
type ServiceCluster struct {
|
||||
ServiceEndpoints map[string]Endpoints
|
||||
}
|
||||
|
||||
// MarshalJSON implements json.Marshaler.
|
||||
func (c *ServiceCluster) MarshalJSON() ([]byte, error) {
|
||||
tmp := struct {
|
||||
Endpoints []string `json:"endpoints"`
|
||||
}{}
|
||||
|
||||
for _, endpoints := range c.ServiceEndpoints {
|
||||
tmp.Endpoints = append(tmp.Endpoints, endpoints.Targets...)
|
||||
}
|
||||
|
||||
slices.Sort(tmp.Endpoints)
|
||||
|
||||
return json.Marshal(tmp)
|
||||
}
|
||||
|
||||
func (c *ServiceCluster) AddOrUpdateEndpoints(eps discoveryv1.EndpointSlice) {
|
||||
if c.ServiceEndpoints == nil {
|
||||
c.ServiceEndpoints = make(map[string]Endpoints)
|
||||
|
|
|
@ -22,4 +22,7 @@ const (
|
|||
FilterNameCORS = "envoy.filters.http.cors"
|
||||
FilterNameHttpRouter = "envoy.filters.http.router"
|
||||
FilterNameHttpConnectionManager = "envoy.filters.network.http_connection_manager"
|
||||
FilterNameBasicAuth = "envoy.filters.http.basic_auth"
|
||||
FilterNameOAuth2 = "envoy.filters.http.oauth2"
|
||||
SocketNameTLS = "envoy.transport_sockets.tls"
|
||||
)
|
||||
|
|
62
internal/controlplane/logging.go
Normal file
62
internal/controlplane/logging.go
Normal file
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
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 (
|
||||
accesslogv3 "github.com/envoyproxy/go-control-plane/envoy/config/accesslog/v3"
|
||||
corev3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
|
||||
streamv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/access_loggers/stream/v3"
|
||||
"google.golang.org/protobuf/types/known/structpb"
|
||||
)
|
||||
|
||||
func AccessLog(name string) *accesslogv3.AccessLog {
|
||||
return &accesslogv3.AccessLog{
|
||||
Name: name,
|
||||
Filter: &accesslogv3.AccessLogFilter{
|
||||
FilterSpecifier: &accesslogv3.AccessLogFilter_NotHealthCheckFilter{
|
||||
NotHealthCheckFilter: new(accesslogv3.NotHealthCheckFilter),
|
||||
},
|
||||
},
|
||||
ConfigType: &accesslogv3.AccessLog_TypedConfig{
|
||||
TypedConfig: MustAny(&streamv3.StderrAccessLog{
|
||||
AccessLogFormat: &streamv3.StderrAccessLog_LogFormat{
|
||||
LogFormat: &corev3.SubstitutionFormatString{
|
||||
Format: &corev3.SubstitutionFormatString_JsonFormat{
|
||||
JsonFormat: &structpb.Struct{
|
||||
Fields: map[string]*structpb.Value{
|
||||
"type": {Kind: &structpb.Value_StringValue{StringValue: "access_log"}},
|
||||
"timestamp": {Kind: &structpb.Value_StringValue{StringValue: "%START_TIME%"}},
|
||||
"protocol": {Kind: &structpb.Value_StringValue{StringValue: "%PROTOCOL%"}},
|
||||
"http_method": {Kind: &structpb.Value_StringValue{StringValue: "%REQ(:METHOD)%"}},
|
||||
"original_path": {Kind: &structpb.Value_StringValue{StringValue: "%REQ(X-ENVOY-ORIGINAL-PATH?:PATH)%"}},
|
||||
"forwarded_for": {Kind: &structpb.Value_StringValue{StringValue: "%REQ(X-FORWARDED-FOR)%"}},
|
||||
"user_agent": {Kind: &structpb.Value_StringValue{StringValue: "%REQ(USER-AGENT)%"}},
|
||||
"request_id": {Kind: &structpb.Value_StringValue{StringValue: "%REQ(X-REQUEST-ID)%"}},
|
||||
"authority": {Kind: &structpb.Value_StringValue{StringValue: "%REQ(:AUTHORITY)%"}},
|
||||
"upstream_host": {Kind: &structpb.Value_StringValue{StringValue: "%UPSTREAM_HOST%"}},
|
||||
"response_code": {Kind: &structpb.Value_StringValue{StringValue: "%RESPONSE_CODE%"}},
|
||||
"response_code_details": {Kind: &structpb.Value_StringValue{StringValue: "%RESPONSE_CODE_DETAILS%"}},
|
||||
"trace_id": {Kind: &structpb.Value_StringValue{StringValue: "%TRACE_ID%"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
}
|
||||
}
|
|
@ -18,30 +18,47 @@ package controlplane
|
|||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"slices"
|
||||
|
||||
accesslogv3 "github.com/envoyproxy/go-control-plane/envoy/config/accesslog/v3"
|
||||
corev3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
|
||||
listenerv3 "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3"
|
||||
route "github.com/envoyproxy/go-control-plane/envoy/config/route/v3"
|
||||
router "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/router/v3"
|
||||
routev3 "github.com/envoyproxy/go-control-plane/envoy/config/route/v3"
|
||||
routerv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/router/v3"
|
||||
hcm "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3"
|
||||
tlsv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3"
|
||||
"github.com/envoyproxy/go-control-plane/pkg/cache/types"
|
||||
"github.com/envoyproxy/go-control-plane/pkg/cache/v3"
|
||||
"github.com/envoyproxy/go-control-plane/pkg/resource/v3"
|
||||
"google.golang.org/protobuf/proto"
|
||||
"google.golang.org/protobuf/types/known/anypb"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
discoveryv1 "k8s.io/api/discovery/v1"
|
||||
"sigs.k8s.io/controller-runtime/pkg/log"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
supabasev1alpha1 "code.icb4dc0.de/prskr/supabase-operator/api/v1alpha1"
|
||||
"code.icb4dc0.de/prskr/supabase-operator/internal/supabase"
|
||||
)
|
||||
|
||||
const (
|
||||
studioRouteName = "supabase-studio"
|
||||
dashboardOAuth2ClusterName = "dashboard-oauth"
|
||||
apiRouteName = "supabase-api"
|
||||
apilistenerName = "supabase-api"
|
||||
)
|
||||
|
||||
type EnvoyServices struct {
|
||||
ServiceLabelKey string `json:"-"`
|
||||
Postgrest *PostgrestCluster `json:"postgrest,omitempty"`
|
||||
GoTrue *GoTrueCluster `json:"auth,omitempty"`
|
||||
StorageApi *StorageApiCluster `json:"storageApi,omitempty"`
|
||||
PGMeta *PGMetaCluster `json:"pgmeta,omitempty"`
|
||||
Studio *StudioCluster `json:"studio,omitempty"`
|
||||
client.Client
|
||||
ServiceLabelKey string
|
||||
Gateway *supabasev1alpha1.APIGateway
|
||||
Postgrest *PostgrestCluster
|
||||
GoTrue *GoTrueCluster
|
||||
StorageApi *StorageApiCluster
|
||||
PGMeta *PGMetaCluster
|
||||
Studio *StudioCluster
|
||||
}
|
||||
|
||||
func (s *EnvoyServices) UpsertEndpointSlices(endpointSlices ...discoveryv1.EndpointSlice) {
|
||||
|
@ -67,6 +84,12 @@ func (s *EnvoyServices) UpsertEndpointSlices(endpointSlices ...discoveryv1.Endpo
|
|||
s.PGMeta = new(PGMetaCluster)
|
||||
}
|
||||
s.PGMeta.AddOrUpdateEndpoints(eps)
|
||||
case supabase.ServiceConfig.Studio.Name:
|
||||
if s.Studio == nil {
|
||||
s.Studio = &StudioCluster{Client: s.Client}
|
||||
}
|
||||
|
||||
s.Studio.AddOrUpdateEndpoints(eps)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -97,19 +120,176 @@ func (s EnvoyServices) Targets() map[string][]string {
|
|||
return targets
|
||||
}
|
||||
|
||||
func (s *EnvoyServices) snapshot(ctx context.Context, instance, version string) (*cache.Snapshot, error) {
|
||||
const (
|
||||
apiRouteName = "supabase"
|
||||
studioRouteName = "supabas-studio"
|
||||
vHostName = "supabase"
|
||||
listenerName = "supabase"
|
||||
func (s *EnvoyServices) snapshot(
|
||||
ctx context.Context,
|
||||
instance, version string,
|
||||
) (snapshot *cache.Snapshot, snapshotHash []byte, err error) {
|
||||
socket, err := s.apiTransportSocket(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to setup API TLS listener: %w", err)
|
||||
}
|
||||
|
||||
listeners := []*listenerv3.Listener{{
|
||||
Name: apilistenerName,
|
||||
Address: &corev3.Address{
|
||||
Address: &corev3.Address_SocketAddress{
|
||||
SocketAddress: &corev3.SocketAddress{
|
||||
Protocol: corev3.SocketAddress_TCP,
|
||||
Address: "0.0.0.0",
|
||||
PortSpecifier: &corev3.SocketAddress_PortValue{
|
||||
PortValue: 8000,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
FilterChains: []*listenerv3.FilterChain{
|
||||
{
|
||||
Filters: []*listenerv3.Filter{
|
||||
{
|
||||
Name: FilterNameHttpConnectionManager,
|
||||
ConfigType: &listenerv3.Filter_TypedConfig{
|
||||
TypedConfig: MustAny(s.apiConnectionManager()),
|
||||
},
|
||||
},
|
||||
},
|
||||
TransportSocket: socket,
|
||||
},
|
||||
},
|
||||
}}
|
||||
|
||||
if studioListener, err := s.Studio.Listener(ctx, instance, s.Gateway); err != nil {
|
||||
return nil, nil, err
|
||||
} else if studioListener != nil {
|
||||
listeners = append(listeners, studioListener)
|
||||
}
|
||||
|
||||
routes := []types.Resource{s.apiRouteConfiguration(instance)}
|
||||
|
||||
if studioRouteCfg := s.Studio.RouteConfiguration(instance); studioRouteCfg != nil {
|
||||
routes = append(routes, studioRouteCfg)
|
||||
}
|
||||
|
||||
studioClusters, err := s.Studio.Cluster(instance, s.Gateway)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
clusters := castResources(
|
||||
slices.Concat(
|
||||
s.Postgrest.Cluster(instance),
|
||||
s.GoTrue.Cluster(instance),
|
||||
s.StorageApi.Cluster(instance),
|
||||
s.PGMeta.Cluster(instance),
|
||||
studioClusters,
|
||||
)...)
|
||||
|
||||
sdsSecrets, err := s.secrets(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to collect dynamic secrets: %w", err)
|
||||
}
|
||||
|
||||
rawSnapshot := map[resource.Type][]types.Resource{
|
||||
resource.ClusterType: clusters,
|
||||
resource.RouteType: routes,
|
||||
resource.ListenerType: castResources(listeners...),
|
||||
resource.SecretType: sdsSecrets,
|
||||
}
|
||||
|
||||
hash := sha256.New()
|
||||
|
||||
for _, resType := range []resource.Type{resource.ClusterType, resource.RouteType, resource.ListenerType, resource.SecretType} {
|
||||
for _, resource := range rawSnapshot[resType] {
|
||||
if raw, err := proto.Marshal(resource); err != nil {
|
||||
return nil, nil, err
|
||||
} else {
|
||||
_, _ = hash.Write(raw)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
snapshot, err = cache.NewSnapshot(
|
||||
version,
|
||||
rawSnapshot,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if err := snapshot.Consistent(); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return snapshot, hash.Sum(nil), nil
|
||||
}
|
||||
|
||||
func (s *EnvoyServices) secrets(ctx context.Context) ([]types.Resource, error) {
|
||||
var (
|
||||
serviceConfig = supabase.ServiceConfig.Envoy
|
||||
resources = make([]types.Resource, 0, 2)
|
||||
)
|
||||
|
||||
logger := log.FromContext(ctx)
|
||||
hmacSecret, err := s.k8sSecretKeyToSecret(
|
||||
ctx,
|
||||
serviceConfig.HmacSecretName(s.Gateway),
|
||||
serviceConfig.Defaults.HmacSecretKey,
|
||||
serviceConfig.Defaults.HmacSecretKey,
|
||||
)
|
||||
|
||||
apiConnectionManager := &hcm.HttpConnectionManager{
|
||||
if err == nil {
|
||||
resources = append(resources, hmacSecret)
|
||||
} else if client.IgnoreNotFound(err) != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if oauth2Spec := s.Gateway.Spec.DashboardEndpoint.OAuth2(); oauth2Spec != nil {
|
||||
oauth2ClientSecret, err := s.k8sSecretKeyToSecret(
|
||||
ctx,
|
||||
oauth2Spec.ClientSecretRef.Name,
|
||||
oauth2Spec.ClientSecretRef.Key,
|
||||
serviceConfig.Defaults.OAuth2ClientSecretKey,
|
||||
)
|
||||
|
||||
if err == nil {
|
||||
resources = append(resources, oauth2ClientSecret)
|
||||
} else if client.IgnoreNotFound(err) != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return resources, nil
|
||||
}
|
||||
|
||||
func (s *EnvoyServices) k8sSecretKeyToSecret(ctx context.Context, secretName, secretKey, envoySecretName string) (*tlsv3.Secret, error) {
|
||||
k8sSecret := &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: secretName,
|
||||
Namespace: s.Gateway.Namespace,
|
||||
},
|
||||
}
|
||||
|
||||
if err := s.Get(ctx, client.ObjectKeyFromObject(k8sSecret), k8sSecret); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &tlsv3.Secret{
|
||||
Name: envoySecretName,
|
||||
Type: &tlsv3.Secret_GenericSecret{
|
||||
GenericSecret: &tlsv3.GenericSecret{
|
||||
Secret: &corev3.DataSource{
|
||||
Specifier: &corev3.DataSource_InlineBytes{
|
||||
InlineBytes: k8sSecret.Data[secretKey],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *EnvoyServices) apiConnectionManager() *hcm.HttpConnectionManager {
|
||||
return &hcm.HttpConnectionManager{
|
||||
CodecType: hcm.HttpConnectionManager_AUTO,
|
||||
StatPrefix: "http",
|
||||
StatPrefix: "supabase_rest",
|
||||
AccessLog: []*accesslogv3.AccessLog{AccessLog("supabase-rest-access-log")},
|
||||
RouteSpecifier: &hcm.HttpConnectionManager_Rds{
|
||||
Rds: &hcm.Rds{
|
||||
ConfigSource: &corev3.ConfigSource{
|
||||
|
@ -141,45 +321,16 @@ func (s *EnvoyServices) snapshot(ctx context.Context, instance, version string)
|
|||
},
|
||||
{
|
||||
Name: FilterNameHttpRouter,
|
||||
ConfigType: &hcm.HttpFilter_TypedConfig{TypedConfig: MustAny(new(router.Router))},
|
||||
ConfigType: &hcm.HttpFilter_TypedConfig{TypedConfig: MustAny(new(routerv3.Router))},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
studioConnetionManager := &hcm.HttpConnectionManager{
|
||||
CodecType: hcm.HttpConnectionManager_AUTO,
|
||||
StatPrefix: "http",
|
||||
RouteSpecifier: &hcm.HttpConnectionManager_Rds{
|
||||
Rds: &hcm.Rds{
|
||||
ConfigSource: &corev3.ConfigSource{
|
||||
ResourceApiVersion: resource.DefaultAPIVersion,
|
||||
ConfigSourceSpecifier: &corev3.ConfigSource_ApiConfigSource{
|
||||
ApiConfigSource: &corev3.ApiConfigSource{
|
||||
TransportApiVersion: resource.DefaultAPIVersion,
|
||||
ApiType: corev3.ApiConfigSource_GRPC,
|
||||
SetNodeOnFirstMessageOnly: true,
|
||||
GrpcServices: []*corev3.GrpcService{{
|
||||
TargetSpecifier: &corev3.GrpcService_EnvoyGrpc_{
|
||||
EnvoyGrpc: &corev3.GrpcService_EnvoyGrpc{ClusterName: "supabase-control-plane"},
|
||||
},
|
||||
}},
|
||||
},
|
||||
},
|
||||
},
|
||||
RouteConfigName: studioRouteName,
|
||||
},
|
||||
},
|
||||
HttpFilters: []*hcm.HttpFilter{
|
||||
{
|
||||
Name: FilterNameHttpRouter,
|
||||
ConfigType: &hcm.HttpFilter_TypedConfig{TypedConfig: MustAny(new(router.Router))},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
apiRouteCfg := &route.RouteConfiguration{
|
||||
func (s *EnvoyServices) apiRouteConfiguration(instance string) *routev3.RouteConfiguration {
|
||||
return &routev3.RouteConfiguration{
|
||||
Name: apiRouteName,
|
||||
VirtualHosts: []*route.VirtualHost{{
|
||||
VirtualHosts: []*routev3.VirtualHost{{
|
||||
Name: "supabase",
|
||||
Domains: []string{"*"},
|
||||
TypedPerFilterConfig: map[string]*anypb.Any{
|
||||
|
@ -197,92 +348,59 @@ func (s *EnvoyServices) snapshot(ctx context.Context, instance, version string)
|
|||
FilterNameCORS: MustAny(CorsPolicy()),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// TODO add studio route config
|
||||
func (s *EnvoyServices) apiTransportSocket(ctx context.Context) (*corev3.TransportSocket, error) {
|
||||
tlsSpec := s.Gateway.Spec.ApiEndpoint.TLSSpec()
|
||||
if tlsSpec == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
listeners := []*listenerv3.Listener{{
|
||||
Name: listenerName,
|
||||
Address: &corev3.Address{
|
||||
Address: &corev3.Address_SocketAddress{
|
||||
SocketAddress: &corev3.SocketAddress{
|
||||
Protocol: corev3.SocketAddress_TCP,
|
||||
Address: "0.0.0.0",
|
||||
PortSpecifier: &corev3.SocketAddress_PortValue{
|
||||
PortValue: 8000,
|
||||
},
|
||||
},
|
||||
},
|
||||
apiTlsSecret := corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: tlsSpec.Cert.SecretName,
|
||||
Namespace: s.Gateway.Namespace,
|
||||
},
|
||||
FilterChains: []*listenerv3.FilterChain{
|
||||
{
|
||||
Filters: []*listenerv3.Filter{
|
||||
{
|
||||
Name: FilterNameHttpConnectionManager,
|
||||
ConfigType: &listenerv3.Filter_TypedConfig{
|
||||
TypedConfig: MustAny(apiConnectionManager),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}}
|
||||
}
|
||||
|
||||
if s.Studio != nil {
|
||||
logger.Info("Adding studio listener")
|
||||
if err := s.Get(ctx, client.ObjectKeyFromObject(&apiTlsSecret), &apiTlsSecret); err != nil {
|
||||
if client.IgnoreNotFound(err) == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
listeners = append(listeners, &listenerv3.Listener{
|
||||
Name: "studio",
|
||||
Address: &corev3.Address{
|
||||
Address: &corev3.Address_SocketAddress{
|
||||
SocketAddress: &corev3.SocketAddress{
|
||||
Protocol: corev3.SocketAddress_TCP,
|
||||
Address: "0.0.0.0",
|
||||
PortSpecifier: &corev3.SocketAddress_PortValue{
|
||||
PortValue: 3000,
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &corev3.TransportSocket{
|
||||
Name: SocketNameTLS,
|
||||
ConfigType: &corev3.TransportSocket_TypedConfig{
|
||||
TypedConfig: MustAny(&tlsv3.DownstreamTlsContext{
|
||||
CommonTlsContext: &tlsv3.CommonTlsContext{
|
||||
TlsCertificates: []*tlsv3.TlsCertificate{{
|
||||
CertificateChain: &corev3.DataSource{
|
||||
Specifier: &corev3.DataSource_InlineBytes{
|
||||
InlineBytes: apiTlsSecret.Data[corev1.TLSCertKey],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
FilterChains: []*listenerv3.FilterChain{
|
||||
{
|
||||
Filters: []*listenerv3.Filter{
|
||||
{
|
||||
Name: FilterNameHttpConnectionManager,
|
||||
ConfigType: &listenerv3.Filter_TypedConfig{
|
||||
TypedConfig: MustAny(studioConnetionManager),
|
||||
PrivateKey: &corev3.DataSource{
|
||||
Specifier: &corev3.DataSource_InlineBytes{
|
||||
InlineBytes: apiTlsSecret.Data[corev1.TLSPrivateKeyKey],
|
||||
},
|
||||
},
|
||||
}},
|
||||
ValidationContextType: &tlsv3.CommonTlsContext_ValidationContext{
|
||||
ValidationContext: &tlsv3.CertificateValidationContext{
|
||||
TrustedCa: &corev3.DataSource{
|
||||
Specifier: &corev3.DataSource_InlineBytes{
|
||||
InlineBytes: apiTlsSecret.Data["ca.crt"],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
rawSnapshot := map[resource.Type][]types.Resource{
|
||||
resource.ClusterType: castResources(
|
||||
slices.Concat(
|
||||
s.Postgrest.Cluster(instance),
|
||||
s.GoTrue.Cluster(instance),
|
||||
s.StorageApi.Cluster(instance),
|
||||
s.PGMeta.Cluster(instance),
|
||||
)...),
|
||||
resource.RouteType: {apiRouteCfg},
|
||||
resource.ListenerType: castResources(listeners...),
|
||||
}
|
||||
|
||||
snapshot, err := cache.NewSnapshot(
|
||||
version,
|
||||
rawSnapshot,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := snapshot.Consistent(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return snapshot, nil
|
||||
}),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func castResources[T types.Resource](from ...T) []types.Resource {
|
||||
|
|
|
@ -16,6 +16,419 @@ limitations under the License.
|
|||
|
||||
package controlplane
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha1"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
accesslogv3 "github.com/envoyproxy/go-control-plane/envoy/config/accesslog/v3"
|
||||
clusterv3 "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3"
|
||||
corev3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
|
||||
endpointv3 "github.com/envoyproxy/go-control-plane/envoy/config/endpoint/v3"
|
||||
listenerv3 "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3"
|
||||
routev3 "github.com/envoyproxy/go-control-plane/envoy/config/route/v3"
|
||||
basic_authv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/basic_auth/v3"
|
||||
oauth2v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/oauth2/v3"
|
||||
routerv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/router/v3"
|
||||
hcm "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3"
|
||||
tlsv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3"
|
||||
matcherv3 "github.com/envoyproxy/go-control-plane/envoy/type/matcher/v3"
|
||||
"github.com/envoyproxy/go-control-plane/pkg/resource/v3"
|
||||
"google.golang.org/protobuf/types/known/durationpb"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
supabasev1alpha1 "code.icb4dc0.de/prskr/supabase-operator/api/v1alpha1"
|
||||
"code.icb4dc0.de/prskr/supabase-operator/internal/supabase"
|
||||
)
|
||||
|
||||
type StudioCluster struct {
|
||||
client.Client
|
||||
ServiceCluster
|
||||
}
|
||||
|
||||
func (c *StudioCluster) Cluster(instance string, gateway *supabasev1alpha1.APIGateway) ([]*clusterv3.Cluster, error) {
|
||||
if c == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
serviceCfg := supabase.ServiceConfig.Studio
|
||||
|
||||
clusters := []*clusterv3.Cluster{
|
||||
c.ServiceCluster.Cluster(fmt.Sprintf("%s@%s", serviceCfg.Name, instance), uint32(serviceCfg.Defaults.APIPort)),
|
||||
}
|
||||
|
||||
if gateway.Spec.DashboardEndpoint.AuthType() == supabasev1alpha1.DashboardAuthTypeOAuth2 {
|
||||
if tokenEndpointCluster, err := c.oauth2TokenEndpointCluster(instance, gateway); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
clusters = append(clusters, tokenEndpointCluster)
|
||||
}
|
||||
}
|
||||
|
||||
return clusters, nil
|
||||
}
|
||||
|
||||
func (s *StudioCluster) Listener(ctx context.Context, instance string, gateway *supabasev1alpha1.APIGateway) (*listenerv3.Listener, error) {
|
||||
if s == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var httpFilters []*hcm.HttpFilter
|
||||
|
||||
switch gateway.Spec.DashboardEndpoint.AuthType() {
|
||||
case supabasev1alpha1.DashboardAuthTypeOAuth2:
|
||||
httpFilters = append(httpFilters, s.oauth2HttpFilter(instance, gateway))
|
||||
case supabasev1alpha1.DashboardAuthTypeBasic:
|
||||
if filter, err := s.basicAuthHttpFilter(ctx, gateway); err != nil {
|
||||
return nil, err
|
||||
} else if filter != nil {
|
||||
httpFilters = append(httpFilters, filter)
|
||||
}
|
||||
}
|
||||
|
||||
studioConnetionManager := &hcm.HttpConnectionManager{
|
||||
CodecType: hcm.HttpConnectionManager_AUTO,
|
||||
StatPrefix: "supbase_studio",
|
||||
AccessLog: []*accesslogv3.AccessLog{AccessLog("supbase_studio_access_log")},
|
||||
RouteSpecifier: &hcm.HttpConnectionManager_Rds{
|
||||
Rds: &hcm.Rds{
|
||||
ConfigSource: &corev3.ConfigSource{
|
||||
ResourceApiVersion: resource.DefaultAPIVersion,
|
||||
ConfigSourceSpecifier: &corev3.ConfigSource_ApiConfigSource{
|
||||
ApiConfigSource: &corev3.ApiConfigSource{
|
||||
TransportApiVersion: resource.DefaultAPIVersion,
|
||||
ApiType: corev3.ApiConfigSource_GRPC,
|
||||
SetNodeOnFirstMessageOnly: true,
|
||||
GrpcServices: []*corev3.GrpcService{{
|
||||
TargetSpecifier: &corev3.GrpcService_EnvoyGrpc_{
|
||||
EnvoyGrpc: &corev3.GrpcService_EnvoyGrpc{ClusterName: "supabase-control-plane"},
|
||||
},
|
||||
}},
|
||||
},
|
||||
},
|
||||
},
|
||||
RouteConfigName: studioRouteName,
|
||||
},
|
||||
},
|
||||
HttpFilters: append(httpFilters, &hcm.HttpFilter{
|
||||
Name: FilterNameHttpRouter,
|
||||
ConfigType: &hcm.HttpFilter_TypedConfig{TypedConfig: MustAny(new(routerv3.Router))},
|
||||
}),
|
||||
}
|
||||
|
||||
socket, err := s.dashboardTransportSocket(ctx, gateway)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to setup dashboard TLS listener: %w", err)
|
||||
}
|
||||
|
||||
return &listenerv3.Listener{
|
||||
Name: "studio",
|
||||
Address: &corev3.Address{
|
||||
Address: &corev3.Address_SocketAddress{
|
||||
SocketAddress: &corev3.SocketAddress{
|
||||
Protocol: corev3.SocketAddress_TCP,
|
||||
Address: "0.0.0.0",
|
||||
PortSpecifier: &corev3.SocketAddress_PortValue{
|
||||
PortValue: 3000,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
FilterChains: []*listenerv3.FilterChain{
|
||||
{
|
||||
Filters: []*listenerv3.Filter{
|
||||
{
|
||||
Name: FilterNameHttpConnectionManager,
|
||||
ConfigType: &listenerv3.Filter_TypedConfig{
|
||||
TypedConfig: MustAny(studioConnetionManager),
|
||||
},
|
||||
},
|
||||
},
|
||||
TransportSocket: socket,
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *StudioCluster) RouteConfiguration(instance string) *routev3.RouteConfiguration {
|
||||
if s == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &routev3.RouteConfiguration{
|
||||
Name: studioRouteName,
|
||||
VirtualHosts: []*routev3.VirtualHost{{
|
||||
Name: "supabase-studio",
|
||||
Domains: []string{"*"},
|
||||
Routes: []*routev3.Route{{
|
||||
Name: "Studio: /* -> http://studio:3000/*",
|
||||
Match: &routev3.RouteMatch{
|
||||
PathSpecifier: &routev3.RouteMatch_Prefix{
|
||||
Prefix: "/",
|
||||
},
|
||||
},
|
||||
Action: &routev3.Route_Route{
|
||||
Route: &routev3.RouteAction{
|
||||
ClusterSpecifier: &routev3.RouteAction_Cluster{
|
||||
Cluster: fmt.Sprintf("%s@%s", supabase.ServiceConfig.Studio.Name, instance),
|
||||
},
|
||||
PrefixRewrite: "/",
|
||||
},
|
||||
},
|
||||
}},
|
||||
}},
|
||||
}
|
||||
}
|
||||
|
||||
func (s *StudioCluster) oauth2TokenEndpointCluster(
|
||||
instance string,
|
||||
gateway *supabasev1alpha1.APIGateway,
|
||||
) (*clusterv3.Cluster, error) {
|
||||
parsedTokenEndpoint, err := url.Parse(gateway.Spec.DashboardEndpoint.OAuth2().TokenEndpoint)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse token endpoint: %w", err)
|
||||
}
|
||||
|
||||
var (
|
||||
endpointPort uint32
|
||||
tls bool
|
||||
)
|
||||
switch parsedTokenEndpoint.Scheme {
|
||||
case "http":
|
||||
endpointPort = 80
|
||||
case "https":
|
||||
endpointPort = 443
|
||||
tls = true
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported token endpoint scheme: %s", parsedTokenEndpoint.Scheme)
|
||||
}
|
||||
|
||||
if tokenEndpointPort := parsedTokenEndpoint.Port(); tokenEndpointPort != "" {
|
||||
if parsedPort, err := strconv.ParseUint(tokenEndpointPort, 10, 32); err != nil {
|
||||
return nil, fmt.Errorf("failed to parse token endpoint port: %w", err)
|
||||
} else {
|
||||
endpointPort = uint32(parsedPort)
|
||||
}
|
||||
}
|
||||
|
||||
cluster := &clusterv3.Cluster{
|
||||
Name: fmt.Sprintf("%s@%s", dashboardOAuth2ClusterName, instance),
|
||||
ConnectTimeout: durationpb.New(3 * time.Second),
|
||||
ClusterDiscoveryType: &clusterv3.Cluster_Type{
|
||||
Type: clusterv3.Cluster_LOGICAL_DNS,
|
||||
},
|
||||
LbPolicy: clusterv3.Cluster_ROUND_ROBIN,
|
||||
LoadAssignment: &endpointv3.ClusterLoadAssignment{
|
||||
ClusterName: dashboardOAuth2ClusterName,
|
||||
Endpoints: []*endpointv3.LocalityLbEndpoints{{
|
||||
LbEndpoints: []*endpointv3.LbEndpoint{{
|
||||
HostIdentifier: &endpointv3.LbEndpoint_Endpoint{
|
||||
Endpoint: &endpointv3.Endpoint{
|
||||
Address: &corev3.Address{
|
||||
Address: &corev3.Address_SocketAddress{
|
||||
SocketAddress: &corev3.SocketAddress{
|
||||
Address: parsedTokenEndpoint.Hostname(),
|
||||
PortSpecifier: &corev3.SocketAddress_PortValue{
|
||||
PortValue: endpointPort,
|
||||
},
|
||||
Protocol: corev3.SocketAddress_TCP,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}},
|
||||
}},
|
||||
},
|
||||
}
|
||||
|
||||
if gateway.Spec.Envoy != nil && gateway.Spec.Envoy.DisableIPv6 {
|
||||
cluster.DnsLookupFamily = clusterv3.Cluster_V4_ONLY
|
||||
}
|
||||
|
||||
if tls {
|
||||
cluster.TransportSocket = &corev3.TransportSocket{
|
||||
Name: "envoy.transport_sockets.tls",
|
||||
ConfigType: &corev3.TransportSocket_TypedConfig{
|
||||
TypedConfig: MustAny(&tlsv3.UpstreamTlsContext{
|
||||
CommonTlsContext: new(tlsv3.CommonTlsContext),
|
||||
}),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return cluster, nil
|
||||
}
|
||||
|
||||
func (s *StudioCluster) dashboardTransportSocket(
|
||||
ctx context.Context,
|
||||
gateway *supabasev1alpha1.APIGateway,
|
||||
) (*corev3.TransportSocket, error) {
|
||||
tlsSpec := gateway.Spec.DashboardEndpoint.TLSSpec()
|
||||
if tlsSpec == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
dashboardTlsSecret := corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: tlsSpec.Cert.SecretName,
|
||||
Namespace: gateway.Namespace,
|
||||
},
|
||||
}
|
||||
|
||||
if err := s.Get(ctx, client.ObjectKeyFromObject(&dashboardTlsSecret), &dashboardTlsSecret); err != nil {
|
||||
if client.IgnoreNotFound(err) == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &corev3.TransportSocket{
|
||||
Name: SocketNameTLS,
|
||||
ConfigType: &corev3.TransportSocket_TypedConfig{
|
||||
TypedConfig: MustAny(&tlsv3.DownstreamTlsContext{
|
||||
CommonTlsContext: &tlsv3.CommonTlsContext{
|
||||
TlsCertificates: []*tlsv3.TlsCertificate{{
|
||||
CertificateChain: &corev3.DataSource{
|
||||
Specifier: &corev3.DataSource_InlineBytes{
|
||||
InlineBytes: dashboardTlsSecret.Data[corev1.TLSCertKey],
|
||||
},
|
||||
},
|
||||
PrivateKey: &corev3.DataSource{
|
||||
Specifier: &corev3.DataSource_InlineBytes{
|
||||
InlineBytes: dashboardTlsSecret.Data[corev1.TLSPrivateKeyKey],
|
||||
},
|
||||
},
|
||||
}},
|
||||
ValidationContextType: &tlsv3.CommonTlsContext_ValidationContext{
|
||||
ValidationContext: &tlsv3.CertificateValidationContext{
|
||||
TrustedCa: &corev3.DataSource{
|
||||
Specifier: &corev3.DataSource_InlineBytes{
|
||||
InlineBytes: dashboardTlsSecret.Data["ca.crt"],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *StudioCluster) basicAuthHttpFilter(ctx context.Context, gateway *supabasev1alpha1.APIGateway) (*hcm.HttpFilter, error) {
|
||||
users := gateway.Spec.DashboardEndpoint.Auth.Basic.UsersInline
|
||||
|
||||
usersSecret := corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: gateway.Spec.DashboardEndpoint.Auth.Basic.PlaintextUsersSecretRef,
|
||||
Namespace: gateway.Namespace,
|
||||
},
|
||||
}
|
||||
|
||||
if err := s.Get(ctx, client.ObjectKeyFromObject(&usersSecret), &usersSecret); err != nil {
|
||||
if client.IgnoreNotFound(err) != nil {
|
||||
return nil, fmt.Errorf("failed to fetch credentials secret: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
hash := sha1.New()
|
||||
for username, passwd := range usersSecret.Data {
|
||||
_, _ = hash.Write(passwd)
|
||||
pwHash := base64.StdEncoding.EncodeToString(hash.Sum(nil))
|
||||
users = append(users, fmt.Sprintf("%s:{SHA}%s", username, pwHash))
|
||||
hash.Reset()
|
||||
}
|
||||
|
||||
if len(users) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
slices.Sort(users)
|
||||
|
||||
return &hcm.HttpFilter{
|
||||
Name: FilterNameBasicAuth,
|
||||
ConfigType: &hcm.HttpFilter_TypedConfig{
|
||||
TypedConfig: MustAny(&basic_authv3.BasicAuth{
|
||||
Users: &corev3.DataSource{
|
||||
Specifier: &corev3.DataSource_InlineString{
|
||||
InlineString: strings.Join(users, "\n"),
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *StudioCluster) oauth2HttpFilter(instance string, gateway *supabasev1alpha1.APIGateway) *hcm.HttpFilter {
|
||||
var (
|
||||
serviceCfg = supabase.ServiceConfig.Envoy
|
||||
oauth2Spec = gateway.Spec.DashboardEndpoint.OAuth2()
|
||||
)
|
||||
|
||||
return &hcm.HttpFilter{
|
||||
Name: FilterNameOAuth2,
|
||||
ConfigType: &hcm.HttpFilter_TypedConfig{TypedConfig: MustAny(&oauth2v3.OAuth2{
|
||||
Config: &oauth2v3.OAuth2Config{
|
||||
TokenEndpoint: &corev3.HttpUri{
|
||||
HttpUpstreamType: &corev3.HttpUri_Cluster{
|
||||
Cluster: fmt.Sprintf("%s@%s", dashboardOAuth2ClusterName, instance),
|
||||
},
|
||||
Uri: gateway.Spec.DashboardEndpoint.Auth.OAuth2.TokenEndpoint,
|
||||
Timeout: durationpb.New(3 * time.Second),
|
||||
},
|
||||
AuthorizationEndpoint: gateway.Spec.DashboardEndpoint.Auth.OAuth2.AuthorizationEndpoint,
|
||||
RedirectUri: "%REQ(x-forwarded-proto)%://%REQ(:authority)%/callback",
|
||||
RedirectPathMatcher: &matcherv3.PathMatcher{
|
||||
Rule: &matcherv3.PathMatcher_Path{
|
||||
Path: &matcherv3.StringMatcher{
|
||||
MatchPattern: &matcherv3.StringMatcher_Exact{
|
||||
Exact: "/callback",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
SignoutPath: &matcherv3.PathMatcher{
|
||||
Rule: &matcherv3.PathMatcher_Path{
|
||||
Path: &matcherv3.StringMatcher{
|
||||
MatchPattern: &matcherv3.StringMatcher_Exact{
|
||||
Exact: "/signout",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Credentials: &oauth2v3.OAuth2Credentials{
|
||||
ClientId: oauth2Spec.ClientID,
|
||||
TokenSecret: &tlsv3.SdsSecretConfig{
|
||||
Name: serviceCfg.Defaults.OAuth2ClientSecretKey,
|
||||
SdsConfig: &corev3.ConfigSource{
|
||||
ConfigSourceSpecifier: &corev3.ConfigSource_Ads{
|
||||
Ads: new(corev3.AggregatedConfigSource),
|
||||
},
|
||||
},
|
||||
},
|
||||
TokenFormation: &oauth2v3.OAuth2Credentials_HmacSecret{
|
||||
HmacSecret: &tlsv3.SdsSecretConfig{
|
||||
Name: serviceCfg.Defaults.HmacSecretKey,
|
||||
SdsConfig: &corev3.ConfigSource{
|
||||
ConfigSourceSpecifier: &corev3.ConfigSource_Ads{
|
||||
Ads: new(corev3.AggregatedConfigSource),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
AuthScopes: oauth2Spec.Scopes,
|
||||
Resources: oauth2Spec.Resources,
|
||||
},
|
||||
})},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ import (
|
|||
"iter"
|
||||
|
||||
"github.com/jackc/pgx/v5"
|
||||
"sigs.k8s.io/controller-runtime/pkg/log"
|
||||
|
||||
supabasev1alpha1 "code.icb4dc0.de/prskr/supabase-operator/api/v1alpha1"
|
||||
"code.icb4dc0.de/prskr/supabase-operator/assets/migrations"
|
||||
|
@ -31,22 +32,33 @@ type Migrator struct {
|
|||
Conn *pgx.Conn
|
||||
}
|
||||
|
||||
func (m Migrator) ApplyAll(ctx context.Context, status supabasev1alpha1.MigrationStatus, seq iter.Seq2[migrations.Script, error]) (appliedSomething bool, err error) {
|
||||
func (m Migrator) ApplyAll(
|
||||
ctx context.Context,
|
||||
status *supabasev1alpha1.CoreStatus,
|
||||
seq iter.Seq2[migrations.Script, error],
|
||||
areInitScripts bool,
|
||||
) (appliedSomething bool, err error) {
|
||||
logger := log.FromContext(ctx)
|
||||
|
||||
for s, err := range seq {
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if status.IsApplied(s.FileName) {
|
||||
if found, upToDate := status.Database.IsMigrationUpToDate(s.FileName, s.Hash); found && upToDate {
|
||||
continue
|
||||
} else if found && !upToDate && areInitScripts {
|
||||
logger.Info("Change in init script was detected - will not apply because init scripts are not idempotent", "file_name", s.FileName)
|
||||
continue
|
||||
}
|
||||
|
||||
if err := m.Apply(ctx, s.Content); err != nil {
|
||||
logger.Info("Applying missing or outdated migration", "filename", s.FileName)
|
||||
err := status.Database.RecordMigrationCondition(s.FileName, s.Hash, m.Apply(ctx, s.Content))
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
appliedSomething = true
|
||||
status.Record(s.FileName)
|
||||
}
|
||||
|
||||
return appliedSomething, nil
|
||||
|
|
42
internal/health/cert_valid.go
Normal file
42
internal/health/cert_valid.go
Normal file
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
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 health
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/healthz"
|
||||
)
|
||||
|
||||
var ErrCertExpired = errors.New("certificate expired")
|
||||
|
||||
func CertValidCheck(cert tls.Certificate) healthz.Checker {
|
||||
return func(req *http.Request) error {
|
||||
if cert.Leaf.NotAfter.Before(time.Now()) {
|
||||
err := fmt.Errorf("%w: %s", ErrCertExpired, cert.Leaf.Subject.CommonName)
|
||||
ctrl.Log.Error(err, "certificate expired", "commonName", cert.Leaf.Subject.CommonName, "not_after", cert.Leaf.NotAfter)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
60
internal/oidc/discovery.go
Normal file
60
internal/oidc/discovery.go
Normal file
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
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 oidc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var ErrUnexpectedStatusCode = errors.New("unexpected status code")
|
||||
|
||||
type DiscoveryDocument struct {
|
||||
AuthorizationEndpoint string `json:"authorization_endpoint"`
|
||||
TokenEndpoint string `json:"token_endpoint"`
|
||||
}
|
||||
|
||||
func IssuerConfiguration(ctx context.Context, issuerUrl string) (dd DiscoveryDocument, err error) {
|
||||
const oidcDiscoveryEndpoint = "/.well-known/openid-configuration"
|
||||
if !strings.HasSuffix(issuerUrl, oidcDiscoveryEndpoint) {
|
||||
issuerUrl = strings.TrimSuffix(issuerUrl, "/") + oidcDiscoveryEndpoint
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, issuerUrl, nil)
|
||||
if err != nil {
|
||||
return dd, err
|
||||
}
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return dd, err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
err = errors.Join(err, resp.Body.Close())
|
||||
}()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return dd, fmt.Errorf("%w: %d - %s", ErrUnexpectedStatusCode, resp.StatusCode, resp.Status)
|
||||
}
|
||||
|
||||
return dd, json.NewDecoder(resp.Body).Decode(&dd)
|
||||
}
|
|
@ -19,6 +19,7 @@ package pw
|
|||
import (
|
||||
"bytes"
|
||||
"math/rand/v2"
|
||||
"time"
|
||||
)
|
||||
|
||||
func GeneratePW(length uint, random *rand.Rand) []byte {
|
||||
|
@ -28,7 +29,8 @@ func GeneratePW(length uint, random *rand.Rand) []byte {
|
|||
)
|
||||
|
||||
if random == nil {
|
||||
random = rand.New(rand.NewPCG(0, 0))
|
||||
now := time.Now()
|
||||
random = rand.New(rand.NewPCG(uint64(now.Year()), uint64(now.UnixNano())))
|
||||
}
|
||||
|
||||
for range length {
|
||||
|
|
|
@ -50,7 +50,8 @@ type authConfigDefaults struct {
|
|||
|
||||
func authServiceConfig() serviceConfig[authEnvKeys, authConfigDefaults] {
|
||||
return serviceConfig[authEnvKeys, authConfigDefaults]{
|
||||
Name: "auth",
|
||||
Name: "auth",
|
||||
LivenessProbePath: "/health",
|
||||
EnvKeys: authEnvKeys{
|
||||
ApiHost: fixedEnvOf("GOTRUE_API_HOST", "0.0.0.0"),
|
||||
ApiPort: fixedEnvOf("GOTRUE_API_PORT", "9999"),
|
||||
|
|
|
@ -23,9 +23,22 @@ import (
|
|||
)
|
||||
|
||||
type serviceConfig[TEnvKeys, TDefaults any] struct {
|
||||
Name string
|
||||
EnvKeys TEnvKeys
|
||||
Defaults TDefaults
|
||||
Name string
|
||||
LivenessProbePath string
|
||||
ReadinessProbePath string
|
||||
EnvKeys TEnvKeys
|
||||
Defaults TDefaults
|
||||
}
|
||||
|
||||
func (cfg serviceConfig[TEnvKeys, TDefaults]) ReadinessPath() string {
|
||||
return cfg.ReadinessProbePath
|
||||
}
|
||||
|
||||
func (cfg serviceConfig[TEnvKeys, TDefaults]) LivenessPath() string {
|
||||
if cfg.LivenessProbePath == "" {
|
||||
return cfg.ReadinessProbePath
|
||||
}
|
||||
return cfg.LivenessProbePath
|
||||
}
|
||||
|
||||
func (cfg serviceConfig[TEnvKeys, TDefaults]) ObjectName(obj metav1.Object) string {
|
||||
|
|
|
@ -25,16 +25,27 @@ import (
|
|||
func newEnvoyServiceConfig() envoyServiceConfig {
|
||||
return envoyServiceConfig{
|
||||
Defaults: envoyDefaults{
|
||||
ConfigKey: "config.yaml",
|
||||
UID: 65532,
|
||||
GID: 65532,
|
||||
ConfigKey: "config.yaml",
|
||||
OAuth2ClientSecretKey: "oauth2_client_secret",
|
||||
HmacSecretKey: "oauth2_hmac_secret",
|
||||
UID: 65532,
|
||||
GID: 65532,
|
||||
StudioPortName: "studio",
|
||||
ApiPortName: "api",
|
||||
StudioPort: 3000,
|
||||
ApiPort: 8000,
|
||||
AdminPort: 19000,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
type envoyDefaults struct {
|
||||
ConfigKey string
|
||||
UID, GID int64
|
||||
ConfigKey string
|
||||
HmacSecretKey string
|
||||
OAuth2ClientSecretKey string
|
||||
UID, GID int64
|
||||
StudioPortName, ApiPortName string
|
||||
StudioPort, ApiPort, AdminPort int32
|
||||
}
|
||||
|
||||
type envoyServiceConfig struct {
|
||||
|
@ -44,3 +55,11 @@ type envoyServiceConfig struct {
|
|||
func (envoyServiceConfig) ObjectName(obj metav1.Object) string {
|
||||
return fmt.Sprintf("%s-envoy", obj.GetName())
|
||||
}
|
||||
|
||||
func (envoyServiceConfig) ControlPlaneClientCertSecretName(obj metav1.Object) string {
|
||||
return fmt.Sprintf("%s-cp-client-cert", obj.GetName())
|
||||
}
|
||||
|
||||
func (envoyServiceConfig) HmacSecretName(obj metav1.Object) string {
|
||||
return fmt.Sprintf("%s-hmac-secret", obj.GetName())
|
||||
}
|
||||
|
|
|
@ -42,15 +42,15 @@ var Images = struct {
|
|||
}{
|
||||
EdgeRuntime: ImageRef{
|
||||
Repository: "supabase/edge-runtime",
|
||||
Tag: "v1.66.4",
|
||||
Tag: "v1.67.4",
|
||||
},
|
||||
Envoy: ImageRef{
|
||||
Repository: "envoyproxy/envoy",
|
||||
Tag: "distroless-v1.33.0",
|
||||
Tag: "distroless-v1.33.2",
|
||||
},
|
||||
Gotrue: ImageRef{
|
||||
Repository: "supabase/gotrue",
|
||||
Tag: "v2.167.0",
|
||||
Tag: "v2.170.0",
|
||||
},
|
||||
ImgProxy: ImageRef{
|
||||
Repository: "darthsim/imgproxy",
|
||||
|
@ -58,22 +58,22 @@ var Images = struct {
|
|||
},
|
||||
PostgresMeta: ImageRef{
|
||||
Repository: "supabase/postgres-meta",
|
||||
Tag: "v0.84.2",
|
||||
Tag: "v0.87.1",
|
||||
},
|
||||
Postgrest: ImageRef{
|
||||
Repository: "postgrest/postgrest",
|
||||
Tag: "v12.2.0",
|
||||
Tag: "v12.2.8",
|
||||
},
|
||||
Realtime: ImageRef{
|
||||
Repository: "supabase/realtime",
|
||||
Tag: "v2.33.70",
|
||||
Tag: "v2.34.43",
|
||||
},
|
||||
Storage: ImageRef{
|
||||
Repository: "supabase/storage-api",
|
||||
Tag: "v1.14.5",
|
||||
Tag: "v1.19.3",
|
||||
},
|
||||
Studio: ImageRef{
|
||||
Repository: "supabase/studio",
|
||||
Tag: "20250113-83c9420",
|
||||
Tag: "20250317-6955350",
|
||||
},
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue