From a99b3a66af65821d97e2dca54f4cc6e0d384bd1d Mon Sep 17 00:00:00 2001 From: Samuel Attwood Date: Wed, 20 Nov 2024 03:46:04 -0500 Subject: [PATCH 01/19] Migrate to kube_codegen.sh --- Makefile | 28 +-- controllers/jetstream/controller.go | 22 +- controllers/jetstream/controller_test.go | 5 +- go.mod | 88 ++++---- go.sum | 109 ++++++++- .../v1beta1/zz_generated.deepcopy.go | 3 +- .../v1beta2/zz_generated.deepcopy.go | 2 +- .../applyconfiguration/internal/internal.go | 2 +- .../jetstream/v1beta2/account.go | 12 +- .../jetstream/v1beta2/accountspec.go | 6 +- .../jetstream/v1beta2/condition.go | 6 +- .../jetstream/v1beta2/consumer.go | 12 +- .../jetstream/v1beta2/consumerspec.go | 6 +- .../jetstream/v1beta2/credssecret.go | 6 +- .../jetstream/v1beta2/republish.go | 6 +- .../jetstream/v1beta2/secretref.go | 6 +- .../jetstream/v1beta2/status.go | 6 +- .../jetstream/v1beta2/stream.go | 12 +- .../jetstream/v1beta2/streamplacement.go | 6 +- .../jetstream/v1beta2/streamsource.go | 6 +- .../jetstream/v1beta2/streamspec.go | 6 +- .../jetstream/v1beta2/subjecttransform.go | 6 +- .../jetstream/v1beta2/tls.go | 6 +- .../jetstream/v1beta2/tlssecret.go | 6 +- .../generated/applyconfiguration/utils.go | 9 +- .../clientset/versioned/clientset.go | 2 +- .../versioned/fake/clientset_generated.go | 41 +++- .../generated/clientset/versioned/fake/doc.go | 2 +- .../clientset/versioned/fake/register.go | 2 +- .../clientset/versioned/scheme/doc.go | 2 +- .../clientset/versioned/scheme/register.go | 2 +- .../typed/jetstream/v1beta2/account.go | 207 +----------------- .../typed/jetstream/v1beta2/consumer.go | 207 +----------------- .../versioned/typed/jetstream/v1beta2/doc.go | 2 +- .../typed/jetstream/v1beta2/fake/doc.go | 2 +- .../jetstream/v1beta2/fake/fake_account.go | 48 ++-- .../jetstream/v1beta2/fake/fake_consumer.go | 48 ++-- .../v1beta2/fake/fake_jetstream_client.go | 2 +- .../jetstream/v1beta2/fake/fake_stream.go | 48 ++-- .../jetstream/v1beta2/generated_expansion.go | 2 +- .../jetstream/v1beta2/jetstream_client.go | 2 +- .../typed/jetstream/v1beta2/stream.go | 207 +----------------- .../informers/externalversions/factory.go | 3 +- .../informers/externalversions/generic.go | 2 +- .../internalinterfaces/factory_interfaces.go | 2 +- .../externalversions/jetstream/interface.go | 2 +- .../jetstream/v1beta2/account.go | 2 +- .../jetstream/v1beta2/consumer.go | 2 +- .../jetstream/v1beta2/interface.go | 2 +- .../jetstream/v1beta2/stream.go | 2 +- .../listers/jetstream/v1beta2/account.go | 41 +--- .../listers/jetstream/v1beta2/consumer.go | 41 +--- .../jetstream/v1beta2/expansion_generated.go | 2 +- .../listers/jetstream/v1beta2/stream.go | 41 +--- pkg/k8scodegen/file-header.txt | 2 +- 55 files changed, 459 insertions(+), 893 deletions(-) diff --git a/Makefile b/Makefile index b9b25047..360ce845 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,7 @@ export GO111MODULE := on +SHELL=/usr/bin/env bash + now := $(shell date -u +%Y-%m-%dT%H:%M:%S%z) gitBranch := $(shell git rev-parse --abbrev-ref HEAD) gitCommit := $(shell git rev-parse --short HEAD) @@ -9,7 +11,6 @@ VERSION ?= version-not-set linkerVars := -X main.BuildTime=$(now) -X main.GitInfo=$(gitBranch)-$(gitCommit)$(repoDirty) -X main.Version=$(VERSION) drepo ?= natsio -jetstreamGenIn:= $(shell grep -l -R -F "// +k8s:" pkg/jetstream/apis) jetstreamSrc := $(shell find cmd/jetstream-controller pkg/jetstream controllers/jetstream -name "*.go") pkg/jetstream/apis/jetstream/v1beta2/zz_generated.deepcopy.go configReloaderSrc := $(shell find cmd/nats-server-config-reloader/ pkg/natsreloader/ -name "*.go") @@ -27,20 +28,21 @@ default: # make nats-server-config-reloader # make nats-boot-config -pkg/jetstream/generated pkg/jetstream/apis/jetstream/v1beta2/zz_generated.deepcopy.go: fetch-modules $(jetstreamGenIn) pkg/k8scodegen/file-header.txt +generate: fetch-modules pkg/k8scodegen/file-header.txt rm -rf pkg/jetstream/generated - # Temporary chmod fix until we migrate to kube_codegen.sh D="$(codeGeneratorDir)"; : "$${D:=`go list -m -f '{{.Dir}}' k8s.io/code-generator`}"; \ - chmod u+x "$$D/generate-internal-groups.sh"; \ - GOFLAGS='' bash "$$D/generate-groups.sh" all \ - github.com/nats-io/nack/pkg/jetstream/generated \ - github.com/nats-io/nack/pkg/jetstream/apis \ - "jetstream:v1beta2" \ - --output-base . \ - --go-header-file pkg/k8scodegen/file-header.txt - mv github.com/nats-io/nack/pkg/jetstream/generated pkg/jetstream/generated - mv github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2/zz_generated.deepcopy.go pkg/jetstream/apis/jetstream/v1beta2/zz_generated.deepcopy.go - rm -rf github.com + source "$$D/kube_codegen.sh" ; \ + kube::codegen::gen_helpers \ + --boilerplate pkg/k8scodegen/file-header.txt \ + pkg/jetstream/apis; \ + kube::codegen::gen_client \ + --with-watch \ + --with-applyconfig \ + --boilerplate pkg/k8scodegen/file-header.txt \ + --output-dir pkg/jetstream/generated \ + --output-pkg github.com/nats-io/nack/pkg/jetstream/generated \ + --one-input-api jetstream/v1beta2 \ + pkg/jetstream/apis jetstream-controller: $(jetstreamSrc) go build -race -o $@ \ diff --git a/controllers/jetstream/controller.go b/controllers/jetstream/controller.go index 35b35b4b..6d918f62 100644 --- a/controllers/jetstream/controller.go +++ b/controllers/jetstream/controller.go @@ -140,15 +140,17 @@ func NewController(opt Options) *Controller { streamQueue := workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "Streams") consumerQueue := workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "Consumers") - streamInformer.Informer().AddEventHandler(eventHandlers( - opt.Ctx, - streamQueue, - )) - - consumerInformer.Informer().AddEventHandler(eventHandlers( - opt.Ctx, - consumerQueue, - )) + streamInformer.Informer().AddEventHandler( + eventHandlers( + streamQueue, + ), + ) + + consumerInformer.Informer().AddEventHandler( + eventHandlers( + consumerQueue, + ), + ) cacheDir, err := os.MkdirTemp(".", "nack") if err != nil { @@ -728,7 +730,7 @@ func shouldEnqueue(prevObj, nextObj interface{}) bool { return markedDelete || specChanged } -func eventHandlers(ctx context.Context, q workqueue.RateLimitingInterface) cache.ResourceEventHandlerFuncs { +func eventHandlers(q workqueue.RateLimitingInterface) cache.ResourceEventHandlerFuncs { return cache.ResourceEventHandlerFuncs{ AddFunc: func(obj interface{}) { if err := enqueueWork(q, obj); err != nil { diff --git a/controllers/jetstream/controller_test.go b/controllers/jetstream/controller_test.go index 94f831bc..68f8f7fd 100644 --- a/controllers/jetstream/controller_test.go +++ b/controllers/jetstream/controller_test.go @@ -1,6 +1,7 @@ package jetstream import ( + "context" "fmt" "os" "testing" @@ -17,8 +18,8 @@ import ( func TestMain(m *testing.M) { // Disable error logs. - utilruntime.ErrorHandlers = []func(error){ - func(err error) {}, + utilruntime.ErrorHandlers = []utilruntime.ErrorHandler{ + func(ctx context.Context, err error, msg string, args ...any) {}, } os.Exit(m.Run()) diff --git a/go.mod b/go.mod index e53ed0fa..821f97dc 100644 --- a/go.mod +++ b/go.mod @@ -1,74 +1,78 @@ module github.com/nats-io/nack -go 1.22 +go 1.23 -toolchain go1.22.2 +toolchain go1.23.2 require ( - github.com/fsnotify/fsnotify v1.7.0 + github.com/fsnotify/fsnotify v1.8.0 github.com/nats-io/jsm.go v0.1.2 - github.com/nats-io/nats-server/v2 v2.10.18 - github.com/nats-io/nats.go v1.36.0 + github.com/nats-io/nats-server/v2 v2.10.22 + github.com/nats-io/nats.go v1.37.0 github.com/sirupsen/logrus v1.9.3 - github.com/stretchr/testify v1.8.4 - golang.org/x/sync v0.7.0 - k8s.io/api v0.29.1 - k8s.io/apimachinery v0.29.1 - k8s.io/client-go v0.29.1 - k8s.io/code-generator v0.29.1 - k8s.io/klog/v2 v2.120.1 - sigs.k8s.io/structured-merge-diff/v4 v4.4.1 + github.com/stretchr/testify v1.9.0 + golang.org/x/sync v0.9.0 + k8s.io/api v0.31.2 + k8s.io/apimachinery v0.31.2 + k8s.io/client-go v0.31.2 + k8s.io/code-generator v0.31.2 + k8s.io/klog/v2 v2.130.1 + sigs.k8s.io/structured-merge-diff/v4 v4.4.3 ) require ( - github.com/davecgh/go-spew v1.1.1 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/dustin/go-humanize v1.0.1 // indirect - github.com/emicklei/go-restful/v3 v3.11.0 // indirect - github.com/evanphx/json-patch v4.12.0+incompatible // indirect + github.com/emicklei/go-restful/v3 v3.12.1 // indirect + github.com/evanphx/json-patch v5.9.0+incompatible // indirect github.com/expr-lang/expr v1.16.9 // indirect - github.com/go-logr/logr v1.4.1 // indirect - github.com/go-openapi/jsonpointer v0.19.6 // indirect - github.com/go-openapi/jsonreference v0.20.2 // indirect - github.com/go-openapi/swag v0.22.3 // indirect + github.com/fxamacker/cbor/v2 v2.7.0 // indirect + github.com/go-logr/logr v1.4.2 // 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/gogo/protobuf v1.3.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect - github.com/golang/protobuf v1.5.3 // indirect + github.com/golang/protobuf v1.5.4 // indirect github.com/google/gnostic-models v0.6.8 // indirect github.com/google/go-cmp v0.6.0 // indirect github.com/google/gofuzz v1.2.0 // indirect - github.com/google/uuid v1.3.0 // indirect - github.com/imdario/mergo v0.3.6 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/imdario/mergo v0.3.16 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/klauspost/compress v1.17.9 // indirect + github.com/klauspost/compress v1.17.11 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/minio/highwayhash v1.0.3 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - github.com/nats-io/jwt/v2 v2.5.8 // indirect + github.com/nats-io/jwt/v2 v2.7.2 // indirect github.com/nats-io/nkeys v0.4.7 // indirect github.com/nats-io/nuid v1.0.1 // indirect github.com/pkg/errors v0.9.1 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/spf13/pflag v1.0.5 // indirect - golang.org/x/crypto v0.25.0 // indirect - golang.org/x/mod v0.17.0 // indirect - golang.org/x/net v0.27.0 // indirect - golang.org/x/oauth2 v0.10.0 // indirect - golang.org/x/sys v0.22.0 // indirect - golang.org/x/term v0.22.0 // indirect - golang.org/x/text v0.16.0 // indirect - golang.org/x/time v0.5.0 // indirect - golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect - google.golang.org/appengine v1.6.7 // indirect - google.golang.org/protobuf v1.33.0 // indirect + github.com/x448/float16 v0.8.4 // indirect + golang.org/x/crypto v0.29.0 // indirect + golang.org/x/mod v0.22.0 // indirect + golang.org/x/net v0.31.0 // indirect + golang.org/x/oauth2 v0.24.0 // indirect + golang.org/x/sys v0.27.0 // indirect + golang.org/x/term v0.26.0 // indirect + golang.org/x/text v0.20.0 // indirect + golang.org/x/time v0.8.0 // indirect + golang.org/x/tools v0.27.0 // indirect + google.golang.org/appengine v1.6.8 // indirect + google.golang.org/protobuf v1.35.2 // 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 gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/gengo v0.0.0-20230829151522-9cce18d56c01 // indirect - k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 // indirect - k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect - sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect - sigs.k8s.io/yaml v1.3.0 // indirect + k8s.io/gengo v0.0.0-20240911193312-2b36238f13e9 // indirect + k8s.io/gengo/v2 v2.0.0-20240911193312-2b36238f13e9 // indirect + k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f // indirect + k8s.io/utils v0.0.0-20241104163129-6fe5fd82f078 // indirect + sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect + sigs.k8s.io/yaml v1.4.0 // indirect ) diff --git a/go.sum b/go.sum index 4225366c..eb7f909b 100644 --- a/go.sum +++ b/go.sum @@ -2,25 +2,43 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 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/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +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/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84= github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch v5.9.0+incompatible h1:fBXyNpNMuTTDdquAq/uisOr2lShz4oaXpDTX2bLe7ls= +github.com/evanphx/json-patch v5.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/expr-lang/expr v1.16.9 h1:WUAzmR0JNI9JCiF0/ewwHB1gmcGw5wW7nWt8gc6PpCI= github.com/expr-lang/expr v1.16.9/go.mod h1:8/vRC7+7HBzESEqt5kKpYXxrxkr31SaO8r40VO/1IT4= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= 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/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= +github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= +github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= +github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ= +github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4= github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g= github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +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-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= @@ -29,8 +47,11 @@ github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= @@ -44,10 +65,15 @@ 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-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 h1:FKHo8hFI3A+7w0aUQuYXQ+6EN5stWmeY/AZqtM8xk9k= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +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/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28= github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= +github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= 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= @@ -56,6 +82,8 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +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.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= @@ -79,10 +107,16 @@ github.com/nats-io/jsm.go v0.1.2 h1:T4Fq88a03sPAPWYwrOLQ85oanYsC2Bs6517rUiWBMpQ= github.com/nats-io/jsm.go v0.1.2/go.mod h1:tnubE70CAKi5TNfQiq6XHFqWTuSIe1H7X4sDwfq6ZK8= github.com/nats-io/jwt/v2 v2.5.8 h1:uvdSzwWiEGWGXf+0Q+70qv6AQdvcvxrv9hPM0RiPamE= github.com/nats-io/jwt/v2 v2.5.8/go.mod h1:ZdWS1nZa6WMZfFwwgpEaqBV8EPGVgOTDHN/wTbz0Y5A= +github.com/nats-io/jwt/v2 v2.7.2 h1:SCRjfDLJ2q8naXp8YlGJJS5/yj3wGSODFYVi4nnwVMw= +github.com/nats-io/jwt/v2 v2.7.2/go.mod h1:kB6QUmqHG6Wdrzj0KP2L+OX4xiTPBeV+NHVstFaATXU= github.com/nats-io/nats-server/v2 v2.10.18 h1:tRdZmBuWKVAFYtayqlBB2BuCHNGAQPvoQIXOKwU3WSM= github.com/nats-io/nats-server/v2 v2.10.18/go.mod h1:97Qyg7YydD8blKlR8yBsUlPlWyZKjA7Bp5cl3MUE9K8= +github.com/nats-io/nats-server/v2 v2.10.22 h1:Yt63BGu2c3DdMoBZNcR6pjGQwk/asrKU7VX846ibxDA= +github.com/nats-io/nats-server/v2 v2.10.22/go.mod h1:X/m1ye9NYansUXYFrbcDwUi/blHkrgHh2rgCJaakonk= github.com/nats-io/nats.go v1.36.0 h1:suEUPuWzTSse/XhESwqLxXGuj8vGRuPRoG7MoRN/qyU= github.com/nats-io/nats.go v1.36.0/go.mod h1:Ubdu4Nh9exXdSz0RVWRFBbRfrbSxOYd26oF0wkWclB8= +github.com/nats-io/nats.go v1.37.0 h1:07rauXbVnnJvv1gfIyghFEo6lUcYRY0WXc3x7x0vUxE= +github.com/nats-io/nats.go v1.37.0/go.mod h1:Ubdu4Nh9exXdSz0RVWRFBbRfrbSxOYd26oF0wkWclB8= github.com/nats-io/nkeys v0.4.7 h1:RwNJbbIdYCoClSDNY7QVKZlyb/wfT6ugvFCiKy6vDvI= github.com/nats-io/nkeys v0.4.7/go.mod h1:kqXRgRDPlGy7nGaEDMuYzmiJCIAAWDK0IMBtDmGD0nc= github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= @@ -95,6 +129,8 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 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/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= @@ -111,68 +147,113 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +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/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 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.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= +golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ= +golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg= 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.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= +golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 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-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= +golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo= +golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM= golang.org/x/oauth2 v0.10.0 h1:zHCpF2Khkwy4mMB4bv0U37YtJdTGW8jI0glAApi0Kh8= golang.org/x/oauth2 v0.10.0/go.mod h1:kTpgurOux7LqtuxjuyZa4Gj2gdezIt/jQtGnNFfypQI= +golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE= +golang.org/x/oauth2 v0.24.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-20201020160332-67f06af15bc9/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.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ= +golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 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-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/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.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= +golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +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.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk= golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4= +golang.org/x/term v0.26.0 h1:WEQa6V3Gja/BhNxg540hBip/kkaYtRg3cxg4oXSw4AU= +golang.org/x/term v0.26.0/go.mod h1:Si5m1o57C5nBNQo5z1iq+XDijt21BDBDp2bK0QI8e3E= 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.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug= +golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg= +golang.org/x/time v0.8.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-20200505023115-26f46d2f7ef8/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= +golang.org/x/tools v0.27.0 h1:qEKojBykQkQ4EynWy4S8Weg69NumxKdn40Fce3uc/8o= +golang.org/x/tools v0.27.0/go.mod h1:sUi0ZgbwW9ZPAq26Ekut+weQPR5eIM6GQLQ1Yjm1H0Q= 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= google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= +google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= -google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= +google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io= +google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= 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/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -183,25 +264,49 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= k8s.io/api v0.29.1 h1:DAjwWX/9YT7NQD4INu49ROJuZAAAP/Ijki48GUPzxqw= k8s.io/api v0.29.1/go.mod h1:7Kl10vBRUXhnQQI8YR/R327zXC8eJ7887/+Ybta+RoQ= +k8s.io/api v0.31.2 h1:3wLBbL5Uom/8Zy98GRPXpJ254nEFpl+hwndmk9RwmL0= +k8s.io/api v0.31.2/go.mod h1:bWmGvrGPssSK1ljmLzd3pwCQ9MgoTsRCuK35u6SygUk= k8s.io/apimachinery v0.29.1 h1:KY4/E6km/wLBguvCZv8cKTeOwwOBqFNjwJIdMkMbbRc= k8s.io/apimachinery v0.29.1/go.mod h1:6HVkd1FwxIagpYrHSwJlQqZI3G9LfYWRPAkUvLnXTKU= +k8s.io/apimachinery v0.31.2 h1:i4vUt2hPK56W6mlT7Ry+AO8eEsyxMD1U44NR22CLTYw= +k8s.io/apimachinery v0.31.2/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo= k8s.io/client-go v0.29.1 h1:19B/+2NGEwnFLzt0uB5kNJnfTsbV8w6TgQRz9l7ti7A= k8s.io/client-go v0.29.1/go.mod h1:TDG/psL9hdet0TI9mGyHJSgRkW3H9JZk2dNEUS7bRks= +k8s.io/client-go v0.31.2 h1:Y2F4dxU5d3AQj+ybwSMqQnpZH9F30//1ObxOKlTI9yc= +k8s.io/client-go v0.31.2/go.mod h1:NPa74jSVR/+eez2dFsEIHNa+3o09vtNaWwWwb1qSxSs= k8s.io/code-generator v0.29.1 h1:8ba8BdtSmAVHgAMpzThb/fuyQeTRtN7NtN7VjMcDLew= k8s.io/code-generator v0.29.1/go.mod h1:FwFi3C9jCrmbPjekhaCYcYG1n07CYiW1+PAPCockaos= +k8s.io/code-generator v0.31.2 h1:xLWxG0HEpMSHfcM//3u3Ro2Hmc6AyyLINQS//Z2GEOI= +k8s.io/code-generator v0.31.2/go.mod h1:eEQHXgBU/m7LDaToDoiz3t97dUUVyOblQdwOr8rivqc= k8s.io/gengo v0.0.0-20230829151522-9cce18d56c01 h1:pWEwq4Asjm4vjW7vcsmijwBhOr1/shsbSYiWXmNGlks= k8s.io/gengo v0.0.0-20230829151522-9cce18d56c01/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= +k8s.io/gengo v0.0.0-20240911193312-2b36238f13e9 h1:B0l8GxRsVc/tP/uCLBQdAjf2nBARx6u/r2OGuL/CyXQ= +k8s.io/gengo v0.0.0-20240911193312-2b36238f13e9/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= +k8s.io/gengo/v2 v2.0.0-20240911193312-2b36238f13e9 h1:si3PfKm8dDYxgfbeA6orqrtLkvvIeH8UqffFJDl0bz4= +k8s.io/gengo/v2 v2.0.0-20240911193312-2b36238f13e9/go.mod h1:EJykeLsmFC60UQbYJezXkEsG2FLrt0GPNkU5iK5GWxU= k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= k8s.io/klog/v2 v2.120.1 h1:QXU6cPEOIslTGvZaXvFWiP9VKyeet3sawzTOvdXb4Vw= k8s.io/klog/v2 v2.120.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= +k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= +k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 h1:aVUu9fTY98ivBPKR9Y5w/AuzbMm96cd3YHRTU83I780= k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00/go.mod h1:AsvuZPBlUDVuCdzJ87iajxtXuR9oktsTctW/R9wwouA= +k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f h1:GA7//TjRY9yWGy1poLzYYJJ4JRdzg3+O6e8I+e+8T5Y= +k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f/go.mod h1:R/HEjbvWI0qdfb8viZUeVZm0X6IZnxAydC7YU42CMw4= k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI= k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +k8s.io/utils v0.0.0-20241104163129-6fe5fd82f078 h1:jGnCPejIetjiy2gqaJ5V0NLwTpF4wbQ6cZIItJCSHno= +k8s.io/utils v0.0.0-20241104163129-6fe5fd82f078/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= +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/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= +sigs.k8s.io/structured-merge-diff/v4 v4.4.3 h1:sCP7Vv3xx/CWIuTPVN38lUPx0uw0lcLfzaiDa8Ja01A= +sigs.k8s.io/structured-merge-diff/v4 v4.4.3/go.mod h1:N8f93tFZh9U6vpxwRArLiikrE5/2tiu1w1AGfACIGE4= sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= +sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= +sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= diff --git a/pkg/jetstream/apis/jetstream/v1beta1/zz_generated.deepcopy.go b/pkg/jetstream/apis/jetstream/v1beta1/zz_generated.deepcopy.go index 9576377b..ce7e3e7e 100644 --- a/pkg/jetstream/apis/jetstream/v1beta1/zz_generated.deepcopy.go +++ b/pkg/jetstream/apis/jetstream/v1beta1/zz_generated.deepcopy.go @@ -1,6 +1,7 @@ +//go:build !ignore_autogenerated // +build !ignore_autogenerated -// Copyright 2020 The NATS Authors +// Copyright 2024 The NATS Authors // 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 diff --git a/pkg/jetstream/apis/jetstream/v1beta2/zz_generated.deepcopy.go b/pkg/jetstream/apis/jetstream/v1beta2/zz_generated.deepcopy.go index 29e4521d..e0c13cc3 100644 --- a/pkg/jetstream/apis/jetstream/v1beta2/zz_generated.deepcopy.go +++ b/pkg/jetstream/apis/jetstream/v1beta2/zz_generated.deepcopy.go @@ -1,7 +1,7 @@ //go:build !ignore_autogenerated // +build !ignore_autogenerated -// Copyright 2020 The NATS Authors +// Copyright 2024 The NATS Authors // 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 diff --git a/pkg/jetstream/generated/applyconfiguration/internal/internal.go b/pkg/jetstream/generated/applyconfiguration/internal/internal.go index 6612a2fb..63b69d16 100644 --- a/pkg/jetstream/generated/applyconfiguration/internal/internal.go +++ b/pkg/jetstream/generated/applyconfiguration/internal/internal.go @@ -1,4 +1,4 @@ -// Copyright 2020 The NATS Authors +// Copyright 2024 The NATS Authors // 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 diff --git a/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/account.go b/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/account.go index 56005dd2..42b91e90 100644 --- a/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/account.go +++ b/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/account.go @@ -1,4 +1,4 @@ -// Copyright 2020 The NATS Authors +// Copyright 2024 The NATS Authors // 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 @@ -21,7 +21,7 @@ import ( v1 "k8s.io/client-go/applyconfigurations/meta/v1" ) -// AccountApplyConfiguration represents an declarative configuration of the Account type for use +// AccountApplyConfiguration represents a declarative configuration of the Account type for use // with apply. type AccountApplyConfiguration struct { v1.TypeMetaApplyConfiguration `json:",inline"` @@ -30,7 +30,7 @@ type AccountApplyConfiguration struct { Status *StatusApplyConfiguration `json:"status,omitempty"` } -// Account constructs an declarative configuration of the Account type for use with +// Account constructs a declarative configuration of the Account type for use with // apply. func Account(name, namespace string) *AccountApplyConfiguration { b := &AccountApplyConfiguration{} @@ -214,3 +214,9 @@ func (b *AccountApplyConfiguration) WithStatus(value *StatusApplyConfiguration) b.Status = value return b } + +// GetName retrieves the value of the Name field in the declarative configuration. +func (b *AccountApplyConfiguration) GetName() *string { + b.ensureObjectMetaApplyConfigurationExists() + return b.Name +} diff --git a/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/accountspec.go b/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/accountspec.go index 09dd5464..c2b2f865 100644 --- a/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/accountspec.go +++ b/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/accountspec.go @@ -1,4 +1,4 @@ -// Copyright 2020 The NATS Authors +// Copyright 2024 The NATS Authors // 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 @@ -15,7 +15,7 @@ package v1beta2 -// AccountSpecApplyConfiguration represents an declarative configuration of the AccountSpec type for use +// AccountSpecApplyConfiguration represents a declarative configuration of the AccountSpec type for use // with apply. type AccountSpecApplyConfiguration struct { Servers []string `json:"servers,omitempty"` @@ -25,7 +25,7 @@ type AccountSpecApplyConfiguration struct { User *UserApplyConfiguration `json:"user,omitempty"` } -// AccountSpecApplyConfiguration constructs an declarative configuration of the AccountSpec type for use with +// AccountSpecApplyConfiguration constructs a declarative configuration of the AccountSpec type for use with // apply. func AccountSpec() *AccountSpecApplyConfiguration { return &AccountSpecApplyConfiguration{} diff --git a/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/condition.go b/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/condition.go index 8875d87d..de299362 100644 --- a/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/condition.go +++ b/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/condition.go @@ -1,4 +1,4 @@ -// Copyright 2020 The NATS Authors +// Copyright 2024 The NATS Authors // 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 @@ -19,7 +19,7 @@ import ( v1 "k8s.io/api/core/v1" ) -// ConditionApplyConfiguration represents an declarative configuration of the Condition type for use +// ConditionApplyConfiguration represents a declarative configuration of the Condition type for use // with apply. type ConditionApplyConfiguration struct { Type *string `json:"type,omitempty"` @@ -29,7 +29,7 @@ type ConditionApplyConfiguration struct { LastTransitionTime *string `json:"lastTransitionTime,omitempty"` } -// ConditionApplyConfiguration constructs an declarative configuration of the Condition type for use with +// ConditionApplyConfiguration constructs a declarative configuration of the Condition type for use with // apply. func Condition() *ConditionApplyConfiguration { return &ConditionApplyConfiguration{} diff --git a/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/consumer.go b/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/consumer.go index 144ef962..e9cbc92f 100644 --- a/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/consumer.go +++ b/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/consumer.go @@ -1,4 +1,4 @@ -// Copyright 2020 The NATS Authors +// Copyright 2024 The NATS Authors // 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 @@ -21,7 +21,7 @@ import ( v1 "k8s.io/client-go/applyconfigurations/meta/v1" ) -// ConsumerApplyConfiguration represents an declarative configuration of the Consumer type for use +// ConsumerApplyConfiguration represents a declarative configuration of the Consumer type for use // with apply. type ConsumerApplyConfiguration struct { v1.TypeMetaApplyConfiguration `json:",inline"` @@ -30,7 +30,7 @@ type ConsumerApplyConfiguration struct { Status *StatusApplyConfiguration `json:"status,omitempty"` } -// Consumer constructs an declarative configuration of the Consumer type for use with +// Consumer constructs a declarative configuration of the Consumer type for use with // apply. func Consumer(name, namespace string) *ConsumerApplyConfiguration { b := &ConsumerApplyConfiguration{} @@ -214,3 +214,9 @@ func (b *ConsumerApplyConfiguration) WithStatus(value *StatusApplyConfiguration) b.Status = value return b } + +// GetName retrieves the value of the Name field in the declarative configuration. +func (b *ConsumerApplyConfiguration) GetName() *string { + b.ensureObjectMetaApplyConfigurationExists() + return b.Name +} diff --git a/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/consumerspec.go b/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/consumerspec.go index 02683d96..8700fe3e 100644 --- a/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/consumerspec.go +++ b/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/consumerspec.go @@ -1,4 +1,4 @@ -// Copyright 2020 The NATS Authors +// Copyright 2024 The NATS Authors // 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 @@ -15,7 +15,7 @@ package v1beta2 -// ConsumerSpecApplyConfiguration represents an declarative configuration of the ConsumerSpec type for use +// ConsumerSpecApplyConfiguration represents a declarative configuration of the ConsumerSpec type for use // with apply. type ConsumerSpecApplyConfiguration struct { AckPolicy *string `json:"ackPolicy,omitempty"` @@ -55,7 +55,7 @@ type ConsumerSpecApplyConfiguration struct { Metadata map[string]string `json:"metadata,omitempty"` } -// ConsumerSpecApplyConfiguration constructs an declarative configuration of the ConsumerSpec type for use with +// ConsumerSpecApplyConfiguration constructs a declarative configuration of the ConsumerSpec type for use with // apply. func ConsumerSpec() *ConsumerSpecApplyConfiguration { return &ConsumerSpecApplyConfiguration{} diff --git a/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/credssecret.go b/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/credssecret.go index 377f34a0..467fb7a4 100644 --- a/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/credssecret.go +++ b/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/credssecret.go @@ -1,4 +1,4 @@ -// Copyright 2020 The NATS Authors +// Copyright 2024 The NATS Authors // 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 @@ -15,14 +15,14 @@ package v1beta2 -// CredsSecretApplyConfiguration represents an declarative configuration of the CredsSecret type for use +// CredsSecretApplyConfiguration represents a declarative configuration of the CredsSecret type for use // with apply. type CredsSecretApplyConfiguration struct { File *string `json:"file,omitempty"` Secret *SecretRefApplyConfiguration `json:"secret,omitempty"` } -// CredsSecretApplyConfiguration constructs an declarative configuration of the CredsSecret type for use with +// CredsSecretApplyConfiguration constructs a declarative configuration of the CredsSecret type for use with // apply. func CredsSecret() *CredsSecretApplyConfiguration { return &CredsSecretApplyConfiguration{} diff --git a/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/republish.go b/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/republish.go index 0a639c8f..45873e7e 100644 --- a/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/republish.go +++ b/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/republish.go @@ -1,4 +1,4 @@ -// Copyright 2020 The NATS Authors +// Copyright 2024 The NATS Authors // 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 @@ -15,7 +15,7 @@ package v1beta2 -// RePublishApplyConfiguration represents an declarative configuration of the RePublish type for use +// RePublishApplyConfiguration represents a declarative configuration of the RePublish type for use // with apply. type RePublishApplyConfiguration struct { Source *string `json:"source,omitempty"` @@ -23,7 +23,7 @@ type RePublishApplyConfiguration struct { HeadersOnly *bool `json:"headers_only,omitempty"` } -// RePublishApplyConfiguration constructs an declarative configuration of the RePublish type for use with +// RePublishApplyConfiguration constructs a declarative configuration of the RePublish type for use with // apply. func RePublish() *RePublishApplyConfiguration { return &RePublishApplyConfiguration{} diff --git a/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/secretref.go b/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/secretref.go index 823655b0..57754747 100644 --- a/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/secretref.go +++ b/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/secretref.go @@ -1,4 +1,4 @@ -// Copyright 2020 The NATS Authors +// Copyright 2024 The NATS Authors // 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 @@ -15,13 +15,13 @@ package v1beta2 -// SecretRefApplyConfiguration represents an declarative configuration of the SecretRef type for use +// SecretRefApplyConfiguration represents a declarative configuration of the SecretRef type for use // with apply. type SecretRefApplyConfiguration struct { Name *string `json:"name,omitempty"` } -// SecretRefApplyConfiguration constructs an declarative configuration of the SecretRef type for use with +// SecretRefApplyConfiguration constructs a declarative configuration of the SecretRef type for use with // apply. func SecretRef() *SecretRefApplyConfiguration { return &SecretRefApplyConfiguration{} diff --git a/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/status.go b/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/status.go index e9334c4d..cebedf26 100644 --- a/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/status.go +++ b/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/status.go @@ -1,4 +1,4 @@ -// Copyright 2020 The NATS Authors +// Copyright 2024 The NATS Authors // 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 @@ -15,14 +15,14 @@ package v1beta2 -// StatusApplyConfiguration represents an declarative configuration of the Status type for use +// StatusApplyConfiguration represents a declarative configuration of the Status type for use // with apply. type StatusApplyConfiguration struct { ObservedGeneration *int64 `json:"observedGeneration,omitempty"` Conditions []ConditionApplyConfiguration `json:"conditions,omitempty"` } -// StatusApplyConfiguration constructs an declarative configuration of the Status type for use with +// StatusApplyConfiguration constructs a declarative configuration of the Status type for use with // apply. func Status() *StatusApplyConfiguration { return &StatusApplyConfiguration{} diff --git a/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/stream.go b/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/stream.go index e4c6bef4..dc987f23 100644 --- a/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/stream.go +++ b/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/stream.go @@ -1,4 +1,4 @@ -// Copyright 2020 The NATS Authors +// Copyright 2024 The NATS Authors // 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 @@ -21,7 +21,7 @@ import ( v1 "k8s.io/client-go/applyconfigurations/meta/v1" ) -// StreamApplyConfiguration represents an declarative configuration of the Stream type for use +// StreamApplyConfiguration represents a declarative configuration of the Stream type for use // with apply. type StreamApplyConfiguration struct { v1.TypeMetaApplyConfiguration `json:",inline"` @@ -30,7 +30,7 @@ type StreamApplyConfiguration struct { Status *StatusApplyConfiguration `json:"status,omitempty"` } -// Stream constructs an declarative configuration of the Stream type for use with +// Stream constructs a declarative configuration of the Stream type for use with // apply. func Stream(name, namespace string) *StreamApplyConfiguration { b := &StreamApplyConfiguration{} @@ -214,3 +214,9 @@ func (b *StreamApplyConfiguration) WithStatus(value *StatusApplyConfiguration) * b.Status = value return b } + +// GetName retrieves the value of the Name field in the declarative configuration. +func (b *StreamApplyConfiguration) GetName() *string { + b.ensureObjectMetaApplyConfigurationExists() + return b.Name +} diff --git a/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/streamplacement.go b/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/streamplacement.go index 654b1865..4063166e 100644 --- a/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/streamplacement.go +++ b/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/streamplacement.go @@ -1,4 +1,4 @@ -// Copyright 2020 The NATS Authors +// Copyright 2024 The NATS Authors // 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 @@ -15,14 +15,14 @@ package v1beta2 -// StreamPlacementApplyConfiguration represents an declarative configuration of the StreamPlacement type for use +// StreamPlacementApplyConfiguration represents a declarative configuration of the StreamPlacement type for use // with apply. type StreamPlacementApplyConfiguration struct { Cluster *string `json:"cluster,omitempty"` Tags []string `json:"tags,omitempty"` } -// StreamPlacementApplyConfiguration constructs an declarative configuration of the StreamPlacement type for use with +// StreamPlacementApplyConfiguration constructs a declarative configuration of the StreamPlacement type for use with // apply. func StreamPlacement() *StreamPlacementApplyConfiguration { return &StreamPlacementApplyConfiguration{} diff --git a/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/streamsource.go b/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/streamsource.go index e0c3e1a2..0d5e63f0 100644 --- a/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/streamsource.go +++ b/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/streamsource.go @@ -1,4 +1,4 @@ -// Copyright 2020 The NATS Authors +// Copyright 2024 The NATS Authors // 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 @@ -19,7 +19,7 @@ import ( v1beta2 "github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2" ) -// StreamSourceApplyConfiguration represents an declarative configuration of the StreamSource type for use +// StreamSourceApplyConfiguration represents a declarative configuration of the StreamSource type for use // with apply. type StreamSourceApplyConfiguration struct { Name *string `json:"name,omitempty"` @@ -31,7 +31,7 @@ type StreamSourceApplyConfiguration struct { SubjectTransforms []*v1beta2.SubjectTransform `json:"subjectTransforms,omitempty"` } -// StreamSourceApplyConfiguration constructs an declarative configuration of the StreamSource type for use with +// StreamSourceApplyConfiguration constructs a declarative configuration of the StreamSource type for use with // apply. func StreamSource() *StreamSourceApplyConfiguration { return &StreamSourceApplyConfiguration{} diff --git a/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/streamspec.go b/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/streamspec.go index e52a845a..7be89f6a 100644 --- a/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/streamspec.go +++ b/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/streamspec.go @@ -1,4 +1,4 @@ -// Copyright 2020 The NATS Authors +// Copyright 2024 The NATS Authors // 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 @@ -19,7 +19,7 @@ import ( jetstreamv1beta2 "github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2" ) -// StreamSpecApplyConfiguration represents an declarative configuration of the StreamSpec type for use +// StreamSpecApplyConfiguration represents a declarative configuration of the StreamSpec type for use // with apply. type StreamSpecApplyConfiguration struct { Account *string `json:"account,omitempty"` @@ -59,7 +59,7 @@ type StreamSpecApplyConfiguration struct { TLS *TLSApplyConfiguration `json:"tls,omitempty"` } -// StreamSpecApplyConfiguration constructs an declarative configuration of the StreamSpec type for use with +// StreamSpecApplyConfiguration constructs a declarative configuration of the StreamSpec type for use with // apply. func StreamSpec() *StreamSpecApplyConfiguration { return &StreamSpecApplyConfiguration{} diff --git a/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/subjecttransform.go b/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/subjecttransform.go index 2d4b9455..98fc658b 100644 --- a/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/subjecttransform.go +++ b/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/subjecttransform.go @@ -1,4 +1,4 @@ -// Copyright 2020 The NATS Authors +// Copyright 2024 The NATS Authors // 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 @@ -15,14 +15,14 @@ package v1beta2 -// SubjectTransformApplyConfiguration represents an declarative configuration of the SubjectTransform type for use +// SubjectTransformApplyConfiguration represents a declarative configuration of the SubjectTransform type for use // with apply. type SubjectTransformApplyConfiguration struct { Source *string `json:"source,omitempty"` Dest *string `json:"dest,omitempty"` } -// SubjectTransformApplyConfiguration constructs an declarative configuration of the SubjectTransform type for use with +// SubjectTransformApplyConfiguration constructs a declarative configuration of the SubjectTransform type for use with // apply. func SubjectTransform() *SubjectTransformApplyConfiguration { return &SubjectTransformApplyConfiguration{} diff --git a/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/tls.go b/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/tls.go index 31b51518..806a9ee5 100644 --- a/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/tls.go +++ b/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/tls.go @@ -1,4 +1,4 @@ -// Copyright 2020 The NATS Authors +// Copyright 2024 The NATS Authors // 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 @@ -15,7 +15,7 @@ package v1beta2 -// TLSApplyConfiguration represents an declarative configuration of the TLS type for use +// TLSApplyConfiguration represents a declarative configuration of the TLS type for use // with apply. type TLSApplyConfiguration struct { ClientCert *string `json:"clientCert,omitempty"` @@ -23,7 +23,7 @@ type TLSApplyConfiguration struct { RootCAs []string `json:"rootCas,omitempty"` } -// TLSApplyConfiguration constructs an declarative configuration of the TLS type for use with +// TLSApplyConfiguration constructs a declarative configuration of the TLS type for use with // apply. func TLS() *TLSApplyConfiguration { return &TLSApplyConfiguration{} diff --git a/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/tlssecret.go b/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/tlssecret.go index 94eebf45..c593036f 100644 --- a/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/tlssecret.go +++ b/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/tlssecret.go @@ -1,4 +1,4 @@ -// Copyright 2020 The NATS Authors +// Copyright 2024 The NATS Authors // 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 @@ -15,7 +15,7 @@ package v1beta2 -// TLSSecretApplyConfiguration represents an declarative configuration of the TLSSecret type for use +// TLSSecretApplyConfiguration represents a declarative configuration of the TLSSecret type for use // with apply. type TLSSecretApplyConfiguration struct { ClientCert *string `json:"cert,omitempty"` @@ -24,7 +24,7 @@ type TLSSecretApplyConfiguration struct { Secret *SecretRefApplyConfiguration `json:"secret,omitempty"` } -// TLSSecretApplyConfiguration constructs an declarative configuration of the TLSSecret type for use with +// TLSSecretApplyConfiguration constructs a declarative configuration of the TLSSecret type for use with // apply. func TLSSecret() *TLSSecretApplyConfiguration { return &TLSSecretApplyConfiguration{} diff --git a/pkg/jetstream/generated/applyconfiguration/utils.go b/pkg/jetstream/generated/applyconfiguration/utils.go index 1e43a122..bb273d79 100644 --- a/pkg/jetstream/generated/applyconfiguration/utils.go +++ b/pkg/jetstream/generated/applyconfiguration/utils.go @@ -1,4 +1,4 @@ -// Copyright 2020 The NATS Authors +// Copyright 2024 The NATS Authors // 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 @@ -17,8 +17,11 @@ package applyconfiguration import ( v1beta2 "github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2" + internal "github.com/nats-io/nack/pkg/jetstream/generated/applyconfiguration/internal" jetstreamv1beta2 "github.com/nats-io/nack/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2" + runtime "k8s.io/apimachinery/pkg/runtime" schema "k8s.io/apimachinery/pkg/runtime/schema" + testing "k8s.io/client-go/testing" ) // ForKind returns an apply configuration type for the given GroupVersionKind, or nil if no @@ -66,3 +69,7 @@ func ForKind(kind schema.GroupVersionKind) interface{} { } return nil } + +func NewTypeConverter(scheme *runtime.Scheme) *testing.TypeConverter { + return &testing.TypeConverter{Scheme: scheme, TypeResolver: internal.Parser()} +} diff --git a/pkg/jetstream/generated/clientset/versioned/clientset.go b/pkg/jetstream/generated/clientset/versioned/clientset.go index 34ddd785..79f86ce7 100644 --- a/pkg/jetstream/generated/clientset/versioned/clientset.go +++ b/pkg/jetstream/generated/clientset/versioned/clientset.go @@ -1,4 +1,4 @@ -// Copyright 2020 The NATS Authors +// Copyright 2024 The NATS Authors // 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 diff --git a/pkg/jetstream/generated/clientset/versioned/fake/clientset_generated.go b/pkg/jetstream/generated/clientset/versioned/fake/clientset_generated.go index eecb1ce6..642b047b 100644 --- a/pkg/jetstream/generated/clientset/versioned/fake/clientset_generated.go +++ b/pkg/jetstream/generated/clientset/versioned/fake/clientset_generated.go @@ -1,4 +1,4 @@ -// Copyright 2020 The NATS Authors +// Copyright 2024 The NATS Authors // 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 @@ -16,6 +16,7 @@ package fake import ( + applyconfiguration "github.com/nats-io/nack/pkg/jetstream/generated/applyconfiguration" clientset "github.com/nats-io/nack/pkg/jetstream/generated/clientset/versioned" jetstreamv1beta2 "github.com/nats-io/nack/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2" fakejetstreamv1beta2 "github.com/nats-io/nack/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/fake" @@ -28,8 +29,12 @@ import ( // NewSimpleClientset returns a clientset that will respond with the provided objects. // It's backed by a very simple object tracker that processes creates, updates and deletions as-is, -// without applying any validations and/or defaults. It shouldn't be considered a replacement +// without applying any field management, validations and/or defaults. It shouldn't be considered a replacement // for a real clientset and is mostly useful in simple unit tests. +// +// DEPRECATED: NewClientset replaces this with support for field management, which significantly improves +// server side apply testing. NewClientset is only available when apply configurations are generated (e.g. +// via --with-applyconfig). func NewSimpleClientset(objects ...runtime.Object) *Clientset { o := testing.NewObjectTracker(scheme, codecs.UniversalDecoder()) for _, obj := range objects { @@ -71,6 +76,38 @@ func (c *Clientset) Tracker() testing.ObjectTracker { return c.tracker } +// NewClientset returns a clientset that will respond with the provided objects. +// It's backed by a very simple object tracker that processes creates, updates and deletions as-is, +// without applying any validations and/or defaults. It shouldn't be considered a replacement +// for a real clientset and is mostly useful in simple unit tests. +func NewClientset(objects ...runtime.Object) *Clientset { + o := testing.NewFieldManagedObjectTracker( + scheme, + codecs.UniversalDecoder(), + applyconfiguration.NewTypeConverter(scheme), + ) + for _, obj := range objects { + if err := o.Add(obj); err != nil { + panic(err) + } + } + + cs := &Clientset{tracker: o} + cs.discovery = &fakediscovery.FakeDiscovery{Fake: &cs.Fake} + cs.AddReactor("*", "*", testing.ObjectReaction(o)) + cs.AddWatchReactor("*", func(action testing.Action) (handled bool, ret watch.Interface, err error) { + gvr := action.GetResource() + ns := action.GetNamespace() + watch, err := o.Watch(gvr, ns) + if err != nil { + return false, nil, err + } + return true, watch, nil + }) + + return cs +} + var ( _ clientset.Interface = &Clientset{} _ testing.FakeClient = &Clientset{} diff --git a/pkg/jetstream/generated/clientset/versioned/fake/doc.go b/pkg/jetstream/generated/clientset/versioned/fake/doc.go index 301e6b0d..fc05515c 100644 --- a/pkg/jetstream/generated/clientset/versioned/fake/doc.go +++ b/pkg/jetstream/generated/clientset/versioned/fake/doc.go @@ -1,4 +1,4 @@ -// Copyright 2020 The NATS Authors +// Copyright 2024 The NATS Authors // 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 diff --git a/pkg/jetstream/generated/clientset/versioned/fake/register.go b/pkg/jetstream/generated/clientset/versioned/fake/register.go index f15e342d..c64246e0 100644 --- a/pkg/jetstream/generated/clientset/versioned/fake/register.go +++ b/pkg/jetstream/generated/clientset/versioned/fake/register.go @@ -1,4 +1,4 @@ -// Copyright 2020 The NATS Authors +// Copyright 2024 The NATS Authors // 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 diff --git a/pkg/jetstream/generated/clientset/versioned/scheme/doc.go b/pkg/jetstream/generated/clientset/versioned/scheme/doc.go index 81abe964..31cfa974 100644 --- a/pkg/jetstream/generated/clientset/versioned/scheme/doc.go +++ b/pkg/jetstream/generated/clientset/versioned/scheme/doc.go @@ -1,4 +1,4 @@ -// Copyright 2020 The NATS Authors +// Copyright 2024 The NATS Authors // 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 diff --git a/pkg/jetstream/generated/clientset/versioned/scheme/register.go b/pkg/jetstream/generated/clientset/versioned/scheme/register.go index c80af823..c900985f 100644 --- a/pkg/jetstream/generated/clientset/versioned/scheme/register.go +++ b/pkg/jetstream/generated/clientset/versioned/scheme/register.go @@ -1,4 +1,4 @@ -// Copyright 2020 The NATS Authors +// Copyright 2024 The NATS Authors // 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 diff --git a/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/account.go b/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/account.go index 9bde13cc..b495a525 100644 --- a/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/account.go +++ b/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/account.go @@ -1,4 +1,4 @@ -// Copyright 2020 The NATS Authors +// Copyright 2024 The NATS Authors // 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 @@ -17,9 +17,6 @@ package v1beta2 import ( "context" - json "encoding/json" - "fmt" - "time" v1beta2 "github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2" jetstreamv1beta2 "github.com/nats-io/nack/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2" @@ -27,7 +24,7 @@ import ( v1 "k8s.io/apimachinery/pkg/apis/meta/v1" types "k8s.io/apimachinery/pkg/types" watch "k8s.io/apimachinery/pkg/watch" - rest "k8s.io/client-go/rest" + gentype "k8s.io/client-go/gentype" ) // AccountsGetter has a method to return a AccountInterface. @@ -40,6 +37,7 @@ type AccountsGetter interface { type AccountInterface interface { Create(ctx context.Context, account *v1beta2.Account, opts v1.CreateOptions) (*v1beta2.Account, error) Update(ctx context.Context, account *v1beta2.Account, opts v1.UpdateOptions) (*v1beta2.Account, error) + // Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). UpdateStatus(ctx context.Context, account *v1beta2.Account, opts v1.UpdateOptions) (*v1beta2.Account, error) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error @@ -48,206 +46,25 @@ type AccountInterface interface { Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1beta2.Account, err error) Apply(ctx context.Context, account *jetstreamv1beta2.AccountApplyConfiguration, opts v1.ApplyOptions) (result *v1beta2.Account, err error) + // Add a +genclient:noStatus comment above the type to avoid generating ApplyStatus(). ApplyStatus(ctx context.Context, account *jetstreamv1beta2.AccountApplyConfiguration, opts v1.ApplyOptions) (result *v1beta2.Account, err error) AccountExpansion } // accounts implements AccountInterface type accounts struct { - client rest.Interface - ns string + *gentype.ClientWithListAndApply[*v1beta2.Account, *v1beta2.AccountList, *jetstreamv1beta2.AccountApplyConfiguration] } // newAccounts returns a Accounts func newAccounts(c *JetstreamV1beta2Client, namespace string) *accounts { return &accounts{ - client: c.RESTClient(), - ns: namespace, + gentype.NewClientWithListAndApply[*v1beta2.Account, *v1beta2.AccountList, *jetstreamv1beta2.AccountApplyConfiguration]( + "accounts", + c.RESTClient(), + scheme.ParameterCodec, + namespace, + func() *v1beta2.Account { return &v1beta2.Account{} }, + func() *v1beta2.AccountList { return &v1beta2.AccountList{} }), } } - -// Get takes name of the account, and returns the corresponding account object, and an error if there is any. -func (c *accounts) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1beta2.Account, err error) { - result = &v1beta2.Account{} - err = c.client.Get(). - Namespace(c.ns). - Resource("accounts"). - Name(name). - VersionedParams(&options, scheme.ParameterCodec). - Do(ctx). - Into(result) - return -} - -// List takes label and field selectors, and returns the list of Accounts that match those selectors. -func (c *accounts) List(ctx context.Context, opts v1.ListOptions) (result *v1beta2.AccountList, err error) { - var timeout time.Duration - if opts.TimeoutSeconds != nil { - timeout = time.Duration(*opts.TimeoutSeconds) * time.Second - } - result = &v1beta2.AccountList{} - err = c.client.Get(). - Namespace(c.ns). - Resource("accounts"). - VersionedParams(&opts, scheme.ParameterCodec). - Timeout(timeout). - Do(ctx). - Into(result) - return -} - -// Watch returns a watch.Interface that watches the requested accounts. -func (c *accounts) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { - var timeout time.Duration - if opts.TimeoutSeconds != nil { - timeout = time.Duration(*opts.TimeoutSeconds) * time.Second - } - opts.Watch = true - return c.client.Get(). - Namespace(c.ns). - Resource("accounts"). - VersionedParams(&opts, scheme.ParameterCodec). - Timeout(timeout). - Watch(ctx) -} - -// Create takes the representation of a account and creates it. Returns the server's representation of the account, and an error, if there is any. -func (c *accounts) Create(ctx context.Context, account *v1beta2.Account, opts v1.CreateOptions) (result *v1beta2.Account, err error) { - result = &v1beta2.Account{} - err = c.client.Post(). - Namespace(c.ns). - Resource("accounts"). - VersionedParams(&opts, scheme.ParameterCodec). - Body(account). - Do(ctx). - Into(result) - return -} - -// Update takes the representation of a account and updates it. Returns the server's representation of the account, and an error, if there is any. -func (c *accounts) Update(ctx context.Context, account *v1beta2.Account, opts v1.UpdateOptions) (result *v1beta2.Account, err error) { - result = &v1beta2.Account{} - err = c.client.Put(). - Namespace(c.ns). - Resource("accounts"). - Name(account.Name). - VersionedParams(&opts, scheme.ParameterCodec). - Body(account). - Do(ctx). - Into(result) - return -} - -// UpdateStatus was generated because the type contains a Status member. -// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). -func (c *accounts) UpdateStatus(ctx context.Context, account *v1beta2.Account, opts v1.UpdateOptions) (result *v1beta2.Account, err error) { - result = &v1beta2.Account{} - err = c.client.Put(). - Namespace(c.ns). - Resource("accounts"). - Name(account.Name). - SubResource("status"). - VersionedParams(&opts, scheme.ParameterCodec). - Body(account). - Do(ctx). - Into(result) - return -} - -// Delete takes name of the account and deletes it. Returns an error if one occurs. -func (c *accounts) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { - return c.client.Delete(). - Namespace(c.ns). - Resource("accounts"). - Name(name). - Body(&opts). - Do(ctx). - Error() -} - -// DeleteCollection deletes a collection of objects. -func (c *accounts) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { - var timeout time.Duration - if listOpts.TimeoutSeconds != nil { - timeout = time.Duration(*listOpts.TimeoutSeconds) * time.Second - } - return c.client.Delete(). - Namespace(c.ns). - Resource("accounts"). - VersionedParams(&listOpts, scheme.ParameterCodec). - Timeout(timeout). - Body(&opts). - Do(ctx). - Error() -} - -// Patch applies the patch and returns the patched account. -func (c *accounts) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1beta2.Account, err error) { - result = &v1beta2.Account{} - err = c.client.Patch(pt). - Namespace(c.ns). - Resource("accounts"). - Name(name). - SubResource(subresources...). - VersionedParams(&opts, scheme.ParameterCodec). - Body(data). - Do(ctx). - Into(result) - return -} - -// Apply takes the given apply declarative configuration, applies it and returns the applied account. -func (c *accounts) Apply(ctx context.Context, account *jetstreamv1beta2.AccountApplyConfiguration, opts v1.ApplyOptions) (result *v1beta2.Account, err error) { - if account == nil { - return nil, fmt.Errorf("account provided to Apply must not be nil") - } - patchOpts := opts.ToPatchOptions() - data, err := json.Marshal(account) - if err != nil { - return nil, err - } - name := account.Name - if name == nil { - return nil, fmt.Errorf("account.Name must be provided to Apply") - } - result = &v1beta2.Account{} - err = c.client.Patch(types.ApplyPatchType). - Namespace(c.ns). - Resource("accounts"). - Name(*name). - VersionedParams(&patchOpts, scheme.ParameterCodec). - Body(data). - Do(ctx). - Into(result) - return -} - -// ApplyStatus was generated because the type contains a Status member. -// Add a +genclient:noStatus comment above the type to avoid generating ApplyStatus(). -func (c *accounts) ApplyStatus(ctx context.Context, account *jetstreamv1beta2.AccountApplyConfiguration, opts v1.ApplyOptions) (result *v1beta2.Account, err error) { - if account == nil { - return nil, fmt.Errorf("account provided to Apply must not be nil") - } - patchOpts := opts.ToPatchOptions() - data, err := json.Marshal(account) - if err != nil { - return nil, err - } - - name := account.Name - if name == nil { - return nil, fmt.Errorf("account.Name must be provided to Apply") - } - - result = &v1beta2.Account{} - err = c.client.Patch(types.ApplyPatchType). - Namespace(c.ns). - Resource("accounts"). - Name(*name). - SubResource("status"). - VersionedParams(&patchOpts, scheme.ParameterCodec). - Body(data). - Do(ctx). - Into(result) - return -} diff --git a/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/consumer.go b/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/consumer.go index 14430d25..de230925 100644 --- a/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/consumer.go +++ b/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/consumer.go @@ -1,4 +1,4 @@ -// Copyright 2020 The NATS Authors +// Copyright 2024 The NATS Authors // 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 @@ -17,9 +17,6 @@ package v1beta2 import ( "context" - json "encoding/json" - "fmt" - "time" v1beta2 "github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2" jetstreamv1beta2 "github.com/nats-io/nack/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2" @@ -27,7 +24,7 @@ import ( v1 "k8s.io/apimachinery/pkg/apis/meta/v1" types "k8s.io/apimachinery/pkg/types" watch "k8s.io/apimachinery/pkg/watch" - rest "k8s.io/client-go/rest" + gentype "k8s.io/client-go/gentype" ) // ConsumersGetter has a method to return a ConsumerInterface. @@ -40,6 +37,7 @@ type ConsumersGetter interface { type ConsumerInterface interface { Create(ctx context.Context, consumer *v1beta2.Consumer, opts v1.CreateOptions) (*v1beta2.Consumer, error) Update(ctx context.Context, consumer *v1beta2.Consumer, opts v1.UpdateOptions) (*v1beta2.Consumer, error) + // Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). UpdateStatus(ctx context.Context, consumer *v1beta2.Consumer, opts v1.UpdateOptions) (*v1beta2.Consumer, error) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error @@ -48,206 +46,25 @@ type ConsumerInterface interface { Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1beta2.Consumer, err error) Apply(ctx context.Context, consumer *jetstreamv1beta2.ConsumerApplyConfiguration, opts v1.ApplyOptions) (result *v1beta2.Consumer, err error) + // Add a +genclient:noStatus comment above the type to avoid generating ApplyStatus(). ApplyStatus(ctx context.Context, consumer *jetstreamv1beta2.ConsumerApplyConfiguration, opts v1.ApplyOptions) (result *v1beta2.Consumer, err error) ConsumerExpansion } // consumers implements ConsumerInterface type consumers struct { - client rest.Interface - ns string + *gentype.ClientWithListAndApply[*v1beta2.Consumer, *v1beta2.ConsumerList, *jetstreamv1beta2.ConsumerApplyConfiguration] } // newConsumers returns a Consumers func newConsumers(c *JetstreamV1beta2Client, namespace string) *consumers { return &consumers{ - client: c.RESTClient(), - ns: namespace, + gentype.NewClientWithListAndApply[*v1beta2.Consumer, *v1beta2.ConsumerList, *jetstreamv1beta2.ConsumerApplyConfiguration]( + "consumers", + c.RESTClient(), + scheme.ParameterCodec, + namespace, + func() *v1beta2.Consumer { return &v1beta2.Consumer{} }, + func() *v1beta2.ConsumerList { return &v1beta2.ConsumerList{} }), } } - -// Get takes name of the consumer, and returns the corresponding consumer object, and an error if there is any. -func (c *consumers) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1beta2.Consumer, err error) { - result = &v1beta2.Consumer{} - err = c.client.Get(). - Namespace(c.ns). - Resource("consumers"). - Name(name). - VersionedParams(&options, scheme.ParameterCodec). - Do(ctx). - Into(result) - return -} - -// List takes label and field selectors, and returns the list of Consumers that match those selectors. -func (c *consumers) List(ctx context.Context, opts v1.ListOptions) (result *v1beta2.ConsumerList, err error) { - var timeout time.Duration - if opts.TimeoutSeconds != nil { - timeout = time.Duration(*opts.TimeoutSeconds) * time.Second - } - result = &v1beta2.ConsumerList{} - err = c.client.Get(). - Namespace(c.ns). - Resource("consumers"). - VersionedParams(&opts, scheme.ParameterCodec). - Timeout(timeout). - Do(ctx). - Into(result) - return -} - -// Watch returns a watch.Interface that watches the requested consumers. -func (c *consumers) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { - var timeout time.Duration - if opts.TimeoutSeconds != nil { - timeout = time.Duration(*opts.TimeoutSeconds) * time.Second - } - opts.Watch = true - return c.client.Get(). - Namespace(c.ns). - Resource("consumers"). - VersionedParams(&opts, scheme.ParameterCodec). - Timeout(timeout). - Watch(ctx) -} - -// Create takes the representation of a consumer and creates it. Returns the server's representation of the consumer, and an error, if there is any. -func (c *consumers) Create(ctx context.Context, consumer *v1beta2.Consumer, opts v1.CreateOptions) (result *v1beta2.Consumer, err error) { - result = &v1beta2.Consumer{} - err = c.client.Post(). - Namespace(c.ns). - Resource("consumers"). - VersionedParams(&opts, scheme.ParameterCodec). - Body(consumer). - Do(ctx). - Into(result) - return -} - -// Update takes the representation of a consumer and updates it. Returns the server's representation of the consumer, and an error, if there is any. -func (c *consumers) Update(ctx context.Context, consumer *v1beta2.Consumer, opts v1.UpdateOptions) (result *v1beta2.Consumer, err error) { - result = &v1beta2.Consumer{} - err = c.client.Put(). - Namespace(c.ns). - Resource("consumers"). - Name(consumer.Name). - VersionedParams(&opts, scheme.ParameterCodec). - Body(consumer). - Do(ctx). - Into(result) - return -} - -// UpdateStatus was generated because the type contains a Status member. -// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). -func (c *consumers) UpdateStatus(ctx context.Context, consumer *v1beta2.Consumer, opts v1.UpdateOptions) (result *v1beta2.Consumer, err error) { - result = &v1beta2.Consumer{} - err = c.client.Put(). - Namespace(c.ns). - Resource("consumers"). - Name(consumer.Name). - SubResource("status"). - VersionedParams(&opts, scheme.ParameterCodec). - Body(consumer). - Do(ctx). - Into(result) - return -} - -// Delete takes name of the consumer and deletes it. Returns an error if one occurs. -func (c *consumers) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { - return c.client.Delete(). - Namespace(c.ns). - Resource("consumers"). - Name(name). - Body(&opts). - Do(ctx). - Error() -} - -// DeleteCollection deletes a collection of objects. -func (c *consumers) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { - var timeout time.Duration - if listOpts.TimeoutSeconds != nil { - timeout = time.Duration(*listOpts.TimeoutSeconds) * time.Second - } - return c.client.Delete(). - Namespace(c.ns). - Resource("consumers"). - VersionedParams(&listOpts, scheme.ParameterCodec). - Timeout(timeout). - Body(&opts). - Do(ctx). - Error() -} - -// Patch applies the patch and returns the patched consumer. -func (c *consumers) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1beta2.Consumer, err error) { - result = &v1beta2.Consumer{} - err = c.client.Patch(pt). - Namespace(c.ns). - Resource("consumers"). - Name(name). - SubResource(subresources...). - VersionedParams(&opts, scheme.ParameterCodec). - Body(data). - Do(ctx). - Into(result) - return -} - -// Apply takes the given apply declarative configuration, applies it and returns the applied consumer. -func (c *consumers) Apply(ctx context.Context, consumer *jetstreamv1beta2.ConsumerApplyConfiguration, opts v1.ApplyOptions) (result *v1beta2.Consumer, err error) { - if consumer == nil { - return nil, fmt.Errorf("consumer provided to Apply must not be nil") - } - patchOpts := opts.ToPatchOptions() - data, err := json.Marshal(consumer) - if err != nil { - return nil, err - } - name := consumer.Name - if name == nil { - return nil, fmt.Errorf("consumer.Name must be provided to Apply") - } - result = &v1beta2.Consumer{} - err = c.client.Patch(types.ApplyPatchType). - Namespace(c.ns). - Resource("consumers"). - Name(*name). - VersionedParams(&patchOpts, scheme.ParameterCodec). - Body(data). - Do(ctx). - Into(result) - return -} - -// ApplyStatus was generated because the type contains a Status member. -// Add a +genclient:noStatus comment above the type to avoid generating ApplyStatus(). -func (c *consumers) ApplyStatus(ctx context.Context, consumer *jetstreamv1beta2.ConsumerApplyConfiguration, opts v1.ApplyOptions) (result *v1beta2.Consumer, err error) { - if consumer == nil { - return nil, fmt.Errorf("consumer provided to Apply must not be nil") - } - patchOpts := opts.ToPatchOptions() - data, err := json.Marshal(consumer) - if err != nil { - return nil, err - } - - name := consumer.Name - if name == nil { - return nil, fmt.Errorf("consumer.Name must be provided to Apply") - } - - result = &v1beta2.Consumer{} - err = c.client.Patch(types.ApplyPatchType). - Namespace(c.ns). - Resource("consumers"). - Name(*name). - SubResource("status"). - VersionedParams(&patchOpts, scheme.ParameterCodec). - Body(data). - Do(ctx). - Into(result) - return -} diff --git a/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/doc.go b/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/doc.go index 2d154137..f278ba06 100644 --- a/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/doc.go +++ b/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/doc.go @@ -1,4 +1,4 @@ -// Copyright 2020 The NATS Authors +// Copyright 2024 The NATS Authors // 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 diff --git a/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/fake/doc.go b/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/fake/doc.go index dbeb4c5b..bd8826c0 100644 --- a/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/fake/doc.go +++ b/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/fake/doc.go @@ -1,4 +1,4 @@ -// Copyright 2020 The NATS Authors +// Copyright 2024 The NATS Authors // 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 diff --git a/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/fake/fake_account.go b/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/fake/fake_account.go index 24d1a110..fa2a89e2 100644 --- a/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/fake/fake_account.go +++ b/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/fake/fake_account.go @@ -1,4 +1,4 @@ -// Copyright 2020 The NATS Authors +// Copyright 2024 The NATS Authors // 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 @@ -41,22 +41,24 @@ var accountsKind = v1beta2.SchemeGroupVersion.WithKind("Account") // Get takes name of the account, and returns the corresponding account object, and an error if there is any. func (c *FakeAccounts) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1beta2.Account, err error) { + emptyResult := &v1beta2.Account{} obj, err := c.Fake. - Invokes(testing.NewGetAction(accountsResource, c.ns, name), &v1beta2.Account{}) + Invokes(testing.NewGetActionWithOptions(accountsResource, c.ns, name, options), emptyResult) if obj == nil { - return nil, err + return emptyResult, err } return obj.(*v1beta2.Account), err } // List takes label and field selectors, and returns the list of Accounts that match those selectors. func (c *FakeAccounts) List(ctx context.Context, opts v1.ListOptions) (result *v1beta2.AccountList, err error) { + emptyResult := &v1beta2.AccountList{} obj, err := c.Fake. - Invokes(testing.NewListAction(accountsResource, accountsKind, c.ns, opts), &v1beta2.AccountList{}) + Invokes(testing.NewListActionWithOptions(accountsResource, accountsKind, c.ns, opts), emptyResult) if obj == nil { - return nil, err + return emptyResult, err } label, _, _ := testing.ExtractFromListOptions(opts) @@ -75,40 +77,43 @@ func (c *FakeAccounts) List(ctx context.Context, opts v1.ListOptions) (result *v // Watch returns a watch.Interface that watches the requested accounts. func (c *FakeAccounts) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { return c.Fake. - InvokesWatch(testing.NewWatchAction(accountsResource, c.ns, opts)) + InvokesWatch(testing.NewWatchActionWithOptions(accountsResource, c.ns, opts)) } // Create takes the representation of a account and creates it. Returns the server's representation of the account, and an error, if there is any. func (c *FakeAccounts) Create(ctx context.Context, account *v1beta2.Account, opts v1.CreateOptions) (result *v1beta2.Account, err error) { + emptyResult := &v1beta2.Account{} obj, err := c.Fake. - Invokes(testing.NewCreateAction(accountsResource, c.ns, account), &v1beta2.Account{}) + Invokes(testing.NewCreateActionWithOptions(accountsResource, c.ns, account, opts), emptyResult) if obj == nil { - return nil, err + return emptyResult, err } return obj.(*v1beta2.Account), err } // Update takes the representation of a account and updates it. Returns the server's representation of the account, and an error, if there is any. func (c *FakeAccounts) Update(ctx context.Context, account *v1beta2.Account, opts v1.UpdateOptions) (result *v1beta2.Account, err error) { + emptyResult := &v1beta2.Account{} obj, err := c.Fake. - Invokes(testing.NewUpdateAction(accountsResource, c.ns, account), &v1beta2.Account{}) + Invokes(testing.NewUpdateActionWithOptions(accountsResource, c.ns, account, opts), emptyResult) if obj == nil { - return nil, err + return emptyResult, err } return obj.(*v1beta2.Account), err } // UpdateStatus was generated because the type contains a Status member. // Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). -func (c *FakeAccounts) UpdateStatus(ctx context.Context, account *v1beta2.Account, opts v1.UpdateOptions) (*v1beta2.Account, error) { +func (c *FakeAccounts) UpdateStatus(ctx context.Context, account *v1beta2.Account, opts v1.UpdateOptions) (result *v1beta2.Account, err error) { + emptyResult := &v1beta2.Account{} obj, err := c.Fake. - Invokes(testing.NewUpdateSubresourceAction(accountsResource, "status", c.ns, account), &v1beta2.Account{}) + Invokes(testing.NewUpdateSubresourceActionWithOptions(accountsResource, "status", c.ns, account, opts), emptyResult) if obj == nil { - return nil, err + return emptyResult, err } return obj.(*v1beta2.Account), err } @@ -123,7 +128,7 @@ func (c *FakeAccounts) Delete(ctx context.Context, name string, opts v1.DeleteOp // DeleteCollection deletes a collection of objects. func (c *FakeAccounts) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { - action := testing.NewDeleteCollectionAction(accountsResource, c.ns, listOpts) + action := testing.NewDeleteCollectionActionWithOptions(accountsResource, c.ns, opts, listOpts) _, err := c.Fake.Invokes(action, &v1beta2.AccountList{}) return err @@ -131,11 +136,12 @@ func (c *FakeAccounts) DeleteCollection(ctx context.Context, opts v1.DeleteOptio // Patch applies the patch and returns the patched account. func (c *FakeAccounts) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1beta2.Account, err error) { + emptyResult := &v1beta2.Account{} obj, err := c.Fake. - Invokes(testing.NewPatchSubresourceAction(accountsResource, c.ns, name, pt, data, subresources...), &v1beta2.Account{}) + Invokes(testing.NewPatchSubresourceActionWithOptions(accountsResource, c.ns, name, pt, data, opts, subresources...), emptyResult) if obj == nil { - return nil, err + return emptyResult, err } return obj.(*v1beta2.Account), err } @@ -153,11 +159,12 @@ func (c *FakeAccounts) Apply(ctx context.Context, account *jetstreamv1beta2.Acco if name == nil { return nil, fmt.Errorf("account.Name must be provided to Apply") } + emptyResult := &v1beta2.Account{} obj, err := c.Fake. - Invokes(testing.NewPatchSubresourceAction(accountsResource, c.ns, *name, types.ApplyPatchType, data), &v1beta2.Account{}) + Invokes(testing.NewPatchSubresourceActionWithOptions(accountsResource, c.ns, *name, types.ApplyPatchType, data, opts.ToPatchOptions()), emptyResult) if obj == nil { - return nil, err + return emptyResult, err } return obj.(*v1beta2.Account), err } @@ -176,11 +183,12 @@ func (c *FakeAccounts) ApplyStatus(ctx context.Context, account *jetstreamv1beta if name == nil { return nil, fmt.Errorf("account.Name must be provided to Apply") } + emptyResult := &v1beta2.Account{} obj, err := c.Fake. - Invokes(testing.NewPatchSubresourceAction(accountsResource, c.ns, *name, types.ApplyPatchType, data, "status"), &v1beta2.Account{}) + Invokes(testing.NewPatchSubresourceActionWithOptions(accountsResource, c.ns, *name, types.ApplyPatchType, data, opts.ToPatchOptions(), "status"), emptyResult) if obj == nil { - return nil, err + return emptyResult, err } return obj.(*v1beta2.Account), err } diff --git a/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/fake/fake_consumer.go b/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/fake/fake_consumer.go index 993aaa62..72e4d444 100644 --- a/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/fake/fake_consumer.go +++ b/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/fake/fake_consumer.go @@ -1,4 +1,4 @@ -// Copyright 2020 The NATS Authors +// Copyright 2024 The NATS Authors // 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 @@ -41,22 +41,24 @@ var consumersKind = v1beta2.SchemeGroupVersion.WithKind("Consumer") // Get takes name of the consumer, and returns the corresponding consumer object, and an error if there is any. func (c *FakeConsumers) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1beta2.Consumer, err error) { + emptyResult := &v1beta2.Consumer{} obj, err := c.Fake. - Invokes(testing.NewGetAction(consumersResource, c.ns, name), &v1beta2.Consumer{}) + Invokes(testing.NewGetActionWithOptions(consumersResource, c.ns, name, options), emptyResult) if obj == nil { - return nil, err + return emptyResult, err } return obj.(*v1beta2.Consumer), err } // List takes label and field selectors, and returns the list of Consumers that match those selectors. func (c *FakeConsumers) List(ctx context.Context, opts v1.ListOptions) (result *v1beta2.ConsumerList, err error) { + emptyResult := &v1beta2.ConsumerList{} obj, err := c.Fake. - Invokes(testing.NewListAction(consumersResource, consumersKind, c.ns, opts), &v1beta2.ConsumerList{}) + Invokes(testing.NewListActionWithOptions(consumersResource, consumersKind, c.ns, opts), emptyResult) if obj == nil { - return nil, err + return emptyResult, err } label, _, _ := testing.ExtractFromListOptions(opts) @@ -75,40 +77,43 @@ func (c *FakeConsumers) List(ctx context.Context, opts v1.ListOptions) (result * // Watch returns a watch.Interface that watches the requested consumers. func (c *FakeConsumers) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { return c.Fake. - InvokesWatch(testing.NewWatchAction(consumersResource, c.ns, opts)) + InvokesWatch(testing.NewWatchActionWithOptions(consumersResource, c.ns, opts)) } // Create takes the representation of a consumer and creates it. Returns the server's representation of the consumer, and an error, if there is any. func (c *FakeConsumers) Create(ctx context.Context, consumer *v1beta2.Consumer, opts v1.CreateOptions) (result *v1beta2.Consumer, err error) { + emptyResult := &v1beta2.Consumer{} obj, err := c.Fake. - Invokes(testing.NewCreateAction(consumersResource, c.ns, consumer), &v1beta2.Consumer{}) + Invokes(testing.NewCreateActionWithOptions(consumersResource, c.ns, consumer, opts), emptyResult) if obj == nil { - return nil, err + return emptyResult, err } return obj.(*v1beta2.Consumer), err } // Update takes the representation of a consumer and updates it. Returns the server's representation of the consumer, and an error, if there is any. func (c *FakeConsumers) Update(ctx context.Context, consumer *v1beta2.Consumer, opts v1.UpdateOptions) (result *v1beta2.Consumer, err error) { + emptyResult := &v1beta2.Consumer{} obj, err := c.Fake. - Invokes(testing.NewUpdateAction(consumersResource, c.ns, consumer), &v1beta2.Consumer{}) + Invokes(testing.NewUpdateActionWithOptions(consumersResource, c.ns, consumer, opts), emptyResult) if obj == nil { - return nil, err + return emptyResult, err } return obj.(*v1beta2.Consumer), err } // UpdateStatus was generated because the type contains a Status member. // Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). -func (c *FakeConsumers) UpdateStatus(ctx context.Context, consumer *v1beta2.Consumer, opts v1.UpdateOptions) (*v1beta2.Consumer, error) { +func (c *FakeConsumers) UpdateStatus(ctx context.Context, consumer *v1beta2.Consumer, opts v1.UpdateOptions) (result *v1beta2.Consumer, err error) { + emptyResult := &v1beta2.Consumer{} obj, err := c.Fake. - Invokes(testing.NewUpdateSubresourceAction(consumersResource, "status", c.ns, consumer), &v1beta2.Consumer{}) + Invokes(testing.NewUpdateSubresourceActionWithOptions(consumersResource, "status", c.ns, consumer, opts), emptyResult) if obj == nil { - return nil, err + return emptyResult, err } return obj.(*v1beta2.Consumer), err } @@ -123,7 +128,7 @@ func (c *FakeConsumers) Delete(ctx context.Context, name string, opts v1.DeleteO // DeleteCollection deletes a collection of objects. func (c *FakeConsumers) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { - action := testing.NewDeleteCollectionAction(consumersResource, c.ns, listOpts) + action := testing.NewDeleteCollectionActionWithOptions(consumersResource, c.ns, opts, listOpts) _, err := c.Fake.Invokes(action, &v1beta2.ConsumerList{}) return err @@ -131,11 +136,12 @@ func (c *FakeConsumers) DeleteCollection(ctx context.Context, opts v1.DeleteOpti // Patch applies the patch and returns the patched consumer. func (c *FakeConsumers) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1beta2.Consumer, err error) { + emptyResult := &v1beta2.Consumer{} obj, err := c.Fake. - Invokes(testing.NewPatchSubresourceAction(consumersResource, c.ns, name, pt, data, subresources...), &v1beta2.Consumer{}) + Invokes(testing.NewPatchSubresourceActionWithOptions(consumersResource, c.ns, name, pt, data, opts, subresources...), emptyResult) if obj == nil { - return nil, err + return emptyResult, err } return obj.(*v1beta2.Consumer), err } @@ -153,11 +159,12 @@ func (c *FakeConsumers) Apply(ctx context.Context, consumer *jetstreamv1beta2.Co if name == nil { return nil, fmt.Errorf("consumer.Name must be provided to Apply") } + emptyResult := &v1beta2.Consumer{} obj, err := c.Fake. - Invokes(testing.NewPatchSubresourceAction(consumersResource, c.ns, *name, types.ApplyPatchType, data), &v1beta2.Consumer{}) + Invokes(testing.NewPatchSubresourceActionWithOptions(consumersResource, c.ns, *name, types.ApplyPatchType, data, opts.ToPatchOptions()), emptyResult) if obj == nil { - return nil, err + return emptyResult, err } return obj.(*v1beta2.Consumer), err } @@ -176,11 +183,12 @@ func (c *FakeConsumers) ApplyStatus(ctx context.Context, consumer *jetstreamv1be if name == nil { return nil, fmt.Errorf("consumer.Name must be provided to Apply") } + emptyResult := &v1beta2.Consumer{} obj, err := c.Fake. - Invokes(testing.NewPatchSubresourceAction(consumersResource, c.ns, *name, types.ApplyPatchType, data, "status"), &v1beta2.Consumer{}) + Invokes(testing.NewPatchSubresourceActionWithOptions(consumersResource, c.ns, *name, types.ApplyPatchType, data, opts.ToPatchOptions(), "status"), emptyResult) if obj == nil { - return nil, err + return emptyResult, err } return obj.(*v1beta2.Consumer), err } diff --git a/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/fake/fake_jetstream_client.go b/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/fake/fake_jetstream_client.go index 66d7cd48..31d69c86 100644 --- a/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/fake/fake_jetstream_client.go +++ b/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/fake/fake_jetstream_client.go @@ -1,4 +1,4 @@ -// Copyright 2020 The NATS Authors +// Copyright 2024 The NATS Authors // 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 diff --git a/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/fake/fake_stream.go b/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/fake/fake_stream.go index cfe11d0d..da603cc1 100644 --- a/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/fake/fake_stream.go +++ b/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/fake/fake_stream.go @@ -1,4 +1,4 @@ -// Copyright 2020 The NATS Authors +// Copyright 2024 The NATS Authors // 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 @@ -41,22 +41,24 @@ var streamsKind = v1beta2.SchemeGroupVersion.WithKind("Stream") // Get takes name of the stream, and returns the corresponding stream object, and an error if there is any. func (c *FakeStreams) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1beta2.Stream, err error) { + emptyResult := &v1beta2.Stream{} obj, err := c.Fake. - Invokes(testing.NewGetAction(streamsResource, c.ns, name), &v1beta2.Stream{}) + Invokes(testing.NewGetActionWithOptions(streamsResource, c.ns, name, options), emptyResult) if obj == nil { - return nil, err + return emptyResult, err } return obj.(*v1beta2.Stream), err } // List takes label and field selectors, and returns the list of Streams that match those selectors. func (c *FakeStreams) List(ctx context.Context, opts v1.ListOptions) (result *v1beta2.StreamList, err error) { + emptyResult := &v1beta2.StreamList{} obj, err := c.Fake. - Invokes(testing.NewListAction(streamsResource, streamsKind, c.ns, opts), &v1beta2.StreamList{}) + Invokes(testing.NewListActionWithOptions(streamsResource, streamsKind, c.ns, opts), emptyResult) if obj == nil { - return nil, err + return emptyResult, err } label, _, _ := testing.ExtractFromListOptions(opts) @@ -75,40 +77,43 @@ func (c *FakeStreams) List(ctx context.Context, opts v1.ListOptions) (result *v1 // Watch returns a watch.Interface that watches the requested streams. func (c *FakeStreams) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { return c.Fake. - InvokesWatch(testing.NewWatchAction(streamsResource, c.ns, opts)) + InvokesWatch(testing.NewWatchActionWithOptions(streamsResource, c.ns, opts)) } // Create takes the representation of a stream and creates it. Returns the server's representation of the stream, and an error, if there is any. func (c *FakeStreams) Create(ctx context.Context, stream *v1beta2.Stream, opts v1.CreateOptions) (result *v1beta2.Stream, err error) { + emptyResult := &v1beta2.Stream{} obj, err := c.Fake. - Invokes(testing.NewCreateAction(streamsResource, c.ns, stream), &v1beta2.Stream{}) + Invokes(testing.NewCreateActionWithOptions(streamsResource, c.ns, stream, opts), emptyResult) if obj == nil { - return nil, err + return emptyResult, err } return obj.(*v1beta2.Stream), err } // Update takes the representation of a stream and updates it. Returns the server's representation of the stream, and an error, if there is any. func (c *FakeStreams) Update(ctx context.Context, stream *v1beta2.Stream, opts v1.UpdateOptions) (result *v1beta2.Stream, err error) { + emptyResult := &v1beta2.Stream{} obj, err := c.Fake. - Invokes(testing.NewUpdateAction(streamsResource, c.ns, stream), &v1beta2.Stream{}) + Invokes(testing.NewUpdateActionWithOptions(streamsResource, c.ns, stream, opts), emptyResult) if obj == nil { - return nil, err + return emptyResult, err } return obj.(*v1beta2.Stream), err } // UpdateStatus was generated because the type contains a Status member. // Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). -func (c *FakeStreams) UpdateStatus(ctx context.Context, stream *v1beta2.Stream, opts v1.UpdateOptions) (*v1beta2.Stream, error) { +func (c *FakeStreams) UpdateStatus(ctx context.Context, stream *v1beta2.Stream, opts v1.UpdateOptions) (result *v1beta2.Stream, err error) { + emptyResult := &v1beta2.Stream{} obj, err := c.Fake. - Invokes(testing.NewUpdateSubresourceAction(streamsResource, "status", c.ns, stream), &v1beta2.Stream{}) + Invokes(testing.NewUpdateSubresourceActionWithOptions(streamsResource, "status", c.ns, stream, opts), emptyResult) if obj == nil { - return nil, err + return emptyResult, err } return obj.(*v1beta2.Stream), err } @@ -123,7 +128,7 @@ func (c *FakeStreams) Delete(ctx context.Context, name string, opts v1.DeleteOpt // DeleteCollection deletes a collection of objects. func (c *FakeStreams) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { - action := testing.NewDeleteCollectionAction(streamsResource, c.ns, listOpts) + action := testing.NewDeleteCollectionActionWithOptions(streamsResource, c.ns, opts, listOpts) _, err := c.Fake.Invokes(action, &v1beta2.StreamList{}) return err @@ -131,11 +136,12 @@ func (c *FakeStreams) DeleteCollection(ctx context.Context, opts v1.DeleteOption // Patch applies the patch and returns the patched stream. func (c *FakeStreams) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1beta2.Stream, err error) { + emptyResult := &v1beta2.Stream{} obj, err := c.Fake. - Invokes(testing.NewPatchSubresourceAction(streamsResource, c.ns, name, pt, data, subresources...), &v1beta2.Stream{}) + Invokes(testing.NewPatchSubresourceActionWithOptions(streamsResource, c.ns, name, pt, data, opts, subresources...), emptyResult) if obj == nil { - return nil, err + return emptyResult, err } return obj.(*v1beta2.Stream), err } @@ -153,11 +159,12 @@ func (c *FakeStreams) Apply(ctx context.Context, stream *jetstreamv1beta2.Stream if name == nil { return nil, fmt.Errorf("stream.Name must be provided to Apply") } + emptyResult := &v1beta2.Stream{} obj, err := c.Fake. - Invokes(testing.NewPatchSubresourceAction(streamsResource, c.ns, *name, types.ApplyPatchType, data), &v1beta2.Stream{}) + Invokes(testing.NewPatchSubresourceActionWithOptions(streamsResource, c.ns, *name, types.ApplyPatchType, data, opts.ToPatchOptions()), emptyResult) if obj == nil { - return nil, err + return emptyResult, err } return obj.(*v1beta2.Stream), err } @@ -176,11 +183,12 @@ func (c *FakeStreams) ApplyStatus(ctx context.Context, stream *jetstreamv1beta2. if name == nil { return nil, fmt.Errorf("stream.Name must be provided to Apply") } + emptyResult := &v1beta2.Stream{} obj, err := c.Fake. - Invokes(testing.NewPatchSubresourceAction(streamsResource, c.ns, *name, types.ApplyPatchType, data, "status"), &v1beta2.Stream{}) + Invokes(testing.NewPatchSubresourceActionWithOptions(streamsResource, c.ns, *name, types.ApplyPatchType, data, opts.ToPatchOptions(), "status"), emptyResult) if obj == nil { - return nil, err + return emptyResult, err } return obj.(*v1beta2.Stream), err } diff --git a/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/generated_expansion.go b/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/generated_expansion.go index cc9ab72a..f3410b9a 100644 --- a/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/generated_expansion.go +++ b/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/generated_expansion.go @@ -1,4 +1,4 @@ -// Copyright 2020 The NATS Authors +// Copyright 2024 The NATS Authors // 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 diff --git a/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/jetstream_client.go b/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/jetstream_client.go index cbf0da3a..b6115fcc 100644 --- a/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/jetstream_client.go +++ b/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/jetstream_client.go @@ -1,4 +1,4 @@ -// Copyright 2020 The NATS Authors +// Copyright 2024 The NATS Authors // 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 diff --git a/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/stream.go b/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/stream.go index d68f306f..44008042 100644 --- a/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/stream.go +++ b/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/stream.go @@ -1,4 +1,4 @@ -// Copyright 2020 The NATS Authors +// Copyright 2024 The NATS Authors // 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 @@ -17,9 +17,6 @@ package v1beta2 import ( "context" - json "encoding/json" - "fmt" - "time" v1beta2 "github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2" jetstreamv1beta2 "github.com/nats-io/nack/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2" @@ -27,7 +24,7 @@ import ( v1 "k8s.io/apimachinery/pkg/apis/meta/v1" types "k8s.io/apimachinery/pkg/types" watch "k8s.io/apimachinery/pkg/watch" - rest "k8s.io/client-go/rest" + gentype "k8s.io/client-go/gentype" ) // StreamsGetter has a method to return a StreamInterface. @@ -40,6 +37,7 @@ type StreamsGetter interface { type StreamInterface interface { Create(ctx context.Context, stream *v1beta2.Stream, opts v1.CreateOptions) (*v1beta2.Stream, error) Update(ctx context.Context, stream *v1beta2.Stream, opts v1.UpdateOptions) (*v1beta2.Stream, error) + // Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). UpdateStatus(ctx context.Context, stream *v1beta2.Stream, opts v1.UpdateOptions) (*v1beta2.Stream, error) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error @@ -48,206 +46,25 @@ type StreamInterface interface { Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1beta2.Stream, err error) Apply(ctx context.Context, stream *jetstreamv1beta2.StreamApplyConfiguration, opts v1.ApplyOptions) (result *v1beta2.Stream, err error) + // Add a +genclient:noStatus comment above the type to avoid generating ApplyStatus(). ApplyStatus(ctx context.Context, stream *jetstreamv1beta2.StreamApplyConfiguration, opts v1.ApplyOptions) (result *v1beta2.Stream, err error) StreamExpansion } // streams implements StreamInterface type streams struct { - client rest.Interface - ns string + *gentype.ClientWithListAndApply[*v1beta2.Stream, *v1beta2.StreamList, *jetstreamv1beta2.StreamApplyConfiguration] } // newStreams returns a Streams func newStreams(c *JetstreamV1beta2Client, namespace string) *streams { return &streams{ - client: c.RESTClient(), - ns: namespace, + gentype.NewClientWithListAndApply[*v1beta2.Stream, *v1beta2.StreamList, *jetstreamv1beta2.StreamApplyConfiguration]( + "streams", + c.RESTClient(), + scheme.ParameterCodec, + namespace, + func() *v1beta2.Stream { return &v1beta2.Stream{} }, + func() *v1beta2.StreamList { return &v1beta2.StreamList{} }), } } - -// Get takes name of the stream, and returns the corresponding stream object, and an error if there is any. -func (c *streams) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1beta2.Stream, err error) { - result = &v1beta2.Stream{} - err = c.client.Get(). - Namespace(c.ns). - Resource("streams"). - Name(name). - VersionedParams(&options, scheme.ParameterCodec). - Do(ctx). - Into(result) - return -} - -// List takes label and field selectors, and returns the list of Streams that match those selectors. -func (c *streams) List(ctx context.Context, opts v1.ListOptions) (result *v1beta2.StreamList, err error) { - var timeout time.Duration - if opts.TimeoutSeconds != nil { - timeout = time.Duration(*opts.TimeoutSeconds) * time.Second - } - result = &v1beta2.StreamList{} - err = c.client.Get(). - Namespace(c.ns). - Resource("streams"). - VersionedParams(&opts, scheme.ParameterCodec). - Timeout(timeout). - Do(ctx). - Into(result) - return -} - -// Watch returns a watch.Interface that watches the requested streams. -func (c *streams) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { - var timeout time.Duration - if opts.TimeoutSeconds != nil { - timeout = time.Duration(*opts.TimeoutSeconds) * time.Second - } - opts.Watch = true - return c.client.Get(). - Namespace(c.ns). - Resource("streams"). - VersionedParams(&opts, scheme.ParameterCodec). - Timeout(timeout). - Watch(ctx) -} - -// Create takes the representation of a stream and creates it. Returns the server's representation of the stream, and an error, if there is any. -func (c *streams) Create(ctx context.Context, stream *v1beta2.Stream, opts v1.CreateOptions) (result *v1beta2.Stream, err error) { - result = &v1beta2.Stream{} - err = c.client.Post(). - Namespace(c.ns). - Resource("streams"). - VersionedParams(&opts, scheme.ParameterCodec). - Body(stream). - Do(ctx). - Into(result) - return -} - -// Update takes the representation of a stream and updates it. Returns the server's representation of the stream, and an error, if there is any. -func (c *streams) Update(ctx context.Context, stream *v1beta2.Stream, opts v1.UpdateOptions) (result *v1beta2.Stream, err error) { - result = &v1beta2.Stream{} - err = c.client.Put(). - Namespace(c.ns). - Resource("streams"). - Name(stream.Name). - VersionedParams(&opts, scheme.ParameterCodec). - Body(stream). - Do(ctx). - Into(result) - return -} - -// UpdateStatus was generated because the type contains a Status member. -// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). -func (c *streams) UpdateStatus(ctx context.Context, stream *v1beta2.Stream, opts v1.UpdateOptions) (result *v1beta2.Stream, err error) { - result = &v1beta2.Stream{} - err = c.client.Put(). - Namespace(c.ns). - Resource("streams"). - Name(stream.Name). - SubResource("status"). - VersionedParams(&opts, scheme.ParameterCodec). - Body(stream). - Do(ctx). - Into(result) - return -} - -// Delete takes name of the stream and deletes it. Returns an error if one occurs. -func (c *streams) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { - return c.client.Delete(). - Namespace(c.ns). - Resource("streams"). - Name(name). - Body(&opts). - Do(ctx). - Error() -} - -// DeleteCollection deletes a collection of objects. -func (c *streams) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { - var timeout time.Duration - if listOpts.TimeoutSeconds != nil { - timeout = time.Duration(*listOpts.TimeoutSeconds) * time.Second - } - return c.client.Delete(). - Namespace(c.ns). - Resource("streams"). - VersionedParams(&listOpts, scheme.ParameterCodec). - Timeout(timeout). - Body(&opts). - Do(ctx). - Error() -} - -// Patch applies the patch and returns the patched stream. -func (c *streams) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1beta2.Stream, err error) { - result = &v1beta2.Stream{} - err = c.client.Patch(pt). - Namespace(c.ns). - Resource("streams"). - Name(name). - SubResource(subresources...). - VersionedParams(&opts, scheme.ParameterCodec). - Body(data). - Do(ctx). - Into(result) - return -} - -// Apply takes the given apply declarative configuration, applies it and returns the applied stream. -func (c *streams) Apply(ctx context.Context, stream *jetstreamv1beta2.StreamApplyConfiguration, opts v1.ApplyOptions) (result *v1beta2.Stream, err error) { - if stream == nil { - return nil, fmt.Errorf("stream provided to Apply must not be nil") - } - patchOpts := opts.ToPatchOptions() - data, err := json.Marshal(stream) - if err != nil { - return nil, err - } - name := stream.Name - if name == nil { - return nil, fmt.Errorf("stream.Name must be provided to Apply") - } - result = &v1beta2.Stream{} - err = c.client.Patch(types.ApplyPatchType). - Namespace(c.ns). - Resource("streams"). - Name(*name). - VersionedParams(&patchOpts, scheme.ParameterCodec). - Body(data). - Do(ctx). - Into(result) - return -} - -// ApplyStatus was generated because the type contains a Status member. -// Add a +genclient:noStatus comment above the type to avoid generating ApplyStatus(). -func (c *streams) ApplyStatus(ctx context.Context, stream *jetstreamv1beta2.StreamApplyConfiguration, opts v1.ApplyOptions) (result *v1beta2.Stream, err error) { - if stream == nil { - return nil, fmt.Errorf("stream provided to Apply must not be nil") - } - patchOpts := opts.ToPatchOptions() - data, err := json.Marshal(stream) - if err != nil { - return nil, err - } - - name := stream.Name - if name == nil { - return nil, fmt.Errorf("stream.Name must be provided to Apply") - } - - result = &v1beta2.Stream{} - err = c.client.Patch(types.ApplyPatchType). - Namespace(c.ns). - Resource("streams"). - Name(*name). - SubResource("status"). - VersionedParams(&patchOpts, scheme.ParameterCodec). - Body(data). - Do(ctx). - Into(result) - return -} diff --git a/pkg/jetstream/generated/informers/externalversions/factory.go b/pkg/jetstream/generated/informers/externalversions/factory.go index 9bc8ae93..8e6d034f 100644 --- a/pkg/jetstream/generated/informers/externalversions/factory.go +++ b/pkg/jetstream/generated/informers/externalversions/factory.go @@ -1,4 +1,4 @@ -// Copyright 2020 The NATS Authors +// Copyright 2024 The NATS Authors // 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 @@ -225,6 +225,7 @@ type SharedInformerFactory interface { // Start initializes all requested informers. They are handled in goroutines // which run until the stop channel gets closed. + // Warning: Start does not block. When run in a go-routine, it will race with a later WaitForCacheSync. Start(stopCh <-chan struct{}) // Shutdown marks a factory as shutting down. At that point no new diff --git a/pkg/jetstream/generated/informers/externalversions/generic.go b/pkg/jetstream/generated/informers/externalversions/generic.go index 234209be..e5ba543b 100644 --- a/pkg/jetstream/generated/informers/externalversions/generic.go +++ b/pkg/jetstream/generated/informers/externalversions/generic.go @@ -1,4 +1,4 @@ -// Copyright 2020 The NATS Authors +// Copyright 2024 The NATS Authors // 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 diff --git a/pkg/jetstream/generated/informers/externalversions/internalinterfaces/factory_interfaces.go b/pkg/jetstream/generated/informers/externalversions/internalinterfaces/factory_interfaces.go index eef59f4c..5156b38f 100644 --- a/pkg/jetstream/generated/informers/externalversions/internalinterfaces/factory_interfaces.go +++ b/pkg/jetstream/generated/informers/externalversions/internalinterfaces/factory_interfaces.go @@ -1,4 +1,4 @@ -// Copyright 2020 The NATS Authors +// Copyright 2024 The NATS Authors // 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 diff --git a/pkg/jetstream/generated/informers/externalversions/jetstream/interface.go b/pkg/jetstream/generated/informers/externalversions/jetstream/interface.go index 580e74a4..ba66ab00 100644 --- a/pkg/jetstream/generated/informers/externalversions/jetstream/interface.go +++ b/pkg/jetstream/generated/informers/externalversions/jetstream/interface.go @@ -1,4 +1,4 @@ -// Copyright 2020 The NATS Authors +// Copyright 2024 The NATS Authors // 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 diff --git a/pkg/jetstream/generated/informers/externalversions/jetstream/v1beta2/account.go b/pkg/jetstream/generated/informers/externalversions/jetstream/v1beta2/account.go index 754f3394..da16b01b 100644 --- a/pkg/jetstream/generated/informers/externalversions/jetstream/v1beta2/account.go +++ b/pkg/jetstream/generated/informers/externalversions/jetstream/v1beta2/account.go @@ -1,4 +1,4 @@ -// Copyright 2020 The NATS Authors +// Copyright 2024 The NATS Authors // 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 diff --git a/pkg/jetstream/generated/informers/externalversions/jetstream/v1beta2/consumer.go b/pkg/jetstream/generated/informers/externalversions/jetstream/v1beta2/consumer.go index 3bd55a4d..ef400250 100644 --- a/pkg/jetstream/generated/informers/externalversions/jetstream/v1beta2/consumer.go +++ b/pkg/jetstream/generated/informers/externalversions/jetstream/v1beta2/consumer.go @@ -1,4 +1,4 @@ -// Copyright 2020 The NATS Authors +// Copyright 2024 The NATS Authors // 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 diff --git a/pkg/jetstream/generated/informers/externalversions/jetstream/v1beta2/interface.go b/pkg/jetstream/generated/informers/externalversions/jetstream/v1beta2/interface.go index f0416b62..05bf3fdb 100644 --- a/pkg/jetstream/generated/informers/externalversions/jetstream/v1beta2/interface.go +++ b/pkg/jetstream/generated/informers/externalversions/jetstream/v1beta2/interface.go @@ -1,4 +1,4 @@ -// Copyright 2020 The NATS Authors +// Copyright 2024 The NATS Authors // 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 diff --git a/pkg/jetstream/generated/informers/externalversions/jetstream/v1beta2/stream.go b/pkg/jetstream/generated/informers/externalversions/jetstream/v1beta2/stream.go index a8f7b1e3..d8d7dbd4 100644 --- a/pkg/jetstream/generated/informers/externalversions/jetstream/v1beta2/stream.go +++ b/pkg/jetstream/generated/informers/externalversions/jetstream/v1beta2/stream.go @@ -1,4 +1,4 @@ -// Copyright 2020 The NATS Authors +// Copyright 2024 The NATS Authors // 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 diff --git a/pkg/jetstream/generated/listers/jetstream/v1beta2/account.go b/pkg/jetstream/generated/listers/jetstream/v1beta2/account.go index 9f92d449..ef987a4e 100644 --- a/pkg/jetstream/generated/listers/jetstream/v1beta2/account.go +++ b/pkg/jetstream/generated/listers/jetstream/v1beta2/account.go @@ -1,4 +1,4 @@ -// Copyright 2020 The NATS Authors +// Copyright 2024 The NATS Authors // 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 @@ -17,8 +17,8 @@ package v1beta2 import ( v1beta2 "github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2" - "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/listers" "k8s.io/client-go/tools/cache" ) @@ -35,25 +35,17 @@ type AccountLister interface { // accountLister implements the AccountLister interface. type accountLister struct { - indexer cache.Indexer + listers.ResourceIndexer[*v1beta2.Account] } // NewAccountLister returns a new AccountLister. func NewAccountLister(indexer cache.Indexer) AccountLister { - return &accountLister{indexer: indexer} -} - -// List lists all Accounts in the indexer. -func (s *accountLister) List(selector labels.Selector) (ret []*v1beta2.Account, err error) { - err = cache.ListAll(s.indexer, selector, func(m interface{}) { - ret = append(ret, m.(*v1beta2.Account)) - }) - return ret, err + return &accountLister{listers.New[*v1beta2.Account](indexer, v1beta2.Resource("account"))} } // Accounts returns an object that can list and get Accounts. func (s *accountLister) Accounts(namespace string) AccountNamespaceLister { - return accountNamespaceLister{indexer: s.indexer, namespace: namespace} + return accountNamespaceLister{listers.NewNamespaced[*v1beta2.Account](s.ResourceIndexer, namespace)} } // AccountNamespaceLister helps list and get Accounts. @@ -71,26 +63,5 @@ type AccountNamespaceLister interface { // accountNamespaceLister implements the AccountNamespaceLister // interface. type accountNamespaceLister struct { - indexer cache.Indexer - namespace string -} - -// List lists all Accounts in the indexer for a given namespace. -func (s accountNamespaceLister) List(selector labels.Selector) (ret []*v1beta2.Account, err error) { - err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) { - ret = append(ret, m.(*v1beta2.Account)) - }) - return ret, err -} - -// Get retrieves the Account from the indexer for a given namespace and name. -func (s accountNamespaceLister) Get(name string) (*v1beta2.Account, error) { - obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name) - if err != nil { - return nil, err - } - if !exists { - return nil, errors.NewNotFound(v1beta2.Resource("account"), name) - } - return obj.(*v1beta2.Account), nil + listers.ResourceIndexer[*v1beta2.Account] } diff --git a/pkg/jetstream/generated/listers/jetstream/v1beta2/consumer.go b/pkg/jetstream/generated/listers/jetstream/v1beta2/consumer.go index 235323c2..1f46c5a0 100644 --- a/pkg/jetstream/generated/listers/jetstream/v1beta2/consumer.go +++ b/pkg/jetstream/generated/listers/jetstream/v1beta2/consumer.go @@ -1,4 +1,4 @@ -// Copyright 2020 The NATS Authors +// Copyright 2024 The NATS Authors // 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 @@ -17,8 +17,8 @@ package v1beta2 import ( v1beta2 "github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2" - "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/listers" "k8s.io/client-go/tools/cache" ) @@ -35,25 +35,17 @@ type ConsumerLister interface { // consumerLister implements the ConsumerLister interface. type consumerLister struct { - indexer cache.Indexer + listers.ResourceIndexer[*v1beta2.Consumer] } // NewConsumerLister returns a new ConsumerLister. func NewConsumerLister(indexer cache.Indexer) ConsumerLister { - return &consumerLister{indexer: indexer} -} - -// List lists all Consumers in the indexer. -func (s *consumerLister) List(selector labels.Selector) (ret []*v1beta2.Consumer, err error) { - err = cache.ListAll(s.indexer, selector, func(m interface{}) { - ret = append(ret, m.(*v1beta2.Consumer)) - }) - return ret, err + return &consumerLister{listers.New[*v1beta2.Consumer](indexer, v1beta2.Resource("consumer"))} } // Consumers returns an object that can list and get Consumers. func (s *consumerLister) Consumers(namespace string) ConsumerNamespaceLister { - return consumerNamespaceLister{indexer: s.indexer, namespace: namespace} + return consumerNamespaceLister{listers.NewNamespaced[*v1beta2.Consumer](s.ResourceIndexer, namespace)} } // ConsumerNamespaceLister helps list and get Consumers. @@ -71,26 +63,5 @@ type ConsumerNamespaceLister interface { // consumerNamespaceLister implements the ConsumerNamespaceLister // interface. type consumerNamespaceLister struct { - indexer cache.Indexer - namespace string -} - -// List lists all Consumers in the indexer for a given namespace. -func (s consumerNamespaceLister) List(selector labels.Selector) (ret []*v1beta2.Consumer, err error) { - err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) { - ret = append(ret, m.(*v1beta2.Consumer)) - }) - return ret, err -} - -// Get retrieves the Consumer from the indexer for a given namespace and name. -func (s consumerNamespaceLister) Get(name string) (*v1beta2.Consumer, error) { - obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name) - if err != nil { - return nil, err - } - if !exists { - return nil, errors.NewNotFound(v1beta2.Resource("consumer"), name) - } - return obj.(*v1beta2.Consumer), nil + listers.ResourceIndexer[*v1beta2.Consumer] } diff --git a/pkg/jetstream/generated/listers/jetstream/v1beta2/expansion_generated.go b/pkg/jetstream/generated/listers/jetstream/v1beta2/expansion_generated.go index 52cf4e10..5cacc517 100644 --- a/pkg/jetstream/generated/listers/jetstream/v1beta2/expansion_generated.go +++ b/pkg/jetstream/generated/listers/jetstream/v1beta2/expansion_generated.go @@ -1,4 +1,4 @@ -// Copyright 2020 The NATS Authors +// Copyright 2024 The NATS Authors // 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 diff --git a/pkg/jetstream/generated/listers/jetstream/v1beta2/stream.go b/pkg/jetstream/generated/listers/jetstream/v1beta2/stream.go index bd28aa24..7151a170 100644 --- a/pkg/jetstream/generated/listers/jetstream/v1beta2/stream.go +++ b/pkg/jetstream/generated/listers/jetstream/v1beta2/stream.go @@ -1,4 +1,4 @@ -// Copyright 2020 The NATS Authors +// Copyright 2024 The NATS Authors // 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 @@ -17,8 +17,8 @@ package v1beta2 import ( v1beta2 "github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2" - "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/listers" "k8s.io/client-go/tools/cache" ) @@ -35,25 +35,17 @@ type StreamLister interface { // streamLister implements the StreamLister interface. type streamLister struct { - indexer cache.Indexer + listers.ResourceIndexer[*v1beta2.Stream] } // NewStreamLister returns a new StreamLister. func NewStreamLister(indexer cache.Indexer) StreamLister { - return &streamLister{indexer: indexer} -} - -// List lists all Streams in the indexer. -func (s *streamLister) List(selector labels.Selector) (ret []*v1beta2.Stream, err error) { - err = cache.ListAll(s.indexer, selector, func(m interface{}) { - ret = append(ret, m.(*v1beta2.Stream)) - }) - return ret, err + return &streamLister{listers.New[*v1beta2.Stream](indexer, v1beta2.Resource("stream"))} } // Streams returns an object that can list and get Streams. func (s *streamLister) Streams(namespace string) StreamNamespaceLister { - return streamNamespaceLister{indexer: s.indexer, namespace: namespace} + return streamNamespaceLister{listers.NewNamespaced[*v1beta2.Stream](s.ResourceIndexer, namespace)} } // StreamNamespaceLister helps list and get Streams. @@ -71,26 +63,5 @@ type StreamNamespaceLister interface { // streamNamespaceLister implements the StreamNamespaceLister // interface. type streamNamespaceLister struct { - indexer cache.Indexer - namespace string -} - -// List lists all Streams in the indexer for a given namespace. -func (s streamNamespaceLister) List(selector labels.Selector) (ret []*v1beta2.Stream, err error) { - err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) { - ret = append(ret, m.(*v1beta2.Stream)) - }) - return ret, err -} - -// Get retrieves the Stream from the indexer for a given namespace and name. -func (s streamNamespaceLister) Get(name string) (*v1beta2.Stream, error) { - obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name) - if err != nil { - return nil, err - } - if !exists { - return nil, errors.NewNotFound(v1beta2.Resource("stream"), name) - } - return obj.(*v1beta2.Stream), nil + listers.ResourceIndexer[*v1beta2.Stream] } diff --git a/pkg/k8scodegen/file-header.txt b/pkg/k8scodegen/file-header.txt index b28b54c4..780fa5e9 100644 --- a/pkg/k8scodegen/file-header.txt +++ b/pkg/k8scodegen/file-header.txt @@ -1,4 +1,4 @@ -// Copyright 2020 The NATS Authors +// Copyright 2024 The NATS Authors // 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 From 4c499e0dbcec106203fb664d1e1681349ae4fd28 Mon Sep 17 00:00:00 2001 From: Adrian <59565247+adriandieter@users.noreply.github.com> Date: Mon, 9 Dec 2024 16:50:12 +0100 Subject: [PATCH 02/19] Migrate to reconciliation loop using controller-runtime (#208) * Setup optional controller-runtime manager in main Removes the kubeconfig flag and instead uses ctrl.RegisterFlags(fs) and ctrl.GetConfig(). The controller-runtime currently registers the kubeconfig flag, which lead to a redefined flag error when registering it again. * Add update permissions for resource finalizers * Add envtest to Makefile This is based on the Makefile of an operator-sdk based project. * Update test to include envtest and run the internal/controller test suite * Add account, consumer and stream controller stubs to be implemented Controllers and tests are based on files generated by operator-sdk. Adds a minimal test suite for the controllers with a etcd test env and a test nats jetStream server to test against. * Add logs to Reconcile functions * Add internal/controller to jetstreamSrc * Register account, consumer and stream reconcilers * Add jsClient to test suit variables * Remove format from log string --- Makefile | 38 +++- cmd/jetstream-controller/main.go | 92 ++++++++-- deploy/rbac.yml | 8 + go.mod | 20 +- go.sum | 172 ++++-------------- internal/controller/account_controller.go | 57 ++++++ .../controller/account_controller_test.go | 84 +++++++++ internal/controller/client.go | 85 +++++++++ internal/controller/consumer_controller.go | 55 ++++++ .../controller/consumer_controller_test.go | 90 +++++++++ internal/controller/register.go | 58 ++++++ internal/controller/stream_controller.go | 56 ++++++ internal/controller/stream_controller_test.go | 90 +++++++++ internal/controller/suite_test.go | 124 +++++++++++++ 14 files changed, 870 insertions(+), 159 deletions(-) create mode 100644 internal/controller/account_controller.go create mode 100644 internal/controller/account_controller_test.go create mode 100644 internal/controller/client.go create mode 100644 internal/controller/consumer_controller.go create mode 100644 internal/controller/consumer_controller_test.go create mode 100644 internal/controller/register.go create mode 100644 internal/controller/stream_controller.go create mode 100644 internal/controller/stream_controller_test.go create mode 100644 internal/controller/suite_test.go diff --git a/Makefile b/Makefile index 360ce845..3c24b0bc 100644 --- a/Makefile +++ b/Makefile @@ -2,6 +2,8 @@ export GO111MODULE := on SHELL=/usr/bin/env bash +ENVTEST_K8S_VERSION = 1.29.0 + now := $(shell date -u +%Y-%m-%dT%H:%M:%S%z) gitBranch := $(shell git rev-parse --abbrev-ref HEAD) gitCommit := $(shell git rev-parse --short HEAD) @@ -11,7 +13,7 @@ VERSION ?= version-not-set linkerVars := -X main.BuildTime=$(now) -X main.GitInfo=$(gitBranch)-$(gitCommit)$(repoDirty) -X main.Version=$(VERSION) drepo ?= natsio -jetstreamSrc := $(shell find cmd/jetstream-controller pkg/jetstream controllers/jetstream -name "*.go") pkg/jetstream/apis/jetstream/v1beta2/zz_generated.deepcopy.go +jetstreamSrc := $(shell find cmd/jetstream-controller pkg/jetstream internal/controller controllers/jetstream -name "*.go") pkg/jetstream/apis/jetstream/v1beta2/zz_generated.deepcopy.go configReloaderSrc := $(shell find cmd/nats-server-config-reloader/ pkg/natsreloader/ -name "*.go") @@ -175,10 +177,38 @@ fetch-modules: .PHONY: build build: jetstream-controller nats-server-config-reloader nats-boot-config +# Setup envtest tools based on a operator-sdk project makefile +LOCALBIN ?= $(shell pwd)/bin +$(LOCALBIN): + mkdir -p $(LOCALBIN) + +# 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 (ideally with version) +# $2 - package url which can be installed +# $3 - specific version of package +define go-install-tool +@[ -f $(1) ] || { \ +set -e; \ +package=$(2)@$(3) ;\ +echo "Downloading $${package}" ;\ +GOBIN=$(LOCALBIN) go install $${package} ;\ +mv "$$(echo "$(1)" | sed "s/-$(3)$$//")" $(1) ;\ +} +endef + +ENVTEST ?= $(LOCALBIN)/setup-envtest-$(ENVTEST_VERSION) +ENVTEST_VERSION ?= release-0.17 + +.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: test -test: - go vet ./controllers/... ./pkg/natsreloader/... - go test -race -cover -count=1 -timeout 10s ./controllers/... ./pkg/natsreloader/... +test: envtest + go vet ./controllers/... ./pkg/natsreloader/... ./internal/controller/... + go test -race -cover -count=1 -timeout 10s ./controllers/... ./pkg/natsreloader/... ./internal/controller/... .PHONY: clean clean: diff --git a/cmd/jetstream-controller/main.go b/cmd/jetstream-controller/main.go index e5635446..1ec60c3d 100644 --- a/cmd/jetstream-controller/main.go +++ b/cmd/jetstream-controller/main.go @@ -18,20 +18,23 @@ import ( "errors" "flag" "fmt" - "os" - "os/signal" - "syscall" - "time" - - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "github.com/nats-io/nack/controllers/jetstream" + "github.com/nats-io/nack/internal/controller" + jetstreamnatsiov1beta2 "github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2" clientset "github.com/nats-io/nack/pkg/jetstream/generated/clientset/versioned" - + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/client-go/kubernetes" + clientgoscheme "k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/rest" - "k8s.io/client-go/tools/clientcmd" klog "k8s.io/klog/v2" + "os" + "os/signal" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/healthz" + "syscall" + "time" ) var ( @@ -49,7 +52,10 @@ func main() { func run() error { klog.InitFlags(nil) - kubeConfig := flag.String("kubeconfig", "", "Path to kubeconfig") + + // Explicitly register controller-runtime flags + ctrl.RegisterFlags(nil) + namespace := flag.String("namespace", v1.NamespaceAll, "Restrict to a namespace") version := flag.Bool("version", false, "Print the version and exit") creds := flag.String("creds", "", "NATS Credentials") @@ -62,6 +68,8 @@ func run() error { crdConnect := flag.Bool("crd-connect", false, "If true, then NATS connections will be made from CRD config, not global config") cleanupPeriod := flag.Duration("cleanup-period", 30*time.Second, "Period to run object cleanup") readOnly := flag.Bool("read-only", false, "Starts the controller without causing changes to the NATS resources") + controlLoop := flag.Bool("control-loop", false, "Experimental: Run controller with a full reconciliation control loop.") + flag.Parse() if *version { @@ -73,18 +81,30 @@ func run() error { return errors.New("NATS Server URL is required") } - var config *rest.Config - var err error - if *kubeConfig == "" { - config, err = rest.InClusterConfig() - if err != nil { - return err + config, err := ctrl.GetConfig() + if err != nil { + return fmt.Errorf("get kubernetes rest config: %w", err) + } + + if *controlLoop { + klog.Warning("Starting jetStream controller in experimental control loop mode") + natsCfg := &controller.NatsConfig{ + CRDConnect: *crdConnect, + ClientName: "jetstream-controller", + Credentials: *creds, + NKey: *nkey, + ServerURL: *server, + CA: *ca, + Certificate: *cert, + Key: *key, + TLSFirst: *tlsfirst, } - } else { - config, err = clientcmd.BuildConfigFromFlags("", *kubeConfig) - if err != nil { - return err + + controllerCfg := &controller.Config{ + ReadOnly: *readOnly, + Namespace: *namespace, } + return runControlLoop(config, natsCfg, controllerCfg) } // K8S API Client. @@ -129,6 +149,38 @@ func run() error { return ctrl.Run() } +func runControlLoop(config *rest.Config, natsCfg *controller.NatsConfig, controllerCfg *controller.Config) error { + + // Setup scheme + scheme := runtime.NewScheme() + utilruntime.Must(clientgoscheme.AddToScheme(scheme)) + utilruntime.Must(jetstreamnatsiov1beta2.AddToScheme(scheme)) + + mgr, err := ctrl.NewManager(config, ctrl.Options{ + Scheme: scheme, + Logger: klog.NewKlogr().WithName("controller-runtime"), + // TODO Add full configuration + }) + if err != nil { + return fmt.Errorf("unable to start manager: %w", err) + } + + err = controller.RegisterAll(mgr, natsCfg, controllerCfg) + if err != nil { + return fmt.Errorf("register jetstream controllers: %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.AddReadyzCheck("readyz", healthz.Ping); err != nil { + return fmt.Errorf("unable to set up ready check: %w", err) + } + + klog.Info("starting manager") + return mgr.Start(ctrl.SetupSignalHandler()) +} + func handleSignals(cancel context.CancelFunc) { sigc := make(chan os.Signal, 2) signal.Notify(sigc, syscall.SIGINT, syscall.SIGTERM) diff --git a/deploy/rbac.yml b/deploy/rbac.yml index 55b19771..388afb73 100644 --- a/deploy/rbac.yml +++ b/deploy/rbac.yml @@ -44,6 +44,14 @@ rules: - patch - update - delete +- apiGroups: + - jetstream.nats.io + resources: + - streams/finalizers + - consumers/finalizers + - accounts/finalizers + verbs: + - update --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding diff --git a/go.mod b/go.mod index 821f97dc..6d8231ed 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,8 @@ require ( github.com/nats-io/jsm.go v0.1.2 github.com/nats-io/nats-server/v2 v2.10.22 github.com/nats-io/nats.go v1.37.0 + github.com/onsi/ginkgo/v2 v2.19.0 + github.com/onsi/gomega v1.33.1 github.com/sirupsen/logrus v1.9.3 github.com/stretchr/testify v1.9.0 golang.org/x/sync v0.9.0 @@ -17,26 +19,33 @@ require ( k8s.io/client-go v0.31.2 k8s.io/code-generator v0.31.2 k8s.io/klog/v2 v2.130.1 + sigs.k8s.io/controller-runtime v0.19.2 sigs.k8s.io/structured-merge-diff/v4 v4.4.3 ) require ( + github.com/beorn7/perks v1.0.1 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/emicklei/go-restful/v3 v3.12.1 // indirect github.com/evanphx/json-patch v5.9.0+incompatible // indirect + github.com/evanphx/json-patch/v5 v5.9.0 // indirect github.com/expr-lang/expr v1.16.9 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect github.com/go-logr/logr v1.4.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/gogo/protobuf v1.3.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/google/gnostic-models v0.6.8 // indirect github.com/google/go-cmp v0.6.0 // indirect github.com/google/gofuzz v1.2.0 // indirect + github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 // indirect github.com/google/uuid v1.6.0 // indirect github.com/imdario/mergo v0.3.16 // indirect github.com/josharian/intern v1.0.0 // indirect @@ -52,9 +61,16 @@ require ( github.com/nats-io/nuid v1.0.1 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/prometheus/client_golang v1.19.1 // indirect + github.com/prometheus/client_model v0.6.1 // indirect + github.com/prometheus/common v0.55.0 // indirect + github.com/prometheus/procfs v0.15.1 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/x448/float16 v0.8.4 // indirect + go.uber.org/multierr v1.11.0 // indirect + go.uber.org/zap v1.26.0 // indirect golang.org/x/crypto v0.29.0 // indirect + golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc // indirect golang.org/x/mod v0.22.0 // indirect golang.org/x/net v0.31.0 // indirect golang.org/x/oauth2 v0.24.0 // indirect @@ -63,13 +79,13 @@ require ( golang.org/x/text v0.20.0 // indirect golang.org/x/time v0.8.0 // indirect golang.org/x/tools v0.27.0 // indirect - google.golang.org/appengine v1.6.8 // indirect + gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect google.golang.org/protobuf v1.35.2 // 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 gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/gengo v0.0.0-20240911193312-2b36238f13e9 // indirect + k8s.io/apiextensions-apiserver v0.31.0 // indirect k8s.io/gengo/v2 v2.0.0-20240911193312-2b36238f13e9 // indirect k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f // indirect k8s.io/utils v0.0.0-20241104163129-6fe5fd82f078 // indirect diff --git a/go.sum b/go.sum index eb7f909b..27b2f751 100644 --- a/go.sum +++ b/go.sum @@ -1,77 +1,55 @@ -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +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/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 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/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= -github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= -github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= 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/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84= -github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v5.9.0+incompatible h1:fBXyNpNMuTTDdquAq/uisOr2lShz4oaXpDTX2bLe7ls= github.com/evanphx/json-patch v5.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +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/expr-lang/expr v1.16.9 h1:WUAzmR0JNI9JCiF0/ewwHB1gmcGw5wW7nWt8gc6PpCI= github.com/expr-lang/expr v1.16.9/go.mod h1:8/vRC7+7HBzESEqt5kKpYXxrxkr31SaO8r40VO/1IT4= -github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= -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/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= -github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= -github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= -github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= +github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= +github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= -github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= -github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ= github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4= -github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g= -github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= 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-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= -github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= +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/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= -github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 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/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/gofuzz v1.1.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-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec= -github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 h1:FKHo8hFI3A+7w0aUQuYXQ+6EN5stWmeY/AZqtM8xk9k= -github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= -github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo= 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/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28= -github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= @@ -80,16 +58,10 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= -github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= 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.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 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/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= @@ -105,208 +77,142 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/nats-io/jsm.go v0.1.2 h1:T4Fq88a03sPAPWYwrOLQ85oanYsC2Bs6517rUiWBMpQ= github.com/nats-io/jsm.go v0.1.2/go.mod h1:tnubE70CAKi5TNfQiq6XHFqWTuSIe1H7X4sDwfq6ZK8= -github.com/nats-io/jwt/v2 v2.5.8 h1:uvdSzwWiEGWGXf+0Q+70qv6AQdvcvxrv9hPM0RiPamE= -github.com/nats-io/jwt/v2 v2.5.8/go.mod h1:ZdWS1nZa6WMZfFwwgpEaqBV8EPGVgOTDHN/wTbz0Y5A= github.com/nats-io/jwt/v2 v2.7.2 h1:SCRjfDLJ2q8naXp8YlGJJS5/yj3wGSODFYVi4nnwVMw= github.com/nats-io/jwt/v2 v2.7.2/go.mod h1:kB6QUmqHG6Wdrzj0KP2L+OX4xiTPBeV+NHVstFaATXU= -github.com/nats-io/nats-server/v2 v2.10.18 h1:tRdZmBuWKVAFYtayqlBB2BuCHNGAQPvoQIXOKwU3WSM= -github.com/nats-io/nats-server/v2 v2.10.18/go.mod h1:97Qyg7YydD8blKlR8yBsUlPlWyZKjA7Bp5cl3MUE9K8= github.com/nats-io/nats-server/v2 v2.10.22 h1:Yt63BGu2c3DdMoBZNcR6pjGQwk/asrKU7VX846ibxDA= github.com/nats-io/nats-server/v2 v2.10.22/go.mod h1:X/m1ye9NYansUXYFrbcDwUi/blHkrgHh2rgCJaakonk= -github.com/nats-io/nats.go v1.36.0 h1:suEUPuWzTSse/XhESwqLxXGuj8vGRuPRoG7MoRN/qyU= -github.com/nats-io/nats.go v1.36.0/go.mod h1:Ubdu4Nh9exXdSz0RVWRFBbRfrbSxOYd26oF0wkWclB8= github.com/nats-io/nats.go v1.37.0 h1:07rauXbVnnJvv1gfIyghFEo6lUcYRY0WXc3x7x0vUxE= github.com/nats-io/nats.go v1.37.0/go.mod h1:Ubdu4Nh9exXdSz0RVWRFBbRfrbSxOYd26oF0wkWclB8= github.com/nats-io/nkeys v0.4.7 h1:RwNJbbIdYCoClSDNY7QVKZlyb/wfT6ugvFCiKy6vDvI= github.com/nats-io/nkeys v0.4.7/go.mod h1:kqXRgRDPlGy7nGaEDMuYzmiJCIAAWDK0IMBtDmGD0nc= github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= -github.com/onsi/ginkgo/v2 v2.13.0 h1:0jY9lJquiL8fcf3M4LAXN5aMlS/b2BV86HFFPCPMgE4= -github.com/onsi/ginkgo/v2 v2.13.0/go.mod h1:TE309ZR8s5FsKKpuB1YAQYBzCaAfUgatB/xlT/ETL/o= -github.com/onsi/gomega v1.29.0 h1:KIA/t2t5UBzoirT4H9tsML45GEbo3ouUnBHsCfD2tVg= -github.com/onsi/gomega v1.29.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ= +github.com/onsi/ginkgo/v2 v2.19.0 h1:9Cnnf7UHo57Hy3k6/m5k3dRfGTMXGvxhHFvkDTCTpvA= +github.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To= +github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk= +github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 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/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= -github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= +github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE= +github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho= +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.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc= +github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8= +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/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 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/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 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 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 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/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +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= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= +go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= 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.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= -golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ= golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg= +golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc h1:mCRnTeVUjcrhlRmO0VK8a6k6Rrf6TF9htwo2pJVSjIU= +golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w= 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.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= -golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 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-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= -golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo= golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM= -golang.org/x/oauth2 v0.10.0 h1:zHCpF2Khkwy4mMB4bv0U37YtJdTGW8jI0glAApi0Kh8= -golang.org/x/oauth2 v0.10.0/go.mod h1:kTpgurOux7LqtuxjuyZa4Gj2gdezIt/jQtGnNFfypQI= golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE= golang.org/x/oauth2 v0.24.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-20201020160332-67f06af15bc9/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.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ= golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 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-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/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.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= -golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -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.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk= -golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4= golang.org/x/term v0.26.0 h1:WEQa6V3Gja/BhNxg540hBip/kkaYtRg3cxg4oXSw4AU= golang.org/x/term v0.26.0/go.mod h1:Si5m1o57C5nBNQo5z1iq+XDijt21BDBDp2bK0QI8e3E= 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.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug= golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= -golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= -golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg= golang.org/x/time v0.8.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-20200505023115-26f46d2f7ef8/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= -golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/tools v0.27.0 h1:qEKojBykQkQ4EynWy4S8Weg69NumxKdn40Fce3uc/8o= golang.org/x/tools v0.27.0/go.mod h1:sUi0ZgbwW9ZPAq26Ekut+weQPR5eIM6GQLQ1Yjm1H0Q= 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= -google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= -google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= -google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= -google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +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/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io= google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= 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/yaml.v2 v2.2.8/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= -k8s.io/api v0.29.1 h1:DAjwWX/9YT7NQD4INu49ROJuZAAAP/Ijki48GUPzxqw= -k8s.io/api v0.29.1/go.mod h1:7Kl10vBRUXhnQQI8YR/R327zXC8eJ7887/+Ybta+RoQ= k8s.io/api v0.31.2 h1:3wLBbL5Uom/8Zy98GRPXpJ254nEFpl+hwndmk9RwmL0= k8s.io/api v0.31.2/go.mod h1:bWmGvrGPssSK1ljmLzd3pwCQ9MgoTsRCuK35u6SygUk= -k8s.io/apimachinery v0.29.1 h1:KY4/E6km/wLBguvCZv8cKTeOwwOBqFNjwJIdMkMbbRc= -k8s.io/apimachinery v0.29.1/go.mod h1:6HVkd1FwxIagpYrHSwJlQqZI3G9LfYWRPAkUvLnXTKU= +k8s.io/apiextensions-apiserver v0.31.0 h1:fZgCVhGwsclj3qCw1buVXCV6khjRzKC5eCFt24kyLSk= +k8s.io/apiextensions-apiserver v0.31.0/go.mod h1:b9aMDEYaEe5sdK+1T0KU78ApR/5ZVp4i56VacZYEHxk= k8s.io/apimachinery v0.31.2 h1:i4vUt2hPK56W6mlT7Ry+AO8eEsyxMD1U44NR22CLTYw= k8s.io/apimachinery v0.31.2/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo= -k8s.io/client-go v0.29.1 h1:19B/+2NGEwnFLzt0uB5kNJnfTsbV8w6TgQRz9l7ti7A= -k8s.io/client-go v0.29.1/go.mod h1:TDG/psL9hdet0TI9mGyHJSgRkW3H9JZk2dNEUS7bRks= k8s.io/client-go v0.31.2 h1:Y2F4dxU5d3AQj+ybwSMqQnpZH9F30//1ObxOKlTI9yc= k8s.io/client-go v0.31.2/go.mod h1:NPa74jSVR/+eez2dFsEIHNa+3o09vtNaWwWwb1qSxSs= -k8s.io/code-generator v0.29.1 h1:8ba8BdtSmAVHgAMpzThb/fuyQeTRtN7NtN7VjMcDLew= -k8s.io/code-generator v0.29.1/go.mod h1:FwFi3C9jCrmbPjekhaCYcYG1n07CYiW1+PAPCockaos= k8s.io/code-generator v0.31.2 h1:xLWxG0HEpMSHfcM//3u3Ro2Hmc6AyyLINQS//Z2GEOI= k8s.io/code-generator v0.31.2/go.mod h1:eEQHXgBU/m7LDaToDoiz3t97dUUVyOblQdwOr8rivqc= -k8s.io/gengo v0.0.0-20230829151522-9cce18d56c01 h1:pWEwq4Asjm4vjW7vcsmijwBhOr1/shsbSYiWXmNGlks= -k8s.io/gengo v0.0.0-20230829151522-9cce18d56c01/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= -k8s.io/gengo v0.0.0-20240911193312-2b36238f13e9 h1:B0l8GxRsVc/tP/uCLBQdAjf2nBARx6u/r2OGuL/CyXQ= -k8s.io/gengo v0.0.0-20240911193312-2b36238f13e9/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= k8s.io/gengo/v2 v2.0.0-20240911193312-2b36238f13e9 h1:si3PfKm8dDYxgfbeA6orqrtLkvvIeH8UqffFJDl0bz4= k8s.io/gengo/v2 v2.0.0-20240911193312-2b36238f13e9/go.mod h1:EJykeLsmFC60UQbYJezXkEsG2FLrt0GPNkU5iK5GWxU= -k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= -k8s.io/klog/v2 v2.120.1 h1:QXU6cPEOIslTGvZaXvFWiP9VKyeet3sawzTOvdXb4Vw= -k8s.io/klog/v2 v2.120.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 h1:aVUu9fTY98ivBPKR9Y5w/AuzbMm96cd3YHRTU83I780= -k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00/go.mod h1:AsvuZPBlUDVuCdzJ87iajxtXuR9oktsTctW/R9wwouA= k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f h1:GA7//TjRY9yWGy1poLzYYJJ4JRdzg3+O6e8I+e+8T5Y= k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f/go.mod h1:R/HEjbvWI0qdfb8viZUeVZm0X6IZnxAydC7YU42CMw4= -k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI= -k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= k8s.io/utils v0.0.0-20241104163129-6fe5fd82f078 h1:jGnCPejIetjiy2gqaJ5V0NLwTpF4wbQ6cZIItJCSHno= k8s.io/utils v0.0.0-20241104163129-6fe5fd82f078/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= -sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= +sigs.k8s.io/controller-runtime v0.19.2 h1:3sPrF58XQEPzbE8T81TN6selQIMGbtYwuaJ6eDssDF8= +sigs.k8s.io/controller-runtime v0.19.2/go.mod h1:iRmWllt8IlaLjvTTDLhRBXIEtkCK6hwVBJJsYS9Ajf4= 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/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= -sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= sigs.k8s.io/structured-merge-diff/v4 v4.4.3 h1:sCP7Vv3xx/CWIuTPVN38lUPx0uw0lcLfzaiDa8Ja01A= sigs.k8s.io/structured-merge-diff/v4 v4.4.3/go.mod h1:N8f93tFZh9U6vpxwRArLiikrE5/2tiu1w1AGfACIGE4= -sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= -sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= -sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= diff --git a/internal/controller/account_controller.go b/internal/controller/account_controller.go new file mode 100644 index 00000000..a5686437 --- /dev/null +++ b/internal/controller/account_controller.go @@ -0,0 +1,57 @@ +/* +Copyright 2024. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package controller + +import ( + "context" + "github.com/nats-io/nats.go/jetstream" + "k8s.io/klog/v2" + + jetstreamnatsiov1beta2 "github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2" + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +// AccountReconciler reconciles a Account object +type AccountReconciler struct { + client.Client + Scheme *runtime.Scheme + Config *Config + JetStream jetstream.JetStream +} + +// Reconcile is part of the main kubernetes reconciliation loop which aims to +// move the current state of the cluster closer to the desired state. +// +// For more details, check Reconcile and its Result here: +// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.17.3/pkg/reconcile +func (r *AccountReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + log := klog.FromContext(ctx) + log.Info("reconcile", "namespace", req.Namespace, "name", req.Name) + + // TODO(user): your logic here + + return ctrl.Result{}, nil +} + +// SetupWithManager sets up the controller with the Manager. +func (r *AccountReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&jetstreamnatsiov1beta2.Account{}). + Complete(r) +} diff --git a/internal/controller/account_controller_test.go b/internal/controller/account_controller_test.go new file mode 100644 index 00000000..aa897363 --- /dev/null +++ b/internal/controller/account_controller_test.go @@ -0,0 +1,84 @@ +/* +Copyright 2024. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package controller + +import ( + "context" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + jetstreamnatsiov1beta2 "github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2" +) + +var _ = Describe("Account Controller", func() { + Context("When reconciling a resource", func() { + const resourceName = "test-resource" + + ctx := context.Background() + + typeNamespacedName := types.NamespacedName{ + Name: resourceName, + Namespace: "default", // TODO(user):Modify as needed + } + account := &jetstreamnatsiov1beta2.Account{} + + BeforeEach(func() { + By("creating the custom resource for the Kind Account") + err := k8sClient.Get(ctx, typeNamespacedName, account) + if err != nil && errors.IsNotFound(err) { + resource := &jetstreamnatsiov1beta2.Account{ + ObjectMeta: metav1.ObjectMeta{ + Name: resourceName, + Namespace: "default", + }, + // TODO(user): Specify other spec details if needed. + } + Expect(k8sClient.Create(ctx, resource)).To(Succeed()) + } + }) + + AfterEach(func() { + // TODO(user): Cleanup logic after each test, like removing the resource instance. + resource := &jetstreamnatsiov1beta2.Account{} + err := k8sClient.Get(ctx, typeNamespacedName, resource) + Expect(err).NotTo(HaveOccurred()) + + By("Cleanup the specific resource instance Account") + Expect(k8sClient.Delete(ctx, resource)).To(Succeed()) + }) + It("should successfully reconcile the resource", func() { + By("Reconciling the created resource") + controllerReconciler := &AccountReconciler{ + Client: k8sClient, + Scheme: k8sClient.Scheme(), + } + + _, err := controllerReconciler.Reconcile(ctx, reconcile.Request{ + NamespacedName: typeNamespacedName, + }) + Expect(err).NotTo(HaveOccurred()) + // TODO(user): Add more specific assertions depending on your controller's reconciliation logic. + // Example: If you expect a certain status condition after reconciliation, verify it here. + }) + }) +}) diff --git a/internal/controller/client.go b/internal/controller/client.go new file mode 100644 index 00000000..8c81994e --- /dev/null +++ b/internal/controller/client.go @@ -0,0 +1,85 @@ +package controller + +import ( + "fmt" + "github.com/nats-io/nats.go" + "github.com/nats-io/nats.go/jetstream" +) + +type NatsConfig struct { + CRDConnect bool + ClientName string + Credentials string + NKey string + ServerURL string + CA string + Certificate string + Key string + TLSFirst bool +} + +// buildOptions creates options from the config to be used in nats.Connect. +func (o *NatsConfig) buildOptions() ([]nats.Option, error) { + opts := make([]nats.Option, 0) + + if o.TLSFirst { + opts = append(opts, nats.TLSHandshakeFirst()) + } + + if o.ClientName != "" { + opts = append(opts, nats.Name(o.ClientName)) + } + + if !o.CRDConnect { + // Use JWT/NKEYS based credentials if present. + if o.Credentials != "" { + opts = append(opts, nats.UserCredentials(o.Credentials)) + } else if o.NKey != "" { + opt, err := nats.NkeyOptionFromSeed(o.NKey) + if err != nil { + return nil, fmt.Errorf("nkey option from seed: %w", err) + } + opts = append(opts, opt) + } + + if o.Certificate != "" && o.Key != "" { + opts = append(opts, nats.ClientCert(o.Certificate, o.Key)) + } + + if o.CA != "" { + opts = append(opts, nats.RootCAs(o.CA)) + } + } + + return opts, nil +} + +func CreateJetStreamClient(cfg *NatsConfig, pedantic bool) (jetstream.JetStream, error) { + + opts, err := cfg.buildOptions() + if err != nil { + return nil, fmt.Errorf("nats options: %w", err) + } + + // Set pedantic option + if pedantic { + opts = append(opts, func(options *nats.Options) error { + options.Pedantic = true + return nil + }) + } + + // client should always attempt to reconnect + opts = append(opts, nats.MaxReconnects(-1)) + + nc, err := nats.Connect(cfg.ServerURL, opts...) + if err != nil { + return nil, fmt.Errorf("nats connect: %w", err) + } + + js, err := jetstream.New(nc) + if err != nil { + return nil, fmt.Errorf("new jetstream: %w", err) + } + return js, nil +} diff --git a/internal/controller/consumer_controller.go b/internal/controller/consumer_controller.go new file mode 100644 index 00000000..6dbe7ba8 --- /dev/null +++ b/internal/controller/consumer_controller.go @@ -0,0 +1,55 @@ +/* +Copyright 2024. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package controller + +import ( + "context" + "github.com/nats-io/nats.go/jetstream" + "k8s.io/klog/v2" + + jetstreamnatsiov1beta2 "github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2" + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +// ConsumerReconciler reconciles a Consumer object +type ConsumerReconciler struct { + client.Client + Scheme *runtime.Scheme + Config *Config + JetStream jetstream.JetStream +} + +// Reconcile is part of the main kubernetes reconciliation loop which aims to +// move the current state of the cluster closer to the desired state. +// +// For more details, check Reconcile and its Result here: +// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.17.3/pkg/reconcile +func (r *ConsumerReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + log := klog.FromContext(ctx) + log.Info("reconcile", "namespace", req.Namespace, "name", req.Name) + + return ctrl.Result{}, nil +} + +// SetupWithManager sets up the controller with the Manager. +func (r *ConsumerReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&jetstreamnatsiov1beta2.Consumer{}). + Complete(r) +} diff --git a/internal/controller/consumer_controller_test.go b/internal/controller/consumer_controller_test.go new file mode 100644 index 00000000..1ffce13e --- /dev/null +++ b/internal/controller/consumer_controller_test.go @@ -0,0 +1,90 @@ +/* +Copyright 2024. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package controller + +import ( + "context" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + jetstreamnatsiov1beta2 "github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2" +) + +var _ = Describe("Consumer Controller", func() { + Context("When reconciling a resource", func() { + const resourceName = "test-resource" + + ctx := context.Background() + + typeNamespacedName := types.NamespacedName{ + Name: resourceName, + Namespace: "default", // TODO(user):Modify as needed + } + consumer := &jetstreamnatsiov1beta2.Consumer{} + + BeforeEach(func() { + By("creating the custom resource for the Kind Consumer") + err := k8sClient.Get(ctx, typeNamespacedName, consumer) + if err != nil && errors.IsNotFound(err) { + resource := &jetstreamnatsiov1beta2.Consumer{ + ObjectMeta: metav1.ObjectMeta{ + Name: resourceName, + Namespace: "default", + }, + Spec: jetstreamnatsiov1beta2.ConsumerSpec{ + AckPolicy: "none", + DeliverPolicy: "new", + DurableName: "test-consumer", + ReplayPolicy: "instant", + }, + // TODO(user): Specify other spec details if needed. + } + Expect(k8sClient.Create(ctx, resource)).To(Succeed()) + } + }) + + AfterEach(func() { + // TODO(user): Cleanup logic after each test, like removing the resource instance. + resource := &jetstreamnatsiov1beta2.Consumer{} + err := k8sClient.Get(ctx, typeNamespacedName, resource) + Expect(err).NotTo(HaveOccurred()) + + By("Cleanup the specific resource instance Consumer") + Expect(k8sClient.Delete(ctx, resource)).To(Succeed()) + }) + It("should successfully reconcile the resource", func() { + By("Reconciling the created resource") + controllerReconciler := &ConsumerReconciler{ + Client: k8sClient, + Scheme: k8sClient.Scheme(), + } + + _, err := controllerReconciler.Reconcile(ctx, reconcile.Request{ + NamespacedName: typeNamespacedName, + }) + Expect(err).NotTo(HaveOccurred()) + // TODO(user): Add more specific assertions depending on your controller's reconciliation logic. + // Example: If you expect a certain status condition after reconciliation, verify it here. + }) + }) +}) diff --git a/internal/controller/register.go b/internal/controller/register.go new file mode 100644 index 00000000..611ff157 --- /dev/null +++ b/internal/controller/register.go @@ -0,0 +1,58 @@ +package controller + +import ( + "fmt" + ctrl "sigs.k8s.io/controller-runtime" +) + +// The Config contains parameters to be considered by the registered controllers. +// +// ReadOnly prevents controllers from actually applying changes NATS resources. +// +// Namespace restricts the controller to resources of the given namespace. +type Config struct { + ReadOnly bool + Namespace string +} + +// RegisterAll registers all available jetStream controllers to the manager. +// natsCfg is specific to the nats server connection. +// controllerCfg defines behaviour of the registered controllers. +func RegisterAll(mgr ctrl.Manager, clientConfig *NatsConfig, config *Config) error { + + // Controllers need access to a nats client in pedantic mode + js, err := CreateJetStreamClient(clientConfig, true) + if err != nil { + return fmt.Errorf("create jetstream client: %w", err) + } + + // Register controllers + if err := (&AccountReconciler{ + Client: mgr.GetClient(), + Config: config, + Scheme: mgr.GetScheme(), + JetStream: js, + }).SetupWithManager(mgr); err != nil { + return fmt.Errorf("unable to create account controller: %w", err) + } + + if err := (&ConsumerReconciler{ + Client: mgr.GetClient(), + Config: config, + Scheme: mgr.GetScheme(), + JetStream: js, + }).SetupWithManager(mgr); err != nil { + return fmt.Errorf("unable to create consumer controller: %w", err) + } + + if err := (&StreamReconciler{ + Client: mgr.GetClient(), + Config: config, + Scheme: mgr.GetScheme(), + JetStream: js, + }).SetupWithManager(mgr); err != nil { + return fmt.Errorf("unable to create stream controller: %w", err) + } + + return nil +} diff --git a/internal/controller/stream_controller.go b/internal/controller/stream_controller.go new file mode 100644 index 00000000..4469b7f0 --- /dev/null +++ b/internal/controller/stream_controller.go @@ -0,0 +1,56 @@ +/* +Copyright 2024. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package controller + +import ( + "context" + "github.com/nats-io/nats.go/jetstream" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/klog/v2" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + + jetstreamnatsiov1beta2 "github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2" +) + +// StreamReconciler reconciles a Stream object +type StreamReconciler struct { + client.Client + Scheme *runtime.Scheme + Config *Config + JetStream jetstream.JetStream +} + +// Reconcile is part of the main kubernetes reconciliation loop which aims to +// move the current state of the cluster closer to the desired state. +// +// For more details, check Reconcile and its Result here: +// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.17.3/pkg/reconcile +func (r *StreamReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + log := klog.FromContext(ctx) + log.Info("reconcile", "namespace", req.Namespace, "name", req.Name) + // TODO(user): your logic here + + return ctrl.Result{}, nil +} + +// SetupWithManager sets up the controller with the Manager. +func (r *StreamReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&jetstreamnatsiov1beta2.Stream{}). + Complete(r) +} diff --git a/internal/controller/stream_controller_test.go b/internal/controller/stream_controller_test.go new file mode 100644 index 00000000..ddd0e1c8 --- /dev/null +++ b/internal/controller/stream_controller_test.go @@ -0,0 +1,90 @@ +/* +Copyright 2024. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package controller + +import ( + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + jetstreamnatsiov1beta2 "github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2" +) + +var _ = Describe("Stream Controller", func() { + Context("When reconciling a resource", func() { + const resourceName = "test-resource" + + typeNamespacedName := types.NamespacedName{ + Name: resourceName, + Namespace: "default", // TODO(user):Modify as needed + } + + stream := &jetstreamnatsiov1beta2.Stream{} + + BeforeEach(func(ctx SpecContext) { + By("creating the custom resource for the Kind Stream") + err := k8sClient.Get(ctx, typeNamespacedName, stream) + if err != nil && errors.IsNotFound(err) { + resource := &jetstreamnatsiov1beta2.Stream{ + ObjectMeta: metav1.ObjectMeta{ + Name: resourceName, + Namespace: "default", + }, + Spec: jetstreamnatsiov1beta2.StreamSpec{ + Name: "test-stream", + Replicas: 1, + Discard: "old", + Storage: "file", + Retention: "workqueue", + }, + + // TODO(user): Specify other spec details if needed. + } + Expect(k8sClient.Create(ctx, resource)).To(Succeed()) + } + }) + + AfterEach(func(ctx SpecContext) { + // TODO(user): Cleanup logic after each test, like removing the resource instance. + resource := &jetstreamnatsiov1beta2.Stream{} + err := k8sClient.Get(ctx, typeNamespacedName, resource) + Expect(err).NotTo(HaveOccurred()) + + By("Cleanup the specific resource instance Stream") + Expect(k8sClient.Delete(ctx, resource)).To(Succeed()) + }) + + It("should successfully reconcile the resource", func(ctx SpecContext) { + By("Reconciling the created resource") + controllerReconciler := &StreamReconciler{ + Client: k8sClient, + Scheme: k8sClient.Scheme(), + } + + _, err := controllerReconciler.Reconcile(ctx, reconcile.Request{ + NamespacedName: typeNamespacedName, + }) + Expect(err).NotTo(HaveOccurred()) + // TODO(user): Add more specific assertions depending on your controller's reconciliation logic. + // Example: If you expect a certain status condition after reconciliation, verify it here. + }) + }) +}) diff --git a/internal/controller/suite_test.go b/internal/controller/suite_test.go new file mode 100644 index 00000000..2dac16e2 --- /dev/null +++ b/internal/controller/suite_test.go @@ -0,0 +1,124 @@ +/* +Copyright 2024. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package controller + +import ( + "fmt" + "github.com/nats-io/nats-server/v2/server" + natsserver "github.com/nats-io/nats-server/v2/test" + "github.com/nats-io/nats.go/jetstream" + "os" + "path/filepath" + "runtime" + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/rest" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/envtest" + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/log/zap" + + jetstreamnatsiov1beta2 "github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2" +) + +// These tests use Ginkgo (BDD-style Go testing framework). Refer to +// http://onsi.github.io/ginkgo/ to learn more about Ginkgo. + +var cfg *rest.Config +var k8sClient client.Client +var testEnv *envtest.Environment +var testServer *server.Server +var jsClient jetstream.JetStream + +func TestControllers(t *testing.T) { + RegisterFailHandler(Fail) + + RunSpecs(t, "Controller Suite") +} + +var _ = BeforeSuite(func() { + logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) + + By("bootstrapping test environment") + testEnv = &envtest.Environment{ + CRDDirectoryPaths: []string{filepath.Join("..", "..", "deploy")}, + ErrorIfCRDPathMissing: true, + + // The BinaryAssetsDirectory is only required if you want to run the tests directly + // without call the makefile target test. If not informed it will look for the + // default path defined in controller-runtime which is /usr/local/kubebuilder/. + // Note that you must have the required binaries setup under the bin directory to perform + // the tests directly. When we run make test it will be setup and used automatically. + BinaryAssetsDirectory: filepath.Join("..", "..", "bin", "k8s", + fmt.Sprintf("1.29.0-%s-%s", runtime.GOOS, runtime.GOARCH)), + } + + var err error + // cfg is defined in this file globally. + cfg, err = testEnv.Start() + Expect(err).NotTo(HaveOccurred()) + Expect(cfg).NotTo(BeNil()) + + err = jetstreamnatsiov1beta2.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + + k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) + Expect(err).NotTo(HaveOccurred()) + Expect(k8sClient).NotTo(BeNil()) + + By("bootstrapping the test server") + testServer = CreateTestServer() + jsClient, err = CreateJetStreamClient( + &NatsConfig{ServerURL: testServer.ClientURL()}, + true, + ) + Expect(err).NotTo(HaveOccurred()) + Expect(jsClient).NotTo(BeNil()) + +}) + +var _ = AfterSuite(func() { + By("tearing down the test environment") + err := testEnv.Stop() + Expect(err).NotTo(HaveOccurred()) + + By("tearing down the test server") + storeDir := testServer.StoreDir() + testServer.Shutdown() + err = os.RemoveAll(storeDir) + Expect(err).NotTo(HaveOccurred()) +}) + +func CreateTestServer() *server.Server { + opts := &natsserver.DefaultTestOptions + opts.JetStream = true + opts.Port = -1 + opts.Debug = true + + dir, err := os.MkdirTemp("", "nats-*") + Expect(err).NotTo(HaveOccurred()) + opts.StoreDir = dir + + ns := natsserver.RunServer(opts) + Expect(err).NotTo(HaveOccurred()) + + return ns +} From e9dd49ccefd8b409428c143e8c7449e0b6045272 Mon Sep 17 00:00:00 2001 From: Adrian <59565247+adriandieter@users.noreply.github.com> Date: Thu, 19 Dec 2024 07:58:26 +0100 Subject: [PATCH 03/19] feat(controller-runtime): Implement stream controller (#211) * Add account, consumer and stream controller stubs to be implemented Controllers and tests are based on files generated by operator-sdk. Adds a minimal test suite for the controllers with a etcd test env and a test nats jetStream server to test against. * Add logs to Reconcile functions * Add jsClient to test suit variables * Remove format from log string * Make upsertCondition public to be used in new controllers * Implement basic cases for stream reconciliation See TODOs on what still needs to be implemented. * refactor to use shared base controller * Support jetstream connection options in stream spec * implement stream deletion * update observedGeneration of status * check Spec.PreventDelete before stream deletion * remove base js client Use a single use client on every connection. This should be replaced by a client pool in the future. * move asJsonString to jetstream_controller * check namespace read only and prevent update mode * Update comments and log * Fix test docs and check precondition * Add preventUpdate test cases * Add tests for read-only or namespace restricted mode * fix empty ca when no ca set Setting CAs: []string{*ca} resulted in []string{""} when no CA was set, leading to an error when creating clients. * simplify error message * fix error loop when the underlying stream was deleted * refactor each phase into separate method * Fix errors during parallel reconciliation & Refactor tests - Trigger only on generation changes - Split initialization and create into separate calls to Reconcile * make test description strings more uniform * Update docs and log messages * extract configuration to buildNatsConfig method * fix checking for preventDelete in the update step Instead check for preventUpdate. Introduced during refactor. * fix k8s binaries not downloaded for tests * add /bin to gitignore * rename stream helper functions Prefix with stream to prevent conflict with other resources. * update naming as suggested * fix assumed reason in log message * Update todo comments marked with review - Add note on opts.Account - Add comment on possible feature to expose TLSFirst in the spec. * separate CA config from client cert and key * set streamName and consumerName fields once on logger Reword log messages. --- .gitignore | 1 + Makefile | 1 + cmd/jetstream-controller/main.go | 7 +- controllers/jetstream/consumer.go | 4 +- controllers/jetstream/controller.go | 2 +- controllers/jetstream/controller_test.go | 6 +- controllers/jetstream/stream.go | 4 +- internal/controller/account_controller.go | 8 +- .../controller/account_controller_test.go | 3 +- internal/controller/client.go | 23 +- internal/controller/consumer_controller.go | 8 +- .../controller/consumer_controller_test.go | 3 +- internal/controller/helpers_test.go | 43 + internal/controller/jetstream_controller.go | 169 ++++ .../controller/jetstream_controller_test.go | 138 ++++ internal/controller/register.go | 20 +- internal/controller/stream_controller.go | 358 ++++++++- internal/controller/stream_controller_test.go | 735 +++++++++++++++++- internal/controller/suite_test.go | 26 +- internal/controller/types.go | 6 + 20 files changed, 1440 insertions(+), 125 deletions(-) create mode 100644 internal/controller/helpers_test.go create mode 100644 internal/controller/jetstream_controller.go create mode 100644 internal/controller/jetstream_controller_test.go create mode 100644 internal/controller/types.go diff --git a/.gitignore b/.gitignore index 8571a5c6..8d5ae051 100644 --- a/.gitignore +++ b/.gitignore @@ -18,4 +18,5 @@ /nats-boot-config /nats-boot-config.docker /tools +/bin /.idea diff --git a/Makefile b/Makefile index 3c24b0bc..2046da66 100644 --- a/Makefile +++ b/Makefile @@ -208,6 +208,7 @@ $(ENVTEST): $(LOCALBIN) .PHONY: test test: envtest go vet ./controllers/... ./pkg/natsreloader/... ./internal/controller/... + $(ENVTEST) use $(ENVTEST_K8S_VERSION) --bin-dir $(LOCALBIN) -p path ## Get k8s binaries go test -race -cover -count=1 -timeout 10s ./controllers/... ./pkg/natsreloader/... ./internal/controller/... .PHONY: clean diff --git a/cmd/jetstream-controller/main.go b/cmd/jetstream-controller/main.go index 1ec60c3d..62638feb 100644 --- a/cmd/jetstream-controller/main.go +++ b/cmd/jetstream-controller/main.go @@ -88,18 +88,23 @@ func run() error { if *controlLoop { klog.Warning("Starting jetStream controller in experimental control loop mode") + natsCfg := &controller.NatsConfig{ CRDConnect: *crdConnect, ClientName: "jetstream-controller", Credentials: *creds, NKey: *nkey, ServerURL: *server, - CA: *ca, + CAs: []string{}, Certificate: *cert, Key: *key, TLSFirst: *tlsfirst, } + if *ca != "" { + natsCfg.CAs = []string{*ca} + } + controllerCfg := &controller.Config{ ReadOnly: *readOnly, Namespace: *namespace, diff --git a/controllers/jetstream/consumer.go b/controllers/jetstream/consumer.go index 83520514..347bd102 100644 --- a/controllers/jetstream/consumer.go +++ b/controllers/jetstream/consumer.go @@ -354,7 +354,7 @@ func setConsumerOK(ctx context.Context, s *apis.Consumer, i typed.ConsumerInterf sc := s.DeepCopy() sc.Status.ObservedGeneration = s.Generation - sc.Status.Conditions = upsertCondition(sc.Status.Conditions, apis.Condition{ + sc.Status.Conditions = UpsertCondition(sc.Status.Conditions, apis.Condition{ Type: readyCondType, Status: k8sapi.ConditionTrue, LastTransitionTime: time.Now().UTC().Format(time.RFC3339Nano), @@ -382,7 +382,7 @@ func setConsumerErrored(ctx context.Context, s *apis.Consumer, sif typed.Consume } sc := s.DeepCopy() - sc.Status.Conditions = upsertCondition(sc.Status.Conditions, apis.Condition{ + sc.Status.Conditions = UpsertCondition(sc.Status.Conditions, apis.Condition{ Type: readyCondType, Status: k8sapi.ConditionFalse, LastTransitionTime: time.Now().UTC().Format(time.RFC3339Nano), diff --git a/controllers/jetstream/controller.go b/controllers/jetstream/controller.go index 6d918f62..ce1a7d9b 100644 --- a/controllers/jetstream/controller.go +++ b/controllers/jetstream/controller.go @@ -695,7 +695,7 @@ func processQueueNext(q workqueue.RateLimitingInterface, jmsClient jsmClientFunc q.Forget(item) } -func upsertCondition(cs []apis.Condition, next apis.Condition) []apis.Condition { +func UpsertCondition(cs []apis.Condition, next apis.Condition) []apis.Condition { for i := 0; i < len(cs); i++ { if cs[i].Type != next.Type { continue diff --git a/controllers/jetstream/controller_test.go b/controllers/jetstream/controller_test.go index 68f8f7fd..aae4d9b6 100644 --- a/controllers/jetstream/controller_test.go +++ b/controllers/jetstream/controller_test.go @@ -186,7 +186,7 @@ func TestUpsertCondition(t *testing.T) { var cs []apis.Condition - cs = upsertCondition(cs, apis.Condition{ + cs = UpsertCondition(cs, apis.Condition{ Type: readyCondType, Status: k8sapis.ConditionTrue, LastTransitionTime: time.Now().UTC().Format(time.RFC3339Nano), @@ -202,7 +202,7 @@ func TestUpsertCondition(t *testing.T) { t.Fatalf("got=%s; want=%s", got, want) } - cs = upsertCondition(cs, apis.Condition{ + cs = UpsertCondition(cs, apis.Condition{ Type: readyCondType, Status: k8sapis.ConditionFalse, LastTransitionTime: time.Now().UTC().Format(time.RFC3339Nano), @@ -218,7 +218,7 @@ func TestUpsertCondition(t *testing.T) { t.Fatalf("got=%s; want=%s", got, want) } - cs = upsertCondition(cs, apis.Condition{ + cs = UpsertCondition(cs, apis.Condition{ Type: "Foo", Status: k8sapis.ConditionTrue, LastTransitionTime: time.Now().UTC().Format(time.RFC3339Nano), diff --git a/controllers/jetstream/stream.go b/controllers/jetstream/stream.go index 5fae6744..5f582fe1 100644 --- a/controllers/jetstream/stream.go +++ b/controllers/jetstream/stream.go @@ -463,7 +463,7 @@ func setStreamErrored(ctx context.Context, s *apis.Stream, sif typed.StreamInter } sc := s.DeepCopy() - sc.Status.Conditions = upsertCondition(sc.Status.Conditions, apis.Condition{ + sc.Status.Conditions = UpsertCondition(sc.Status.Conditions, apis.Condition{ Type: readyCondType, Status: k8sapi.ConditionFalse, LastTransitionTime: time.Now().UTC().Format(time.RFC3339Nano), @@ -492,7 +492,7 @@ func setStreamOK(ctx context.Context, s *apis.Stream, i typed.StreamInterface) ( sc := s.DeepCopy() sc.Status.ObservedGeneration = s.Generation - sc.Status.Conditions = upsertCondition(sc.Status.Conditions, apis.Condition{ + sc.Status.Conditions = UpsertCondition(sc.Status.Conditions, apis.Condition{ Type: readyCondType, Status: k8sapi.ConditionTrue, LastTransitionTime: time.Now().UTC().Format(time.RFC3339Nano), diff --git a/internal/controller/account_controller.go b/internal/controller/account_controller.go index a5686437..72418667 100644 --- a/internal/controller/account_controller.go +++ b/internal/controller/account_controller.go @@ -18,21 +18,15 @@ package controller import ( "context" - "github.com/nats-io/nats.go/jetstream" "k8s.io/klog/v2" jetstreamnatsiov1beta2 "github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2" - "k8s.io/apimachinery/pkg/runtime" ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" ) // AccountReconciler reconciles a Account object type AccountReconciler struct { - client.Client - Scheme *runtime.Scheme - Config *Config - JetStream jetstream.JetStream + JetStreamController } // Reconcile is part of the main kubernetes reconciliation loop which aims to diff --git a/internal/controller/account_controller_test.go b/internal/controller/account_controller_test.go index aa897363..46debade 100644 --- a/internal/controller/account_controller_test.go +++ b/internal/controller/account_controller_test.go @@ -69,8 +69,7 @@ var _ = Describe("Account Controller", func() { It("should successfully reconcile the resource", func() { By("Reconciling the created resource") controllerReconciler := &AccountReconciler{ - Client: k8sClient, - Scheme: k8sClient.Scheme(), + baseController, } _, err := controllerReconciler.Reconcile(ctx, reconcile.Request{ diff --git a/internal/controller/client.go b/internal/controller/client.go index 8c81994e..c20144b3 100644 --- a/internal/controller/client.go +++ b/internal/controller/client.go @@ -12,7 +12,7 @@ type NatsConfig struct { Credentials string NKey string ServerURL string - CA string + CAs []string Certificate string Key string TLSFirst bool @@ -46,19 +46,26 @@ func (o *NatsConfig) buildOptions() ([]nats.Option, error) { opts = append(opts, nats.ClientCert(o.Certificate, o.Key)) } - if o.CA != "" { - opts = append(opts, nats.RootCAs(o.CA)) + if o.CAs != nil && len(o.CAs) > 0 { + opts = append(opts, nats.RootCAs(o.CAs...)) } } return opts, nil } -func CreateJetStreamClient(cfg *NatsConfig, pedantic bool) (jetstream.JetStream, error) { +type Closable interface { + Close() +} + +// CreateJetStreamClient creates new Jetstream client with a connection based on the given NatsConfig. +// Returns a jetstream.Jetstream client and the Closable of the underlying connection. +// Close should be called when the client is no longer used. +func CreateJetStreamClient(cfg *NatsConfig, pedantic bool) (jetstream.JetStream, Closable, error) { opts, err := cfg.buildOptions() if err != nil { - return nil, fmt.Errorf("nats options: %w", err) + return nil, nil, fmt.Errorf("nats options: %w", err) } // Set pedantic option @@ -74,12 +81,12 @@ func CreateJetStreamClient(cfg *NatsConfig, pedantic bool) (jetstream.JetStream, nc, err := nats.Connect(cfg.ServerURL, opts...) if err != nil { - return nil, fmt.Errorf("nats connect: %w", err) + return nil, nil, fmt.Errorf("nats connect: %w", err) } js, err := jetstream.New(nc) if err != nil { - return nil, fmt.Errorf("new jetstream: %w", err) + return nil, nil, fmt.Errorf("new jetstream: %w", err) } - return js, nil + return js, nc, nil } diff --git a/internal/controller/consumer_controller.go b/internal/controller/consumer_controller.go index 6dbe7ba8..22905065 100644 --- a/internal/controller/consumer_controller.go +++ b/internal/controller/consumer_controller.go @@ -18,21 +18,15 @@ package controller import ( "context" - "github.com/nats-io/nats.go/jetstream" "k8s.io/klog/v2" jetstreamnatsiov1beta2 "github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2" - "k8s.io/apimachinery/pkg/runtime" ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" ) // ConsumerReconciler reconciles a Consumer object type ConsumerReconciler struct { - client.Client - Scheme *runtime.Scheme - Config *Config - JetStream jetstream.JetStream + JetStreamController } // Reconcile is part of the main kubernetes reconciliation loop which aims to diff --git a/internal/controller/consumer_controller_test.go b/internal/controller/consumer_controller_test.go index 1ffce13e..6ce92bcc 100644 --- a/internal/controller/consumer_controller_test.go +++ b/internal/controller/consumer_controller_test.go @@ -75,8 +75,7 @@ var _ = Describe("Consumer Controller", func() { It("should successfully reconcile the resource", func() { By("Reconciling the created resource") controllerReconciler := &ConsumerReconciler{ - Client: k8sClient, - Scheme: k8sClient.Scheme(), + baseController, } _, err := controllerReconciler.Reconcile(ctx, reconcile.Request{ diff --git a/internal/controller/helpers_test.go b/internal/controller/helpers_test.go new file mode 100644 index 00000000..8235f11c --- /dev/null +++ b/internal/controller/helpers_test.go @@ -0,0 +1,43 @@ +package controller + +import ( + api "github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2" + "github.com/nats-io/nats-server/v2/server" + natsserver "github.com/nats-io/nats-server/v2/test" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + v1 "k8s.io/api/core/v1" + + "os" + "time" +) + +func assertReadyStateMatches(condition api.Condition, status v1.ConditionStatus, reason string, message string, transitionTime time.Time) { + GinkgoHelper() + + Expect(condition.Type).To(Equal(readyCondType)) + Expect(condition.Status).To(Equal(status)) + Expect(condition.Reason).To(Equal(reason)) + Expect(condition.Message).To(ContainSubstring(message)) + + // Assert valid transition time + t, err := time.Parse(time.RFC3339Nano, condition.LastTransitionTime) + Expect(err).NotTo(HaveOccurred()) + Expect(t).To(BeTemporally("~", transitionTime, time.Second)) +} + +func CreateTestServer() *server.Server { + opts := &natsserver.DefaultTestOptions + opts.JetStream = true + opts.Port = -1 + opts.Debug = true + + dir, err := os.MkdirTemp("", "nats-*") + Expect(err).NotTo(HaveOccurred()) + opts.StoreDir = dir + + ns := natsserver.RunServer(opts) + Expect(err).NotTo(HaveOccurred()) + + return ns +} diff --git a/internal/controller/jetstream_controller.go b/internal/controller/jetstream_controller.go new file mode 100644 index 00000000..3796fc47 --- /dev/null +++ b/internal/controller/jetstream_controller.go @@ -0,0 +1,169 @@ +package controller + +import ( + "fmt" + js "github.com/nats-io/nack/controllers/jetstream" + api "github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2" + "github.com/nats-io/nats.go/jetstream" + v1 "k8s.io/api/core/v1" + "strings" + + "sigs.k8s.io/controller-runtime/pkg/client" + "time" +) + +type connectionOptions struct { + Account string `json:"account"` + Creds string `json:"creds"` + Nkey string `json:"nkey"` + Servers []string `json:"servers"` + TLS api.TLS `json:"tls"` +} + +type JetStreamController interface { + client.Client + + // ReadOnly returns true when no changes should be made by the controller. + ReadOnly() bool + + // ValidNamespace ok if the controllers namespace restriction allows the given namespace. + ValidNamespace(namespace string) bool + + // WithJetStreamClient provides a jetStream client to the given operation. + // The client uses the controllers connection configuration merged with opts. + // + // The given opts values take precedence over the controllers base configuration. + // + // Returns the error of the operation or errors during client setup. + WithJetStreamClient(opts *connectionOptions, op func(js jetstream.JetStream) error) error +} + +func NewJSController(k8sClient client.Client, natsConfig *NatsConfig, controllerConfig *Config) (JetStreamController, error) { + + return &jsController{ + Client: k8sClient, + config: natsConfig, + controllerConfig: controllerConfig, + }, nil +} + +type jsController struct { + client.Client + config *NatsConfig + controllerConfig *Config +} + +func (c *jsController) ReadOnly() bool { + return c.controllerConfig.ReadOnly +} + +func (c *jsController) ValidNamespace(namespace string) bool { + ns := c.controllerConfig.Namespace + return ns == "" || ns == namespace +} + +func (c *jsController) WithJetStreamClient(opts *connectionOptions, op func(js jetstream.JetStream) error) error { + + // Build single use client + // TODO(future-feature): Use client-pool instead of single use client + cfg := c.buildNatsConfig(opts) + + jsClient, closer, err := CreateJetStreamClient(cfg, true) + if err != nil { + return fmt.Errorf("create jetstream client: %w", err) + } + defer closer.Close() + + return op(jsClient) +} + +// buildNatsConfig uses given opts to override the base NatsConfig. +func (c *jsController) buildNatsConfig(opts *connectionOptions) *NatsConfig { + + serverUrls := strings.Join(opts.Servers, ",") + + // Takes opts values if present + cfg := &NatsConfig{ + CRDConnect: false, + ClientName: c.config.ClientName, + ServerURL: or(serverUrls, c.config.ServerURL), + TLSFirst: c.config.TLSFirst, // TODO(future-feature): expose TLSFirst in the spec config + } + + // Note: The opts.Account value coming from the resource spec is currently not considered. + // creds/nkey are associated with an account, the account field might be redundant. + // See https://github.com/nats-io/nack/pull/211#pullrequestreview-2511111670 + + // Authentication either from opts or base config + if opts.Creds != "" || opts.Nkey != "" { + cfg.Credentials = opts.Creds + cfg.NKey = opts.Nkey + } else { + cfg.Credentials = c.config.Credentials + cfg.NKey = c.config.NKey + } + + // CAs from opts or base config + if len(opts.TLS.RootCAs) > 0 { + cfg.CAs = opts.TLS.RootCAs + } else { + cfg.CAs = c.config.CAs + } + + // Client Cert and Key either from opts or base config + if opts.TLS.ClientCert != "" && opts.TLS.ClientKey != "" { + cfg.Certificate = opts.TLS.ClientCert + cfg.Key = opts.TLS.ClientKey + } else { + cfg.Certificate = c.config.Certificate + cfg.Key = c.config.Key + } + + return cfg +} + +// or returns the value if it is not the null value. Otherwise, the fallback value is returned +func or[T comparable](v T, fallback T) T { + if v == *new(T) { + return fallback + } + return v +} + +// updateReadyCondition returns the given conditions with an added or updated ready condition. +func updateReadyCondition(conditions []api.Condition, status v1.ConditionStatus, reason string, message string) []api.Condition { + + var currentStatus v1.ConditionStatus + var lastTransitionTime string + for _, condition := range conditions { + if condition.Type == readyCondType { + currentStatus = condition.Status + lastTransitionTime = condition.LastTransitionTime + break + } + } + + // Set transition time to now, when no previous ready condition or the status changed + if lastTransitionTime == "" || currentStatus != status { + lastTransitionTime = time.Now().UTC().Format(time.RFC3339Nano) + } + + newCondition := api.Condition{ + Type: readyCondType, + Status: status, + Reason: reason, + Message: message, + LastTransitionTime: lastTransitionTime, + } + if conditions == nil { + return []api.Condition{newCondition} + } else { + return js.UpsertCondition(conditions, newCondition) + } +} + +// asJsonString returns the given string wrapped in " and converted to []byte. +// Helper for mapping spec config to jetStream config using UnmarshalJSON. +func asJsonString(v string) []byte { + return []byte("\"" + v + "\"") +} diff --git a/internal/controller/jetstream_controller_test.go b/internal/controller/jetstream_controller_test.go new file mode 100644 index 00000000..d7176d04 --- /dev/null +++ b/internal/controller/jetstream_controller_test.go @@ -0,0 +1,138 @@ +package controller + +import ( + api "github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2" + "github.com/stretchr/testify/assert" + v1 "k8s.io/api/core/v1" + "testing" + "time" +) + +func Test_updateReadyCondition(t *testing.T) { + + pastTransition := time.Now().UTC().Add(-time.Hour).Format(time.RFC3339Nano) + updatedTransition := "now" + + otherCondition := api.Condition{ + Type: "other", + Status: v1.ConditionFalse, + Reason: "Reason", + Message: "Message", + LastTransitionTime: pastTransition, + } + + type args struct { + conditions []api.Condition + status v1.ConditionStatus + reason string + message string + } + tests := []struct { + name string + args args + want []api.Condition + }{ + { + name: "new ready condition", + args: args{ + conditions: nil, + status: v1.ConditionTrue, + reason: "Test", + message: "Test Message", + }, + want: []api.Condition{ + { + Type: readyCondType, + Status: v1.ConditionTrue, + Reason: "Test", + Message: "Test Message", + LastTransitionTime: updatedTransition, + }, + }, + }, + { + name: "update ready condition", + args: args{ + conditions: []api.Condition{ + otherCondition, + { + Type: readyCondType, + Status: v1.ConditionFalse, + Reason: "Test", + Message: "Test Message", + LastTransitionTime: pastTransition, + }, + }, + status: v1.ConditionTrue, + reason: "New Reason", + message: "New Message", + }, + want: []api.Condition{ + otherCondition, + { + Type: readyCondType, + Status: v1.ConditionTrue, + Reason: "New Reason", + Message: "New Message", + LastTransitionTime: updatedTransition, + }, + }, + }, + { + name: "should not update transition time when status is not changed", + args: args{ + conditions: []api.Condition{ + otherCondition, + { + Type: readyCondType, + Status: v1.ConditionTrue, + Reason: "Test", + Message: "Test Message", + LastTransitionTime: pastTransition, + }, + }, + status: v1.ConditionTrue, + reason: "New Reason", + message: "New Message", + }, + want: []api.Condition{ + otherCondition, + { + Type: readyCondType, + Status: v1.ConditionTrue, + Reason: "New Reason", + Message: "New Message", + LastTransitionTime: pastTransition, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert := assert.New(t) + + got := updateReadyCondition(tt.args.conditions, tt.args.status, tt.args.reason, tt.args.message) + + assert.Len(got, len(tt.want)) + for i, want := range tt.want { + actual := got[i] + + assert.Equal(actual.Type, want.Type) + assert.Equal(actual.Status, want.Status) + assert.Equal(actual.Reason, want.Reason) + assert.Equal(actual.Message, want.Message) + + // Assert transition time was updated + if want.LastTransitionTime == updatedTransition { + actualTransitionTime, err := time.Parse(time.RFC3339Nano, actual.LastTransitionTime) + assert.NoError(err) + assert.WithinDuration(actualTransitionTime, time.Now(), 5*time.Second) + } + // Assert transition time was not updated + if want.LastTransitionTime == pastTransition { + assert.Equal(pastTransition, actual.LastTransitionTime) + } + } + }) + } +} diff --git a/internal/controller/register.go b/internal/controller/register.go index 611ff157..5fa21b5a 100644 --- a/internal/controller/register.go +++ b/internal/controller/register.go @@ -20,36 +20,26 @@ type Config struct { // controllerCfg defines behaviour of the registered controllers. func RegisterAll(mgr ctrl.Manager, clientConfig *NatsConfig, config *Config) error { - // Controllers need access to a nats client in pedantic mode - js, err := CreateJetStreamClient(clientConfig, true) + baseController, err := NewJSController(mgr.GetClient(), clientConfig, config) if err != nil { - return fmt.Errorf("create jetstream client: %w", err) + return fmt.Errorf("create base jetstream controller: %w", err) } // Register controllers if err := (&AccountReconciler{ - Client: mgr.GetClient(), - Config: config, - Scheme: mgr.GetScheme(), - JetStream: js, + baseController, }).SetupWithManager(mgr); err != nil { return fmt.Errorf("unable to create account controller: %w", err) } if err := (&ConsumerReconciler{ - Client: mgr.GetClient(), - Config: config, - Scheme: mgr.GetScheme(), - JetStream: js, + baseController, }).SetupWithManager(mgr); err != nil { return fmt.Errorf("unable to create consumer controller: %w", err) } if err := (&StreamReconciler{ - Client: mgr.GetClient(), - Config: config, - Scheme: mgr.GetScheme(), - JetStream: js, + JetStreamController: baseController, }).SetupWithManager(mgr); err != nil { return fmt.Errorf("unable to create stream controller: %w", err) } diff --git a/internal/controller/stream_controller.go b/internal/controller/stream_controller.go index 4469b7f0..1766c8ba 100644 --- a/internal/controller/stream_controller.go +++ b/internal/controller/stream_controller.go @@ -18,39 +18,371 @@ package controller import ( "context" + "errors" + "fmt" + "github.com/go-logr/logr" + api "github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2" "github.com/nats-io/nats.go/jetstream" - "k8s.io/apimachinery/pkg/runtime" + v1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/klog/v2" ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - - jetstreamnatsiov1beta2 "github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + "sigs.k8s.io/controller-runtime/pkg/predicate" + "time" ) // StreamReconciler reconciles a Stream object type StreamReconciler struct { - client.Client - Scheme *runtime.Scheme - Config *Config - JetStream jetstream.JetStream + JetStreamController } // Reconcile is part of the main kubernetes reconciliation loop which aims to // move the current state of the cluster closer to the desired state. // -// For more details, check Reconcile and its Result here: -// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.17.3/pkg/reconcile +// It performs three main operations: +// - Initialize finalizer and ready condition if not present +// - Delete stream if it is marked for deletion. +// - Create or Update the stream +// +// A call to reconcile may perform only one action, expecting the reconciliation to be triggered again by an update. +// For example: Setting the finalizer triggers a second reconciliation. Reconcile returns after setting the finalizer, +// to prevent parallel reconciliations performing the same steps. func (r *StreamReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { log := klog.FromContext(ctx) - log.Info("reconcile", "namespace", req.Namespace, "name", req.Name) - // TODO(user): your logic here + if ok := r.ValidNamespace(req.Namespace); !ok { + log.Info("Controller restricted to namespace, skipping reconciliation.") + return ctrl.Result{}, nil + } + + // Fetch stream resource + stream := &api.Stream{} + if err := r.Get(ctx, req.NamespacedName, stream); err != nil { + if apierrors.IsNotFound(err) { + log.Info("Stream resource not found. Ignoring since object must be deleted.") + return ctrl.Result{}, nil + } + return ctrl.Result{}, fmt.Errorf("get stream resource '%s': %w", req.NamespacedName.String(), err) + } + + log = log.WithValues("streamName", stream.Spec.Name) + + // Update ready status to unknown when no status is set + if stream.Status.Conditions == nil || len(stream.Status.Conditions) == 0 { + log.Info("Setting initial ready condition to unknown.") + stream.Status.Conditions = updateReadyCondition(stream.Status.Conditions, v1.ConditionUnknown, "Reconciling", "Starting reconciliation") + err := r.Status().Update(ctx, stream) + if err != nil { + return ctrl.Result{}, fmt.Errorf("set condition unknown: %w", err) + } + return ctrl.Result{Requeue: true}, nil + } + + // Add finalizer + if !controllerutil.ContainsFinalizer(stream, streamFinalizer) { + log.Info("Adding stream finalizer.") + if ok := controllerutil.AddFinalizer(stream, streamFinalizer); !ok { + return ctrl.Result{}, errors.New("failed to add finalizer to stream resource") + } + + if err := r.Update(ctx, stream); err != nil { + return ctrl.Result{}, fmt.Errorf("update stream resource to add finalizer: %w", err) + } + return ctrl.Result{}, nil + } + + // Check Deletion + markedForDeletion := stream.GetDeletionTimestamp() != nil + if markedForDeletion { + if controllerutil.ContainsFinalizer(stream, streamFinalizer) { + err := r.deleteStream(ctx, log, stream) + if err != nil { + return ctrl.Result{}, fmt.Errorf("delete stream: %w", err) + } + } else { + log.Info("Stream marked for deletion and already finalized. Ignoring.") + } + + return ctrl.Result{}, nil + } + + // Create or update stream + if err := r.createOrUpdate(ctx, log, stream); err != nil { + return ctrl.Result{}, fmt.Errorf("create or update: %s", err) + } return ctrl.Result{}, nil } +func (r *StreamReconciler) deleteStream(ctx context.Context, log logr.Logger, stream *api.Stream) error { + + // Set status to not false + stream.Status.Conditions = updateReadyCondition(stream.Status.Conditions, v1.ConditionFalse, "Finalizing", "Performing finalizer operations.") + if err := r.Status().Update(ctx, stream); err != nil { + return fmt.Errorf("update ready condition: %w", err) + } + + if !stream.Spec.PreventDelete && !r.ReadOnly() { + log.Info("Deleting stream.") + err := r.WithJetStreamClient(streamConnOpts(stream.Spec), func(js jetstream.JetStream) error { + return js.DeleteStream(ctx, stream.Spec.Name) + }) + if errors.Is(err, jetstream.ErrStreamNotFound) { + log.Info("Stream does not exist, unable to delete.", "streamName", stream.Spec.Name) + } else if err != nil { + return fmt.Errorf("delete stream during finalization: %w", err) + } + } else { + log.Info("Skipping stream deletion.", + "preventDelete", stream.Spec.PreventDelete, + "read-only", r.ReadOnly(), + ) + } + + log.Info("Removing stream finalizer.") + if ok := controllerutil.RemoveFinalizer(stream, streamFinalizer); !ok { + return errors.New("failed to remove stream finalizer") + } + if err := r.Update(ctx, stream); err != nil { + return fmt.Errorf("remove finalizer: %w", err) + } + + return nil +} + +func (r *StreamReconciler) createOrUpdate(ctx context.Context, log logr.Logger, stream *api.Stream) error { + + // Create or Update the stream based on the spec + if stream.Spec.PreventUpdate || r.ReadOnly() { + log.Info("Skipping stream creation or update.", + "preventDelete", stream.Spec.PreventDelete, + "read-only", r.ReadOnly(), + ) + return nil + } + + // Map spec to stream targetConfig + targetConfig, err := streamSpecToConfig(&stream.Spec) + if err != nil { + return fmt.Errorf("map spec to stream targetConfig: %w", err) + } + + // CreateOrUpdateStream is called on every reconciliation when the stream is not to be deleted. + // TODO(future-feature): Do we need to check if config differs? + err = r.WithJetStreamClient(streamConnOpts(stream.Spec), func(js jetstream.JetStream) error { + log.Info("Creating or updating stream.") + _, err = js.CreateOrUpdateStream(ctx, targetConfig) + return err + }) + if err != nil { + err = fmt.Errorf("create or update stream: %w", err) + stream.Status.Conditions = updateReadyCondition(stream.Status.Conditions, v1.ConditionFalse, "Errored", err.Error()) + if err := r.Status().Update(ctx, stream); err != nil { + log.Error(err, "Failed to update ready condition to Errored.") + } + return err + } + + // update the observed generation and ready status + stream.Status.ObservedGeneration = stream.Generation + stream.Status.Conditions = updateReadyCondition( + stream.Status.Conditions, + v1.ConditionTrue, + "Reconciling", + "Stream successfully created or updated.", + ) + err = r.Status().Update(ctx, stream) + if err != nil { + return fmt.Errorf("update ready condition: %w", err) + } + + return nil +} + +// streamConnOpts extracts nats connection relevant fields from the given stream spec as connectionOptions. +func streamConnOpts(spec api.StreamSpec) *connectionOptions { + return &connectionOptions{ + Account: spec.Account, + Creds: spec.Creds, + Nkey: spec.Nkey, + Servers: spec.Servers, + TLS: spec.TLS, + } +} + +// streamSpecToConfig creates a jetstream.StreamConfig matching the given stream resource spec +func streamSpecToConfig(spec *api.StreamSpec) (jetstream.StreamConfig, error) { + + // Set directly mapped fields + config := jetstream.StreamConfig{ + Name: spec.Name, + Description: spec.Description, + Subjects: spec.Subjects, + MaxConsumers: spec.MaxConsumers, + MaxMsgs: int64(spec.MaxMsgs), + MaxBytes: int64(spec.MaxBytes), + DiscardNewPerSubject: spec.DiscardPerSubject, + MaxMsgsPerSubject: int64(spec.MaxMsgsPerSubject), + MaxMsgSize: int32(spec.MaxMsgSize), + Replicas: spec.Replicas, + NoAck: spec.NoAck, + DenyDelete: spec.DenyDelete, + DenyPurge: spec.DenyPurge, + AllowRollup: spec.AllowRollup, + FirstSeq: spec.FirstSequence, + AllowDirect: spec.AllowDirect, + // Explicitly set not (yet) mapped fields + Sealed: false, + MirrorDirect: false, + ConsumerLimits: jetstream.StreamConsumerLimits{}, + } + + // Set not directly mapped fields + + // retention + if spec.Retention != "" { + // Wrap string in " to be properly unmarshalled as json string + err := config.Retention.UnmarshalJSON(asJsonString(spec.Retention)) + if err != nil { + return jetstream.StreamConfig{}, fmt.Errorf("invalid retention policy: %w", err) + } + } + + // discard + if spec.Discard != "" { + err := config.Discard.UnmarshalJSON(asJsonString(spec.Discard)) + if err != nil { + return jetstream.StreamConfig{}, fmt.Errorf("invalid retention policy: %w", err) + } + } + + // maxAge + if spec.MaxAge != "" { + d, err := time.ParseDuration(spec.MaxAge) + if err != nil { + return jetstream.StreamConfig{}, fmt.Errorf("parse max age: %w", err) + } + config.MaxAge = d + } + // storage + if spec.Storage != "" { + err := config.Storage.UnmarshalJSON(asJsonString(spec.Storage)) + if err != nil { + return jetstream.StreamConfig{}, fmt.Errorf("invalid storage: %w", err) + } + } + + // duplicates + if spec.DuplicateWindow != "" { + d, err := time.ParseDuration(spec.DuplicateWindow) + if err != nil { + return jetstream.StreamConfig{}, fmt.Errorf("parse duplicate window: %w", err) + } + config.Duplicates = d + } + + // placement + if spec.Placement != nil { + config.Placement = &jetstream.Placement{ + Cluster: spec.Placement.Cluster, + Tags: spec.Placement.Tags, + } + } + + // mirror + if spec.Mirror != nil { + ss, err := mapStreamSource(spec.Mirror) + if err != nil { + return jetstream.StreamConfig{}, fmt.Errorf("map mirror stream soruce: %w", err) + } + config.Mirror = ss + } + + // sources + if spec.Sources != nil { + config.Sources = []*jetstream.StreamSource{} + for _, source := range spec.Sources { + s, err := mapStreamSource(source) + if err != nil { + return jetstream.StreamConfig{}, fmt.Errorf("map stream soruce: %w", err) + } + config.Sources = append(config.Sources, s) + } + } + + // compression + if spec.Compression != "" { + err := config.Compression.UnmarshalJSON(asJsonString(spec.Compression)) + if err != nil { + return jetstream.StreamConfig{}, fmt.Errorf("invalid compression: %w", err) + } + } + + // subjectTransform + if spec.SubjectTransform != nil { + config.SubjectTransform = &jetstream.SubjectTransformConfig{ + Source: spec.SubjectTransform.Source, + Destination: spec.SubjectTransform.Dest, + } + } + + // rePublish + if spec.Republish != nil { + config.RePublish = &jetstream.RePublish{ + Source: spec.Republish.Source, + Destination: spec.Republish.Destination, + HeadersOnly: spec.Republish.HeadersOnly, + } + } + + // metadata + if spec.Metadata != nil { + config.Metadata = spec.Metadata + } + + return config, nil +} + +func mapStreamSource(ss *api.StreamSource) (*jetstream.StreamSource, error) { + jss := &jetstream.StreamSource{ + Name: ss.Name, + FilterSubject: ss.FilterSubject, + } + + if ss.OptStartSeq > 0 { + jss.OptStartSeq = uint64(ss.OptStartSeq) + } + if ss.OptStartTime != "" { + t, err := time.Parse(time.RFC3339, ss.OptStartTime) + if err != nil { + return nil, fmt.Errorf("parse opt start time: %w", err) + } + jss.OptStartTime = &t + } + + if ss.ExternalAPIPrefix != "" || ss.ExternalDeliverPrefix != "" { + jss.External = &jetstream.ExternalStream{ + APIPrefix: ss.ExternalAPIPrefix, + DeliverPrefix: ss.ExternalDeliverPrefix, + } + } + + for _, transform := range ss.SubjectTransforms { + jss.SubjectTransforms = append(jss.SubjectTransforms, jetstream.SubjectTransformConfig{ + Source: transform.Source, + Destination: transform.Dest, + }) + } + + return jss, nil +} + // SetupWithManager sets up the controller with the Manager. func (r *StreamReconciler) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). - For(&jetstreamnatsiov1beta2.Stream{}). + For(&api.Stream{}). + Owns(&api.Stream{}). + // Only trigger on generation changes + WithEventFilter(predicate.GenerationChangedPredicate{}). Complete(r) } diff --git a/internal/controller/stream_controller_test.go b/internal/controller/stream_controller_test.go index ddd0e1c8..e80d327b 100644 --- a/internal/controller/stream_controller_test.go +++ b/internal/controller/stream_controller_test.go @@ -17,74 +17,729 @@ limitations under the License. package controller import ( + "github.com/nats-io/nats.go/jetstream" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - "k8s.io/apimachinery/pkg/api/errors" + "github.com/stretchr/testify/assert" + v1 "k8s.io/api/core/v1" + k8serrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/types" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" "sigs.k8s.io/controller-runtime/pkg/reconcile" + "testing" + "time" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - jetstreamnatsiov1beta2 "github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2" + api "github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2" ) var _ = Describe("Stream Controller", func() { - Context("When reconciling a resource", func() { - const resourceName = "test-resource" - typeNamespacedName := types.NamespacedName{ - Name: resourceName, - Namespace: "default", // TODO(user):Modify as needed + // The test stream resource + const resourceName = "test-stream" + const streamName = "orders" + typeNamespacedName := types.NamespacedName{ + Name: resourceName, + Namespace: "default", + } + stream := &api.Stream{} + + // The tested controller + var controller *StreamReconciler + + // Config to create minimal nats stream + emptyStreamConfig := jetstream.StreamConfig{ + Name: streamName, + Replicas: 1, + Retention: jetstream.WorkQueuePolicy, + Discard: jetstream.DiscardOld, + Storage: jetstream.FileStorage, + } + + BeforeEach(func(ctx SpecContext) { + By("creating a test stream resource") + err := k8sClient.Get(ctx, typeNamespacedName, stream) + if err != nil && k8serrors.IsNotFound(err) { + resource := &api.Stream{ + ObjectMeta: metav1.ObjectMeta{ + Name: resourceName, + Namespace: "default", + }, + Spec: api.StreamSpec{ + Name: streamName, + Replicas: 1, + Subjects: []string{"tests.*"}, + Description: "test stream", + Retention: "workqueue", + Discard: "old", + Storage: "file", + }, + } + Expect(k8sClient.Create(ctx, resource)).To(Succeed()) + // Re-fetch stream + Expect(k8sClient.Get(ctx, typeNamespacedName, stream)).To(Succeed()) } - stream := &jetstreamnatsiov1beta2.Stream{} + By("checking precondition: nats stream does not exist") + _, err = jsClient.Stream(ctx, streamName) + Expect(err).To(MatchError(jetstream.ErrStreamNotFound)) + + By("setting up the tested controller") + controller = &StreamReconciler{ + baseController, + } + }) + + AfterEach(func(ctx SpecContext) { + By("removing the test stream resource") + resource := &api.Stream{} + err := k8sClient.Get(ctx, typeNamespacedName, resource) + if err != nil { + Expect(err).To(MatchError(k8serrors.IsNotFound, "Is not found")) + } else { + if controllerutil.ContainsFinalizer(resource, streamFinalizer) { + By("removing the finalizer") + controllerutil.RemoveFinalizer(resource, streamFinalizer) + Expect(k8sClient.Update(ctx, resource)).To(Succeed()) + } + + By("removing the stream resource") + Expect(k8sClient.Delete(ctx, resource)). + To(SatisfyAny( + Succeed(), + MatchError(k8serrors.IsNotFound, "is not found"), + )) + } + + By("deleting the nats stream") + Expect(jsClient.DeleteStream(ctx, streamName)). + To(SatisfyAny( + Succeed(), + MatchError(jetstream.ErrStreamNotFound), + )) + }) + + When("reconciling a not existing resource", func() { + It("should stop reconciliation without error", func(ctx SpecContext) { + By("reconciling the created resource") + result, err := controller.Reconcile(ctx, reconcile.Request{ + NamespacedName: types.NamespacedName{ + Namespace: "fake", + Name: "not-existing", + }, + }) + Expect(err).NotTo(HaveOccurred()) + Expect(result).To(Equal(ctrl.Result{})) + }) + }) + + When("reconciling a not initialized resource", func() { + + It("should initialize a new resource", func(ctx SpecContext) { + + By("re-queueing until it is initialized") + // Initialization can require multiple reconciliation loops + Eventually(func(ctx SpecContext) *api.Stream { + _, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName}) + Expect(err).NotTo(HaveOccurred()) + got := &api.Stream{} + Expect(k8sClient.Get(ctx, typeNamespacedName, got)).To(Succeed()) + return got + }).WithContext(ctx). + Should(SatisfyAll( + HaveField("Finalizers", HaveExactElements(streamFinalizer)), + HaveField("Status.Conditions", Not(BeEmpty())), + )) + + By("validating the ready condition") + // Fetch stream + Expect(k8sClient.Get(ctx, typeNamespacedName, stream)).To(Succeed()) + Expect(stream.Status.Conditions).To(HaveLen(1)) + + assertReadyStateMatches(stream.Status.Conditions[0], v1.ConditionUnknown, "Reconciling", "Starting reconciliation", time.Now()) + }) + + }) + + When("reconciling an initialized resource", func() { BeforeEach(func(ctx SpecContext) { - By("creating the custom resource for the Kind Stream") - err := k8sClient.Get(ctx, typeNamespacedName, stream) - if err != nil && errors.IsNotFound(err) { - resource := &jetstreamnatsiov1beta2.Stream{ - ObjectMeta: metav1.ObjectMeta{ - Name: resourceName, - Namespace: "default", - }, - Spec: jetstreamnatsiov1beta2.StreamSpec{ - Name: "test-stream", - Replicas: 1, - Discard: "old", - Storage: "file", - Retention: "workqueue", - }, + By("initializing the stream resource") + + By("setting the finalizer") + Expect(controllerutil.AddFinalizer(stream, streamFinalizer)).To(BeTrue()) + Expect(k8sClient.Update(ctx, stream)).To(Succeed()) + + By("setting an unknown ready state") + stream.Status.Conditions = []api.Condition{{ + Type: readyCondType, + Status: v1.ConditionUnknown, + Reason: "Test", + Message: "start condition", + LastTransitionTime: time.Now().Format(time.RFC3339Nano), + }} + Expect(k8sClient.Status().Update(ctx, stream)).To(Succeed()) + + }) + + It("should create a new stream", func(ctx SpecContext) { - // TODO(user): Specify other spec details if needed. + By("running Reconcile") + result, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName}) + Expect(err).NotTo(HaveOccurred()) + Expect(result.IsZero()).To(BeTrue()) + + // Fetch resource + Expect(k8sClient.Get(ctx, typeNamespacedName, stream)).To(Succeed()) + + By("checking if the ready state was updated") + Expect(stream.Status.Conditions).To(HaveLen(1)) + assertReadyStateMatches(stream.Status.Conditions[0], v1.ConditionTrue, "Reconciling", "created or updated", time.Now()) + + By("checking if the observed generation matches") + Expect(stream.Status.ObservedGeneration).To(Equal(stream.Generation)) + + By("checking if the stream was created") + natsStream, err := jsClient.Stream(ctx, streamName) + Expect(err).NotTo(HaveOccurred()) + streamInfo, err := natsStream.Info(ctx) + Expect(err).NotTo(HaveOccurred()) + Expect(streamInfo.Config.Name).To(Equal(streamName)) + Expect(streamInfo.Config.Description).To(Equal("test stream")) + Expect(streamInfo.Created).To(BeTemporally("~", time.Now(), time.Second)) + }) + + When("PreventUpdate is set", func() { + + BeforeEach(func(ctx SpecContext) { + By("setting preventDelete on the resource") + stream.Spec.PreventUpdate = true + Expect(k8sClient.Update(ctx, stream)).To(Succeed()) + }) + It("should not create the stream", func(ctx SpecContext) { + By("running Reconcile") + result, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName}) + Expect(err).NotTo(HaveOccurred()) + Expect(result.IsZero()).To(BeTrue()) + + By("checking that no stream was created") + _, err = jsClient.Stream(ctx, streamName) + Expect(err).To(MatchError(jetstream.ErrStreamNotFound)) + }) + It("should not update the stream", func(ctx SpecContext) { + By("creating the stream") + _, err := jsClient.CreateStream(ctx, emptyStreamConfig) + Expect(err).NotTo(HaveOccurred()) + + By("running Reconcile") + result, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName}) + Expect(err).NotTo(HaveOccurred()) + Expect(result.IsZero()).To(BeTrue()) + + By("checking that stream was not updated") + s, err := jsClient.Stream(ctx, streamName) + Expect(s.CachedInfo().Config.Description).To(BeEmpty()) + }) + }) + + When("read-only mode is enabled", func() { + + BeforeEach(func(ctx SpecContext) { + By("setting read only on the controller") + readOnly, err := NewJSController(k8sClient, &NatsConfig{ServerURL: testServer.ClientURL()}, &Config{ReadOnly: true}) + Expect(err).NotTo(HaveOccurred()) + controller = &StreamReconciler{ + JetStreamController: readOnly, } - Expect(k8sClient.Create(ctx, resource)).To(Succeed()) - } + }) + + It("should not create the stream", func(ctx SpecContext) { + By("running Reconcile") + result, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName}) + Expect(err).NotTo(HaveOccurred()) + Expect(result.IsZero()).To(BeTrue()) + + By("checking that no stream was created") + _, err = jsClient.Stream(ctx, streamName) + Expect(err).To(MatchError(jetstream.ErrStreamNotFound)) + }) + It("should not update the stream", func(ctx SpecContext) { + By("creating the stream") + _, err := jsClient.CreateStream(ctx, emptyStreamConfig) + Expect(err).NotTo(HaveOccurred()) + + By("running Reconcile") + result, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName}) + Expect(err).NotTo(HaveOccurred()) + Expect(result.IsZero()).To(BeTrue()) + + By("checking that stream was not updated") + s, err := jsClient.Stream(ctx, streamName) + Expect(s.CachedInfo().Config.Description).To(BeEmpty()) + }) + }) + + When("namespace restriction is enabled", func() { + + BeforeEach(func(ctx SpecContext) { + By("setting a namespace on the resource") + namespaced, err := NewJSController(k8sClient, &NatsConfig{ServerURL: testServer.ClientURL()}, &Config{Namespace: "other-namespace"}) + Expect(err).NotTo(HaveOccurred()) + controller = &StreamReconciler{ + JetStreamController: namespaced, + } + }) + + It("should not create the stream", func(ctx SpecContext) { + By("running Reconcile") + result, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName}) + Expect(err).NotTo(HaveOccurred()) + Expect(result.IsZero()).To(BeTrue()) + + By("checking that no stream was created") + _, err = jsClient.Stream(ctx, streamName) + Expect(err).To(MatchError(jetstream.ErrStreamNotFound)) + }) + It("should not update the stream", func(ctx SpecContext) { + By("creating the stream") + _, err := jsClient.CreateStream(ctx, emptyStreamConfig) + Expect(err).NotTo(HaveOccurred()) + + By("running Reconcile") + result, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName}) + Expect(err).NotTo(HaveOccurred()) + Expect(result.IsZero()).To(BeTrue()) + + By("checking that stream was not updated") + s, err := jsClient.Stream(ctx, streamName) + Expect(s.CachedInfo().Config.Description).To(BeEmpty()) + }) }) - AfterEach(func(ctx SpecContext) { - // TODO(user): Cleanup logic after each test, like removing the resource instance. - resource := &jetstreamnatsiov1beta2.Stream{} - err := k8sClient.Get(ctx, typeNamespacedName, resource) + It("should update an existing stream", func(ctx SpecContext) { + By("reconciling once to create the stream") + result, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName}) + Expect(err).NotTo(HaveOccurred()) + Expect(result.IsZero()).To(BeTrue()) + + // Fetch resource + Expect(k8sClient.Get(ctx, typeNamespacedName, stream)).To(Succeed()) + previousTransitionTime := stream.Status.Conditions[0].LastTransitionTime + + By("updating the resource") + stream.Spec.Description = "new description" + Expect(k8sClient.Update(ctx, stream)).To(Succeed()) + + By("reconciling the updated resource") + result, err = controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName}) Expect(err).NotTo(HaveOccurred()) + Expect(result.IsZero()).To(BeTrue()) - By("Cleanup the specific resource instance Stream") - Expect(k8sClient.Delete(ctx, resource)).To(Succeed()) + // Fetch resource + Expect(k8sClient.Get(ctx, typeNamespacedName, stream)).To(Succeed()) + + By("checking if the state transition time was not updated") + Expect(stream.Status.Conditions).To(HaveLen(1)) + Expect(stream.Status.Conditions[0].LastTransitionTime).To(Equal(previousTransitionTime)) + + By("checking if the observed generation matches") + Expect(stream.Status.ObservedGeneration).To(Equal(stream.Generation)) + + By("checking if the stream was updated") + natsStream, err := jsClient.Stream(ctx, streamName) + Expect(err).NotTo(HaveOccurred()) + + streamInfo, err := natsStream.Info(ctx) + Expect(err).NotTo(HaveOccurred()) + Expect(streamInfo.Config.Description).To(Equal("new description")) + // Other fields unchanged + Expect(streamInfo.Config.Subjects).To(Equal([]string{"tests.*"})) }) - It("should successfully reconcile the resource", func(ctx SpecContext) { - By("Reconciling the created resource") - controllerReconciler := &StreamReconciler{ - Client: k8sClient, - Scheme: k8sClient.Scheme(), + It("should set an error state when the nats server is not available", func(ctx SpecContext) { + + By("setting up controller with unavailable nats server") + // Setup client for not running server + // Use actual test server to ensure port not used by other service on test instance + sv := CreateTestServer() + base, err := NewJSController(k8sClient, &NatsConfig{ServerURL: sv.ClientURL()}, &Config{}) + Expect(err).NotTo(HaveOccurred()) + sv.Shutdown() + + controller := &StreamReconciler{ + base, } - _, err := controllerReconciler.Reconcile(ctx, reconcile.Request{ + By("reconciling resource") + result, err := controller.Reconcile(ctx, reconcile.Request{ NamespacedName: typeNamespacedName, }) + Expect(result).To(Equal(ctrl.Result{})) + Expect(err).To(HaveOccurred()) // Will be re-queued with back-off + + // Fetch resource + err = k8sClient.Get(ctx, typeNamespacedName, stream) Expect(err).NotTo(HaveOccurred()) - // TODO(user): Add more specific assertions depending on your controller's reconciliation logic. - // Example: If you expect a certain status condition after reconciliation, verify it here. + + By("checking if the status was updated") + Expect(stream.Status.Conditions).To(HaveLen(1)) + assertReadyStateMatches( + stream.Status.Conditions[0], + v1.ConditionFalse, + "Errored", + "create or update stream:", + time.Now(), + ) + + By("checking if the observed generation does not match") + Expect(stream.Status.ObservedGeneration).ToNot(Equal(stream.Generation)) + }) + + When("the resource is marked for deletion", func() { + + BeforeEach(func(ctx SpecContext) { + By("marking the resource for deletion") + Expect(k8sClient.Delete(ctx, stream)).To(Succeed()) + Expect(k8sClient.Get(ctx, typeNamespacedName, stream)).To(Succeed()) // re-fetch after update + }) + + It("should succeed deleting a not existing stream", func(ctx SpecContext) { + By("reconciling") + result, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName}) + Expect(err).NotTo(HaveOccurred()) + Expect(result.IsZero()).To(BeTrue()) + + By("checking that the resource is deleted") + Eventually(k8sClient.Get). + WithArguments(ctx, typeNamespacedName, stream). + ShouldNot(Succeed()) + }) + + When("the underlying stream exists", func() { + BeforeEach(func(ctx SpecContext) { + By("creating the stream on the nats server") + _, err := jsClient.CreateStream(ctx, emptyStreamConfig) + Expect(err).NotTo(HaveOccurred()) + }) + + AfterEach(func(ctx SpecContext) { + err := jsClient.DeleteStream(ctx, streamName) + if err != nil { + Expect(err).To(MatchError(jetstream.ErrStreamNotFound)) + } + }) + + It("should delete the stream", func(ctx SpecContext) { + By("reconciling") + result, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName}) + Expect(err).NotTo(HaveOccurred()) + Expect(result.IsZero()).To(BeTrue()) + + By("checking that the stream is deleted") + _, err = jsClient.Stream(ctx, streamName) + Expect(err).To(MatchError(jetstream.ErrStreamNotFound)) + + By("checking that the resource is deleted") + Eventually(k8sClient.Get). + WithArguments(ctx, typeNamespacedName, stream). + ShouldNot(Succeed()) + }) + + When("PreventDelete is set", func() { + BeforeEach(func(ctx SpecContext) { + By("setting preventDelete on the resource") + stream.Spec.PreventDelete = true + Expect(k8sClient.Update(ctx, stream)).To(Succeed()) + }) + It("Should delete the resource and not delete the nats stream", func(ctx SpecContext) { + By("reconciling") + result, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName}) + Expect(err).NotTo(HaveOccurred()) + Expect(result.IsZero()).To(BeTrue()) + + By("checking that the stream is not deleted") + _, err = jsClient.Stream(ctx, streamName) + Expect(err).NotTo(HaveOccurred()) + + By("checking that the resource is deleted") + Eventually(k8sClient.Get). + WithArguments(ctx, typeNamespacedName, stream). + ShouldNot(Succeed()) + }) + }) + + When("read only is set", func() { + BeforeEach(func(ctx SpecContext) { + By("setting read only on the controller") + readOnly, err := NewJSController(k8sClient, &NatsConfig{ServerURL: testServer.ClientURL()}, &Config{ReadOnly: true}) + Expect(err).NotTo(HaveOccurred()) + controller = &StreamReconciler{ + JetStreamController: readOnly, + } + }) + It("should delete the resource and not delete the stream", func(ctx SpecContext) { + + By("reconciling") + result, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName}) + Expect(err).NotTo(HaveOccurred()) + Expect(result.IsZero()).To(BeTrue()) + + By("checking that the stream is not deleted") + _, err = jsClient.Stream(ctx, streamName) + Expect(err).NotTo(HaveOccurred()) + + By("checking that the resource is deleted") + Eventually(k8sClient.Get). + WithArguments(ctx, typeNamespacedName, stream). + ShouldNot(Succeed()) + }) + }) + + When("controller is restricted to different namespace", func() { + BeforeEach(func(ctx SpecContext) { + namespaced, err := NewJSController(k8sClient, &NatsConfig{ServerURL: testServer.ClientURL()}, &Config{Namespace: "other-namespace"}) + Expect(err).NotTo(HaveOccurred()) + controller = &StreamReconciler{ + JetStreamController: namespaced, + } + }) + It("should not delete the resource and stream", func(ctx SpecContext) { + + By("reconciling") + result, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName}) + Expect(err).NotTo(HaveOccurred()) + Expect(result.IsZero()).To(BeTrue()) + + By("checking that the stream is not deleted") + _, err = jsClient.Stream(ctx, streamName) + Expect(err).NotTo(HaveOccurred()) + + By("checking that the finalizer is not removed") + Expect(k8sClient.Get(ctx, typeNamespacedName, stream)).To(Succeed()) + Expect(stream.Finalizers).To(ContainElement(streamFinalizer)) + }) + }) + }) + }) + + It("should update stream on different server as specified in spec", func(ctx SpecContext) { + By("setting up the alternative server") + // Setup altClient for alternate server + altServer := CreateTestServer() + defer altServer.Shutdown() + + By("setting the server in the stream spec") + stream.Spec.Servers = []string{altServer.ClientURL()} + Expect(k8sClient.Update(ctx, stream)).To(Succeed()) + + By("checking precondition, that the stream does not yet exist") + got, err := jsClient.Stream(ctx, streamName) + Expect(err).To(MatchError(jetstream.ErrStreamNotFound)) + + By("reconciling the resource") + result, err := controller.Reconcile(ctx, reconcile.Request{ + NamespacedName: typeNamespacedName, + }) + Expect(err).NotTo(HaveOccurred()) + Expect(result.IsZero()).To(BeTrue()) + + By("checking if the stream was created on the alternative server") + altClient, closer, err := CreateJetStreamClient(&NatsConfig{ServerURL: altServer.ClientURL()}, true) + defer closer.Close() + Expect(err).NotTo(HaveOccurred()) + + got, err = altClient.Stream(ctx, streamName) + Expect(err).NotTo(HaveOccurred()) + Expect(got.CachedInfo().Created).To(BeTemporally("~", time.Now(), time.Second)) + + By("checking that the stream was NOT created on the original server") + _, err = jsClient.Stream(ctx, streamName) + Expect(err).To(MatchError(jetstream.ErrStreamNotFound)) + }) }) }) + +func Test_mapSpecToConfig(t *testing.T) { + + date := time.Date(2024, 12, 03, 16, 55, 5, 0, time.UTC) + dateString := date.Format(time.RFC3339) + + tests := []struct { + name string + spec *api.StreamSpec + want jetstream.StreamConfig + wantErr bool + }{ + { + name: "emtpy spec", + spec: &api.StreamSpec{}, + want: jetstream.StreamConfig{}, + wantErr: false, + }, + { + name: "full spec", + spec: &api.StreamSpec{ + Account: "", + AllowDirect: true, + AllowRollup: true, + Creds: "", + DenyDelete: true, + DenyPurge: true, + Description: "stream description", + DiscardPerSubject: true, + PreventDelete: false, + PreventUpdate: false, + Discard: "new", + DuplicateWindow: "5s", + MaxAge: "30s", + MaxBytes: -1, + MaxConsumers: -1, + MaxMsgs: -1, + MaxMsgSize: -1, + MaxMsgsPerSubject: 10, + Mirror: &api.StreamSource{ + Name: "mirror", + OptStartSeq: 5, + OptStartTime: dateString, + FilterSubject: "orders", + ExternalAPIPrefix: "api", + ExternalDeliverPrefix: "deliver", + SubjectTransforms: []*api.SubjectTransform{{ + Source: "transform-source", + Dest: "transform-dest", + }}, + }, + Name: "stream-name", + Nkey: "", + NoAck: true, + Placement: &api.StreamPlacement{ + Cluster: "test-cluster", + Tags: []string{"tag"}, + }, + Replicas: 3, + Republish: &api.RePublish{ + Source: "re-publish-source", + Destination: "re-publish-dest", + HeadersOnly: true, + }, + SubjectTransform: &api.SubjectTransform{ + Source: "transform-source", + Dest: "transform-dest", + }, + FirstSequence: 42, + Compression: "s2", + Metadata: map[string]string{ + "meta": "data", + }, + Retention: "interest", + Servers: nil, + Sources: []*api.StreamSource{{ + Name: "source", + OptStartSeq: 5, + OptStartTime: dateString, + FilterSubject: "orders", + ExternalAPIPrefix: "api", + ExternalDeliverPrefix: "deliver", + SubjectTransforms: []*api.SubjectTransform{{ + Source: "transform-source", + Dest: "transform-dest", + }}, + }}, + Storage: "file", + Subjects: []string{"orders.*"}, + TLS: api.TLS{}, + }, + want: jetstream.StreamConfig{ + Name: "stream-name", + Description: "stream description", + Subjects: []string{"orders.*"}, + Retention: jetstream.InterestPolicy, + MaxConsumers: -1, + MaxMsgs: -1, + MaxBytes: -1, + Discard: jetstream.DiscardNew, + DiscardNewPerSubject: true, + MaxAge: time.Second * 30, + MaxMsgsPerSubject: 10, + MaxMsgSize: -1, + Storage: jetstream.FileStorage, + Replicas: 3, + NoAck: true, + Duplicates: time.Second * 5, + Placement: &jetstream.Placement{ + Cluster: "test-cluster", + Tags: []string{"tag"}, + }, + Mirror: &jetstream.StreamSource{ + Name: "mirror", + OptStartSeq: 5, + OptStartTime: &date, + FilterSubject: "orders", + SubjectTransforms: []jetstream.SubjectTransformConfig{{ + Source: "transform-source", + Destination: "transform-dest", + }}, + External: &jetstream.ExternalStream{ + APIPrefix: "api", + DeliverPrefix: "deliver", + }, + Domain: "", + }, + Sources: []*jetstream.StreamSource{{ + Name: "source", + OptStartSeq: 5, + OptStartTime: &date, + FilterSubject: "orders", + SubjectTransforms: []jetstream.SubjectTransformConfig{{ + Source: "transform-source", + Destination: "transform-dest", + }}, + External: &jetstream.ExternalStream{ + APIPrefix: "api", + DeliverPrefix: "deliver", + }, + Domain: "", + }}, + Sealed: false, + DenyDelete: true, + DenyPurge: true, + AllowRollup: true, + Compression: jetstream.S2Compression, + FirstSeq: 42, + SubjectTransform: &jetstream.SubjectTransformConfig{ + Source: "transform-source", + Destination: "transform-dest", + }, + RePublish: &jetstream.RePublish{ + Source: "re-publish-source", + Destination: "re-publish-dest", + HeadersOnly: true, + }, + AllowDirect: true, + MirrorDirect: false, + ConsumerLimits: jetstream.StreamConsumerLimits{}, + Metadata: map[string]string{ + "meta": "data", + }, + Template: "", + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert := assert.New(t) + got, err := streamSpecToConfig(tt.spec) + if (err != nil) != tt.wantErr { + t.Errorf("streamSpecToConfig() error = %v, wantErr %v", err, tt.wantErr) + return + } + + // Compare nested structs + assert.EqualValues(tt.want, got) + }) + } +} diff --git a/internal/controller/suite_test.go b/internal/controller/suite_test.go index 2dac16e2..dc7a59b9 100644 --- a/internal/controller/suite_test.go +++ b/internal/controller/suite_test.go @@ -19,7 +19,6 @@ package controller import ( "fmt" "github.com/nats-io/nats-server/v2/server" - natsserver "github.com/nats-io/nats-server/v2/test" "github.com/nats-io/nats.go/jetstream" "os" "path/filepath" @@ -47,6 +46,7 @@ var k8sClient client.Client var testEnv *envtest.Environment var testServer *server.Server var jsClient jetstream.JetStream +var baseController JetStreamController func TestControllers(t *testing.T) { RegisterFailHandler(Fail) @@ -86,13 +86,11 @@ var _ = BeforeSuite(func() { By("bootstrapping the test server") testServer = CreateTestServer() - jsClient, err = CreateJetStreamClient( - &NatsConfig{ServerURL: testServer.ClientURL()}, - true, - ) Expect(err).NotTo(HaveOccurred()) - Expect(jsClient).NotTo(BeNil()) + testNatsConfig := &NatsConfig{ServerURL: testServer.ClientURL()} + baseController, err = NewJSController(k8sClient, testNatsConfig, &Config{}) + jsClient, _, err = CreateJetStreamClient(testNatsConfig, true) }) var _ = AfterSuite(func() { @@ -106,19 +104,3 @@ var _ = AfterSuite(func() { err = os.RemoveAll(storeDir) Expect(err).NotTo(HaveOccurred()) }) - -func CreateTestServer() *server.Server { - opts := &natsserver.DefaultTestOptions - opts.JetStream = true - opts.Port = -1 - opts.Debug = true - - dir, err := os.MkdirTemp("", "nats-*") - Expect(err).NotTo(HaveOccurred()) - opts.StoreDir = dir - - ns := natsserver.RunServer(opts) - Expect(err).NotTo(HaveOccurred()) - - return ns -} diff --git a/internal/controller/types.go b/internal/controller/types.go new file mode 100644 index 00000000..f48d3c4b --- /dev/null +++ b/internal/controller/types.go @@ -0,0 +1,6 @@ +package controller + +const ( + readyCondType = "Ready" + streamFinalizer = "stream.nats.io/finalizer" +) From c353ccca0b71a62ca0cf361dc914f67d1bf2671c Mon Sep 17 00:00:00 2001 From: Adrian <59565247+adriandieter@users.noreply.github.com> Date: Tue, 31 Dec 2024 13:13:41 +0100 Subject: [PATCH 04/19] feat(controller-runtime): Add consumer controller (#212) * implement consumerSpecToConfig * implement consumer resource initialization * implement consumer update/creation * implement preventUpdate, readonly and namespace restrictions Checks for the PreventUpdate or readonly mode during creation/update. Skips reconciliation when resource is in namespace not matching restriction. * test consumer creation on alternative server * implement consumer deletion * handle deletion when the underlying stream was deleted * add missing GenerationChanged event filter to consumerReconciler * update logging Set streamName and consumerName fields once. Reword log messages. --- internal/controller/consumer_controller.go | 265 ++++++- .../controller/consumer_controller_test.go | 651 +++++++++++++++++- internal/controller/types.go | 5 +- 3 files changed, 888 insertions(+), 33 deletions(-) diff --git a/internal/controller/consumer_controller.go b/internal/controller/consumer_controller.go index 22905065..ba7ebb0d 100644 --- a/internal/controller/consumer_controller.go +++ b/internal/controller/consumer_controller.go @@ -18,9 +18,18 @@ package controller import ( "context" + "errors" + "fmt" + "github.com/go-logr/logr" + "github.com/nats-io/nats.go/jetstream" + v1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/klog/v2" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + "sigs.k8s.io/controller-runtime/pkg/predicate" + "time" - jetstreamnatsiov1beta2 "github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2" + api "github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2" ctrl "sigs.k8s.io/controller-runtime" ) @@ -36,14 +45,264 @@ type ConsumerReconciler struct { // - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.17.3/pkg/reconcile func (r *ConsumerReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { log := klog.FromContext(ctx) - log.Info("reconcile", "namespace", req.Namespace, "name", req.Name) + if ok := r.ValidNamespace(req.Namespace); !ok { + log.Info("Controller restricted to namespace, skipping reconciliation.") + return ctrl.Result{}, nil + } + + // Fetch consumer resource + consumer := &api.Consumer{} + if err := r.Get(ctx, req.NamespacedName, consumer); err != nil { + if apierrors.IsNotFound(err) { + log.Info("Consumer resource not found. Ignoring since object must be deleted.") + return ctrl.Result{}, nil + } + return ctrl.Result{}, fmt.Errorf("get consumer resource '%s': %w", req.NamespacedName.String(), err) + } + + log = log.WithValues( + "streamName", consumer.Spec.StreamName, + "consumerName", consumer.Spec.DurableName, + ) + + // Update ready status to unknown when no status is set + if consumer.Status.Conditions == nil || len(consumer.Status.Conditions) == 0 { + log.Info("Setting initial ready condition to unknown.") + consumer.Status.Conditions = updateReadyCondition(consumer.Status.Conditions, v1.ConditionUnknown, "Reconciling", "Starting reconciliation") + err := r.Status().Update(ctx, consumer) + if err != nil { + return ctrl.Result{}, fmt.Errorf("set condition unknown: %w", err) + } + return ctrl.Result{Requeue: true}, nil + } + + // Add finalizer + if !controllerutil.ContainsFinalizer(consumer, consumerFinalizer) { + log.Info("Adding consumer finalizer.") + if ok := controllerutil.AddFinalizer(consumer, consumerFinalizer); !ok { + return ctrl.Result{}, errors.New("failed to add finalizer to consumer resource") + } + + if err := r.Update(ctx, consumer); err != nil { + return ctrl.Result{}, fmt.Errorf("update consumer resource to add finalizer: %w", err) + } + return ctrl.Result{}, nil + } + + // Check Deletion + markedForDeletion := consumer.GetDeletionTimestamp() != nil + if markedForDeletion { + if controllerutil.ContainsFinalizer(consumer, consumerFinalizer) { + err := r.deleteConsumer(ctx, log, consumer) + if err != nil { + return ctrl.Result{}, fmt.Errorf("delete consumer: %w", err) + } + } else { + log.Info("Consumer marked for deletion and already finalized. Ignoring.") + } + + return ctrl.Result{}, nil + } + + // Create or update stream + if err := r.createOrUpdate(ctx, log, consumer); err != nil { + return ctrl.Result{}, fmt.Errorf("create or update: %s", err) + } return ctrl.Result{}, nil } +func (r *ConsumerReconciler) deleteConsumer(ctx context.Context, log logr.Logger, consumer *api.Consumer) error { + + // Set status to not false + consumer.Status.Conditions = updateReadyCondition(consumer.Status.Conditions, v1.ConditionFalse, "Finalizing", "Performing finalizer operations.") + if err := r.Status().Update(ctx, consumer); err != nil { + return fmt.Errorf("update ready condition: %w", err) + } + + if !consumer.Spec.PreventDelete && !r.ReadOnly() { + err := r.WithJetStreamClient(consumerConnOpts(consumer.Spec), func(js jetstream.JetStream) error { + return js.DeleteConsumer(ctx, consumer.Spec.StreamName, consumer.Spec.DurableName) + }) + switch { + case errors.Is(err, jetstream.ErrConsumerNotFound): + log.Info("Consumer does not exist. Unable to delete.") + case errors.Is(err, jetstream.ErrStreamNotFound): + log.Info("Stream of consumer does not exist. Unable to delete.") + case err != nil: + return fmt.Errorf("delete jetstream consumer: %w", err) + default: + log.Info("Consumer deleted.") + } + } else { + log.Info("Skipping consumer deletion.", + "consumerName", consumer.Spec.DurableName, + "preventDelete", consumer.Spec.PreventDelete, + "read-only", r.ReadOnly(), + ) + } + + log.Info("Removing consumer finalizer.") + if ok := controllerutil.RemoveFinalizer(consumer, consumerFinalizer); !ok { + return errors.New("failed to remove consumer finalizer") + } + if err := r.Update(ctx, consumer); err != nil { + return fmt.Errorf("remove finalizer: %w", err) + } + + return nil +} + +func (r *ConsumerReconciler) createOrUpdate(ctx context.Context, log klog.Logger, consumer *api.Consumer) error { + + // Create or Update the stream based on the spec + if consumer.Spec.PreventUpdate || r.ReadOnly() { + log.Info("Skipping consumer creation or update.", + "preventDelete", consumer.Spec.PreventDelete, + "read-only", r.ReadOnly(), + ) + return nil + } + + // Map spec to consumer target config + targetConfig, err := consumerSpecToConfig(&consumer.Spec) + if err != nil { + return fmt.Errorf("map consumer spec to target config: %w", err) + } + + err = r.WithJetStreamClient(consumerConnOpts(consumer.Spec), func(js jetstream.JetStream) error { + log.Info("Consumer created or updated.") + _, err := js.CreateOrUpdateConsumer(ctx, consumer.Spec.StreamName, *targetConfig) + return err + }) + if err != nil { + err = fmt.Errorf("create or update consumer: %w", err) + consumer.Status.Conditions = updateReadyCondition(consumer.Status.Conditions, v1.ConditionFalse, "Errored", err.Error()) + if err := r.Status().Update(ctx, consumer); err != nil { + log.Error(err, "Failed to update ready condition to Errored.") + } + return err + } + + // update the observed generation and ready status + consumer.Status.ObservedGeneration = consumer.Generation + consumer.Status.Conditions = updateReadyCondition( + consumer.Status.Conditions, + v1.ConditionTrue, + "Reconciling", + "Consumer successfully created or updated.", + ) + err = r.Status().Update(ctx, consumer) + if err != nil { + return fmt.Errorf("update ready condition: %w", err) + } + + return nil +} + +func consumerConnOpts(spec api.ConsumerSpec) *connectionOptions { + return &connectionOptions{ + Account: spec.Account, + Creds: spec.Creds, + Nkey: spec.Nkey, + Servers: spec.Servers, + TLS: spec.TLS, + } +} + +func consumerSpecToConfig(spec *api.ConsumerSpec) (*jetstream.ConsumerConfig, error) { + + config := &jetstream.ConsumerConfig{ + Durable: spec.DurableName, + Description: spec.Description, + OptStartSeq: uint64(spec.OptStartSeq), + MaxDeliver: spec.MaxDeliver, + FilterSubject: spec.FilterSubject, + RateLimit: uint64(spec.RateLimitBps), + SampleFrequency: spec.SampleFreq, + MaxWaiting: spec.MaxWaiting, + MaxAckPending: spec.MaxAckPending, + HeadersOnly: spec.HeadersOnly, + MaxRequestBatch: spec.MaxRequestBatch, + MaxRequestMaxBytes: spec.MaxRequestMaxBytes, + Replicas: spec.Replicas, + MemoryStorage: spec.MemStorage, + FilterSubjects: spec.FilterSubjects, + Metadata: spec.Metadata, + + // Explicitly set not (yet) mapped fields + Name: "", + InactiveThreshold: 0, + } + + // DeliverPolicy + if spec.DeliverPolicy != "" { + err := config.DeliverPolicy.UnmarshalJSON(asJsonString(spec.DeliverPolicy)) + if err != nil { + return nil, fmt.Errorf("invalid delivery policy: %w", err) + } + } + + // OptStartTime RFC3339 + if spec.OptStartTime != "" { + t, err := time.Parse(time.RFC3339, spec.OptStartTime) + if err != nil { + return nil, fmt.Errorf("invalid opt start time: %w", err) + } + config.OptStartTime = &t + } + + // AckPolicy + if spec.AckPolicy != "" { + err := config.AckPolicy.UnmarshalJSON(asJsonString(spec.AckPolicy)) + if err != nil { + return nil, fmt.Errorf("invalid ack policy: %w", err) + } + } + + // AckWait + if spec.AckWait != "" { + d, err := time.ParseDuration(spec.AckWait) + if err != nil { + return nil, fmt.Errorf("invalid ack wait duration: %w", err) + } + config.AckWait = d + } + + //BackOff + for _, bo := range spec.BackOff { + d, err := time.ParseDuration(bo) + if err != nil { + return nil, fmt.Errorf("invalid backoff: %w", err) + } + + config.BackOff = append(config.BackOff, d) + } + + // ReplayPolicy + if spec.ReplayPolicy != "" { + err := config.ReplayPolicy.UnmarshalJSON(asJsonString(spec.ReplayPolicy)) + if err != nil { + return nil, fmt.Errorf("invalid replay policy: %w", err) + } + } + + // MaxRequestExpires + if spec.MaxRequestExpires != "" { + d, err := time.ParseDuration(spec.MaxRequestExpires) + if err != nil { + return nil, fmt.Errorf("invalid opt start time: %w", err) + } + config.MaxRequestExpires = d + } + + return config, nil +} + // SetupWithManager sets up the controller with the Manager. func (r *ConsumerReconciler) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). - For(&jetstreamnatsiov1beta2.Consumer{}). + For(&api.Consumer{}). + WithEventFilter(predicate.GenerationChangedPredicate{}). Complete(r) } diff --git a/internal/controller/consumer_controller_test.go b/internal/controller/consumer_controller_test.go index 6ce92bcc..62fd2d22 100644 --- a/internal/controller/consumer_controller_test.go +++ b/internal/controller/consumer_controller_test.go @@ -17,73 +17,668 @@ limitations under the License. package controller import ( - "context" - + "github.com/nats-io/nats.go/jetstream" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + "github.com/stretchr/testify/assert" + v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/errors" + k8serrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/types" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" "sigs.k8s.io/controller-runtime/pkg/reconcile" + "testing" + "time" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - jetstreamnatsiov1beta2 "github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2" + api "github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2" ) var _ = Describe("Consumer Controller", func() { Context("When reconciling a resource", func() { const resourceName = "test-resource" - ctx := context.Background() + const streamName = "orders" + const consumerName = "test-consumer" typeNamespacedName := types.NamespacedName{ Name: resourceName, Namespace: "default", // TODO(user):Modify as needed } - consumer := &jetstreamnatsiov1beta2.Consumer{} + consumer := &api.Consumer{} + + emptyStreamConfig := jetstream.StreamConfig{ + Name: streamName, + Replicas: 1, + Retention: jetstream.WorkQueuePolicy, + Discard: jetstream.DiscardOld, + Storage: jetstream.FileStorage, + } - BeforeEach(func() { + emptyConsumerConfig := jetstream.ConsumerConfig{ + Durable: consumerName, + } + + // Tested coontroller + var controller *ConsumerReconciler + + BeforeEach(func(ctx SpecContext) { By("creating the custom resource for the Kind Consumer") err := k8sClient.Get(ctx, typeNamespacedName, consumer) if err != nil && errors.IsNotFound(err) { - resource := &jetstreamnatsiov1beta2.Consumer{ + resource := &api.Consumer{ ObjectMeta: metav1.ObjectMeta{ Name: resourceName, Namespace: "default", }, - Spec: jetstreamnatsiov1beta2.ConsumerSpec{ - AckPolicy: "none", - DeliverPolicy: "new", - DurableName: "test-consumer", + Spec: api.ConsumerSpec{ + AckPolicy: "explicit", + DeliverPolicy: "all", + DurableName: consumerName, + Description: "test consumer", + StreamName: streamName, ReplayPolicy: "instant", }, - // TODO(user): Specify other spec details if needed. } Expect(k8sClient.Create(ctx, resource)).To(Succeed()) + // Fetch consumer + Expect(k8sClient.Get(ctx, typeNamespacedName, consumer)).To(Succeed()) + } + + By("creating the underlying stream") + _, err = jsClient.CreateStream(ctx, emptyStreamConfig) + Expect(err).ToNot(HaveOccurred()) + + By("setting up the tested controller") + controller = &ConsumerReconciler{ + baseController, } }) - AfterEach(func() { - // TODO(user): Cleanup logic after each test, like removing the resource instance. - resource := &jetstreamnatsiov1beta2.Consumer{} + AfterEach(func(ctx SpecContext) { + By("removing the consumer resource") + resource := &api.Consumer{} err := k8sClient.Get(ctx, typeNamespacedName, resource) - Expect(err).NotTo(HaveOccurred()) + if err != nil { + Expect(err).To(MatchError(k8serrors.IsNotFound, "Is not found")) + } else { + if controllerutil.ContainsFinalizer(resource, consumerFinalizer) { + By("removing the finalizer") + controllerutil.RemoveFinalizer(resource, consumerFinalizer) + Expect(k8sClient.Update(ctx, resource)).To(Succeed()) + } - By("Cleanup the specific resource instance Consumer") - Expect(k8sClient.Delete(ctx, resource)).To(Succeed()) - }) - It("should successfully reconcile the resource", func() { - By("Reconciling the created resource") - controllerReconciler := &ConsumerReconciler{ - baseController, + By("removing the consumer resource") + Expect(k8sClient.Delete(ctx, resource)). + To(SatisfyAny( + Succeed(), + MatchError(k8serrors.IsNotFound, "is not found"), + )) } - _, err := controllerReconciler.Reconcile(ctx, reconcile.Request{ - NamespacedName: typeNamespacedName, + By("deleting the nats consumer") + Expect(jsClient.DeleteConsumer(ctx, streamName, consumerName)). + To(SatisfyAny( + Succeed(), + MatchError(jetstream.ErrStreamNotFound), + MatchError(jetstream.ErrConsumerNotFound), + )) + + By("deleting the consumers nats stream") + Expect(jsClient.DeleteStream(ctx, streamName)). + To(SatisfyAny( + Succeed(), + MatchError(jetstream.ErrStreamNotFound), + )) + }) + + When("reconciling a not existing resource", func() { + It("should stop reconciliation without error", func(ctx SpecContext) { + By("reconciling the created resource") + result, err := controller.Reconcile(ctx, reconcile.Request{ + NamespacedName: types.NamespacedName{ + Namespace: "fake", + Name: "not-existing", + }, + }) + Expect(err).NotTo(HaveOccurred()) + Expect(result).To(Equal(ctrl.Result{})) + }) + }) + + When("reconciling a not initialized resource", func() { + + It("should initialize a new resource", func(ctx SpecContext) { + + By("re-queueing until it is initialized") + // Initialization can require multiple reconciliation loops + Eventually(func(ctx SpecContext) *api.Consumer { + _, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName}) + Expect(err).NotTo(HaveOccurred()) + got := &api.Consumer{} + Expect(k8sClient.Get(ctx, typeNamespacedName, got)).To(Succeed()) + return got + }).WithContext(ctx). + Within(time.Second). + Should(SatisfyAll( + HaveField("Finalizers", HaveExactElements(consumerFinalizer)), + HaveField("Status.Conditions", Not(BeEmpty())), + )) + + By("validating the ready condition") + // Fetch consumer + Expect(k8sClient.Get(ctx, typeNamespacedName, consumer)).To(Succeed()) + Expect(consumer.Status.Conditions).To(HaveLen(1)) + + assertReadyStateMatches(consumer.Status.Conditions[0], v1.ConditionUnknown, "Reconciling", "Starting reconciliation", time.Now()) + }) + }) + + When("reconciling an initialized resource", func() { + + BeforeEach(func(ctx SpecContext) { + By("initializing the stream resource") + + By("setting the finalizer") + Expect(controllerutil.AddFinalizer(consumer, consumerFinalizer)).To(BeTrue()) + Expect(k8sClient.Update(ctx, consumer)).To(Succeed()) + + By("setting an unknown ready state") + consumer.Status.Conditions = []api.Condition{{ + Type: readyCondType, + Status: v1.ConditionUnknown, + Reason: "Test", + Message: "start condition", + LastTransitionTime: time.Now().Format(time.RFC3339Nano), + }} + Expect(k8sClient.Status().Update(ctx, consumer)).To(Succeed()) + Expect(k8sClient.Get(ctx, typeNamespacedName, consumer)).To(Succeed()) + }) + + When("the underlying stream does not exist", func() { + It("should set false ready state and error", func(ctx SpecContext) { + By("setting a not existing stream on the resource") + consumer.Spec.StreamName = "not-existing" + Expect(k8sClient.Update(ctx, consumer)).To(Succeed()) + + By("running Reconcile") + result, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName}) + Expect(err).To(HaveOccurred()) + Expect(result.IsZero()).To(BeTrue()) + + By("checking for expected ready state") + Expect(k8sClient.Get(ctx, typeNamespacedName, consumer)).To(Succeed()) + Expect(consumer.Status.Conditions).To(HaveLen(1)) + assertReadyStateMatches( + consumer.Status.Conditions[0], + v1.ConditionFalse, + "Errored", + "stream", // Not existing stream as message + time.Now(), + ) + }) + }) + + It("should create a new consumer", func(ctx SpecContext) { + + By("running Reconcile") + result, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName}) + Expect(err).NotTo(HaveOccurred()) + Expect(result.IsZero()).To(BeTrue()) + + // Fetch resource + Expect(k8sClient.Get(ctx, typeNamespacedName, consumer)).To(Succeed()) + + By("checking if the ready state was updated") + Expect(consumer.Status.Conditions).To(HaveLen(1)) + assertReadyStateMatches(consumer.Status.Conditions[0], v1.ConditionTrue, "Reconciling", "created or updated", time.Now()) + + By("checking if the observed generation matches") + Expect(consumer.Status.ObservedGeneration).To(Equal(consumer.Generation)) + + By("checking if the consumer was created") + natsconsumer, err := jsClient.Consumer(ctx, streamName, consumerName) + Expect(err).NotTo(HaveOccurred()) + consumerInfo, err := natsconsumer.Info(ctx) + Expect(err).NotTo(HaveOccurred()) + Expect(consumerInfo.Config.Name).To(Equal(consumerName)) + Expect(consumerInfo.Config.Description).To(Equal("test consumer")) + Expect(consumerInfo.Created).To(BeTemporally("~", time.Now(), time.Second)) + }) + + It("should update an existing consumer", func(ctx SpecContext) { + + By("reconciling once to create the consumer") + result, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName}) + Expect(err).NotTo(HaveOccurred()) + Expect(result.IsZero()).To(BeTrue()) + + // Fetch resource + Expect(k8sClient.Get(ctx, typeNamespacedName, consumer)).To(Succeed()) + previousTransitionTime := consumer.Status.Conditions[0].LastTransitionTime + + By("updating the resource") + consumer.Spec.Description = "new description" + Expect(k8sClient.Update(ctx, consumer)).To(Succeed()) + + By("reconciling the updated resource") + result, err = controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName}) + Expect(err).NotTo(HaveOccurred()) + Expect(result.IsZero()).To(BeTrue()) + + // Fetch resource + Expect(k8sClient.Get(ctx, typeNamespacedName, consumer)).To(Succeed()) + + By("checking if the state transition time was not updated") + Expect(consumer.Status.Conditions).To(HaveLen(1)) + Expect(consumer.Status.Conditions[0].LastTransitionTime).To(Equal(previousTransitionTime)) + + By("checking if the observed generation matches") + Expect(consumer.Status.ObservedGeneration).To(Equal(consumer.Generation)) + + By("checking if the consumer was updated") + natsStream, err := jsClient.Consumer(ctx, streamName, consumerName) + Expect(err).NotTo(HaveOccurred()) + + streamInfo, err := natsStream.Info(ctx) + Expect(err).NotTo(HaveOccurred()) + Expect(streamInfo.Config.Description).To(Equal("new description")) + // Other fields unchanged + Expect(streamInfo.Config.ReplayPolicy).To(Equal(jetstream.ReplayInstantPolicy)) + }) + + When("PreventUpdate is set", func() { + + BeforeEach(func(ctx SpecContext) { + By("setting preventUpdate on the resource") + consumer.Spec.PreventUpdate = true + Expect(k8sClient.Update(ctx, consumer)).To(Succeed()) + }) + It("should not create the consumer", func(ctx SpecContext) { + By("running Reconcile") + result, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName}) + Expect(err).NotTo(HaveOccurred()) + Expect(result.IsZero()).To(BeTrue()) + + By("checking that no consumer was created") + _, err = jsClient.Consumer(ctx, streamName, consumerName) + Expect(err).To(MatchError(jetstream.ErrConsumerNotFound)) + }) + It("should not update the consumer", func(ctx SpecContext) { + By("creating the consumer") + _, err := jsClient.CreateConsumer(ctx, streamName, emptyConsumerConfig) + Expect(err).NotTo(HaveOccurred()) + + By("running Reconcile") + result, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName}) + Expect(err).NotTo(HaveOccurred()) + Expect(result.IsZero()).To(BeTrue()) + + By("checking that consumer was not updated") + c, err := jsClient.Consumer(ctx, streamName, consumerName) + Expect(c.CachedInfo().Config.Description).To(BeEmpty()) + }) + }) + + When("read-only mode is enabled", func() { + + BeforeEach(func(ctx SpecContext) { + By("setting read only on the controller") + readOnly, err := NewJSController(k8sClient, &NatsConfig{ServerURL: testServer.ClientURL()}, &Config{ReadOnly: true}) + Expect(err).NotTo(HaveOccurred()) + controller = &ConsumerReconciler{ + JetStreamController: readOnly, + } + }) + + It("should not create the consumer", func(ctx SpecContext) { + By("running Reconcile") + result, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName}) + Expect(err).NotTo(HaveOccurred()) + Expect(result.IsZero()).To(BeTrue()) + + By("checking that no consumer was created") + _, err = jsClient.Consumer(ctx, streamName, consumerName) + Expect(err).To(MatchError(jetstream.ErrConsumerNotFound)) + }) + It("should not update the consumer", func(ctx SpecContext) { + By("creating the consumer") + _, err := jsClient.CreateConsumer(ctx, streamName, emptyConsumerConfig) + Expect(err).NotTo(HaveOccurred()) + + By("running Reconcile") + result, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName}) + Expect(err).NotTo(HaveOccurred()) + Expect(result.IsZero()).To(BeTrue()) + + By("checking that consumer was not updated") + s, err := jsClient.Consumer(ctx, streamName, consumerName) + Expect(s.CachedInfo().Config.Description).To(BeEmpty()) + }) + }) + + When("namespace restriction is enabled", func() { + + BeforeEach(func(ctx SpecContext) { + By("setting a namespace on the resource") + namespaced, err := NewJSController(k8sClient, &NatsConfig{ServerURL: testServer.ClientURL()}, &Config{Namespace: "other-namespace"}) + Expect(err).NotTo(HaveOccurred()) + controller = &ConsumerReconciler{ + JetStreamController: namespaced, + } + }) + + It("should not create the consumer", func(ctx SpecContext) { + By("running Reconcile") + result, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName}) + Expect(err).NotTo(HaveOccurred()) + Expect(result.IsZero()).To(BeTrue()) + + By("checking that no consumer was created") + _, err = jsClient.Consumer(ctx, streamName, consumerName) + Expect(err).To(MatchError(jetstream.ErrConsumerNotFound)) + }) + It("should not update the consumer", func(ctx SpecContext) { + By("creating the consumer") + _, err := jsClient.CreateConsumer(ctx, streamName, emptyConsumerConfig) + Expect(err).NotTo(HaveOccurred()) + + By("running Reconcile") + result, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName}) + Expect(err).NotTo(HaveOccurred()) + Expect(result.IsZero()).To(BeTrue()) + + By("checking that consumer was not updated") + s, err := jsClient.Consumer(ctx, streamName, consumerName) + Expect(s.CachedInfo().Config.Description).To(BeEmpty()) + }) + }) + + When("the resource is marked for deletion", func() { + + BeforeEach(func(ctx SpecContext) { + By("marking the resource for deletion") + Expect(k8sClient.Delete(ctx, consumer)).To(Succeed()) + Expect(k8sClient.Get(ctx, typeNamespacedName, consumer)).To(Succeed()) // re-fetch after update + }) + + It("should succeed deleting a not existing consumer", func(ctx SpecContext) { + By("reconciling") + result, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName}) + Expect(err).NotTo(HaveOccurred()) + Expect(result.IsZero()).To(BeTrue()) + + By("checking that the resource is deleted") + Eventually(k8sClient.Get). + WithArguments(ctx, typeNamespacedName, consumer). + ShouldNot(Succeed()) + }) + + It("should succeed deleting a consumer of a deleted stream", func(ctx SpecContext) { + By("Setting not existing stream") + consumer.Spec.StreamName = "deleted-stream" + Expect(k8sClient.Update(ctx, consumer)).To(Succeed()) + + By("reconciling") + result, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName}) + Expect(err).NotTo(HaveOccurred()) + Expect(result.IsZero()).To(BeTrue()) + + By("checking that the resource is deleted") + Eventually(k8sClient.Get). + WithArguments(ctx, typeNamespacedName, consumer). + ShouldNot(Succeed()) + }) + + When("the underlying consumer exists", func() { + BeforeEach(func(ctx SpecContext) { + By("creating the consumer on the nats server") + _, err := jsClient.CreateConsumer(ctx, streamName, emptyConsumerConfig) + Expect(err).NotTo(HaveOccurred()) + }) + + AfterEach(func(ctx SpecContext) { + err := jsClient.DeleteConsumer(ctx, streamName, consumerName) + if err != nil { + Expect(err).To(MatchError(jetstream.ErrConsumerNotFound)) + } + }) + + It("should delete the consumer", func(ctx SpecContext) { + By("reconciling") + result, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName}) + Expect(err).NotTo(HaveOccurred()) + Expect(result.IsZero()).To(BeTrue()) + + By("checking that the consumer is deleted") + _, err = jsClient.Consumer(ctx, streamName, consumerName) + Expect(err).To(MatchError(jetstream.ErrConsumerNotFound)) + + By("checking that the resource is deleted") + Eventually(k8sClient.Get). + WithArguments(ctx, typeNamespacedName, consumer). + ShouldNot(Succeed()) + }) + + When("PreventDelete is set", func() { + BeforeEach(func(ctx SpecContext) { + By("setting preventDelete on the resource") + consumer.Spec.PreventDelete = true + Expect(k8sClient.Update(ctx, consumer)).To(Succeed()) + }) + It("Should delete the resource and not delete the nats consumer", func(ctx SpecContext) { + By("reconciling") + result, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName}) + Expect(err).NotTo(HaveOccurred()) + Expect(result.IsZero()).To(BeTrue()) + + By("checking that the consumer is not deleted") + _, err = jsClient.Consumer(ctx, streamName, consumerName) + Expect(err).NotTo(HaveOccurred()) + + By("checking that the resource is deleted") + Eventually(k8sClient.Get). + WithArguments(ctx, typeNamespacedName, consumer). + ShouldNot(Succeed()) + }) + }) + + When("read only is set", func() { + BeforeEach(func(ctx SpecContext) { + By("setting read only on the controller") + readOnly, err := NewJSController(k8sClient, &NatsConfig{ServerURL: testServer.ClientURL()}, &Config{ReadOnly: true}) + Expect(err).NotTo(HaveOccurred()) + controller = &ConsumerReconciler{ + JetStreamController: readOnly, + } + }) + It("should delete the resource and not delete the consumer", func(ctx SpecContext) { + + By("reconciling") + result, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName}) + Expect(err).NotTo(HaveOccurred()) + Expect(result.IsZero()).To(BeTrue()) + + By("checking that the consumer is not deleted") + _, err = jsClient.Consumer(ctx, streamName, consumerName) + Expect(err).NotTo(HaveOccurred()) + + By("checking that the resource is deleted") + Eventually(k8sClient.Get). + WithArguments(ctx, typeNamespacedName, consumer). + ShouldNot(Succeed()) + }) + }) + + When("controller is restricted to different namespace", func() { + BeforeEach(func(ctx SpecContext) { + namespaced, err := NewJSController(k8sClient, &NatsConfig{ServerURL: testServer.ClientURL()}, &Config{Namespace: "other-namespace"}) + Expect(err).NotTo(HaveOccurred()) + controller = &ConsumerReconciler{ + JetStreamController: namespaced, + } + }) + It("should not delete the resource and consumer", func(ctx SpecContext) { + + By("reconciling") + result, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName}) + Expect(err).NotTo(HaveOccurred()) + Expect(result.IsZero()).To(BeTrue()) + + By("checking that the consumer is not deleted") + _, err = jsClient.Consumer(ctx, streamName, consumerName) + Expect(err).NotTo(HaveOccurred()) + + By("checking that the finalizer is not removed") + Expect(k8sClient.Get(ctx, typeNamespacedName, consumer)).To(Succeed()) + Expect(consumer.Finalizers).To(ContainElement(consumerFinalizer)) + }) + }) + }) + }) + + It("should create consumer on different server as specified in spec", func(ctx SpecContext) { + + By("setting up the alternative server") + altServer := CreateTestServer() + defer altServer.Shutdown() + // Setup altClient for alternate server + altClient, closer, err := CreateJetStreamClient(&NatsConfig{ServerURL: altServer.ClientURL()}, true) + defer closer.Close() + Expect(err).NotTo(HaveOccurred()) + + By("setting up the stream on the alternative server") + _, err = altClient.CreateStream(ctx, emptyStreamConfig) + Expect(err).NotTo(HaveOccurred()) + + By("setting the server in the consumer spec") + consumer.Spec.Servers = []string{altServer.ClientURL()} + Expect(k8sClient.Update(ctx, consumer)).To(Succeed()) + + By("checking precondition, that the consumer does not yet exist") + got, err := jsClient.Consumer(ctx, streamName, consumerName) + Expect(err).To(MatchError(jetstream.ErrConsumerNotFound)) + + By("reconciling the resource") + result, err := controller.Reconcile(ctx, reconcile.Request{ + NamespacedName: typeNamespacedName, + }) + Expect(err).NotTo(HaveOccurred()) + Expect(result.IsZero()).To(BeTrue()) + + By("checking if the consumer was created on the alternative server") + got, err = altClient.Consumer(ctx, streamName, consumerName) + Expect(err).NotTo(HaveOccurred()) + Expect(got.CachedInfo().Created).To(BeTemporally("~", time.Now(), time.Second)) + + By("checking that the consumer was NOT created on the original server") + _, err = jsClient.Consumer(ctx, streamName, consumerName) + Expect(err).To(MatchError(jetstream.ErrConsumerNotFound)) }) - Expect(err).NotTo(HaveOccurred()) - // TODO(user): Add more specific assertions depending on your controller's reconciliation logic. - // Example: If you expect a certain status condition after reconciliation, verify it here. }) }) }) + +func Test_consumerSpecToConfig(t *testing.T) { + + date := time.Date(2024, 12, 03, 16, 55, 5, 0, time.UTC) + dateString := date.Format(time.RFC3339) + + tests := []struct { + name string + spec *api.ConsumerSpec + want *jetstream.ConsumerConfig + wantErr bool + }{ + { + name: "empty spec", + spec: &api.ConsumerSpec{}, + want: &jetstream.ConsumerConfig{}, + wantErr: false, + }, + { + name: "full spec", + spec: &api.ConsumerSpec{ + AckPolicy: "explicit", + AckWait: "10ns", + BackOff: []string{"1s", "5m"}, + Creds: "", + DeliverGroup: "", + DeliverPolicy: "new", + DeliverSubject: "", + Description: "test consumer", + PreventDelete: false, + PreventUpdate: false, + DurableName: "test-consumer", + FilterSubject: "time.us.>", + FilterSubjects: []string{"time.us.east", "time.us.west"}, + FlowControl: false, + HeadersOnly: true, + HeartbeatInterval: "", + MaxAckPending: 6, + MaxDeliver: 3, + MaxRequestBatch: 7, + MaxRequestExpires: "8s", + MaxRequestMaxBytes: 1024, + MaxWaiting: 5, + MemStorage: true, + Nkey: "", + OptStartSeq: 17, + OptStartTime: dateString, + RateLimitBps: 512, + ReplayPolicy: "instant", + Replicas: 9, + SampleFreq: "25%", + Servers: nil, + StreamName: "", + TLS: api.TLS{}, + Account: "", + Metadata: map[string]string{ + "meta": "data", + }, + }, + want: &jetstream.ConsumerConfig{ + Name: "", // Optional, not mapped + Durable: "test-consumer", + Description: "test consumer", + DeliverPolicy: jetstream.DeliverNewPolicy, + OptStartSeq: 17, + OptStartTime: &date, + AckPolicy: jetstream.AckExplicitPolicy, + AckWait: 10 * time.Nanosecond, + MaxDeliver: 3, + BackOff: []time.Duration{time.Second, 5 * time.Minute}, + FilterSubject: "time.us.>", + ReplayPolicy: jetstream.ReplayInstantPolicy, + RateLimit: 512, + SampleFrequency: "25%", + MaxWaiting: 5, + MaxAckPending: 6, + HeadersOnly: true, + MaxRequestBatch: 7, + MaxRequestExpires: 8 * time.Second, + MaxRequestMaxBytes: 1024, + InactiveThreshold: 0, // TODO no value? + Replicas: 9, + MemoryStorage: true, + FilterSubjects: []string{"time.us.east", "time.us.west"}, + Metadata: map[string]string{ + "meta": "data", + }, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := consumerSpecToConfig(tt.spec) + if (err != nil) != tt.wantErr { + t.Errorf("consumerSpecToConfig() error = %v, wantErr %v", err, tt.wantErr) + return + } + assert.EqualValues(t, tt.want, got, "consumerSpecToConfig(%v)", tt.spec) + }) + } +} diff --git a/internal/controller/types.go b/internal/controller/types.go index f48d3c4b..de166267 100644 --- a/internal/controller/types.go +++ b/internal/controller/types.go @@ -1,6 +1,7 @@ package controller const ( - readyCondType = "Ready" - streamFinalizer = "stream.nats.io/finalizer" + readyCondType = "Ready" + streamFinalizer = "stream.nats.io/finalizer" + consumerFinalizer = "consumer.nats.io/finalizer" ) From 5bd094581935da416fa6a0f09267ca848edb48c6 Mon Sep 17 00:00:00 2001 From: Samuel Attwood <45669855+samuelattwood@users.noreply.github.com> Date: Tue, 7 Jan 2025 01:43:46 -0500 Subject: [PATCH 05/19] feat(controller-runtime): Add keyvalue store spec and controller (#215) * Bump helm/kind-action from 1.10.0 to 1.11.0 (#213) Bumps [helm/kind-action](https://github.com/helm/kind-action) from 1.10.0 to 1.11.0. - [Release notes](https://github.com/helm/kind-action/releases) - [Commits](https://github.com/helm/kind-action/compare/v1.10.0...v1.11.0) --- updated-dependencies: - dependency-name: helm/kind-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Bump helm/kind-action from 1.11.0 to 1.12.0 (#214) Bumps [helm/kind-action](https://github.com/helm/kind-action) from 1.11.0 to 1.12.0. - [Release notes](https://github.com/helm/kind-action/releases) - [Commits](https://github.com/helm/kind-action/compare/v1.11.0...v1.12.0) --- updated-dependencies: - dependency-name: helm/kind-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * * Formatting * Add initial definitions for KeyValue store * Deps * Fix test * Add KeyValue controller * Add KeyValue tests * Update PreventUpdate behavior * Minor error handling change --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Makefile | 4 +- cmd/jetstream-controller/main.go | 11 +- controllers/jetstream/consumer.go | 4 +- controllers/jetstream/controller.go | 22 +- controllers/jetstream/controller_test.go | 8 +- controllers/jetstream/stream.go | 11 +- deploy/crds.yml | 211 ++++++ deploy/rbac.yml | 3 + go.mod | 72 +- go.sum | 136 ++-- internal/controller/account_controller.go | 1 + internal/controller/client.go | 4 +- internal/controller/consumer_controller.go | 46 +- .../controller/consumer_controller_test.go | 36 +- internal/controller/helpers_test.go | 6 +- internal/controller/jetstream_controller.go | 9 +- .../controller/jetstream_controller_test.go | 6 +- internal/controller/keyvalue_controller.go | 312 ++++++++ .../controller/keyvalue_controller_test.go | 699 ++++++++++++++++++ internal/controller/register.go | 2 +- internal/controller/stream_controller.go | 43 +- internal/controller/stream_controller_test.go | 36 +- internal/controller/suite_test.go | 23 +- internal/controller/types.go | 1 + pkg/bootconfig/bootconfig.go | 4 +- .../v1beta1/zz_generated.deepcopy.go | 2 +- .../apis/jetstream/v1beta2/keyvaluetypes.go | 55 ++ .../apis/jetstream/v1beta2/register.go | 2 + .../v1beta2/zz_generated.deepcopy.go | 111 ++- .../applyconfiguration/internal/internal.go | 6 +- .../jetstream/v1beta2/account.go | 42 +- .../jetstream/v1beta2/accountspec.go | 2 +- .../jetstream/v1beta2/condition.go | 2 +- .../jetstream/v1beta2/consumer.go | 42 +- .../jetstream/v1beta2/consumerspec.go | 2 +- .../jetstream/v1beta2/credssecret.go | 2 +- .../jetstream/v1beta2/keyvalue.go | 222 ++++++ .../jetstream/v1beta2/keyvaluespec.go | 218 ++++++ .../jetstream/v1beta2/republish.go | 2 +- .../jetstream/v1beta2/secretref.go | 2 +- .../jetstream/v1beta2/status.go | 2 +- .../jetstream/v1beta2/stream.go | 42 +- .../jetstream/v1beta2/streamplacement.go | 2 +- .../jetstream/v1beta2/streamsource.go | 20 +- .../jetstream/v1beta2/streamspec.go | 2 +- .../jetstream/v1beta2/subjecttransform.go | 2 +- .../jetstream/v1beta2/tls.go | 2 +- .../jetstream/v1beta2/tlssecret.go | 2 +- .../generated/applyconfiguration/utils.go | 6 +- .../clientset/versioned/clientset.go | 6 +- .../versioned/fake/clientset_generated.go | 2 +- .../generated/clientset/versioned/fake/doc.go | 2 +- .../clientset/versioned/fake/register.go | 2 +- .../clientset/versioned/scheme/doc.go | 2 +- .../clientset/versioned/scheme/register.go | 2 +- .../typed/jetstream/v1beta2/account.go | 33 +- .../typed/jetstream/v1beta2/consumer.go | 33 +- .../versioned/typed/jetstream/v1beta2/doc.go | 2 +- .../typed/jetstream/v1beta2/fake/doc.go | 2 +- .../jetstream/v1beta2/fake/fake_account.go | 190 +---- .../jetstream/v1beta2/fake/fake_consumer.go | 190 +---- .../v1beta2/fake/fake_jetstream_client.go | 12 +- .../jetstream/v1beta2/fake/fake_keyvalue.go | 48 ++ .../jetstream/v1beta2/fake/fake_stream.go | 188 +---- .../jetstream/v1beta2/generated_expansion.go | 4 +- .../jetstream/v1beta2/jetstream_client.go | 17 +- .../typed/jetstream/v1beta2/keyvalue.go | 71 ++ .../typed/jetstream/v1beta2/stream.go | 33 +- .../informers/externalversions/factory.go | 2 +- .../informers/externalversions/generic.go | 6 +- .../internalinterfaces/factory_interfaces.go | 2 +- .../externalversions/jetstream/interface.go | 2 +- .../jetstream/v1beta2/account.go | 18 +- .../jetstream/v1beta2/consumer.go | 18 +- .../jetstream/v1beta2/interface.go | 9 +- .../jetstream/v1beta2/keyvalue.go | 87 +++ .../jetstream/v1beta2/stream.go | 18 +- .../listers/jetstream/v1beta2/account.go | 24 +- .../listers/jetstream/v1beta2/consumer.go | 24 +- .../jetstream/v1beta2/expansion_generated.go | 10 +- .../listers/jetstream/v1beta2/keyvalue.go | 67 ++ .../listers/jetstream/v1beta2/stream.go | 24 +- pkg/k8scodegen/file-header.txt | 2 +- pkg/natsreloader/natsreloader_test.go | 7 +- 84 files changed, 2690 insertions(+), 971 deletions(-) create mode 100644 internal/controller/keyvalue_controller.go create mode 100644 internal/controller/keyvalue_controller_test.go create mode 100644 pkg/jetstream/apis/jetstream/v1beta2/keyvaluetypes.go create mode 100644 pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/keyvalue.go create mode 100644 pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/keyvaluespec.go create mode 100644 pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/fake/fake_keyvalue.go create mode 100644 pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/keyvalue.go create mode 100644 pkg/jetstream/generated/informers/externalversions/jetstream/v1beta2/keyvalue.go create mode 100644 pkg/jetstream/generated/listers/jetstream/v1beta2/keyvalue.go diff --git a/Makefile b/Makefile index 2046da66..be80ba25 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ export GO111MODULE := on SHELL=/usr/bin/env bash -ENVTEST_K8S_VERSION = 1.29.0 +ENVTEST_K8S_VERSION = 1.31.0 now := $(shell date -u +%Y-%m-%dT%H:%M:%S%z) gitBranch := $(shell git rev-parse --abbrev-ref HEAD) @@ -197,7 +197,7 @@ mv "$$(echo "$(1)" | sed "s/-$(3)$$//")" $(1) ;\ endef ENVTEST ?= $(LOCALBIN)/setup-envtest-$(ENVTEST_VERSION) -ENVTEST_VERSION ?= release-0.17 +ENVTEST_VERSION ?= release-0.19 .PHONY: envtest envtest: $(ENVTEST) ## Download setup-envtest locally if necessary. diff --git a/cmd/jetstream-controller/main.go b/cmd/jetstream-controller/main.go index 62638feb..32678f32 100644 --- a/cmd/jetstream-controller/main.go +++ b/cmd/jetstream-controller/main.go @@ -18,6 +18,11 @@ import ( "errors" "flag" "fmt" + "os" + "os/signal" + "syscall" + "time" + "github.com/nats-io/nack/controllers/jetstream" "github.com/nats-io/nack/internal/controller" jetstreamnatsiov1beta2 "github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2" @@ -29,12 +34,9 @@ import ( clientgoscheme "k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/rest" klog "k8s.io/klog/v2" - "os" - "os/signal" + ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/healthz" - "syscall" - "time" ) var ( @@ -155,7 +157,6 @@ func run() error { } func runControlLoop(config *rest.Config, natsCfg *controller.NatsConfig, controllerCfg *controller.Config) error { - // Setup scheme scheme := runtime.NewScheme() utilruntime.Must(clientgoscheme.AddToScheme(scheme)) diff --git a/controllers/jetstream/consumer.go b/controllers/jetstream/consumer.go index 347bd102..01ffc726 100644 --- a/controllers/jetstream/consumer.go +++ b/controllers/jetstream/consumer.go @@ -244,7 +244,7 @@ func consumerSpecToOpts(spec apis.ConsumerSpec) ([]jsm.ConsumerOption, error) { opts = append(opts, jsm.AcknowledgeExplicit()) case "": default: - return nil, fmt.Errorf("invalid value for 'ackPolicy': '%s'. Must be one of 'none', 'all', 'explicit'.", spec.AckPolicy) + return nil, fmt.Errorf("invalid value for 'ackPolicy': '%s'. Must be one of 'none', 'all', 'explicit'", spec.AckPolicy) } if spec.AckWait != "" { @@ -262,7 +262,7 @@ func consumerSpecToOpts(spec apis.ConsumerSpec) ([]jsm.ConsumerOption, error) { opts = append(opts, jsm.ReplayAsReceived()) case "": default: - return nil, fmt.Errorf("invalid value for 'replayPolicy': '%s'. Must be one of 'instant', 'original'.", spec.ReplayPolicy) + return nil, fmt.Errorf("invalid value for 'replayPolicy': '%s'. Must be one of 'instant', 'original'", spec.ReplayPolicy) } if spec.SampleFreq != "" { diff --git a/controllers/jetstream/controller.go b/controllers/jetstream/controller.go index ce1a7d9b..d9dba8b8 100644 --- a/controllers/jetstream/controller.go +++ b/controllers/jetstream/controller.go @@ -98,11 +98,11 @@ type Controller struct { strLister listers.StreamLister strSynced cache.InformerSynced - strQueue workqueue.RateLimitingInterface + strQueue workqueue.TypedRateLimitingInterface[any] cnsLister listers.ConsumerLister cnsSynced cache.InformerSynced - cnsQueue workqueue.RateLimitingInterface + cnsQueue workqueue.TypedRateLimitingInterface[any] accLister listers.AccountLister @@ -137,8 +137,8 @@ func NewController(opt Options) *Controller { } ji := opt.JetstreamIface.JetstreamV1beta2() - streamQueue := workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "Streams") - consumerQueue := workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "Consumers") + streamQueue := workqueue.NewNamedRateLimitingQueue(workqueue.DefaultTypedControllerRateLimiter[any](), "Streams") + consumerQueue := workqueue.NewNamedRateLimitingQueue(workqueue.DefaultTypedControllerRateLimiter[any](), "Consumers") streamInformer.Informer().AddEventHandler( eventHandlers( @@ -156,6 +156,7 @@ func NewController(opt Options) *Controller { if err != nil { panic(err) } + defer os.RemoveAll(cacheDir) return &Controller{ ctx: opt.Ctx, @@ -180,7 +181,6 @@ func NewController(opt Options) *Controller { } func (c *Controller) Run() error { - // Connect to NATS. opts := make([]nats.Option, 0) // Always attempt to have a connection to NATS. @@ -647,7 +647,7 @@ func getStorageType(s string) (jsmapi.StorageType, error) { } } -func enqueueWork(q workqueue.RateLimitingInterface, item interface{}) (err error) { +func enqueueWork(q workqueue.TypedRateLimitingInterface[any], item interface{}) (err error) { key, err := cache.MetaNamespaceKeyFunc(item) if err != nil { return fmt.Errorf("failed to enqueue work: %w", err) @@ -657,10 +657,12 @@ func enqueueWork(q workqueue.RateLimitingInterface, item interface{}) (err error return nil } -type jsmClientFunc func(*natsContext) (jsmClient, error) -type processorFunc func(ns, name string, jmsClient jsmClientFunc) error +type ( + jsmClientFunc func(*natsContext) (jsmClient, error) + processorFunc func(ns, name string, jmsClient jsmClientFunc) error +) -func processQueueNext(q workqueue.RateLimitingInterface, jmsClient jsmClientFunc, process processorFunc) { +func processQueueNext(q workqueue.TypedRateLimitingInterface[any], jmsClient jsmClientFunc, process processorFunc) { item, shutdown := q.Get() if shutdown { return @@ -730,7 +732,7 @@ func shouldEnqueue(prevObj, nextObj interface{}) bool { return markedDelete || specChanged } -func eventHandlers(q workqueue.RateLimitingInterface) cache.ResourceEventHandlerFuncs { +func eventHandlers(q workqueue.TypedRateLimitingInterface[any]) cache.ResourceEventHandlerFuncs { return cache.ResourceEventHandlerFuncs{ AddFunc: func(obj interface{}) { if err := enqueueWork(q, obj); err != nil { diff --git a/controllers/jetstream/controller_test.go b/controllers/jetstream/controller_test.go index aae4d9b6..0b85f461 100644 --- a/controllers/jetstream/controller_test.go +++ b/controllers/jetstream/controller_test.go @@ -63,7 +63,7 @@ func TestGetStorageType(t *testing.T) { func TestEnqueueWork(t *testing.T) { t.Parallel() - limiter := workqueue.DefaultControllerRateLimiter() + limiter := workqueue.DefaultTypedControllerRateLimiter[any]() q := workqueue.NewNamedRateLimitingQueue(limiter, "StreamsTest") defer q.ShutDown() @@ -97,7 +97,7 @@ func TestProcessQueueNext(t *testing.T) { t.Run("bad item key", func(t *testing.T) { t.Parallel() - limiter := workqueue.DefaultControllerRateLimiter() + limiter := workqueue.DefaultTypedControllerRateLimiter[any]() q := workqueue.NewNamedRateLimitingQueue(limiter, "StreamsTest") defer q.ShutDown() @@ -122,7 +122,7 @@ func TestProcessQueueNext(t *testing.T) { t.Run("process error", func(t *testing.T) { t.Parallel() - limiter := workqueue.DefaultControllerRateLimiter() + limiter := workqueue.DefaultTypedControllerRateLimiter[any]() q := workqueue.NewNamedRateLimitingQueue(limiter, "StreamsTest") defer q.ShutDown() @@ -156,7 +156,7 @@ func TestProcessQueueNext(t *testing.T) { t.Run("process ok", func(t *testing.T) { t.Parallel() - limiter := workqueue.DefaultControllerRateLimiter() + limiter := workqueue.DefaultTypedControllerRateLimiter[any]() q := workqueue.NewNamedRateLimitingQueue(limiter, "StreamsTest") defer q.ShutDown() diff --git a/controllers/jetstream/stream.go b/controllers/jetstream/stream.go index 5f582fe1..0eb67d26 100644 --- a/controllers/jetstream/stream.go +++ b/controllers/jetstream/stream.go @@ -20,7 +20,6 @@ import ( "time" jsm "github.com/nats-io/jsm.go" - "github.com/nats-io/jsm.go/api" jsmapi "github.com/nats-io/jsm.go/api" apis "github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2" typed "github.com/nats-io/nack/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2" @@ -223,9 +222,9 @@ func createStream(ctx context.Context, c jsmClient, spec apis.StreamSpec) (err e switch spec.Compression { case "s2": - opts = append(opts, jsm.Compression(api.S2Compression)) + opts = append(opts, jsm.Compression(jsmapi.S2Compression)) case "none": - opts = append(opts, jsm.Compression(api.NoCompression)) + opts = append(opts, jsm.Compression(jsmapi.NoCompression)) } if spec.NoAck { @@ -289,7 +288,7 @@ func createStream(ctx context.Context, c jsmClient, spec apis.StreamSpec) (err e } if spec.SubjectTransform != nil { - opts = append(opts, func(o *api.StreamConfig) error { + opts = append(opts, func(o *jsmapi.StreamConfig) error { o.SubjectTransform = &jsmapi.SubjectTransformConfig{ Source: spec.SubjectTransform.Source, Destination: spec.SubjectTransform.Dest, @@ -425,9 +424,9 @@ func updateStream(ctx context.Context, c jsmClient, spec apis.StreamSpec) (err e switch spec.Compression { case "s2": - config.Compression = api.S2Compression + config.Compression = jsmapi.S2Compression case "none": - config.Compression = api.NoCompression + config.Compression = jsmapi.NoCompression } return js.UpdateConfiguration(config) diff --git a/deploy/crds.yml b/deploy/crds.yml index 6171ff99..618e109c 100644 --- a/deploy/crds.yml +++ b/deploy/crds.yml @@ -1042,3 +1042,214 @@ spec: password: description: Key in the secret that contains the password. type: string +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: keyvalues.jetstream.nats.io +spec: + group: jetstream.nats.io + scope: Namespaced + names: + kind: KeyValue + singular: keyvalue + plural: keyvalues + shortNames: + - kv + versions: + - name: v1beta2 + served: true + storage: true + subresources: + status: {} + schema: + openAPIV3Schema: + type: object + properties: + spec: + type: object + properties: + name: + description: A unique name for the Key/Value store. + type: string + description: + description: The description of the Key/Value store. + type: string + maxValueSize: + description: The maximum size of a value in bytes. + type: integer + history: + description: The number of historical values to keep per key. + type: integer + ttl: + description: The time expiry for keys. + type: string + maxBytes: + description: The maximum size of the Key/Value store in bytes. + type: integer + storage: + description: The storage backend to use for the Key/Value store. + type: string + enum: + - file + - memory + replicas: + description: The number of replicas to keep for the Key/Value store in clustered JetStream. + type: integer + minimum: 1 + maximum: 5 + placement: + description: The Key/Value store placement via tags or cluster name. + type: object + properties: + cluster: + type: string + tags: + type: array + items: + type: string + republish: + description: Republish configuration for the Key/Value store. + type: object + properties: + destination: + type: string + description: Messages will be additionally published to this subject after store. + source: + type: string + description: Messages will be published from this subject to the destination subject. + mirror: + description: A Key/Value store mirror. + type: object + properties: + name: + type: string + optStartSeq: + type: integer + optStartTime: + description: Time format must be RFC3339. + type: string + filterSubject: + type: string + externalApiPrefix: + type: string + externalDeliverPrefix: + type: string + subjectTransforms: + description: List of subject transforms for this mirror. + type: array + items: + description: A subject transform pair. + type: object + properties: + source: + description: Source subject. + type: string + dest: + description: Destination subject. + type: string + compression: + description: Key/Value store compression. + type: boolean + sources: + description: A Key/Value store's sources. + type: array + items: + type: object + properties: + name: + type: string + optStartSeq: + type: integer + optStartTime: + description: Time format must be RFC3339. + type: string + filterSubject: + type: string + externalApiPrefix: + type: string + externalDeliverPrefix: + type: string + subjectTransforms: + description: List of subject transforms for this mirror. + type: array + items: + description: A subject transform pair. + type: object + properties: + source: + description: Source subject. + type: string + dest: + description: Destination subject. + type: string + servers: + description: A list of servers for creating stream + type: array + items: + type: string + default: [] + creds: + description: NATS user credentials for connecting to servers. Please make sure your controller has mounted the cerds on its path. + type: string + default: '' + nkey: + description: NATS user NKey for connecting to servers. + type: string + default: '' + tls: + description: A client's TLS certs and keys. + type: object + properties: + clientCert: + description: A client's cert filepath. Should be mounted. + type: string + clientKey: + description: A client's key filepath. Should be mounted. + type: string + rootCas: + description: A list of filepaths to CAs. Should be mounted. + type: array + items: + type: string + account: + description: Name of the account to which the Stream belongs. + type: string + pattern: '^[^.*>]*$' + preventDelete: + description: When true, the managed Stream will not be deleted when the resource is deleted + type: boolean + default: false + preventUpdate: + description: When true, the managed Stream will not be updated when the resource is updated + type: boolean + default: false + status: + type: object + properties: + observedGeneration: + type: integer + conditions: + type: array + items: + type: object + properties: + type: + type: string + status: + type: string + lastTransitionTime: + type: string + reason: + type: string + message: + type: string + additionalPrinterColumns: + - name: State + type: string + description: The current state of the Key/Value store. + jsonPath: .status.conditions[?(@.type == 'Ready')].reason + - name: Key/Value Store Name + type: string + description: The name of the Key/Value store. + jsonPath: .spec.name \ No newline at end of file diff --git a/deploy/rbac.yml b/deploy/rbac.yml index 388afb73..789e4043 100644 --- a/deploy/rbac.yml +++ b/deploy/rbac.yml @@ -31,6 +31,8 @@ rules: resources: - streams - streams/status + - keyvalues + - keyvalues/status - consumers - consumers/status - streamtemplates @@ -48,6 +50,7 @@ rules: - jetstream.nats.io resources: - streams/finalizers + - keyvalues/finalizers - consumers/finalizers - accounts/finalizers verbs: diff --git a/go.mod b/go.mod index 6d8231ed..3ceacd34 100644 --- a/go.mod +++ b/go.mod @@ -1,26 +1,27 @@ module github.com/nats-io/nack -go 1.23 +go 1.23.0 -toolchain go1.23.2 +toolchain go1.23.4 require ( github.com/fsnotify/fsnotify v1.8.0 + github.com/go-logr/logr v1.4.2 github.com/nats-io/jsm.go v0.1.2 - github.com/nats-io/nats-server/v2 v2.10.22 - github.com/nats-io/nats.go v1.37.0 - github.com/onsi/ginkgo/v2 v2.19.0 - github.com/onsi/gomega v1.33.1 + github.com/nats-io/nats-server/v2 v2.10.24 + github.com/nats-io/nats.go v1.38.0 + github.com/onsi/ginkgo/v2 v2.22.2 + github.com/onsi/gomega v1.36.2 github.com/sirupsen/logrus v1.9.3 - github.com/stretchr/testify v1.9.0 - golang.org/x/sync v0.9.0 - k8s.io/api v0.31.2 - k8s.io/apimachinery v0.31.2 - k8s.io/client-go v0.31.2 - k8s.io/code-generator v0.31.2 + github.com/stretchr/testify v1.10.0 + golang.org/x/sync v0.10.0 + k8s.io/api v0.32.0 + k8s.io/apimachinery v0.32.0 + k8s.io/client-go v0.32.0 + k8s.io/code-generator v0.32.0 k8s.io/klog/v2 v2.130.1 - sigs.k8s.io/controller-runtime v0.19.2 - sigs.k8s.io/structured-merge-diff/v4 v4.4.3 + sigs.k8s.io/controller-runtime v0.19.3 + sigs.k8s.io/structured-merge-diff/v4 v4.5.0 ) require ( @@ -29,66 +30,61 @@ require ( github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/emicklei/go-restful/v3 v3.12.1 // indirect - github.com/evanphx/json-patch v5.9.0+incompatible // indirect github.com/evanphx/json-patch/v5 v5.9.0 // indirect github.com/expr-lang/expr v1.16.9 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect - github.com/go-logr/logr v1.4.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/gogo/protobuf v1.3.2 // indirect - github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.4 // indirect - github.com/google/gnostic-models v0.6.8 // indirect + github.com/google/gnostic-models v0.6.9 // indirect github.com/google/go-cmp v0.6.0 // indirect github.com/google/gofuzz v1.2.0 // indirect - github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 // indirect + github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad // indirect github.com/google/uuid v1.6.0 // indirect - github.com/imdario/mergo v0.3.16 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/compress v1.17.11 // indirect - github.com/mailru/easyjson v0.7.7 // indirect + github.com/mailru/easyjson v0.9.0 // indirect github.com/minio/highwayhash v1.0.3 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - github.com/nats-io/jwt/v2 v2.7.2 // indirect - github.com/nats-io/nkeys v0.4.7 // indirect + github.com/nats-io/jwt/v2 v2.7.3 // indirect + github.com/nats-io/nkeys v0.4.9 // indirect github.com/nats-io/nuid v1.0.1 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/prometheus/client_golang v1.19.1 // indirect + github.com/prometheus/client_golang v1.20.5 // indirect github.com/prometheus/client_model v0.6.1 // indirect - github.com/prometheus/common v0.55.0 // indirect + github.com/prometheus/common v0.61.0 // indirect github.com/prometheus/procfs v0.15.1 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/x448/float16 v0.8.4 // indirect go.uber.org/multierr v1.11.0 // indirect - go.uber.org/zap v1.26.0 // indirect - golang.org/x/crypto v0.29.0 // indirect - golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc // indirect + go.uber.org/zap v1.27.0 // indirect + golang.org/x/crypto v0.31.0 // indirect + golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67 // indirect golang.org/x/mod v0.22.0 // indirect - golang.org/x/net v0.31.0 // indirect + golang.org/x/net v0.33.0 // indirect golang.org/x/oauth2 v0.24.0 // indirect - golang.org/x/sys v0.27.0 // indirect - golang.org/x/term v0.26.0 // indirect - golang.org/x/text v0.20.0 // indirect + golang.org/x/sys v0.28.0 // indirect + golang.org/x/term v0.27.0 // indirect + golang.org/x/text v0.21.0 // indirect golang.org/x/time v0.8.0 // indirect - golang.org/x/tools v0.27.0 // indirect + golang.org/x/tools v0.28.0 // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect - google.golang.org/protobuf v1.35.2 // indirect + google.golang.org/protobuf v1.36.1 // 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 gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/apiextensions-apiserver v0.31.0 // indirect + k8s.io/apiextensions-apiserver v0.32.0 // indirect k8s.io/gengo/v2 v2.0.0-20240911193312-2b36238f13e9 // indirect - k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f // indirect - k8s.io/utils v0.0.0-20241104163129-6fe5fd82f078 // indirect + k8s.io/kube-openapi v0.0.0-20241212222426-2c72e554b1e7 // indirect + k8s.io/utils v0.0.0-20241210054802-24370beab758 // indirect sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect sigs.k8s.io/yaml v1.4.0 // indirect ) diff --git a/go.sum b/go.sum index 27b2f751..d89930d8 100644 --- a/go.sum +++ b/go.sum @@ -10,8 +10,8 @@ github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkp github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= 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/evanphx/json-patch v5.9.0+incompatible h1:fBXyNpNMuTTDdquAq/uisOr2lShz4oaXpDTX2bLe7ls= -github.com/evanphx/json-patch v5.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +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/expr-lang/expr v1.16.9 h1:WUAzmR0JNI9JCiF0/ewwHB1gmcGw5wW7nWt8gc6PpCI= @@ -34,24 +34,20 @@ github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1v github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= -github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= +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.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/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-20240727154555-813a5fbdbec8 h1:FKHo8hFI3A+7w0aUQuYXQ+6EN5stWmeY/AZqtM8xk9k= -github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo= +github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad h1:a6HEuzUHeKH6hwfN/ZoQgRgVIWFJljSWa/zetS2WTvg= +github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= 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/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= -github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= 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= @@ -64,8 +60,10 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= -github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +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/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= +github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= github.com/minio/highwayhash v1.0.3 h1:kbnuUMoHYyVl7szWjSxJnxw11k2U709jqFPPmIUyD6Q= github.com/minio/highwayhash v1.0.3/go.mod h1:GGYsuwP/fPD6Y9hMiXuapVvlIUEhFhMTh0rxU3ik1LQ= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -77,31 +75,31 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/nats-io/jsm.go v0.1.2 h1:T4Fq88a03sPAPWYwrOLQ85oanYsC2Bs6517rUiWBMpQ= github.com/nats-io/jsm.go v0.1.2/go.mod h1:tnubE70CAKi5TNfQiq6XHFqWTuSIe1H7X4sDwfq6ZK8= -github.com/nats-io/jwt/v2 v2.7.2 h1:SCRjfDLJ2q8naXp8YlGJJS5/yj3wGSODFYVi4nnwVMw= -github.com/nats-io/jwt/v2 v2.7.2/go.mod h1:kB6QUmqHG6Wdrzj0KP2L+OX4xiTPBeV+NHVstFaATXU= -github.com/nats-io/nats-server/v2 v2.10.22 h1:Yt63BGu2c3DdMoBZNcR6pjGQwk/asrKU7VX846ibxDA= -github.com/nats-io/nats-server/v2 v2.10.22/go.mod h1:X/m1ye9NYansUXYFrbcDwUi/blHkrgHh2rgCJaakonk= -github.com/nats-io/nats.go v1.37.0 h1:07rauXbVnnJvv1gfIyghFEo6lUcYRY0WXc3x7x0vUxE= -github.com/nats-io/nats.go v1.37.0/go.mod h1:Ubdu4Nh9exXdSz0RVWRFBbRfrbSxOYd26oF0wkWclB8= -github.com/nats-io/nkeys v0.4.7 h1:RwNJbbIdYCoClSDNY7QVKZlyb/wfT6ugvFCiKy6vDvI= -github.com/nats-io/nkeys v0.4.7/go.mod h1:kqXRgRDPlGy7nGaEDMuYzmiJCIAAWDK0IMBtDmGD0nc= +github.com/nats-io/jwt/v2 v2.7.3 h1:6bNPK+FXgBeAqdj4cYQ0F8ViHRbi7woQLq4W29nUAzE= +github.com/nats-io/jwt/v2 v2.7.3/go.mod h1:GvkcbHhKquj3pkioy5put1wvPxs78UlZ7D/pY+BgZk4= +github.com/nats-io/nats-server/v2 v2.10.24 h1:KcqqQAD0ZZcG4yLxtvSFJY7CYKVYlnlWoAiVZ6i/IY4= +github.com/nats-io/nats-server/v2 v2.10.24/go.mod h1:olvKt8E5ZlnjyqBGbAXtxvSQKsPodISK5Eo/euIta4s= +github.com/nats-io/nats.go v1.38.0 h1:A7P+g7Wjp4/NWqDOOP/K6hfhr54DvdDQUznt5JFg9XA= +github.com/nats-io/nats.go v1.38.0/go.mod h1:IGUM++TwokGnXPs82/wCuiHS02/aKrdYUQkU8If6yjw= +github.com/nats-io/nkeys v0.4.9 h1:qe9Faq2Gxwi6RZnZMXfmGMZkg3afLLOtrU+gDZJ35b0= +github.com/nats-io/nkeys v0.4.9/go.mod h1:jcMqs+FLG+W5YO36OX6wFIFcmpdAns+w1Wm6D3I/evE= github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= -github.com/onsi/ginkgo/v2 v2.19.0 h1:9Cnnf7UHo57Hy3k6/m5k3dRfGTMXGvxhHFvkDTCTpvA= -github.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To= -github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk= -github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0= +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/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 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.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE= -github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho= +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/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.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc= -github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8= +github.com/prometheus/common v0.61.0 h1:3gv/GThfX0cV2lpO7gkTUwZru38mxevy90Bj8YFSRQQ= +github.com/prometheus/common v0.61.0/go.mod h1:zr29OCN/2BsJRaFwG8QOBr41D6kkchKbpeNH7pAjb/s= 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= @@ -113,8 +111,8 @@ github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +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/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -123,15 +121,15 @@ 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= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= -go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +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.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ= -golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg= -golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc h1:mCRnTeVUjcrhlRmO0VK8a6k6Rrf6TF9htwo2pJVSjIU= -golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w= +golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= +golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= +golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67 h1:1UoZQm6f0P/ZO0w1Ri+f+ifG/gXhegadRdwBIXEFWDo= +golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c= 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.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= @@ -140,44 +138,44 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn 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-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo= -golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM= +golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= +golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE= golang.org/x/oauth2 v0.24.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-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ= -golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +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/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-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= -golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.26.0 h1:WEQa6V3Gja/BhNxg540hBip/kkaYtRg3cxg4oXSw4AU= -golang.org/x/term v0.26.0/go.mod h1:Si5m1o57C5nBNQo5z1iq+XDijt21BDBDp2bK0QI8e3E= +golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= +golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q= +golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug= -golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= +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.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg= golang.org/x/time v0.8.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-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.27.0 h1:qEKojBykQkQ4EynWy4S8Weg69NumxKdn40Fce3uc/8o= -golang.org/x/tools v0.27.0/go.mod h1:sUi0ZgbwW9ZPAq26Ekut+weQPR5eIM6GQLQ1Yjm1H0Q= +golang.org/x/tools v0.28.0 h1:WuB6qZ4RPCQo5aP3WdKZS7i595EdWqWR8vqJTlwTVK8= +golang.org/x/tools v0.28.0/go.mod h1:dcIOrVd3mfQKTgrDVQHqCPMWy6lnhfhtX3hLXYVLfRw= 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= 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/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io= -google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk= +google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/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= @@ -185,34 +183,32 @@ gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSP 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/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= -k8s.io/api v0.31.2 h1:3wLBbL5Uom/8Zy98GRPXpJ254nEFpl+hwndmk9RwmL0= -k8s.io/api v0.31.2/go.mod h1:bWmGvrGPssSK1ljmLzd3pwCQ9MgoTsRCuK35u6SygUk= -k8s.io/apiextensions-apiserver v0.31.0 h1:fZgCVhGwsclj3qCw1buVXCV6khjRzKC5eCFt24kyLSk= -k8s.io/apiextensions-apiserver v0.31.0/go.mod h1:b9aMDEYaEe5sdK+1T0KU78ApR/5ZVp4i56VacZYEHxk= -k8s.io/apimachinery v0.31.2 h1:i4vUt2hPK56W6mlT7Ry+AO8eEsyxMD1U44NR22CLTYw= -k8s.io/apimachinery v0.31.2/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo= -k8s.io/client-go v0.31.2 h1:Y2F4dxU5d3AQj+ybwSMqQnpZH9F30//1ObxOKlTI9yc= -k8s.io/client-go v0.31.2/go.mod h1:NPa74jSVR/+eez2dFsEIHNa+3o09vtNaWwWwb1qSxSs= -k8s.io/code-generator v0.31.2 h1:xLWxG0HEpMSHfcM//3u3Ro2Hmc6AyyLINQS//Z2GEOI= -k8s.io/code-generator v0.31.2/go.mod h1:eEQHXgBU/m7LDaToDoiz3t97dUUVyOblQdwOr8rivqc= +k8s.io/api v0.32.0 h1:OL9JpbvAU5ny9ga2fb24X8H6xQlVp+aJMFlgtQjR9CE= +k8s.io/api v0.32.0/go.mod h1:4LEwHZEf6Q/cG96F3dqR965sYOfmPM7rq81BLgsE0p0= +k8s.io/apiextensions-apiserver v0.32.0 h1:S0Xlqt51qzzqjKPxfgX1xh4HBZE+p8KKBq+k2SWNOE0= +k8s.io/apiextensions-apiserver v0.32.0/go.mod h1:86hblMvN5yxMvZrZFX2OhIHAuFIMJIZ19bTvzkP+Fmw= +k8s.io/apimachinery v0.32.0 h1:cFSE7N3rmEEtv4ei5X6DaJPHHX0C+upp+v5lVPiEwpg= +k8s.io/apimachinery v0.32.0/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE= +k8s.io/client-go v0.32.0 h1:DimtMcnN/JIKZcrSrstiwvvZvLjG0aSxy8PxN8IChp8= +k8s.io/client-go v0.32.0/go.mod h1:boDWvdM1Drk4NJj/VddSLnx59X3OPgwrOo0vGbtq9+8= +k8s.io/code-generator v0.32.0 h1:s0lNN8VSWny8LBz5t5iy7MCdgwdOhdg7vAGVxvS+VWU= +k8s.io/code-generator v0.32.0/go.mod h1:b7Q7KMZkvsYFy72A79QYjiv4aTz3GvW0f1T3UfhFq4s= k8s.io/gengo/v2 v2.0.0-20240911193312-2b36238f13e9 h1:si3PfKm8dDYxgfbeA6orqrtLkvvIeH8UqffFJDl0bz4= k8s.io/gengo/v2 v2.0.0-20240911193312-2b36238f13e9/go.mod h1:EJykeLsmFC60UQbYJezXkEsG2FLrt0GPNkU5iK5GWxU= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f h1:GA7//TjRY9yWGy1poLzYYJJ4JRdzg3+O6e8I+e+8T5Y= -k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f/go.mod h1:R/HEjbvWI0qdfb8viZUeVZm0X6IZnxAydC7YU42CMw4= -k8s.io/utils v0.0.0-20241104163129-6fe5fd82f078 h1:jGnCPejIetjiy2gqaJ5V0NLwTpF4wbQ6cZIItJCSHno= -k8s.io/utils v0.0.0-20241104163129-6fe5fd82f078/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -sigs.k8s.io/controller-runtime v0.19.2 h1:3sPrF58XQEPzbE8T81TN6selQIMGbtYwuaJ6eDssDF8= -sigs.k8s.io/controller-runtime v0.19.2/go.mod h1:iRmWllt8IlaLjvTTDLhRBXIEtkCK6hwVBJJsYS9Ajf4= +k8s.io/kube-openapi v0.0.0-20241212222426-2c72e554b1e7 h1:hcha5B1kVACrLujCKLbr8XWMxCxzQx42DY8QKYJrDLg= +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/controller-runtime v0.19.3 h1:XO2GvC9OPftRst6xWCpTgBZO04S2cbp0Qqkj8bX1sPw= +sigs.k8s.io/controller-runtime v0.19.3/go.mod h1:j4j87DqtsThvwTv5/Tc5NFRyyF/RF0ip4+62tbTSIUM= 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/structured-merge-diff/v4 v4.4.3 h1:sCP7Vv3xx/CWIuTPVN38lUPx0uw0lcLfzaiDa8Ja01A= -sigs.k8s.io/structured-merge-diff/v4 v4.4.3/go.mod h1:N8f93tFZh9U6vpxwRArLiikrE5/2tiu1w1AGfACIGE4= +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= sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= diff --git a/internal/controller/account_controller.go b/internal/controller/account_controller.go index 72418667..0be62eeb 100644 --- a/internal/controller/account_controller.go +++ b/internal/controller/account_controller.go @@ -18,6 +18,7 @@ package controller import ( "context" + "k8s.io/klog/v2" jetstreamnatsiov1beta2 "github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2" diff --git a/internal/controller/client.go b/internal/controller/client.go index c20144b3..5eb9b8d6 100644 --- a/internal/controller/client.go +++ b/internal/controller/client.go @@ -2,6 +2,7 @@ package controller import ( "fmt" + "github.com/nats-io/nats.go" "github.com/nats-io/nats.go/jetstream" ) @@ -46,7 +47,7 @@ func (o *NatsConfig) buildOptions() ([]nats.Option, error) { opts = append(opts, nats.ClientCert(o.Certificate, o.Key)) } - if o.CAs != nil && len(o.CAs) > 0 { + if len(o.CAs) > 0 { opts = append(opts, nats.RootCAs(o.CAs...)) } } @@ -62,7 +63,6 @@ type Closable interface { // Returns a jetstream.Jetstream client and the Closable of the underlying connection. // Close should be called when the client is no longer used. func CreateJetStreamClient(cfg *NatsConfig, pedantic bool) (jetstream.JetStream, Closable, error) { - opts, err := cfg.buildOptions() if err != nil { return nil, nil, fmt.Errorf("nats options: %w", err) diff --git a/internal/controller/consumer_controller.go b/internal/controller/consumer_controller.go index ba7ebb0d..9010509f 100644 --- a/internal/controller/consumer_controller.go +++ b/internal/controller/consumer_controller.go @@ -20,6 +20,8 @@ import ( "context" "errors" "fmt" + "time" + "github.com/go-logr/logr" "github.com/nats-io/nats.go/jetstream" v1 "k8s.io/api/core/v1" @@ -27,7 +29,6 @@ import ( "k8s.io/klog/v2" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" "sigs.k8s.io/controller-runtime/pkg/predicate" - "time" api "github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2" ctrl "sigs.k8s.io/controller-runtime" @@ -67,7 +68,7 @@ func (r *ConsumerReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c ) // Update ready status to unknown when no status is set - if consumer.Status.Conditions == nil || len(consumer.Status.Conditions) == 0 { + if len(consumer.Status.Conditions) == 0 { log.Info("Setting initial ready condition to unknown.") consumer.Status.Conditions = updateReadyCondition(consumer.Status.Conditions, v1.ConditionUnknown, "Reconciling", "Starting reconciliation") err := r.Status().Update(ctx, consumer) @@ -113,7 +114,6 @@ func (r *ConsumerReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c } func (r *ConsumerReconciler) deleteConsumer(ctx context.Context, log logr.Logger, consumer *api.Consumer) error { - // Set status to not false consumer.Status.Conditions = updateReadyCondition(consumer.Status.Conditions, v1.ConditionFalse, "Finalizing", "Performing finalizer operations.") if err := r.Status().Update(ctx, consumer); err != nil { @@ -154,11 +154,9 @@ func (r *ConsumerReconciler) deleteConsumer(ctx context.Context, log logr.Logger } func (r *ConsumerReconciler) createOrUpdate(ctx context.Context, log klog.Logger, consumer *api.Consumer) error { - // Create or Update the stream based on the spec - if consumer.Spec.PreventUpdate || r.ReadOnly() { + if r.ReadOnly() { log.Info("Skipping consumer creation or update.", - "preventDelete", consumer.Spec.PreventDelete, "read-only", r.ReadOnly(), ) return nil @@ -171,9 +169,36 @@ func (r *ConsumerReconciler) createOrUpdate(ctx context.Context, log klog.Logger } err = r.WithJetStreamClient(consumerConnOpts(consumer.Spec), func(js jetstream.JetStream) error { - log.Info("Consumer created or updated.") - _, err := js.CreateOrUpdateConsumer(ctx, consumer.Spec.StreamName, *targetConfig) - return err + consumerName := targetConfig.Name + if consumerName == "" { + consumerName = targetConfig.Durable + } + + exists := false + _, err := js.Consumer(ctx, consumer.Spec.StreamName, consumerName) + if err == nil { + exists = true + } else if !errors.Is(err, jetstream.ErrConsumerNotFound) { + return err + } + + if !exists { + log.Info("Creating Consumer.") + _, err := js.CreateConsumer(ctx, consumer.Spec.StreamName, *targetConfig) + return err + } + + if !consumer.Spec.PreventUpdate { + log.Info("Updating Consumer.") + _, err := js.UpdateConsumer(ctx, consumer.Spec.StreamName, *targetConfig) + return err + } else { + log.Info("Skipping Consumer update.", + "preventUpdate", consumer.Spec.PreventUpdate, + ) + } + + return nil }) if err != nil { err = fmt.Errorf("create or update consumer: %w", err) @@ -211,7 +236,6 @@ func consumerConnOpts(spec api.ConsumerSpec) *connectionOptions { } func consumerSpecToConfig(spec *api.ConsumerSpec) (*jetstream.ConsumerConfig, error) { - config := &jetstream.ConsumerConfig{ Durable: spec.DurableName, Description: spec.Description, @@ -269,7 +293,7 @@ func consumerSpecToConfig(spec *api.ConsumerSpec) (*jetstream.ConsumerConfig, er config.AckWait = d } - //BackOff + // BackOff for _, bo := range spec.BackOff { d, err := time.ParseDuration(bo) if err != nil { diff --git a/internal/controller/consumer_controller_test.go b/internal/controller/consumer_controller_test.go index 62fd2d22..bf5410a1 100644 --- a/internal/controller/consumer_controller_test.go +++ b/internal/controller/consumer_controller_test.go @@ -17,19 +17,19 @@ limitations under the License. package controller import ( + "testing" + "time" + "github.com/nats-io/nats.go/jetstream" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/stretchr/testify/assert" v1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/errors" k8serrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/types" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" "sigs.k8s.io/controller-runtime/pkg/reconcile" - "testing" - "time" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -67,7 +67,7 @@ var _ = Describe("Consumer Controller", func() { BeforeEach(func(ctx SpecContext) { By("creating the custom resource for the Kind Consumer") err := k8sClient.Get(ctx, typeNamespacedName, consumer) - if err != nil && errors.IsNotFound(err) { + if err != nil && k8serrors.IsNotFound(err) { resource := &api.Consumer{ ObjectMeta: metav1.ObjectMeta{ Name: resourceName, @@ -149,9 +149,7 @@ var _ = Describe("Consumer Controller", func() { }) When("reconciling a not initialized resource", func() { - It("should initialize a new resource", func(ctx SpecContext) { - By("re-queueing until it is initialized") // Initialization can require multiple reconciliation loops Eventually(func(ctx SpecContext) *api.Consumer { @@ -177,7 +175,6 @@ var _ = Describe("Consumer Controller", func() { }) When("reconciling an initialized resource", func() { - BeforeEach(func(ctx SpecContext) { By("initializing the stream resource") @@ -222,7 +219,6 @@ var _ = Describe("Consumer Controller", func() { }) It("should create a new consumer", func(ctx SpecContext) { - By("running Reconcile") result, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName}) Expect(err).NotTo(HaveOccurred()) @@ -249,7 +245,6 @@ var _ = Describe("Consumer Controller", func() { }) It("should update an existing consumer", func(ctx SpecContext) { - By("reconciling once to create the consumer") result, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName}) Expect(err).NotTo(HaveOccurred()) @@ -290,21 +285,20 @@ var _ = Describe("Consumer Controller", func() { }) When("PreventUpdate is set", func() { - BeforeEach(func(ctx SpecContext) { By("setting preventUpdate on the resource") consumer.Spec.PreventUpdate = true Expect(k8sClient.Update(ctx, consumer)).To(Succeed()) }) - It("should not create the consumer", func(ctx SpecContext) { + It("should create the consumer", func(ctx SpecContext) { By("running Reconcile") result, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName}) Expect(err).NotTo(HaveOccurred()) Expect(result.IsZero()).To(BeTrue()) - By("checking that no consumer was created") + By("checking that consumer was created") _, err = jsClient.Consumer(ctx, streamName, consumerName) - Expect(err).To(MatchError(jetstream.ErrConsumerNotFound)) + Expect(err).ToNot(HaveOccurred()) }) It("should not update the consumer", func(ctx SpecContext) { By("creating the consumer") @@ -318,12 +312,12 @@ var _ = Describe("Consumer Controller", func() { By("checking that consumer was not updated") c, err := jsClient.Consumer(ctx, streamName, consumerName) + Expect(err).NotTo(HaveOccurred()) Expect(c.CachedInfo().Config.Description).To(BeEmpty()) }) }) When("read-only mode is enabled", func() { - BeforeEach(func(ctx SpecContext) { By("setting read only on the controller") readOnly, err := NewJSController(k8sClient, &NatsConfig{ServerURL: testServer.ClientURL()}, &Config{ReadOnly: true}) @@ -355,12 +349,12 @@ var _ = Describe("Consumer Controller", func() { By("checking that consumer was not updated") s, err := jsClient.Consumer(ctx, streamName, consumerName) + Expect(err).NotTo(HaveOccurred()) Expect(s.CachedInfo().Config.Description).To(BeEmpty()) }) }) When("namespace restriction is enabled", func() { - BeforeEach(func(ctx SpecContext) { By("setting a namespace on the resource") namespaced, err := NewJSController(k8sClient, &NatsConfig{ServerURL: testServer.ClientURL()}, &Config{Namespace: "other-namespace"}) @@ -392,12 +386,12 @@ var _ = Describe("Consumer Controller", func() { By("checking that consumer was not updated") s, err := jsClient.Consumer(ctx, streamName, consumerName) + Expect(err).NotTo(HaveOccurred()) Expect(s.CachedInfo().Config.Description).To(BeEmpty()) }) }) When("the resource is marked for deletion", func() { - BeforeEach(func(ctx SpecContext) { By("marking the resource for deletion") Expect(k8sClient.Delete(ctx, consumer)).To(Succeed()) @@ -495,7 +489,6 @@ var _ = Describe("Consumer Controller", func() { } }) It("should delete the resource and not delete the consumer", func(ctx SpecContext) { - By("reconciling") result, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName}) Expect(err).NotTo(HaveOccurred()) @@ -521,7 +514,6 @@ var _ = Describe("Consumer Controller", func() { } }) It("should not delete the resource and consumer", func(ctx SpecContext) { - By("reconciling") result, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName}) Expect(err).NotTo(HaveOccurred()) @@ -540,7 +532,6 @@ var _ = Describe("Consumer Controller", func() { }) It("should create consumer on different server as specified in spec", func(ctx SpecContext) { - By("setting up the alternative server") altServer := CreateTestServer() defer altServer.Shutdown() @@ -558,7 +549,7 @@ var _ = Describe("Consumer Controller", func() { Expect(k8sClient.Update(ctx, consumer)).To(Succeed()) By("checking precondition, that the consumer does not yet exist") - got, err := jsClient.Consumer(ctx, streamName, consumerName) + _, err = jsClient.Consumer(ctx, streamName, consumerName) Expect(err).To(MatchError(jetstream.ErrConsumerNotFound)) By("reconciling the resource") @@ -569,7 +560,7 @@ var _ = Describe("Consumer Controller", func() { Expect(result.IsZero()).To(BeTrue()) By("checking if the consumer was created on the alternative server") - got, err = altClient.Consumer(ctx, streamName, consumerName) + got, err := altClient.Consumer(ctx, streamName, consumerName) Expect(err).NotTo(HaveOccurred()) Expect(got.CachedInfo().Created).To(BeTemporally("~", time.Now(), time.Second)) @@ -582,8 +573,7 @@ var _ = Describe("Consumer Controller", func() { }) func Test_consumerSpecToConfig(t *testing.T) { - - date := time.Date(2024, 12, 03, 16, 55, 5, 0, time.UTC) + date := time.Date(2024, 12, 3, 16, 55, 5, 0, time.UTC) dateString := date.Format(time.RFC3339) tests := []struct { diff --git a/internal/controller/helpers_test.go b/internal/controller/helpers_test.go index 8235f11c..58f176c8 100644 --- a/internal/controller/helpers_test.go +++ b/internal/controller/helpers_test.go @@ -1,15 +1,15 @@ package controller import ( + "os" + "time" + api "github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2" "github.com/nats-io/nats-server/v2/server" natsserver "github.com/nats-io/nats-server/v2/test" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" v1 "k8s.io/api/core/v1" - - "os" - "time" ) func assertReadyStateMatches(condition api.Condition, status v1.ConditionStatus, reason string, message string, transitionTime time.Time) { diff --git a/internal/controller/jetstream_controller.go b/internal/controller/jetstream_controller.go index 3796fc47..63e8f720 100644 --- a/internal/controller/jetstream_controller.go +++ b/internal/controller/jetstream_controller.go @@ -2,14 +2,15 @@ package controller import ( "fmt" + "strings" + "time" + js "github.com/nats-io/nack/controllers/jetstream" api "github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2" "github.com/nats-io/nats.go/jetstream" v1 "k8s.io/api/core/v1" - "strings" "sigs.k8s.io/controller-runtime/pkg/client" - "time" ) type connectionOptions struct { @@ -39,7 +40,6 @@ type JetStreamController interface { } func NewJSController(k8sClient client.Client, natsConfig *NatsConfig, controllerConfig *Config) (JetStreamController, error) { - return &jsController{ Client: k8sClient, config: natsConfig, @@ -63,7 +63,6 @@ func (c *jsController) ValidNamespace(namespace string) bool { } func (c *jsController) WithJetStreamClient(opts *connectionOptions, op func(js jetstream.JetStream) error) error { - // Build single use client // TODO(future-feature): Use client-pool instead of single use client cfg := c.buildNatsConfig(opts) @@ -79,7 +78,6 @@ func (c *jsController) WithJetStreamClient(opts *connectionOptions, op func(js j // buildNatsConfig uses given opts to override the base NatsConfig. func (c *jsController) buildNatsConfig(opts *connectionOptions) *NatsConfig { - serverUrls := strings.Join(opts.Servers, ",") // Takes opts values if present @@ -132,7 +130,6 @@ func or[T comparable](v T, fallback T) T { // updateReadyCondition returns the given conditions with an added or updated ready condition. func updateReadyCondition(conditions []api.Condition, status v1.ConditionStatus, reason string, message string) []api.Condition { - var currentStatus v1.ConditionStatus var lastTransitionTime string for _, condition := range conditions { diff --git a/internal/controller/jetstream_controller_test.go b/internal/controller/jetstream_controller_test.go index d7176d04..83b75ee8 100644 --- a/internal/controller/jetstream_controller_test.go +++ b/internal/controller/jetstream_controller_test.go @@ -1,15 +1,15 @@ package controller import ( + "testing" + "time" + api "github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2" "github.com/stretchr/testify/assert" v1 "k8s.io/api/core/v1" - "testing" - "time" ) func Test_updateReadyCondition(t *testing.T) { - pastTransition := time.Now().UTC().Add(-time.Hour).Format(time.RFC3339Nano) updatedTransition := "now" diff --git a/internal/controller/keyvalue_controller.go b/internal/controller/keyvalue_controller.go new file mode 100644 index 00000000..d76220c1 --- /dev/null +++ b/internal/controller/keyvalue_controller.go @@ -0,0 +1,312 @@ +/* +Copyright 2025. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package controller + +import ( + "context" + "errors" + "fmt" + "time" + + "github.com/go-logr/logr" + api "github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2" + "github.com/nats-io/nats.go/jetstream" + v1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/klog/v2" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + "sigs.k8s.io/controller-runtime/pkg/predicate" +) + +// KeyValueReconciler reconciles a KeyValue object +type KeyValueReconciler struct { + JetStreamController +} + +// Reconcile is part of the main kubernetes reconciliation loop which aims to +// move the current state of the cluster closer to the desired state. +// +// It performs three main operations: +// - Initialize finalizer and ready condition if not present +// - Delete KeyValue if it is marked for deletion. +// - Create or Update the KeyValue +// +// A call to reconcile may perform only one action, expecting the reconciliation to be triggered again by an update. +// For example: Setting the finalizer triggers a second reconciliation. Reconcile returns after setting the finalizer, +// to prevent parallel reconciliations performing the same steps. +func (r *KeyValueReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + log := klog.FromContext(ctx) + + if ok := r.ValidNamespace(req.Namespace); !ok { + log.Info("Controller restricted to namespace, skipping reconciliation.") + return ctrl.Result{}, nil + } + + // Fetch KeyValue resource + keyValue := &api.KeyValue{} + if err := r.Get(ctx, req.NamespacedName, keyValue); err != nil { + if apierrors.IsNotFound(err) { + log.Info("KeyValue resource not found. Ignoring since object must be deleted.") + return ctrl.Result{}, nil + } + return ctrl.Result{}, fmt.Errorf("get keyvalue resource '%s': %w", req.NamespacedName.String(), err) + } + + log = log.WithValues("keyValueName", keyValue.Spec.Name) + + // Update ready status to unknown when no status is set + if len(keyValue.Status.Conditions) == 0 { + log.Info("Setting initial ready condition to unknown.") + keyValue.Status.Conditions = updateReadyCondition(keyValue.Status.Conditions, v1.ConditionUnknown, "Reconciling", "Starting reconciliation") + err := r.Status().Update(ctx, keyValue) + if err != nil { + return ctrl.Result{}, fmt.Errorf("set condition unknown: %w", err) + } + return ctrl.Result{Requeue: true}, nil + } + + // Add finalizer + if !controllerutil.ContainsFinalizer(keyValue, keyValueFinalizer) { + log.Info("Adding KeyValue finalizer.") + if ok := controllerutil.AddFinalizer(keyValue, keyValueFinalizer); !ok { + return ctrl.Result{}, errors.New("failed to add finalizer to keyvalue resource") + } + + if err := r.Update(ctx, keyValue); err != nil { + return ctrl.Result{}, fmt.Errorf("update keyvalue resource to add finalizer: %w", err) + } + return ctrl.Result{}, nil + } + + // Check Deletion + markedForDeletion := keyValue.GetDeletionTimestamp() != nil + if markedForDeletion { + if controllerutil.ContainsFinalizer(keyValue, keyValueFinalizer) { + err := r.deleteKeyValue(ctx, log, keyValue) + if err != nil { + return ctrl.Result{}, fmt.Errorf("delete keyvalue: %w", err) + } + } else { + log.Info("KeyValue marked for deletion and already finalized. Ignoring.") + } + + return ctrl.Result{}, nil + } + + // Create or update KeyValue + if err := r.createOrUpdate(ctx, log, keyValue); err != nil { + return ctrl.Result{}, fmt.Errorf("create or update: %s", err) + } + return ctrl.Result{}, nil +} + +func (r *KeyValueReconciler) deleteKeyValue(ctx context.Context, log logr.Logger, keyValue *api.KeyValue) error { + // Set status to not false + keyValue.Status.Conditions = updateReadyCondition(keyValue.Status.Conditions, v1.ConditionFalse, "Finalizing", "Performing finalizer operations.") + if err := r.Status().Update(ctx, keyValue); err != nil { + return fmt.Errorf("update ready condition: %w", err) + } + + if !keyValue.Spec.PreventDelete && !r.ReadOnly() { + log.Info("Deleting KeyValue.") + err := r.WithJetStreamClient(keyValueConnOpts(keyValue.Spec), func(js jetstream.JetStream) error { + return js.DeleteKeyValue(ctx, keyValue.Spec.Name) + }) + if errors.Is(err, jetstream.ErrBucketNotFound) { + log.Info("KeyValue does not exist, unable to delete.", "keyValueName", keyValue.Spec.Name) + } else if err != nil { + return fmt.Errorf("delete keyvalue during finalization: %w", err) + } + } else { + log.Info("Skipping KeyValue deletion.", + "preventDelete", keyValue.Spec.PreventDelete, + "read-only", r.ReadOnly(), + ) + } + + log.Info("Removing KeyValue finalizer.") + if ok := controllerutil.RemoveFinalizer(keyValue, keyValueFinalizer); !ok { + return errors.New("failed to remove keyvalue finalizer") + } + if err := r.Update(ctx, keyValue); err != nil { + return fmt.Errorf("remove finalizer: %w", err) + } + + return nil +} + +func (r *KeyValueReconciler) createOrUpdate(ctx context.Context, log logr.Logger, keyValue *api.KeyValue) error { + // Create or Update the KeyValue based on the spec + if r.ReadOnly() { + log.Info("Skipping KeyValue creation or update.", + "read-only", r.ReadOnly(), + ) + return nil + } + + // Map spec to KeyValue targetConfig + targetConfig, err := keyValueSpecToConfig(&keyValue.Spec) + if err != nil { + return fmt.Errorf("map spec to keyvalue targetConfig: %w", err) + } + + // UpdateKeyValue is called on every reconciliation when the stream is not to be deleted. + // TODO(future-feature): Do we need to check if config differs? + err = r.WithJetStreamClient(keyValueConnOpts(keyValue.Spec), func(js jetstream.JetStream) error { + exists := false + _, err := js.KeyValue(ctx, targetConfig.Bucket) + if err == nil { + exists = true + } else if !errors.Is(err, jetstream.ErrBucketNotFound) { + return err + } + + if !exists { + log.Info("Creating KeyValue.") + _, err = js.CreateKeyValue(ctx, targetConfig) + return err + } + + if !keyValue.Spec.PreventUpdate { + log.Info("Updating KeyValue.") + _, err = js.UpdateKeyValue(ctx, targetConfig) + return err + } else { + log.Info("Skipping KeyValue update.", + "preventUpdate", keyValue.Spec.PreventUpdate, + ) + } + + return nil + }) + if err != nil { + err = fmt.Errorf("create or update keyvalue: %w", err) + keyValue.Status.Conditions = updateReadyCondition(keyValue.Status.Conditions, v1.ConditionFalse, "Errored", err.Error()) + if err := r.Status().Update(ctx, keyValue); err != nil { + log.Error(err, "Failed to update ready condition to Errored.") + } + return err + } + + // update the observed generation and ready status + keyValue.Status.ObservedGeneration = keyValue.Generation + keyValue.Status.Conditions = updateReadyCondition( + keyValue.Status.Conditions, + v1.ConditionTrue, + "Reconciling", + "KeyValue successfully created or updated.", + ) + err = r.Status().Update(ctx, keyValue) + if err != nil { + return fmt.Errorf("update ready condition: %w", err) + } + + return nil +} + +// keyValueConnOpts extracts nats connection relevant fields from the given KeyValue spec as connectionOptions. +func keyValueConnOpts(spec api.KeyValueSpec) *connectionOptions { + return &connectionOptions{ + Account: spec.Account, + Creds: spec.Creds, + Nkey: spec.Nkey, + Servers: spec.Servers, + TLS: spec.TLS, + } +} + +// keyValueSpecToConfig creates a jetstream.KeyValueConfig matching the given KeyValue resource spec +func keyValueSpecToConfig(spec *api.KeyValueSpec) (jetstream.KeyValueConfig, error) { + // Set directly mapped fields + config := jetstream.KeyValueConfig{ + Bucket: spec.Name, + Compression: spec.Compression, + Description: spec.Description, + History: uint8(spec.History), + MaxBytes: int64(spec.MaxBytes), + MaxValueSize: int32(spec.MaxValueSize), + Replicas: spec.Replicas, + } + + // TTL + if spec.TTL != "" { + t, err := time.ParseDuration(spec.TTL) + if err != nil { + return jetstream.KeyValueConfig{}, fmt.Errorf("invalid ttl: %w", err) + } + config.TTL = t + } + + // storage + if spec.Storage != "" { + err := config.Storage.UnmarshalJSON(asJsonString(spec.Storage)) + if err != nil { + return jetstream.KeyValueConfig{}, fmt.Errorf("invalid storage: %w", err) + } + } + + // placement + if spec.Placement != nil { + config.Placement = &jetstream.Placement{ + Cluster: spec.Placement.Cluster, + Tags: spec.Placement.Tags, + } + } + + // mirror + if spec.Mirror != nil { + ss, err := mapStreamSource(spec.Mirror) + if err != nil { + return jetstream.KeyValueConfig{}, fmt.Errorf("map mirror keyvalue source: %w", err) + } + config.Mirror = ss + } + + // sources + if spec.Sources != nil { + config.Sources = []*jetstream.StreamSource{} + for _, source := range spec.Sources { + s, err := mapStreamSource(source) + if err != nil { + return jetstream.KeyValueConfig{}, fmt.Errorf("map keyvalue source: %w", err) + } + config.Sources = append(config.Sources, s) + } + } + + // RePublish + if spec.Republish != nil { + config.RePublish = &jetstream.RePublish{ + Source: spec.Republish.Source, + Destination: spec.Republish.Destination, + HeadersOnly: spec.Republish.HeadersOnly, + } + } + + return config, nil +} + +// SetupWithManager sets up the controller with the Manager. +func (r *KeyValueReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&api.KeyValue{}). + Owns(&api.KeyValue{}). + // Only trigger on generation changes + WithEventFilter(predicate.GenerationChangedPredicate{}). + Complete(r) +} diff --git a/internal/controller/keyvalue_controller_test.go b/internal/controller/keyvalue_controller_test.go new file mode 100644 index 00000000..26b06688 --- /dev/null +++ b/internal/controller/keyvalue_controller_test.go @@ -0,0 +1,699 @@ +/* +Copyright 2024. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package controller + +import ( + "testing" + "time" + + "github.com/nats-io/nats.go/jetstream" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "github.com/stretchr/testify/assert" + v1 "k8s.io/api/core/v1" + k8serrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/types" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + api "github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2" +) + +var _ = Describe("KeyValue Controller", func() { + // The test keyValue resource + const resourceName = "test-kv" + const keyValueName = "orders" + typeNamespacedName := types.NamespacedName{ + Name: resourceName, + Namespace: "default", + } + keyValue := &api.KeyValue{} + + // The tested controller + var controller *KeyValueReconciler + + // Config to create minimal nats KeyValue store + emptyKeyValueConfig := jetstream.KeyValueConfig{ + Bucket: keyValueName, + Replicas: 1, + Storage: jetstream.FileStorage, + } + + BeforeEach(func(ctx SpecContext) { + By("creating a test keyvalue resource") + err := k8sClient.Get(ctx, typeNamespacedName, keyValue) + if err != nil && k8serrors.IsNotFound(err) { + resource := &api.KeyValue{ + ObjectMeta: metav1.ObjectMeta{ + Name: resourceName, + Namespace: "default", + }, + Spec: api.KeyValueSpec{ + Name: keyValueName, + Replicas: 1, + History: 10, + TTL: "5m", + Compression: true, + Description: "test keyvalue", + Storage: "file", + }, + } + Expect(k8sClient.Create(ctx, resource)).To(Succeed()) + // Re-fetch KeyValue + Expect(k8sClient.Get(ctx, typeNamespacedName, keyValue)).To(Succeed()) + } + + By("checking precondition: nats keyvalue does not exist") + _, err = jsClient.KeyValue(ctx, keyValueName) + Expect(err).To(MatchError(jetstream.ErrBucketNotFound)) + + By("setting up the tested controller") + controller = &KeyValueReconciler{ + baseController, + } + }) + + AfterEach(func(ctx SpecContext) { + By("removing the test keyvalue resource") + resource := &api.KeyValue{} + err := k8sClient.Get(ctx, typeNamespacedName, resource) + if err != nil { + Expect(err).To(MatchError(k8serrors.IsNotFound, "Is not found")) + } else { + if controllerutil.ContainsFinalizer(resource, keyValueFinalizer) { + By("removing the finalizer") + controllerutil.RemoveFinalizer(resource, keyValueFinalizer) + Expect(k8sClient.Update(ctx, resource)).To(Succeed()) + } + + By("removing the keyvalue resource") + Expect(k8sClient.Delete(ctx, resource)). + To(SatisfyAny( + Succeed(), + MatchError(k8serrors.IsNotFound, "is not found"), + )) + } + + By("deleting the nats keyvalue store") + Expect(jsClient.DeleteKeyValue(ctx, keyValueName)). + To(SatisfyAny( + Succeed(), + MatchError(jetstream.ErrBucketNotFound), + )) + }) + + When("reconciling a not existing resource", func() { + It("should stop reconciliation without error", func(ctx SpecContext) { + By("reconciling the created resource") + result, err := controller.Reconcile(ctx, reconcile.Request{ + NamespacedName: types.NamespacedName{ + Namespace: "fake", + Name: "not-existing", + }, + }) + Expect(err).NotTo(HaveOccurred()) + Expect(result).To(Equal(ctrl.Result{})) + }) + }) + + When("reconciling a not initialized resource", func() { + It("should initialize a new resource", func(ctx SpecContext) { + By("re-queueing until it is initialized") + // Initialization can require multiple reconciliation loops + Eventually(func(ctx SpecContext) *api.KeyValue { + _, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName}) + Expect(err).NotTo(HaveOccurred()) + got := &api.KeyValue{} + Expect(k8sClient.Get(ctx, typeNamespacedName, got)).To(Succeed()) + return got + }).WithContext(ctx). + Should(SatisfyAll( + HaveField("Finalizers", HaveExactElements(keyValueFinalizer)), + HaveField("Status.Conditions", Not(BeEmpty())), + )) + + By("validating the ready condition") + // Fetch KeyValue + Expect(k8sClient.Get(ctx, typeNamespacedName, keyValue)).To(Succeed()) + Expect(keyValue.Status.Conditions).To(HaveLen(1)) + + assertReadyStateMatches(keyValue.Status.Conditions[0], v1.ConditionUnknown, "Reconciling", "Starting reconciliation", time.Now()) + }) + }) + + When("reconciling an initialized resource", func() { + BeforeEach(func(ctx SpecContext) { + By("initializing the keyvalue resource") + + By("setting the finalizer") + Expect(controllerutil.AddFinalizer(keyValue, keyValueFinalizer)).To(BeTrue()) + Expect(k8sClient.Update(ctx, keyValue)).To(Succeed()) + + By("setting an unknown ready state") + keyValue.Status.Conditions = []api.Condition{{ + Type: readyCondType, + Status: v1.ConditionUnknown, + Reason: "Test", + Message: "start condition", + LastTransitionTime: time.Now().Format(time.RFC3339Nano), + }} + Expect(k8sClient.Status().Update(ctx, keyValue)).To(Succeed()) + }) + + It("should create a new keyvalue store", func(ctx SpecContext) { + By("running Reconcile") + result, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName}) + Expect(err).NotTo(HaveOccurred()) + Expect(result.IsZero()).To(BeTrue()) + + // Fetch resource + Expect(k8sClient.Get(ctx, typeNamespacedName, keyValue)).To(Succeed()) + + By("checking if the ready state was updated") + Expect(keyValue.Status.Conditions).To(HaveLen(1)) + assertReadyStateMatches(keyValue.Status.Conditions[0], v1.ConditionTrue, "Reconciling", "created or updated", time.Now()) + + By("checking if the observed generation matches") + Expect(keyValue.Status.ObservedGeneration).To(Equal(keyValue.Generation)) + + By("checking if the keyvalue store was created") + natsKeyValue, err := jsClient.KeyValue(ctx, keyValueName) + Expect(err).NotTo(HaveOccurred()) + kvStatus, err := natsKeyValue.Status(ctx) + Expect(err).NotTo(HaveOccurred()) + Expect(kvStatus.Bucket()).To(Equal(keyValueName)) + Expect(kvStatus.History()).To(Equal(int64(10))) + Expect(kvStatus.TTL()).To(Equal(5 * time.Minute)) + Expect(kvStatus.IsCompressed()).To(BeTrue()) + }) + + When("PreventUpdate is set", func() { + BeforeEach(func(ctx SpecContext) { + By("setting preventDelete on the resource") + keyValue.Spec.PreventUpdate = true + Expect(k8sClient.Update(ctx, keyValue)).To(Succeed()) + }) + It("should create the keyvalue", func(ctx SpecContext) { + By("running Reconcile") + result, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName}) + Expect(err).NotTo(HaveOccurred()) + Expect(result.IsZero()).To(BeTrue()) + + By("checking that keyvalue was created") + _, err = jsClient.KeyValue(ctx, keyValueName) + Expect(err).NotTo(HaveOccurred()) + }) + It("should not update the keyvalue", func(ctx SpecContext) { + By("creating the keyvalue") + _, err := jsClient.CreateKeyValue(ctx, emptyKeyValueConfig) + Expect(err).NotTo(HaveOccurred()) + + By("running Reconcile") + result, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName}) + Expect(err).NotTo(HaveOccurred()) + Expect(result.IsZero()).To(BeTrue()) + + By("checking that keyvalue was not updated") + natsKeyValue, err := jsClient.KeyValue(ctx, keyValueName) + Expect(err).NotTo(HaveOccurred()) + s, err := natsKeyValue.Status(ctx) + Expect(err).NotTo(HaveOccurred()) + Expect(s.IsCompressed()).To(BeFalse()) + Expect(s.History()).To(BeEquivalentTo(int64(1))) + }) + }) + + When("read-only mode is enabled", func() { + BeforeEach(func(ctx SpecContext) { + By("setting read only on the controller") + readOnly, err := NewJSController(k8sClient, &NatsConfig{ServerURL: testServer.ClientURL()}, &Config{ReadOnly: true}) + Expect(err).NotTo(HaveOccurred()) + controller = &KeyValueReconciler{ + JetStreamController: readOnly, + } + }) + + It("should not create the keyvalue", func(ctx SpecContext) { + By("running Reconcile") + result, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName}) + Expect(err).NotTo(HaveOccurred()) + Expect(result.IsZero()).To(BeTrue()) + + By("checking that no keyvalue was created") + _, err = jsClient.KeyValue(ctx, keyValueName) + Expect(err).To(MatchError(jetstream.ErrBucketNotFound)) + }) + It("should not update the keyvalue", func(ctx SpecContext) { + By("creating the keyvalue") + _, err := jsClient.CreateKeyValue(ctx, emptyKeyValueConfig) + Expect(err).NotTo(HaveOccurred()) + + By("running Reconcile") + result, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName}) + Expect(err).NotTo(HaveOccurred()) + Expect(result.IsZero()).To(BeTrue()) + + By("checking that keyvalue was not updated") + natsKeyValue, err := jsClient.KeyValue(ctx, keyValueName) + Expect(err).NotTo(HaveOccurred()) + s, err := natsKeyValue.Status(ctx) + Expect(err).NotTo(HaveOccurred()) + Expect(s.IsCompressed()).To(BeFalse()) + Expect(s.History()).To(BeEquivalentTo(int64(1))) + }) + }) + + When("namespace restriction is enabled", func() { + BeforeEach(func(ctx SpecContext) { + By("setting a namespace on the resource") + namespaced, err := NewJSController(k8sClient, &NatsConfig{ServerURL: testServer.ClientURL()}, &Config{Namespace: "other-namespace"}) + Expect(err).NotTo(HaveOccurred()) + controller = &KeyValueReconciler{ + JetStreamController: namespaced, + } + }) + + It("should not create the keyvalue", func(ctx SpecContext) { + By("running Reconcile") + result, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName}) + Expect(err).NotTo(HaveOccurred()) + Expect(result.IsZero()).To(BeTrue()) + + By("checking that no keyvalue was created") + _, err = jsClient.KeyValue(ctx, keyValueName) + Expect(err).To(MatchError(jetstream.ErrBucketNotFound)) + }) + It("should not update the keyvalue", func(ctx SpecContext) { + By("creating the keyvalue") + _, err := jsClient.CreateKeyValue(ctx, emptyKeyValueConfig) + Expect(err).NotTo(HaveOccurred()) + + By("running Reconcile") + result, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName}) + Expect(err).NotTo(HaveOccurred()) + Expect(result.IsZero()).To(BeTrue()) + + By("checking that keyvalue was not updated") + natsKeyValue, err := jsClient.KeyValue(ctx, keyValueName) + Expect(err).NotTo(HaveOccurred()) + s, err := natsKeyValue.Status(ctx) + Expect(err).NotTo(HaveOccurred()) + Expect(s.IsCompressed()).To(BeFalse()) + Expect(s.History()).To(BeEquivalentTo(int64(1))) + }) + }) + + It("should update an existing keyvalue", func(ctx SpecContext) { + By("reconciling once to create the keyvalue") + result, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName}) + Expect(err).NotTo(HaveOccurred()) + Expect(result.IsZero()).To(BeTrue()) + + // Fetch resource + Expect(k8sClient.Get(ctx, typeNamespacedName, keyValue)).To(Succeed()) + previousTransitionTime := keyValue.Status.Conditions[0].LastTransitionTime + + By("updating the resource") + keyValue.Spec.Description = "new description" + keyValue.Spec.History = 50 + keyValue.Spec.TTL = "1h" + Expect(k8sClient.Update(ctx, keyValue)).To(Succeed()) + + By("reconciling the updated resource") + result, err = controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName}) + Expect(err).NotTo(HaveOccurred()) + Expect(result.IsZero()).To(BeTrue()) + + // Fetch resource + Expect(k8sClient.Get(ctx, typeNamespacedName, keyValue)).To(Succeed()) + + By("checking if the state transition time was not updated") + Expect(keyValue.Status.Conditions).To(HaveLen(1)) + Expect(keyValue.Status.Conditions[0].LastTransitionTime).To(Equal(previousTransitionTime)) + + By("checking if the observed generation matches") + Expect(keyValue.Status.ObservedGeneration).To(Equal(keyValue.Generation)) + + By("checking if the keyvalue was updated") + natsKeyValue, err := jsClient.KeyValue(ctx, keyValueName) + Expect(err).NotTo(HaveOccurred()) + + keyValueStatus, err := natsKeyValue.Status(ctx) + Expect(err).NotTo(HaveOccurred()) + Expect(keyValueStatus.Bucket()).To(Equal(keyValueName)) + Expect(keyValueStatus.History()).To(Equal(int64(50))) + Expect(keyValueStatus.TTL()).To(Equal(1 * time.Hour)) + Expect(keyValueStatus.IsCompressed()).To(BeTrue()) + }) + + It("should set an error state when the nats server is not available", func(ctx SpecContext) { + By("setting up controller with unavailable nats server") + // Setup client for not running server + // Use actual test server to ensure port not used by other service on test instance + sv := CreateTestServer() + base, err := NewJSController(k8sClient, &NatsConfig{ServerURL: sv.ClientURL()}, &Config{}) + Expect(err).NotTo(HaveOccurred()) + sv.Shutdown() + + controller := &KeyValueReconciler{ + base, + } + + By("reconciling resource") + result, err := controller.Reconcile(ctx, reconcile.Request{ + NamespacedName: typeNamespacedName, + }) + Expect(result).To(Equal(ctrl.Result{})) + Expect(err).To(HaveOccurred()) // Will be re-queued with back-off + + // Fetch resource + err = k8sClient.Get(ctx, typeNamespacedName, keyValue) + Expect(err).NotTo(HaveOccurred()) + + By("checking if the status was updated") + Expect(keyValue.Status.Conditions).To(HaveLen(1)) + assertReadyStateMatches( + keyValue.Status.Conditions[0], + v1.ConditionFalse, + "Errored", + "create or update keyvalue:", + time.Now(), + ) + + By("checking if the observed generation does not match") + Expect(keyValue.Status.ObservedGeneration).ToNot(Equal(keyValue.Generation)) + }) + + When("the resource is marked for deletion", func() { + BeforeEach(func(ctx SpecContext) { + By("marking the resource for deletion") + Expect(k8sClient.Delete(ctx, keyValue)).To(Succeed()) + Expect(k8sClient.Get(ctx, typeNamespacedName, keyValue)).To(Succeed()) // re-fetch after update + }) + + It("should succeed deleting a not existing keyvalue", func(ctx SpecContext) { + By("reconciling") + result, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName}) + Expect(err).NotTo(HaveOccurred()) + Expect(result.IsZero()).To(BeTrue()) + + By("checking that the resource is deleted") + Eventually(k8sClient.Get). + WithArguments(ctx, typeNamespacedName, keyValue). + ShouldNot(Succeed()) + }) + + When("the underlying keyvalue exists", func() { + BeforeEach(func(ctx SpecContext) { + By("creating the keyvalue on the nats server") + _, err := jsClient.CreateKeyValue(ctx, emptyKeyValueConfig) + Expect(err).NotTo(HaveOccurred()) + }) + + AfterEach(func(ctx SpecContext) { + err := jsClient.DeleteKeyValue(ctx, keyValueName) + if err != nil { + Expect(err).To(MatchError(jetstream.ErrBucketNotFound)) + } + }) + + It("should delete the keyvalue", func(ctx SpecContext) { + By("reconciling") + result, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName}) + Expect(err).NotTo(HaveOccurred()) + Expect(result.IsZero()).To(BeTrue()) + + By("checking that the keyvalue is deleted") + _, err = jsClient.KeyValue(ctx, keyValueName) + Expect(err).To(MatchError(jetstream.ErrBucketNotFound)) + + By("checking that the resource is deleted") + Eventually(k8sClient.Get). + WithArguments(ctx, typeNamespacedName, keyValue). + ShouldNot(Succeed()) + }) + + When("PreventDelete is set", func() { + BeforeEach(func(ctx SpecContext) { + By("setting preventDelete on the resource") + keyValue.Spec.PreventDelete = true + Expect(k8sClient.Update(ctx, keyValue)).To(Succeed()) + }) + It("Should delete the resource and not delete the nats keyvalue", func(ctx SpecContext) { + By("reconciling") + result, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName}) + Expect(err).NotTo(HaveOccurred()) + Expect(result.IsZero()).To(BeTrue()) + + By("checking that the keyvalue is not deleted") + _, err = jsClient.KeyValue(ctx, keyValueName) + Expect(err).NotTo(HaveOccurred()) + + By("checking that the resource is deleted") + Eventually(k8sClient.Get). + WithArguments(ctx, typeNamespacedName, keyValue). + ShouldNot(Succeed()) + }) + }) + + When("read only is set", func() { + BeforeEach(func(ctx SpecContext) { + By("setting read only on the controller") + readOnly, err := NewJSController(k8sClient, &NatsConfig{ServerURL: testServer.ClientURL()}, &Config{ReadOnly: true}) + Expect(err).NotTo(HaveOccurred()) + controller = &KeyValueReconciler{ + JetStreamController: readOnly, + } + }) + It("should delete the resource and not delete the keyvalue", func(ctx SpecContext) { + By("reconciling") + result, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName}) + Expect(err).NotTo(HaveOccurred()) + Expect(result.IsZero()).To(BeTrue()) + + By("checking that the keyvalue is not deleted") + _, err = jsClient.KeyValue(ctx, keyValueName) + Expect(err).NotTo(HaveOccurred()) + + By("checking that the resource is deleted") + Eventually(k8sClient.Get). + WithArguments(ctx, typeNamespacedName, keyValue). + ShouldNot(Succeed()) + }) + }) + + When("controller is restricted to different namespace", func() { + BeforeEach(func(ctx SpecContext) { + namespaced, err := NewJSController(k8sClient, &NatsConfig{ServerURL: testServer.ClientURL()}, &Config{Namespace: "other-namespace"}) + Expect(err).NotTo(HaveOccurred()) + controller = &KeyValueReconciler{ + JetStreamController: namespaced, + } + }) + It("should not delete the resource and keyvalue", func(ctx SpecContext) { + By("reconciling") + result, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName}) + Expect(err).NotTo(HaveOccurred()) + Expect(result.IsZero()).To(BeTrue()) + + By("checking that the keyvalue is not deleted") + _, err = jsClient.KeyValue(ctx, keyValueName) + Expect(err).NotTo(HaveOccurred()) + + By("checking that the finalizer is not removed") + Expect(k8sClient.Get(ctx, typeNamespacedName, keyValue)).To(Succeed()) + Expect(keyValue.Finalizers).To(ContainElement(keyValueFinalizer)) + }) + }) + }) + }) + + It("should update keyvalue on different server as specified in spec", func(ctx SpecContext) { + By("setting up the alternative server") + // Setup altClient for alternate server + altServer := CreateTestServer() + defer altServer.Shutdown() + + By("setting the server in the keyvalue spec") + keyValue.Spec.Servers = []string{altServer.ClientURL()} + Expect(k8sClient.Update(ctx, keyValue)).To(Succeed()) + + By("checking precondition, that the keyvalue does not yet exist") + _, err := jsClient.KeyValue(ctx, keyValueName) + Expect(err).To(MatchError(jetstream.ErrBucketNotFound)) + + By("reconciling the resource") + result, err := controller.Reconcile(ctx, reconcile.Request{ + NamespacedName: typeNamespacedName, + }) + Expect(err).NotTo(HaveOccurred()) + Expect(result.IsZero()).To(BeTrue()) + + By("checking if the keyvalue was created on the alternative server") + altClient, closer, err := CreateJetStreamClient(&NatsConfig{ServerURL: altServer.ClientURL()}, true) + defer closer.Close() + Expect(err).NotTo(HaveOccurred()) + + _, err = altClient.KeyValue(ctx, keyValueName) + Expect(err).NotTo(HaveOccurred()) + + By("checking that the keyvalue was NOT created on the original server") + _, err = jsClient.KeyValue(ctx, keyValueName) + Expect(err).To(MatchError(jetstream.ErrBucketNotFound)) + }) + }) +}) + +func Test_mapKVSpecToConfig(t *testing.T) { + date := time.Date(2024, 12, 3, 16, 55, 5, 0, time.UTC) + dateString := date.Format(time.RFC3339) + + tests := []struct { + name string + spec *api.KeyValueSpec + want jetstream.KeyValueConfig + wantErr bool + }{ + { + name: "emtpy spec", + spec: &api.KeyValueSpec{}, + want: jetstream.KeyValueConfig{}, + wantErr: false, + }, + { + name: "full spec", + spec: &api.KeyValueSpec{ + Account: "", + Creds: "", + Description: "kv description", + PreventDelete: false, + PreventUpdate: false, + History: 20, + MaxValueSize: 1024, + MaxBytes: 1048576, + TTL: "1h", + Mirror: &api.StreamSource{ + Name: "mirror", + OptStartSeq: 5, + OptStartTime: dateString, + FilterSubject: "orders", + ExternalAPIPrefix: "api", + ExternalDeliverPrefix: "deliver", + SubjectTransforms: []*api.SubjectTransform{{ + Source: "transform-source", + Dest: "transform-dest", + }}, + }, + Name: "kv-name", + Nkey: "", + Placement: &api.StreamPlacement{ + Cluster: "test-cluster", + Tags: []string{"tag"}, + }, + Replicas: 3, + Republish: &api.RePublish{ + Source: "re-publish-source", + Destination: "re-publish-dest", + HeadersOnly: true, + }, + Compression: true, + Servers: nil, + Sources: []*api.StreamSource{{ + Name: "source", + OptStartSeq: 5, + OptStartTime: dateString, + FilterSubject: "orders", + ExternalAPIPrefix: "api", + ExternalDeliverPrefix: "deliver", + SubjectTransforms: []*api.SubjectTransform{{ + Source: "transform-source", + Dest: "transform-dest", + }}, + }}, + Storage: "memory", + TLS: api.TLS{}, + }, + want: jetstream.KeyValueConfig{ + Bucket: "kv-name", + Description: "kv description", + MaxBytes: 1048576, + TTL: time.Hour, + MaxValueSize: 1024, + History: 20, + Storage: jetstream.MemoryStorage, + Replicas: 3, + Placement: &jetstream.Placement{ + Cluster: "test-cluster", + Tags: []string{"tag"}, + }, + Mirror: &jetstream.StreamSource{ + Name: "mirror", + OptStartSeq: 5, + OptStartTime: &date, + FilterSubject: "orders", + SubjectTransforms: []jetstream.SubjectTransformConfig{{ + Source: "transform-source", + Destination: "transform-dest", + }}, + External: &jetstream.ExternalStream{ + APIPrefix: "api", + DeliverPrefix: "deliver", + }, + Domain: "", + }, + Sources: []*jetstream.StreamSource{{ + Name: "source", + OptStartSeq: 5, + OptStartTime: &date, + FilterSubject: "orders", + SubjectTransforms: []jetstream.SubjectTransformConfig{{ + Source: "transform-source", + Destination: "transform-dest", + }}, + External: &jetstream.ExternalStream{ + APIPrefix: "api", + DeliverPrefix: "deliver", + }, + Domain: "", + }}, + Compression: true, + RePublish: &jetstream.RePublish{ + Source: "re-publish-source", + Destination: "re-publish-dest", + HeadersOnly: true, + }, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert := assert.New(t) + got, err := keyValueSpecToConfig(tt.spec) + if (err != nil) != tt.wantErr { + t.Errorf("keyValueSpecToConfig() error = %v, wantErr %v", err, tt.wantErr) + return + } + + // Compare nested structs + assert.EqualValues(tt.want, got) + }) + } +} diff --git a/internal/controller/register.go b/internal/controller/register.go index 5fa21b5a..8c5c8def 100644 --- a/internal/controller/register.go +++ b/internal/controller/register.go @@ -2,6 +2,7 @@ package controller import ( "fmt" + ctrl "sigs.k8s.io/controller-runtime" ) @@ -19,7 +20,6 @@ type Config struct { // natsCfg is specific to the nats server connection. // controllerCfg defines behaviour of the registered controllers. func RegisterAll(mgr ctrl.Manager, clientConfig *NatsConfig, config *Config) error { - baseController, err := NewJSController(mgr.GetClient(), clientConfig, config) if err != nil { return fmt.Errorf("create base jetstream controller: %w", err) diff --git a/internal/controller/stream_controller.go b/internal/controller/stream_controller.go index 1766c8ba..bd7ad3ad 100644 --- a/internal/controller/stream_controller.go +++ b/internal/controller/stream_controller.go @@ -20,6 +20,8 @@ import ( "context" "errors" "fmt" + "time" + "github.com/go-logr/logr" api "github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2" "github.com/nats-io/nats.go/jetstream" @@ -29,7 +31,6 @@ import ( ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" "sigs.k8s.io/controller-runtime/pkg/predicate" - "time" ) // StreamReconciler reconciles a Stream object @@ -69,7 +70,7 @@ func (r *StreamReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctr log = log.WithValues("streamName", stream.Spec.Name) // Update ready status to unknown when no status is set - if stream.Status.Conditions == nil || len(stream.Status.Conditions) == 0 { + if len(stream.Status.Conditions) == 0 { log.Info("Setting initial ready condition to unknown.") stream.Status.Conditions = updateReadyCondition(stream.Status.Conditions, v1.ConditionUnknown, "Reconciling", "Starting reconciliation") err := r.Status().Update(ctx, stream) @@ -115,7 +116,6 @@ func (r *StreamReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctr } func (r *StreamReconciler) deleteStream(ctx context.Context, log logr.Logger, stream *api.Stream) error { - // Set status to not false stream.Status.Conditions = updateReadyCondition(stream.Status.Conditions, v1.ConditionFalse, "Finalizing", "Performing finalizer operations.") if err := r.Status().Update(ctx, stream); err != nil { @@ -151,11 +151,9 @@ func (r *StreamReconciler) deleteStream(ctx context.Context, log logr.Logger, st } func (r *StreamReconciler) createOrUpdate(ctx context.Context, log logr.Logger, stream *api.Stream) error { - // Create or Update the stream based on the spec - if stream.Spec.PreventUpdate || r.ReadOnly() { + if r.ReadOnly() { log.Info("Skipping stream creation or update.", - "preventDelete", stream.Spec.PreventDelete, "read-only", r.ReadOnly(), ) return nil @@ -170,9 +168,31 @@ func (r *StreamReconciler) createOrUpdate(ctx context.Context, log logr.Logger, // CreateOrUpdateStream is called on every reconciliation when the stream is not to be deleted. // TODO(future-feature): Do we need to check if config differs? err = r.WithJetStreamClient(streamConnOpts(stream.Spec), func(js jetstream.JetStream) error { - log.Info("Creating or updating stream.") - _, err = js.CreateOrUpdateStream(ctx, targetConfig) - return err + exists := false + _, err := js.Stream(ctx, targetConfig.Name) + if err == nil { + exists = true + } else if !errors.Is(err, jetstream.ErrStreamNotFound) { + return err + } + + if !exists { + log.Info("Creating Stream.") + _, err := js.CreateStream(ctx, targetConfig) + return err + } + + if !stream.Spec.PreventUpdate { + log.Info("Updating Stream.") + _, err := js.UpdateStream(ctx, targetConfig) + return err + } else { + log.Info("Skipping Stream update.", + "preventUpdate", stream.Spec.PreventUpdate, + ) + } + + return nil }) if err != nil { err = fmt.Errorf("create or update stream: %w", err) @@ -212,7 +232,6 @@ func streamConnOpts(spec api.StreamSpec) *connectionOptions { // streamSpecToConfig creates a jetstream.StreamConfig matching the given stream resource spec func streamSpecToConfig(spec *api.StreamSpec) (jetstream.StreamConfig, error) { - // Set directly mapped fields config := jetstream.StreamConfig{ Name: spec.Name, @@ -293,7 +312,7 @@ func streamSpecToConfig(spec *api.StreamSpec) (jetstream.StreamConfig, error) { if spec.Mirror != nil { ss, err := mapStreamSource(spec.Mirror) if err != nil { - return jetstream.StreamConfig{}, fmt.Errorf("map mirror stream soruce: %w", err) + return jetstream.StreamConfig{}, fmt.Errorf("map mirror stream source: %w", err) } config.Mirror = ss } @@ -304,7 +323,7 @@ func streamSpecToConfig(spec *api.StreamSpec) (jetstream.StreamConfig, error) { for _, source := range spec.Sources { s, err := mapStreamSource(source) if err != nil { - return jetstream.StreamConfig{}, fmt.Errorf("map stream soruce: %w", err) + return jetstream.StreamConfig{}, fmt.Errorf("map stream source: %w", err) } config.Sources = append(config.Sources, s) } diff --git a/internal/controller/stream_controller_test.go b/internal/controller/stream_controller_test.go index e80d327b..e2506a7f 100644 --- a/internal/controller/stream_controller_test.go +++ b/internal/controller/stream_controller_test.go @@ -17,6 +17,9 @@ limitations under the License. package controller import ( + "testing" + "time" + "github.com/nats-io/nats.go/jetstream" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" @@ -27,8 +30,6 @@ import ( ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" "sigs.k8s.io/controller-runtime/pkg/reconcile" - "testing" - "time" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -36,7 +37,6 @@ import ( ) var _ = Describe("Stream Controller", func() { - // The test stream resource const resourceName = "test-stream" const streamName = "orders" @@ -136,9 +136,7 @@ var _ = Describe("Stream Controller", func() { }) When("reconciling a not initialized resource", func() { - It("should initialize a new resource", func(ctx SpecContext) { - By("re-queueing until it is initialized") // Initialization can require multiple reconciliation loops Eventually(func(ctx SpecContext) *api.Stream { @@ -160,11 +158,9 @@ var _ = Describe("Stream Controller", func() { assertReadyStateMatches(stream.Status.Conditions[0], v1.ConditionUnknown, "Reconciling", "Starting reconciliation", time.Now()) }) - }) When("reconciling an initialized resource", func() { - BeforeEach(func(ctx SpecContext) { By("initializing the stream resource") @@ -181,11 +177,9 @@ var _ = Describe("Stream Controller", func() { LastTransitionTime: time.Now().Format(time.RFC3339Nano), }} Expect(k8sClient.Status().Update(ctx, stream)).To(Succeed()) - }) It("should create a new stream", func(ctx SpecContext) { - By("running Reconcile") result, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName}) Expect(err).NotTo(HaveOccurred()) @@ -212,21 +206,20 @@ var _ = Describe("Stream Controller", func() { }) When("PreventUpdate is set", func() { - BeforeEach(func(ctx SpecContext) { By("setting preventDelete on the resource") stream.Spec.PreventUpdate = true Expect(k8sClient.Update(ctx, stream)).To(Succeed()) }) - It("should not create the stream", func(ctx SpecContext) { + It("should create the stream", func(ctx SpecContext) { By("running Reconcile") result, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName}) Expect(err).NotTo(HaveOccurred()) Expect(result.IsZero()).To(BeTrue()) - By("checking that no stream was created") + By("checking that stream was created") _, err = jsClient.Stream(ctx, streamName) - Expect(err).To(MatchError(jetstream.ErrStreamNotFound)) + Expect(err).NotTo(HaveOccurred()) }) It("should not update the stream", func(ctx SpecContext) { By("creating the stream") @@ -240,12 +233,12 @@ var _ = Describe("Stream Controller", func() { By("checking that stream was not updated") s, err := jsClient.Stream(ctx, streamName) + Expect(err).NotTo(HaveOccurred()) Expect(s.CachedInfo().Config.Description).To(BeEmpty()) }) }) When("read-only mode is enabled", func() { - BeforeEach(func(ctx SpecContext) { By("setting read only on the controller") readOnly, err := NewJSController(k8sClient, &NatsConfig{ServerURL: testServer.ClientURL()}, &Config{ReadOnly: true}) @@ -277,12 +270,12 @@ var _ = Describe("Stream Controller", func() { By("checking that stream was not updated") s, err := jsClient.Stream(ctx, streamName) + Expect(err).NotTo(HaveOccurred()) Expect(s.CachedInfo().Config.Description).To(BeEmpty()) }) }) When("namespace restriction is enabled", func() { - BeforeEach(func(ctx SpecContext) { By("setting a namespace on the resource") namespaced, err := NewJSController(k8sClient, &NatsConfig{ServerURL: testServer.ClientURL()}, &Config{Namespace: "other-namespace"}) @@ -314,6 +307,7 @@ var _ = Describe("Stream Controller", func() { By("checking that stream was not updated") s, err := jsClient.Stream(ctx, streamName) + Expect(err).NotTo(HaveOccurred()) Expect(s.CachedInfo().Config.Description).To(BeEmpty()) }) }) @@ -359,7 +353,6 @@ var _ = Describe("Stream Controller", func() { }) It("should set an error state when the nats server is not available", func(ctx SpecContext) { - By("setting up controller with unavailable nats server") // Setup client for not running server // Use actual test server to ensure port not used by other service on test instance @@ -398,7 +391,6 @@ var _ = Describe("Stream Controller", func() { }) When("the resource is marked for deletion", func() { - BeforeEach(func(ctx SpecContext) { By("marking the resource for deletion") Expect(k8sClient.Delete(ctx, stream)).To(Succeed()) @@ -480,7 +472,6 @@ var _ = Describe("Stream Controller", func() { } }) It("should delete the resource and not delete the stream", func(ctx SpecContext) { - By("reconciling") result, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName}) Expect(err).NotTo(HaveOccurred()) @@ -506,7 +497,6 @@ var _ = Describe("Stream Controller", func() { } }) It("should not delete the resource and stream", func(ctx SpecContext) { - By("reconciling") result, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName}) Expect(err).NotTo(HaveOccurred()) @@ -535,7 +525,7 @@ var _ = Describe("Stream Controller", func() { Expect(k8sClient.Update(ctx, stream)).To(Succeed()) By("checking precondition, that the stream does not yet exist") - got, err := jsClient.Stream(ctx, streamName) + _, err := jsClient.Stream(ctx, streamName) Expect(err).To(MatchError(jetstream.ErrStreamNotFound)) By("reconciling the resource") @@ -550,21 +540,19 @@ var _ = Describe("Stream Controller", func() { defer closer.Close() Expect(err).NotTo(HaveOccurred()) - got, err = altClient.Stream(ctx, streamName) + got, err := altClient.Stream(ctx, streamName) Expect(err).NotTo(HaveOccurred()) Expect(got.CachedInfo().Created).To(BeTemporally("~", time.Now(), time.Second)) By("checking that the stream was NOT created on the original server") _, err = jsClient.Stream(ctx, streamName) Expect(err).To(MatchError(jetstream.ErrStreamNotFound)) - }) }) }) func Test_mapSpecToConfig(t *testing.T) { - - date := time.Date(2024, 12, 03, 16, 55, 5, 0, time.UTC) + date := time.Date(2024, 12, 3, 16, 55, 5, 0, time.UTC) dateString := date.Format(time.RFC3339) tests := []struct { diff --git a/internal/controller/suite_test.go b/internal/controller/suite_test.go index dc7a59b9..75bf60f5 100644 --- a/internal/controller/suite_test.go +++ b/internal/controller/suite_test.go @@ -18,13 +18,14 @@ package controller import ( "fmt" - "github.com/nats-io/nats-server/v2/server" - "github.com/nats-io/nats.go/jetstream" "os" "path/filepath" "runtime" "testing" + "github.com/nats-io/nats-server/v2/server" + "github.com/nats-io/nats.go/jetstream" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" @@ -41,12 +42,14 @@ import ( // These tests use Ginkgo (BDD-style Go testing framework). Refer to // http://onsi.github.io/ginkgo/ to learn more about Ginkgo. -var cfg *rest.Config -var k8sClient client.Client -var testEnv *envtest.Environment -var testServer *server.Server -var jsClient jetstream.JetStream -var baseController JetStreamController +var ( + cfg *rest.Config + k8sClient client.Client + testEnv *envtest.Environment + testServer *server.Server + jsClient jetstream.JetStream + baseController JetStreamController +) func TestControllers(t *testing.T) { RegisterFailHandler(Fail) @@ -68,7 +71,7 @@ var _ = BeforeSuite(func() { // Note that you must have the required binaries setup under the bin directory to perform // the tests directly. When we run make test it will be setup and used automatically. BinaryAssetsDirectory: filepath.Join("..", "..", "bin", "k8s", - fmt.Sprintf("1.29.0-%s-%s", runtime.GOOS, runtime.GOARCH)), + fmt.Sprintf("1.31.0-%s-%s", runtime.GOOS, runtime.GOARCH)), } var err error @@ -90,7 +93,9 @@ var _ = BeforeSuite(func() { testNatsConfig := &NatsConfig{ServerURL: testServer.ClientURL()} baseController, err = NewJSController(k8sClient, testNatsConfig, &Config{}) + Expect(err).NotTo(HaveOccurred()) jsClient, _, err = CreateJetStreamClient(testNatsConfig, true) + Expect(err).NotTo(HaveOccurred()) }) var _ = AfterSuite(func() { diff --git a/internal/controller/types.go b/internal/controller/types.go index de166267..bc3b20c3 100644 --- a/internal/controller/types.go +++ b/internal/controller/types.go @@ -3,5 +3,6 @@ package controller const ( readyCondType = "Ready" streamFinalizer = "stream.nats.io/finalizer" + keyValueFinalizer = "keyvalue.nats.io/finalizer" consumerFinalizer = "consumer.nats.io/finalizer" ) diff --git a/pkg/bootconfig/bootconfig.go b/pkg/bootconfig/bootconfig.go index d4dd50ca..ddbe8c1c 100644 --- a/pkg/bootconfig/bootconfig.go +++ b/pkg/bootconfig/bootconfig.go @@ -121,7 +121,7 @@ func (c *Controller) Run(ctx context.Context) error { clientAdvertiseConfig := fmt.Sprintf("\nclient_advertise = \"%s\"\n\n", externalAddress) - err = os.WriteFile(c.opts.ClientAdvertiseFileName, []byte(clientAdvertiseConfig), 0644) + err = os.WriteFile(c.opts.ClientAdvertiseFileName, []byte(clientAdvertiseConfig), 0o644) if err != nil { return fmt.Errorf("Could not write client advertise config: %s", err) } @@ -129,7 +129,7 @@ func (c *Controller) Run(ctx context.Context) error { gatewayAdvertiseConfig := fmt.Sprintf("\nadvertise = \"%s\"\n\n", externalAddress) - err = os.WriteFile(c.opts.GatewayAdvertiseFileName, []byte(gatewayAdvertiseConfig), 0644) + err = os.WriteFile(c.opts.GatewayAdvertiseFileName, []byte(gatewayAdvertiseConfig), 0o644) if err != nil { return fmt.Errorf("Could not write gateway advertise config: %s", err) } diff --git a/pkg/jetstream/apis/jetstream/v1beta1/zz_generated.deepcopy.go b/pkg/jetstream/apis/jetstream/v1beta1/zz_generated.deepcopy.go index ce7e3e7e..25749171 100644 --- a/pkg/jetstream/apis/jetstream/v1beta1/zz_generated.deepcopy.go +++ b/pkg/jetstream/apis/jetstream/v1beta1/zz_generated.deepcopy.go @@ -1,7 +1,7 @@ //go:build !ignore_autogenerated // +build !ignore_autogenerated -// Copyright 2024 The NATS Authors +// Copyright 2025 The NATS Authors // 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 diff --git a/pkg/jetstream/apis/jetstream/v1beta2/keyvaluetypes.go b/pkg/jetstream/apis/jetstream/v1beta2/keyvaluetypes.go new file mode 100644 index 00000000..03bc9dbe --- /dev/null +++ b/pkg/jetstream/apis/jetstream/v1beta2/keyvaluetypes.go @@ -0,0 +1,55 @@ +package v1beta2 + +import ( + k8smeta "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// +genclient +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// Stream is a specification for a Stream resource +type KeyValue struct { + k8smeta.TypeMeta `json:",inline"` + k8smeta.ObjectMeta `json:"metadata,omitempty"` + + Spec KeyValueSpec `json:"spec"` + Status Status `json:"status"` +} + +func (s *KeyValue) GetSpec() interface{} { + return s.Spec +} + +// StreamSpec is the spec for a Stream resource +type KeyValueSpec struct { + Account string `json:"account"` + Compression bool `json:"compression"` + Creds string `json:"creds"` + Description string `json:"description"` + History int `json:"history"` + MaxBytes int `json:"maxBytes"` + MaxValueSize int `json:"maxValueSize"` + Mirror *StreamSource `json:"mirror"` + Name string `json:"name"` + Nkey string `json:"nkey"` + Placement *StreamPlacement `json:"placement"` + PreventDelete bool `json:"preventDelete"` + PreventUpdate bool `json:"preventUpdate"` + Replicas int `json:"replicas"` + Republish *RePublish `json:"republish"` + Servers []string `json:"servers"` + Sources []*StreamSource `json:"sources"` + Storage string `json:"storage"` + TLS TLS `json:"tls"` + TTL string `json:"ttl"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// KeyValueList is a list of Stream resources +type KeyValueList struct { + k8smeta.TypeMeta `json:",inline"` + k8smeta.ListMeta `json:"metadata"` + + Items []KeyValue `json:"items"` +} diff --git a/pkg/jetstream/apis/jetstream/v1beta2/register.go b/pkg/jetstream/apis/jetstream/v1beta2/register.go index a2f25342..881fe90a 100644 --- a/pkg/jetstream/apis/jetstream/v1beta2/register.go +++ b/pkg/jetstream/apis/jetstream/v1beta2/register.go @@ -33,6 +33,8 @@ func addKnownTypes(scheme *runtime.Scheme) error { scheme.AddKnownTypes(SchemeGroupVersion, &Stream{}, &StreamList{}, + &KeyValue{}, + &KeyValueList{}, &Consumer{}, &ConsumerList{}, &Account{}, diff --git a/pkg/jetstream/apis/jetstream/v1beta2/zz_generated.deepcopy.go b/pkg/jetstream/apis/jetstream/v1beta2/zz_generated.deepcopy.go index e0c13cc3..811aec4a 100644 --- a/pkg/jetstream/apis/jetstream/v1beta2/zz_generated.deepcopy.go +++ b/pkg/jetstream/apis/jetstream/v1beta2/zz_generated.deepcopy.go @@ -1,7 +1,7 @@ //go:build !ignore_autogenerated // +build !ignore_autogenerated -// Copyright 2024 The NATS Authors +// Copyright 2025 The NATS Authors // 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 @@ -273,6 +273,115 @@ func (in *CredsSecret) DeepCopy() *CredsSecret { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *KeyValue) DeepCopyInto(out *KeyValue) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KeyValue. +func (in *KeyValue) DeepCopy() *KeyValue { + if in == nil { + return nil + } + out := new(KeyValue) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *KeyValue) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *KeyValueList) DeepCopyInto(out *KeyValueList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]KeyValue, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KeyValueList. +func (in *KeyValueList) DeepCopy() *KeyValueList { + if in == nil { + return nil + } + out := new(KeyValueList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *KeyValueList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *KeyValueSpec) DeepCopyInto(out *KeyValueSpec) { + *out = *in + if in.Mirror != nil { + in, out := &in.Mirror, &out.Mirror + *out = new(StreamSource) + (*in).DeepCopyInto(*out) + } + if in.Placement != nil { + in, out := &in.Placement, &out.Placement + *out = new(StreamPlacement) + (*in).DeepCopyInto(*out) + } + if in.Republish != nil { + in, out := &in.Republish, &out.Republish + *out = new(RePublish) + **out = **in + } + if in.Servers != nil { + in, out := &in.Servers, &out.Servers + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.Sources != nil { + in, out := &in.Sources, &out.Sources + *out = make([]*StreamSource, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(StreamSource) + (*in).DeepCopyInto(*out) + } + } + } + in.TLS.DeepCopyInto(&out.TLS) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KeyValueSpec. +func (in *KeyValueSpec) DeepCopy() *KeyValueSpec { + if in == nil { + return nil + } + out := new(KeyValueSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *RePublish) DeepCopyInto(out *RePublish) { *out = *in diff --git a/pkg/jetstream/generated/applyconfiguration/internal/internal.go b/pkg/jetstream/generated/applyconfiguration/internal/internal.go index 63b69d16..083dbf96 100644 --- a/pkg/jetstream/generated/applyconfiguration/internal/internal.go +++ b/pkg/jetstream/generated/applyconfiguration/internal/internal.go @@ -1,4 +1,4 @@ -// Copyright 2024 The NATS Authors +// Copyright 2025 The NATS Authors // 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 @@ -16,8 +16,8 @@ package internal import ( - "fmt" - "sync" + fmt "fmt" + sync "sync" typed "sigs.k8s.io/structured-merge-diff/v4/typed" ) diff --git a/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/account.go b/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/account.go index 42b91e90..3ff85c0f 100644 --- a/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/account.go +++ b/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/account.go @@ -1,4 +1,4 @@ -// Copyright 2024 The NATS Authors +// Copyright 2025 The NATS Authors // 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 @@ -45,7 +45,7 @@ func Account(name, namespace string) *AccountApplyConfiguration { // and returns the receiver, so that objects can be built by chaining "With" function invocations. // If called multiple times, the Kind field is set to the value of the last call. func (b *AccountApplyConfiguration) WithKind(value string) *AccountApplyConfiguration { - b.Kind = &value + b.TypeMetaApplyConfiguration.Kind = &value return b } @@ -53,7 +53,7 @@ func (b *AccountApplyConfiguration) WithKind(value string) *AccountApplyConfigur // and returns the receiver, so that objects can be built by chaining "With" function invocations. // If called multiple times, the APIVersion field is set to the value of the last call. func (b *AccountApplyConfiguration) WithAPIVersion(value string) *AccountApplyConfiguration { - b.APIVersion = &value + b.TypeMetaApplyConfiguration.APIVersion = &value return b } @@ -62,7 +62,7 @@ func (b *AccountApplyConfiguration) WithAPIVersion(value string) *AccountApplyCo // If called multiple times, the Name field is set to the value of the last call. func (b *AccountApplyConfiguration) WithName(value string) *AccountApplyConfiguration { b.ensureObjectMetaApplyConfigurationExists() - b.Name = &value + b.ObjectMetaApplyConfiguration.Name = &value return b } @@ -71,7 +71,7 @@ func (b *AccountApplyConfiguration) WithName(value string) *AccountApplyConfigur // If called multiple times, the GenerateName field is set to the value of the last call. func (b *AccountApplyConfiguration) WithGenerateName(value string) *AccountApplyConfiguration { b.ensureObjectMetaApplyConfigurationExists() - b.GenerateName = &value + b.ObjectMetaApplyConfiguration.GenerateName = &value return b } @@ -80,7 +80,7 @@ func (b *AccountApplyConfiguration) WithGenerateName(value string) *AccountApply // If called multiple times, the Namespace field is set to the value of the last call. func (b *AccountApplyConfiguration) WithNamespace(value string) *AccountApplyConfiguration { b.ensureObjectMetaApplyConfigurationExists() - b.Namespace = &value + b.ObjectMetaApplyConfiguration.Namespace = &value return b } @@ -89,7 +89,7 @@ func (b *AccountApplyConfiguration) WithNamespace(value string) *AccountApplyCon // If called multiple times, the UID field is set to the value of the last call. func (b *AccountApplyConfiguration) WithUID(value types.UID) *AccountApplyConfiguration { b.ensureObjectMetaApplyConfigurationExists() - b.UID = &value + b.ObjectMetaApplyConfiguration.UID = &value return b } @@ -98,7 +98,7 @@ func (b *AccountApplyConfiguration) WithUID(value types.UID) *AccountApplyConfig // If called multiple times, the ResourceVersion field is set to the value of the last call. func (b *AccountApplyConfiguration) WithResourceVersion(value string) *AccountApplyConfiguration { b.ensureObjectMetaApplyConfigurationExists() - b.ResourceVersion = &value + b.ObjectMetaApplyConfiguration.ResourceVersion = &value return b } @@ -107,7 +107,7 @@ func (b *AccountApplyConfiguration) WithResourceVersion(value string) *AccountAp // If called multiple times, the Generation field is set to the value of the last call. func (b *AccountApplyConfiguration) WithGeneration(value int64) *AccountApplyConfiguration { b.ensureObjectMetaApplyConfigurationExists() - b.Generation = &value + b.ObjectMetaApplyConfiguration.Generation = &value return b } @@ -116,7 +116,7 @@ func (b *AccountApplyConfiguration) WithGeneration(value int64) *AccountApplyCon // If called multiple times, the CreationTimestamp field is set to the value of the last call. func (b *AccountApplyConfiguration) WithCreationTimestamp(value metav1.Time) *AccountApplyConfiguration { b.ensureObjectMetaApplyConfigurationExists() - b.CreationTimestamp = &value + b.ObjectMetaApplyConfiguration.CreationTimestamp = &value return b } @@ -125,7 +125,7 @@ func (b *AccountApplyConfiguration) WithCreationTimestamp(value metav1.Time) *Ac // If called multiple times, the DeletionTimestamp field is set to the value of the last call. func (b *AccountApplyConfiguration) WithDeletionTimestamp(value metav1.Time) *AccountApplyConfiguration { b.ensureObjectMetaApplyConfigurationExists() - b.DeletionTimestamp = &value + b.ObjectMetaApplyConfiguration.DeletionTimestamp = &value return b } @@ -134,7 +134,7 @@ func (b *AccountApplyConfiguration) WithDeletionTimestamp(value metav1.Time) *Ac // If called multiple times, the DeletionGracePeriodSeconds field is set to the value of the last call. func (b *AccountApplyConfiguration) WithDeletionGracePeriodSeconds(value int64) *AccountApplyConfiguration { b.ensureObjectMetaApplyConfigurationExists() - b.DeletionGracePeriodSeconds = &value + b.ObjectMetaApplyConfiguration.DeletionGracePeriodSeconds = &value return b } @@ -144,11 +144,11 @@ func (b *AccountApplyConfiguration) WithDeletionGracePeriodSeconds(value int64) // overwriting an existing map entries in Labels field with the same key. func (b *AccountApplyConfiguration) WithLabels(entries map[string]string) *AccountApplyConfiguration { b.ensureObjectMetaApplyConfigurationExists() - if b.Labels == nil && len(entries) > 0 { - b.Labels = make(map[string]string, len(entries)) + if b.ObjectMetaApplyConfiguration.Labels == nil && len(entries) > 0 { + b.ObjectMetaApplyConfiguration.Labels = make(map[string]string, len(entries)) } for k, v := range entries { - b.Labels[k] = v + b.ObjectMetaApplyConfiguration.Labels[k] = v } return b } @@ -159,11 +159,11 @@ func (b *AccountApplyConfiguration) WithLabels(entries map[string]string) *Accou // overwriting an existing map entries in Annotations field with the same key. func (b *AccountApplyConfiguration) WithAnnotations(entries map[string]string) *AccountApplyConfiguration { b.ensureObjectMetaApplyConfigurationExists() - if b.Annotations == nil && len(entries) > 0 { - b.Annotations = make(map[string]string, len(entries)) + if b.ObjectMetaApplyConfiguration.Annotations == nil && len(entries) > 0 { + b.ObjectMetaApplyConfiguration.Annotations = make(map[string]string, len(entries)) } for k, v := range entries { - b.Annotations[k] = v + b.ObjectMetaApplyConfiguration.Annotations[k] = v } return b } @@ -177,7 +177,7 @@ func (b *AccountApplyConfiguration) WithOwnerReferences(values ...*v1.OwnerRefer if values[i] == nil { panic("nil value passed to WithOwnerReferences") } - b.OwnerReferences = append(b.OwnerReferences, *values[i]) + b.ObjectMetaApplyConfiguration.OwnerReferences = append(b.ObjectMetaApplyConfiguration.OwnerReferences, *values[i]) } return b } @@ -188,7 +188,7 @@ func (b *AccountApplyConfiguration) WithOwnerReferences(values ...*v1.OwnerRefer func (b *AccountApplyConfiguration) WithFinalizers(values ...string) *AccountApplyConfiguration { b.ensureObjectMetaApplyConfigurationExists() for i := range values { - b.Finalizers = append(b.Finalizers, values[i]) + b.ObjectMetaApplyConfiguration.Finalizers = append(b.ObjectMetaApplyConfiguration.Finalizers, values[i]) } return b } @@ -218,5 +218,5 @@ func (b *AccountApplyConfiguration) WithStatus(value *StatusApplyConfiguration) // GetName retrieves the value of the Name field in the declarative configuration. func (b *AccountApplyConfiguration) GetName() *string { b.ensureObjectMetaApplyConfigurationExists() - return b.Name + return b.ObjectMetaApplyConfiguration.Name } diff --git a/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/accountspec.go b/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/accountspec.go index c2b2f865..ee3bc54d 100644 --- a/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/accountspec.go +++ b/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/accountspec.go @@ -1,4 +1,4 @@ -// Copyright 2024 The NATS Authors +// Copyright 2025 The NATS Authors // 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 diff --git a/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/condition.go b/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/condition.go index de299362..ec80bbe4 100644 --- a/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/condition.go +++ b/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/condition.go @@ -1,4 +1,4 @@ -// Copyright 2024 The NATS Authors +// Copyright 2025 The NATS Authors // 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 diff --git a/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/consumer.go b/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/consumer.go index e9cbc92f..731260f9 100644 --- a/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/consumer.go +++ b/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/consumer.go @@ -1,4 +1,4 @@ -// Copyright 2024 The NATS Authors +// Copyright 2025 The NATS Authors // 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 @@ -45,7 +45,7 @@ func Consumer(name, namespace string) *ConsumerApplyConfiguration { // and returns the receiver, so that objects can be built by chaining "With" function invocations. // If called multiple times, the Kind field is set to the value of the last call. func (b *ConsumerApplyConfiguration) WithKind(value string) *ConsumerApplyConfiguration { - b.Kind = &value + b.TypeMetaApplyConfiguration.Kind = &value return b } @@ -53,7 +53,7 @@ func (b *ConsumerApplyConfiguration) WithKind(value string) *ConsumerApplyConfig // and returns the receiver, so that objects can be built by chaining "With" function invocations. // If called multiple times, the APIVersion field is set to the value of the last call. func (b *ConsumerApplyConfiguration) WithAPIVersion(value string) *ConsumerApplyConfiguration { - b.APIVersion = &value + b.TypeMetaApplyConfiguration.APIVersion = &value return b } @@ -62,7 +62,7 @@ func (b *ConsumerApplyConfiguration) WithAPIVersion(value string) *ConsumerApply // If called multiple times, the Name field is set to the value of the last call. func (b *ConsumerApplyConfiguration) WithName(value string) *ConsumerApplyConfiguration { b.ensureObjectMetaApplyConfigurationExists() - b.Name = &value + b.ObjectMetaApplyConfiguration.Name = &value return b } @@ -71,7 +71,7 @@ func (b *ConsumerApplyConfiguration) WithName(value string) *ConsumerApplyConfig // If called multiple times, the GenerateName field is set to the value of the last call. func (b *ConsumerApplyConfiguration) WithGenerateName(value string) *ConsumerApplyConfiguration { b.ensureObjectMetaApplyConfigurationExists() - b.GenerateName = &value + b.ObjectMetaApplyConfiguration.GenerateName = &value return b } @@ -80,7 +80,7 @@ func (b *ConsumerApplyConfiguration) WithGenerateName(value string) *ConsumerApp // If called multiple times, the Namespace field is set to the value of the last call. func (b *ConsumerApplyConfiguration) WithNamespace(value string) *ConsumerApplyConfiguration { b.ensureObjectMetaApplyConfigurationExists() - b.Namespace = &value + b.ObjectMetaApplyConfiguration.Namespace = &value return b } @@ -89,7 +89,7 @@ func (b *ConsumerApplyConfiguration) WithNamespace(value string) *ConsumerApplyC // If called multiple times, the UID field is set to the value of the last call. func (b *ConsumerApplyConfiguration) WithUID(value types.UID) *ConsumerApplyConfiguration { b.ensureObjectMetaApplyConfigurationExists() - b.UID = &value + b.ObjectMetaApplyConfiguration.UID = &value return b } @@ -98,7 +98,7 @@ func (b *ConsumerApplyConfiguration) WithUID(value types.UID) *ConsumerApplyConf // If called multiple times, the ResourceVersion field is set to the value of the last call. func (b *ConsumerApplyConfiguration) WithResourceVersion(value string) *ConsumerApplyConfiguration { b.ensureObjectMetaApplyConfigurationExists() - b.ResourceVersion = &value + b.ObjectMetaApplyConfiguration.ResourceVersion = &value return b } @@ -107,7 +107,7 @@ func (b *ConsumerApplyConfiguration) WithResourceVersion(value string) *Consumer // If called multiple times, the Generation field is set to the value of the last call. func (b *ConsumerApplyConfiguration) WithGeneration(value int64) *ConsumerApplyConfiguration { b.ensureObjectMetaApplyConfigurationExists() - b.Generation = &value + b.ObjectMetaApplyConfiguration.Generation = &value return b } @@ -116,7 +116,7 @@ func (b *ConsumerApplyConfiguration) WithGeneration(value int64) *ConsumerApplyC // If called multiple times, the CreationTimestamp field is set to the value of the last call. func (b *ConsumerApplyConfiguration) WithCreationTimestamp(value metav1.Time) *ConsumerApplyConfiguration { b.ensureObjectMetaApplyConfigurationExists() - b.CreationTimestamp = &value + b.ObjectMetaApplyConfiguration.CreationTimestamp = &value return b } @@ -125,7 +125,7 @@ func (b *ConsumerApplyConfiguration) WithCreationTimestamp(value metav1.Time) *C // If called multiple times, the DeletionTimestamp field is set to the value of the last call. func (b *ConsumerApplyConfiguration) WithDeletionTimestamp(value metav1.Time) *ConsumerApplyConfiguration { b.ensureObjectMetaApplyConfigurationExists() - b.DeletionTimestamp = &value + b.ObjectMetaApplyConfiguration.DeletionTimestamp = &value return b } @@ -134,7 +134,7 @@ func (b *ConsumerApplyConfiguration) WithDeletionTimestamp(value metav1.Time) *C // If called multiple times, the DeletionGracePeriodSeconds field is set to the value of the last call. func (b *ConsumerApplyConfiguration) WithDeletionGracePeriodSeconds(value int64) *ConsumerApplyConfiguration { b.ensureObjectMetaApplyConfigurationExists() - b.DeletionGracePeriodSeconds = &value + b.ObjectMetaApplyConfiguration.DeletionGracePeriodSeconds = &value return b } @@ -144,11 +144,11 @@ func (b *ConsumerApplyConfiguration) WithDeletionGracePeriodSeconds(value int64) // overwriting an existing map entries in Labels field with the same key. func (b *ConsumerApplyConfiguration) WithLabels(entries map[string]string) *ConsumerApplyConfiguration { b.ensureObjectMetaApplyConfigurationExists() - if b.Labels == nil && len(entries) > 0 { - b.Labels = make(map[string]string, len(entries)) + if b.ObjectMetaApplyConfiguration.Labels == nil && len(entries) > 0 { + b.ObjectMetaApplyConfiguration.Labels = make(map[string]string, len(entries)) } for k, v := range entries { - b.Labels[k] = v + b.ObjectMetaApplyConfiguration.Labels[k] = v } return b } @@ -159,11 +159,11 @@ func (b *ConsumerApplyConfiguration) WithLabels(entries map[string]string) *Cons // overwriting an existing map entries in Annotations field with the same key. func (b *ConsumerApplyConfiguration) WithAnnotations(entries map[string]string) *ConsumerApplyConfiguration { b.ensureObjectMetaApplyConfigurationExists() - if b.Annotations == nil && len(entries) > 0 { - b.Annotations = make(map[string]string, len(entries)) + if b.ObjectMetaApplyConfiguration.Annotations == nil && len(entries) > 0 { + b.ObjectMetaApplyConfiguration.Annotations = make(map[string]string, len(entries)) } for k, v := range entries { - b.Annotations[k] = v + b.ObjectMetaApplyConfiguration.Annotations[k] = v } return b } @@ -177,7 +177,7 @@ func (b *ConsumerApplyConfiguration) WithOwnerReferences(values ...*v1.OwnerRefe if values[i] == nil { panic("nil value passed to WithOwnerReferences") } - b.OwnerReferences = append(b.OwnerReferences, *values[i]) + b.ObjectMetaApplyConfiguration.OwnerReferences = append(b.ObjectMetaApplyConfiguration.OwnerReferences, *values[i]) } return b } @@ -188,7 +188,7 @@ func (b *ConsumerApplyConfiguration) WithOwnerReferences(values ...*v1.OwnerRefe func (b *ConsumerApplyConfiguration) WithFinalizers(values ...string) *ConsumerApplyConfiguration { b.ensureObjectMetaApplyConfigurationExists() for i := range values { - b.Finalizers = append(b.Finalizers, values[i]) + b.ObjectMetaApplyConfiguration.Finalizers = append(b.ObjectMetaApplyConfiguration.Finalizers, values[i]) } return b } @@ -218,5 +218,5 @@ func (b *ConsumerApplyConfiguration) WithStatus(value *StatusApplyConfiguration) // GetName retrieves the value of the Name field in the declarative configuration. func (b *ConsumerApplyConfiguration) GetName() *string { b.ensureObjectMetaApplyConfigurationExists() - return b.Name + return b.ObjectMetaApplyConfiguration.Name } diff --git a/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/consumerspec.go b/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/consumerspec.go index 8700fe3e..ca97ae62 100644 --- a/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/consumerspec.go +++ b/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/consumerspec.go @@ -1,4 +1,4 @@ -// Copyright 2024 The NATS Authors +// Copyright 2025 The NATS Authors // 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 diff --git a/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/credssecret.go b/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/credssecret.go index 467fb7a4..f7f3de8c 100644 --- a/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/credssecret.go +++ b/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/credssecret.go @@ -1,4 +1,4 @@ -// Copyright 2024 The NATS Authors +// Copyright 2025 The NATS Authors // 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 diff --git a/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/keyvalue.go b/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/keyvalue.go new file mode 100644 index 00000000..6b13dff1 --- /dev/null +++ b/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/keyvalue.go @@ -0,0 +1,222 @@ +// Copyright 2025 The NATS Authors +// 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. + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1beta2 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + v1 "k8s.io/client-go/applyconfigurations/meta/v1" +) + +// KeyValueApplyConfiguration represents a declarative configuration of the KeyValue type for use +// with apply. +type KeyValueApplyConfiguration struct { + v1.TypeMetaApplyConfiguration `json:",inline"` + *v1.ObjectMetaApplyConfiguration `json:"metadata,omitempty"` + Spec *KeyValueSpecApplyConfiguration `json:"spec,omitempty"` + Status *StatusApplyConfiguration `json:"status,omitempty"` +} + +// KeyValue constructs a declarative configuration of the KeyValue type for use with +// apply. +func KeyValue(name, namespace string) *KeyValueApplyConfiguration { + b := &KeyValueApplyConfiguration{} + b.WithName(name) + b.WithNamespace(namespace) + b.WithKind("KeyValue") + b.WithAPIVersion("jetstream.nats.io/v1beta2") + return b +} + +// WithKind sets the Kind field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Kind field is set to the value of the last call. +func (b *KeyValueApplyConfiguration) WithKind(value string) *KeyValueApplyConfiguration { + b.TypeMetaApplyConfiguration.Kind = &value + return b +} + +// WithAPIVersion sets the APIVersion field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the APIVersion field is set to the value of the last call. +func (b *KeyValueApplyConfiguration) WithAPIVersion(value string) *KeyValueApplyConfiguration { + b.TypeMetaApplyConfiguration.APIVersion = &value + return b +} + +// WithName sets the Name field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Name field is set to the value of the last call. +func (b *KeyValueApplyConfiguration) WithName(value string) *KeyValueApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.Name = &value + return b +} + +// WithGenerateName sets the GenerateName field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the GenerateName field is set to the value of the last call. +func (b *KeyValueApplyConfiguration) WithGenerateName(value string) *KeyValueApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.GenerateName = &value + return b +} + +// WithNamespace sets the Namespace field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Namespace field is set to the value of the last call. +func (b *KeyValueApplyConfiguration) WithNamespace(value string) *KeyValueApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.Namespace = &value + return b +} + +// WithUID sets the UID field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the UID field is set to the value of the last call. +func (b *KeyValueApplyConfiguration) WithUID(value types.UID) *KeyValueApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.UID = &value + return b +} + +// WithResourceVersion sets the ResourceVersion field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ResourceVersion field is set to the value of the last call. +func (b *KeyValueApplyConfiguration) WithResourceVersion(value string) *KeyValueApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.ResourceVersion = &value + return b +} + +// WithGeneration sets the Generation field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Generation field is set to the value of the last call. +func (b *KeyValueApplyConfiguration) WithGeneration(value int64) *KeyValueApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.Generation = &value + return b +} + +// WithCreationTimestamp sets the CreationTimestamp field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the CreationTimestamp field is set to the value of the last call. +func (b *KeyValueApplyConfiguration) WithCreationTimestamp(value metav1.Time) *KeyValueApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.CreationTimestamp = &value + return b +} + +// WithDeletionTimestamp sets the DeletionTimestamp field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the DeletionTimestamp field is set to the value of the last call. +func (b *KeyValueApplyConfiguration) WithDeletionTimestamp(value metav1.Time) *KeyValueApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.DeletionTimestamp = &value + return b +} + +// WithDeletionGracePeriodSeconds sets the DeletionGracePeriodSeconds field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the DeletionGracePeriodSeconds field is set to the value of the last call. +func (b *KeyValueApplyConfiguration) WithDeletionGracePeriodSeconds(value int64) *KeyValueApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.DeletionGracePeriodSeconds = &value + return b +} + +// WithLabels puts the entries into the Labels field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, the entries provided by each call will be put on the Labels field, +// overwriting an existing map entries in Labels field with the same key. +func (b *KeyValueApplyConfiguration) WithLabels(entries map[string]string) *KeyValueApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + if b.ObjectMetaApplyConfiguration.Labels == nil && len(entries) > 0 { + b.ObjectMetaApplyConfiguration.Labels = make(map[string]string, len(entries)) + } + for k, v := range entries { + b.ObjectMetaApplyConfiguration.Labels[k] = v + } + return b +} + +// WithAnnotations puts the entries into the Annotations field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, the entries provided by each call will be put on the Annotations field, +// overwriting an existing map entries in Annotations field with the same key. +func (b *KeyValueApplyConfiguration) WithAnnotations(entries map[string]string) *KeyValueApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + if b.ObjectMetaApplyConfiguration.Annotations == nil && len(entries) > 0 { + b.ObjectMetaApplyConfiguration.Annotations = make(map[string]string, len(entries)) + } + for k, v := range entries { + b.ObjectMetaApplyConfiguration.Annotations[k] = v + } + return b +} + +// WithOwnerReferences adds the given value to the OwnerReferences field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the OwnerReferences field. +func (b *KeyValueApplyConfiguration) WithOwnerReferences(values ...*v1.OwnerReferenceApplyConfiguration) *KeyValueApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + for i := range values { + if values[i] == nil { + panic("nil value passed to WithOwnerReferences") + } + b.ObjectMetaApplyConfiguration.OwnerReferences = append(b.ObjectMetaApplyConfiguration.OwnerReferences, *values[i]) + } + return b +} + +// WithFinalizers adds the given value to the Finalizers field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the Finalizers field. +func (b *KeyValueApplyConfiguration) WithFinalizers(values ...string) *KeyValueApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + for i := range values { + b.ObjectMetaApplyConfiguration.Finalizers = append(b.ObjectMetaApplyConfiguration.Finalizers, values[i]) + } + return b +} + +func (b *KeyValueApplyConfiguration) ensureObjectMetaApplyConfigurationExists() { + if b.ObjectMetaApplyConfiguration == nil { + b.ObjectMetaApplyConfiguration = &v1.ObjectMetaApplyConfiguration{} + } +} + +// WithSpec sets the Spec field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Spec field is set to the value of the last call. +func (b *KeyValueApplyConfiguration) WithSpec(value *KeyValueSpecApplyConfiguration) *KeyValueApplyConfiguration { + b.Spec = value + return b +} + +// WithStatus sets the Status field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Status field is set to the value of the last call. +func (b *KeyValueApplyConfiguration) WithStatus(value *StatusApplyConfiguration) *KeyValueApplyConfiguration { + b.Status = value + return b +} + +// GetName retrieves the value of the Name field in the declarative configuration. +func (b *KeyValueApplyConfiguration) GetName() *string { + b.ensureObjectMetaApplyConfigurationExists() + return b.ObjectMetaApplyConfiguration.Name +} diff --git a/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/keyvaluespec.go b/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/keyvaluespec.go new file mode 100644 index 00000000..a35967bf --- /dev/null +++ b/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/keyvaluespec.go @@ -0,0 +1,218 @@ +// Copyright 2025 The NATS Authors +// 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. + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1beta2 + +import ( + jetstreamv1beta2 "github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2" +) + +// KeyValueSpecApplyConfiguration represents a declarative configuration of the KeyValueSpec type for use +// with apply. +type KeyValueSpecApplyConfiguration struct { + Account *string `json:"account,omitempty"` + Compression *bool `json:"compression,omitempty"` + Creds *string `json:"creds,omitempty"` + Description *string `json:"description,omitempty"` + History *int `json:"history,omitempty"` + MaxBytes *int `json:"maxBytes,omitempty"` + MaxValueSize *int `json:"maxValueSize,omitempty"` + Mirror *StreamSourceApplyConfiguration `json:"mirror,omitempty"` + Name *string `json:"name,omitempty"` + Nkey *string `json:"nkey,omitempty"` + Placement *StreamPlacementApplyConfiguration `json:"placement,omitempty"` + PreventDelete *bool `json:"preventDelete,omitempty"` + PreventUpdate *bool `json:"preventUpdate,omitempty"` + Replicas *int `json:"replicas,omitempty"` + Republish *RePublishApplyConfiguration `json:"republish,omitempty"` + Servers []string `json:"servers,omitempty"` + Sources []*jetstreamv1beta2.StreamSource `json:"sources,omitempty"` + Storage *string `json:"storage,omitempty"` + TLS *TLSApplyConfiguration `json:"tls,omitempty"` + TTL *string `json:"ttl,omitempty"` +} + +// KeyValueSpecApplyConfiguration constructs a declarative configuration of the KeyValueSpec type for use with +// apply. +func KeyValueSpec() *KeyValueSpecApplyConfiguration { + return &KeyValueSpecApplyConfiguration{} +} + +// WithAccount sets the Account field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Account field is set to the value of the last call. +func (b *KeyValueSpecApplyConfiguration) WithAccount(value string) *KeyValueSpecApplyConfiguration { + b.Account = &value + return b +} + +// WithCompression sets the Compression field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Compression field is set to the value of the last call. +func (b *KeyValueSpecApplyConfiguration) WithCompression(value bool) *KeyValueSpecApplyConfiguration { + b.Compression = &value + return b +} + +// WithCreds sets the Creds field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Creds field is set to the value of the last call. +func (b *KeyValueSpecApplyConfiguration) WithCreds(value string) *KeyValueSpecApplyConfiguration { + b.Creds = &value + return b +} + +// WithDescription sets the Description field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Description field is set to the value of the last call. +func (b *KeyValueSpecApplyConfiguration) WithDescription(value string) *KeyValueSpecApplyConfiguration { + b.Description = &value + return b +} + +// WithHistory sets the History field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the History field is set to the value of the last call. +func (b *KeyValueSpecApplyConfiguration) WithHistory(value int) *KeyValueSpecApplyConfiguration { + b.History = &value + return b +} + +// WithMaxBytes sets the MaxBytes field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the MaxBytes field is set to the value of the last call. +func (b *KeyValueSpecApplyConfiguration) WithMaxBytes(value int) *KeyValueSpecApplyConfiguration { + b.MaxBytes = &value + return b +} + +// WithMaxValueSize sets the MaxValueSize field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the MaxValueSize field is set to the value of the last call. +func (b *KeyValueSpecApplyConfiguration) WithMaxValueSize(value int) *KeyValueSpecApplyConfiguration { + b.MaxValueSize = &value + return b +} + +// WithMirror sets the Mirror field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Mirror field is set to the value of the last call. +func (b *KeyValueSpecApplyConfiguration) WithMirror(value *StreamSourceApplyConfiguration) *KeyValueSpecApplyConfiguration { + b.Mirror = value + return b +} + +// WithName sets the Name field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Name field is set to the value of the last call. +func (b *KeyValueSpecApplyConfiguration) WithName(value string) *KeyValueSpecApplyConfiguration { + b.Name = &value + return b +} + +// WithNkey sets the Nkey field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Nkey field is set to the value of the last call. +func (b *KeyValueSpecApplyConfiguration) WithNkey(value string) *KeyValueSpecApplyConfiguration { + b.Nkey = &value + return b +} + +// WithPlacement sets the Placement field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Placement field is set to the value of the last call. +func (b *KeyValueSpecApplyConfiguration) WithPlacement(value *StreamPlacementApplyConfiguration) *KeyValueSpecApplyConfiguration { + b.Placement = value + return b +} + +// WithPreventDelete sets the PreventDelete field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the PreventDelete field is set to the value of the last call. +func (b *KeyValueSpecApplyConfiguration) WithPreventDelete(value bool) *KeyValueSpecApplyConfiguration { + b.PreventDelete = &value + return b +} + +// WithPreventUpdate sets the PreventUpdate field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the PreventUpdate field is set to the value of the last call. +func (b *KeyValueSpecApplyConfiguration) WithPreventUpdate(value bool) *KeyValueSpecApplyConfiguration { + b.PreventUpdate = &value + return b +} + +// WithReplicas sets the Replicas field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Replicas field is set to the value of the last call. +func (b *KeyValueSpecApplyConfiguration) WithReplicas(value int) *KeyValueSpecApplyConfiguration { + b.Replicas = &value + return b +} + +// WithRepublish sets the Republish field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Republish field is set to the value of the last call. +func (b *KeyValueSpecApplyConfiguration) WithRepublish(value *RePublishApplyConfiguration) *KeyValueSpecApplyConfiguration { + b.Republish = value + return b +} + +// WithServers adds the given value to the Servers field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the Servers field. +func (b *KeyValueSpecApplyConfiguration) WithServers(values ...string) *KeyValueSpecApplyConfiguration { + for i := range values { + b.Servers = append(b.Servers, values[i]) + } + return b +} + +// WithSources adds the given value to the Sources field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the Sources field. +func (b *KeyValueSpecApplyConfiguration) WithSources(values ...**jetstreamv1beta2.StreamSource) *KeyValueSpecApplyConfiguration { + for i := range values { + if values[i] == nil { + panic("nil value passed to WithSources") + } + b.Sources = append(b.Sources, *values[i]) + } + return b +} + +// WithStorage sets the Storage field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Storage field is set to the value of the last call. +func (b *KeyValueSpecApplyConfiguration) WithStorage(value string) *KeyValueSpecApplyConfiguration { + b.Storage = &value + return b +} + +// WithTLS sets the TLS field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the TLS field is set to the value of the last call. +func (b *KeyValueSpecApplyConfiguration) WithTLS(value *TLSApplyConfiguration) *KeyValueSpecApplyConfiguration { + b.TLS = value + return b +} + +// WithTTL sets the TTL field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the TTL field is set to the value of the last call. +func (b *KeyValueSpecApplyConfiguration) WithTTL(value string) *KeyValueSpecApplyConfiguration { + b.TTL = &value + return b +} diff --git a/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/republish.go b/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/republish.go index 45873e7e..6a57feb2 100644 --- a/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/republish.go +++ b/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/republish.go @@ -1,4 +1,4 @@ -// Copyright 2024 The NATS Authors +// Copyright 2025 The NATS Authors // 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 diff --git a/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/secretref.go b/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/secretref.go index 57754747..e1ce93bd 100644 --- a/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/secretref.go +++ b/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/secretref.go @@ -1,4 +1,4 @@ -// Copyright 2024 The NATS Authors +// Copyright 2025 The NATS Authors // 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 diff --git a/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/status.go b/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/status.go index cebedf26..b7b8e484 100644 --- a/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/status.go +++ b/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/status.go @@ -1,4 +1,4 @@ -// Copyright 2024 The NATS Authors +// Copyright 2025 The NATS Authors // 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 diff --git a/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/stream.go b/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/stream.go index dc987f23..f363d091 100644 --- a/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/stream.go +++ b/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/stream.go @@ -1,4 +1,4 @@ -// Copyright 2024 The NATS Authors +// Copyright 2025 The NATS Authors // 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 @@ -45,7 +45,7 @@ func Stream(name, namespace string) *StreamApplyConfiguration { // and returns the receiver, so that objects can be built by chaining "With" function invocations. // If called multiple times, the Kind field is set to the value of the last call. func (b *StreamApplyConfiguration) WithKind(value string) *StreamApplyConfiguration { - b.Kind = &value + b.TypeMetaApplyConfiguration.Kind = &value return b } @@ -53,7 +53,7 @@ func (b *StreamApplyConfiguration) WithKind(value string) *StreamApplyConfigurat // and returns the receiver, so that objects can be built by chaining "With" function invocations. // If called multiple times, the APIVersion field is set to the value of the last call. func (b *StreamApplyConfiguration) WithAPIVersion(value string) *StreamApplyConfiguration { - b.APIVersion = &value + b.TypeMetaApplyConfiguration.APIVersion = &value return b } @@ -62,7 +62,7 @@ func (b *StreamApplyConfiguration) WithAPIVersion(value string) *StreamApplyConf // If called multiple times, the Name field is set to the value of the last call. func (b *StreamApplyConfiguration) WithName(value string) *StreamApplyConfiguration { b.ensureObjectMetaApplyConfigurationExists() - b.Name = &value + b.ObjectMetaApplyConfiguration.Name = &value return b } @@ -71,7 +71,7 @@ func (b *StreamApplyConfiguration) WithName(value string) *StreamApplyConfigurat // If called multiple times, the GenerateName field is set to the value of the last call. func (b *StreamApplyConfiguration) WithGenerateName(value string) *StreamApplyConfiguration { b.ensureObjectMetaApplyConfigurationExists() - b.GenerateName = &value + b.ObjectMetaApplyConfiguration.GenerateName = &value return b } @@ -80,7 +80,7 @@ func (b *StreamApplyConfiguration) WithGenerateName(value string) *StreamApplyCo // If called multiple times, the Namespace field is set to the value of the last call. func (b *StreamApplyConfiguration) WithNamespace(value string) *StreamApplyConfiguration { b.ensureObjectMetaApplyConfigurationExists() - b.Namespace = &value + b.ObjectMetaApplyConfiguration.Namespace = &value return b } @@ -89,7 +89,7 @@ func (b *StreamApplyConfiguration) WithNamespace(value string) *StreamApplyConfi // If called multiple times, the UID field is set to the value of the last call. func (b *StreamApplyConfiguration) WithUID(value types.UID) *StreamApplyConfiguration { b.ensureObjectMetaApplyConfigurationExists() - b.UID = &value + b.ObjectMetaApplyConfiguration.UID = &value return b } @@ -98,7 +98,7 @@ func (b *StreamApplyConfiguration) WithUID(value types.UID) *StreamApplyConfigur // If called multiple times, the ResourceVersion field is set to the value of the last call. func (b *StreamApplyConfiguration) WithResourceVersion(value string) *StreamApplyConfiguration { b.ensureObjectMetaApplyConfigurationExists() - b.ResourceVersion = &value + b.ObjectMetaApplyConfiguration.ResourceVersion = &value return b } @@ -107,7 +107,7 @@ func (b *StreamApplyConfiguration) WithResourceVersion(value string) *StreamAppl // If called multiple times, the Generation field is set to the value of the last call. func (b *StreamApplyConfiguration) WithGeneration(value int64) *StreamApplyConfiguration { b.ensureObjectMetaApplyConfigurationExists() - b.Generation = &value + b.ObjectMetaApplyConfiguration.Generation = &value return b } @@ -116,7 +116,7 @@ func (b *StreamApplyConfiguration) WithGeneration(value int64) *StreamApplyConfi // If called multiple times, the CreationTimestamp field is set to the value of the last call. func (b *StreamApplyConfiguration) WithCreationTimestamp(value metav1.Time) *StreamApplyConfiguration { b.ensureObjectMetaApplyConfigurationExists() - b.CreationTimestamp = &value + b.ObjectMetaApplyConfiguration.CreationTimestamp = &value return b } @@ -125,7 +125,7 @@ func (b *StreamApplyConfiguration) WithCreationTimestamp(value metav1.Time) *Str // If called multiple times, the DeletionTimestamp field is set to the value of the last call. func (b *StreamApplyConfiguration) WithDeletionTimestamp(value metav1.Time) *StreamApplyConfiguration { b.ensureObjectMetaApplyConfigurationExists() - b.DeletionTimestamp = &value + b.ObjectMetaApplyConfiguration.DeletionTimestamp = &value return b } @@ -134,7 +134,7 @@ func (b *StreamApplyConfiguration) WithDeletionTimestamp(value metav1.Time) *Str // If called multiple times, the DeletionGracePeriodSeconds field is set to the value of the last call. func (b *StreamApplyConfiguration) WithDeletionGracePeriodSeconds(value int64) *StreamApplyConfiguration { b.ensureObjectMetaApplyConfigurationExists() - b.DeletionGracePeriodSeconds = &value + b.ObjectMetaApplyConfiguration.DeletionGracePeriodSeconds = &value return b } @@ -144,11 +144,11 @@ func (b *StreamApplyConfiguration) WithDeletionGracePeriodSeconds(value int64) * // overwriting an existing map entries in Labels field with the same key. func (b *StreamApplyConfiguration) WithLabels(entries map[string]string) *StreamApplyConfiguration { b.ensureObjectMetaApplyConfigurationExists() - if b.Labels == nil && len(entries) > 0 { - b.Labels = make(map[string]string, len(entries)) + if b.ObjectMetaApplyConfiguration.Labels == nil && len(entries) > 0 { + b.ObjectMetaApplyConfiguration.Labels = make(map[string]string, len(entries)) } for k, v := range entries { - b.Labels[k] = v + b.ObjectMetaApplyConfiguration.Labels[k] = v } return b } @@ -159,11 +159,11 @@ func (b *StreamApplyConfiguration) WithLabels(entries map[string]string) *Stream // overwriting an existing map entries in Annotations field with the same key. func (b *StreamApplyConfiguration) WithAnnotations(entries map[string]string) *StreamApplyConfiguration { b.ensureObjectMetaApplyConfigurationExists() - if b.Annotations == nil && len(entries) > 0 { - b.Annotations = make(map[string]string, len(entries)) + if b.ObjectMetaApplyConfiguration.Annotations == nil && len(entries) > 0 { + b.ObjectMetaApplyConfiguration.Annotations = make(map[string]string, len(entries)) } for k, v := range entries { - b.Annotations[k] = v + b.ObjectMetaApplyConfiguration.Annotations[k] = v } return b } @@ -177,7 +177,7 @@ func (b *StreamApplyConfiguration) WithOwnerReferences(values ...*v1.OwnerRefere if values[i] == nil { panic("nil value passed to WithOwnerReferences") } - b.OwnerReferences = append(b.OwnerReferences, *values[i]) + b.ObjectMetaApplyConfiguration.OwnerReferences = append(b.ObjectMetaApplyConfiguration.OwnerReferences, *values[i]) } return b } @@ -188,7 +188,7 @@ func (b *StreamApplyConfiguration) WithOwnerReferences(values ...*v1.OwnerRefere func (b *StreamApplyConfiguration) WithFinalizers(values ...string) *StreamApplyConfiguration { b.ensureObjectMetaApplyConfigurationExists() for i := range values { - b.Finalizers = append(b.Finalizers, values[i]) + b.ObjectMetaApplyConfiguration.Finalizers = append(b.ObjectMetaApplyConfiguration.Finalizers, values[i]) } return b } @@ -218,5 +218,5 @@ func (b *StreamApplyConfiguration) WithStatus(value *StatusApplyConfiguration) * // GetName retrieves the value of the Name field in the declarative configuration. func (b *StreamApplyConfiguration) GetName() *string { b.ensureObjectMetaApplyConfigurationExists() - return b.Name + return b.ObjectMetaApplyConfiguration.Name } diff --git a/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/streamplacement.go b/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/streamplacement.go index 4063166e..b6834dd5 100644 --- a/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/streamplacement.go +++ b/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/streamplacement.go @@ -1,4 +1,4 @@ -// Copyright 2024 The NATS Authors +// Copyright 2025 The NATS Authors // 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 diff --git a/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/streamsource.go b/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/streamsource.go index 0d5e63f0..7fccc24f 100644 --- a/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/streamsource.go +++ b/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/streamsource.go @@ -1,4 +1,4 @@ -// Copyright 2024 The NATS Authors +// Copyright 2025 The NATS Authors // 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 @@ -16,19 +16,19 @@ package v1beta2 import ( - v1beta2 "github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2" + jetstreamv1beta2 "github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2" ) // StreamSourceApplyConfiguration represents a declarative configuration of the StreamSource type for use // with apply. type StreamSourceApplyConfiguration struct { - Name *string `json:"name,omitempty"` - OptStartSeq *int `json:"optStartSeq,omitempty"` - OptStartTime *string `json:"optStartTime,omitempty"` - FilterSubject *string `json:"filterSubject,omitempty"` - ExternalAPIPrefix *string `json:"externalApiPrefix,omitempty"` - ExternalDeliverPrefix *string `json:"externalDeliverPrefix,omitempty"` - SubjectTransforms []*v1beta2.SubjectTransform `json:"subjectTransforms,omitempty"` + Name *string `json:"name,omitempty"` + OptStartSeq *int `json:"optStartSeq,omitempty"` + OptStartTime *string `json:"optStartTime,omitempty"` + FilterSubject *string `json:"filterSubject,omitempty"` + ExternalAPIPrefix *string `json:"externalApiPrefix,omitempty"` + ExternalDeliverPrefix *string `json:"externalDeliverPrefix,omitempty"` + SubjectTransforms []*jetstreamv1beta2.SubjectTransform `json:"subjectTransforms,omitempty"` } // StreamSourceApplyConfiguration constructs a declarative configuration of the StreamSource type for use with @@ -88,7 +88,7 @@ func (b *StreamSourceApplyConfiguration) WithExternalDeliverPrefix(value string) // WithSubjectTransforms adds the given value to the SubjectTransforms field in the declarative configuration // and returns the receiver, so that objects can be build by chaining "With" function invocations. // If called multiple times, values provided by each call will be appended to the SubjectTransforms field. -func (b *StreamSourceApplyConfiguration) WithSubjectTransforms(values ...**v1beta2.SubjectTransform) *StreamSourceApplyConfiguration { +func (b *StreamSourceApplyConfiguration) WithSubjectTransforms(values ...**jetstreamv1beta2.SubjectTransform) *StreamSourceApplyConfiguration { for i := range values { if values[i] == nil { panic("nil value passed to WithSubjectTransforms") diff --git a/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/streamspec.go b/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/streamspec.go index 7be89f6a..608a118d 100644 --- a/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/streamspec.go +++ b/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/streamspec.go @@ -1,4 +1,4 @@ -// Copyright 2024 The NATS Authors +// Copyright 2025 The NATS Authors // 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 diff --git a/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/subjecttransform.go b/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/subjecttransform.go index 98fc658b..ec7283fd 100644 --- a/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/subjecttransform.go +++ b/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/subjecttransform.go @@ -1,4 +1,4 @@ -// Copyright 2024 The NATS Authors +// Copyright 2025 The NATS Authors // 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 diff --git a/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/tls.go b/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/tls.go index 806a9ee5..0eb0ec56 100644 --- a/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/tls.go +++ b/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/tls.go @@ -1,4 +1,4 @@ -// Copyright 2024 The NATS Authors +// Copyright 2025 The NATS Authors // 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 diff --git a/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/tlssecret.go b/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/tlssecret.go index c593036f..530a415c 100644 --- a/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/tlssecret.go +++ b/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/tlssecret.go @@ -1,4 +1,4 @@ -// Copyright 2024 The NATS Authors +// Copyright 2025 The NATS Authors // 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 diff --git a/pkg/jetstream/generated/applyconfiguration/utils.go b/pkg/jetstream/generated/applyconfiguration/utils.go index bb273d79..206d9b72 100644 --- a/pkg/jetstream/generated/applyconfiguration/utils.go +++ b/pkg/jetstream/generated/applyconfiguration/utils.go @@ -1,4 +1,4 @@ -// Copyright 2024 The NATS Authors +// Copyright 2025 The NATS Authors // 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 @@ -41,6 +41,10 @@ func ForKind(kind schema.GroupVersionKind) interface{} { return &jetstreamv1beta2.ConsumerSpecApplyConfiguration{} case v1beta2.SchemeGroupVersion.WithKind("CredsSecret"): return &jetstreamv1beta2.CredsSecretApplyConfiguration{} + case v1beta2.SchemeGroupVersion.WithKind("KeyValue"): + return &jetstreamv1beta2.KeyValueApplyConfiguration{} + case v1beta2.SchemeGroupVersion.WithKind("KeyValueSpec"): + return &jetstreamv1beta2.KeyValueSpecApplyConfiguration{} case v1beta2.SchemeGroupVersion.WithKind("RePublish"): return &jetstreamv1beta2.RePublishApplyConfiguration{} case v1beta2.SchemeGroupVersion.WithKind("SecretRef"): diff --git a/pkg/jetstream/generated/clientset/versioned/clientset.go b/pkg/jetstream/generated/clientset/versioned/clientset.go index 79f86ce7..6ef82837 100644 --- a/pkg/jetstream/generated/clientset/versioned/clientset.go +++ b/pkg/jetstream/generated/clientset/versioned/clientset.go @@ -1,4 +1,4 @@ -// Copyright 2024 The NATS Authors +// Copyright 2025 The NATS Authors // 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 @@ -16,8 +16,8 @@ package versioned import ( - "fmt" - "net/http" + fmt "fmt" + http "net/http" jetstreamv1beta2 "github.com/nats-io/nack/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2" discovery "k8s.io/client-go/discovery" diff --git a/pkg/jetstream/generated/clientset/versioned/fake/clientset_generated.go b/pkg/jetstream/generated/clientset/versioned/fake/clientset_generated.go index 642b047b..ceb891d7 100644 --- a/pkg/jetstream/generated/clientset/versioned/fake/clientset_generated.go +++ b/pkg/jetstream/generated/clientset/versioned/fake/clientset_generated.go @@ -1,4 +1,4 @@ -// Copyright 2024 The NATS Authors +// Copyright 2025 The NATS Authors // 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 diff --git a/pkg/jetstream/generated/clientset/versioned/fake/doc.go b/pkg/jetstream/generated/clientset/versioned/fake/doc.go index fc05515c..f3f6430c 100644 --- a/pkg/jetstream/generated/clientset/versioned/fake/doc.go +++ b/pkg/jetstream/generated/clientset/versioned/fake/doc.go @@ -1,4 +1,4 @@ -// Copyright 2024 The NATS Authors +// Copyright 2025 The NATS Authors // 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 diff --git a/pkg/jetstream/generated/clientset/versioned/fake/register.go b/pkg/jetstream/generated/clientset/versioned/fake/register.go index c64246e0..aa9df72f 100644 --- a/pkg/jetstream/generated/clientset/versioned/fake/register.go +++ b/pkg/jetstream/generated/clientset/versioned/fake/register.go @@ -1,4 +1,4 @@ -// Copyright 2024 The NATS Authors +// Copyright 2025 The NATS Authors // 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 diff --git a/pkg/jetstream/generated/clientset/versioned/scheme/doc.go b/pkg/jetstream/generated/clientset/versioned/scheme/doc.go index 31cfa974..2d5c26bc 100644 --- a/pkg/jetstream/generated/clientset/versioned/scheme/doc.go +++ b/pkg/jetstream/generated/clientset/versioned/scheme/doc.go @@ -1,4 +1,4 @@ -// Copyright 2024 The NATS Authors +// Copyright 2025 The NATS Authors // 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 diff --git a/pkg/jetstream/generated/clientset/versioned/scheme/register.go b/pkg/jetstream/generated/clientset/versioned/scheme/register.go index c900985f..bd153a8c 100644 --- a/pkg/jetstream/generated/clientset/versioned/scheme/register.go +++ b/pkg/jetstream/generated/clientset/versioned/scheme/register.go @@ -1,4 +1,4 @@ -// Copyright 2024 The NATS Authors +// Copyright 2025 The NATS Authors // 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 diff --git a/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/account.go b/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/account.go index b495a525..9b6541bf 100644 --- a/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/account.go +++ b/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/account.go @@ -1,4 +1,4 @@ -// Copyright 2024 The NATS Authors +// Copyright 2025 The NATS Authors // 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 @@ -16,10 +16,10 @@ package v1beta2 import ( - "context" + context "context" - v1beta2 "github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2" - jetstreamv1beta2 "github.com/nats-io/nack/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2" + jetstreamv1beta2 "github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2" + applyconfigurationjetstreamv1beta2 "github.com/nats-io/nack/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2" scheme "github.com/nats-io/nack/pkg/jetstream/generated/clientset/versioned/scheme" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" types "k8s.io/apimachinery/pkg/types" @@ -35,36 +35,37 @@ type AccountsGetter interface { // AccountInterface has methods to work with Account resources. type AccountInterface interface { - Create(ctx context.Context, account *v1beta2.Account, opts v1.CreateOptions) (*v1beta2.Account, error) - Update(ctx context.Context, account *v1beta2.Account, opts v1.UpdateOptions) (*v1beta2.Account, error) + Create(ctx context.Context, account *jetstreamv1beta2.Account, opts v1.CreateOptions) (*jetstreamv1beta2.Account, error) + Update(ctx context.Context, account *jetstreamv1beta2.Account, opts v1.UpdateOptions) (*jetstreamv1beta2.Account, error) // Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). - UpdateStatus(ctx context.Context, account *v1beta2.Account, opts v1.UpdateOptions) (*v1beta2.Account, error) + UpdateStatus(ctx context.Context, account *jetstreamv1beta2.Account, opts v1.UpdateOptions) (*jetstreamv1beta2.Account, error) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error - Get(ctx context.Context, name string, opts v1.GetOptions) (*v1beta2.Account, error) - List(ctx context.Context, opts v1.ListOptions) (*v1beta2.AccountList, error) + Get(ctx context.Context, name string, opts v1.GetOptions) (*jetstreamv1beta2.Account, error) + List(ctx context.Context, opts v1.ListOptions) (*jetstreamv1beta2.AccountList, error) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) - Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1beta2.Account, err error) - Apply(ctx context.Context, account *jetstreamv1beta2.AccountApplyConfiguration, opts v1.ApplyOptions) (result *v1beta2.Account, err error) + Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *jetstreamv1beta2.Account, err error) + Apply(ctx context.Context, account *applyconfigurationjetstreamv1beta2.AccountApplyConfiguration, opts v1.ApplyOptions) (result *jetstreamv1beta2.Account, err error) // Add a +genclient:noStatus comment above the type to avoid generating ApplyStatus(). - ApplyStatus(ctx context.Context, account *jetstreamv1beta2.AccountApplyConfiguration, opts v1.ApplyOptions) (result *v1beta2.Account, err error) + ApplyStatus(ctx context.Context, account *applyconfigurationjetstreamv1beta2.AccountApplyConfiguration, opts v1.ApplyOptions) (result *jetstreamv1beta2.Account, err error) AccountExpansion } // accounts implements AccountInterface type accounts struct { - *gentype.ClientWithListAndApply[*v1beta2.Account, *v1beta2.AccountList, *jetstreamv1beta2.AccountApplyConfiguration] + *gentype.ClientWithListAndApply[*jetstreamv1beta2.Account, *jetstreamv1beta2.AccountList, *applyconfigurationjetstreamv1beta2.AccountApplyConfiguration] } // newAccounts returns a Accounts func newAccounts(c *JetstreamV1beta2Client, namespace string) *accounts { return &accounts{ - gentype.NewClientWithListAndApply[*v1beta2.Account, *v1beta2.AccountList, *jetstreamv1beta2.AccountApplyConfiguration]( + gentype.NewClientWithListAndApply[*jetstreamv1beta2.Account, *jetstreamv1beta2.AccountList, *applyconfigurationjetstreamv1beta2.AccountApplyConfiguration]( "accounts", c.RESTClient(), scheme.ParameterCodec, namespace, - func() *v1beta2.Account { return &v1beta2.Account{} }, - func() *v1beta2.AccountList { return &v1beta2.AccountList{} }), + func() *jetstreamv1beta2.Account { return &jetstreamv1beta2.Account{} }, + func() *jetstreamv1beta2.AccountList { return &jetstreamv1beta2.AccountList{} }, + ), } } diff --git a/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/consumer.go b/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/consumer.go index de230925..3560df58 100644 --- a/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/consumer.go +++ b/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/consumer.go @@ -1,4 +1,4 @@ -// Copyright 2024 The NATS Authors +// Copyright 2025 The NATS Authors // 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 @@ -16,10 +16,10 @@ package v1beta2 import ( - "context" + context "context" - v1beta2 "github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2" - jetstreamv1beta2 "github.com/nats-io/nack/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2" + jetstreamv1beta2 "github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2" + applyconfigurationjetstreamv1beta2 "github.com/nats-io/nack/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2" scheme "github.com/nats-io/nack/pkg/jetstream/generated/clientset/versioned/scheme" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" types "k8s.io/apimachinery/pkg/types" @@ -35,36 +35,37 @@ type ConsumersGetter interface { // ConsumerInterface has methods to work with Consumer resources. type ConsumerInterface interface { - Create(ctx context.Context, consumer *v1beta2.Consumer, opts v1.CreateOptions) (*v1beta2.Consumer, error) - Update(ctx context.Context, consumer *v1beta2.Consumer, opts v1.UpdateOptions) (*v1beta2.Consumer, error) + Create(ctx context.Context, consumer *jetstreamv1beta2.Consumer, opts v1.CreateOptions) (*jetstreamv1beta2.Consumer, error) + Update(ctx context.Context, consumer *jetstreamv1beta2.Consumer, opts v1.UpdateOptions) (*jetstreamv1beta2.Consumer, error) // Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). - UpdateStatus(ctx context.Context, consumer *v1beta2.Consumer, opts v1.UpdateOptions) (*v1beta2.Consumer, error) + UpdateStatus(ctx context.Context, consumer *jetstreamv1beta2.Consumer, opts v1.UpdateOptions) (*jetstreamv1beta2.Consumer, error) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error - Get(ctx context.Context, name string, opts v1.GetOptions) (*v1beta2.Consumer, error) - List(ctx context.Context, opts v1.ListOptions) (*v1beta2.ConsumerList, error) + Get(ctx context.Context, name string, opts v1.GetOptions) (*jetstreamv1beta2.Consumer, error) + List(ctx context.Context, opts v1.ListOptions) (*jetstreamv1beta2.ConsumerList, error) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) - Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1beta2.Consumer, err error) - Apply(ctx context.Context, consumer *jetstreamv1beta2.ConsumerApplyConfiguration, opts v1.ApplyOptions) (result *v1beta2.Consumer, err error) + Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *jetstreamv1beta2.Consumer, err error) + Apply(ctx context.Context, consumer *applyconfigurationjetstreamv1beta2.ConsumerApplyConfiguration, opts v1.ApplyOptions) (result *jetstreamv1beta2.Consumer, err error) // Add a +genclient:noStatus comment above the type to avoid generating ApplyStatus(). - ApplyStatus(ctx context.Context, consumer *jetstreamv1beta2.ConsumerApplyConfiguration, opts v1.ApplyOptions) (result *v1beta2.Consumer, err error) + ApplyStatus(ctx context.Context, consumer *applyconfigurationjetstreamv1beta2.ConsumerApplyConfiguration, opts v1.ApplyOptions) (result *jetstreamv1beta2.Consumer, err error) ConsumerExpansion } // consumers implements ConsumerInterface type consumers struct { - *gentype.ClientWithListAndApply[*v1beta2.Consumer, *v1beta2.ConsumerList, *jetstreamv1beta2.ConsumerApplyConfiguration] + *gentype.ClientWithListAndApply[*jetstreamv1beta2.Consumer, *jetstreamv1beta2.ConsumerList, *applyconfigurationjetstreamv1beta2.ConsumerApplyConfiguration] } // newConsumers returns a Consumers func newConsumers(c *JetstreamV1beta2Client, namespace string) *consumers { return &consumers{ - gentype.NewClientWithListAndApply[*v1beta2.Consumer, *v1beta2.ConsumerList, *jetstreamv1beta2.ConsumerApplyConfiguration]( + gentype.NewClientWithListAndApply[*jetstreamv1beta2.Consumer, *jetstreamv1beta2.ConsumerList, *applyconfigurationjetstreamv1beta2.ConsumerApplyConfiguration]( "consumers", c.RESTClient(), scheme.ParameterCodec, namespace, - func() *v1beta2.Consumer { return &v1beta2.Consumer{} }, - func() *v1beta2.ConsumerList { return &v1beta2.ConsumerList{} }), + func() *jetstreamv1beta2.Consumer { return &jetstreamv1beta2.Consumer{} }, + func() *jetstreamv1beta2.ConsumerList { return &jetstreamv1beta2.ConsumerList{} }, + ), } } diff --git a/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/doc.go b/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/doc.go index f278ba06..7db3dc1e 100644 --- a/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/doc.go +++ b/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/doc.go @@ -1,4 +1,4 @@ -// Copyright 2024 The NATS Authors +// Copyright 2025 The NATS Authors // 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 diff --git a/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/fake/doc.go b/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/fake/doc.go index bd8826c0..ef446d4a 100644 --- a/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/fake/doc.go +++ b/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/fake/doc.go @@ -1,4 +1,4 @@ -// Copyright 2024 The NATS Authors +// Copyright 2025 The NATS Authors // 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 diff --git a/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/fake/fake_account.go b/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/fake/fake_account.go index fa2a89e2..1caa3b4d 100644 --- a/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/fake/fake_account.go +++ b/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/fake/fake_account.go @@ -1,4 +1,4 @@ -// Copyright 2024 The NATS Authors +// Copyright 2025 The NATS Authors // 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 @@ -16,179 +16,33 @@ package fake import ( - "context" - json "encoding/json" - "fmt" - v1beta2 "github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2" jetstreamv1beta2 "github.com/nats-io/nack/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - labels "k8s.io/apimachinery/pkg/labels" - types "k8s.io/apimachinery/pkg/types" - watch "k8s.io/apimachinery/pkg/watch" - testing "k8s.io/client-go/testing" + typedjetstreamv1beta2 "github.com/nats-io/nack/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2" + gentype "k8s.io/client-go/gentype" ) -// FakeAccounts implements AccountInterface -type FakeAccounts struct { +// fakeAccounts implements AccountInterface +type fakeAccounts struct { + *gentype.FakeClientWithListAndApply[*v1beta2.Account, *v1beta2.AccountList, *jetstreamv1beta2.AccountApplyConfiguration] Fake *FakeJetstreamV1beta2 - ns string -} - -var accountsResource = v1beta2.SchemeGroupVersion.WithResource("accounts") - -var accountsKind = v1beta2.SchemeGroupVersion.WithKind("Account") - -// Get takes name of the account, and returns the corresponding account object, and an error if there is any. -func (c *FakeAccounts) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1beta2.Account, err error) { - emptyResult := &v1beta2.Account{} - obj, err := c.Fake. - Invokes(testing.NewGetActionWithOptions(accountsResource, c.ns, name, options), emptyResult) - - if obj == nil { - return emptyResult, err - } - return obj.(*v1beta2.Account), err -} - -// List takes label and field selectors, and returns the list of Accounts that match those selectors. -func (c *FakeAccounts) List(ctx context.Context, opts v1.ListOptions) (result *v1beta2.AccountList, err error) { - emptyResult := &v1beta2.AccountList{} - obj, err := c.Fake. - Invokes(testing.NewListActionWithOptions(accountsResource, accountsKind, c.ns, opts), emptyResult) - - if obj == nil { - return emptyResult, err - } - - label, _, _ := testing.ExtractFromListOptions(opts) - if label == nil { - label = labels.Everything() - } - list := &v1beta2.AccountList{ListMeta: obj.(*v1beta2.AccountList).ListMeta} - for _, item := range obj.(*v1beta2.AccountList).Items { - if label.Matches(labels.Set(item.Labels)) { - list.Items = append(list.Items, item) - } - } - return list, err -} - -// Watch returns a watch.Interface that watches the requested accounts. -func (c *FakeAccounts) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { - return c.Fake. - InvokesWatch(testing.NewWatchActionWithOptions(accountsResource, c.ns, opts)) - -} - -// Create takes the representation of a account and creates it. Returns the server's representation of the account, and an error, if there is any. -func (c *FakeAccounts) Create(ctx context.Context, account *v1beta2.Account, opts v1.CreateOptions) (result *v1beta2.Account, err error) { - emptyResult := &v1beta2.Account{} - obj, err := c.Fake. - Invokes(testing.NewCreateActionWithOptions(accountsResource, c.ns, account, opts), emptyResult) - - if obj == nil { - return emptyResult, err - } - return obj.(*v1beta2.Account), err -} - -// Update takes the representation of a account and updates it. Returns the server's representation of the account, and an error, if there is any. -func (c *FakeAccounts) Update(ctx context.Context, account *v1beta2.Account, opts v1.UpdateOptions) (result *v1beta2.Account, err error) { - emptyResult := &v1beta2.Account{} - obj, err := c.Fake. - Invokes(testing.NewUpdateActionWithOptions(accountsResource, c.ns, account, opts), emptyResult) - - if obj == nil { - return emptyResult, err - } - return obj.(*v1beta2.Account), err } -// UpdateStatus was generated because the type contains a Status member. -// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). -func (c *FakeAccounts) UpdateStatus(ctx context.Context, account *v1beta2.Account, opts v1.UpdateOptions) (result *v1beta2.Account, err error) { - emptyResult := &v1beta2.Account{} - obj, err := c.Fake. - Invokes(testing.NewUpdateSubresourceActionWithOptions(accountsResource, "status", c.ns, account, opts), emptyResult) - - if obj == nil { - return emptyResult, err - } - return obj.(*v1beta2.Account), err -} - -// Delete takes name of the account and deletes it. Returns an error if one occurs. -func (c *FakeAccounts) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { - _, err := c.Fake. - Invokes(testing.NewDeleteActionWithOptions(accountsResource, c.ns, name, opts), &v1beta2.Account{}) - - return err -} - -// DeleteCollection deletes a collection of objects. -func (c *FakeAccounts) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { - action := testing.NewDeleteCollectionActionWithOptions(accountsResource, c.ns, opts, listOpts) - - _, err := c.Fake.Invokes(action, &v1beta2.AccountList{}) - return err -} - -// Patch applies the patch and returns the patched account. -func (c *FakeAccounts) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1beta2.Account, err error) { - emptyResult := &v1beta2.Account{} - obj, err := c.Fake. - Invokes(testing.NewPatchSubresourceActionWithOptions(accountsResource, c.ns, name, pt, data, opts, subresources...), emptyResult) - - if obj == nil { - return emptyResult, err - } - return obj.(*v1beta2.Account), err -} - -// Apply takes the given apply declarative configuration, applies it and returns the applied account. -func (c *FakeAccounts) Apply(ctx context.Context, account *jetstreamv1beta2.AccountApplyConfiguration, opts v1.ApplyOptions) (result *v1beta2.Account, err error) { - if account == nil { - return nil, fmt.Errorf("account provided to Apply must not be nil") - } - data, err := json.Marshal(account) - if err != nil { - return nil, err - } - name := account.Name - if name == nil { - return nil, fmt.Errorf("account.Name must be provided to Apply") - } - emptyResult := &v1beta2.Account{} - obj, err := c.Fake. - Invokes(testing.NewPatchSubresourceActionWithOptions(accountsResource, c.ns, *name, types.ApplyPatchType, data, opts.ToPatchOptions()), emptyResult) - - if obj == nil { - return emptyResult, err - } - return obj.(*v1beta2.Account), err -} - -// ApplyStatus was generated because the type contains a Status member. -// Add a +genclient:noStatus comment above the type to avoid generating ApplyStatus(). -func (c *FakeAccounts) ApplyStatus(ctx context.Context, account *jetstreamv1beta2.AccountApplyConfiguration, opts v1.ApplyOptions) (result *v1beta2.Account, err error) { - if account == nil { - return nil, fmt.Errorf("account provided to Apply must not be nil") - } - data, err := json.Marshal(account) - if err != nil { - return nil, err - } - name := account.Name - if name == nil { - return nil, fmt.Errorf("account.Name must be provided to Apply") - } - emptyResult := &v1beta2.Account{} - obj, err := c.Fake. - Invokes(testing.NewPatchSubresourceActionWithOptions(accountsResource, c.ns, *name, types.ApplyPatchType, data, opts.ToPatchOptions(), "status"), emptyResult) - - if obj == nil { - return emptyResult, err +func newFakeAccounts(fake *FakeJetstreamV1beta2, namespace string) typedjetstreamv1beta2.AccountInterface { + return &fakeAccounts{ + gentype.NewFakeClientWithListAndApply[*v1beta2.Account, *v1beta2.AccountList, *jetstreamv1beta2.AccountApplyConfiguration]( + fake.Fake, + namespace, + v1beta2.SchemeGroupVersion.WithResource("accounts"), + v1beta2.SchemeGroupVersion.WithKind("Account"), + func() *v1beta2.Account { return &v1beta2.Account{} }, + func() *v1beta2.AccountList { return &v1beta2.AccountList{} }, + func(dst, src *v1beta2.AccountList) { dst.ListMeta = src.ListMeta }, + func(list *v1beta2.AccountList) []*v1beta2.Account { return gentype.ToPointerSlice(list.Items) }, + func(list *v1beta2.AccountList, items []*v1beta2.Account) { + list.Items = gentype.FromPointerSlice(items) + }, + ), + fake, } - return obj.(*v1beta2.Account), err } diff --git a/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/fake/fake_consumer.go b/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/fake/fake_consumer.go index 72e4d444..33ed318f 100644 --- a/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/fake/fake_consumer.go +++ b/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/fake/fake_consumer.go @@ -1,4 +1,4 @@ -// Copyright 2024 The NATS Authors +// Copyright 2025 The NATS Authors // 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 @@ -16,179 +16,33 @@ package fake import ( - "context" - json "encoding/json" - "fmt" - v1beta2 "github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2" jetstreamv1beta2 "github.com/nats-io/nack/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - labels "k8s.io/apimachinery/pkg/labels" - types "k8s.io/apimachinery/pkg/types" - watch "k8s.io/apimachinery/pkg/watch" - testing "k8s.io/client-go/testing" + typedjetstreamv1beta2 "github.com/nats-io/nack/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2" + gentype "k8s.io/client-go/gentype" ) -// FakeConsumers implements ConsumerInterface -type FakeConsumers struct { +// fakeConsumers implements ConsumerInterface +type fakeConsumers struct { + *gentype.FakeClientWithListAndApply[*v1beta2.Consumer, *v1beta2.ConsumerList, *jetstreamv1beta2.ConsumerApplyConfiguration] Fake *FakeJetstreamV1beta2 - ns string -} - -var consumersResource = v1beta2.SchemeGroupVersion.WithResource("consumers") - -var consumersKind = v1beta2.SchemeGroupVersion.WithKind("Consumer") - -// Get takes name of the consumer, and returns the corresponding consumer object, and an error if there is any. -func (c *FakeConsumers) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1beta2.Consumer, err error) { - emptyResult := &v1beta2.Consumer{} - obj, err := c.Fake. - Invokes(testing.NewGetActionWithOptions(consumersResource, c.ns, name, options), emptyResult) - - if obj == nil { - return emptyResult, err - } - return obj.(*v1beta2.Consumer), err -} - -// List takes label and field selectors, and returns the list of Consumers that match those selectors. -func (c *FakeConsumers) List(ctx context.Context, opts v1.ListOptions) (result *v1beta2.ConsumerList, err error) { - emptyResult := &v1beta2.ConsumerList{} - obj, err := c.Fake. - Invokes(testing.NewListActionWithOptions(consumersResource, consumersKind, c.ns, opts), emptyResult) - - if obj == nil { - return emptyResult, err - } - - label, _, _ := testing.ExtractFromListOptions(opts) - if label == nil { - label = labels.Everything() - } - list := &v1beta2.ConsumerList{ListMeta: obj.(*v1beta2.ConsumerList).ListMeta} - for _, item := range obj.(*v1beta2.ConsumerList).Items { - if label.Matches(labels.Set(item.Labels)) { - list.Items = append(list.Items, item) - } - } - return list, err -} - -// Watch returns a watch.Interface that watches the requested consumers. -func (c *FakeConsumers) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { - return c.Fake. - InvokesWatch(testing.NewWatchActionWithOptions(consumersResource, c.ns, opts)) - -} - -// Create takes the representation of a consumer and creates it. Returns the server's representation of the consumer, and an error, if there is any. -func (c *FakeConsumers) Create(ctx context.Context, consumer *v1beta2.Consumer, opts v1.CreateOptions) (result *v1beta2.Consumer, err error) { - emptyResult := &v1beta2.Consumer{} - obj, err := c.Fake. - Invokes(testing.NewCreateActionWithOptions(consumersResource, c.ns, consumer, opts), emptyResult) - - if obj == nil { - return emptyResult, err - } - return obj.(*v1beta2.Consumer), err -} - -// Update takes the representation of a consumer and updates it. Returns the server's representation of the consumer, and an error, if there is any. -func (c *FakeConsumers) Update(ctx context.Context, consumer *v1beta2.Consumer, opts v1.UpdateOptions) (result *v1beta2.Consumer, err error) { - emptyResult := &v1beta2.Consumer{} - obj, err := c.Fake. - Invokes(testing.NewUpdateActionWithOptions(consumersResource, c.ns, consumer, opts), emptyResult) - - if obj == nil { - return emptyResult, err - } - return obj.(*v1beta2.Consumer), err } -// UpdateStatus was generated because the type contains a Status member. -// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). -func (c *FakeConsumers) UpdateStatus(ctx context.Context, consumer *v1beta2.Consumer, opts v1.UpdateOptions) (result *v1beta2.Consumer, err error) { - emptyResult := &v1beta2.Consumer{} - obj, err := c.Fake. - Invokes(testing.NewUpdateSubresourceActionWithOptions(consumersResource, "status", c.ns, consumer, opts), emptyResult) - - if obj == nil { - return emptyResult, err - } - return obj.(*v1beta2.Consumer), err -} - -// Delete takes name of the consumer and deletes it. Returns an error if one occurs. -func (c *FakeConsumers) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { - _, err := c.Fake. - Invokes(testing.NewDeleteActionWithOptions(consumersResource, c.ns, name, opts), &v1beta2.Consumer{}) - - return err -} - -// DeleteCollection deletes a collection of objects. -func (c *FakeConsumers) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { - action := testing.NewDeleteCollectionActionWithOptions(consumersResource, c.ns, opts, listOpts) - - _, err := c.Fake.Invokes(action, &v1beta2.ConsumerList{}) - return err -} - -// Patch applies the patch and returns the patched consumer. -func (c *FakeConsumers) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1beta2.Consumer, err error) { - emptyResult := &v1beta2.Consumer{} - obj, err := c.Fake. - Invokes(testing.NewPatchSubresourceActionWithOptions(consumersResource, c.ns, name, pt, data, opts, subresources...), emptyResult) - - if obj == nil { - return emptyResult, err - } - return obj.(*v1beta2.Consumer), err -} - -// Apply takes the given apply declarative configuration, applies it and returns the applied consumer. -func (c *FakeConsumers) Apply(ctx context.Context, consumer *jetstreamv1beta2.ConsumerApplyConfiguration, opts v1.ApplyOptions) (result *v1beta2.Consumer, err error) { - if consumer == nil { - return nil, fmt.Errorf("consumer provided to Apply must not be nil") - } - data, err := json.Marshal(consumer) - if err != nil { - return nil, err - } - name := consumer.Name - if name == nil { - return nil, fmt.Errorf("consumer.Name must be provided to Apply") - } - emptyResult := &v1beta2.Consumer{} - obj, err := c.Fake. - Invokes(testing.NewPatchSubresourceActionWithOptions(consumersResource, c.ns, *name, types.ApplyPatchType, data, opts.ToPatchOptions()), emptyResult) - - if obj == nil { - return emptyResult, err - } - return obj.(*v1beta2.Consumer), err -} - -// ApplyStatus was generated because the type contains a Status member. -// Add a +genclient:noStatus comment above the type to avoid generating ApplyStatus(). -func (c *FakeConsumers) ApplyStatus(ctx context.Context, consumer *jetstreamv1beta2.ConsumerApplyConfiguration, opts v1.ApplyOptions) (result *v1beta2.Consumer, err error) { - if consumer == nil { - return nil, fmt.Errorf("consumer provided to Apply must not be nil") - } - data, err := json.Marshal(consumer) - if err != nil { - return nil, err - } - name := consumer.Name - if name == nil { - return nil, fmt.Errorf("consumer.Name must be provided to Apply") - } - emptyResult := &v1beta2.Consumer{} - obj, err := c.Fake. - Invokes(testing.NewPatchSubresourceActionWithOptions(consumersResource, c.ns, *name, types.ApplyPatchType, data, opts.ToPatchOptions(), "status"), emptyResult) - - if obj == nil { - return emptyResult, err +func newFakeConsumers(fake *FakeJetstreamV1beta2, namespace string) typedjetstreamv1beta2.ConsumerInterface { + return &fakeConsumers{ + gentype.NewFakeClientWithListAndApply[*v1beta2.Consumer, *v1beta2.ConsumerList, *jetstreamv1beta2.ConsumerApplyConfiguration]( + fake.Fake, + namespace, + v1beta2.SchemeGroupVersion.WithResource("consumers"), + v1beta2.SchemeGroupVersion.WithKind("Consumer"), + func() *v1beta2.Consumer { return &v1beta2.Consumer{} }, + func() *v1beta2.ConsumerList { return &v1beta2.ConsumerList{} }, + func(dst, src *v1beta2.ConsumerList) { dst.ListMeta = src.ListMeta }, + func(list *v1beta2.ConsumerList) []*v1beta2.Consumer { return gentype.ToPointerSlice(list.Items) }, + func(list *v1beta2.ConsumerList, items []*v1beta2.Consumer) { + list.Items = gentype.FromPointerSlice(items) + }, + ), + fake, } - return obj.(*v1beta2.Consumer), err } diff --git a/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/fake/fake_jetstream_client.go b/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/fake/fake_jetstream_client.go index 31d69c86..7eec1be1 100644 --- a/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/fake/fake_jetstream_client.go +++ b/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/fake/fake_jetstream_client.go @@ -1,4 +1,4 @@ -// Copyright 2024 The NATS Authors +// Copyright 2025 The NATS Authors // 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 @@ -26,15 +26,19 @@ type FakeJetstreamV1beta2 struct { } func (c *FakeJetstreamV1beta2) Accounts(namespace string) v1beta2.AccountInterface { - return &FakeAccounts{c, namespace} + return newFakeAccounts(c, namespace) } func (c *FakeJetstreamV1beta2) Consumers(namespace string) v1beta2.ConsumerInterface { - return &FakeConsumers{c, namespace} + return newFakeConsumers(c, namespace) +} + +func (c *FakeJetstreamV1beta2) KeyValues(namespace string) v1beta2.KeyValueInterface { + return newFakeKeyValues(c, namespace) } func (c *FakeJetstreamV1beta2) Streams(namespace string) v1beta2.StreamInterface { - return &FakeStreams{c, namespace} + return newFakeStreams(c, namespace) } // RESTClient returns a RESTClient that is used to communicate diff --git a/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/fake/fake_keyvalue.go b/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/fake/fake_keyvalue.go new file mode 100644 index 00000000..1fbf269a --- /dev/null +++ b/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/fake/fake_keyvalue.go @@ -0,0 +1,48 @@ +// Copyright 2025 The NATS Authors +// 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. + +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + v1beta2 "github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2" + jetstreamv1beta2 "github.com/nats-io/nack/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2" + typedjetstreamv1beta2 "github.com/nats-io/nack/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2" + gentype "k8s.io/client-go/gentype" +) + +// fakeKeyValues implements KeyValueInterface +type fakeKeyValues struct { + *gentype.FakeClientWithListAndApply[*v1beta2.KeyValue, *v1beta2.KeyValueList, *jetstreamv1beta2.KeyValueApplyConfiguration] + Fake *FakeJetstreamV1beta2 +} + +func newFakeKeyValues(fake *FakeJetstreamV1beta2, namespace string) typedjetstreamv1beta2.KeyValueInterface { + return &fakeKeyValues{ + gentype.NewFakeClientWithListAndApply[*v1beta2.KeyValue, *v1beta2.KeyValueList, *jetstreamv1beta2.KeyValueApplyConfiguration]( + fake.Fake, + namespace, + v1beta2.SchemeGroupVersion.WithResource("keyvalues"), + v1beta2.SchemeGroupVersion.WithKind("KeyValue"), + func() *v1beta2.KeyValue { return &v1beta2.KeyValue{} }, + func() *v1beta2.KeyValueList { return &v1beta2.KeyValueList{} }, + func(dst, src *v1beta2.KeyValueList) { dst.ListMeta = src.ListMeta }, + func(list *v1beta2.KeyValueList) []*v1beta2.KeyValue { return gentype.ToPointerSlice(list.Items) }, + func(list *v1beta2.KeyValueList, items []*v1beta2.KeyValue) { + list.Items = gentype.FromPointerSlice(items) + }, + ), + fake, + } +} diff --git a/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/fake/fake_stream.go b/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/fake/fake_stream.go index da603cc1..f7f36cce 100644 --- a/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/fake/fake_stream.go +++ b/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/fake/fake_stream.go @@ -1,4 +1,4 @@ -// Copyright 2024 The NATS Authors +// Copyright 2025 The NATS Authors // 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 @@ -16,179 +16,31 @@ package fake import ( - "context" - json "encoding/json" - "fmt" - v1beta2 "github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2" jetstreamv1beta2 "github.com/nats-io/nack/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - labels "k8s.io/apimachinery/pkg/labels" - types "k8s.io/apimachinery/pkg/types" - watch "k8s.io/apimachinery/pkg/watch" - testing "k8s.io/client-go/testing" + typedjetstreamv1beta2 "github.com/nats-io/nack/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2" + gentype "k8s.io/client-go/gentype" ) -// FakeStreams implements StreamInterface -type FakeStreams struct { +// fakeStreams implements StreamInterface +type fakeStreams struct { + *gentype.FakeClientWithListAndApply[*v1beta2.Stream, *v1beta2.StreamList, *jetstreamv1beta2.StreamApplyConfiguration] Fake *FakeJetstreamV1beta2 - ns string -} - -var streamsResource = v1beta2.SchemeGroupVersion.WithResource("streams") - -var streamsKind = v1beta2.SchemeGroupVersion.WithKind("Stream") - -// Get takes name of the stream, and returns the corresponding stream object, and an error if there is any. -func (c *FakeStreams) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1beta2.Stream, err error) { - emptyResult := &v1beta2.Stream{} - obj, err := c.Fake. - Invokes(testing.NewGetActionWithOptions(streamsResource, c.ns, name, options), emptyResult) - - if obj == nil { - return emptyResult, err - } - return obj.(*v1beta2.Stream), err -} - -// List takes label and field selectors, and returns the list of Streams that match those selectors. -func (c *FakeStreams) List(ctx context.Context, opts v1.ListOptions) (result *v1beta2.StreamList, err error) { - emptyResult := &v1beta2.StreamList{} - obj, err := c.Fake. - Invokes(testing.NewListActionWithOptions(streamsResource, streamsKind, c.ns, opts), emptyResult) - - if obj == nil { - return emptyResult, err - } - - label, _, _ := testing.ExtractFromListOptions(opts) - if label == nil { - label = labels.Everything() - } - list := &v1beta2.StreamList{ListMeta: obj.(*v1beta2.StreamList).ListMeta} - for _, item := range obj.(*v1beta2.StreamList).Items { - if label.Matches(labels.Set(item.Labels)) { - list.Items = append(list.Items, item) - } - } - return list, err -} - -// Watch returns a watch.Interface that watches the requested streams. -func (c *FakeStreams) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { - return c.Fake. - InvokesWatch(testing.NewWatchActionWithOptions(streamsResource, c.ns, opts)) - -} - -// Create takes the representation of a stream and creates it. Returns the server's representation of the stream, and an error, if there is any. -func (c *FakeStreams) Create(ctx context.Context, stream *v1beta2.Stream, opts v1.CreateOptions) (result *v1beta2.Stream, err error) { - emptyResult := &v1beta2.Stream{} - obj, err := c.Fake. - Invokes(testing.NewCreateActionWithOptions(streamsResource, c.ns, stream, opts), emptyResult) - - if obj == nil { - return emptyResult, err - } - return obj.(*v1beta2.Stream), err -} - -// Update takes the representation of a stream and updates it. Returns the server's representation of the stream, and an error, if there is any. -func (c *FakeStreams) Update(ctx context.Context, stream *v1beta2.Stream, opts v1.UpdateOptions) (result *v1beta2.Stream, err error) { - emptyResult := &v1beta2.Stream{} - obj, err := c.Fake. - Invokes(testing.NewUpdateActionWithOptions(streamsResource, c.ns, stream, opts), emptyResult) - - if obj == nil { - return emptyResult, err - } - return obj.(*v1beta2.Stream), err } -// UpdateStatus was generated because the type contains a Status member. -// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). -func (c *FakeStreams) UpdateStatus(ctx context.Context, stream *v1beta2.Stream, opts v1.UpdateOptions) (result *v1beta2.Stream, err error) { - emptyResult := &v1beta2.Stream{} - obj, err := c.Fake. - Invokes(testing.NewUpdateSubresourceActionWithOptions(streamsResource, "status", c.ns, stream, opts), emptyResult) - - if obj == nil { - return emptyResult, err - } - return obj.(*v1beta2.Stream), err -} - -// Delete takes name of the stream and deletes it. Returns an error if one occurs. -func (c *FakeStreams) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { - _, err := c.Fake. - Invokes(testing.NewDeleteActionWithOptions(streamsResource, c.ns, name, opts), &v1beta2.Stream{}) - - return err -} - -// DeleteCollection deletes a collection of objects. -func (c *FakeStreams) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { - action := testing.NewDeleteCollectionActionWithOptions(streamsResource, c.ns, opts, listOpts) - - _, err := c.Fake.Invokes(action, &v1beta2.StreamList{}) - return err -} - -// Patch applies the patch and returns the patched stream. -func (c *FakeStreams) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1beta2.Stream, err error) { - emptyResult := &v1beta2.Stream{} - obj, err := c.Fake. - Invokes(testing.NewPatchSubresourceActionWithOptions(streamsResource, c.ns, name, pt, data, opts, subresources...), emptyResult) - - if obj == nil { - return emptyResult, err - } - return obj.(*v1beta2.Stream), err -} - -// Apply takes the given apply declarative configuration, applies it and returns the applied stream. -func (c *FakeStreams) Apply(ctx context.Context, stream *jetstreamv1beta2.StreamApplyConfiguration, opts v1.ApplyOptions) (result *v1beta2.Stream, err error) { - if stream == nil { - return nil, fmt.Errorf("stream provided to Apply must not be nil") - } - data, err := json.Marshal(stream) - if err != nil { - return nil, err - } - name := stream.Name - if name == nil { - return nil, fmt.Errorf("stream.Name must be provided to Apply") - } - emptyResult := &v1beta2.Stream{} - obj, err := c.Fake. - Invokes(testing.NewPatchSubresourceActionWithOptions(streamsResource, c.ns, *name, types.ApplyPatchType, data, opts.ToPatchOptions()), emptyResult) - - if obj == nil { - return emptyResult, err - } - return obj.(*v1beta2.Stream), err -} - -// ApplyStatus was generated because the type contains a Status member. -// Add a +genclient:noStatus comment above the type to avoid generating ApplyStatus(). -func (c *FakeStreams) ApplyStatus(ctx context.Context, stream *jetstreamv1beta2.StreamApplyConfiguration, opts v1.ApplyOptions) (result *v1beta2.Stream, err error) { - if stream == nil { - return nil, fmt.Errorf("stream provided to Apply must not be nil") - } - data, err := json.Marshal(stream) - if err != nil { - return nil, err - } - name := stream.Name - if name == nil { - return nil, fmt.Errorf("stream.Name must be provided to Apply") - } - emptyResult := &v1beta2.Stream{} - obj, err := c.Fake. - Invokes(testing.NewPatchSubresourceActionWithOptions(streamsResource, c.ns, *name, types.ApplyPatchType, data, opts.ToPatchOptions(), "status"), emptyResult) - - if obj == nil { - return emptyResult, err +func newFakeStreams(fake *FakeJetstreamV1beta2, namespace string) typedjetstreamv1beta2.StreamInterface { + return &fakeStreams{ + gentype.NewFakeClientWithListAndApply[*v1beta2.Stream, *v1beta2.StreamList, *jetstreamv1beta2.StreamApplyConfiguration]( + fake.Fake, + namespace, + v1beta2.SchemeGroupVersion.WithResource("streams"), + v1beta2.SchemeGroupVersion.WithKind("Stream"), + func() *v1beta2.Stream { return &v1beta2.Stream{} }, + func() *v1beta2.StreamList { return &v1beta2.StreamList{} }, + func(dst, src *v1beta2.StreamList) { dst.ListMeta = src.ListMeta }, + func(list *v1beta2.StreamList) []*v1beta2.Stream { return gentype.ToPointerSlice(list.Items) }, + func(list *v1beta2.StreamList, items []*v1beta2.Stream) { list.Items = gentype.FromPointerSlice(items) }, + ), + fake, } - return obj.(*v1beta2.Stream), err } diff --git a/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/generated_expansion.go b/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/generated_expansion.go index f3410b9a..97b76f2e 100644 --- a/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/generated_expansion.go +++ b/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/generated_expansion.go @@ -1,4 +1,4 @@ -// Copyright 2024 The NATS Authors +// Copyright 2025 The NATS Authors // 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 @@ -19,4 +19,6 @@ type AccountExpansion interface{} type ConsumerExpansion interface{} +type KeyValueExpansion interface{} + type StreamExpansion interface{} diff --git a/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/jetstream_client.go b/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/jetstream_client.go index b6115fcc..c18538bb 100644 --- a/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/jetstream_client.go +++ b/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/jetstream_client.go @@ -1,4 +1,4 @@ -// Copyright 2024 The NATS Authors +// Copyright 2025 The NATS Authors // 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 @@ -16,10 +16,10 @@ package v1beta2 import ( - "net/http" + http "net/http" - v1beta2 "github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2" - "github.com/nats-io/nack/pkg/jetstream/generated/clientset/versioned/scheme" + jetstreamv1beta2 "github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2" + scheme "github.com/nats-io/nack/pkg/jetstream/generated/clientset/versioned/scheme" rest "k8s.io/client-go/rest" ) @@ -27,6 +27,7 @@ type JetstreamV1beta2Interface interface { RESTClient() rest.Interface AccountsGetter ConsumersGetter + KeyValuesGetter StreamsGetter } @@ -43,6 +44,10 @@ func (c *JetstreamV1beta2Client) Consumers(namespace string) ConsumerInterface { return newConsumers(c, namespace) } +func (c *JetstreamV1beta2Client) KeyValues(namespace string) KeyValueInterface { + return newKeyValues(c, namespace) +} + func (c *JetstreamV1beta2Client) Streams(namespace string) StreamInterface { return newStreams(c, namespace) } @@ -92,10 +97,10 @@ func New(c rest.Interface) *JetstreamV1beta2Client { } func setConfigDefaults(config *rest.Config) error { - gv := v1beta2.SchemeGroupVersion + gv := jetstreamv1beta2.SchemeGroupVersion config.GroupVersion = &gv config.APIPath = "/apis" - config.NegotiatedSerializer = scheme.Codecs.WithoutConversion() + config.NegotiatedSerializer = rest.CodecFactoryForGeneratedClient(scheme.Scheme, scheme.Codecs).WithoutConversion() if config.UserAgent == "" { config.UserAgent = rest.DefaultKubernetesUserAgent() diff --git a/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/keyvalue.go b/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/keyvalue.go new file mode 100644 index 00000000..c649bd05 --- /dev/null +++ b/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/keyvalue.go @@ -0,0 +1,71 @@ +// Copyright 2025 The NATS Authors +// 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. + +// Code generated by client-gen. DO NOT EDIT. + +package v1beta2 + +import ( + context "context" + + jetstreamv1beta2 "github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2" + applyconfigurationjetstreamv1beta2 "github.com/nats-io/nack/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2" + scheme "github.com/nats-io/nack/pkg/jetstream/generated/clientset/versioned/scheme" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + gentype "k8s.io/client-go/gentype" +) + +// KeyValuesGetter has a method to return a KeyValueInterface. +// A group's client should implement this interface. +type KeyValuesGetter interface { + KeyValues(namespace string) KeyValueInterface +} + +// KeyValueInterface has methods to work with KeyValue resources. +type KeyValueInterface interface { + Create(ctx context.Context, keyValue *jetstreamv1beta2.KeyValue, opts v1.CreateOptions) (*jetstreamv1beta2.KeyValue, error) + Update(ctx context.Context, keyValue *jetstreamv1beta2.KeyValue, opts v1.UpdateOptions) (*jetstreamv1beta2.KeyValue, error) + // Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). + UpdateStatus(ctx context.Context, keyValue *jetstreamv1beta2.KeyValue, opts v1.UpdateOptions) (*jetstreamv1beta2.KeyValue, error) + Delete(ctx context.Context, name string, opts v1.DeleteOptions) error + DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error + Get(ctx context.Context, name string, opts v1.GetOptions) (*jetstreamv1beta2.KeyValue, error) + List(ctx context.Context, opts v1.ListOptions) (*jetstreamv1beta2.KeyValueList, error) + Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) + Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *jetstreamv1beta2.KeyValue, err error) + Apply(ctx context.Context, keyValue *applyconfigurationjetstreamv1beta2.KeyValueApplyConfiguration, opts v1.ApplyOptions) (result *jetstreamv1beta2.KeyValue, err error) + // Add a +genclient:noStatus comment above the type to avoid generating ApplyStatus(). + ApplyStatus(ctx context.Context, keyValue *applyconfigurationjetstreamv1beta2.KeyValueApplyConfiguration, opts v1.ApplyOptions) (result *jetstreamv1beta2.KeyValue, err error) + KeyValueExpansion +} + +// keyValues implements KeyValueInterface +type keyValues struct { + *gentype.ClientWithListAndApply[*jetstreamv1beta2.KeyValue, *jetstreamv1beta2.KeyValueList, *applyconfigurationjetstreamv1beta2.KeyValueApplyConfiguration] +} + +// newKeyValues returns a KeyValues +func newKeyValues(c *JetstreamV1beta2Client, namespace string) *keyValues { + return &keyValues{ + gentype.NewClientWithListAndApply[*jetstreamv1beta2.KeyValue, *jetstreamv1beta2.KeyValueList, *applyconfigurationjetstreamv1beta2.KeyValueApplyConfiguration]( + "keyvalues", + c.RESTClient(), + scheme.ParameterCodec, + namespace, + func() *jetstreamv1beta2.KeyValue { return &jetstreamv1beta2.KeyValue{} }, + func() *jetstreamv1beta2.KeyValueList { return &jetstreamv1beta2.KeyValueList{} }, + ), + } +} diff --git a/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/stream.go b/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/stream.go index 44008042..644d63f6 100644 --- a/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/stream.go +++ b/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/stream.go @@ -1,4 +1,4 @@ -// Copyright 2024 The NATS Authors +// Copyright 2025 The NATS Authors // 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 @@ -16,10 +16,10 @@ package v1beta2 import ( - "context" + context "context" - v1beta2 "github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2" - jetstreamv1beta2 "github.com/nats-io/nack/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2" + jetstreamv1beta2 "github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2" + applyconfigurationjetstreamv1beta2 "github.com/nats-io/nack/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2" scheme "github.com/nats-io/nack/pkg/jetstream/generated/clientset/versioned/scheme" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" types "k8s.io/apimachinery/pkg/types" @@ -35,36 +35,37 @@ type StreamsGetter interface { // StreamInterface has methods to work with Stream resources. type StreamInterface interface { - Create(ctx context.Context, stream *v1beta2.Stream, opts v1.CreateOptions) (*v1beta2.Stream, error) - Update(ctx context.Context, stream *v1beta2.Stream, opts v1.UpdateOptions) (*v1beta2.Stream, error) + Create(ctx context.Context, stream *jetstreamv1beta2.Stream, opts v1.CreateOptions) (*jetstreamv1beta2.Stream, error) + Update(ctx context.Context, stream *jetstreamv1beta2.Stream, opts v1.UpdateOptions) (*jetstreamv1beta2.Stream, error) // Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). - UpdateStatus(ctx context.Context, stream *v1beta2.Stream, opts v1.UpdateOptions) (*v1beta2.Stream, error) + UpdateStatus(ctx context.Context, stream *jetstreamv1beta2.Stream, opts v1.UpdateOptions) (*jetstreamv1beta2.Stream, error) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error - Get(ctx context.Context, name string, opts v1.GetOptions) (*v1beta2.Stream, error) - List(ctx context.Context, opts v1.ListOptions) (*v1beta2.StreamList, error) + Get(ctx context.Context, name string, opts v1.GetOptions) (*jetstreamv1beta2.Stream, error) + List(ctx context.Context, opts v1.ListOptions) (*jetstreamv1beta2.StreamList, error) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) - Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1beta2.Stream, err error) - Apply(ctx context.Context, stream *jetstreamv1beta2.StreamApplyConfiguration, opts v1.ApplyOptions) (result *v1beta2.Stream, err error) + Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *jetstreamv1beta2.Stream, err error) + Apply(ctx context.Context, stream *applyconfigurationjetstreamv1beta2.StreamApplyConfiguration, opts v1.ApplyOptions) (result *jetstreamv1beta2.Stream, err error) // Add a +genclient:noStatus comment above the type to avoid generating ApplyStatus(). - ApplyStatus(ctx context.Context, stream *jetstreamv1beta2.StreamApplyConfiguration, opts v1.ApplyOptions) (result *v1beta2.Stream, err error) + ApplyStatus(ctx context.Context, stream *applyconfigurationjetstreamv1beta2.StreamApplyConfiguration, opts v1.ApplyOptions) (result *jetstreamv1beta2.Stream, err error) StreamExpansion } // streams implements StreamInterface type streams struct { - *gentype.ClientWithListAndApply[*v1beta2.Stream, *v1beta2.StreamList, *jetstreamv1beta2.StreamApplyConfiguration] + *gentype.ClientWithListAndApply[*jetstreamv1beta2.Stream, *jetstreamv1beta2.StreamList, *applyconfigurationjetstreamv1beta2.StreamApplyConfiguration] } // newStreams returns a Streams func newStreams(c *JetstreamV1beta2Client, namespace string) *streams { return &streams{ - gentype.NewClientWithListAndApply[*v1beta2.Stream, *v1beta2.StreamList, *jetstreamv1beta2.StreamApplyConfiguration]( + gentype.NewClientWithListAndApply[*jetstreamv1beta2.Stream, *jetstreamv1beta2.StreamList, *applyconfigurationjetstreamv1beta2.StreamApplyConfiguration]( "streams", c.RESTClient(), scheme.ParameterCodec, namespace, - func() *v1beta2.Stream { return &v1beta2.Stream{} }, - func() *v1beta2.StreamList { return &v1beta2.StreamList{} }), + func() *jetstreamv1beta2.Stream { return &jetstreamv1beta2.Stream{} }, + func() *jetstreamv1beta2.StreamList { return &jetstreamv1beta2.StreamList{} }, + ), } } diff --git a/pkg/jetstream/generated/informers/externalversions/factory.go b/pkg/jetstream/generated/informers/externalversions/factory.go index 8e6d034f..1f01ea2e 100644 --- a/pkg/jetstream/generated/informers/externalversions/factory.go +++ b/pkg/jetstream/generated/informers/externalversions/factory.go @@ -1,4 +1,4 @@ -// Copyright 2024 The NATS Authors +// Copyright 2025 The NATS Authors // 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 diff --git a/pkg/jetstream/generated/informers/externalversions/generic.go b/pkg/jetstream/generated/informers/externalversions/generic.go index e5ba543b..b03765d4 100644 --- a/pkg/jetstream/generated/informers/externalversions/generic.go +++ b/pkg/jetstream/generated/informers/externalversions/generic.go @@ -1,4 +1,4 @@ -// Copyright 2024 The NATS Authors +// Copyright 2025 The NATS Authors // 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 @@ -16,7 +16,7 @@ package externalversions import ( - "fmt" + fmt "fmt" v1beta2 "github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2" schema "k8s.io/apimachinery/pkg/runtime/schema" @@ -54,6 +54,8 @@ func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource return &genericInformer{resource: resource.GroupResource(), informer: f.Jetstream().V1beta2().Accounts().Informer()}, nil case v1beta2.SchemeGroupVersion.WithResource("consumers"): return &genericInformer{resource: resource.GroupResource(), informer: f.Jetstream().V1beta2().Consumers().Informer()}, nil + case v1beta2.SchemeGroupVersion.WithResource("keyvalues"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Jetstream().V1beta2().KeyValues().Informer()}, nil case v1beta2.SchemeGroupVersion.WithResource("streams"): return &genericInformer{resource: resource.GroupResource(), informer: f.Jetstream().V1beta2().Streams().Informer()}, nil diff --git a/pkg/jetstream/generated/informers/externalversions/internalinterfaces/factory_interfaces.go b/pkg/jetstream/generated/informers/externalversions/internalinterfaces/factory_interfaces.go index 5156b38f..bec8fb37 100644 --- a/pkg/jetstream/generated/informers/externalversions/internalinterfaces/factory_interfaces.go +++ b/pkg/jetstream/generated/informers/externalversions/internalinterfaces/factory_interfaces.go @@ -1,4 +1,4 @@ -// Copyright 2024 The NATS Authors +// Copyright 2025 The NATS Authors // 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 diff --git a/pkg/jetstream/generated/informers/externalversions/jetstream/interface.go b/pkg/jetstream/generated/informers/externalversions/jetstream/interface.go index ba66ab00..6101526b 100644 --- a/pkg/jetstream/generated/informers/externalversions/jetstream/interface.go +++ b/pkg/jetstream/generated/informers/externalversions/jetstream/interface.go @@ -1,4 +1,4 @@ -// Copyright 2024 The NATS Authors +// Copyright 2025 The NATS Authors // 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 diff --git a/pkg/jetstream/generated/informers/externalversions/jetstream/v1beta2/account.go b/pkg/jetstream/generated/informers/externalversions/jetstream/v1beta2/account.go index da16b01b..2e9d5e47 100644 --- a/pkg/jetstream/generated/informers/externalversions/jetstream/v1beta2/account.go +++ b/pkg/jetstream/generated/informers/externalversions/jetstream/v1beta2/account.go @@ -1,4 +1,4 @@ -// Copyright 2024 The NATS Authors +// Copyright 2025 The NATS Authors // 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 @@ -16,13 +16,13 @@ package v1beta2 import ( - "context" + context "context" time "time" - jetstreamv1beta2 "github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2" + apisjetstreamv1beta2 "github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2" versioned "github.com/nats-io/nack/pkg/jetstream/generated/clientset/versioned" internalinterfaces "github.com/nats-io/nack/pkg/jetstream/generated/informers/externalversions/internalinterfaces" - v1beta2 "github.com/nats-io/nack/pkg/jetstream/generated/listers/jetstream/v1beta2" + jetstreamv1beta2 "github.com/nats-io/nack/pkg/jetstream/generated/listers/jetstream/v1beta2" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" watch "k8s.io/apimachinery/pkg/watch" @@ -33,7 +33,7 @@ import ( // Accounts. type AccountInformer interface { Informer() cache.SharedIndexInformer - Lister() v1beta2.AccountLister + Lister() jetstreamv1beta2.AccountLister } type accountInformer struct { @@ -68,7 +68,7 @@ func NewFilteredAccountInformer(client versioned.Interface, namespace string, re return client.JetstreamV1beta2().Accounts(namespace).Watch(context.TODO(), options) }, }, - &jetstreamv1beta2.Account{}, + &apisjetstreamv1beta2.Account{}, resyncPeriod, indexers, ) @@ -79,9 +79,9 @@ func (f *accountInformer) defaultInformer(client versioned.Interface, resyncPeri } func (f *accountInformer) Informer() cache.SharedIndexInformer { - return f.factory.InformerFor(&jetstreamv1beta2.Account{}, f.defaultInformer) + return f.factory.InformerFor(&apisjetstreamv1beta2.Account{}, f.defaultInformer) } -func (f *accountInformer) Lister() v1beta2.AccountLister { - return v1beta2.NewAccountLister(f.Informer().GetIndexer()) +func (f *accountInformer) Lister() jetstreamv1beta2.AccountLister { + return jetstreamv1beta2.NewAccountLister(f.Informer().GetIndexer()) } diff --git a/pkg/jetstream/generated/informers/externalversions/jetstream/v1beta2/consumer.go b/pkg/jetstream/generated/informers/externalversions/jetstream/v1beta2/consumer.go index ef400250..09aff42e 100644 --- a/pkg/jetstream/generated/informers/externalversions/jetstream/v1beta2/consumer.go +++ b/pkg/jetstream/generated/informers/externalversions/jetstream/v1beta2/consumer.go @@ -1,4 +1,4 @@ -// Copyright 2024 The NATS Authors +// Copyright 2025 The NATS Authors // 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 @@ -16,13 +16,13 @@ package v1beta2 import ( - "context" + context "context" time "time" - jetstreamv1beta2 "github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2" + apisjetstreamv1beta2 "github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2" versioned "github.com/nats-io/nack/pkg/jetstream/generated/clientset/versioned" internalinterfaces "github.com/nats-io/nack/pkg/jetstream/generated/informers/externalversions/internalinterfaces" - v1beta2 "github.com/nats-io/nack/pkg/jetstream/generated/listers/jetstream/v1beta2" + jetstreamv1beta2 "github.com/nats-io/nack/pkg/jetstream/generated/listers/jetstream/v1beta2" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" watch "k8s.io/apimachinery/pkg/watch" @@ -33,7 +33,7 @@ import ( // Consumers. type ConsumerInformer interface { Informer() cache.SharedIndexInformer - Lister() v1beta2.ConsumerLister + Lister() jetstreamv1beta2.ConsumerLister } type consumerInformer struct { @@ -68,7 +68,7 @@ func NewFilteredConsumerInformer(client versioned.Interface, namespace string, r return client.JetstreamV1beta2().Consumers(namespace).Watch(context.TODO(), options) }, }, - &jetstreamv1beta2.Consumer{}, + &apisjetstreamv1beta2.Consumer{}, resyncPeriod, indexers, ) @@ -79,9 +79,9 @@ func (f *consumerInformer) defaultInformer(client versioned.Interface, resyncPer } func (f *consumerInformer) Informer() cache.SharedIndexInformer { - return f.factory.InformerFor(&jetstreamv1beta2.Consumer{}, f.defaultInformer) + return f.factory.InformerFor(&apisjetstreamv1beta2.Consumer{}, f.defaultInformer) } -func (f *consumerInformer) Lister() v1beta2.ConsumerLister { - return v1beta2.NewConsumerLister(f.Informer().GetIndexer()) +func (f *consumerInformer) Lister() jetstreamv1beta2.ConsumerLister { + return jetstreamv1beta2.NewConsumerLister(f.Informer().GetIndexer()) } diff --git a/pkg/jetstream/generated/informers/externalversions/jetstream/v1beta2/interface.go b/pkg/jetstream/generated/informers/externalversions/jetstream/v1beta2/interface.go index 05bf3fdb..29a492fb 100644 --- a/pkg/jetstream/generated/informers/externalversions/jetstream/v1beta2/interface.go +++ b/pkg/jetstream/generated/informers/externalversions/jetstream/v1beta2/interface.go @@ -1,4 +1,4 @@ -// Copyright 2024 The NATS Authors +// Copyright 2025 The NATS Authors // 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 @@ -25,6 +25,8 @@ type Interface interface { Accounts() AccountInformer // Consumers returns a ConsumerInformer. Consumers() ConsumerInformer + // KeyValues returns a KeyValueInformer. + KeyValues() KeyValueInformer // Streams returns a StreamInformer. Streams() StreamInformer } @@ -50,6 +52,11 @@ func (v *version) Consumers() ConsumerInformer { return &consumerInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} } +// KeyValues returns a KeyValueInformer. +func (v *version) KeyValues() KeyValueInformer { + return &keyValueInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} +} + // Streams returns a StreamInformer. func (v *version) Streams() StreamInformer { return &streamInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} diff --git a/pkg/jetstream/generated/informers/externalversions/jetstream/v1beta2/keyvalue.go b/pkg/jetstream/generated/informers/externalversions/jetstream/v1beta2/keyvalue.go new file mode 100644 index 00000000..3f3c7489 --- /dev/null +++ b/pkg/jetstream/generated/informers/externalversions/jetstream/v1beta2/keyvalue.go @@ -0,0 +1,87 @@ +// Copyright 2025 The NATS Authors +// 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. + +// Code generated by informer-gen. DO NOT EDIT. + +package v1beta2 + +import ( + context "context" + time "time" + + apisjetstreamv1beta2 "github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2" + versioned "github.com/nats-io/nack/pkg/jetstream/generated/clientset/versioned" + internalinterfaces "github.com/nats-io/nack/pkg/jetstream/generated/informers/externalversions/internalinterfaces" + jetstreamv1beta2 "github.com/nats-io/nack/pkg/jetstream/generated/listers/jetstream/v1beta2" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + watch "k8s.io/apimachinery/pkg/watch" + cache "k8s.io/client-go/tools/cache" +) + +// KeyValueInformer provides access to a shared informer and lister for +// KeyValues. +type KeyValueInformer interface { + Informer() cache.SharedIndexInformer + Lister() jetstreamv1beta2.KeyValueLister +} + +type keyValueInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string +} + +// NewKeyValueInformer constructs a new informer for KeyValue type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewKeyValueInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredKeyValueInformer(client, namespace, resyncPeriod, indexers, nil) +} + +// NewFilteredKeyValueInformer constructs a new informer for KeyValue type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewFilteredKeyValueInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { + return cache.NewSharedIndexInformer( + &cache.ListWatch{ + ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.JetstreamV1beta2().KeyValues(namespace).List(context.TODO(), options) + }, + WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.JetstreamV1beta2().KeyValues(namespace).Watch(context.TODO(), options) + }, + }, + &apisjetstreamv1beta2.KeyValue{}, + resyncPeriod, + indexers, + ) +} + +func (f *keyValueInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredKeyValueInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) +} + +func (f *keyValueInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&apisjetstreamv1beta2.KeyValue{}, f.defaultInformer) +} + +func (f *keyValueInformer) Lister() jetstreamv1beta2.KeyValueLister { + return jetstreamv1beta2.NewKeyValueLister(f.Informer().GetIndexer()) +} diff --git a/pkg/jetstream/generated/informers/externalversions/jetstream/v1beta2/stream.go b/pkg/jetstream/generated/informers/externalversions/jetstream/v1beta2/stream.go index d8d7dbd4..b3af693c 100644 --- a/pkg/jetstream/generated/informers/externalversions/jetstream/v1beta2/stream.go +++ b/pkg/jetstream/generated/informers/externalversions/jetstream/v1beta2/stream.go @@ -1,4 +1,4 @@ -// Copyright 2024 The NATS Authors +// Copyright 2025 The NATS Authors // 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 @@ -16,13 +16,13 @@ package v1beta2 import ( - "context" + context "context" time "time" - jetstreamv1beta2 "github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2" + apisjetstreamv1beta2 "github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2" versioned "github.com/nats-io/nack/pkg/jetstream/generated/clientset/versioned" internalinterfaces "github.com/nats-io/nack/pkg/jetstream/generated/informers/externalversions/internalinterfaces" - v1beta2 "github.com/nats-io/nack/pkg/jetstream/generated/listers/jetstream/v1beta2" + jetstreamv1beta2 "github.com/nats-io/nack/pkg/jetstream/generated/listers/jetstream/v1beta2" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" watch "k8s.io/apimachinery/pkg/watch" @@ -33,7 +33,7 @@ import ( // Streams. type StreamInformer interface { Informer() cache.SharedIndexInformer - Lister() v1beta2.StreamLister + Lister() jetstreamv1beta2.StreamLister } type streamInformer struct { @@ -68,7 +68,7 @@ func NewFilteredStreamInformer(client versioned.Interface, namespace string, res return client.JetstreamV1beta2().Streams(namespace).Watch(context.TODO(), options) }, }, - &jetstreamv1beta2.Stream{}, + &apisjetstreamv1beta2.Stream{}, resyncPeriod, indexers, ) @@ -79,9 +79,9 @@ func (f *streamInformer) defaultInformer(client versioned.Interface, resyncPerio } func (f *streamInformer) Informer() cache.SharedIndexInformer { - return f.factory.InformerFor(&jetstreamv1beta2.Stream{}, f.defaultInformer) + return f.factory.InformerFor(&apisjetstreamv1beta2.Stream{}, f.defaultInformer) } -func (f *streamInformer) Lister() v1beta2.StreamLister { - return v1beta2.NewStreamLister(f.Informer().GetIndexer()) +func (f *streamInformer) Lister() jetstreamv1beta2.StreamLister { + return jetstreamv1beta2.NewStreamLister(f.Informer().GetIndexer()) } diff --git a/pkg/jetstream/generated/listers/jetstream/v1beta2/account.go b/pkg/jetstream/generated/listers/jetstream/v1beta2/account.go index ef987a4e..019d7df5 100644 --- a/pkg/jetstream/generated/listers/jetstream/v1beta2/account.go +++ b/pkg/jetstream/generated/listers/jetstream/v1beta2/account.go @@ -1,4 +1,4 @@ -// Copyright 2024 The NATS Authors +// Copyright 2025 The NATS Authors // 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 @@ -16,10 +16,10 @@ package v1beta2 import ( - v1beta2 "github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2" - "k8s.io/apimachinery/pkg/labels" - "k8s.io/client-go/listers" - "k8s.io/client-go/tools/cache" + jetstreamv1beta2 "github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2" + labels "k8s.io/apimachinery/pkg/labels" + listers "k8s.io/client-go/listers" + cache "k8s.io/client-go/tools/cache" ) // AccountLister helps list Accounts. @@ -27,7 +27,7 @@ import ( type AccountLister interface { // List lists all Accounts in the indexer. // Objects returned here must be treated as read-only. - List(selector labels.Selector) (ret []*v1beta2.Account, err error) + List(selector labels.Selector) (ret []*jetstreamv1beta2.Account, err error) // Accounts returns an object that can list and get Accounts. Accounts(namespace string) AccountNamespaceLister AccountListerExpansion @@ -35,17 +35,17 @@ type AccountLister interface { // accountLister implements the AccountLister interface. type accountLister struct { - listers.ResourceIndexer[*v1beta2.Account] + listers.ResourceIndexer[*jetstreamv1beta2.Account] } // NewAccountLister returns a new AccountLister. func NewAccountLister(indexer cache.Indexer) AccountLister { - return &accountLister{listers.New[*v1beta2.Account](indexer, v1beta2.Resource("account"))} + return &accountLister{listers.New[*jetstreamv1beta2.Account](indexer, jetstreamv1beta2.Resource("account"))} } // Accounts returns an object that can list and get Accounts. func (s *accountLister) Accounts(namespace string) AccountNamespaceLister { - return accountNamespaceLister{listers.NewNamespaced[*v1beta2.Account](s.ResourceIndexer, namespace)} + return accountNamespaceLister{listers.NewNamespaced[*jetstreamv1beta2.Account](s.ResourceIndexer, namespace)} } // AccountNamespaceLister helps list and get Accounts. @@ -53,15 +53,15 @@ func (s *accountLister) Accounts(namespace string) AccountNamespaceLister { type AccountNamespaceLister interface { // List lists all Accounts in the indexer for a given namespace. // Objects returned here must be treated as read-only. - List(selector labels.Selector) (ret []*v1beta2.Account, err error) + List(selector labels.Selector) (ret []*jetstreamv1beta2.Account, err error) // Get retrieves the Account from the indexer for a given namespace and name. // Objects returned here must be treated as read-only. - Get(name string) (*v1beta2.Account, error) + Get(name string) (*jetstreamv1beta2.Account, error) AccountNamespaceListerExpansion } // accountNamespaceLister implements the AccountNamespaceLister // interface. type accountNamespaceLister struct { - listers.ResourceIndexer[*v1beta2.Account] + listers.ResourceIndexer[*jetstreamv1beta2.Account] } diff --git a/pkg/jetstream/generated/listers/jetstream/v1beta2/consumer.go b/pkg/jetstream/generated/listers/jetstream/v1beta2/consumer.go index 1f46c5a0..651a55d8 100644 --- a/pkg/jetstream/generated/listers/jetstream/v1beta2/consumer.go +++ b/pkg/jetstream/generated/listers/jetstream/v1beta2/consumer.go @@ -1,4 +1,4 @@ -// Copyright 2024 The NATS Authors +// Copyright 2025 The NATS Authors // 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 @@ -16,10 +16,10 @@ package v1beta2 import ( - v1beta2 "github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2" - "k8s.io/apimachinery/pkg/labels" - "k8s.io/client-go/listers" - "k8s.io/client-go/tools/cache" + jetstreamv1beta2 "github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2" + labels "k8s.io/apimachinery/pkg/labels" + listers "k8s.io/client-go/listers" + cache "k8s.io/client-go/tools/cache" ) // ConsumerLister helps list Consumers. @@ -27,7 +27,7 @@ import ( type ConsumerLister interface { // List lists all Consumers in the indexer. // Objects returned here must be treated as read-only. - List(selector labels.Selector) (ret []*v1beta2.Consumer, err error) + List(selector labels.Selector) (ret []*jetstreamv1beta2.Consumer, err error) // Consumers returns an object that can list and get Consumers. Consumers(namespace string) ConsumerNamespaceLister ConsumerListerExpansion @@ -35,17 +35,17 @@ type ConsumerLister interface { // consumerLister implements the ConsumerLister interface. type consumerLister struct { - listers.ResourceIndexer[*v1beta2.Consumer] + listers.ResourceIndexer[*jetstreamv1beta2.Consumer] } // NewConsumerLister returns a new ConsumerLister. func NewConsumerLister(indexer cache.Indexer) ConsumerLister { - return &consumerLister{listers.New[*v1beta2.Consumer](indexer, v1beta2.Resource("consumer"))} + return &consumerLister{listers.New[*jetstreamv1beta2.Consumer](indexer, jetstreamv1beta2.Resource("consumer"))} } // Consumers returns an object that can list and get Consumers. func (s *consumerLister) Consumers(namespace string) ConsumerNamespaceLister { - return consumerNamespaceLister{listers.NewNamespaced[*v1beta2.Consumer](s.ResourceIndexer, namespace)} + return consumerNamespaceLister{listers.NewNamespaced[*jetstreamv1beta2.Consumer](s.ResourceIndexer, namespace)} } // ConsumerNamespaceLister helps list and get Consumers. @@ -53,15 +53,15 @@ func (s *consumerLister) Consumers(namespace string) ConsumerNamespaceLister { type ConsumerNamespaceLister interface { // List lists all Consumers in the indexer for a given namespace. // Objects returned here must be treated as read-only. - List(selector labels.Selector) (ret []*v1beta2.Consumer, err error) + List(selector labels.Selector) (ret []*jetstreamv1beta2.Consumer, err error) // Get retrieves the Consumer from the indexer for a given namespace and name. // Objects returned here must be treated as read-only. - Get(name string) (*v1beta2.Consumer, error) + Get(name string) (*jetstreamv1beta2.Consumer, error) ConsumerNamespaceListerExpansion } // consumerNamespaceLister implements the ConsumerNamespaceLister // interface. type consumerNamespaceLister struct { - listers.ResourceIndexer[*v1beta2.Consumer] + listers.ResourceIndexer[*jetstreamv1beta2.Consumer] } diff --git a/pkg/jetstream/generated/listers/jetstream/v1beta2/expansion_generated.go b/pkg/jetstream/generated/listers/jetstream/v1beta2/expansion_generated.go index 5cacc517..b49efd09 100644 --- a/pkg/jetstream/generated/listers/jetstream/v1beta2/expansion_generated.go +++ b/pkg/jetstream/generated/listers/jetstream/v1beta2/expansion_generated.go @@ -1,4 +1,4 @@ -// Copyright 2024 The NATS Authors +// Copyright 2025 The NATS Authors // 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 @@ -31,6 +31,14 @@ type ConsumerListerExpansion interface{} // ConsumerNamespaceLister. type ConsumerNamespaceListerExpansion interface{} +// KeyValueListerExpansion allows custom methods to be added to +// KeyValueLister. +type KeyValueListerExpansion interface{} + +// KeyValueNamespaceListerExpansion allows custom methods to be added to +// KeyValueNamespaceLister. +type KeyValueNamespaceListerExpansion interface{} + // StreamListerExpansion allows custom methods to be added to // StreamLister. type StreamListerExpansion interface{} diff --git a/pkg/jetstream/generated/listers/jetstream/v1beta2/keyvalue.go b/pkg/jetstream/generated/listers/jetstream/v1beta2/keyvalue.go new file mode 100644 index 00000000..e9a691a7 --- /dev/null +++ b/pkg/jetstream/generated/listers/jetstream/v1beta2/keyvalue.go @@ -0,0 +1,67 @@ +// Copyright 2025 The NATS Authors +// 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. + +// Code generated by lister-gen. DO NOT EDIT. + +package v1beta2 + +import ( + jetstreamv1beta2 "github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2" + labels "k8s.io/apimachinery/pkg/labels" + listers "k8s.io/client-go/listers" + cache "k8s.io/client-go/tools/cache" +) + +// KeyValueLister helps list KeyValues. +// All objects returned here must be treated as read-only. +type KeyValueLister interface { + // List lists all KeyValues in the indexer. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*jetstreamv1beta2.KeyValue, err error) + // KeyValues returns an object that can list and get KeyValues. + KeyValues(namespace string) KeyValueNamespaceLister + KeyValueListerExpansion +} + +// keyValueLister implements the KeyValueLister interface. +type keyValueLister struct { + listers.ResourceIndexer[*jetstreamv1beta2.KeyValue] +} + +// NewKeyValueLister returns a new KeyValueLister. +func NewKeyValueLister(indexer cache.Indexer) KeyValueLister { + return &keyValueLister{listers.New[*jetstreamv1beta2.KeyValue](indexer, jetstreamv1beta2.Resource("keyvalue"))} +} + +// KeyValues returns an object that can list and get KeyValues. +func (s *keyValueLister) KeyValues(namespace string) KeyValueNamespaceLister { + return keyValueNamespaceLister{listers.NewNamespaced[*jetstreamv1beta2.KeyValue](s.ResourceIndexer, namespace)} +} + +// KeyValueNamespaceLister helps list and get KeyValues. +// All objects returned here must be treated as read-only. +type KeyValueNamespaceLister interface { + // List lists all KeyValues in the indexer for a given namespace. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*jetstreamv1beta2.KeyValue, err error) + // Get retrieves the KeyValue from the indexer for a given namespace and name. + // Objects returned here must be treated as read-only. + Get(name string) (*jetstreamv1beta2.KeyValue, error) + KeyValueNamespaceListerExpansion +} + +// keyValueNamespaceLister implements the KeyValueNamespaceLister +// interface. +type keyValueNamespaceLister struct { + listers.ResourceIndexer[*jetstreamv1beta2.KeyValue] +} diff --git a/pkg/jetstream/generated/listers/jetstream/v1beta2/stream.go b/pkg/jetstream/generated/listers/jetstream/v1beta2/stream.go index 7151a170..19bcfecf 100644 --- a/pkg/jetstream/generated/listers/jetstream/v1beta2/stream.go +++ b/pkg/jetstream/generated/listers/jetstream/v1beta2/stream.go @@ -1,4 +1,4 @@ -// Copyright 2024 The NATS Authors +// Copyright 2025 The NATS Authors // 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 @@ -16,10 +16,10 @@ package v1beta2 import ( - v1beta2 "github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2" - "k8s.io/apimachinery/pkg/labels" - "k8s.io/client-go/listers" - "k8s.io/client-go/tools/cache" + jetstreamv1beta2 "github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2" + labels "k8s.io/apimachinery/pkg/labels" + listers "k8s.io/client-go/listers" + cache "k8s.io/client-go/tools/cache" ) // StreamLister helps list Streams. @@ -27,7 +27,7 @@ import ( type StreamLister interface { // List lists all Streams in the indexer. // Objects returned here must be treated as read-only. - List(selector labels.Selector) (ret []*v1beta2.Stream, err error) + List(selector labels.Selector) (ret []*jetstreamv1beta2.Stream, err error) // Streams returns an object that can list and get Streams. Streams(namespace string) StreamNamespaceLister StreamListerExpansion @@ -35,17 +35,17 @@ type StreamLister interface { // streamLister implements the StreamLister interface. type streamLister struct { - listers.ResourceIndexer[*v1beta2.Stream] + listers.ResourceIndexer[*jetstreamv1beta2.Stream] } // NewStreamLister returns a new StreamLister. func NewStreamLister(indexer cache.Indexer) StreamLister { - return &streamLister{listers.New[*v1beta2.Stream](indexer, v1beta2.Resource("stream"))} + return &streamLister{listers.New[*jetstreamv1beta2.Stream](indexer, jetstreamv1beta2.Resource("stream"))} } // Streams returns an object that can list and get Streams. func (s *streamLister) Streams(namespace string) StreamNamespaceLister { - return streamNamespaceLister{listers.NewNamespaced[*v1beta2.Stream](s.ResourceIndexer, namespace)} + return streamNamespaceLister{listers.NewNamespaced[*jetstreamv1beta2.Stream](s.ResourceIndexer, namespace)} } // StreamNamespaceLister helps list and get Streams. @@ -53,15 +53,15 @@ func (s *streamLister) Streams(namespace string) StreamNamespaceLister { type StreamNamespaceLister interface { // List lists all Streams in the indexer for a given namespace. // Objects returned here must be treated as read-only. - List(selector labels.Selector) (ret []*v1beta2.Stream, err error) + List(selector labels.Selector) (ret []*jetstreamv1beta2.Stream, err error) // Get retrieves the Stream from the indexer for a given namespace and name. // Objects returned here must be treated as read-only. - Get(name string) (*v1beta2.Stream, error) + Get(name string) (*jetstreamv1beta2.Stream, error) StreamNamespaceListerExpansion } // streamNamespaceLister implements the StreamNamespaceLister // interface. type streamNamespaceLister struct { - listers.ResourceIndexer[*v1beta2.Stream] + listers.ResourceIndexer[*jetstreamv1beta2.Stream] } diff --git a/pkg/k8scodegen/file-header.txt b/pkg/k8scodegen/file-header.txt index 780fa5e9..a620c453 100644 --- a/pkg/k8scodegen/file-header.txt +++ b/pkg/k8scodegen/file-header.txt @@ -1,4 +1,4 @@ -// Copyright 2024 The NATS Authors +// Copyright 2025 The NATS Authors // 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 diff --git a/pkg/natsreloader/natsreloader_test.go b/pkg/natsreloader/natsreloader_test.go index 1e05edee..2425e434 100644 --- a/pkg/natsreloader/natsreloader_test.go +++ b/pkg/natsreloader/natsreloader_test.go @@ -88,10 +88,12 @@ tls: { ` ) -var configContents = `port = 2222` -var newConfigContents = `port = 2222 +var ( + configContents = `port = 2222` + newConfigContents = `port = 2222 someOtherThing = "bar" ` +) func TestReloader(t *testing.T) { // Setup a pidfile that points to us @@ -261,7 +263,6 @@ func TestInclude(t *testing.T) { t.Fatal("Expected include paths do not match") } } - } func TestFileFinder(t *testing.T) { From 60d50f82053a3fd82557ab2efca8e5359f489674 Mon Sep 17 00:00:00 2001 From: Samuel Attwood Date: Tue, 7 Jan 2025 01:47:12 -0500 Subject: [PATCH 06/19] Add initial object store types. Update Stream config and reorg to keep in-line with nats client. --- controllers/jetstream/stream.go | 14 +- deploy/crds.yml | 235 ++++++------ internal/controller/consumer_controller.go | 2 +- ...eyvalue_controller.go => kv_controller.go} | 26 +- ...ntroller_test.go => kv_controller_test.go} | 42 ++- internal/controller/object_controller.go | 280 ++++++++++++++ internal/controller/stream_controller.go | 10 +- internal/controller/stream_controller_test.go | 18 +- internal/controller/types.go | 3 +- .../apis/jetstream/v1beta2/keyvaluetypes.go | 55 --- .../apis/jetstream/v1beta2/kvtypes.go | 49 +++ .../apis/jetstream/v1beta2/objecttypes.go | 45 +++ .../apis/jetstream/v1beta2/register.go | 2 + .../apis/jetstream/v1beta2/streamtypes.go | 51 ++- pkg/jetstream/apis/jetstream/v1beta2/types.go | 15 + .../v1beta2/zz_generated.deepcopy.go | 207 ++++++++--- .../jetstream/v1beta2/basestreamconfig.go | 92 +++++ .../jetstream/v1beta2/consumerlimits.go | 45 +++ .../jetstream/v1beta2/keyvaluespec.go | 171 +++------ .../jetstream/v1beta2/objectstore.go | 222 +++++++++++ .../jetstream/v1beta2/objectstorespec.go | 114 ++++++ .../jetstream/v1beta2/streamspec.go | 346 ++++++++---------- .../generated/applyconfiguration/utils.go | 8 + .../v1beta2/fake/fake_jetstream_client.go | 4 + .../v1beta2/fake/fake_objectstore.go | 48 +++ .../jetstream/v1beta2/generated_expansion.go | 2 + .../jetstream/v1beta2/jetstream_client.go | 5 + .../typed/jetstream/v1beta2/objectstore.go | 71 ++++ .../informers/externalversions/generic.go | 2 + .../jetstream/v1beta2/interface.go | 7 + .../jetstream/v1beta2/objectstore.go | 87 +++++ .../jetstream/v1beta2/expansion_generated.go | 8 + .../listers/jetstream/v1beta2/objectstore.go | 67 ++++ 33 files changed, 1755 insertions(+), 598 deletions(-) rename internal/controller/{keyvalue_controller.go => kv_controller.go} (93%) rename internal/controller/{keyvalue_controller_test.go => kv_controller_test.go} (96%) create mode 100644 internal/controller/object_controller.go delete mode 100644 pkg/jetstream/apis/jetstream/v1beta2/keyvaluetypes.go create mode 100644 pkg/jetstream/apis/jetstream/v1beta2/kvtypes.go create mode 100644 pkg/jetstream/apis/jetstream/v1beta2/objecttypes.go create mode 100644 pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/basestreamconfig.go create mode 100644 pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/consumerlimits.go create mode 100644 pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/objectstore.go create mode 100644 pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/objectstorespec.go create mode 100644 pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/fake/fake_objectstore.go create mode 100644 pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/objectstore.go create mode 100644 pkg/jetstream/generated/informers/externalversions/jetstream/v1beta2/objectstore.go create mode 100644 pkg/jetstream/generated/listers/jetstream/v1beta2/objectstore.go diff --git a/controllers/jetstream/stream.go b/controllers/jetstream/stream.go index 0eb67d26..813c73cf 100644 --- a/controllers/jetstream/stream.go +++ b/controllers/jetstream/stream.go @@ -280,10 +280,10 @@ func createStream(ctx context.Context, c jsmClient, spec apis.StreamSpec) (err e return nil }) - if spec.Republish != nil { + if spec.RePublish != nil { opts = append(opts, jsm.Republish(&jsmapi.RePublish{ - Source: spec.Republish.Source, - Destination: spec.Republish.Destination, + Source: spec.RePublish.Source, + Destination: spec.RePublish.Destination, })) } @@ -387,11 +387,11 @@ func updateStream(ctx context.Context, c jsmClient, spec apis.StreamSpec) (err e FirstSeq: spec.FirstSequence, SubjectTransform: subjectTransform, } - if spec.Republish != nil { + if spec.RePublish != nil { config.RePublish = &jsmapi.RePublish{ - Source: spec.Republish.Source, - Destination: spec.Republish.Destination, - HeadersOnly: spec.Republish.HeadersOnly, + Source: spec.RePublish.Source, + Destination: spec.RePublish.Destination, + HeadersOnly: spec.RePublish.HeadersOnly, } } if spec.Mirror != nil { diff --git a/deploy/crds.yml b/deploy/crds.yml index 618e109c..e179785a 100644 --- a/deploy/crds.yml +++ b/deploy/crds.yml @@ -28,6 +28,9 @@ spec: type: string pattern: '^[^.*>]*$' minLength: 1 + description: + description: The description of the stream. + type: string subjects: description: A list of subjects to consume, supports wildcards. type: array @@ -58,10 +61,25 @@ spec: type: integer minimum: -1 default: -1 + discard: + description: When a Stream reach it's limits either old messages are deleted or new ones are denied. + type: string + enum: + - old + - new + default: old + discardPerSubject: + description: Allows to discard messages on a subject basis. + type: boolean + default: false maxAge: description: Maximum age of any message in the stream, expressed in Go's time.Duration format. Empty for unlimited. type: string default: '' + maxMsgsPerSubject: + description: The maximum of messages per subject. + type: integer + default: 0 maxMsgSize: description: The largest message that will be accepted by the Stream. -1 for unlimited. type: integer @@ -83,23 +101,19 @@ spec: description: Disables acknowledging messages that are received by the Stream. type: boolean default: false - discard: - description: When a Stream reach it's limits either old messages are deleted or new ones are denied. - type: string - enum: - - old - - new - default: old duplicateWindow: description: The duration window to track duplicate messages for. type: string - description: - description: The description of the stream. - type: string - maxMsgsPerSubject: - description: The maximum of messages per subject. - type: integer - default: 0 + placement: + description: A stream's placement. + type: object + properties: + cluster: + type: string + tags: + type: array + items: + type: string mirror: description: A stream mirror. type: object @@ -130,16 +144,6 @@ spec: dest: description: Destination subject. type: string - placement: - description: A stream's placement. - type: object - properties: - cluster: - type: string - tags: - type: array - items: - type: string sources: description: A stream's sources. type: array @@ -172,58 +176,22 @@ spec: dest: description: Destination subject. type: string - metadata: - description: Additional Stream metadata. - type: object - additionalProperties: - type: string - servers: - description: A list of servers for creating stream - type: array - items: - type: string - default: [] - creds: - description: NATS user credentials for connecting to servers. Please make sure your controller has mounted the cerds on its path. - type: string - default: '' - nkey: - description: NATS user NKey for connecting to servers. - type: string - default: '' - tls: - description: A client's TLS certs and keys. - type: object - properties: - clientCert: - description: A client's cert filepath. Should be mounted. - type: string - clientKey: - description: A client's key filepath. Should be mounted. - type: string - rootCas: - description: A list of filepaths to CAs. Should be mounted. - type: array - items: - type: string - account: - description: Name of the account to which the Stream belongs. - type: string - pattern: '^[^.*>]*$' - republish: - description: Republish configuration of the stream. - type: object - properties: - destination: - type: string - description: Messages will be additionally published to that subject. - source: - type: string - description: Messages will be published from that subject to the destination subject. - firstSequence: - description: Sequence number from which the Stream will start. - type: number - default: 0 + sealed: + description: Seal an existing stream so no new messages can be added. + type: boolean + default: false + denyDelete: + description: When true, restricts the ability to delete messages from a stream via the API. Cannot be changed once set to true. + type: boolean + default: false + denyPurge: + description: When true, restricts the ability to purge a stream via the API. Cannot be changed once set to true. + type: boolean + default: false + allowRollup: + description: When true, allows the use of the Nats-Rollup header to replace all contents of a stream, or subject in a stream, with a single new message. + type: boolean + default: false compression: description: Stream specific compression. type: string @@ -232,6 +200,10 @@ spec: - none - '' default: '' + firstSequence: + description: Sequence number from which the Stream will start. + type: number + default: 0 subjectTransform: description: SubjectTransform is for applying a subject transform (to matching messages) when a new message is received type: object @@ -242,34 +214,79 @@ spec: dest: type: string description: Destination subject to transform into - preventDelete: - description: When true, the managed Stream will not be deleted when the resource is deleted - type: boolean - default: false - preventUpdate: - description: When true, the managed Stream will not be updated when the resource is updated - type: boolean - default: false + republish: + description: Republish configuration of the stream. + type: object + properties: + destination: + type: string + description: Messages will be additionally published to that subject. + source: + type: string + description: Messages will be published from that subject to the destination subject. allowDirect: description: When true, allow higher performance, direct access to get individual messages type: boolean default: false - allowRollup: - description: When true, allows the use of the Nats-Rollup header to replace all contents of a stream, or subject in a stream, with a single new message. - type: boolean - default: false - denyDelete: - description: When true, restricts the ability to delete messages from a stream via the API. Cannot be changed once set to true. + mirrorDirect: + description: When true, enables direct access to messages from the origin stream type: boolean default: false - denyPurge: - description: When true, restricts the ability to purge a stream via the API. Cannot be changed once set to true. + consumerLimits: + type: object + properties: + inactiveThreshold: + description: The duration of inactivity after which a consumer is considered inactive. + type: string + maxAckPending: + description: Maximum number of outstanding unacknowledged messages. + type: integer + metadata: + description: Additional Stream metadata. + type: object + additionalProperties: + type: string + account: + description: Name of the account to which the Stream belongs. + type: string + pattern: '^[^.*>]*$' + creds: + description: NATS user credentials for connecting to servers. Please make sure your controller has mounted the cerds on its path. + type: string + default: '' + nkey: + description: NATS user NKey for connecting to servers. + type: string + default: '' + preventDelete: + description: When true, the managed Stream will not be deleted when the resource is deleted type: boolean default: false - discardPerSubject: - description: Allows to discard messages on a subject basis. + preventUpdate: + description: When true, the managed Stream will not be updated when the resource is updated type: boolean default: false + servers: + description: A list of servers for creating stream + type: array + items: + type: string + default: [] + tls: + description: A client's TLS certs and keys. + type: object + properties: + clientCert: + description: A client's cert filepath. Should be mounted. + type: string + clientKey: + description: A client's key filepath. Should be mounted. + type: string + rootCas: + description: A list of filepaths to CAs. Should be mounted. + type: array + items: + type: string status: type: object properties: @@ -1069,11 +1086,11 @@ spec: spec: type: object properties: - name: - description: A unique name for the Key/Value store. + bucket: + description: A unique name for the KV Bucket. type: string description: - description: The description of the Key/Value store. + description: The description of the KV Bucket. type: string maxValueSize: description: The maximum size of a value in bytes. @@ -1085,21 +1102,21 @@ spec: description: The time expiry for keys. type: string maxBytes: - description: The maximum size of the Key/Value store in bytes. + description: The maximum size of the KV Bucket in bytes. type: integer storage: - description: The storage backend to use for the Key/Value store. + description: The storage backend to use for the KV Bucket. type: string enum: - file - memory replicas: - description: The number of replicas to keep for the Key/Value store in clustered JetStream. + description: The number of replicas to keep for the KV Bucket in clustered JetStream. type: integer minimum: 1 maximum: 5 placement: - description: The Key/Value store placement via tags or cluster name. + description: The KV Bucket placement via tags or cluster name. type: object properties: cluster: @@ -1109,17 +1126,17 @@ spec: items: type: string republish: - description: Republish configuration for the Key/Value store. + description: Republish configuration for the KV Bucket. type: object properties: destination: type: string - description: Messages will be additionally published to this subject after store. + description: Messages will be additionally published to this subject after Bucket. source: type: string description: Messages will be published from this subject to the destination subject. mirror: - description: A Key/Value store mirror. + description: A KV Bucket mirror. type: object properties: name: @@ -1149,10 +1166,10 @@ spec: description: Destination subject. type: string compression: - description: Key/Value store compression. + description: KV Bucket compression. type: boolean sources: - description: A Key/Value store's sources. + description: A KV Bucket's sources. type: array items: type: object @@ -1247,9 +1264,9 @@ spec: additionalPrinterColumns: - name: State type: string - description: The current state of the Key/Value store. + description: The current state of the KV Bucket. jsonPath: .status.conditions[?(@.type == 'Ready')].reason - - name: Key/Value Store Name + - name: KV Bucket Name type: string - description: The name of the Key/Value store. + description: The name of the KV Bucket. jsonPath: .spec.name \ No newline at end of file diff --git a/internal/controller/consumer_controller.go b/internal/controller/consumer_controller.go index 9010509f..67540458 100644 --- a/internal/controller/consumer_controller.go +++ b/internal/controller/consumer_controller.go @@ -114,7 +114,7 @@ func (r *ConsumerReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c } func (r *ConsumerReconciler) deleteConsumer(ctx context.Context, log logr.Logger, consumer *api.Consumer) error { - // Set status to not false + // Set status to false consumer.Status.Conditions = updateReadyCondition(consumer.Status.Conditions, v1.ConditionFalse, "Finalizing", "Performing finalizer operations.") if err := r.Status().Update(ctx, consumer); err != nil { return fmt.Errorf("update ready condition: %w", err) diff --git a/internal/controller/keyvalue_controller.go b/internal/controller/kv_controller.go similarity index 93% rename from internal/controller/keyvalue_controller.go rename to internal/controller/kv_controller.go index d76220c1..5f10b967 100644 --- a/internal/controller/keyvalue_controller.go +++ b/internal/controller/kv_controller.go @@ -67,7 +67,7 @@ func (r *KeyValueReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c return ctrl.Result{}, fmt.Errorf("get keyvalue resource '%s': %w", req.NamespacedName.String(), err) } - log = log.WithValues("keyValueName", keyValue.Spec.Name) + log = log.WithValues("keyValueName", keyValue.Spec.Bucket) // Update ready status to unknown when no status is set if len(keyValue.Status.Conditions) == 0 { @@ -81,9 +81,9 @@ func (r *KeyValueReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c } // Add finalizer - if !controllerutil.ContainsFinalizer(keyValue, keyValueFinalizer) { + if !controllerutil.ContainsFinalizer(keyValue, kvFinalizer) { log.Info("Adding KeyValue finalizer.") - if ok := controllerutil.AddFinalizer(keyValue, keyValueFinalizer); !ok { + if ok := controllerutil.AddFinalizer(keyValue, kvFinalizer); !ok { return ctrl.Result{}, errors.New("failed to add finalizer to keyvalue resource") } @@ -96,7 +96,7 @@ func (r *KeyValueReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c // Check Deletion markedForDeletion := keyValue.GetDeletionTimestamp() != nil if markedForDeletion { - if controllerutil.ContainsFinalizer(keyValue, keyValueFinalizer) { + if controllerutil.ContainsFinalizer(keyValue, kvFinalizer) { err := r.deleteKeyValue(ctx, log, keyValue) if err != nil { return ctrl.Result{}, fmt.Errorf("delete keyvalue: %w", err) @@ -116,7 +116,7 @@ func (r *KeyValueReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c } func (r *KeyValueReconciler) deleteKeyValue(ctx context.Context, log logr.Logger, keyValue *api.KeyValue) error { - // Set status to not false + // Set status to false keyValue.Status.Conditions = updateReadyCondition(keyValue.Status.Conditions, v1.ConditionFalse, "Finalizing", "Performing finalizer operations.") if err := r.Status().Update(ctx, keyValue); err != nil { return fmt.Errorf("update ready condition: %w", err) @@ -125,10 +125,10 @@ func (r *KeyValueReconciler) deleteKeyValue(ctx context.Context, log logr.Logger if !keyValue.Spec.PreventDelete && !r.ReadOnly() { log.Info("Deleting KeyValue.") err := r.WithJetStreamClient(keyValueConnOpts(keyValue.Spec), func(js jetstream.JetStream) error { - return js.DeleteKeyValue(ctx, keyValue.Spec.Name) + return js.DeleteKeyValue(ctx, keyValue.Spec.Bucket) }) if errors.Is(err, jetstream.ErrBucketNotFound) { - log.Info("KeyValue does not exist, unable to delete.", "keyValueName", keyValue.Spec.Name) + log.Info("KeyValue does not exist, unable to delete.", "keyValueName", keyValue.Spec.Bucket) } else if err != nil { return fmt.Errorf("delete keyvalue during finalization: %w", err) } @@ -140,7 +140,7 @@ func (r *KeyValueReconciler) deleteKeyValue(ctx context.Context, log logr.Logger } log.Info("Removing KeyValue finalizer.") - if ok := controllerutil.RemoveFinalizer(keyValue, keyValueFinalizer); !ok { + if ok := controllerutil.RemoveFinalizer(keyValue, kvFinalizer); !ok { return errors.New("failed to remove keyvalue finalizer") } if err := r.Update(ctx, keyValue); err != nil { @@ -234,7 +234,7 @@ func keyValueConnOpts(spec api.KeyValueSpec) *connectionOptions { func keyValueSpecToConfig(spec *api.KeyValueSpec) (jetstream.KeyValueConfig, error) { // Set directly mapped fields config := jetstream.KeyValueConfig{ - Bucket: spec.Name, + Bucket: spec.Bucket, Compression: spec.Compression, Description: spec.Description, History: uint8(spec.History), @@ -290,11 +290,11 @@ func keyValueSpecToConfig(spec *api.KeyValueSpec) (jetstream.KeyValueConfig, err } // RePublish - if spec.Republish != nil { + if spec.RePublish != nil { config.RePublish = &jetstream.RePublish{ - Source: spec.Republish.Source, - Destination: spec.Republish.Destination, - HeadersOnly: spec.Republish.HeadersOnly, + Source: spec.RePublish.Source, + Destination: spec.RePublish.Destination, + HeadersOnly: spec.RePublish.HeadersOnly, } } diff --git a/internal/controller/keyvalue_controller_test.go b/internal/controller/kv_controller_test.go similarity index 96% rename from internal/controller/keyvalue_controller_test.go rename to internal/controller/kv_controller_test.go index 26b06688..8131fe72 100644 --- a/internal/controller/keyvalue_controller_test.go +++ b/internal/controller/kv_controller_test.go @@ -66,7 +66,7 @@ var _ = Describe("KeyValue Controller", func() { Namespace: "default", }, Spec: api.KeyValueSpec{ - Name: keyValueName, + Bucket: keyValueName, Replicas: 1, History: 10, TTL: "5m", @@ -97,9 +97,9 @@ var _ = Describe("KeyValue Controller", func() { if err != nil { Expect(err).To(MatchError(k8serrors.IsNotFound, "Is not found")) } else { - if controllerutil.ContainsFinalizer(resource, keyValueFinalizer) { + if controllerutil.ContainsFinalizer(resource, kvFinalizer) { By("removing the finalizer") - controllerutil.RemoveFinalizer(resource, keyValueFinalizer) + controllerutil.RemoveFinalizer(resource, kvFinalizer) Expect(k8sClient.Update(ctx, resource)).To(Succeed()) } @@ -145,7 +145,7 @@ var _ = Describe("KeyValue Controller", func() { return got }).WithContext(ctx). Should(SatisfyAll( - HaveField("Finalizers", HaveExactElements(keyValueFinalizer)), + HaveField("Finalizers", HaveExactElements(kvFinalizer)), HaveField("Status.Conditions", Not(BeEmpty())), )) @@ -163,7 +163,7 @@ var _ = Describe("KeyValue Controller", func() { By("initializing the keyvalue resource") By("setting the finalizer") - Expect(controllerutil.AddFinalizer(keyValue, keyValueFinalizer)).To(BeTrue()) + Expect(controllerutil.AddFinalizer(keyValue, kvFinalizer)).To(BeTrue()) Expect(k8sClient.Update(ctx, keyValue)).To(Succeed()) By("setting an unknown ready state") @@ -519,7 +519,7 @@ var _ = Describe("KeyValue Controller", func() { By("checking that the finalizer is not removed") Expect(k8sClient.Get(ctx, typeNamespacedName, keyValue)).To(Succeed()) - Expect(keyValue.Finalizers).To(ContainElement(keyValueFinalizer)) + Expect(keyValue.Finalizers).To(ContainElement(kvFinalizer)) }) }) }) @@ -580,15 +580,11 @@ func Test_mapKVSpecToConfig(t *testing.T) { { name: "full spec", spec: &api.KeyValueSpec{ - Account: "", - Creds: "", - Description: "kv description", - PreventDelete: false, - PreventUpdate: false, - History: 20, - MaxValueSize: 1024, - MaxBytes: 1048576, - TTL: "1h", + Description: "kv description", + History: 20, + MaxValueSize: 1024, + MaxBytes: 1048576, + TTL: "1h", Mirror: &api.StreamSource{ Name: "mirror", OptStartSeq: 5, @@ -601,20 +597,18 @@ func Test_mapKVSpecToConfig(t *testing.T) { Dest: "transform-dest", }}, }, - Name: "kv-name", - Nkey: "", + Bucket: "kv-name", Placement: &api.StreamPlacement{ Cluster: "test-cluster", Tags: []string{"tag"}, }, Replicas: 3, - Republish: &api.RePublish{ + RePublish: &api.RePublish{ Source: "re-publish-source", Destination: "re-publish-dest", HeadersOnly: true, }, Compression: true, - Servers: nil, Sources: []*api.StreamSource{{ Name: "source", OptStartSeq: 5, @@ -628,7 +622,15 @@ func Test_mapKVSpecToConfig(t *testing.T) { }}, }}, Storage: "memory", - TLS: api.TLS{}, + BaseStreamConfig: api.BaseStreamConfig{ + Account: "", + Creds: "", + PreventDelete: false, + PreventUpdate: false, + Nkey: "", + Servers: nil, + TLS: api.TLS{}, + }, }, want: jetstream.KeyValueConfig{ Bucket: "kv-name", diff --git a/internal/controller/object_controller.go b/internal/controller/object_controller.go new file mode 100644 index 00000000..89482a87 --- /dev/null +++ b/internal/controller/object_controller.go @@ -0,0 +1,280 @@ +/* +Copyright 2025. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package controller + +import ( + "context" + "errors" + "fmt" + "time" + + "github.com/go-logr/logr" + api "github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2" + "github.com/nats-io/nats.go/jetstream" + v1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/klog/v2" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + "sigs.k8s.io/controller-runtime/pkg/predicate" +) + +// ObjectStoreReconciler reconciles a ObjectStore object +type ObjectStoreReconciler struct { + JetStreamController +} + +// Reconcile is part of the main kubernetes reconciliation loop which aims to +// move the current state of the cluster closer to the desired state. +// +// It performs three main operations: +// - Initialize finalizer and ready condition if not present +// - Delete ObjectStore if it is marked for deletion. +// - Create or Update the ObjectStore +// +// A call to reconcile may perform only one action, expecting the reconciliation to be triggered again by an update. +// For example: Setting the finalizer triggers a second reconciliation. Reconcile returns after setting the finalizer, +// to prevent parallel reconciliations performing the same steps. +func (r *ObjectStoreReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + log := klog.FromContext(ctx) + + if ok := r.ValidNamespace(req.Namespace); !ok { + log.Info("Controller restricted to namespace, skipping reconciliation.") + return ctrl.Result{}, nil + } + + // Fetch ObjectStore resource + objectStore := &api.ObjectStore{} + if err := r.Get(ctx, req.NamespacedName, objectStore); err != nil { + if apierrors.IsNotFound(err) { + log.Info("ObjectStore resource not found. Ignoring since object must be deleted.") + return ctrl.Result{}, nil + } + return ctrl.Result{}, fmt.Errorf("get objectstore resource '%s': %w", req.NamespacedName.String(), err) + } + + log = log.WithValues("objectStoreName", objectStore.Spec.Bucket) + + // Update ready status to unknown when no status is set + if len(objectStore.Status.Conditions) == 0 { + log.Info("Setting initial ready condition to unknown.") + objectStore.Status.Conditions = updateReadyCondition(objectStore.Status.Conditions, v1.ConditionUnknown, "Reconciling", "Starting reconciliation") + err := r.Status().Update(ctx, objectStore) + if err != nil { + return ctrl.Result{}, fmt.Errorf("set condition unknown: %w", err) + } + return ctrl.Result{Requeue: true}, nil + } + + // Add finalizer + if !controllerutil.ContainsFinalizer(objectStore, objectFinalizer) { + log.Info("Adding ObjectStore finalizer.") + if ok := controllerutil.AddFinalizer(objectStore, objectFinalizer); !ok { + return ctrl.Result{}, errors.New("failed to add finalizer to objectstore resource") + } + + if err := r.Update(ctx, objectStore); err != nil { + return ctrl.Result{}, fmt.Errorf("update objectstore resource to add finalizer: %w", err) + } + return ctrl.Result{}, nil + } + + // Check Deletion + markedForDeletion := objectStore.GetDeletionTimestamp() != nil + if markedForDeletion { + if controllerutil.ContainsFinalizer(objectStore, objectFinalizer) { + err := r.deleteObjectStore(ctx, log, objectStore) + if err != nil { + return ctrl.Result{}, fmt.Errorf("delete objectstore: %w", err) + } + } else { + log.Info("ObjectStore marked for deletion and already finalized. Ignoring.") + } + + return ctrl.Result{}, nil + } + + // Create or update ObjectStore + if err := r.createOrUpdate(ctx, log, objectStore); err != nil { + return ctrl.Result{}, fmt.Errorf("create or update: %s", err) + } + return ctrl.Result{}, nil +} + +func (r *ObjectStoreReconciler) deleteObjectStore(ctx context.Context, log logr.Logger, objectStore *api.ObjectStore) error { + // Set status to false + objectStore.Status.Conditions = updateReadyCondition(objectStore.Status.Conditions, v1.ConditionFalse, "Finalizing", "Performing finalizer operations.") + if err := r.Status().Update(ctx, objectStore); err != nil { + return fmt.Errorf("update ready condition: %w", err) + } + + if !objectStore.Spec.PreventDelete && !r.ReadOnly() { + log.Info("Deleting ObjectStore.") + err := r.WithJetStreamClient(objectStoreConnOpts(objectStore.Spec), func(js jetstream.JetStream) error { + return js.DeleteObjectStore(ctx, objectStore.Spec.Bucket) + }) + if errors.Is(err, jetstream.ErrBucketNotFound) { + log.Info("ObjectStore does not exist, unable to delete.", "objectStoreName", objectStore.Spec.Bucket) + } else if err != nil { + return fmt.Errorf("delete objectstore during finalization: %w", err) + } + } else { + log.Info("Skipping ObjectStore deletion.", + "preventDelete", objectStore.Spec.PreventDelete, + "read-only", r.ReadOnly(), + ) + } + + log.Info("Removing ObjectStore finalizer.") + if ok := controllerutil.RemoveFinalizer(objectStore, objectFinalizer); !ok { + return errors.New("failed to remove objectstore finalizer") + } + if err := r.Update(ctx, objectStore); err != nil { + return fmt.Errorf("remove finalizer: %w", err) + } + + return nil +} + +func (r *ObjectStoreReconciler) createOrUpdate(ctx context.Context, log logr.Logger, objectStore *api.ObjectStore) error { + // Create or Update the ObjectStore based on the spec + if r.ReadOnly() { + log.Info("Skipping ObjectStore creation or update.", + "read-only", r.ReadOnly(), + ) + return nil + } + + // Map spec to ObjectStore targetConfig + targetConfig, err := objectStoreSpecToConfig(&objectStore.Spec) + if err != nil { + return fmt.Errorf("map spec to objectstore targetConfig: %w", err) + } + + // UpdateObjectStore is called on every reconciliation when the stream is not to be deleted. + // TODO(future-feature): Do we need to check if config differs? + err = r.WithJetStreamClient(objectStoreConnOpts(objectStore.Spec), func(js jetstream.JetStream) error { + exists := false + _, err := js.ObjectStore(ctx, targetConfig.Bucket) + if err == nil { + exists = true + } else if !errors.Is(err, jetstream.ErrBucketNotFound) { + return err + } + + if !exists { + log.Info("Creating ObjectStore.") + _, err = js.CreateObjectStore(ctx, targetConfig) + return err + } + + if !objectStore.Spec.PreventUpdate { + log.Info("Updating ObjectStore.") + _, err = js.UpdateObjectStore(ctx, targetConfig) + return err + } else { + log.Info("Skipping ObjectStore update.", + "preventUpdate", objectStore.Spec.PreventUpdate, + ) + } + + return nil + }) + if err != nil { + err = fmt.Errorf("create or update objectstore: %w", err) + objectStore.Status.Conditions = updateReadyCondition(objectStore.Status.Conditions, v1.ConditionFalse, "Errored", err.Error()) + if err := r.Status().Update(ctx, objectStore); err != nil { + log.Error(err, "Failed to update ready condition to Errored.") + } + return err + } + + // update the observed generation and ready status + objectStore.Status.ObservedGeneration = objectStore.Generation + objectStore.Status.Conditions = updateReadyCondition( + objectStore.Status.Conditions, + v1.ConditionTrue, + "Reconciling", + "ObjectStore successfully created or updated.", + ) + err = r.Status().Update(ctx, objectStore) + if err != nil { + return fmt.Errorf("update ready condition: %w", err) + } + + return nil +} + +// objectStoreConnOpts extracts nats connection relevant fields from the given ObjectStore spec as connectionOptions. +func objectStoreConnOpts(spec api.ObjectStoreSpec) *connectionOptions { + return &connectionOptions{ + Account: spec.Account, + Creds: spec.Creds, + Nkey: spec.Nkey, + Servers: spec.Servers, + TLS: spec.TLS, + } +} + +// objectStoreSpecToConfig creates a jetstream.ObjectStoreConfig matching the given ObjectStore resource spec +func objectStoreSpecToConfig(spec *api.ObjectStoreSpec) (jetstream.ObjectStoreConfig, error) { + // Set directly mapped fields + config := jetstream.ObjectStoreConfig{ + Bucket: spec.Bucket, + Compression: spec.Compression, + Description: spec.Description, + MaxBytes: int64(spec.MaxBytes), + Replicas: spec.Replicas, + } + + // TTL + if spec.TTL != "" { + t, err := time.ParseDuration(spec.TTL) + if err != nil { + return jetstream.ObjectStoreConfig{}, fmt.Errorf("invalid ttl: %w", err) + } + config.TTL = t + } + + // storage + if spec.Storage != "" { + err := config.Storage.UnmarshalJSON(asJsonString(spec.Storage)) + if err != nil { + return jetstream.ObjectStoreConfig{}, fmt.Errorf("invalid storage: %w", err) + } + } + + // placement + if spec.Placement != nil { + config.Placement = &jetstream.Placement{ + Cluster: spec.Placement.Cluster, + Tags: spec.Placement.Tags, + } + } + + return config, nil +} + +// SetupWithManager sets up the controller with the Manager. +func (r *ObjectStoreReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&api.ObjectStore{}). + Owns(&api.ObjectStore{}). + // Only trigger on generation changes + WithEventFilter(predicate.GenerationChangedPredicate{}). + Complete(r) +} diff --git a/internal/controller/stream_controller.go b/internal/controller/stream_controller.go index bd7ad3ad..3f1b2f70 100644 --- a/internal/controller/stream_controller.go +++ b/internal/controller/stream_controller.go @@ -116,7 +116,7 @@ func (r *StreamReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctr } func (r *StreamReconciler) deleteStream(ctx context.Context, log logr.Logger, stream *api.Stream) error { - // Set status to not false + // Set status to false stream.Status.Conditions = updateReadyCondition(stream.Status.Conditions, v1.ConditionFalse, "Finalizing", "Performing finalizer operations.") if err := r.Status().Update(ctx, stream); err != nil { return fmt.Errorf("update ready condition: %w", err) @@ -346,11 +346,11 @@ func streamSpecToConfig(spec *api.StreamSpec) (jetstream.StreamConfig, error) { } // rePublish - if spec.Republish != nil { + if spec.RePublish != nil { config.RePublish = &jetstream.RePublish{ - Source: spec.Republish.Source, - Destination: spec.Republish.Destination, - HeadersOnly: spec.Republish.HeadersOnly, + Source: spec.RePublish.Source, + Destination: spec.RePublish.Destination, + HeadersOnly: spec.RePublish.HeadersOnly, } } diff --git a/internal/controller/stream_controller_test.go b/internal/controller/stream_controller_test.go index e2506a7f..79c83fbf 100644 --- a/internal/controller/stream_controller_test.go +++ b/internal/controller/stream_controller_test.go @@ -570,16 +570,12 @@ func Test_mapSpecToConfig(t *testing.T) { { name: "full spec", spec: &api.StreamSpec{ - Account: "", AllowDirect: true, AllowRollup: true, - Creds: "", DenyDelete: true, DenyPurge: true, Description: "stream description", DiscardPerSubject: true, - PreventDelete: false, - PreventUpdate: false, Discard: "new", DuplicateWindow: "5s", MaxAge: "30s", @@ -601,14 +597,13 @@ func Test_mapSpecToConfig(t *testing.T) { }}, }, Name: "stream-name", - Nkey: "", NoAck: true, Placement: &api.StreamPlacement{ Cluster: "test-cluster", Tags: []string{"tag"}, }, Replicas: 3, - Republish: &api.RePublish{ + RePublish: &api.RePublish{ Source: "re-publish-source", Destination: "re-publish-dest", HeadersOnly: true, @@ -623,7 +618,6 @@ func Test_mapSpecToConfig(t *testing.T) { "meta": "data", }, Retention: "interest", - Servers: nil, Sources: []*api.StreamSource{{ Name: "source", OptStartSeq: 5, @@ -638,7 +632,15 @@ func Test_mapSpecToConfig(t *testing.T) { }}, Storage: "file", Subjects: []string{"orders.*"}, - TLS: api.TLS{}, + BaseStreamConfig: api.BaseStreamConfig{ + Account: "", + Creds: "", + Nkey: "", + PreventDelete: false, + PreventUpdate: false, + Servers: nil, + TLS: api.TLS{}, + }, }, want: jetstream.StreamConfig{ Name: "stream-name", diff --git a/internal/controller/types.go b/internal/controller/types.go index bc3b20c3..247f0304 100644 --- a/internal/controller/types.go +++ b/internal/controller/types.go @@ -3,6 +3,7 @@ package controller const ( readyCondType = "Ready" streamFinalizer = "stream.nats.io/finalizer" - keyValueFinalizer = "keyvalue.nats.io/finalizer" + kvFinalizer = "kv.nats.io/finalizer" + objectFinalizer = "object.nats.io/finalizer" consumerFinalizer = "consumer.nats.io/finalizer" ) diff --git a/pkg/jetstream/apis/jetstream/v1beta2/keyvaluetypes.go b/pkg/jetstream/apis/jetstream/v1beta2/keyvaluetypes.go deleted file mode 100644 index 03bc9dbe..00000000 --- a/pkg/jetstream/apis/jetstream/v1beta2/keyvaluetypes.go +++ /dev/null @@ -1,55 +0,0 @@ -package v1beta2 - -import ( - k8smeta "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -// +genclient -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// Stream is a specification for a Stream resource -type KeyValue struct { - k8smeta.TypeMeta `json:",inline"` - k8smeta.ObjectMeta `json:"metadata,omitempty"` - - Spec KeyValueSpec `json:"spec"` - Status Status `json:"status"` -} - -func (s *KeyValue) GetSpec() interface{} { - return s.Spec -} - -// StreamSpec is the spec for a Stream resource -type KeyValueSpec struct { - Account string `json:"account"` - Compression bool `json:"compression"` - Creds string `json:"creds"` - Description string `json:"description"` - History int `json:"history"` - MaxBytes int `json:"maxBytes"` - MaxValueSize int `json:"maxValueSize"` - Mirror *StreamSource `json:"mirror"` - Name string `json:"name"` - Nkey string `json:"nkey"` - Placement *StreamPlacement `json:"placement"` - PreventDelete bool `json:"preventDelete"` - PreventUpdate bool `json:"preventUpdate"` - Replicas int `json:"replicas"` - Republish *RePublish `json:"republish"` - Servers []string `json:"servers"` - Sources []*StreamSource `json:"sources"` - Storage string `json:"storage"` - TLS TLS `json:"tls"` - TTL string `json:"ttl"` -} - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// KeyValueList is a list of Stream resources -type KeyValueList struct { - k8smeta.TypeMeta `json:",inline"` - k8smeta.ListMeta `json:"metadata"` - - Items []KeyValue `json:"items"` -} diff --git a/pkg/jetstream/apis/jetstream/v1beta2/kvtypes.go b/pkg/jetstream/apis/jetstream/v1beta2/kvtypes.go new file mode 100644 index 00000000..fbf37e0a --- /dev/null +++ b/pkg/jetstream/apis/jetstream/v1beta2/kvtypes.go @@ -0,0 +1,49 @@ +package v1beta2 + +import ( + k8smeta "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// +genclient +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// Stream is a specification for a Stream resource +type KeyValue struct { + k8smeta.TypeMeta `json:",inline"` + k8smeta.ObjectMeta `json:"metadata,omitempty"` + + Spec KeyValueSpec `json:"spec"` + Status Status `json:"status"` +} + +func (s *KeyValue) GetSpec() interface{} { + return s.Spec +} + +// StreamSpec is the spec for a Stream resource +type KeyValueSpec struct { + Bucket string `json:"bucket"` + Description string `json:"description"` + MaxValueSize int `json:"maxValueSize"` + History int `json:"history"` + TTL string `json:"ttl"` + MaxBytes int `json:"maxBytes"` + Storage string `json:"storage"` + Replicas int `json:"replicas"` + Placement *StreamPlacement `json:"placement"` + RePublish *RePublish `json:"republish"` + Mirror *StreamSource `json:"mirror"` + Sources []*StreamSource `json:"sources"` + Compression bool `json:"compression"` + BaseStreamConfig +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// KeyValueList is a list of Stream resources +type KeyValueList struct { + k8smeta.TypeMeta `json:",inline"` + k8smeta.ListMeta `json:"metadata"` + + Items []KeyValue `json:"items"` +} diff --git a/pkg/jetstream/apis/jetstream/v1beta2/objecttypes.go b/pkg/jetstream/apis/jetstream/v1beta2/objecttypes.go new file mode 100644 index 00000000..396426da --- /dev/null +++ b/pkg/jetstream/apis/jetstream/v1beta2/objecttypes.go @@ -0,0 +1,45 @@ +package v1beta2 + +import ( + k8smeta "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// +genclient +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// Stream is a specification for a Stream resource +type ObjectStore struct { + k8smeta.TypeMeta `json:",inline"` + k8smeta.ObjectMeta `json:"metadata,omitempty"` + + Spec ObjectStoreSpec `json:"spec"` + Status Status `json:"status"` +} + +func (s *ObjectStore) GetSpec() interface{} { + return s.Spec +} + +// StreamSpec is the spec for a Stream resource +type ObjectStoreSpec struct { + Bucket string `json:"bucket"` + Description string `json:"description"` + TTL string `json:"ttl"` + MaxBytes int `json:"maxBytes"` + Storage string `json:"storage"` + Replicas int `json:"replicas"` + Placement *StreamPlacement `json:"placement"` + Compression bool `json:"compression"` + Metadata map[string]string `json:"metadata"` + BaseStreamConfig +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// ObjectStoreList is a list of Stream resources +type ObjectStoreList struct { + k8smeta.TypeMeta `json:",inline"` + k8smeta.ListMeta `json:"metadata"` + + Items []ObjectStore `json:"items"` +} diff --git a/pkg/jetstream/apis/jetstream/v1beta2/register.go b/pkg/jetstream/apis/jetstream/v1beta2/register.go index 881fe90a..72e63313 100644 --- a/pkg/jetstream/apis/jetstream/v1beta2/register.go +++ b/pkg/jetstream/apis/jetstream/v1beta2/register.go @@ -35,6 +35,8 @@ func addKnownTypes(scheme *runtime.Scheme) error { &StreamList{}, &KeyValue{}, &KeyValueList{}, + &ObjectStore{}, + &ObjectStoreList{}, &Consumer{}, &ConsumerList{}, &Account{}, diff --git a/pkg/jetstream/apis/jetstream/v1beta2/streamtypes.go b/pkg/jetstream/apis/jetstream/v1beta2/streamtypes.go index 1128d1c4..964a5e27 100644 --- a/pkg/jetstream/apis/jetstream/v1beta2/streamtypes.go +++ b/pkg/jetstream/apis/jetstream/v1beta2/streamtypes.go @@ -22,41 +22,38 @@ func (s *Stream) GetSpec() interface{} { // StreamSpec is the spec for a Stream resource type StreamSpec struct { - Account string `json:"account"` - AllowDirect bool `json:"allowDirect"` - AllowRollup bool `json:"allowRollup"` - Creds string `json:"creds"` - DenyDelete bool `json:"denyDelete"` - DenyPurge bool `json:"denyPurge"` + Name string `json:"name"` Description string `json:"description"` - DiscardPerSubject bool `json:"discardPerSubject"` - PreventDelete bool `json:"preventDelete"` - PreventUpdate bool `json:"preventUpdate"` - Discard string `json:"discard"` - DuplicateWindow string `json:"duplicateWindow"` - MaxAge string `json:"maxAge"` - MaxBytes int `json:"maxBytes"` + Subjects []string `json:"subjects"` + Retention string `json:"retention"` MaxConsumers int `json:"maxConsumers"` MaxMsgs int `json:"maxMsgs"` - MaxMsgSize int `json:"maxMsgSize"` + MaxBytes int `json:"maxBytes"` + Discard string `json:"discard"` + DiscardPerSubject bool `json:"discardPerSubject"` // Maps to DiscardNewPerSubject + MaxAge string `json:"maxAge"` MaxMsgsPerSubject int `json:"maxMsgsPerSubject"` - Mirror *StreamSource `json:"mirror"` - Name string `json:"name"` - Nkey string `json:"nkey"` + MaxMsgSize int `json:"maxMsgSize"` + Storage string `json:"storage"` + Replicas int `json:"replicas"` NoAck bool `json:"noAck"` + DuplicateWindow string `json:"duplicateWindow"` // Maps to Duplicates Placement *StreamPlacement `json:"placement"` - Replicas int `json:"replicas"` - Republish *RePublish `json:"republish"` - SubjectTransform *SubjectTransform `json:"subjectTransform"` - FirstSequence uint64 `json:"firstSequence"` + Mirror *StreamSource `json:"mirror"` + Sources []*StreamSource `json:"sources"` + Sealed bool `json:"sealed"` + DenyDelete bool `json:"denyDelete"` + DenyPurge bool `json:"denyPurge"` + AllowRollup bool `json:"allowRollup"` Compression string `json:"compression"` + FirstSequence uint64 `json:"firstSequence"` // Maps to FirstSeq + SubjectTransform *SubjectTransform `json:"subjectTransform"` + RePublish *RePublish `json:"republish"` + AllowDirect bool `json:"allowDirect"` + MirrorDirect bool `json:"mirrorDirect"` + ConsumerLimits *ConsumerLimits `json:"consumerLimits"` Metadata map[string]string `json:"metadata"` - Retention string `json:"retention"` - Servers []string `json:"servers"` - Sources []*StreamSource `json:"sources"` - Storage string `json:"storage"` - Subjects []string `json:"subjects"` - TLS TLS `json:"tls"` + BaseStreamConfig } type SubjectTransform struct { diff --git a/pkg/jetstream/apis/jetstream/v1beta2/types.go b/pkg/jetstream/apis/jetstream/v1beta2/types.go index 18504510..966389e8 100644 --- a/pkg/jetstream/apis/jetstream/v1beta2/types.go +++ b/pkg/jetstream/apis/jetstream/v1beta2/types.go @@ -22,6 +22,21 @@ type Condition struct { LastTransitionTime string `json:"lastTransitionTime"` } +type BaseStreamConfig struct { + Account string `json:"account"` + Creds string `json:"creds"` + Nkey string `json:"nkey"` + PreventDelete bool `json:"preventDelete"` + PreventUpdate bool `json:"preventUpdate"` + Servers []string `json:"servers"` + TLS TLS `json:"tls"` +} + +type ConsumerLimits struct { + InactiveThreshold string `json:"inactiveThreshold"` + MaxAckPending int `json:"maxAckPending"` +} + type TLS struct { ClientCert string `json:"clientCert"` ClientKey string `json:"clientKey"` diff --git a/pkg/jetstream/apis/jetstream/v1beta2/zz_generated.deepcopy.go b/pkg/jetstream/apis/jetstream/v1beta2/zz_generated.deepcopy.go index 811aec4a..224a2b4d 100644 --- a/pkg/jetstream/apis/jetstream/v1beta2/zz_generated.deepcopy.go +++ b/pkg/jetstream/apis/jetstream/v1beta2/zz_generated.deepcopy.go @@ -124,6 +124,28 @@ func (in *AccountSpec) DeepCopy() *AccountSpec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *BaseStreamConfig) DeepCopyInto(out *BaseStreamConfig) { + *out = *in + if in.Servers != nil { + in, out := &in.Servers, &out.Servers + *out = make([]string, len(*in)) + copy(*out, *in) + } + in.TLS.DeepCopyInto(&out.TLS) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BaseStreamConfig. +func (in *BaseStreamConfig) DeepCopy() *BaseStreamConfig { + if in == nil { + return nil + } + out := new(BaseStreamConfig) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Condition) DeepCopyInto(out *Condition) { *out = *in @@ -168,6 +190,22 @@ func (in *Consumer) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ConsumerLimits) DeepCopyInto(out *ConsumerLimits) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConsumerLimits. +func (in *ConsumerLimits) DeepCopy() *ConsumerLimits { + if in == nil { + return nil + } + out := new(ConsumerLimits) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ConsumerList) DeepCopyInto(out *ConsumerList) { *out = *in @@ -337,25 +375,20 @@ func (in *KeyValueList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *KeyValueSpec) DeepCopyInto(out *KeyValueSpec) { *out = *in - if in.Mirror != nil { - in, out := &in.Mirror, &out.Mirror - *out = new(StreamSource) - (*in).DeepCopyInto(*out) - } if in.Placement != nil { in, out := &in.Placement, &out.Placement *out = new(StreamPlacement) (*in).DeepCopyInto(*out) } - if in.Republish != nil { - in, out := &in.Republish, &out.Republish + if in.RePublish != nil { + in, out := &in.RePublish, &out.RePublish *out = new(RePublish) **out = **in } - if in.Servers != nil { - in, out := &in.Servers, &out.Servers - *out = make([]string, len(*in)) - copy(*out, *in) + if in.Mirror != nil { + in, out := &in.Mirror, &out.Mirror + *out = new(StreamSource) + (*in).DeepCopyInto(*out) } if in.Sources != nil { in, out := &in.Sources, &out.Sources @@ -368,7 +401,7 @@ func (in *KeyValueSpec) DeepCopyInto(out *KeyValueSpec) { } } } - in.TLS.DeepCopyInto(&out.TLS) + in.BaseStreamConfig.DeepCopyInto(&out.BaseStreamConfig) return } @@ -382,6 +415,96 @@ func (in *KeyValueSpec) DeepCopy() *KeyValueSpec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ObjectStore) DeepCopyInto(out *ObjectStore) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ObjectStore. +func (in *ObjectStore) DeepCopy() *ObjectStore { + if in == nil { + return nil + } + out := new(ObjectStore) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ObjectStore) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ObjectStoreList) DeepCopyInto(out *ObjectStoreList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]ObjectStore, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ObjectStoreList. +func (in *ObjectStoreList) DeepCopy() *ObjectStoreList { + if in == nil { + return nil + } + out := new(ObjectStoreList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ObjectStoreList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ObjectStoreSpec) DeepCopyInto(out *ObjectStoreSpec) { + *out = *in + if in.Placement != nil { + in, out := &in.Placement, &out.Placement + *out = new(StreamPlacement) + (*in).DeepCopyInto(*out) + } + if in.Metadata != nil { + in, out := &in.Metadata, &out.Metadata + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + in.BaseStreamConfig.DeepCopyInto(&out.BaseStreamConfig) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ObjectStoreSpec. +func (in *ObjectStoreSpec) DeepCopy() *ObjectStoreSpec { + if in == nil { + return nil + } + out := new(ObjectStoreSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *RePublish) DeepCopyInto(out *RePublish) { *out = *in @@ -547,37 +670,20 @@ func (in *StreamSource) DeepCopy() *StreamSource { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *StreamSpec) DeepCopyInto(out *StreamSpec) { *out = *in - if in.Mirror != nil { - in, out := &in.Mirror, &out.Mirror - *out = new(StreamSource) - (*in).DeepCopyInto(*out) + if in.Subjects != nil { + in, out := &in.Subjects, &out.Subjects + *out = make([]string, len(*in)) + copy(*out, *in) } if in.Placement != nil { in, out := &in.Placement, &out.Placement *out = new(StreamPlacement) (*in).DeepCopyInto(*out) } - if in.Republish != nil { - in, out := &in.Republish, &out.Republish - *out = new(RePublish) - **out = **in - } - if in.SubjectTransform != nil { - in, out := &in.SubjectTransform, &out.SubjectTransform - *out = new(SubjectTransform) - **out = **in - } - if in.Metadata != nil { - in, out := &in.Metadata, &out.Metadata - *out = make(map[string]string, len(*in)) - for key, val := range *in { - (*out)[key] = val - } - } - if in.Servers != nil { - in, out := &in.Servers, &out.Servers - *out = make([]string, len(*in)) - copy(*out, *in) + if in.Mirror != nil { + in, out := &in.Mirror, &out.Mirror + *out = new(StreamSource) + (*in).DeepCopyInto(*out) } if in.Sources != nil { in, out := &in.Sources, &out.Sources @@ -590,12 +696,29 @@ func (in *StreamSpec) DeepCopyInto(out *StreamSpec) { } } } - if in.Subjects != nil { - in, out := &in.Subjects, &out.Subjects - *out = make([]string, len(*in)) - copy(*out, *in) + if in.SubjectTransform != nil { + in, out := &in.SubjectTransform, &out.SubjectTransform + *out = new(SubjectTransform) + **out = **in } - in.TLS.DeepCopyInto(&out.TLS) + if in.RePublish != nil { + in, out := &in.RePublish, &out.RePublish + *out = new(RePublish) + **out = **in + } + if in.ConsumerLimits != nil { + in, out := &in.ConsumerLimits, &out.ConsumerLimits + *out = new(ConsumerLimits) + **out = **in + } + if in.Metadata != nil { + in, out := &in.Metadata, &out.Metadata + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + in.BaseStreamConfig.DeepCopyInto(&out.BaseStreamConfig) return } diff --git a/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/basestreamconfig.go b/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/basestreamconfig.go new file mode 100644 index 00000000..656d1e50 --- /dev/null +++ b/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/basestreamconfig.go @@ -0,0 +1,92 @@ +// Copyright 2025 The NATS Authors +// 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. + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1beta2 + +// BaseStreamConfigApplyConfiguration represents a declarative configuration of the BaseStreamConfig type for use +// with apply. +type BaseStreamConfigApplyConfiguration struct { + Account *string `json:"account,omitempty"` + Creds *string `json:"creds,omitempty"` + Nkey *string `json:"nkey,omitempty"` + PreventDelete *bool `json:"preventDelete,omitempty"` + PreventUpdate *bool `json:"preventUpdate,omitempty"` + Servers []string `json:"servers,omitempty"` + TLS *TLSApplyConfiguration `json:"tls,omitempty"` +} + +// BaseStreamConfigApplyConfiguration constructs a declarative configuration of the BaseStreamConfig type for use with +// apply. +func BaseStreamConfig() *BaseStreamConfigApplyConfiguration { + return &BaseStreamConfigApplyConfiguration{} +} + +// WithAccount sets the Account field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Account field is set to the value of the last call. +func (b *BaseStreamConfigApplyConfiguration) WithAccount(value string) *BaseStreamConfigApplyConfiguration { + b.Account = &value + return b +} + +// WithCreds sets the Creds field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Creds field is set to the value of the last call. +func (b *BaseStreamConfigApplyConfiguration) WithCreds(value string) *BaseStreamConfigApplyConfiguration { + b.Creds = &value + return b +} + +// WithNkey sets the Nkey field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Nkey field is set to the value of the last call. +func (b *BaseStreamConfigApplyConfiguration) WithNkey(value string) *BaseStreamConfigApplyConfiguration { + b.Nkey = &value + return b +} + +// WithPreventDelete sets the PreventDelete field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the PreventDelete field is set to the value of the last call. +func (b *BaseStreamConfigApplyConfiguration) WithPreventDelete(value bool) *BaseStreamConfigApplyConfiguration { + b.PreventDelete = &value + return b +} + +// WithPreventUpdate sets the PreventUpdate field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the PreventUpdate field is set to the value of the last call. +func (b *BaseStreamConfigApplyConfiguration) WithPreventUpdate(value bool) *BaseStreamConfigApplyConfiguration { + b.PreventUpdate = &value + return b +} + +// WithServers adds the given value to the Servers field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the Servers field. +func (b *BaseStreamConfigApplyConfiguration) WithServers(values ...string) *BaseStreamConfigApplyConfiguration { + for i := range values { + b.Servers = append(b.Servers, values[i]) + } + return b +} + +// WithTLS sets the TLS field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the TLS field is set to the value of the last call. +func (b *BaseStreamConfigApplyConfiguration) WithTLS(value *TLSApplyConfiguration) *BaseStreamConfigApplyConfiguration { + b.TLS = value + return b +} diff --git a/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/consumerlimits.go b/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/consumerlimits.go new file mode 100644 index 00000000..258b6f96 --- /dev/null +++ b/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/consumerlimits.go @@ -0,0 +1,45 @@ +// Copyright 2025 The NATS Authors +// 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. + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1beta2 + +// ConsumerLimitsApplyConfiguration represents a declarative configuration of the ConsumerLimits type for use +// with apply. +type ConsumerLimitsApplyConfiguration struct { + InactiveThreshold *string `json:"inactiveThreshold,omitempty"` + MaxAckPending *int `json:"maxAckPending,omitempty"` +} + +// ConsumerLimitsApplyConfiguration constructs a declarative configuration of the ConsumerLimits type for use with +// apply. +func ConsumerLimits() *ConsumerLimitsApplyConfiguration { + return &ConsumerLimitsApplyConfiguration{} +} + +// WithInactiveThreshold sets the InactiveThreshold field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the InactiveThreshold field is set to the value of the last call. +func (b *ConsumerLimitsApplyConfiguration) WithInactiveThreshold(value string) *ConsumerLimitsApplyConfiguration { + b.InactiveThreshold = &value + return b +} + +// WithMaxAckPending sets the MaxAckPending field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the MaxAckPending field is set to the value of the last call. +func (b *ConsumerLimitsApplyConfiguration) WithMaxAckPending(value int) *ConsumerLimitsApplyConfiguration { + b.MaxAckPending = &value + return b +} diff --git a/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/keyvaluespec.go b/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/keyvaluespec.go index a35967bf..92cbf75d 100644 --- a/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/keyvaluespec.go +++ b/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/keyvaluespec.go @@ -22,26 +22,19 @@ import ( // KeyValueSpecApplyConfiguration represents a declarative configuration of the KeyValueSpec type for use // with apply. type KeyValueSpecApplyConfiguration struct { - Account *string `json:"account,omitempty"` - Compression *bool `json:"compression,omitempty"` - Creds *string `json:"creds,omitempty"` - Description *string `json:"description,omitempty"` - History *int `json:"history,omitempty"` - MaxBytes *int `json:"maxBytes,omitempty"` - MaxValueSize *int `json:"maxValueSize,omitempty"` - Mirror *StreamSourceApplyConfiguration `json:"mirror,omitempty"` - Name *string `json:"name,omitempty"` - Nkey *string `json:"nkey,omitempty"` - Placement *StreamPlacementApplyConfiguration `json:"placement,omitempty"` - PreventDelete *bool `json:"preventDelete,omitempty"` - PreventUpdate *bool `json:"preventUpdate,omitempty"` - Replicas *int `json:"replicas,omitempty"` - Republish *RePublishApplyConfiguration `json:"republish,omitempty"` - Servers []string `json:"servers,omitempty"` - Sources []*jetstreamv1beta2.StreamSource `json:"sources,omitempty"` - Storage *string `json:"storage,omitempty"` - TLS *TLSApplyConfiguration `json:"tls,omitempty"` - TTL *string `json:"ttl,omitempty"` + Bucket *string `json:"bucket,omitempty"` + Description *string `json:"description,omitempty"` + MaxValueSize *int `json:"maxValueSize,omitempty"` + History *int `json:"history,omitempty"` + TTL *string `json:"ttl,omitempty"` + MaxBytes *int `json:"maxBytes,omitempty"` + Storage *string `json:"storage,omitempty"` + Replicas *int `json:"replicas,omitempty"` + Placement *StreamPlacementApplyConfiguration `json:"placement,omitempty"` + RePublish *RePublishApplyConfiguration `json:"republish,omitempty"` + Mirror *StreamSourceApplyConfiguration `json:"mirror,omitempty"` + Sources []*jetstreamv1beta2.StreamSource `json:"sources,omitempty"` + Compression *bool `json:"compression,omitempty"` } // KeyValueSpecApplyConfiguration constructs a declarative configuration of the KeyValueSpec type for use with @@ -50,27 +43,11 @@ func KeyValueSpec() *KeyValueSpecApplyConfiguration { return &KeyValueSpecApplyConfiguration{} } -// WithAccount sets the Account field in the declarative configuration to the given value +// WithBucket sets the Bucket field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Account field is set to the value of the last call. -func (b *KeyValueSpecApplyConfiguration) WithAccount(value string) *KeyValueSpecApplyConfiguration { - b.Account = &value - return b -} - -// WithCompression sets the Compression field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Compression field is set to the value of the last call. -func (b *KeyValueSpecApplyConfiguration) WithCompression(value bool) *KeyValueSpecApplyConfiguration { - b.Compression = &value - return b -} - -// WithCreds sets the Creds field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Creds field is set to the value of the last call. -func (b *KeyValueSpecApplyConfiguration) WithCreds(value string) *KeyValueSpecApplyConfiguration { - b.Creds = &value +// If called multiple times, the Bucket field is set to the value of the last call. +func (b *KeyValueSpecApplyConfiguration) WithBucket(value string) *KeyValueSpecApplyConfiguration { + b.Bucket = &value return b } @@ -82,22 +59,6 @@ func (b *KeyValueSpecApplyConfiguration) WithDescription(value string) *KeyValue return b } -// WithHistory sets the History field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the History field is set to the value of the last call. -func (b *KeyValueSpecApplyConfiguration) WithHistory(value int) *KeyValueSpecApplyConfiguration { - b.History = &value - return b -} - -// WithMaxBytes sets the MaxBytes field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the MaxBytes field is set to the value of the last call. -func (b *KeyValueSpecApplyConfiguration) WithMaxBytes(value int) *KeyValueSpecApplyConfiguration { - b.MaxBytes = &value - return b -} - // WithMaxValueSize sets the MaxValueSize field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. // If called multiple times, the MaxValueSize field is set to the value of the last call. @@ -106,77 +67,67 @@ func (b *KeyValueSpecApplyConfiguration) WithMaxValueSize(value int) *KeyValueSp return b } -// WithMirror sets the Mirror field in the declarative configuration to the given value +// WithHistory sets the History field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Mirror field is set to the value of the last call. -func (b *KeyValueSpecApplyConfiguration) WithMirror(value *StreamSourceApplyConfiguration) *KeyValueSpecApplyConfiguration { - b.Mirror = value +// If called multiple times, the History field is set to the value of the last call. +func (b *KeyValueSpecApplyConfiguration) WithHistory(value int) *KeyValueSpecApplyConfiguration { + b.History = &value return b } -// WithName sets the Name field in the declarative configuration to the given value +// WithTTL sets the TTL field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Name field is set to the value of the last call. -func (b *KeyValueSpecApplyConfiguration) WithName(value string) *KeyValueSpecApplyConfiguration { - b.Name = &value +// If called multiple times, the TTL field is set to the value of the last call. +func (b *KeyValueSpecApplyConfiguration) WithTTL(value string) *KeyValueSpecApplyConfiguration { + b.TTL = &value return b } -// WithNkey sets the Nkey field in the declarative configuration to the given value +// WithMaxBytes sets the MaxBytes field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Nkey field is set to the value of the last call. -func (b *KeyValueSpecApplyConfiguration) WithNkey(value string) *KeyValueSpecApplyConfiguration { - b.Nkey = &value +// If called multiple times, the MaxBytes field is set to the value of the last call. +func (b *KeyValueSpecApplyConfiguration) WithMaxBytes(value int) *KeyValueSpecApplyConfiguration { + b.MaxBytes = &value return b } -// WithPlacement sets the Placement field in the declarative configuration to the given value +// WithStorage sets the Storage field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Placement field is set to the value of the last call. -func (b *KeyValueSpecApplyConfiguration) WithPlacement(value *StreamPlacementApplyConfiguration) *KeyValueSpecApplyConfiguration { - b.Placement = value +// If called multiple times, the Storage field is set to the value of the last call. +func (b *KeyValueSpecApplyConfiguration) WithStorage(value string) *KeyValueSpecApplyConfiguration { + b.Storage = &value return b } -// WithPreventDelete sets the PreventDelete field in the declarative configuration to the given value +// WithReplicas sets the Replicas field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the PreventDelete field is set to the value of the last call. -func (b *KeyValueSpecApplyConfiguration) WithPreventDelete(value bool) *KeyValueSpecApplyConfiguration { - b.PreventDelete = &value +// If called multiple times, the Replicas field is set to the value of the last call. +func (b *KeyValueSpecApplyConfiguration) WithReplicas(value int) *KeyValueSpecApplyConfiguration { + b.Replicas = &value return b } -// WithPreventUpdate sets the PreventUpdate field in the declarative configuration to the given value +// WithPlacement sets the Placement field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the PreventUpdate field is set to the value of the last call. -func (b *KeyValueSpecApplyConfiguration) WithPreventUpdate(value bool) *KeyValueSpecApplyConfiguration { - b.PreventUpdate = &value +// If called multiple times, the Placement field is set to the value of the last call. +func (b *KeyValueSpecApplyConfiguration) WithPlacement(value *StreamPlacementApplyConfiguration) *KeyValueSpecApplyConfiguration { + b.Placement = value return b } -// WithReplicas sets the Replicas field in the declarative configuration to the given value +// WithRePublish sets the RePublish field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Replicas field is set to the value of the last call. -func (b *KeyValueSpecApplyConfiguration) WithReplicas(value int) *KeyValueSpecApplyConfiguration { - b.Replicas = &value +// If called multiple times, the RePublish field is set to the value of the last call. +func (b *KeyValueSpecApplyConfiguration) WithRePublish(value *RePublishApplyConfiguration) *KeyValueSpecApplyConfiguration { + b.RePublish = value return b } -// WithRepublish sets the Republish field in the declarative configuration to the given value +// WithMirror sets the Mirror field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Republish field is set to the value of the last call. -func (b *KeyValueSpecApplyConfiguration) WithRepublish(value *RePublishApplyConfiguration) *KeyValueSpecApplyConfiguration { - b.Republish = value - return b -} - -// WithServers adds the given value to the Servers field in the declarative configuration -// and returns the receiver, so that objects can be build by chaining "With" function invocations. -// If called multiple times, values provided by each call will be appended to the Servers field. -func (b *KeyValueSpecApplyConfiguration) WithServers(values ...string) *KeyValueSpecApplyConfiguration { - for i := range values { - b.Servers = append(b.Servers, values[i]) - } +// If called multiple times, the Mirror field is set to the value of the last call. +func (b *KeyValueSpecApplyConfiguration) WithMirror(value *StreamSourceApplyConfiguration) *KeyValueSpecApplyConfiguration { + b.Mirror = value return b } @@ -193,26 +144,10 @@ func (b *KeyValueSpecApplyConfiguration) WithSources(values ...**jetstreamv1beta return b } -// WithStorage sets the Storage field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Storage field is set to the value of the last call. -func (b *KeyValueSpecApplyConfiguration) WithStorage(value string) *KeyValueSpecApplyConfiguration { - b.Storage = &value - return b -} - -// WithTLS sets the TLS field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the TLS field is set to the value of the last call. -func (b *KeyValueSpecApplyConfiguration) WithTLS(value *TLSApplyConfiguration) *KeyValueSpecApplyConfiguration { - b.TLS = value - return b -} - -// WithTTL sets the TTL field in the declarative configuration to the given value +// WithCompression sets the Compression field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the TTL field is set to the value of the last call. -func (b *KeyValueSpecApplyConfiguration) WithTTL(value string) *KeyValueSpecApplyConfiguration { - b.TTL = &value +// If called multiple times, the Compression field is set to the value of the last call. +func (b *KeyValueSpecApplyConfiguration) WithCompression(value bool) *KeyValueSpecApplyConfiguration { + b.Compression = &value return b } diff --git a/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/objectstore.go b/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/objectstore.go new file mode 100644 index 00000000..c0ea3663 --- /dev/null +++ b/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/objectstore.go @@ -0,0 +1,222 @@ +// Copyright 2025 The NATS Authors +// 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. + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1beta2 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + v1 "k8s.io/client-go/applyconfigurations/meta/v1" +) + +// ObjectStoreApplyConfiguration represents a declarative configuration of the ObjectStore type for use +// with apply. +type ObjectStoreApplyConfiguration struct { + v1.TypeMetaApplyConfiguration `json:",inline"` + *v1.ObjectMetaApplyConfiguration `json:"metadata,omitempty"` + Spec *ObjectStoreSpecApplyConfiguration `json:"spec,omitempty"` + Status *StatusApplyConfiguration `json:"status,omitempty"` +} + +// ObjectStore constructs a declarative configuration of the ObjectStore type for use with +// apply. +func ObjectStore(name, namespace string) *ObjectStoreApplyConfiguration { + b := &ObjectStoreApplyConfiguration{} + b.WithName(name) + b.WithNamespace(namespace) + b.WithKind("ObjectStore") + b.WithAPIVersion("jetstream.nats.io/v1beta2") + return b +} + +// WithKind sets the Kind field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Kind field is set to the value of the last call. +func (b *ObjectStoreApplyConfiguration) WithKind(value string) *ObjectStoreApplyConfiguration { + b.TypeMetaApplyConfiguration.Kind = &value + return b +} + +// WithAPIVersion sets the APIVersion field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the APIVersion field is set to the value of the last call. +func (b *ObjectStoreApplyConfiguration) WithAPIVersion(value string) *ObjectStoreApplyConfiguration { + b.TypeMetaApplyConfiguration.APIVersion = &value + return b +} + +// WithName sets the Name field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Name field is set to the value of the last call. +func (b *ObjectStoreApplyConfiguration) WithName(value string) *ObjectStoreApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.Name = &value + return b +} + +// WithGenerateName sets the GenerateName field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the GenerateName field is set to the value of the last call. +func (b *ObjectStoreApplyConfiguration) WithGenerateName(value string) *ObjectStoreApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.GenerateName = &value + return b +} + +// WithNamespace sets the Namespace field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Namespace field is set to the value of the last call. +func (b *ObjectStoreApplyConfiguration) WithNamespace(value string) *ObjectStoreApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.Namespace = &value + return b +} + +// WithUID sets the UID field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the UID field is set to the value of the last call. +func (b *ObjectStoreApplyConfiguration) WithUID(value types.UID) *ObjectStoreApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.UID = &value + return b +} + +// WithResourceVersion sets the ResourceVersion field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ResourceVersion field is set to the value of the last call. +func (b *ObjectStoreApplyConfiguration) WithResourceVersion(value string) *ObjectStoreApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.ResourceVersion = &value + return b +} + +// WithGeneration sets the Generation field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Generation field is set to the value of the last call. +func (b *ObjectStoreApplyConfiguration) WithGeneration(value int64) *ObjectStoreApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.Generation = &value + return b +} + +// WithCreationTimestamp sets the CreationTimestamp field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the CreationTimestamp field is set to the value of the last call. +func (b *ObjectStoreApplyConfiguration) WithCreationTimestamp(value metav1.Time) *ObjectStoreApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.CreationTimestamp = &value + return b +} + +// WithDeletionTimestamp sets the DeletionTimestamp field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the DeletionTimestamp field is set to the value of the last call. +func (b *ObjectStoreApplyConfiguration) WithDeletionTimestamp(value metav1.Time) *ObjectStoreApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.DeletionTimestamp = &value + return b +} + +// WithDeletionGracePeriodSeconds sets the DeletionGracePeriodSeconds field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the DeletionGracePeriodSeconds field is set to the value of the last call. +func (b *ObjectStoreApplyConfiguration) WithDeletionGracePeriodSeconds(value int64) *ObjectStoreApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.DeletionGracePeriodSeconds = &value + return b +} + +// WithLabels puts the entries into the Labels field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, the entries provided by each call will be put on the Labels field, +// overwriting an existing map entries in Labels field with the same key. +func (b *ObjectStoreApplyConfiguration) WithLabels(entries map[string]string) *ObjectStoreApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + if b.ObjectMetaApplyConfiguration.Labels == nil && len(entries) > 0 { + b.ObjectMetaApplyConfiguration.Labels = make(map[string]string, len(entries)) + } + for k, v := range entries { + b.ObjectMetaApplyConfiguration.Labels[k] = v + } + return b +} + +// WithAnnotations puts the entries into the Annotations field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, the entries provided by each call will be put on the Annotations field, +// overwriting an existing map entries in Annotations field with the same key. +func (b *ObjectStoreApplyConfiguration) WithAnnotations(entries map[string]string) *ObjectStoreApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + if b.ObjectMetaApplyConfiguration.Annotations == nil && len(entries) > 0 { + b.ObjectMetaApplyConfiguration.Annotations = make(map[string]string, len(entries)) + } + for k, v := range entries { + b.ObjectMetaApplyConfiguration.Annotations[k] = v + } + return b +} + +// WithOwnerReferences adds the given value to the OwnerReferences field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the OwnerReferences field. +func (b *ObjectStoreApplyConfiguration) WithOwnerReferences(values ...*v1.OwnerReferenceApplyConfiguration) *ObjectStoreApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + for i := range values { + if values[i] == nil { + panic("nil value passed to WithOwnerReferences") + } + b.ObjectMetaApplyConfiguration.OwnerReferences = append(b.ObjectMetaApplyConfiguration.OwnerReferences, *values[i]) + } + return b +} + +// WithFinalizers adds the given value to the Finalizers field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the Finalizers field. +func (b *ObjectStoreApplyConfiguration) WithFinalizers(values ...string) *ObjectStoreApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + for i := range values { + b.ObjectMetaApplyConfiguration.Finalizers = append(b.ObjectMetaApplyConfiguration.Finalizers, values[i]) + } + return b +} + +func (b *ObjectStoreApplyConfiguration) ensureObjectMetaApplyConfigurationExists() { + if b.ObjectMetaApplyConfiguration == nil { + b.ObjectMetaApplyConfiguration = &v1.ObjectMetaApplyConfiguration{} + } +} + +// WithSpec sets the Spec field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Spec field is set to the value of the last call. +func (b *ObjectStoreApplyConfiguration) WithSpec(value *ObjectStoreSpecApplyConfiguration) *ObjectStoreApplyConfiguration { + b.Spec = value + return b +} + +// WithStatus sets the Status field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Status field is set to the value of the last call. +func (b *ObjectStoreApplyConfiguration) WithStatus(value *StatusApplyConfiguration) *ObjectStoreApplyConfiguration { + b.Status = value + return b +} + +// GetName retrieves the value of the Name field in the declarative configuration. +func (b *ObjectStoreApplyConfiguration) GetName() *string { + b.ensureObjectMetaApplyConfigurationExists() + return b.ObjectMetaApplyConfiguration.Name +} diff --git a/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/objectstorespec.go b/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/objectstorespec.go new file mode 100644 index 00000000..0b9fc683 --- /dev/null +++ b/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/objectstorespec.go @@ -0,0 +1,114 @@ +// Copyright 2025 The NATS Authors +// 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. + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1beta2 + +// ObjectStoreSpecApplyConfiguration represents a declarative configuration of the ObjectStoreSpec type for use +// with apply. +type ObjectStoreSpecApplyConfiguration struct { + Bucket *string `json:"bucket,omitempty"` + Description *string `json:"description,omitempty"` + TTL *string `json:"ttl,omitempty"` + MaxBytes *int `json:"maxBytes,omitempty"` + Storage *string `json:"storage,omitempty"` + Replicas *int `json:"replicas,omitempty"` + Placement *StreamPlacementApplyConfiguration `json:"placement,omitempty"` + Compression *bool `json:"compression,omitempty"` + Metadata map[string]string `json:"metadata,omitempty"` +} + +// ObjectStoreSpecApplyConfiguration constructs a declarative configuration of the ObjectStoreSpec type for use with +// apply. +func ObjectStoreSpec() *ObjectStoreSpecApplyConfiguration { + return &ObjectStoreSpecApplyConfiguration{} +} + +// WithBucket sets the Bucket field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Bucket field is set to the value of the last call. +func (b *ObjectStoreSpecApplyConfiguration) WithBucket(value string) *ObjectStoreSpecApplyConfiguration { + b.Bucket = &value + return b +} + +// WithDescription sets the Description field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Description field is set to the value of the last call. +func (b *ObjectStoreSpecApplyConfiguration) WithDescription(value string) *ObjectStoreSpecApplyConfiguration { + b.Description = &value + return b +} + +// WithTTL sets the TTL field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the TTL field is set to the value of the last call. +func (b *ObjectStoreSpecApplyConfiguration) WithTTL(value string) *ObjectStoreSpecApplyConfiguration { + b.TTL = &value + return b +} + +// WithMaxBytes sets the MaxBytes field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the MaxBytes field is set to the value of the last call. +func (b *ObjectStoreSpecApplyConfiguration) WithMaxBytes(value int) *ObjectStoreSpecApplyConfiguration { + b.MaxBytes = &value + return b +} + +// WithStorage sets the Storage field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Storage field is set to the value of the last call. +func (b *ObjectStoreSpecApplyConfiguration) WithStorage(value string) *ObjectStoreSpecApplyConfiguration { + b.Storage = &value + return b +} + +// WithReplicas sets the Replicas field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Replicas field is set to the value of the last call. +func (b *ObjectStoreSpecApplyConfiguration) WithReplicas(value int) *ObjectStoreSpecApplyConfiguration { + b.Replicas = &value + return b +} + +// WithPlacement sets the Placement field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Placement field is set to the value of the last call. +func (b *ObjectStoreSpecApplyConfiguration) WithPlacement(value *StreamPlacementApplyConfiguration) *ObjectStoreSpecApplyConfiguration { + b.Placement = value + return b +} + +// WithCompression sets the Compression field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Compression field is set to the value of the last call. +func (b *ObjectStoreSpecApplyConfiguration) WithCompression(value bool) *ObjectStoreSpecApplyConfiguration { + b.Compression = &value + return b +} + +// WithMetadata puts the entries into the Metadata field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, the entries provided by each call will be put on the Metadata field, +// overwriting an existing map entries in Metadata field with the same key. +func (b *ObjectStoreSpecApplyConfiguration) WithMetadata(entries map[string]string) *ObjectStoreSpecApplyConfiguration { + if b.Metadata == nil && len(entries) > 0 { + b.Metadata = make(map[string]string, len(entries)) + } + for k, v := range entries { + b.Metadata[k] = v + } + return b +} diff --git a/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/streamspec.go b/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/streamspec.go index 608a118d..1831f3de 100644 --- a/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/streamspec.go +++ b/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/streamspec.go @@ -22,41 +22,37 @@ import ( // StreamSpecApplyConfiguration represents a declarative configuration of the StreamSpec type for use // with apply. type StreamSpecApplyConfiguration struct { - Account *string `json:"account,omitempty"` - AllowDirect *bool `json:"allowDirect,omitempty"` - AllowRollup *bool `json:"allowRollup,omitempty"` - Creds *string `json:"creds,omitempty"` - DenyDelete *bool `json:"denyDelete,omitempty"` - DenyPurge *bool `json:"denyPurge,omitempty"` + Name *string `json:"name,omitempty"` Description *string `json:"description,omitempty"` - DiscardPerSubject *bool `json:"discardPerSubject,omitempty"` - PreventDelete *bool `json:"preventDelete,omitempty"` - PreventUpdate *bool `json:"preventUpdate,omitempty"` - Discard *string `json:"discard,omitempty"` - DuplicateWindow *string `json:"duplicateWindow,omitempty"` - MaxAge *string `json:"maxAge,omitempty"` - MaxBytes *int `json:"maxBytes,omitempty"` + Subjects []string `json:"subjects,omitempty"` + Retention *string `json:"retention,omitempty"` MaxConsumers *int `json:"maxConsumers,omitempty"` MaxMsgs *int `json:"maxMsgs,omitempty"` - MaxMsgSize *int `json:"maxMsgSize,omitempty"` + MaxBytes *int `json:"maxBytes,omitempty"` + Discard *string `json:"discard,omitempty"` + DiscardPerSubject *bool `json:"discardPerSubject,omitempty"` + MaxAge *string `json:"maxAge,omitempty"` MaxMsgsPerSubject *int `json:"maxMsgsPerSubject,omitempty"` - Mirror *StreamSourceApplyConfiguration `json:"mirror,omitempty"` - Name *string `json:"name,omitempty"` - Nkey *string `json:"nkey,omitempty"` + MaxMsgSize *int `json:"maxMsgSize,omitempty"` + Storage *string `json:"storage,omitempty"` + Replicas *int `json:"replicas,omitempty"` NoAck *bool `json:"noAck,omitempty"` + DuplicateWindow *string `json:"duplicateWindow,omitempty"` Placement *StreamPlacementApplyConfiguration `json:"placement,omitempty"` - Replicas *int `json:"replicas,omitempty"` - Republish *RePublishApplyConfiguration `json:"republish,omitempty"` - SubjectTransform *SubjectTransformApplyConfiguration `json:"subjectTransform,omitempty"` - FirstSequence *uint64 `json:"firstSequence,omitempty"` + Mirror *StreamSourceApplyConfiguration `json:"mirror,omitempty"` + Sources []*jetstreamv1beta2.StreamSource `json:"sources,omitempty"` + Sealed *bool `json:"sealed,omitempty"` + DenyDelete *bool `json:"denyDelete,omitempty"` + DenyPurge *bool `json:"denyPurge,omitempty"` + AllowRollup *bool `json:"allowRollup,omitempty"` Compression *string `json:"compression,omitempty"` + FirstSequence *uint64 `json:"firstSequence,omitempty"` + SubjectTransform *SubjectTransformApplyConfiguration `json:"subjectTransform,omitempty"` + RePublish *RePublishApplyConfiguration `json:"republish,omitempty"` + AllowDirect *bool `json:"allowDirect,omitempty"` + MirrorDirect *bool `json:"mirrorDirect,omitempty"` + ConsumerLimits *ConsumerLimitsApplyConfiguration `json:"consumerLimits,omitempty"` Metadata map[string]string `json:"metadata,omitempty"` - Retention *string `json:"retention,omitempty"` - Servers []string `json:"servers,omitempty"` - Sources []*jetstreamv1beta2.StreamSource `json:"sources,omitempty"` - Storage *string `json:"storage,omitempty"` - Subjects []string `json:"subjects,omitempty"` - TLS *TLSApplyConfiguration `json:"tls,omitempty"` } // StreamSpecApplyConfiguration constructs a declarative configuration of the StreamSpec type for use with @@ -65,51 +61,11 @@ func StreamSpec() *StreamSpecApplyConfiguration { return &StreamSpecApplyConfiguration{} } -// WithAccount sets the Account field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Account field is set to the value of the last call. -func (b *StreamSpecApplyConfiguration) WithAccount(value string) *StreamSpecApplyConfiguration { - b.Account = &value - return b -} - -// WithAllowDirect sets the AllowDirect field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the AllowDirect field is set to the value of the last call. -func (b *StreamSpecApplyConfiguration) WithAllowDirect(value bool) *StreamSpecApplyConfiguration { - b.AllowDirect = &value - return b -} - -// WithAllowRollup sets the AllowRollup field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the AllowRollup field is set to the value of the last call. -func (b *StreamSpecApplyConfiguration) WithAllowRollup(value bool) *StreamSpecApplyConfiguration { - b.AllowRollup = &value - return b -} - -// WithCreds sets the Creds field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Creds field is set to the value of the last call. -func (b *StreamSpecApplyConfiguration) WithCreds(value string) *StreamSpecApplyConfiguration { - b.Creds = &value - return b -} - -// WithDenyDelete sets the DenyDelete field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the DenyDelete field is set to the value of the last call. -func (b *StreamSpecApplyConfiguration) WithDenyDelete(value bool) *StreamSpecApplyConfiguration { - b.DenyDelete = &value - return b -} - -// WithDenyPurge sets the DenyPurge field in the declarative configuration to the given value +// WithName sets the Name field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the DenyPurge field is set to the value of the last call. -func (b *StreamSpecApplyConfiguration) WithDenyPurge(value bool) *StreamSpecApplyConfiguration { - b.DenyPurge = &value +// If called multiple times, the Name field is set to the value of the last call. +func (b *StreamSpecApplyConfiguration) WithName(value string) *StreamSpecApplyConfiguration { + b.Name = &value return b } @@ -121,51 +77,37 @@ func (b *StreamSpecApplyConfiguration) WithDescription(value string) *StreamSpec return b } -// WithDiscardPerSubject sets the DiscardPerSubject field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the DiscardPerSubject field is set to the value of the last call. -func (b *StreamSpecApplyConfiguration) WithDiscardPerSubject(value bool) *StreamSpecApplyConfiguration { - b.DiscardPerSubject = &value - return b -} - -// WithPreventDelete sets the PreventDelete field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the PreventDelete field is set to the value of the last call. -func (b *StreamSpecApplyConfiguration) WithPreventDelete(value bool) *StreamSpecApplyConfiguration { - b.PreventDelete = &value - return b -} - -// WithPreventUpdate sets the PreventUpdate field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the PreventUpdate field is set to the value of the last call. -func (b *StreamSpecApplyConfiguration) WithPreventUpdate(value bool) *StreamSpecApplyConfiguration { - b.PreventUpdate = &value +// WithSubjects adds the given value to the Subjects field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the Subjects field. +func (b *StreamSpecApplyConfiguration) WithSubjects(values ...string) *StreamSpecApplyConfiguration { + for i := range values { + b.Subjects = append(b.Subjects, values[i]) + } return b } -// WithDiscard sets the Discard field in the declarative configuration to the given value +// WithRetention sets the Retention field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Discard field is set to the value of the last call. -func (b *StreamSpecApplyConfiguration) WithDiscard(value string) *StreamSpecApplyConfiguration { - b.Discard = &value +// If called multiple times, the Retention field is set to the value of the last call. +func (b *StreamSpecApplyConfiguration) WithRetention(value string) *StreamSpecApplyConfiguration { + b.Retention = &value return b } -// WithDuplicateWindow sets the DuplicateWindow field in the declarative configuration to the given value +// WithMaxConsumers sets the MaxConsumers field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the DuplicateWindow field is set to the value of the last call. -func (b *StreamSpecApplyConfiguration) WithDuplicateWindow(value string) *StreamSpecApplyConfiguration { - b.DuplicateWindow = &value +// If called multiple times, the MaxConsumers field is set to the value of the last call. +func (b *StreamSpecApplyConfiguration) WithMaxConsumers(value int) *StreamSpecApplyConfiguration { + b.MaxConsumers = &value return b } -// WithMaxAge sets the MaxAge field in the declarative configuration to the given value +// WithMaxMsgs sets the MaxMsgs field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the MaxAge field is set to the value of the last call. -func (b *StreamSpecApplyConfiguration) WithMaxAge(value string) *StreamSpecApplyConfiguration { - b.MaxAge = &value +// If called multiple times, the MaxMsgs field is set to the value of the last call. +func (b *StreamSpecApplyConfiguration) WithMaxMsgs(value int) *StreamSpecApplyConfiguration { + b.MaxMsgs = &value return b } @@ -177,27 +119,27 @@ func (b *StreamSpecApplyConfiguration) WithMaxBytes(value int) *StreamSpecApplyC return b } -// WithMaxConsumers sets the MaxConsumers field in the declarative configuration to the given value +// WithDiscard sets the Discard field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the MaxConsumers field is set to the value of the last call. -func (b *StreamSpecApplyConfiguration) WithMaxConsumers(value int) *StreamSpecApplyConfiguration { - b.MaxConsumers = &value +// If called multiple times, the Discard field is set to the value of the last call. +func (b *StreamSpecApplyConfiguration) WithDiscard(value string) *StreamSpecApplyConfiguration { + b.Discard = &value return b } -// WithMaxMsgs sets the MaxMsgs field in the declarative configuration to the given value +// WithDiscardPerSubject sets the DiscardPerSubject field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the MaxMsgs field is set to the value of the last call. -func (b *StreamSpecApplyConfiguration) WithMaxMsgs(value int) *StreamSpecApplyConfiguration { - b.MaxMsgs = &value +// If called multiple times, the DiscardPerSubject field is set to the value of the last call. +func (b *StreamSpecApplyConfiguration) WithDiscardPerSubject(value bool) *StreamSpecApplyConfiguration { + b.DiscardPerSubject = &value return b } -// WithMaxMsgSize sets the MaxMsgSize field in the declarative configuration to the given value +// WithMaxAge sets the MaxAge field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the MaxMsgSize field is set to the value of the last call. -func (b *StreamSpecApplyConfiguration) WithMaxMsgSize(value int) *StreamSpecApplyConfiguration { - b.MaxMsgSize = &value +// If called multiple times, the MaxAge field is set to the value of the last call. +func (b *StreamSpecApplyConfiguration) WithMaxAge(value string) *StreamSpecApplyConfiguration { + b.MaxAge = &value return b } @@ -209,27 +151,27 @@ func (b *StreamSpecApplyConfiguration) WithMaxMsgsPerSubject(value int) *StreamS return b } -// WithMirror sets the Mirror field in the declarative configuration to the given value +// WithMaxMsgSize sets the MaxMsgSize field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Mirror field is set to the value of the last call. -func (b *StreamSpecApplyConfiguration) WithMirror(value *StreamSourceApplyConfiguration) *StreamSpecApplyConfiguration { - b.Mirror = value +// If called multiple times, the MaxMsgSize field is set to the value of the last call. +func (b *StreamSpecApplyConfiguration) WithMaxMsgSize(value int) *StreamSpecApplyConfiguration { + b.MaxMsgSize = &value return b } -// WithName sets the Name field in the declarative configuration to the given value +// WithStorage sets the Storage field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Name field is set to the value of the last call. -func (b *StreamSpecApplyConfiguration) WithName(value string) *StreamSpecApplyConfiguration { - b.Name = &value +// If called multiple times, the Storage field is set to the value of the last call. +func (b *StreamSpecApplyConfiguration) WithStorage(value string) *StreamSpecApplyConfiguration { + b.Storage = &value return b } -// WithNkey sets the Nkey field in the declarative configuration to the given value +// WithReplicas sets the Replicas field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Nkey field is set to the value of the last call. -func (b *StreamSpecApplyConfiguration) WithNkey(value string) *StreamSpecApplyConfiguration { - b.Nkey = &value +// If called multiple times, the Replicas field is set to the value of the last call. +func (b *StreamSpecApplyConfiguration) WithReplicas(value int) *StreamSpecApplyConfiguration { + b.Replicas = &value return b } @@ -241,6 +183,14 @@ func (b *StreamSpecApplyConfiguration) WithNoAck(value bool) *StreamSpecApplyCon return b } +// WithDuplicateWindow sets the DuplicateWindow field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the DuplicateWindow field is set to the value of the last call. +func (b *StreamSpecApplyConfiguration) WithDuplicateWindow(value string) *StreamSpecApplyConfiguration { + b.DuplicateWindow = &value + return b +} + // WithPlacement sets the Placement field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. // If called multiple times, the Placement field is set to the value of the last call. @@ -249,35 +199,56 @@ func (b *StreamSpecApplyConfiguration) WithPlacement(value *StreamPlacementApply return b } -// WithReplicas sets the Replicas field in the declarative configuration to the given value +// WithMirror sets the Mirror field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Replicas field is set to the value of the last call. -func (b *StreamSpecApplyConfiguration) WithReplicas(value int) *StreamSpecApplyConfiguration { - b.Replicas = &value +// If called multiple times, the Mirror field is set to the value of the last call. +func (b *StreamSpecApplyConfiguration) WithMirror(value *StreamSourceApplyConfiguration) *StreamSpecApplyConfiguration { + b.Mirror = value + return b +} + +// WithSources adds the given value to the Sources field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the Sources field. +func (b *StreamSpecApplyConfiguration) WithSources(values ...**jetstreamv1beta2.StreamSource) *StreamSpecApplyConfiguration { + for i := range values { + if values[i] == nil { + panic("nil value passed to WithSources") + } + b.Sources = append(b.Sources, *values[i]) + } return b } -// WithRepublish sets the Republish field in the declarative configuration to the given value +// WithSealed sets the Sealed field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Republish field is set to the value of the last call. -func (b *StreamSpecApplyConfiguration) WithRepublish(value *RePublishApplyConfiguration) *StreamSpecApplyConfiguration { - b.Republish = value +// If called multiple times, the Sealed field is set to the value of the last call. +func (b *StreamSpecApplyConfiguration) WithSealed(value bool) *StreamSpecApplyConfiguration { + b.Sealed = &value return b } -// WithSubjectTransform sets the SubjectTransform field in the declarative configuration to the given value +// WithDenyDelete sets the DenyDelete field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the SubjectTransform field is set to the value of the last call. -func (b *StreamSpecApplyConfiguration) WithSubjectTransform(value *SubjectTransformApplyConfiguration) *StreamSpecApplyConfiguration { - b.SubjectTransform = value +// If called multiple times, the DenyDelete field is set to the value of the last call. +func (b *StreamSpecApplyConfiguration) WithDenyDelete(value bool) *StreamSpecApplyConfiguration { + b.DenyDelete = &value return b } -// WithFirstSequence sets the FirstSequence field in the declarative configuration to the given value +// WithDenyPurge sets the DenyPurge field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the FirstSequence field is set to the value of the last call. -func (b *StreamSpecApplyConfiguration) WithFirstSequence(value uint64) *StreamSpecApplyConfiguration { - b.FirstSequence = &value +// If called multiple times, the DenyPurge field is set to the value of the last call. +func (b *StreamSpecApplyConfiguration) WithDenyPurge(value bool) *StreamSpecApplyConfiguration { + b.DenyPurge = &value + return b +} + +// WithAllowRollup sets the AllowRollup field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the AllowRollup field is set to the value of the last call. +func (b *StreamSpecApplyConfiguration) WithAllowRollup(value bool) *StreamSpecApplyConfiguration { + b.AllowRollup = &value return b } @@ -289,73 +260,64 @@ func (b *StreamSpecApplyConfiguration) WithCompression(value string) *StreamSpec return b } -// WithMetadata puts the entries into the Metadata field in the declarative configuration -// and returns the receiver, so that objects can be build by chaining "With" function invocations. -// If called multiple times, the entries provided by each call will be put on the Metadata field, -// overwriting an existing map entries in Metadata field with the same key. -func (b *StreamSpecApplyConfiguration) WithMetadata(entries map[string]string) *StreamSpecApplyConfiguration { - if b.Metadata == nil && len(entries) > 0 { - b.Metadata = make(map[string]string, len(entries)) - } - for k, v := range entries { - b.Metadata[k] = v - } +// WithFirstSequence sets the FirstSequence field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the FirstSequence field is set to the value of the last call. +func (b *StreamSpecApplyConfiguration) WithFirstSequence(value uint64) *StreamSpecApplyConfiguration { + b.FirstSequence = &value return b } -// WithRetention sets the Retention field in the declarative configuration to the given value +// WithSubjectTransform sets the SubjectTransform field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Retention field is set to the value of the last call. -func (b *StreamSpecApplyConfiguration) WithRetention(value string) *StreamSpecApplyConfiguration { - b.Retention = &value +// If called multiple times, the SubjectTransform field is set to the value of the last call. +func (b *StreamSpecApplyConfiguration) WithSubjectTransform(value *SubjectTransformApplyConfiguration) *StreamSpecApplyConfiguration { + b.SubjectTransform = value return b } -// WithServers adds the given value to the Servers field in the declarative configuration -// and returns the receiver, so that objects can be build by chaining "With" function invocations. -// If called multiple times, values provided by each call will be appended to the Servers field. -func (b *StreamSpecApplyConfiguration) WithServers(values ...string) *StreamSpecApplyConfiguration { - for i := range values { - b.Servers = append(b.Servers, values[i]) - } +// WithRePublish sets the RePublish field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the RePublish field is set to the value of the last call. +func (b *StreamSpecApplyConfiguration) WithRePublish(value *RePublishApplyConfiguration) *StreamSpecApplyConfiguration { + b.RePublish = value return b } -// WithSources adds the given value to the Sources field in the declarative configuration -// and returns the receiver, so that objects can be build by chaining "With" function invocations. -// If called multiple times, values provided by each call will be appended to the Sources field. -func (b *StreamSpecApplyConfiguration) WithSources(values ...**jetstreamv1beta2.StreamSource) *StreamSpecApplyConfiguration { - for i := range values { - if values[i] == nil { - panic("nil value passed to WithSources") - } - b.Sources = append(b.Sources, *values[i]) - } +// WithAllowDirect sets the AllowDirect field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the AllowDirect field is set to the value of the last call. +func (b *StreamSpecApplyConfiguration) WithAllowDirect(value bool) *StreamSpecApplyConfiguration { + b.AllowDirect = &value return b } -// WithStorage sets the Storage field in the declarative configuration to the given value +// WithMirrorDirect sets the MirrorDirect field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Storage field is set to the value of the last call. -func (b *StreamSpecApplyConfiguration) WithStorage(value string) *StreamSpecApplyConfiguration { - b.Storage = &value +// If called multiple times, the MirrorDirect field is set to the value of the last call. +func (b *StreamSpecApplyConfiguration) WithMirrorDirect(value bool) *StreamSpecApplyConfiguration { + b.MirrorDirect = &value return b } -// WithSubjects adds the given value to the Subjects field in the declarative configuration -// and returns the receiver, so that objects can be build by chaining "With" function invocations. -// If called multiple times, values provided by each call will be appended to the Subjects field. -func (b *StreamSpecApplyConfiguration) WithSubjects(values ...string) *StreamSpecApplyConfiguration { - for i := range values { - b.Subjects = append(b.Subjects, values[i]) - } +// WithConsumerLimits sets the ConsumerLimits field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ConsumerLimits field is set to the value of the last call. +func (b *StreamSpecApplyConfiguration) WithConsumerLimits(value *ConsumerLimitsApplyConfiguration) *StreamSpecApplyConfiguration { + b.ConsumerLimits = value return b } -// WithTLS sets the TLS field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the TLS field is set to the value of the last call. -func (b *StreamSpecApplyConfiguration) WithTLS(value *TLSApplyConfiguration) *StreamSpecApplyConfiguration { - b.TLS = value +// WithMetadata puts the entries into the Metadata field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, the entries provided by each call will be put on the Metadata field, +// overwriting an existing map entries in Metadata field with the same key. +func (b *StreamSpecApplyConfiguration) WithMetadata(entries map[string]string) *StreamSpecApplyConfiguration { + if b.Metadata == nil && len(entries) > 0 { + b.Metadata = make(map[string]string, len(entries)) + } + for k, v := range entries { + b.Metadata[k] = v + } return b } diff --git a/pkg/jetstream/generated/applyconfiguration/utils.go b/pkg/jetstream/generated/applyconfiguration/utils.go index 206d9b72..7ccfa1ae 100644 --- a/pkg/jetstream/generated/applyconfiguration/utils.go +++ b/pkg/jetstream/generated/applyconfiguration/utils.go @@ -33,10 +33,14 @@ func ForKind(kind schema.GroupVersionKind) interface{} { return &jetstreamv1beta2.AccountApplyConfiguration{} case v1beta2.SchemeGroupVersion.WithKind("AccountSpec"): return &jetstreamv1beta2.AccountSpecApplyConfiguration{} + case v1beta2.SchemeGroupVersion.WithKind("BaseStreamConfig"): + return &jetstreamv1beta2.BaseStreamConfigApplyConfiguration{} case v1beta2.SchemeGroupVersion.WithKind("Condition"): return &jetstreamv1beta2.ConditionApplyConfiguration{} case v1beta2.SchemeGroupVersion.WithKind("Consumer"): return &jetstreamv1beta2.ConsumerApplyConfiguration{} + case v1beta2.SchemeGroupVersion.WithKind("ConsumerLimits"): + return &jetstreamv1beta2.ConsumerLimitsApplyConfiguration{} case v1beta2.SchemeGroupVersion.WithKind("ConsumerSpec"): return &jetstreamv1beta2.ConsumerSpecApplyConfiguration{} case v1beta2.SchemeGroupVersion.WithKind("CredsSecret"): @@ -45,6 +49,10 @@ func ForKind(kind schema.GroupVersionKind) interface{} { return &jetstreamv1beta2.KeyValueApplyConfiguration{} case v1beta2.SchemeGroupVersion.WithKind("KeyValueSpec"): return &jetstreamv1beta2.KeyValueSpecApplyConfiguration{} + case v1beta2.SchemeGroupVersion.WithKind("ObjectStore"): + return &jetstreamv1beta2.ObjectStoreApplyConfiguration{} + case v1beta2.SchemeGroupVersion.WithKind("ObjectStoreSpec"): + return &jetstreamv1beta2.ObjectStoreSpecApplyConfiguration{} case v1beta2.SchemeGroupVersion.WithKind("RePublish"): return &jetstreamv1beta2.RePublishApplyConfiguration{} case v1beta2.SchemeGroupVersion.WithKind("SecretRef"): diff --git a/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/fake/fake_jetstream_client.go b/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/fake/fake_jetstream_client.go index 7eec1be1..d8df64c3 100644 --- a/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/fake/fake_jetstream_client.go +++ b/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/fake/fake_jetstream_client.go @@ -37,6 +37,10 @@ func (c *FakeJetstreamV1beta2) KeyValues(namespace string) v1beta2.KeyValueInter return newFakeKeyValues(c, namespace) } +func (c *FakeJetstreamV1beta2) ObjectStores(namespace string) v1beta2.ObjectStoreInterface { + return newFakeObjectStores(c, namespace) +} + func (c *FakeJetstreamV1beta2) Streams(namespace string) v1beta2.StreamInterface { return newFakeStreams(c, namespace) } diff --git a/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/fake/fake_objectstore.go b/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/fake/fake_objectstore.go new file mode 100644 index 00000000..4a837f69 --- /dev/null +++ b/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/fake/fake_objectstore.go @@ -0,0 +1,48 @@ +// Copyright 2025 The NATS Authors +// 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. + +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + v1beta2 "github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2" + jetstreamv1beta2 "github.com/nats-io/nack/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2" + typedjetstreamv1beta2 "github.com/nats-io/nack/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2" + gentype "k8s.io/client-go/gentype" +) + +// fakeObjectStores implements ObjectStoreInterface +type fakeObjectStores struct { + *gentype.FakeClientWithListAndApply[*v1beta2.ObjectStore, *v1beta2.ObjectStoreList, *jetstreamv1beta2.ObjectStoreApplyConfiguration] + Fake *FakeJetstreamV1beta2 +} + +func newFakeObjectStores(fake *FakeJetstreamV1beta2, namespace string) typedjetstreamv1beta2.ObjectStoreInterface { + return &fakeObjectStores{ + gentype.NewFakeClientWithListAndApply[*v1beta2.ObjectStore, *v1beta2.ObjectStoreList, *jetstreamv1beta2.ObjectStoreApplyConfiguration]( + fake.Fake, + namespace, + v1beta2.SchemeGroupVersion.WithResource("objectstores"), + v1beta2.SchemeGroupVersion.WithKind("ObjectStore"), + func() *v1beta2.ObjectStore { return &v1beta2.ObjectStore{} }, + func() *v1beta2.ObjectStoreList { return &v1beta2.ObjectStoreList{} }, + func(dst, src *v1beta2.ObjectStoreList) { dst.ListMeta = src.ListMeta }, + func(list *v1beta2.ObjectStoreList) []*v1beta2.ObjectStore { return gentype.ToPointerSlice(list.Items) }, + func(list *v1beta2.ObjectStoreList, items []*v1beta2.ObjectStore) { + list.Items = gentype.FromPointerSlice(items) + }, + ), + fake, + } +} diff --git a/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/generated_expansion.go b/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/generated_expansion.go index 97b76f2e..31400b17 100644 --- a/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/generated_expansion.go +++ b/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/generated_expansion.go @@ -21,4 +21,6 @@ type ConsumerExpansion interface{} type KeyValueExpansion interface{} +type ObjectStoreExpansion interface{} + type StreamExpansion interface{} diff --git a/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/jetstream_client.go b/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/jetstream_client.go index c18538bb..18411e7b 100644 --- a/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/jetstream_client.go +++ b/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/jetstream_client.go @@ -28,6 +28,7 @@ type JetstreamV1beta2Interface interface { AccountsGetter ConsumersGetter KeyValuesGetter + ObjectStoresGetter StreamsGetter } @@ -48,6 +49,10 @@ func (c *JetstreamV1beta2Client) KeyValues(namespace string) KeyValueInterface { return newKeyValues(c, namespace) } +func (c *JetstreamV1beta2Client) ObjectStores(namespace string) ObjectStoreInterface { + return newObjectStores(c, namespace) +} + func (c *JetstreamV1beta2Client) Streams(namespace string) StreamInterface { return newStreams(c, namespace) } diff --git a/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/objectstore.go b/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/objectstore.go new file mode 100644 index 00000000..d69b2d3c --- /dev/null +++ b/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/objectstore.go @@ -0,0 +1,71 @@ +// Copyright 2025 The NATS Authors +// 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. + +// Code generated by client-gen. DO NOT EDIT. + +package v1beta2 + +import ( + context "context" + + jetstreamv1beta2 "github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2" + applyconfigurationjetstreamv1beta2 "github.com/nats-io/nack/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2" + scheme "github.com/nats-io/nack/pkg/jetstream/generated/clientset/versioned/scheme" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + gentype "k8s.io/client-go/gentype" +) + +// ObjectStoresGetter has a method to return a ObjectStoreInterface. +// A group's client should implement this interface. +type ObjectStoresGetter interface { + ObjectStores(namespace string) ObjectStoreInterface +} + +// ObjectStoreInterface has methods to work with ObjectStore resources. +type ObjectStoreInterface interface { + Create(ctx context.Context, objectStore *jetstreamv1beta2.ObjectStore, opts v1.CreateOptions) (*jetstreamv1beta2.ObjectStore, error) + Update(ctx context.Context, objectStore *jetstreamv1beta2.ObjectStore, opts v1.UpdateOptions) (*jetstreamv1beta2.ObjectStore, error) + // Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). + UpdateStatus(ctx context.Context, objectStore *jetstreamv1beta2.ObjectStore, opts v1.UpdateOptions) (*jetstreamv1beta2.ObjectStore, error) + Delete(ctx context.Context, name string, opts v1.DeleteOptions) error + DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error + Get(ctx context.Context, name string, opts v1.GetOptions) (*jetstreamv1beta2.ObjectStore, error) + List(ctx context.Context, opts v1.ListOptions) (*jetstreamv1beta2.ObjectStoreList, error) + Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) + Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *jetstreamv1beta2.ObjectStore, err error) + Apply(ctx context.Context, objectStore *applyconfigurationjetstreamv1beta2.ObjectStoreApplyConfiguration, opts v1.ApplyOptions) (result *jetstreamv1beta2.ObjectStore, err error) + // Add a +genclient:noStatus comment above the type to avoid generating ApplyStatus(). + ApplyStatus(ctx context.Context, objectStore *applyconfigurationjetstreamv1beta2.ObjectStoreApplyConfiguration, opts v1.ApplyOptions) (result *jetstreamv1beta2.ObjectStore, err error) + ObjectStoreExpansion +} + +// objectStores implements ObjectStoreInterface +type objectStores struct { + *gentype.ClientWithListAndApply[*jetstreamv1beta2.ObjectStore, *jetstreamv1beta2.ObjectStoreList, *applyconfigurationjetstreamv1beta2.ObjectStoreApplyConfiguration] +} + +// newObjectStores returns a ObjectStores +func newObjectStores(c *JetstreamV1beta2Client, namespace string) *objectStores { + return &objectStores{ + gentype.NewClientWithListAndApply[*jetstreamv1beta2.ObjectStore, *jetstreamv1beta2.ObjectStoreList, *applyconfigurationjetstreamv1beta2.ObjectStoreApplyConfiguration]( + "objectstores", + c.RESTClient(), + scheme.ParameterCodec, + namespace, + func() *jetstreamv1beta2.ObjectStore { return &jetstreamv1beta2.ObjectStore{} }, + func() *jetstreamv1beta2.ObjectStoreList { return &jetstreamv1beta2.ObjectStoreList{} }, + ), + } +} diff --git a/pkg/jetstream/generated/informers/externalversions/generic.go b/pkg/jetstream/generated/informers/externalversions/generic.go index b03765d4..9ef89d85 100644 --- a/pkg/jetstream/generated/informers/externalversions/generic.go +++ b/pkg/jetstream/generated/informers/externalversions/generic.go @@ -56,6 +56,8 @@ func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource return &genericInformer{resource: resource.GroupResource(), informer: f.Jetstream().V1beta2().Consumers().Informer()}, nil case v1beta2.SchemeGroupVersion.WithResource("keyvalues"): return &genericInformer{resource: resource.GroupResource(), informer: f.Jetstream().V1beta2().KeyValues().Informer()}, nil + case v1beta2.SchemeGroupVersion.WithResource("objectstores"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Jetstream().V1beta2().ObjectStores().Informer()}, nil case v1beta2.SchemeGroupVersion.WithResource("streams"): return &genericInformer{resource: resource.GroupResource(), informer: f.Jetstream().V1beta2().Streams().Informer()}, nil diff --git a/pkg/jetstream/generated/informers/externalversions/jetstream/v1beta2/interface.go b/pkg/jetstream/generated/informers/externalversions/jetstream/v1beta2/interface.go index 29a492fb..772f3ab7 100644 --- a/pkg/jetstream/generated/informers/externalversions/jetstream/v1beta2/interface.go +++ b/pkg/jetstream/generated/informers/externalversions/jetstream/v1beta2/interface.go @@ -27,6 +27,8 @@ type Interface interface { Consumers() ConsumerInformer // KeyValues returns a KeyValueInformer. KeyValues() KeyValueInformer + // ObjectStores returns a ObjectStoreInformer. + ObjectStores() ObjectStoreInformer // Streams returns a StreamInformer. Streams() StreamInformer } @@ -57,6 +59,11 @@ func (v *version) KeyValues() KeyValueInformer { return &keyValueInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} } +// ObjectStores returns a ObjectStoreInformer. +func (v *version) ObjectStores() ObjectStoreInformer { + return &objectStoreInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} +} + // Streams returns a StreamInformer. func (v *version) Streams() StreamInformer { return &streamInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} diff --git a/pkg/jetstream/generated/informers/externalversions/jetstream/v1beta2/objectstore.go b/pkg/jetstream/generated/informers/externalversions/jetstream/v1beta2/objectstore.go new file mode 100644 index 00000000..7fb5384e --- /dev/null +++ b/pkg/jetstream/generated/informers/externalversions/jetstream/v1beta2/objectstore.go @@ -0,0 +1,87 @@ +// Copyright 2025 The NATS Authors +// 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. + +// Code generated by informer-gen. DO NOT EDIT. + +package v1beta2 + +import ( + context "context" + time "time" + + apisjetstreamv1beta2 "github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2" + versioned "github.com/nats-io/nack/pkg/jetstream/generated/clientset/versioned" + internalinterfaces "github.com/nats-io/nack/pkg/jetstream/generated/informers/externalversions/internalinterfaces" + jetstreamv1beta2 "github.com/nats-io/nack/pkg/jetstream/generated/listers/jetstream/v1beta2" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + watch "k8s.io/apimachinery/pkg/watch" + cache "k8s.io/client-go/tools/cache" +) + +// ObjectStoreInformer provides access to a shared informer and lister for +// ObjectStores. +type ObjectStoreInformer interface { + Informer() cache.SharedIndexInformer + Lister() jetstreamv1beta2.ObjectStoreLister +} + +type objectStoreInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string +} + +// NewObjectStoreInformer constructs a new informer for ObjectStore type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewObjectStoreInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredObjectStoreInformer(client, namespace, resyncPeriod, indexers, nil) +} + +// NewFilteredObjectStoreInformer constructs a new informer for ObjectStore type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewFilteredObjectStoreInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { + return cache.NewSharedIndexInformer( + &cache.ListWatch{ + ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.JetstreamV1beta2().ObjectStores(namespace).List(context.TODO(), options) + }, + WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.JetstreamV1beta2().ObjectStores(namespace).Watch(context.TODO(), options) + }, + }, + &apisjetstreamv1beta2.ObjectStore{}, + resyncPeriod, + indexers, + ) +} + +func (f *objectStoreInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredObjectStoreInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) +} + +func (f *objectStoreInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&apisjetstreamv1beta2.ObjectStore{}, f.defaultInformer) +} + +func (f *objectStoreInformer) Lister() jetstreamv1beta2.ObjectStoreLister { + return jetstreamv1beta2.NewObjectStoreLister(f.Informer().GetIndexer()) +} diff --git a/pkg/jetstream/generated/listers/jetstream/v1beta2/expansion_generated.go b/pkg/jetstream/generated/listers/jetstream/v1beta2/expansion_generated.go index b49efd09..fc1de0d4 100644 --- a/pkg/jetstream/generated/listers/jetstream/v1beta2/expansion_generated.go +++ b/pkg/jetstream/generated/listers/jetstream/v1beta2/expansion_generated.go @@ -39,6 +39,14 @@ type KeyValueListerExpansion interface{} // KeyValueNamespaceLister. type KeyValueNamespaceListerExpansion interface{} +// ObjectStoreListerExpansion allows custom methods to be added to +// ObjectStoreLister. +type ObjectStoreListerExpansion interface{} + +// ObjectStoreNamespaceListerExpansion allows custom methods to be added to +// ObjectStoreNamespaceLister. +type ObjectStoreNamespaceListerExpansion interface{} + // StreamListerExpansion allows custom methods to be added to // StreamLister. type StreamListerExpansion interface{} diff --git a/pkg/jetstream/generated/listers/jetstream/v1beta2/objectstore.go b/pkg/jetstream/generated/listers/jetstream/v1beta2/objectstore.go new file mode 100644 index 00000000..332e256d --- /dev/null +++ b/pkg/jetstream/generated/listers/jetstream/v1beta2/objectstore.go @@ -0,0 +1,67 @@ +// Copyright 2025 The NATS Authors +// 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. + +// Code generated by lister-gen. DO NOT EDIT. + +package v1beta2 + +import ( + jetstreamv1beta2 "github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2" + labels "k8s.io/apimachinery/pkg/labels" + listers "k8s.io/client-go/listers" + cache "k8s.io/client-go/tools/cache" +) + +// ObjectStoreLister helps list ObjectStores. +// All objects returned here must be treated as read-only. +type ObjectStoreLister interface { + // List lists all ObjectStores in the indexer. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*jetstreamv1beta2.ObjectStore, err error) + // ObjectStores returns an object that can list and get ObjectStores. + ObjectStores(namespace string) ObjectStoreNamespaceLister + ObjectStoreListerExpansion +} + +// objectStoreLister implements the ObjectStoreLister interface. +type objectStoreLister struct { + listers.ResourceIndexer[*jetstreamv1beta2.ObjectStore] +} + +// NewObjectStoreLister returns a new ObjectStoreLister. +func NewObjectStoreLister(indexer cache.Indexer) ObjectStoreLister { + return &objectStoreLister{listers.New[*jetstreamv1beta2.ObjectStore](indexer, jetstreamv1beta2.Resource("objectstore"))} +} + +// ObjectStores returns an object that can list and get ObjectStores. +func (s *objectStoreLister) ObjectStores(namespace string) ObjectStoreNamespaceLister { + return objectStoreNamespaceLister{listers.NewNamespaced[*jetstreamv1beta2.ObjectStore](s.ResourceIndexer, namespace)} +} + +// ObjectStoreNamespaceLister helps list and get ObjectStores. +// All objects returned here must be treated as read-only. +type ObjectStoreNamespaceLister interface { + // List lists all ObjectStores in the indexer for a given namespace. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*jetstreamv1beta2.ObjectStore, err error) + // Get retrieves the ObjectStore from the indexer for a given namespace and name. + // Objects returned here must be treated as read-only. + Get(name string) (*jetstreamv1beta2.ObjectStore, error) + ObjectStoreNamespaceListerExpansion +} + +// objectStoreNamespaceLister implements the ObjectStoreNamespaceLister +// interface. +type objectStoreNamespaceLister struct { + listers.ResourceIndexer[*jetstreamv1beta2.ObjectStore] +} From f15f695b0ec1bcf79b5783e0bb203fa18ad5e29b Mon Sep 17 00:00:00 2001 From: Samuel Attwood Date: Tue, 7 Jan 2025 04:49:12 -0500 Subject: [PATCH 07/19] Add ObjectStore tests and remaining options --- deploy/crds.yml | 180 ++++- ...v_controller.go => keyvalue_controller.go} | 8 +- ...er_test.go => keyvalue_controller_test.go} | 12 +- ...ontroller.go => objectstore_controller.go} | 14 +- .../controller/objectstore_controller_test.go | 628 ++++++++++++++++++ internal/controller/stream_controller.go | 20 +- internal/controller/stream_controller_test.go | 2 +- internal/controller/types.go | 10 +- .../v1beta2/{kvtypes.go => keyvaluetypes.go} | 0 .../{objecttypes.go => objectstoretypes.go} | 0 10 files changed, 823 insertions(+), 51 deletions(-) rename internal/controller/{kv_controller.go => keyvalue_controller.go} (97%) rename internal/controller/{kv_controller_test.go => keyvalue_controller_test.go} (98%) rename internal/controller/{object_controller.go => objectstore_controller.go} (95%) create mode 100644 internal/controller/objectstore_controller_test.go rename pkg/jetstream/apis/jetstream/v1beta2/{kvtypes.go => keyvaluetypes.go} (100%) rename pkg/jetstream/apis/jetstream/v1beta2/{objecttypes.go => objectstoretypes.go} (100%) diff --git a/deploy/crds.yml b/deploy/crds.yml index e179785a..17cdc203 100644 --- a/deploy/crds.yml +++ b/deploy/crds.yml @@ -1087,10 +1087,10 @@ spec: type: object properties: bucket: - description: A unique name for the KV Bucket. + description: A unique name for the KV Store. type: string description: - description: The description of the KV Bucket. + description: The description of the KV Store. type: string maxValueSize: description: The maximum size of a value in bytes. @@ -1102,21 +1102,21 @@ spec: description: The time expiry for keys. type: string maxBytes: - description: The maximum size of the KV Bucket in bytes. + description: The maximum size of the KV Store in bytes. type: integer storage: - description: The storage backend to use for the KV Bucket. + description: The storage backend to use for the KV Store. type: string enum: - file - memory replicas: - description: The number of replicas to keep for the KV Bucket in clustered JetStream. + description: The number of replicas to keep for the KV Store in clustered JetStream. type: integer minimum: 1 maximum: 5 placement: - description: The KV Bucket placement via tags or cluster name. + description: The KV Store placement via tags or cluster name. type: object properties: cluster: @@ -1126,7 +1126,7 @@ spec: items: type: string republish: - description: Republish configuration for the KV Bucket. + description: Republish configuration for the KV Store. type: object properties: destination: @@ -1136,7 +1136,7 @@ spec: type: string description: Messages will be published from this subject to the destination subject. mirror: - description: A KV Bucket mirror. + description: A KV Store mirror. type: object properties: name: @@ -1166,10 +1166,10 @@ spec: description: Destination subject. type: string compression: - description: KV Bucket compression. + description: KV Store compression. type: boolean sources: - description: A KV Bucket's sources. + description: A KV Store's sources. type: array items: type: object @@ -1200,12 +1200,10 @@ spec: dest: description: Destination subject. type: string - servers: - description: A list of servers for creating stream - type: array - items: - type: string - default: [] + account: + description: Name of the account to which the Stream belongs. + type: string + pattern: '^[^.*>]*$' creds: description: NATS user credentials for connecting to servers. Please make sure your controller has mounted the cerds on its path. type: string @@ -1214,6 +1212,20 @@ spec: description: NATS user NKey for connecting to servers. type: string default: '' + preventDelete: + description: When true, the managed KV Store will not be deleted when the resource is deleted + type: boolean + default: false + preventUpdate: + description: When true, the managed KV Store will not be updated when the resource is updated + type: boolean + default: false + servers: + description: A list of servers for creating the KV Store + type: array + items: + type: string + default: [] tls: description: A client's TLS certs and keys. type: object @@ -1229,18 +1241,142 @@ spec: type: array items: type: string + status: + type: object + properties: + observedGeneration: + type: integer + conditions: + type: array + items: + type: object + properties: + type: + type: string + status: + type: string + lastTransitionTime: + type: string + reason: + type: string + message: + type: string + additionalPrinterColumns: + - name: State + type: string + description: The current state of the KV Store. + jsonPath: .status.conditions[?(@.type == 'Ready')].reason + - name: KV Store Name + type: string + description: The name of the KV Store. + jsonPath: .spec.name +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: objectstores.jetstream.nats.io +spec: + group: jetstream.nats.io + scope: Namespaced + names: + kind: ObjectStore + singular: objectstore + plural: objectstores + versions: + - name: v1beta2 + served: true + storage: true + subresources: + status: {} + schema: + openAPIV3Schema: + type: object + properties: + spec: + type: object + properties: + bucket: + description: A unique name for the Object Store. + type: string + description: + description: The description of the Object Store. + type: string + ttl: + description: The time expiry for keys. + type: string + maxBytes: + description: The maximum size of the Store in bytes. + type: integer + storage: + description: The storage backend to use for the Object Store. + type: string + enum: + - file + - memory + replicas: + description: The number of replicas to keep for the Object Store in clustered JetStream. + type: integer + minimum: 1 + maximum: 5 + placement: + description: The Object Store placement via tags or cluster name. + type: object + properties: + cluster: + type: string + tags: + type: array + items: + type: string + compression: + description: Object Store compression. + type: boolean + metadata: + description: Additional Object Store metadata. + type: object + additionalProperties: + type: string account: - description: Name of the account to which the Stream belongs. + description: Name of the account to which the Object Store belongs. type: string pattern: '^[^.*>]*$' + creds: + description: NATS user credentials for connecting to servers. Please make sure your controller has mounted the cerds on its path. + type: string + default: '' + nkey: + description: NATS user NKey for connecting to servers. + type: string + default: '' preventDelete: - description: When true, the managed Stream will not be deleted when the resource is deleted + description: When true, the managed Object Store will not be deleted when the resource is deleted type: boolean default: false preventUpdate: - description: When true, the managed Stream will not be updated when the resource is updated + description: When true, the managed Object Store will not be updated when the resource is updated type: boolean default: false + servers: + description: A list of servers for creating the Object Store + type: array + items: + type: string + default: [] + tls: + description: A client's TLS certs and keys. + type: object + properties: + clientCert: + description: A client's cert filepath. Should be mounted. + type: string + clientKey: + description: A client's key filepath. Should be mounted. + type: string + rootCas: + description: A list of filepaths to CAs. Should be mounted. + type: array + items: + type: string status: type: object properties: @@ -1264,9 +1400,9 @@ spec: additionalPrinterColumns: - name: State type: string - description: The current state of the KV Bucket. + description: The current state of the Object Store. jsonPath: .status.conditions[?(@.type == 'Ready')].reason - - name: KV Bucket Name + - name: Object Store Name type: string - description: The name of the KV Bucket. + description: The name of the Object Store. jsonPath: .spec.name \ No newline at end of file diff --git a/internal/controller/kv_controller.go b/internal/controller/keyvalue_controller.go similarity index 97% rename from internal/controller/kv_controller.go rename to internal/controller/keyvalue_controller.go index 5f10b967..e174ee0c 100644 --- a/internal/controller/kv_controller.go +++ b/internal/controller/keyvalue_controller.go @@ -81,9 +81,9 @@ func (r *KeyValueReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c } // Add finalizer - if !controllerutil.ContainsFinalizer(keyValue, kvFinalizer) { + if !controllerutil.ContainsFinalizer(keyValue, keyValueFinalizer) { log.Info("Adding KeyValue finalizer.") - if ok := controllerutil.AddFinalizer(keyValue, kvFinalizer); !ok { + if ok := controllerutil.AddFinalizer(keyValue, keyValueFinalizer); !ok { return ctrl.Result{}, errors.New("failed to add finalizer to keyvalue resource") } @@ -96,7 +96,7 @@ func (r *KeyValueReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c // Check Deletion markedForDeletion := keyValue.GetDeletionTimestamp() != nil if markedForDeletion { - if controllerutil.ContainsFinalizer(keyValue, kvFinalizer) { + if controllerutil.ContainsFinalizer(keyValue, keyValueFinalizer) { err := r.deleteKeyValue(ctx, log, keyValue) if err != nil { return ctrl.Result{}, fmt.Errorf("delete keyvalue: %w", err) @@ -140,7 +140,7 @@ func (r *KeyValueReconciler) deleteKeyValue(ctx context.Context, log logr.Logger } log.Info("Removing KeyValue finalizer.") - if ok := controllerutil.RemoveFinalizer(keyValue, kvFinalizer); !ok { + if ok := controllerutil.RemoveFinalizer(keyValue, keyValueFinalizer); !ok { return errors.New("failed to remove keyvalue finalizer") } if err := r.Update(ctx, keyValue); err != nil { diff --git a/internal/controller/kv_controller_test.go b/internal/controller/keyvalue_controller_test.go similarity index 98% rename from internal/controller/kv_controller_test.go rename to internal/controller/keyvalue_controller_test.go index 8131fe72..0333f9f1 100644 --- a/internal/controller/kv_controller_test.go +++ b/internal/controller/keyvalue_controller_test.go @@ -97,9 +97,9 @@ var _ = Describe("KeyValue Controller", func() { if err != nil { Expect(err).To(MatchError(k8serrors.IsNotFound, "Is not found")) } else { - if controllerutil.ContainsFinalizer(resource, kvFinalizer) { + if controllerutil.ContainsFinalizer(resource, keyValueFinalizer) { By("removing the finalizer") - controllerutil.RemoveFinalizer(resource, kvFinalizer) + controllerutil.RemoveFinalizer(resource, keyValueFinalizer) Expect(k8sClient.Update(ctx, resource)).To(Succeed()) } @@ -145,7 +145,7 @@ var _ = Describe("KeyValue Controller", func() { return got }).WithContext(ctx). Should(SatisfyAll( - HaveField("Finalizers", HaveExactElements(kvFinalizer)), + HaveField("Finalizers", HaveExactElements(keyValueFinalizer)), HaveField("Status.Conditions", Not(BeEmpty())), )) @@ -163,7 +163,7 @@ var _ = Describe("KeyValue Controller", func() { By("initializing the keyvalue resource") By("setting the finalizer") - Expect(controllerutil.AddFinalizer(keyValue, kvFinalizer)).To(BeTrue()) + Expect(controllerutil.AddFinalizer(keyValue, keyValueFinalizer)).To(BeTrue()) Expect(k8sClient.Update(ctx, keyValue)).To(Succeed()) By("setting an unknown ready state") @@ -519,7 +519,7 @@ var _ = Describe("KeyValue Controller", func() { By("checking that the finalizer is not removed") Expect(k8sClient.Get(ctx, typeNamespacedName, keyValue)).To(Succeed()) - Expect(keyValue.Finalizers).To(ContainElement(kvFinalizer)) + Expect(keyValue.Finalizers).To(ContainElement(keyValueFinalizer)) }) }) }) @@ -572,7 +572,7 @@ func Test_mapKVSpecToConfig(t *testing.T) { wantErr bool }{ { - name: "emtpy spec", + name: "empty spec", spec: &api.KeyValueSpec{}, want: jetstream.KeyValueConfig{}, wantErr: false, diff --git a/internal/controller/object_controller.go b/internal/controller/objectstore_controller.go similarity index 95% rename from internal/controller/object_controller.go rename to internal/controller/objectstore_controller.go index 89482a87..1d32f6f6 100644 --- a/internal/controller/object_controller.go +++ b/internal/controller/objectstore_controller.go @@ -81,9 +81,9 @@ func (r *ObjectStoreReconciler) Reconcile(ctx context.Context, req ctrl.Request) } // Add finalizer - if !controllerutil.ContainsFinalizer(objectStore, objectFinalizer) { + if !controllerutil.ContainsFinalizer(objectStore, objectStoreFinalizer) { log.Info("Adding ObjectStore finalizer.") - if ok := controllerutil.AddFinalizer(objectStore, objectFinalizer); !ok { + if ok := controllerutil.AddFinalizer(objectStore, objectStoreFinalizer); !ok { return ctrl.Result{}, errors.New("failed to add finalizer to objectstore resource") } @@ -96,7 +96,7 @@ func (r *ObjectStoreReconciler) Reconcile(ctx context.Context, req ctrl.Request) // Check Deletion markedForDeletion := objectStore.GetDeletionTimestamp() != nil if markedForDeletion { - if controllerutil.ContainsFinalizer(objectStore, objectFinalizer) { + if controllerutil.ContainsFinalizer(objectStore, objectStoreFinalizer) { err := r.deleteObjectStore(ctx, log, objectStore) if err != nil { return ctrl.Result{}, fmt.Errorf("delete objectstore: %w", err) @@ -127,7 +127,8 @@ func (r *ObjectStoreReconciler) deleteObjectStore(ctx context.Context, log logr. err := r.WithJetStreamClient(objectStoreConnOpts(objectStore.Spec), func(js jetstream.JetStream) error { return js.DeleteObjectStore(ctx, objectStore.Spec.Bucket) }) - if errors.Is(err, jetstream.ErrBucketNotFound) { + // FIX: ErrStreamNotFound -> ErrBucketNotFound once nats.go is corrected + if errors.Is(err, jetstream.ErrStreamNotFound) { log.Info("ObjectStore does not exist, unable to delete.", "objectStoreName", objectStore.Spec.Bucket) } else if err != nil { return fmt.Errorf("delete objectstore during finalization: %w", err) @@ -140,7 +141,7 @@ func (r *ObjectStoreReconciler) deleteObjectStore(ctx context.Context, log logr. } log.Info("Removing ObjectStore finalizer.") - if ok := controllerutil.RemoveFinalizer(objectStore, objectFinalizer); !ok { + if ok := controllerutil.RemoveFinalizer(objectStore, objectStoreFinalizer); !ok { return errors.New("failed to remove objectstore finalizer") } if err := r.Update(ctx, objectStore); err != nil { @@ -235,10 +236,11 @@ func objectStoreSpecToConfig(spec *api.ObjectStoreSpec) (jetstream.ObjectStoreCo // Set directly mapped fields config := jetstream.ObjectStoreConfig{ Bucket: spec.Bucket, - Compression: spec.Compression, Description: spec.Description, MaxBytes: int64(spec.MaxBytes), Replicas: spec.Replicas, + Compression: spec.Compression, + Metadata: spec.Metadata, } // TTL diff --git a/internal/controller/objectstore_controller_test.go b/internal/controller/objectstore_controller_test.go new file mode 100644 index 00000000..a2ecabf1 --- /dev/null +++ b/internal/controller/objectstore_controller_test.go @@ -0,0 +1,628 @@ +/* +Copyright 2024. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package controller + +import ( + "testing" + "time" + + "github.com/nats-io/nats.go/jetstream" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "github.com/stretchr/testify/assert" + v1 "k8s.io/api/core/v1" + k8serrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/types" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + api "github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2" +) + +var _ = Describe("ObjectStore Controller", func() { + // The test objectStore resource + const resourceName = "test-objectstore" + const objectStoreName = "orders" + typeNamespacedName := types.NamespacedName{ + Name: resourceName, + Namespace: "default", + } + objectStore := &api.ObjectStore{} + + // The tested controller + var controller *ObjectStoreReconciler + + // Config to create minimal nats ObjectStore store + emptyObjectStoreConfig := jetstream.ObjectStoreConfig{ + Bucket: objectStoreName, + Replicas: 1, + Storage: jetstream.FileStorage, + } + + BeforeEach(func(ctx SpecContext) { + By("creating a test objectstore resource") + err := k8sClient.Get(ctx, typeNamespacedName, objectStore) + if err != nil && k8serrors.IsNotFound(err) { + resource := &api.ObjectStore{ + ObjectMeta: metav1.ObjectMeta{ + Name: resourceName, + Namespace: "default", + }, + Spec: api.ObjectStoreSpec{ + Bucket: objectStoreName, + Replicas: 1, + TTL: "5m", + Compression: true, + Description: "test objectstore", + Storage: "file", + }, + } + Expect(k8sClient.Create(ctx, resource)).To(Succeed()) + // Re-fetch ObjectStore + Expect(k8sClient.Get(ctx, typeNamespacedName, objectStore)).To(Succeed()) + } + + By("checking precondition: nats objectstore does not exist") + _, err = jsClient.ObjectStore(ctx, objectStoreName) + Expect(err).To(MatchError(jetstream.ErrBucketNotFound)) + + By("setting up the tested controller") + controller = &ObjectStoreReconciler{ + baseController, + } + }) + + AfterEach(func(ctx SpecContext) { + By("removing the test objectstore resource") + resource := &api.ObjectStore{} + err := k8sClient.Get(ctx, typeNamespacedName, resource) + if err != nil { + Expect(err).To(MatchError(k8serrors.IsNotFound, "Is not found")) + } else { + if controllerutil.ContainsFinalizer(resource, objectStoreFinalizer) { + By("removing the finalizer") + controllerutil.RemoveFinalizer(resource, objectStoreFinalizer) + Expect(k8sClient.Update(ctx, resource)).To(Succeed()) + } + + By("removing the objectstore resource") + Expect(k8sClient.Delete(ctx, resource)). + To(SatisfyAny( + Succeed(), + MatchError(k8serrors.IsNotFound, "is not found"), + )) + } + + By("deleting the nats objectstore store") + Expect(jsClient.DeleteObjectStore(ctx, objectStoreName)). + To(SatisfyAny( + Succeed(), + MatchError(jetstream.ErrStreamNotFound), + )) + }) + + When("reconciling a not existing resource", func() { + It("should stop reconciliation without error", func(ctx SpecContext) { + By("reconciling the created resource") + result, err := controller.Reconcile(ctx, reconcile.Request{ + NamespacedName: types.NamespacedName{ + Namespace: "fake", + Name: "not-existing", + }, + }) + Expect(err).NotTo(HaveOccurred()) + Expect(result).To(Equal(ctrl.Result{})) + }) + }) + + When("reconciling a not initialized resource", func() { + It("should initialize a new resource", func(ctx SpecContext) { + By("re-queueing until it is initialized") + // Initialization can require multiple reconciliation loops + Eventually(func(ctx SpecContext) *api.ObjectStore { + _, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName}) + Expect(err).NotTo(HaveOccurred()) + got := &api.ObjectStore{} + Expect(k8sClient.Get(ctx, typeNamespacedName, got)).To(Succeed()) + return got + }).WithContext(ctx). + Should(SatisfyAll( + HaveField("Status.Conditions", Not(BeEmpty())), + )) + + By("validating the ready condition") + // Fetch ObjectStore + Expect(k8sClient.Get(ctx, typeNamespacedName, objectStore)).To(Succeed()) + Expect(objectStore.Status.Conditions).To(HaveLen(1)) + + assertReadyStateMatches(objectStore.Status.Conditions[0], v1.ConditionUnknown, "Reconciling", "Starting reconciliation", time.Now()) + }) + }) + + When("reconciling an initialized resource", func() { + BeforeEach(func(ctx SpecContext) { + By("initializing the objectstore resource") + + By("setting the finalizer") + Expect(controllerutil.AddFinalizer(objectStore, objectStoreFinalizer)).To(BeTrue()) + Expect(k8sClient.Update(ctx, objectStore)).To(Succeed()) + + By("setting an unknown ready state") + objectStore.Status.Conditions = []api.Condition{{ + Type: readyCondType, + Status: v1.ConditionUnknown, + Reason: "Test", + Message: "start condition", + LastTransitionTime: time.Now().Format(time.RFC3339Nano), + }} + Expect(k8sClient.Status().Update(ctx, objectStore)).To(Succeed()) + }) + + It("should create a new objectstore store", func(ctx SpecContext) { + By("running Reconcile") + result, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName}) + Expect(err).NotTo(HaveOccurred()) + Expect(result.IsZero()).To(BeTrue()) + + // Fetch resource + Expect(k8sClient.Get(ctx, typeNamespacedName, objectStore)).To(Succeed()) + + By("checking if the ready state was updated") + Expect(objectStore.Status.Conditions).To(HaveLen(1)) + assertReadyStateMatches(objectStore.Status.Conditions[0], v1.ConditionTrue, "Reconciling", "created or updated", time.Now()) + + By("checking if the observed generation matches") + Expect(objectStore.Status.ObservedGeneration).To(Equal(objectStore.Generation)) + + By("checking if the objectstore store was created") + natsObjectStore, err := jsClient.ObjectStore(ctx, objectStoreName) + Expect(err).NotTo(HaveOccurred()) + objectstoreStatus, err := natsObjectStore.Status(ctx) + Expect(err).NotTo(HaveOccurred()) + Expect(objectstoreStatus.Bucket()).To(Equal(objectStoreName)) + Expect(objectstoreStatus.TTL()).To(Equal(5 * time.Minute)) + Expect(objectstoreStatus.IsCompressed()).To(BeTrue()) + }) + + When("PreventUpdate is set", func() { + BeforeEach(func(ctx SpecContext) { + By("setting preventDelete on the resource") + objectStore.Spec.PreventUpdate = true + Expect(k8sClient.Update(ctx, objectStore)).To(Succeed()) + }) + It("should create the objectstore", func(ctx SpecContext) { + By("running Reconcile") + result, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName}) + Expect(err).NotTo(HaveOccurred()) + Expect(result.IsZero()).To(BeTrue()) + + By("checking that objectstore was created") + _, err = jsClient.ObjectStore(ctx, objectStoreName) + Expect(err).NotTo(HaveOccurred()) + }) + It("should not update the objectstore", func(ctx SpecContext) { + By("creating the objectstore") + _, err := jsClient.CreateObjectStore(ctx, emptyObjectStoreConfig) + Expect(err).NotTo(HaveOccurred()) + + By("running Reconcile") + result, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName}) + Expect(err).NotTo(HaveOccurred()) + Expect(result.IsZero()).To(BeTrue()) + + By("checking that objectstore was not updated") + natsObjectStore, err := jsClient.ObjectStore(ctx, objectStoreName) + Expect(err).NotTo(HaveOccurred()) + s, err := natsObjectStore.Status(ctx) + Expect(err).NotTo(HaveOccurred()) + Expect(s.IsCompressed()).To(BeFalse()) + }) + }) + + When("read-only mode is enabled", func() { + BeforeEach(func(ctx SpecContext) { + By("setting read only on the controller") + readOnly, err := NewJSController(k8sClient, &NatsConfig{ServerURL: testServer.ClientURL()}, &Config{ReadOnly: true}) + Expect(err).NotTo(HaveOccurred()) + controller = &ObjectStoreReconciler{ + JetStreamController: readOnly, + } + }) + + It("should not create the objectstore", func(ctx SpecContext) { + By("running Reconcile") + result, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName}) + Expect(err).NotTo(HaveOccurred()) + Expect(result.IsZero()).To(BeTrue()) + + By("checking that no objectstore was created") + _, err = jsClient.ObjectStore(ctx, objectStoreName) + Expect(err).To(MatchError(jetstream.ErrBucketNotFound)) + }) + It("should not update the objectstore", func(ctx SpecContext) { + By("creating the objectstore") + _, err := jsClient.CreateObjectStore(ctx, emptyObjectStoreConfig) + Expect(err).NotTo(HaveOccurred()) + + By("running Reconcile") + result, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName}) + Expect(err).NotTo(HaveOccurred()) + Expect(result.IsZero()).To(BeTrue()) + + By("checking that objectstore was not updated") + natsObjectStore, err := jsClient.ObjectStore(ctx, objectStoreName) + Expect(err).NotTo(HaveOccurred()) + s, err := natsObjectStore.Status(ctx) + Expect(err).NotTo(HaveOccurred()) + Expect(s.IsCompressed()).To(BeFalse()) + }) + }) + + When("namespace restriction is enabled", func() { + BeforeEach(func(ctx SpecContext) { + By("setting a namespace on the resource") + namespaced, err := NewJSController(k8sClient, &NatsConfig{ServerURL: testServer.ClientURL()}, &Config{Namespace: "other-namespace"}) + Expect(err).NotTo(HaveOccurred()) + controller = &ObjectStoreReconciler{ + JetStreamController: namespaced, + } + }) + + It("should not create the objectstore", func(ctx SpecContext) { + By("running Reconcile") + result, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName}) + Expect(err).NotTo(HaveOccurred()) + Expect(result.IsZero()).To(BeTrue()) + + By("checking that no objectstore was created") + _, err = jsClient.ObjectStore(ctx, objectStoreName) + Expect(err).To(MatchError(jetstream.ErrBucketNotFound)) + }) + It("should not update the objectstore", func(ctx SpecContext) { + By("creating the objectstore") + _, err := jsClient.CreateObjectStore(ctx, emptyObjectStoreConfig) + Expect(err).NotTo(HaveOccurred()) + + By("running Reconcile") + result, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName}) + Expect(err).NotTo(HaveOccurred()) + Expect(result.IsZero()).To(BeTrue()) + + By("checking that objectstore was not updated") + natsObjectStore, err := jsClient.ObjectStore(ctx, objectStoreName) + Expect(err).NotTo(HaveOccurred()) + s, err := natsObjectStore.Status(ctx) + Expect(err).NotTo(HaveOccurred()) + Expect(s.IsCompressed()).To(BeFalse()) + }) + }) + + It("should update an existing objectstore", func(ctx SpecContext) { + By("reconciling once to create the objectstore") + result, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName}) + Expect(err).NotTo(HaveOccurred()) + Expect(result.IsZero()).To(BeTrue()) + + // Fetch resource + Expect(k8sClient.Get(ctx, typeNamespacedName, objectStore)).To(Succeed()) + previousTransitionTime := objectStore.Status.Conditions[0].LastTransitionTime + + By("updating the resource") + objectStore.Spec.Description = "new description" + objectStore.Spec.TTL = "1h" + Expect(k8sClient.Update(ctx, objectStore)).To(Succeed()) + + By("reconciling the updated resource") + result, err = controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName}) + Expect(err).NotTo(HaveOccurred()) + Expect(result.IsZero()).To(BeTrue()) + + // Fetch resource + Expect(k8sClient.Get(ctx, typeNamespacedName, objectStore)).To(Succeed()) + + By("checking if the state transition time was not updated") + Expect(objectStore.Status.Conditions).To(HaveLen(1)) + Expect(objectStore.Status.Conditions[0].LastTransitionTime).To(Equal(previousTransitionTime)) + + By("checking if the observed generation matches") + Expect(objectStore.Status.ObservedGeneration).To(Equal(objectStore.Generation)) + + By("checking if the objectstore was updated") + natsObjectStore, err := jsClient.ObjectStore(ctx, objectStoreName) + Expect(err).NotTo(HaveOccurred()) + + objectStoreStatus, err := natsObjectStore.Status(ctx) + Expect(err).NotTo(HaveOccurred()) + Expect(objectStoreStatus.Bucket()).To(Equal(objectStoreName)) + Expect(objectStoreStatus.TTL()).To(Equal(1 * time.Hour)) + Expect(objectStoreStatus.IsCompressed()).To(BeTrue()) + }) + + It("should set an error state when the nats server is not available", func(ctx SpecContext) { + By("setting up controller with unavailable nats server") + // Setup client for not running server + // Use actual test server to ensure port not used by other service on test instance + sv := CreateTestServer() + base, err := NewJSController(k8sClient, &NatsConfig{ServerURL: sv.ClientURL()}, &Config{}) + Expect(err).NotTo(HaveOccurred()) + sv.Shutdown() + + controller := &ObjectStoreReconciler{ + base, + } + + By("reconciling resource") + result, err := controller.Reconcile(ctx, reconcile.Request{ + NamespacedName: typeNamespacedName, + }) + Expect(result).To(Equal(ctrl.Result{})) + Expect(err).To(HaveOccurred()) // Will be re-queued with back-off + + // Fetch resource + err = k8sClient.Get(ctx, typeNamespacedName, objectStore) + Expect(err).NotTo(HaveOccurred()) + + By("checking if the status was updated") + Expect(objectStore.Status.Conditions).To(HaveLen(1)) + assertReadyStateMatches( + objectStore.Status.Conditions[0], + v1.ConditionFalse, + "Errored", + "create or update objectstore:", + time.Now(), + ) + + By("checking if the observed generation does not match") + Expect(objectStore.Status.ObservedGeneration).ToNot(Equal(objectStore.Generation)) + }) + + When("the resource is marked for deletion", func() { + BeforeEach(func(ctx SpecContext) { + By("marking the resource for deletion") + Expect(k8sClient.Delete(ctx, objectStore)).To(Succeed()) + Expect(k8sClient.Get(ctx, typeNamespacedName, objectStore)).To(Succeed()) // re-fetch after update + }) + + It("should succeed deleting a not existing objectstore", func(ctx SpecContext) { + By("reconciling") + result, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName}) + Expect(err).NotTo(HaveOccurred()) + Expect(result.IsZero()).To(BeTrue()) + + By("checking that the resource is deleted") + Eventually(k8sClient.Get). + WithArguments(ctx, typeNamespacedName, objectStore). + ShouldNot(Succeed()) + }) + + When("the underlying objectstore exists", func() { + BeforeEach(func(ctx SpecContext) { + By("creating the objectstore on the nats server") + _, err := jsClient.CreateObjectStore(ctx, emptyObjectStoreConfig) + Expect(err).NotTo(HaveOccurred()) + }) + + AfterEach(func(ctx SpecContext) { + err := jsClient.DeleteObjectStore(ctx, objectStoreName) + if err != nil { + Expect(err).To(MatchError(jetstream.ErrStreamNotFound)) + } + }) + + It("should delete the objectstore", func(ctx SpecContext) { + By("reconciling") + result, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName}) + Expect(err).NotTo(HaveOccurred()) + Expect(result.IsZero()).To(BeTrue()) + + By("checking that the objectstore is deleted") + _, err = jsClient.ObjectStore(ctx, objectStoreName) + Expect(err).To(MatchError(jetstream.ErrBucketNotFound)) + + By("checking that the resource is deleted") + Eventually(k8sClient.Get). + WithArguments(ctx, typeNamespacedName, objectStore). + ShouldNot(Succeed()) + }) + + When("PreventDelete is set", func() { + BeforeEach(func(ctx SpecContext) { + By("setting preventDelete on the resource") + objectStore.Spec.PreventDelete = true + Expect(k8sClient.Update(ctx, objectStore)).To(Succeed()) + }) + It("Should delete the resource and not delete the nats objectstore", func(ctx SpecContext) { + By("reconciling") + result, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName}) + Expect(err).NotTo(HaveOccurred()) + Expect(result.IsZero()).To(BeTrue()) + + By("checking that the objectstore is not deleted") + _, err = jsClient.ObjectStore(ctx, objectStoreName) + Expect(err).NotTo(HaveOccurred()) + + By("checking that the resource is deleted") + Eventually(k8sClient.Get). + WithArguments(ctx, typeNamespacedName, objectStore). + ShouldNot(Succeed()) + }) + }) + + When("read only is set", func() { + BeforeEach(func(ctx SpecContext) { + By("setting read only on the controller") + readOnly, err := NewJSController(k8sClient, &NatsConfig{ServerURL: testServer.ClientURL()}, &Config{ReadOnly: true}) + Expect(err).NotTo(HaveOccurred()) + controller = &ObjectStoreReconciler{ + JetStreamController: readOnly, + } + }) + It("should delete the resource and not delete the objectstore", func(ctx SpecContext) { + By("reconciling") + result, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName}) + Expect(err).NotTo(HaveOccurred()) + Expect(result.IsZero()).To(BeTrue()) + + By("checking that the objectstore is not deleted") + _, err = jsClient.ObjectStore(ctx, objectStoreName) + Expect(err).NotTo(HaveOccurred()) + + By("checking that the resource is deleted") + Eventually(k8sClient.Get). + WithArguments(ctx, typeNamespacedName, objectStore). + ShouldNot(Succeed()) + }) + }) + + When("controller is restricted to different namespace", func() { + BeforeEach(func(ctx SpecContext) { + namespaced, err := NewJSController(k8sClient, &NatsConfig{ServerURL: testServer.ClientURL()}, &Config{Namespace: "other-namespace"}) + Expect(err).NotTo(HaveOccurred()) + controller = &ObjectStoreReconciler{ + JetStreamController: namespaced, + } + }) + It("should not delete the resource and objectstore", func(ctx SpecContext) { + By("reconciling") + result, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName}) + Expect(err).NotTo(HaveOccurred()) + Expect(result.IsZero()).To(BeTrue()) + + By("checking that the objectstore is not deleted") + _, err = jsClient.ObjectStore(ctx, objectStoreName) + Expect(err).NotTo(HaveOccurred()) + + By("checking that the finalizer is not removed") + Expect(k8sClient.Get(ctx, typeNamespacedName, objectStore)).To(Succeed()) + Expect(objectStore.Finalizers).To(ContainElement(objectStoreFinalizer)) + }) + }) + }) + }) + + It("should update objectstore on different server as specified in spec", func(ctx SpecContext) { + By("setting up the alternative server") + // Setup altClient for alternate server + altServer := CreateTestServer() + defer altServer.Shutdown() + + By("setting the server in the objectstore spec") + objectStore.Spec.Servers = []string{altServer.ClientURL()} + Expect(k8sClient.Update(ctx, objectStore)).To(Succeed()) + + By("checking precondition, that the objectstore does not yet exist") + _, err := jsClient.ObjectStore(ctx, objectStoreName) + Expect(err).To(MatchError(jetstream.ErrBucketNotFound)) + + By("reconciling the resource") + result, err := controller.Reconcile(ctx, reconcile.Request{ + NamespacedName: typeNamespacedName, + }) + Expect(err).NotTo(HaveOccurred()) + Expect(result.IsZero()).To(BeTrue()) + + By("checking if the objectstore was created on the alternative server") + altClient, closer, err := CreateJetStreamClient(&NatsConfig{ServerURL: altServer.ClientURL()}, true) + defer closer.Close() + Expect(err).NotTo(HaveOccurred()) + + _, err = altClient.ObjectStore(ctx, objectStoreName) + Expect(err).NotTo(HaveOccurred()) + + By("checking that the objectstore was NOT created on the original server") + _, err = jsClient.ObjectStore(ctx, objectStoreName) + Expect(err).To(MatchError(jetstream.ErrBucketNotFound)) + }) + }) +}) + +func Test_mapobjectstoreSpecToConfig(t *testing.T) { + tests := []struct { + name string + spec *api.ObjectStoreSpec + want jetstream.ObjectStoreConfig + wantErr bool + }{ + { + name: "empty spec", + spec: &api.ObjectStoreSpec{}, + want: jetstream.ObjectStoreConfig{}, + wantErr: false, + }, + { + name: "full spec", + spec: &api.ObjectStoreSpec{ + Description: "objectstore description", + MaxBytes: 1048576, + TTL: "1h", + Bucket: "objectstore-name", + Placement: &api.StreamPlacement{ + Cluster: "test-cluster", + Tags: []string{"tag"}, + }, + Replicas: 3, + Compression: true, + Storage: "memory", + Metadata: map[string]string{ + "foo": "bar", + }, + BaseStreamConfig: api.BaseStreamConfig{ + Account: "", + Creds: "", + PreventDelete: false, + PreventUpdate: false, + Nkey: "", + Servers: nil, + TLS: api.TLS{}, + }, + }, + want: jetstream.ObjectStoreConfig{ + Bucket: "objectstore-name", + Description: "objectstore description", + MaxBytes: 1048576, + TTL: time.Hour, + Storage: jetstream.MemoryStorage, + Replicas: 3, + Placement: &jetstream.Placement{ + Cluster: "test-cluster", + Tags: []string{"tag"}, + }, + Compression: true, + Metadata: map[string]string{ + "foo": "bar", + }, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert := assert.New(t) + got, err := objectStoreSpecToConfig(tt.spec) + if (err != nil) != tt.wantErr { + t.Errorf("objectStoreSpecToConfig() error = %v, wantErr %v", err, tt.wantErr) + return + } + + // Compare nested structs + assert.EqualValues(tt.want, got) + }) + } +} diff --git a/internal/controller/stream_controller.go b/internal/controller/stream_controller.go index 3f1b2f70..511b9e72 100644 --- a/internal/controller/stream_controller.go +++ b/internal/controller/stream_controller.go @@ -245,15 +245,14 @@ func streamSpecToConfig(spec *api.StreamSpec) (jetstream.StreamConfig, error) { MaxMsgSize: int32(spec.MaxMsgSize), Replicas: spec.Replicas, NoAck: spec.NoAck, + Sealed: spec.Sealed, DenyDelete: spec.DenyDelete, DenyPurge: spec.DenyPurge, AllowRollup: spec.AllowRollup, FirstSeq: spec.FirstSequence, AllowDirect: spec.AllowDirect, - // Explicitly set not (yet) mapped fields - Sealed: false, - MirrorDirect: false, - ConsumerLimits: jetstream.StreamConsumerLimits{}, + MirrorDirect: spec.MirrorDirect, + Metadata: spec.Metadata, } // Set not directly mapped fields @@ -354,9 +353,16 @@ func streamSpecToConfig(spec *api.StreamSpec) (jetstream.StreamConfig, error) { } } - // metadata - if spec.Metadata != nil { - config.Metadata = spec.Metadata + // consumerLimits + if spec.ConsumerLimits != nil { + inactiveThreshold, err := time.ParseDuration(spec.ConsumerLimits.InactiveThreshold) + if err != nil { + return jetstream.StreamConfig{}, fmt.Errorf("parse inactive threshold: %w", err) + } + config.ConsumerLimits = jetstream.StreamConsumerLimits{ + InactiveThreshold: inactiveThreshold, + MaxAckPending: spec.ConsumerLimits.MaxAckPending, + } } return config, nil diff --git a/internal/controller/stream_controller_test.go b/internal/controller/stream_controller_test.go index 79c83fbf..24731e0e 100644 --- a/internal/controller/stream_controller_test.go +++ b/internal/controller/stream_controller_test.go @@ -562,7 +562,7 @@ func Test_mapSpecToConfig(t *testing.T) { wantErr bool }{ { - name: "emtpy spec", + name: "empty spec", spec: &api.StreamSpec{}, want: jetstream.StreamConfig{}, wantErr: false, diff --git a/internal/controller/types.go b/internal/controller/types.go index 247f0304..bd4561de 100644 --- a/internal/controller/types.go +++ b/internal/controller/types.go @@ -1,9 +1,9 @@ package controller const ( - readyCondType = "Ready" - streamFinalizer = "stream.nats.io/finalizer" - kvFinalizer = "kv.nats.io/finalizer" - objectFinalizer = "object.nats.io/finalizer" - consumerFinalizer = "consumer.nats.io/finalizer" + readyCondType = "Ready" + streamFinalizer = "stream.nats.io/finalizer" + keyValueFinalizer = "kv.nats.io/finalizer" + objectStoreFinalizer = "objectstore.nats.io/finalizer" + consumerFinalizer = "consumer.nats.io/finalizer" ) diff --git a/pkg/jetstream/apis/jetstream/v1beta2/kvtypes.go b/pkg/jetstream/apis/jetstream/v1beta2/keyvaluetypes.go similarity index 100% rename from pkg/jetstream/apis/jetstream/v1beta2/kvtypes.go rename to pkg/jetstream/apis/jetstream/v1beta2/keyvaluetypes.go diff --git a/pkg/jetstream/apis/jetstream/v1beta2/objecttypes.go b/pkg/jetstream/apis/jetstream/v1beta2/objectstoretypes.go similarity index 100% rename from pkg/jetstream/apis/jetstream/v1beta2/objecttypes.go rename to pkg/jetstream/apis/jetstream/v1beta2/objectstoretypes.go From 726b0d5b089f095e371b08656e5eca450e041659 Mon Sep 17 00:00:00 2001 From: Samuel Attwood Date: Tue, 7 Jan 2025 06:23:47 -0500 Subject: [PATCH 08/19] Add test for sealed stream option --- internal/controller/stream_controller_test.go | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/internal/controller/stream_controller_test.go b/internal/controller/stream_controller_test.go index 24731e0e..8ba50b1b 100644 --- a/internal/controller/stream_controller_test.go +++ b/internal/controller/stream_controller_test.go @@ -205,6 +205,19 @@ var _ = Describe("Stream Controller", func() { Expect(streamInfo.Created).To(BeTemporally("~", time.Now(), time.Second)) }) + When("sealed is true", func() { + BeforeEach(func(ctx SpecContext) { + By("setting sealed to true") + stream.Spec.Name = "sealed" + stream.Spec.Sealed = true + Expect(k8sClient.Update(ctx, stream)).To(Succeed()) + }) + It("should not create the stream", func(ctx SpecContext) { + _, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName}) + Expect(err.Error()).To(HaveSuffix("can not be sealed")) + }) + }) + When("PreventUpdate is set", func() { BeforeEach(func(ctx SpecContext) { By("setting preventDelete on the resource") @@ -324,6 +337,7 @@ var _ = Describe("Stream Controller", func() { By("updating the resource") stream.Spec.Description = "new description" + stream.Spec.Sealed = true Expect(k8sClient.Update(ctx, stream)).To(Succeed()) By("reconciling the updated resource") @@ -348,6 +362,7 @@ var _ = Describe("Stream Controller", func() { streamInfo, err := natsStream.Info(ctx) Expect(err).NotTo(HaveOccurred()) Expect(streamInfo.Config.Description).To(Equal("new description")) + Expect(streamInfo.Config.Sealed).To(BeTrue()) // Other fields unchanged Expect(streamInfo.Config.Subjects).To(Equal([]string{"tests.*"})) }) From 180099abecbfdab2c421d55186f4fdd5b5ca284f Mon Sep 17 00:00:00 2001 From: Samuel Attwood <45669855+samuelattwood@users.noreply.github.com> Date: Thu, 16 Jan 2025 02:06:25 -0500 Subject: [PATCH 09/19] Add Account Controller (#224) * Add explicit Scheme field to Reconciler structs to better match convention * Update spec to assume consumer field 'Name' over deprecated 'DurableName' * Add account controller and support for account CRD auth config * Update CRDs. Slight change to connection options * Namespace and error handling improvements * Improve deleted log message * Namespace fix * Add check for removed Push Consumer options * Revert consumer name change * Add InactiveThreshold parsing * Fix test * Add Watch for Account resource changes to trigger reconcile of dependent resources. Improve connection opts handling * Add actual/desired state comparison for stream/consumer to avoid unnecessary update calls. Corrected ready state * Fix duplicate resource tracking * Improve config comparison logic * Add flags for sync interval and cache directory * Add back generation changed filter. Move finalizer add to after deletion check. * Rework Reconcile scheduled sync --- cmd/jetstream-controller/main.go | 25 +- deploy/crds.yml | 124 ++++--- internal/controller/account_controller.go | 191 ++++++++++- .../controller/account_controller_test.go | 171 +++++++-- internal/controller/client.go | 35 +- internal/controller/consumer_controller.go | 205 ++++++++--- .../controller/consumer_controller_test.go | 38 +- internal/controller/jetstream_controller.go | 220 +++++++++--- internal/controller/keyvalue_controller.go | 183 +++++++--- .../controller/keyvalue_controller_test.go | 38 +- internal/controller/objectstore_controller.go | 187 +++++++--- .../controller/objectstore_controller_test.go | 38 +- internal/controller/register.go | 32 +- internal/controller/stream_controller.go | 181 +++++++--- internal/controller/stream_controller_test.go | 38 +- internal/controller/suite_test.go | 8 +- internal/controller/types.go | 11 + .../apis/jetstream/v1beta2/consumertypes.go | 50 +-- pkg/jetstream/apis/jetstream/v1beta2/types.go | 23 +- .../v1beta2/zz_generated.deepcopy.go | 44 ++- .../jetstream/v1beta2/basestreamconfig.go | 51 +-- .../jetstream/v1beta2/connectionopts.go | 83 +++++ .../jetstream/v1beta2/consumerspec.go | 324 ++++++++---------- .../generated/applyconfiguration/utils.go | 2 + 24 files changed, 1571 insertions(+), 731 deletions(-) create mode 100644 pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/connectionopts.go diff --git a/cmd/jetstream-controller/main.go b/cmd/jetstream-controller/main.go index 32678f32..e58ff7ce 100644 --- a/cmd/jetstream-controller/main.go +++ b/cmd/jetstream-controller/main.go @@ -25,7 +25,7 @@ import ( "github.com/nats-io/nack/controllers/jetstream" "github.com/nats-io/nack/internal/controller" - jetstreamnatsiov1beta2 "github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2" + v1beta2 "github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2" clientset "github.com/nats-io/nack/pkg/jetstream/generated/clientset/versioned" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" @@ -67,10 +67,12 @@ func run() error { ca := flag.String("tlsca", "", "NATS TLS certificate authority chain") tlsfirst := flag.Bool("tlsfirst", false, "If enabled, forces explicit TLS without waiting for Server INFO") server := flag.String("s", "", "NATS Server URL") - crdConnect := flag.Bool("crd-connect", false, "If true, then NATS connections will be made from CRD config, not global config") + crdConnect := flag.Bool("crd-connect", false, "If true, then NATS connections will be made from CRD config, not global config. Ignored if running with control loop, CRD options will always override global config") cleanupPeriod := flag.Duration("cleanup-period", 30*time.Second, "Period to run object cleanup") readOnly := flag.Bool("read-only", false, "Starts the controller without causing changes to the NATS resources") + cacheDir := flag.String("cache-dir", "", "Directory to store cached credential and TLS files") controlLoop := flag.Bool("control-loop", false, "Experimental: Run controller with a full reconciliation control loop.") + controlLoopSyncInterval := flag.Duration("sync-interval", 5*time.Minute, "Interval to perform scheduled reconcile") flag.Parse() @@ -108,9 +110,12 @@ func run() error { } controllerCfg := &controller.Config{ - ReadOnly: *readOnly, - Namespace: *namespace, + ReadOnly: *readOnly, + Namespace: *namespace, + CacheDir: *cacheDir, + RequeueInterval: *controlLoopSyncInterval, } + return runControlLoop(config, natsCfg, controllerCfg) } @@ -160,17 +165,25 @@ func runControlLoop(config *rest.Config, natsCfg *controller.NatsConfig, control // Setup scheme scheme := runtime.NewScheme() utilruntime.Must(clientgoscheme.AddToScheme(scheme)) - utilruntime.Must(jetstreamnatsiov1beta2.AddToScheme(scheme)) + utilruntime.Must(v1beta2.AddToScheme(scheme)) mgr, err := ctrl.NewManager(config, ctrl.Options{ Scheme: scheme, Logger: klog.NewKlogr().WithName("controller-runtime"), - // TODO Add full configuration }) if err != nil { return fmt.Errorf("unable to start manager: %w", err) } + if controllerCfg.CacheDir == "" { + cacheDir, err := os.MkdirTemp(".", "nack") + if err != nil { + return fmt.Errorf("create cache dir: %w", err) + } + defer os.RemoveAll(cacheDir) + controllerCfg.CacheDir = cacheDir + } + err = controller.RegisterAll(mgr, natsCfg, controllerCfg) if err != nil { return fmt.Errorf("register jetstream controllers: %w", err) diff --git a/deploy/crds.yml b/deploy/crds.yml index 17cdc203..055c1dcb 100644 --- a/deploy/crds.yml +++ b/deploy/crds.yml @@ -287,6 +287,10 @@ spec: type: array items: type: string + tlsFirst: + description: When true, the KV Store will iniate TLS before server INFO. + type: boolean + default: false status: type: object properties: @@ -513,6 +517,11 @@ spec: spec: type: object properties: + durableName: + description: The name of the Consumer. + type: string + pattern: '^[^.*>]+$' + minLength: 1 streamName: description: The name of the Stream to create the Consumer in. type: string @@ -533,11 +542,6 @@ spec: optStartTime: description: Time format must be RFC3339. type: string - durableName: - description: The name of the Consumer. - type: string - pattern: '^[^.*>]+$' - minLength: 1 deliverSubject: description: The subject to deliver observed messages, when not set, a pull-based Consumer is created. type: string @@ -626,6 +630,32 @@ spec: type: object additionalProperties: type: string + account: + description: Name of the account to which the Consumer belongs. + type: string + pattern: '^[^.*>]*$' + creds: + description: NATS user credentials for connecting to servers. Please make sure your controller has mounted the cerds on its path. + type: string + default: '' + nkey: + description: NATS user NKey for connecting to servers. + type: string + default: '' + preventDelete: + description: When true, the managed Consumer will not be deleted when the resource is deleted + type: boolean + default: false + preventUpdate: + description: When true, the managed Consumer will not be updated when the resource is updated + type: boolean + default: false + servers: + description: A list of servers for creating consumer + type: array + items: + type: string + default: [] tls: description: A client's TLS certs and keys. type: object @@ -641,30 +671,8 @@ spec: type: array items: type: string - servers: - description: A list of servers for creating consumer - type: array - items: - type: string - default: [] - creds: - description: NATS user credentials for connecting to servers. Please make sure your controller has mounted the cerds on its path. - type: string - default: '' - nkey: - description: NATS user NKey for connecting to servers. - type: string - default: '' - account: - description: Name of the account to which the Consumer belongs. - type: string - pattern: '^[^.*>]*$' - preventDelete: - description: When true, the managed Consumer will not be deleted when the resource is deleted - type: boolean - default: false - preventUpdate: - description: When true, the managed Consumer will not be updated when the resource is updated + tlsFirst: + description: When true, the KV Store will iniate TLS before server INFO. type: boolean default: false status: @@ -991,6 +999,19 @@ spec: type: string pattern: '^[^.*>]*$' minLength: 1 + creds: + description: The creds to be used to connect to the NATS Service. + type: object + properties: + secret: + type: object + properties: + name: + description: Name of the secret with the creds. + type: string + file: + description: Credentials file, generated with github.com/nats-io/nsc tool. + type: string servers: description: A list of servers to connect. type: array @@ -1017,19 +1038,6 @@ spec: key: description: Filename of the TLS cert key. type: string - creds: - description: The creds to be used to connect to the NATS Service. - type: object - properties: - secret: - type: object - properties: - name: - description: Name of the secret with the creds. - type: string - file: - description: Credentials file, generated with github.com/nats-io/nsc tool. - type: string token: description: The token to be used to connect to the NATS Service. type: object @@ -1059,6 +1067,30 @@ spec: password: description: Key in the secret that contains the password. type: string + tlsFirst: + description: When true, the KV Store will iniate TLS before server INFO. + type: boolean + default: false + status: + type: object + properties: + observedGeneration: + type: integer + conditions: + type: array + items: + type: object + properties: + type: + type: string + status: + type: string + lastTransitionTime: + type: string + reason: + type: string + message: + type: string --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition @@ -1241,6 +1273,10 @@ spec: type: array items: type: string + tlsFirst: + description: When true, the KV Store will iniate TLS before server INFO. + type: boolean + default: false status: type: object properties: @@ -1377,6 +1413,10 @@ spec: type: array items: type: string + tlsFirst: + description: When true, the KV Store will iniate TLS before server INFO. + type: boolean + default: false status: type: object properties: diff --git a/internal/controller/account_controller.go b/internal/controller/account_controller.go index 0be62eeb..2bcea621 100644 --- a/internal/controller/account_controller.go +++ b/internal/controller/account_controller.go @@ -1,5 +1,5 @@ /* -Copyright 2024. +Copyright 2025. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -18,35 +18,210 @@ package controller import ( "context" + "errors" + "fmt" + "github.com/go-logr/logr" + api "github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2" + v1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" "k8s.io/klog/v2" - - jetstreamnatsiov1beta2 "github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2" ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + "sigs.k8s.io/controller-runtime/pkg/predicate" + "sigs.k8s.io/controller-runtime/pkg/reconcile" ) // AccountReconciler reconciles a Account object type AccountReconciler struct { + Scheme *runtime.Scheme JetStreamController } +type JetStreamResource interface { + GetName() string + GetNamespace() string +} + +type JetStreamResourceList []JetStreamResource + // Reconcile is part of the main kubernetes reconciliation loop which aims to // move the current state of the cluster closer to the desired state. // -// For more details, check Reconcile and its Result here: -// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.17.3/pkg/reconcile +// It performs two main operations: +// - Initialize finalizer if not present +// - Remove the finalizer on deletion once no other resources are referencing the account +// +// A call to reconcile may perform only one action, expecting the reconciliation to be triggered again by an update. +// For example: Setting the finalizer triggers a second reconciliation. Reconcile returns after setting the finalizer, +// to prevent parallel reconciliations performing the same steps. func (r *AccountReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { log := klog.FromContext(ctx) - log.Info("reconcile", "namespace", req.Namespace, "name", req.Name) - // TODO(user): your logic here + if ok := r.ValidNamespace(req.Namespace); !ok { + log.Info("Controller restricted to namespace, skipping reconciliation.") + return ctrl.Result{}, nil + } + + // Fetch Account resource + account := &api.Account{} + if err := r.Get(ctx, req.NamespacedName, account); err != nil { + if apierrors.IsNotFound(err) { + log.Info("Account deleted.", "accountName", req.NamespacedName.String()) + return ctrl.Result{}, nil + } + return ctrl.Result{}, fmt.Errorf("get account resource '%s': %w", req.NamespacedName.String(), err) + } + + // Update ready status to unknown when no status is set + if len(account.Status.Conditions) == 0 { + log.Info("Setting initial ready condition to unknown.") + account.Status.Conditions = updateReadyCondition(account.Status.Conditions, v1.ConditionUnknown, stateReconciling, "Starting reconciliation") + err := r.Status().Update(ctx, account) + if err != nil { + return ctrl.Result{}, fmt.Errorf("set condition unknown: %w", err) + } + r.Get(ctx, req.NamespacedName, account) + log.Info("Status", "Conditions", account.Status.Conditions) + return ctrl.Result{Requeue: true}, nil + } + + // Check Deletion + markedForDeletion := account.GetDeletionTimestamp() != nil + if markedForDeletion { + if controllerutil.ContainsFinalizer(account, accountFinalizer) { + // Get list of resources referencing this account + requests := r.findDependentResources(ctx, log, account) + if len(requests) > 0 { + log.Info("Account still has dependent resources, cannot delete", "dependentCount", len(requests)) + account.Status.Conditions = updateReadyCondition( + account.Status.Conditions, + v1.ConditionFalse, + stateFinalizing, + "Account has dependent resources that must be deleted first", + ) + if err := r.Status().Update(ctx, account); err != nil { + return ctrl.Result{}, fmt.Errorf("update status: %w", err) + } + return ctrl.Result{Requeue: true}, nil + } + + log.Info("Removing Account finalizer") + if ok := controllerutil.RemoveFinalizer(account, accountFinalizer); !ok { + return ctrl.Result{}, errors.New("failed to remove finalizer") + } + if err := r.Update(ctx, account); err != nil { + return ctrl.Result{}, fmt.Errorf("remove finalizer: %w", err) + } + } else { + log.Info("Account marked for deletion and already finalized. Ignoring.") + } + return ctrl.Result{}, nil + } + + // Add finalizer + if !controllerutil.ContainsFinalizer(account, accountFinalizer) { + log.Info("Adding Account finalizer.") + if ok := controllerutil.AddFinalizer(account, accountFinalizer); !ok { + return ctrl.Result{}, errors.New("failed to add finalizer to account resource") + } + + if err := r.Update(ctx, account); err != nil { + return ctrl.Result{}, fmt.Errorf("update account resource to add finalizer: %w", err) + } + return ctrl.Result{}, nil + } + + // Update ready status for non-deleted accounts + account.Status.ObservedGeneration = account.Generation + account.Status.Conditions = updateReadyCondition( + account.Status.Conditions, + v1.ConditionTrue, + stateReady, + "Account is ready", + ) + if err := r.Status().Update(ctx, account); err != nil { + return ctrl.Result{}, fmt.Errorf("update status: %w", err) + } return ctrl.Result{}, nil } +func (r *AccountReconciler) findDependentResources(ctx context.Context, log logr.Logger, obj client.Object) []reconcile.Request { + var resourceList JetStreamResourceList + + var consumerList api.ConsumerList + if err := r.List(ctx, &consumerList, + client.InNamespace(obj.GetNamespace()), + ); err != nil { + log.Error(err, "Failed to list consumers") + } + for _, i := range consumerList.Items { + if i.Spec.Account == obj.GetName() { + resourceList = append(resourceList, &i) + } + } + + var keyValueList api.KeyValueList + if err := r.List(ctx, &keyValueList, + client.InNamespace(obj.GetNamespace()), + ); err != nil { + log.Error(err, "Failed to list accounts") + } + for _, i := range keyValueList.Items { + if i.Spec.Account == obj.GetName() { + resourceList = append(resourceList, &i) + } + } + + var objectStoreList api.ObjectStoreList + if err := r.List(ctx, &objectStoreList, + client.InNamespace(obj.GetNamespace()), + ); err != nil { + log.Error(err, "Failed to list objectstores") + } + for _, i := range objectStoreList.Items { + if i.Spec.Account == obj.GetName() { + resourceList = append(resourceList, &i) + } + } + + var streamList api.StreamList + if err := r.List(ctx, &streamList, + client.InNamespace(obj.GetNamespace()), + ); err != nil { + log.Error(err, "Failed to list streams") + } + for _, i := range streamList.Items { + if i.Spec.Account == obj.GetName() { + resourceList = append(resourceList, &i) + } + } + + requests := make([]reconcile.Request, 0, len(resourceList)) + for _, resource := range resourceList { + requests = append(requests, reconcile.Request{ + NamespacedName: types.NamespacedName{ + Namespace: resource.GetNamespace(), + Name: resource.GetName(), + }, + }) + } + + return requests +} + // SetupWithManager sets up the controller with the Manager. func (r *AccountReconciler) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). - For(&jetstreamnatsiov1beta2.Account{}). + For(&api.Account{}). + WithEventFilter(predicate.GenerationChangedPredicate{}). + WithOptions(controller.Options{ + MaxConcurrentReconciles: 1, + }). Complete(r) } diff --git a/internal/controller/account_controller_test.go b/internal/controller/account_controller_test.go index 46debade..2067b758 100644 --- a/internal/controller/account_controller_test.go +++ b/internal/controller/account_controller_test.go @@ -17,67 +17,164 @@ limitations under the License. package controller import ( - "context" + "time" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - "k8s.io/apimachinery/pkg/api/errors" + v1 "k8s.io/api/core/v1" + k8serrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/types" - "sigs.k8s.io/controller-runtime/pkg/reconcile" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - jetstreamnatsiov1beta2 "github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2" + api "github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2" ) var _ = Describe("Account Controller", func() { Context("When reconciling a resource", func() { - const resourceName = "test-resource" - - ctx := context.Background() + const resourceName = "test-account" typeNamespacedName := types.NamespacedName{ Name: resourceName, - Namespace: "default", // TODO(user):Modify as needed + Namespace: "default", } - account := &jetstreamnatsiov1beta2.Account{} + account := &api.Account{} + + // Tested controller + var controller *AccountReconciler + + BeforeEach(func(ctx SpecContext) { + controller = &AccountReconciler{ + Scheme: k8sClient.Scheme(), + JetStreamController: baseController, + } + }) + + When("the resource is marked for deletion", func() { + var stream *api.Stream + var streamName types.NamespacedName + + BeforeEach(func(ctx SpecContext) { + By("creating the custom resource for the Kind Account") + err := k8sClient.Get(ctx, typeNamespacedName, account) + if err != nil && k8serrors.IsNotFound(err) { + resource := &api.Account{ + ObjectMeta: metav1.ObjectMeta{ + Name: resourceName, + Namespace: "default", + }, + Spec: api.AccountSpec{ + Servers: []string{"nats://nats.io"}, + }, + } + + Expect(k8sClient.Create(ctx, resource)).To(Succeed()) + Expect(k8sClient.Get(ctx, typeNamespacedName, account)).To(Succeed()) - BeforeEach(func() { - By("creating the custom resource for the Kind Account") - err := k8sClient.Get(ctx, typeNamespacedName, account) - if err != nil && errors.IsNotFound(err) { - resource := &jetstreamnatsiov1beta2.Account{ + controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName}) + controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName}) + + Expect(k8sClient.Get(ctx, typeNamespacedName, account)).To(Succeed()) + Expect(controllerutil.ContainsFinalizer(account, accountFinalizer)).To(BeTrue()) + } + + By("creating a dependent stream resource") + stream = &api.Stream{ ObjectMeta: metav1.ObjectMeta{ - Name: resourceName, + Name: "test-stream", Namespace: "default", }, - // TODO(user): Specify other spec details if needed. + Spec: api.StreamSpec{ + Name: "test-stream", + Replicas: 1, + Discard: "old", + Storage: "file", + Retention: "workqueue", + BaseStreamConfig: api.BaseStreamConfig{ + ConnectionOpts: api.ConnectionOpts{ + Account: resourceName, + }, + }, + }, } - Expect(k8sClient.Create(ctx, resource)).To(Succeed()) - } - }) + streamName = types.NamespacedName{ + Name: stream.Name, + Namespace: stream.Namespace, + } + Expect(k8sClient.Create(ctx, stream)).To(Succeed()) - AfterEach(func() { - // TODO(user): Cleanup logic after each test, like removing the resource instance. - resource := &jetstreamnatsiov1beta2.Account{} - err := k8sClient.Get(ctx, typeNamespacedName, resource) - Expect(err).NotTo(HaveOccurred()) + By("marking the account for deletion") + Expect(k8sClient.Delete(ctx, account)).To(Succeed()) + Expect(k8sClient.Get(ctx, typeNamespacedName, account)).To(Succeed()) + }) - By("Cleanup the specific resource instance Account") - Expect(k8sClient.Delete(ctx, resource)).To(Succeed()) - }) - It("should successfully reconcile the resource", func() { - By("Reconciling the created resource") - controllerReconciler := &AccountReconciler{ - baseController, - } + AfterEach(func(ctx SpecContext) { + By("cleaning up the stream") + stream := &api.Stream{} + err := k8sClient.Get(ctx, streamName, stream) + if err == nil { + Expect(k8sClient.Delete(ctx, stream)).To(Succeed()) + } + + By("removing the account resource") + controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName}) + Expect(k8sClient.Get(ctx, typeNamespacedName, account)).To(Not(Succeed())) + }) + + It("should not remove finalizer while dependent resources exist", func(ctx SpecContext) { + By("reconciling the deletion") + result, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName}) + Expect(err).NotTo(HaveOccurred()) + Expect(result.Requeue).To(BeTrue()) + + By("checking the account still exists") + Expect(k8sClient.Get(ctx, typeNamespacedName, account)).To(Succeed()) + Expect(account.Finalizers).To(ContainElement(accountFinalizer)) + + By("verifying the ready condition is set to false") + Expect(account.Status.Conditions).To(HaveLen(1)) + assertReadyStateMatches( + account.Status.Conditions[0], + v1.ConditionFalse, + stateFinalizing, + "Account has dependent resources that must be deleted first", + time.Now(), + ) + }) + + It("should remove finalizer after dependent resources are removed", func(ctx SpecContext) { + By("removing the dependent stream") + Expect(k8sClient.Delete(ctx, stream)).To(Succeed()) + + By("reconciling the deletion") + result, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName}) + Expect(err).NotTo(HaveOccurred()) + Expect(result.IsZero()).To(BeTrue()) + + By("checking the account is deleted") + err = k8sClient.Get(ctx, typeNamespacedName, account) + Expect(err).To(HaveOccurred()) + Expect(k8serrors.IsNotFound(err)).To(BeTrue()) + }) + + It("should remove finalizer after dependent resources are updated", func(ctx SpecContext) { + By("updating the dependent stream to remove account reference") + Expect(k8sClient.Get(ctx, streamName, stream)).To(Succeed()) + stream.Spec.Account = "" + Expect(k8sClient.Update(ctx, stream)).To(Succeed()) + + By("reconciling the deletion") + result, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName}) + Expect(err).NotTo(HaveOccurred()) + Expect(result.IsZero()).To(BeTrue()) - _, err := controllerReconciler.Reconcile(ctx, reconcile.Request{ - NamespacedName: typeNamespacedName, + By("checking the account is deleted") + err = k8sClient.Get(ctx, typeNamespacedName, account) + Expect(err).To(HaveOccurred()) + Expect(k8serrors.IsNotFound(err)).To(BeTrue()) }) - Expect(err).NotTo(HaveOccurred()) - // TODO(user): Add more specific assertions depending on your controller's reconciliation logic. - // Example: If you expect a certain status condition after reconciliation, verify it here. }) }) }) diff --git a/internal/controller/client.go b/internal/controller/client.go index 5eb9b8d6..7e79a658 100644 --- a/internal/controller/client.go +++ b/internal/controller/client.go @@ -23,6 +23,10 @@ type NatsConfig struct { func (o *NatsConfig) buildOptions() ([]nats.Option, error) { opts := make([]nats.Option, 0) + if o.ServerURL == "" { + return nil, fmt.Errorf("server url is required") + } + if o.TLSFirst { opts = append(opts, nats.TLSHandshakeFirst()) } @@ -31,25 +35,24 @@ func (o *NatsConfig) buildOptions() ([]nats.Option, error) { opts = append(opts, nats.Name(o.ClientName)) } - if !o.CRDConnect { - // Use JWT/NKEYS based credentials if present. - if o.Credentials != "" { - opts = append(opts, nats.UserCredentials(o.Credentials)) - } else if o.NKey != "" { - opt, err := nats.NkeyOptionFromSeed(o.NKey) - if err != nil { - return nil, fmt.Errorf("nkey option from seed: %w", err) - } - opts = append(opts, opt) - } + if o.Credentials != "" { + opts = append(opts, nats.UserCredentials(o.Credentials)) + } - if o.Certificate != "" && o.Key != "" { - opts = append(opts, nats.ClientCert(o.Certificate, o.Key)) + if o.NKey != "" { + opt, err := nats.NkeyOptionFromSeed(o.NKey) + if err != nil { + return nil, fmt.Errorf("nkey option from seed: %w", err) } + opts = append(opts, opt) + } - if len(o.CAs) > 0 { - opts = append(opts, nats.RootCAs(o.CAs...)) - } + if o.Certificate != "" && o.Key != "" { + opts = append(opts, nats.ClientCert(o.Certificate, o.Key)) + } + + if len(o.CAs) > 0 { + opts = append(opts, nats.RootCAs(o.CAs...)) } return opts, nil diff --git a/internal/controller/consumer_controller.go b/internal/controller/consumer_controller.go index 67540458..21efa23f 100644 --- a/internal/controller/consumer_controller.go +++ b/internal/controller/consumer_controller.go @@ -18,6 +18,7 @@ package controller import ( "context" + "encoding/json" "errors" "fmt" "time" @@ -26,7 +27,10 @@ import ( "github.com/nats-io/nats.go/jetstream" v1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime" "k8s.io/klog/v2" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" "sigs.k8s.io/controller-runtime/pkg/predicate" @@ -36,6 +40,8 @@ import ( // ConsumerReconciler reconciles a Consumer object type ConsumerReconciler struct { + Scheme *runtime.Scheme + JetStreamController } @@ -56,7 +62,7 @@ func (r *ConsumerReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c consumer := &api.Consumer{} if err := r.Get(ctx, req.NamespacedName, consumer); err != nil { if apierrors.IsNotFound(err) { - log.Info("Consumer resource not found. Ignoring since object must be deleted.") + log.Info("Consumer deleted.", "consumerName", req.NamespacedName.String()) return ctrl.Result{}, nil } return ctrl.Result{}, fmt.Errorf("get consumer resource '%s': %w", req.NamespacedName.String(), err) @@ -70,7 +76,7 @@ func (r *ConsumerReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c // Update ready status to unknown when no status is set if len(consumer.Status.Conditions) == 0 { log.Info("Setting initial ready condition to unknown.") - consumer.Status.Conditions = updateReadyCondition(consumer.Status.Conditions, v1.ConditionUnknown, "Reconciling", "Starting reconciliation") + consumer.Status.Conditions = updateReadyCondition(consumer.Status.Conditions, v1.ConditionUnknown, stateReconciling, "Starting reconciliation") err := r.Status().Update(ctx, consumer) if err != nil { return ctrl.Result{}, fmt.Errorf("set condition unknown: %w", err) @@ -78,6 +84,21 @@ func (r *ConsumerReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c return ctrl.Result{Requeue: true}, nil } + // Check Deletion + markedForDeletion := consumer.GetDeletionTimestamp() != nil + if markedForDeletion { + if controllerutil.ContainsFinalizer(consumer, consumerFinalizer) { + err := r.deleteConsumer(ctx, log, consumer) + if err != nil { + return ctrl.Result{}, fmt.Errorf("delete consumer: %w", err) + } + } else { + log.Info("Consumer marked for deletion and already finalized. Ignoring.") + } + + return ctrl.Result{}, nil + } + // Add finalizer if !controllerutil.ContainsFinalizer(consumer, consumerFinalizer) { log.Info("Adding consumer finalizer.") @@ -91,37 +112,52 @@ func (r *ConsumerReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c return ctrl.Result{}, nil } - // Check Deletion - markedForDeletion := consumer.GetDeletionTimestamp() != nil - if markedForDeletion { - if controllerutil.ContainsFinalizer(consumer, consumerFinalizer) { - err := r.deleteConsumer(ctx, log, consumer) - if err != nil { - return ctrl.Result{}, fmt.Errorf("delete consumer: %w", err) - } - } else { - log.Info("Consumer marked for deletion and already finalized. Ignoring.") + if consumer.Spec.FlowControl || consumer.Spec.DeliverSubject != "" || consumer.Spec.DeliverGroup != "" || consumer.Spec.HeartbeatInterval != "" { + log.Info("FlowControl, DeliverSubject, DeliverGroup, and HeartbeatInterval are Push Consumer options, which are not supported. Skipping consumer creation or update.") + consumer.Status.Conditions = updateReadyCondition(consumer.Status.Conditions, v1.ConditionFalse, stateErrored, "Push Consumer options are not supported.") + if err := r.Status().Update(ctx, consumer); err != nil { + log.Error(err, "Failed to update ready condition to Errored.") } - return ctrl.Result{}, nil } // Create or update stream if err := r.createOrUpdate(ctx, log, consumer); err != nil { + if err := r.Get(ctx, client.ObjectKeyFromObject(consumer), consumer); err != nil { + return ctrl.Result{}, fmt.Errorf("get consumer resource: %w", err) + } + consumer.Status.Conditions = updateReadyCondition(consumer.Status.Conditions, v1.ConditionFalse, stateErrored, err.Error()) + if err := r.Status().Update(ctx, consumer); err != nil { + log.Error(err, "Failed to update ready condition to Errored.") + } return ctrl.Result{}, fmt.Errorf("create or update: %s", err) } - return ctrl.Result{}, nil + + return ctrl.Result{RequeueAfter: r.RequeueInterval()}, nil } func (r *ConsumerReconciler) deleteConsumer(ctx context.Context, log logr.Logger, consumer *api.Consumer) error { // Set status to false - consumer.Status.Conditions = updateReadyCondition(consumer.Status.Conditions, v1.ConditionFalse, "Finalizing", "Performing finalizer operations.") + consumer.Status.Conditions = updateReadyCondition(consumer.Status.Conditions, v1.ConditionFalse, stateFinalizing, "Performing finalizer operations.") if err := r.Status().Update(ctx, consumer); err != nil { return fmt.Errorf("update ready condition: %w", err) } + storedState, err := getStoredConsumerState(consumer) + if err != nil { + log.Error(err, "Failed to fetch stored state.") + } + if !consumer.Spec.PreventDelete && !r.ReadOnly() { - err := r.WithJetStreamClient(consumerConnOpts(consumer.Spec), func(js jetstream.JetStream) error { + err := r.WithJetStreamClient(consumer.Spec.ConnectionOpts, consumer.Namespace, func(js jetstream.JetStream) error { + _, err := getServerConsumerState(ctx, js, consumer) + // If we have no known state for this consumer it has never been reconciled. + // If we are also receiving an error fetching state, either the consumer does not exist + // or this resource config is invalid. + if err != nil && storedState == nil { + return nil + } + return js.DeleteConsumer(ctx, consumer.Spec.StreamName, consumer.Spec.DurableName) }) switch { @@ -130,7 +166,11 @@ func (r *ConsumerReconciler) deleteConsumer(ctx context.Context, log logr.Logger case errors.Is(err, jetstream.ErrStreamNotFound): log.Info("Stream of consumer does not exist. Unable to delete.") case err != nil: - return fmt.Errorf("delete jetstream consumer: %w", err) + if storedState == nil { + log.Info("Consumer not reconciled and no state received from server. Removing finalizer.") + } else { + return fmt.Errorf("delete jetstream consumer: %w", err) + } default: log.Info("Consumer deleted.") } @@ -155,54 +195,84 @@ func (r *ConsumerReconciler) deleteConsumer(ctx context.Context, log logr.Logger func (r *ConsumerReconciler) createOrUpdate(ctx context.Context, log klog.Logger, consumer *api.Consumer) error { // Create or Update the stream based on the spec - if r.ReadOnly() { - log.Info("Skipping consumer creation or update.", - "read-only", r.ReadOnly(), - ) - return nil - } - // Map spec to consumer target config targetConfig, err := consumerSpecToConfig(&consumer.Spec) if err != nil { return fmt.Errorf("map consumer spec to target config: %w", err) } - err = r.WithJetStreamClient(consumerConnOpts(consumer.Spec), func(js jetstream.JetStream) error { - consumerName := targetConfig.Name - if consumerName == "" { - consumerName = targetConfig.Durable + err = r.WithJetStreamClient(consumer.Spec.ConnectionOpts, consumer.Namespace, func(js jetstream.JetStream) error { + storedState, err := getStoredConsumerState(consumer) + if err != nil { + log.Error(err, "Failed to fetch stored consumer state.") } - exists := false - _, err := js.Consumer(ctx, consumer.Spec.StreamName, consumerName) - if err == nil { - exists = true - } else if !errors.Is(err, jetstream.ErrConsumerNotFound) { + serverState, err := getServerConsumerState(ctx, js, consumer) + if err != nil { return err } - if !exists { - log.Info("Creating Consumer.") - _, err := js.CreateConsumer(ctx, consumer.Spec.StreamName, *targetConfig) - return err + // Check against known state. Skip Update if converged. + // Storing returned state from the server avoids have to + // check default values or call Update on already converged resources + if storedState != nil && serverState != nil && consumer.Status.ObservedGeneration == consumer.Generation { + diff := compareConfigState(storedState, serverState) + + if diff == "" { + return nil + } + + log.Info("Consumer config drifted from desired state.", "diff", diff) } - if !consumer.Spec.PreventUpdate { + if r.ReadOnly() { + log.Info("Skipping consumer creation or update.", + "read-only", r.ReadOnly(), + ) + return nil + } + + var updatedConsumer jetstream.Consumer + err = nil + + if serverState == nil { + log.Info("Creating Consumer.") + updatedConsumer, err = js.CreateConsumer(ctx, consumer.Spec.StreamName, *targetConfig) + if err != nil { + return err + } + } else if !consumer.Spec.PreventUpdate { log.Info("Updating Consumer.") - _, err := js.UpdateConsumer(ctx, consumer.Spec.StreamName, *targetConfig) - return err + updatedConsumer, err = js.UpdateConsumer(ctx, consumer.Spec.StreamName, *targetConfig) + if err != nil { + return err + } } else { log.Info("Skipping Consumer update.", "preventUpdate", consumer.Spec.PreventUpdate, ) } + if updatedConsumer != nil { + // Store known state in annotation + updatedState, err := json.Marshal(updatedConsumer.CachedInfo().Config) + if err != nil { + return err + } + + if consumer.Annotations == nil { + consumer.Annotations = map[string]string{} + } + consumer.Annotations[stateAnnotationConsumer] = string(updatedState) + + return r.Update(ctx, consumer) + } + return nil }) if err != nil { err = fmt.Errorf("create or update consumer: %w", err) - consumer.Status.Conditions = updateReadyCondition(consumer.Status.Conditions, v1.ConditionFalse, "Errored", err.Error()) + consumer.Status.Conditions = updateReadyCondition(consumer.Status.Conditions, v1.ConditionFalse, stateErrored, err.Error()) if err := r.Status().Update(ctx, consumer); err != nil { log.Error(err, "Failed to update ready condition to Errored.") } @@ -214,7 +284,7 @@ func (r *ConsumerReconciler) createOrUpdate(ctx context.Context, log klog.Logger consumer.Status.Conditions = updateReadyCondition( consumer.Status.Conditions, v1.ConditionTrue, - "Reconciling", + stateReady, "Consumer successfully created or updated.", ) err = r.Status().Update(ctx, consumer) @@ -225,14 +295,30 @@ func (r *ConsumerReconciler) createOrUpdate(ctx context.Context, log klog.Logger return nil } -func consumerConnOpts(spec api.ConsumerSpec) *connectionOptions { - return &connectionOptions{ - Account: spec.Account, - Creds: spec.Creds, - Nkey: spec.Nkey, - Servers: spec.Servers, - TLS: spec.TLS, +func getStoredConsumerState(consumer *api.Consumer) (*jetstream.ConsumerConfig, error) { + var storedState *jetstream.ConsumerConfig + if state, ok := consumer.Annotations[stateAnnotationConsumer]; ok { + err := json.Unmarshal([]byte(state), &storedState) + if err != nil { + return nil, err + } + } + + return storedState, nil +} + +// Fetch the current state of the consumer from the server. +// ErrConsumerNotFound is considered a valid response and does not return error +func getServerConsumerState(ctx context.Context, js jetstream.JetStream, consumer *api.Consumer) (*jetstream.ConsumerConfig, error) { + c, err := js.Consumer(ctx, consumer.Spec.StreamName, consumer.Spec.DurableName) + if errors.Is(err, jetstream.ErrConsumerNotFound) { + return nil, nil } + if err != nil { + return nil, err + } + + return &c.CachedInfo().Config, nil } func consumerSpecToConfig(spec *api.ConsumerSpec) (*jetstream.ConsumerConfig, error) { @@ -253,15 +339,11 @@ func consumerSpecToConfig(spec *api.ConsumerSpec) (*jetstream.ConsumerConfig, er MemoryStorage: spec.MemStorage, FilterSubjects: spec.FilterSubjects, Metadata: spec.Metadata, - - // Explicitly set not (yet) mapped fields - Name: "", - InactiveThreshold: 0, } // DeliverPolicy if spec.DeliverPolicy != "" { - err := config.DeliverPolicy.UnmarshalJSON(asJsonString(spec.DeliverPolicy)) + err := config.DeliverPolicy.UnmarshalJSON(jsonString(spec.DeliverPolicy)) if err != nil { return nil, fmt.Errorf("invalid delivery policy: %w", err) } @@ -278,7 +360,7 @@ func consumerSpecToConfig(spec *api.ConsumerSpec) (*jetstream.ConsumerConfig, er // AckPolicy if spec.AckPolicy != "" { - err := config.AckPolicy.UnmarshalJSON(asJsonString(spec.AckPolicy)) + err := config.AckPolicy.UnmarshalJSON(jsonString(spec.AckPolicy)) if err != nil { return nil, fmt.Errorf("invalid ack policy: %w", err) } @@ -305,7 +387,7 @@ func consumerSpecToConfig(spec *api.ConsumerSpec) (*jetstream.ConsumerConfig, er // ReplayPolicy if spec.ReplayPolicy != "" { - err := config.ReplayPolicy.UnmarshalJSON(asJsonString(spec.ReplayPolicy)) + err := config.ReplayPolicy.UnmarshalJSON(jsonString(spec.ReplayPolicy)) if err != nil { return nil, fmt.Errorf("invalid replay policy: %w", err) } @@ -320,6 +402,14 @@ func consumerSpecToConfig(spec *api.ConsumerSpec) (*jetstream.ConsumerConfig, er config.MaxRequestExpires = d } + if spec.InactiveThreshold != "" { + d, err := time.ParseDuration(spec.InactiveThreshold) + if err != nil { + return nil, fmt.Errorf("invalid inactive threshold: %w", err) + } + config.InactiveThreshold = d + } + return config, nil } @@ -328,5 +418,8 @@ func (r *ConsumerReconciler) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). For(&api.Consumer{}). WithEventFilter(predicate.GenerationChangedPredicate{}). + WithOptions(controller.Options{ + MaxConcurrentReconciles: 1, + }). Complete(r) } diff --git a/internal/controller/consumer_controller_test.go b/internal/controller/consumer_controller_test.go index bf5410a1..5b72f579 100644 --- a/internal/controller/consumer_controller_test.go +++ b/internal/controller/consumer_controller_test.go @@ -61,7 +61,7 @@ var _ = Describe("Consumer Controller", func() { Durable: consumerName, } - // Tested coontroller + // Tested controller var controller *ConsumerReconciler BeforeEach(func(ctx SpecContext) { @@ -93,7 +93,8 @@ var _ = Describe("Consumer Controller", func() { By("setting up the tested controller") controller = &ConsumerReconciler{ - baseController, + Scheme: k8sClient.Scheme(), + JetStreamController: baseController, } }) @@ -170,7 +171,7 @@ var _ = Describe("Consumer Controller", func() { Expect(k8sClient.Get(ctx, typeNamespacedName, consumer)).To(Succeed()) Expect(consumer.Status.Conditions).To(HaveLen(1)) - assertReadyStateMatches(consumer.Status.Conditions[0], v1.ConditionUnknown, "Reconciling", "Starting reconciliation", time.Now()) + assertReadyStateMatches(consumer.Status.Conditions[0], v1.ConditionUnknown, stateReconciling, "Starting reconciliation", time.Now()) }) }) @@ -211,7 +212,7 @@ var _ = Describe("Consumer Controller", func() { assertReadyStateMatches( consumer.Status.Conditions[0], v1.ConditionFalse, - "Errored", + stateErrored, "stream", // Not existing stream as message time.Now(), ) @@ -229,7 +230,7 @@ var _ = Describe("Consumer Controller", func() { By("checking if the ready state was updated") Expect(consumer.Status.Conditions).To(HaveLen(1)) - assertReadyStateMatches(consumer.Status.Conditions[0], v1.ConditionTrue, "Reconciling", "created or updated", time.Now()) + assertReadyStateMatches(consumer.Status.Conditions[0], v1.ConditionTrue, stateReady, "created or updated", time.Now()) By("checking if the observed generation matches") Expect(consumer.Status.ObservedGeneration).To(Equal(consumer.Generation)) @@ -320,9 +321,10 @@ var _ = Describe("Consumer Controller", func() { When("read-only mode is enabled", func() { BeforeEach(func(ctx SpecContext) { By("setting read only on the controller") - readOnly, err := NewJSController(k8sClient, &NatsConfig{ServerURL: testServer.ClientURL()}, &Config{ReadOnly: true}) + readOnly, err := NewJSController(k8sClient, &NatsConfig{ServerURL: clientUrl}, &Config{ReadOnly: true}) Expect(err).NotTo(HaveOccurred()) controller = &ConsumerReconciler{ + Scheme: k8sClient.Scheme(), JetStreamController: readOnly, } }) @@ -357,9 +359,10 @@ var _ = Describe("Consumer Controller", func() { When("namespace restriction is enabled", func() { BeforeEach(func(ctx SpecContext) { By("setting a namespace on the resource") - namespaced, err := NewJSController(k8sClient, &NatsConfig{ServerURL: testServer.ClientURL()}, &Config{Namespace: "other-namespace"}) + namespaced, err := NewJSController(k8sClient, &NatsConfig{ServerURL: clientUrl}, &Config{Namespace: "other-namespace"}) Expect(err).NotTo(HaveOccurred()) controller = &ConsumerReconciler{ + Scheme: k8sClient.Scheme(), JetStreamController: namespaced, } }) @@ -485,6 +488,7 @@ var _ = Describe("Consumer Controller", func() { readOnly, err := NewJSController(k8sClient, &NatsConfig{ServerURL: testServer.ClientURL()}, &Config{ReadOnly: true}) Expect(err).NotTo(HaveOccurred()) controller = &ConsumerReconciler{ + Scheme: k8sClient.Scheme(), JetStreamController: readOnly, } }) @@ -510,6 +514,7 @@ var _ = Describe("Consumer Controller", func() { namespaced, err := NewJSController(k8sClient, &NatsConfig{ServerURL: testServer.ClientURL()}, &Config{Namespace: "other-namespace"}) Expect(err).NotTo(HaveOccurred()) controller = &ConsumerReconciler{ + Scheme: k8sClient.Scheme(), JetStreamController: namespaced, } }) @@ -594,13 +599,10 @@ func Test_consumerSpecToConfig(t *testing.T) { AckPolicy: "explicit", AckWait: "10ns", BackOff: []string{"1s", "5m"}, - Creds: "", DeliverGroup: "", DeliverPolicy: "new", DeliverSubject: "", Description: "test consumer", - PreventDelete: false, - PreventUpdate: false, DurableName: "test-consumer", FilterSubject: "time.us.>", FilterSubjects: []string{"time.us.east", "time.us.west"}, @@ -614,23 +616,29 @@ func Test_consumerSpecToConfig(t *testing.T) { MaxRequestMaxBytes: 1024, MaxWaiting: 5, MemStorage: true, - Nkey: "", OptStartSeq: 17, OptStartTime: dateString, RateLimitBps: 512, ReplayPolicy: "instant", Replicas: 9, SampleFreq: "25%", - Servers: nil, StreamName: "", - TLS: api.TLS{}, - Account: "", Metadata: map[string]string{ "meta": "data", }, + BaseStreamConfig: api.BaseStreamConfig{ + PreventDelete: false, + PreventUpdate: false, + ConnectionOpts: api.ConnectionOpts{ + Account: "", + Creds: "", + Nkey: "", + TLS: api.TLS{}, + Servers: nil, + }, + }, }, want: &jetstream.ConsumerConfig{ - Name: "", // Optional, not mapped Durable: "test-consumer", Description: "test consumer", DeliverPolicy: jetstream.DeliverNewPolicy, diff --git a/internal/controller/jetstream_controller.go b/internal/controller/jetstream_controller.go index 63e8f720..05fa338b 100644 --- a/internal/controller/jetstream_controller.go +++ b/internal/controller/jetstream_controller.go @@ -1,26 +1,24 @@ package controller import ( + "context" "fmt" + "math/rand/v2" + "os" + "path/filepath" "strings" "time" + "github.com/google/go-cmp/cmp" js "github.com/nats-io/nack/controllers/jetstream" api "github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2" "github.com/nats-io/nats.go/jetstream" v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" ) -type connectionOptions struct { - Account string `json:"account"` - Creds string `json:"creds"` - Nkey string `json:"nkey"` - Servers []string `json:"servers"` - TLS api.TLS `json:"tls"` -} - type JetStreamController interface { client.Client @@ -36,7 +34,9 @@ type JetStreamController interface { // The given opts values take precedence over the controllers base configuration. // // Returns the error of the operation or errors during client setup. - WithJetStreamClient(opts *connectionOptions, op func(js jetstream.JetStream) error) error + WithJetStreamClient(opts api.ConnectionOpts, ns string, op func(js jetstream.JetStream) error) error + + RequeueInterval() time.Duration } func NewJSController(k8sClient client.Client, natsConfig *NatsConfig, controllerConfig *Config) (JetStreamController, error) { @@ -44,6 +44,7 @@ func NewJSController(k8sClient client.Client, natsConfig *NatsConfig, controller Client: k8sClient, config: natsConfig, controllerConfig: controllerConfig, + cacheDir: controllerConfig.CacheDir, }, nil } @@ -51,6 +52,19 @@ type jsController struct { client.Client config *NatsConfig controllerConfig *Config + cacheDir string +} + +func (c *jsController) RequeueInterval() time.Duration { + // Stagger the requeue slightly + interval := c.controllerConfig.RequeueInterval + + // Allow up to a 10% variance + intervalRange := float64(interval.Nanoseconds()) * 0.1 + + randomFactor := (rand.Float64() * 2) - 1.0 + + return time.Duration(float64(interval.Nanoseconds()) + (intervalRange * randomFactor)) } func (c *jsController) ReadOnly() bool { @@ -62,10 +76,13 @@ func (c *jsController) ValidNamespace(namespace string) bool { return ns == "" || ns == namespace } -func (c *jsController) WithJetStreamClient(opts *connectionOptions, op func(js jetstream.JetStream) error) error { +func (c *jsController) WithJetStreamClient(opts api.ConnectionOpts, ns string, op func(js jetstream.JetStream) error) error { // Build single use client // TODO(future-feature): Use client-pool instead of single use client - cfg := c.buildNatsConfig(opts) + cfg, err := c.natsConfigFromOpts(opts, ns) + if err != nil { + return err + } jsClient, closer, err := CreateJetStreamClient(cfg, true) if err != nil { @@ -76,56 +93,155 @@ func (c *jsController) WithJetStreamClient(opts *connectionOptions, op func(js j return op(jsClient) } -// buildNatsConfig uses given opts to override the base NatsConfig. -func (c *jsController) buildNatsConfig(opts *connectionOptions) *NatsConfig { - serverUrls := strings.Join(opts.Servers, ",") +// Setup default options, override from account resource and CRD options if configured +func (c *jsController) natsConfigFromOpts(opts api.ConnectionOpts, ns string) (*NatsConfig, error) { + ctx, done := context.WithTimeout(context.Background(), 5*time.Second) + defer done() - // Takes opts values if present - cfg := &NatsConfig{ - CRDConnect: false, - ClientName: c.config.ClientName, - ServerURL: or(serverUrls, c.config.ServerURL), - TLSFirst: c.config.TLSFirst, // TODO(future-feature): expose TLSFirst in the spec config + natsConfig := &NatsConfig{ + ClientName: c.config.ClientName, + Credentials: c.config.Credentials, + NKey: c.config.NKey, + ServerURL: c.config.ServerURL, + CAs: c.config.CAs, + TLSFirst: c.config.TLSFirst, } - // Note: The opts.Account value coming from the resource spec is currently not considered. - // creds/nkey are associated with an account, the account field might be redundant. - // See https://github.com/nats-io/nack/pull/211#pullrequestreview-2511111670 + if c.config.Certificate != "" && c.config.Key != "" { + natsConfig.Certificate = c.config.Certificate + natsConfig.Key = c.config.Key + } - // Authentication either from opts or base config - if opts.Creds != "" || opts.Nkey != "" { - cfg.Credentials = opts.Creds - cfg.NKey = opts.Nkey - } else { - cfg.Credentials = c.config.Credentials - cfg.NKey = c.config.NKey + if opts.Account == "" { + return applyConnOpts(*natsConfig, opts), nil } - // CAs from opts or base config - if len(opts.TLS.RootCAs) > 0 { - cfg.CAs = opts.TLS.RootCAs - } else { - cfg.CAs = c.config.CAs + // Apply Account options first, over global. + // Apply remaining CRD options last + + account := &api.Account{} + err := c.Get(ctx, + types.NamespacedName{ + Name: opts.Account, + Namespace: ns, + }, + account, + ) + if err != nil { + return nil, err } - // Client Cert and Key either from opts or base config - if opts.TLS.ClientCert != "" && opts.TLS.ClientKey != "" { - cfg.Certificate = opts.TLS.ClientCert - cfg.Key = opts.TLS.ClientKey - } else { - cfg.Certificate = c.config.Certificate - cfg.Key = c.config.Key + if len(account.Spec.Servers) > 0 { + natsConfig.ServerURL = strings.Join(account.Spec.Servers, ",") } - return cfg + if account.Spec.TLS != nil && account.Spec.TLS.Secret != nil { + tlsSecret := &v1.Secret{} + err := c.Get(ctx, + types.NamespacedName{ + Name: account.Spec.TLS.Secret.Name, + Namespace: ns, + }, + tlsSecret, + ) + if err != nil { + return nil, err + } + + accDir := filepath.Join(c.cacheDir, c.controllerConfig.Namespace, opts.Account) + if err := os.MkdirAll(accDir, 0o755); err != nil { + return nil, err + } + + for k, v := range tlsSecret.Data { + filePath := "" + switch k { + case account.Spec.TLS.ClientCert: + filePath = filepath.Join(accDir, account.Spec.TLS.ClientCert) + natsConfig.Certificate = filePath + case account.Spec.TLS.ClientKey: + filePath = filepath.Join(accDir, account.Spec.TLS.ClientKey) + natsConfig.Key = filePath + case account.Spec.TLS.RootCAs: + filePath = filepath.Join(accDir, account.Spec.TLS.RootCAs) + natsConfig.CAs = append(natsConfig.CAs, filePath) + default: + return nil, fmt.Errorf("key in TLS secret does not match any of the expected values") + } + if err := os.WriteFile(filePath, v, 0o600); err != nil { + return nil, err + } + } + } else if account.Spec.TLS != nil { + natsConfig.Certificate = account.Spec.TLS.ClientCert + natsConfig.Key = account.Spec.TLS.ClientKey + natsConfig.CAs = []string{account.Spec.TLS.RootCAs} + } + + if account.Spec.Creds != nil && account.Spec.Creds.Secret != nil { + credsSecret := &v1.Secret{} + err := c.Get(ctx, + types.NamespacedName{ + Name: account.Spec.Creds.Secret.Name, + Namespace: ns, + }, + credsSecret, + ) + if err != nil { + return nil, err + } + + accDir := filepath.Join(c.cacheDir, c.controllerConfig.Namespace, opts.Account) + if err := os.MkdirAll(accDir, 0o755); err != nil { + return nil, err + } + + for k, v := range credsSecret.Data { + filePath := "" + switch k { + case account.Spec.Creds.File: + filePath = filepath.Join(accDir, account.Spec.Creds.File) + natsConfig.Credentials = filePath + default: + return nil, fmt.Errorf("key in Creds secret does not match any of the expected values") + } + if err := os.WriteFile(filePath, v, 0o600); err != nil { + return nil, err + } + } + } else if account.Spec.Creds.File != "" { + natsConfig.Credentials = account.Spec.Creds.File + } + + return applyConnOpts(*natsConfig, opts), nil } -// or returns the value if it is not the null value. Otherwise, the fallback value is returned -func or[T comparable](v T, fallback T) T { - if v == *new(T) { - return fallback +func applyConnOpts(baseConfig NatsConfig, opts api.ConnectionOpts) *NatsConfig { + natsConfig := baseConfig + if len(opts.Servers) > 0 { + natsConfig.ServerURL = strings.Join(opts.Servers, ",") + } + + // Currently, if the global TLSFirst is set, a false value in the CRD will not override + // due to that being the bool zero value. A true value in the CRD can override a global false. + if opts.TLSFirst { + natsConfig.TLSFirst = opts.TLSFirst } - return v + + if opts.Creds != "" { + natsConfig.Credentials = opts.Creds + } + + if len(opts.TLS.RootCAs) > 0 { + natsConfig.CAs = opts.TLS.RootCAs + } + + if opts.TLS.ClientCert != "" && opts.TLS.ClientKey != "" { + natsConfig.Certificate = opts.TLS.ClientCert + natsConfig.Key = opts.TLS.ClientKey + } + + return &natsConfig } // updateReadyCondition returns the given conditions with an added or updated ready condition. @@ -159,8 +275,12 @@ func updateReadyCondition(conditions []api.Condition, status v1.ConditionStatus, } } -// asJsonString returns the given string wrapped in " and converted to []byte. +// jsonString returns the given string wrapped in " and converted to []byte. // Helper for mapping spec config to jetStream config using UnmarshalJSON. -func asJsonString(v string) []byte { +func jsonString(v string) []byte { return []byte("\"" + v + "\"") } + +func compareConfigState(actual any, desired any) string { + return cmp.Diff(actual, desired) +} diff --git a/internal/controller/keyvalue_controller.go b/internal/controller/keyvalue_controller.go index e174ee0c..11a4261f 100644 --- a/internal/controller/keyvalue_controller.go +++ b/internal/controller/keyvalue_controller.go @@ -18,6 +18,7 @@ package controller import ( "context" + "encoding/json" "errors" "fmt" "time" @@ -27,14 +28,21 @@ import ( "github.com/nats-io/nats.go/jetstream" v1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime" "k8s.io/klog/v2" ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/controller" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" "sigs.k8s.io/controller-runtime/pkg/predicate" ) +const ( + kvStreamPrefix = "KV_" +) + // KeyValueReconciler reconciles a KeyValue object type KeyValueReconciler struct { + Scheme *runtime.Scheme JetStreamController } @@ -61,7 +69,7 @@ func (r *KeyValueReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c keyValue := &api.KeyValue{} if err := r.Get(ctx, req.NamespacedName, keyValue); err != nil { if apierrors.IsNotFound(err) { - log.Info("KeyValue resource not found. Ignoring since object must be deleted.") + log.Info("KeyValue deleted.", "keyValueName", req.NamespacedName.String()) return ctrl.Result{}, nil } return ctrl.Result{}, fmt.Errorf("get keyvalue resource '%s': %w", req.NamespacedName.String(), err) @@ -72,7 +80,7 @@ func (r *KeyValueReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c // Update ready status to unknown when no status is set if len(keyValue.Status.Conditions) == 0 { log.Info("Setting initial ready condition to unknown.") - keyValue.Status.Conditions = updateReadyCondition(keyValue.Status.Conditions, v1.ConditionUnknown, "Reconciling", "Starting reconciliation") + keyValue.Status.Conditions = updateReadyCondition(keyValue.Status.Conditions, v1.ConditionUnknown, stateReconciling, "Starting reconciliation") err := r.Status().Update(ctx, keyValue) if err != nil { return ctrl.Result{}, fmt.Errorf("set condition unknown: %w", err) @@ -80,19 +88,6 @@ func (r *KeyValueReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c return ctrl.Result{Requeue: true}, nil } - // Add finalizer - if !controllerutil.ContainsFinalizer(keyValue, keyValueFinalizer) { - log.Info("Adding KeyValue finalizer.") - if ok := controllerutil.AddFinalizer(keyValue, keyValueFinalizer); !ok { - return ctrl.Result{}, errors.New("failed to add finalizer to keyvalue resource") - } - - if err := r.Update(ctx, keyValue); err != nil { - return ctrl.Result{}, fmt.Errorf("update keyvalue resource to add finalizer: %w", err) - } - return ctrl.Result{}, nil - } - // Check Deletion markedForDeletion := keyValue.GetDeletionTimestamp() != nil if markedForDeletion { @@ -108,27 +103,56 @@ func (r *KeyValueReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c return ctrl.Result{}, nil } + // Add finalizer + if !controllerutil.ContainsFinalizer(keyValue, keyValueFinalizer) { + log.Info("Adding KeyValue finalizer.") + if ok := controllerutil.AddFinalizer(keyValue, keyValueFinalizer); !ok { + return ctrl.Result{}, errors.New("failed to add finalizer to keyvalue resource") + } + + if err := r.Update(ctx, keyValue); err != nil { + return ctrl.Result{}, fmt.Errorf("update keyvalue resource to add finalizer: %w", err) + } + return ctrl.Result{}, nil + } + // Create or update KeyValue if err := r.createOrUpdate(ctx, log, keyValue); err != nil { return ctrl.Result{}, fmt.Errorf("create or update: %s", err) } - return ctrl.Result{}, nil + + return ctrl.Result{RequeueAfter: r.RequeueInterval()}, nil } func (r *KeyValueReconciler) deleteKeyValue(ctx context.Context, log logr.Logger, keyValue *api.KeyValue) error { // Set status to false - keyValue.Status.Conditions = updateReadyCondition(keyValue.Status.Conditions, v1.ConditionFalse, "Finalizing", "Performing finalizer operations.") + keyValue.Status.Conditions = updateReadyCondition(keyValue.Status.Conditions, v1.ConditionFalse, stateFinalizing, "Performing finalizer operations.") if err := r.Status().Update(ctx, keyValue); err != nil { return fmt.Errorf("update ready condition: %w", err) } + storedState, err := getStoredKeyValueState(keyValue) + if err != nil { + log.Error(err, "Failed to fetch stored state.") + } + if !keyValue.Spec.PreventDelete && !r.ReadOnly() { log.Info("Deleting KeyValue.") - err := r.WithJetStreamClient(keyValueConnOpts(keyValue.Spec), func(js jetstream.JetStream) error { + err := r.WithJetStreamClient(keyValue.Spec.ConnectionOpts, keyValue.Namespace, func(js jetstream.JetStream) error { + _, err := getServerKeyValueState(ctx, js, keyValue) + // If we have no known state for this KeyValue it has never been reconciled. + // If we are also receiving an error fetching state, either the KeyValue does not exist + // or this resource config is invalid. + if err != nil && storedState == nil { + return nil + } + return js.DeleteKeyValue(ctx, keyValue.Spec.Bucket) }) if errors.Is(err, jetstream.ErrBucketNotFound) { log.Info("KeyValue does not exist, unable to delete.", "keyValueName", keyValue.Spec.Bucket) + } else if err != nil && storedState == nil { + log.Info("KeyValue not reconciled and no state received from server. Removing finalizer.") } else if err != nil { return fmt.Errorf("delete keyvalue during finalization: %w", err) } @@ -152,13 +176,6 @@ func (r *KeyValueReconciler) deleteKeyValue(ctx context.Context, log logr.Logger func (r *KeyValueReconciler) createOrUpdate(ctx context.Context, log logr.Logger, keyValue *api.KeyValue) error { // Create or Update the KeyValue based on the spec - if r.ReadOnly() { - log.Info("Skipping KeyValue creation or update.", - "read-only", r.ReadOnly(), - ) - return nil - } - // Map spec to KeyValue targetConfig targetConfig, err := keyValueSpecToConfig(&keyValue.Spec) if err != nil { @@ -166,37 +183,83 @@ func (r *KeyValueReconciler) createOrUpdate(ctx context.Context, log logr.Logger } // UpdateKeyValue is called on every reconciliation when the stream is not to be deleted. - // TODO(future-feature): Do we need to check if config differs? - err = r.WithJetStreamClient(keyValueConnOpts(keyValue.Spec), func(js jetstream.JetStream) error { - exists := false - _, err := js.KeyValue(ctx, targetConfig.Bucket) - if err == nil { - exists = true - } else if !errors.Is(err, jetstream.ErrBucketNotFound) { - return err + err = r.WithJetStreamClient(keyValue.Spec.ConnectionOpts, keyValue.Namespace, func(js jetstream.JetStream) error { + storedState, err := getStoredKeyValueState(keyValue) + if err != nil { + log.Error(err, "Failed to fetch stored KeyValue state") } - if !exists { - log.Info("Creating KeyValue.") - _, err = js.CreateKeyValue(ctx, targetConfig) + serverState, err := getServerKeyValueState(ctx, js, keyValue) + if err != nil { return err } - if !keyValue.Spec.PreventUpdate { + // Check against known state. Skip Update if converged. + // Storing returned state from the server avoids have to + // check default values or call Update on already converged resources + if storedState != nil && serverState != nil && keyValue.Status.ObservedGeneration == keyValue.Generation { + diff := compareConfigState(storedState, serverState) + + if diff == "" { + return nil + } + + log.Info("KeyValue config drifted from desired state.", "diff", diff) + } + + if r.ReadOnly() { + log.Info("Skipping KeyValue creation or update.", + "read-only", r.ReadOnly(), + ) + return nil + } + + var updatedKeyValue jetstream.KeyValue + err = nil + + if serverState == nil { + log.Info("Creating KeyValue.") + updatedKeyValue, err = js.CreateKeyValue(ctx, targetConfig) + if err != nil { + return err + } + } else if !keyValue.Spec.PreventUpdate { log.Info("Updating KeyValue.") - _, err = js.UpdateKeyValue(ctx, targetConfig) - return err + updatedKeyValue, err = js.UpdateKeyValue(ctx, targetConfig) + if err != nil { + return err + } } else { log.Info("Skipping KeyValue update.", "preventUpdate", keyValue.Spec.PreventUpdate, ) } + if updatedKeyValue != nil { + // Store known state in annotation + serverState, err = getServerKeyValueState(ctx, js, keyValue) + if err != nil { + return err + } + + updatedState, err := json.Marshal(serverState) + if err != nil { + return err + } + + if keyValue.Annotations == nil { + keyValue.Annotations = map[string]string{} + } + keyValue.Annotations[stateAnnotationKV] = string(updatedState) + + return r.Update(ctx, keyValue) + } + return nil }) if err != nil { err = fmt.Errorf("create or update keyvalue: %w", err) - keyValue.Status.Conditions = updateReadyCondition(keyValue.Status.Conditions, v1.ConditionFalse, "Errored", err.Error()) + keyValue.Status.Conditions = updateReadyCondition(keyValue.Status.Conditions, v1.ConditionFalse, stateErrored, err.Error()) if err := r.Status().Update(ctx, keyValue); err != nil { log.Error(err, "Failed to update ready condition to Errored.") } @@ -208,7 +271,7 @@ func (r *KeyValueReconciler) createOrUpdate(ctx context.Context, log logr.Logger keyValue.Status.Conditions = updateReadyCondition( keyValue.Status.Conditions, v1.ConditionTrue, - "Reconciling", + stateReady, "KeyValue successfully created or updated.", ) err = r.Status().Update(ctx, keyValue) @@ -219,15 +282,30 @@ func (r *KeyValueReconciler) createOrUpdate(ctx context.Context, log logr.Logger return nil } -// keyValueConnOpts extracts nats connection relevant fields from the given KeyValue spec as connectionOptions. -func keyValueConnOpts(spec api.KeyValueSpec) *connectionOptions { - return &connectionOptions{ - Account: spec.Account, - Creds: spec.Creds, - Nkey: spec.Nkey, - Servers: spec.Servers, - TLS: spec.TLS, +func getStoredKeyValueState(keyValue *api.KeyValue) (*jetstream.StreamConfig, error) { + var storedState *jetstream.StreamConfig + if state, ok := keyValue.Annotations[stateAnnotationKV]; ok { + err := json.Unmarshal([]byte(state), &storedState) + if err != nil { + return nil, err + } } + + return storedState, nil +} + +// Fetch the current state of the KeyValue stream from the server. +// ErrStreamNotFound is considered a valid response and does not return error +func getServerKeyValueState(ctx context.Context, js jetstream.JetStream, keyValue *api.KeyValue) (*jetstream.StreamConfig, error) { + s, err := js.Stream(ctx, fmt.Sprintf("%s%s", kvStreamPrefix, keyValue.Spec.Bucket)) + if errors.Is(err, jetstream.ErrStreamNotFound) { + return nil, nil + } + if err != nil { + return nil, err + } + + return &s.CachedInfo().Config, nil } // keyValueSpecToConfig creates a jetstream.KeyValueConfig matching the given KeyValue resource spec @@ -254,7 +332,7 @@ func keyValueSpecToConfig(spec *api.KeyValueSpec) (jetstream.KeyValueConfig, err // storage if spec.Storage != "" { - err := config.Storage.UnmarshalJSON(asJsonString(spec.Storage)) + err := config.Storage.UnmarshalJSON(jsonString(spec.Storage)) if err != nil { return jetstream.KeyValueConfig{}, fmt.Errorf("invalid storage: %w", err) } @@ -305,8 +383,9 @@ func keyValueSpecToConfig(spec *api.KeyValueSpec) (jetstream.KeyValueConfig, err func (r *KeyValueReconciler) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). For(&api.KeyValue{}). - Owns(&api.KeyValue{}). - // Only trigger on generation changes WithEventFilter(predicate.GenerationChangedPredicate{}). + WithOptions(controller.Options{ + MaxConcurrentReconciles: 1, + }). Complete(r) } diff --git a/internal/controller/keyvalue_controller_test.go b/internal/controller/keyvalue_controller_test.go index 0333f9f1..82cac273 100644 --- a/internal/controller/keyvalue_controller_test.go +++ b/internal/controller/keyvalue_controller_test.go @@ -86,7 +86,8 @@ var _ = Describe("KeyValue Controller", func() { By("setting up the tested controller") controller = &KeyValueReconciler{ - baseController, + Scheme: k8sClient.Scheme(), + JetStreamController: baseController, } }) @@ -154,7 +155,7 @@ var _ = Describe("KeyValue Controller", func() { Expect(k8sClient.Get(ctx, typeNamespacedName, keyValue)).To(Succeed()) Expect(keyValue.Status.Conditions).To(HaveLen(1)) - assertReadyStateMatches(keyValue.Status.Conditions[0], v1.ConditionUnknown, "Reconciling", "Starting reconciliation", time.Now()) + assertReadyStateMatches(keyValue.Status.Conditions[0], v1.ConditionUnknown, stateReconciling, "Starting reconciliation", time.Now()) }) }) @@ -188,7 +189,7 @@ var _ = Describe("KeyValue Controller", func() { By("checking if the ready state was updated") Expect(keyValue.Status.Conditions).To(HaveLen(1)) - assertReadyStateMatches(keyValue.Status.Conditions[0], v1.ConditionTrue, "Reconciling", "created or updated", time.Now()) + assertReadyStateMatches(keyValue.Status.Conditions[0], v1.ConditionTrue, stateReady, "created or updated", time.Now()) By("checking if the observed generation matches") Expect(keyValue.Status.ObservedGeneration).To(Equal(keyValue.Generation)) @@ -243,9 +244,10 @@ var _ = Describe("KeyValue Controller", func() { When("read-only mode is enabled", func() { BeforeEach(func(ctx SpecContext) { By("setting read only on the controller") - readOnly, err := NewJSController(k8sClient, &NatsConfig{ServerURL: testServer.ClientURL()}, &Config{ReadOnly: true}) + readOnly, err := NewJSController(k8sClient, &NatsConfig{ServerURL: clientUrl}, &Config{ReadOnly: true}) Expect(err).NotTo(HaveOccurred()) controller = &KeyValueReconciler{ + Scheme: k8sClient.Scheme(), JetStreamController: readOnly, } }) @@ -283,9 +285,10 @@ var _ = Describe("KeyValue Controller", func() { When("namespace restriction is enabled", func() { BeforeEach(func(ctx SpecContext) { By("setting a namespace on the resource") - namespaced, err := NewJSController(k8sClient, &NatsConfig{ServerURL: testServer.ClientURL()}, &Config{Namespace: "other-namespace"}) + namespaced, err := NewJSController(k8sClient, &NatsConfig{ServerURL: clientUrl}, &Config{Namespace: "other-namespace"}) Expect(err).NotTo(HaveOccurred()) controller = &KeyValueReconciler{ + Scheme: k8sClient.Scheme(), JetStreamController: namespaced, } }) @@ -368,12 +371,13 @@ var _ = Describe("KeyValue Controller", func() { // Setup client for not running server // Use actual test server to ensure port not used by other service on test instance sv := CreateTestServer() - base, err := NewJSController(k8sClient, &NatsConfig{ServerURL: sv.ClientURL()}, &Config{}) + disconnectedController, err := NewJSController(k8sClient, &NatsConfig{ServerURL: sv.ClientURL()}, &Config{}) Expect(err).NotTo(HaveOccurred()) sv.Shutdown() controller := &KeyValueReconciler{ - base, + Scheme: k8sClient.Scheme(), + JetStreamController: disconnectedController, } By("reconciling resource") @@ -392,7 +396,7 @@ var _ = Describe("KeyValue Controller", func() { assertReadyStateMatches( keyValue.Status.Conditions[0], v1.ConditionFalse, - "Errored", + stateErrored, "create or update keyvalue:", time.Now(), ) @@ -476,9 +480,10 @@ var _ = Describe("KeyValue Controller", func() { When("read only is set", func() { BeforeEach(func(ctx SpecContext) { By("setting read only on the controller") - readOnly, err := NewJSController(k8sClient, &NatsConfig{ServerURL: testServer.ClientURL()}, &Config{ReadOnly: true}) + readOnly, err := NewJSController(k8sClient, &NatsConfig{ServerURL: clientUrl}, &Config{ReadOnly: true}) Expect(err).NotTo(HaveOccurred()) controller = &KeyValueReconciler{ + Scheme: k8sClient.Scheme(), JetStreamController: readOnly, } }) @@ -501,9 +506,10 @@ var _ = Describe("KeyValue Controller", func() { When("controller is restricted to different namespace", func() { BeforeEach(func(ctx SpecContext) { - namespaced, err := NewJSController(k8sClient, &NatsConfig{ServerURL: testServer.ClientURL()}, &Config{Namespace: "other-namespace"}) + namespaced, err := NewJSController(k8sClient, &NatsConfig{ServerURL: clientUrl}, &Config{Namespace: "other-namespace"}) Expect(err).NotTo(HaveOccurred()) controller = &KeyValueReconciler{ + Scheme: k8sClient.Scheme(), JetStreamController: namespaced, } }) @@ -623,13 +629,15 @@ func Test_mapKVSpecToConfig(t *testing.T) { }}, Storage: "memory", BaseStreamConfig: api.BaseStreamConfig{ - Account: "", - Creds: "", PreventDelete: false, PreventUpdate: false, - Nkey: "", - Servers: nil, - TLS: api.TLS{}, + ConnectionOpts: api.ConnectionOpts{ + Account: "", + Creds: "", + Nkey: "", + Servers: nil, + TLS: api.TLS{}, + }, }, }, want: jetstream.KeyValueConfig{ diff --git a/internal/controller/objectstore_controller.go b/internal/controller/objectstore_controller.go index 1d32f6f6..4f88fb69 100644 --- a/internal/controller/objectstore_controller.go +++ b/internal/controller/objectstore_controller.go @@ -18,6 +18,7 @@ package controller import ( "context" + "encoding/json" "errors" "fmt" "time" @@ -27,14 +28,22 @@ import ( "github.com/nats-io/nats.go/jetstream" v1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime" "k8s.io/klog/v2" ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/controller" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" "sigs.k8s.io/controller-runtime/pkg/predicate" ) +const ( + objStreamPrefix = "OBJ_" +) + // ObjectStoreReconciler reconciles a ObjectStore object type ObjectStoreReconciler struct { + Scheme *runtime.Scheme + JetStreamController } @@ -61,7 +70,7 @@ func (r *ObjectStoreReconciler) Reconcile(ctx context.Context, req ctrl.Request) objectStore := &api.ObjectStore{} if err := r.Get(ctx, req.NamespacedName, objectStore); err != nil { if apierrors.IsNotFound(err) { - log.Info("ObjectStore resource not found. Ignoring since object must be deleted.") + log.Info("ObjectStore deleted.", "objectStoreName", req.NamespacedName.String()) return ctrl.Result{}, nil } return ctrl.Result{}, fmt.Errorf("get objectstore resource '%s': %w", req.NamespacedName.String(), err) @@ -72,7 +81,7 @@ func (r *ObjectStoreReconciler) Reconcile(ctx context.Context, req ctrl.Request) // Update ready status to unknown when no status is set if len(objectStore.Status.Conditions) == 0 { log.Info("Setting initial ready condition to unknown.") - objectStore.Status.Conditions = updateReadyCondition(objectStore.Status.Conditions, v1.ConditionUnknown, "Reconciling", "Starting reconciliation") + objectStore.Status.Conditions = updateReadyCondition(objectStore.Status.Conditions, v1.ConditionUnknown, stateReconciling, "Starting reconciliation") err := r.Status().Update(ctx, objectStore) if err != nil { return ctrl.Result{}, fmt.Errorf("set condition unknown: %w", err) @@ -80,19 +89,6 @@ func (r *ObjectStoreReconciler) Reconcile(ctx context.Context, req ctrl.Request) return ctrl.Result{Requeue: true}, nil } - // Add finalizer - if !controllerutil.ContainsFinalizer(objectStore, objectStoreFinalizer) { - log.Info("Adding ObjectStore finalizer.") - if ok := controllerutil.AddFinalizer(objectStore, objectStoreFinalizer); !ok { - return ctrl.Result{}, errors.New("failed to add finalizer to objectstore resource") - } - - if err := r.Update(ctx, objectStore); err != nil { - return ctrl.Result{}, fmt.Errorf("update objectstore resource to add finalizer: %w", err) - } - return ctrl.Result{}, nil - } - // Check Deletion markedForDeletion := objectStore.GetDeletionTimestamp() != nil if markedForDeletion { @@ -108,28 +104,56 @@ func (r *ObjectStoreReconciler) Reconcile(ctx context.Context, req ctrl.Request) return ctrl.Result{}, nil } + // Add finalizer + if !controllerutil.ContainsFinalizer(objectStore, objectStoreFinalizer) { + log.Info("Adding ObjectStore finalizer.") + if ok := controllerutil.AddFinalizer(objectStore, objectStoreFinalizer); !ok { + return ctrl.Result{}, errors.New("failed to add finalizer to objectstore resource") + } + + if err := r.Update(ctx, objectStore); err != nil { + return ctrl.Result{}, fmt.Errorf("update objectstore resource to add finalizer: %w", err) + } + return ctrl.Result{}, nil + } + // Create or update ObjectStore if err := r.createOrUpdate(ctx, log, objectStore); err != nil { return ctrl.Result{}, fmt.Errorf("create or update: %s", err) } - return ctrl.Result{}, nil + + return ctrl.Result{RequeueAfter: r.RequeueInterval()}, nil } func (r *ObjectStoreReconciler) deleteObjectStore(ctx context.Context, log logr.Logger, objectStore *api.ObjectStore) error { // Set status to false - objectStore.Status.Conditions = updateReadyCondition(objectStore.Status.Conditions, v1.ConditionFalse, "Finalizing", "Performing finalizer operations.") + objectStore.Status.Conditions = updateReadyCondition(objectStore.Status.Conditions, v1.ConditionFalse, stateFinalizing, "Performing finalizer operations.") if err := r.Status().Update(ctx, objectStore); err != nil { return fmt.Errorf("update ready condition: %w", err) } + storedState, err := getStoredObjectStoreState(objectStore) + if err != nil { + log.Error(err, "Failed to fetch stored state.") + } + if !objectStore.Spec.PreventDelete && !r.ReadOnly() { log.Info("Deleting ObjectStore.") - err := r.WithJetStreamClient(objectStoreConnOpts(objectStore.Spec), func(js jetstream.JetStream) error { + err := r.WithJetStreamClient(objectStore.Spec.ConnectionOpts, objectStore.Namespace, func(js jetstream.JetStream) error { + _, err := getServerObjectStoreState(ctx, js, objectStore) + // If we have no known state for this object store it has never been reconciled. + // If we are also receiving an error fetching state, either the object store does not exist + // or this resource config is invalid. + if err != nil && storedState == nil { + return nil + } + return js.DeleteObjectStore(ctx, objectStore.Spec.Bucket) }) - // FIX: ErrStreamNotFound -> ErrBucketNotFound once nats.go is corrected - if errors.Is(err, jetstream.ErrStreamNotFound) { + if errors.Is(err, jetstream.ErrStreamNotFound) || errors.Is(err, jetstream.ErrBucketNotFound) { log.Info("ObjectStore does not exist, unable to delete.", "objectStoreName", objectStore.Spec.Bucket) + } else if err != nil && storedState == nil { + log.Info("ObjectStore not reconciled and no state received from server. Removing finalizer.") } else if err != nil { return fmt.Errorf("delete objectstore during finalization: %w", err) } @@ -153,13 +177,6 @@ func (r *ObjectStoreReconciler) deleteObjectStore(ctx context.Context, log logr. func (r *ObjectStoreReconciler) createOrUpdate(ctx context.Context, log logr.Logger, objectStore *api.ObjectStore) error { // Create or Update the ObjectStore based on the spec - if r.ReadOnly() { - log.Info("Skipping ObjectStore creation or update.", - "read-only", r.ReadOnly(), - ) - return nil - } - // Map spec to ObjectStore targetConfig targetConfig, err := objectStoreSpecToConfig(&objectStore.Spec) if err != nil { @@ -167,37 +184,83 @@ func (r *ObjectStoreReconciler) createOrUpdate(ctx context.Context, log logr.Log } // UpdateObjectStore is called on every reconciliation when the stream is not to be deleted. - // TODO(future-feature): Do we need to check if config differs? - err = r.WithJetStreamClient(objectStoreConnOpts(objectStore.Spec), func(js jetstream.JetStream) error { - exists := false - _, err := js.ObjectStore(ctx, targetConfig.Bucket) - if err == nil { - exists = true - } else if !errors.Is(err, jetstream.ErrBucketNotFound) { - return err + err = r.WithJetStreamClient(objectStore.Spec.ConnectionOpts, objectStore.Namespace, func(js jetstream.JetStream) error { + storedState, err := getStoredObjectStoreState(objectStore) + if err != nil { + log.Error(err, "Failed to fetch stored objectstore state") } - if !exists { - log.Info("Creating ObjectStore.") - _, err = js.CreateObjectStore(ctx, targetConfig) + serverState, err := getServerObjectStoreState(ctx, js, objectStore) + if err != nil { return err } - if !objectStore.Spec.PreventUpdate { + // Check against known state. Skip Update if converged. + // Storing returned state from the server avoids have to + // check default values or call Update on already converged resources + if storedState != nil && serverState != nil && objectStore.Status.ObservedGeneration == objectStore.Generation { + diff := compareConfigState(storedState, serverState) + + if diff == "" { + return nil + } + + log.Info("Object Store config drifted from desired state.", "diff", diff) + } + + if r.ReadOnly() { + log.Info("Skipping ObjectStore creation or update.", + "read-only", r.ReadOnly(), + ) + return nil + } + + var updatedObjectStore jetstream.ObjectStore + err = nil + + if serverState == nil { + log.Info("Creating ObjectStore.") + updatedObjectStore, err = js.CreateObjectStore(ctx, targetConfig) + if err != nil { + return err + } + } else if !objectStore.Spec.PreventUpdate { log.Info("Updating ObjectStore.") - _, err = js.UpdateObjectStore(ctx, targetConfig) - return err + updatedObjectStore, err = js.UpdateObjectStore(ctx, targetConfig) + if err != nil { + return err + } } else { log.Info("Skipping ObjectStore update.", "preventUpdate", objectStore.Spec.PreventUpdate, ) } + if updatedObjectStore != nil { + // Store known state in annotation + serverState, err = getServerObjectStoreState(ctx, js, objectStore) + if err != nil { + return err + } + + updatedState, err := json.Marshal(serverState) + if err != nil { + return err + } + + if objectStore.Annotations == nil { + objectStore.Annotations = map[string]string{} + } + objectStore.Annotations[stateAnnotationObj] = string(updatedState) + + return r.Update(ctx, objectStore) + } + return nil }) if err != nil { err = fmt.Errorf("create or update objectstore: %w", err) - objectStore.Status.Conditions = updateReadyCondition(objectStore.Status.Conditions, v1.ConditionFalse, "Errored", err.Error()) + objectStore.Status.Conditions = updateReadyCondition(objectStore.Status.Conditions, v1.ConditionFalse, stateErrored, err.Error()) if err := r.Status().Update(ctx, objectStore); err != nil { log.Error(err, "Failed to update ready condition to Errored.") } @@ -209,7 +272,7 @@ func (r *ObjectStoreReconciler) createOrUpdate(ctx context.Context, log logr.Log objectStore.Status.Conditions = updateReadyCondition( objectStore.Status.Conditions, v1.ConditionTrue, - "Reconciling", + stateReady, "ObjectStore successfully created or updated.", ) err = r.Status().Update(ctx, objectStore) @@ -220,15 +283,30 @@ func (r *ObjectStoreReconciler) createOrUpdate(ctx context.Context, log logr.Log return nil } -// objectStoreConnOpts extracts nats connection relevant fields from the given ObjectStore spec as connectionOptions. -func objectStoreConnOpts(spec api.ObjectStoreSpec) *connectionOptions { - return &connectionOptions{ - Account: spec.Account, - Creds: spec.Creds, - Nkey: spec.Nkey, - Servers: spec.Servers, - TLS: spec.TLS, +func getStoredObjectStoreState(objectStore *api.ObjectStore) (*jetstream.StreamConfig, error) { + var storedState *jetstream.StreamConfig + if state, ok := objectStore.Annotations[stateAnnotationObj]; ok { + err := json.Unmarshal([]byte(state), &storedState) + if err != nil { + return nil, err + } } + + return storedState, nil +} + +// Fetch the current state of the ObjectStore stream from the server. +// ErrStreamNotFound is considered a valid response and does not return error +func getServerObjectStoreState(ctx context.Context, js jetstream.JetStream, objectStore *api.ObjectStore) (*jetstream.StreamConfig, error) { + s, err := js.Stream(ctx, fmt.Sprintf("%s%s", objStreamPrefix, objectStore.Spec.Bucket)) + if errors.Is(err, jetstream.ErrStreamNotFound) { + return nil, nil + } + if err != nil { + return nil, err + } + + return &s.CachedInfo().Config, nil } // objectStoreSpecToConfig creates a jetstream.ObjectStoreConfig matching the given ObjectStore resource spec @@ -254,7 +332,7 @@ func objectStoreSpecToConfig(spec *api.ObjectStoreSpec) (jetstream.ObjectStoreCo // storage if spec.Storage != "" { - err := config.Storage.UnmarshalJSON(asJsonString(spec.Storage)) + err := config.Storage.UnmarshalJSON(jsonString(spec.Storage)) if err != nil { return jetstream.ObjectStoreConfig{}, fmt.Errorf("invalid storage: %w", err) } @@ -275,8 +353,9 @@ func objectStoreSpecToConfig(spec *api.ObjectStoreSpec) (jetstream.ObjectStoreCo func (r *ObjectStoreReconciler) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). For(&api.ObjectStore{}). - Owns(&api.ObjectStore{}). - // Only trigger on generation changes WithEventFilter(predicate.GenerationChangedPredicate{}). + WithOptions(controller.Options{ + MaxConcurrentReconciles: 1, + }). Complete(r) } diff --git a/internal/controller/objectstore_controller_test.go b/internal/controller/objectstore_controller_test.go index a2ecabf1..e3a0d999 100644 --- a/internal/controller/objectstore_controller_test.go +++ b/internal/controller/objectstore_controller_test.go @@ -85,7 +85,8 @@ var _ = Describe("ObjectStore Controller", func() { By("setting up the tested controller") controller = &ObjectStoreReconciler{ - baseController, + Scheme: k8sClient.Scheme(), + JetStreamController: baseController, } }) @@ -152,7 +153,7 @@ var _ = Describe("ObjectStore Controller", func() { Expect(k8sClient.Get(ctx, typeNamespacedName, objectStore)).To(Succeed()) Expect(objectStore.Status.Conditions).To(HaveLen(1)) - assertReadyStateMatches(objectStore.Status.Conditions[0], v1.ConditionUnknown, "Reconciling", "Starting reconciliation", time.Now()) + assertReadyStateMatches(objectStore.Status.Conditions[0], v1.ConditionUnknown, stateReconciling, "Starting reconciliation", time.Now()) }) }) @@ -186,7 +187,7 @@ var _ = Describe("ObjectStore Controller", func() { By("checking if the ready state was updated") Expect(objectStore.Status.Conditions).To(HaveLen(1)) - assertReadyStateMatches(objectStore.Status.Conditions[0], v1.ConditionTrue, "Reconciling", "created or updated", time.Now()) + assertReadyStateMatches(objectStore.Status.Conditions[0], v1.ConditionTrue, stateReady, "created or updated", time.Now()) By("checking if the observed generation matches") Expect(objectStore.Status.ObservedGeneration).To(Equal(objectStore.Generation)) @@ -239,9 +240,10 @@ var _ = Describe("ObjectStore Controller", func() { When("read-only mode is enabled", func() { BeforeEach(func(ctx SpecContext) { By("setting read only on the controller") - readOnly, err := NewJSController(k8sClient, &NatsConfig{ServerURL: testServer.ClientURL()}, &Config{ReadOnly: true}) + readOnly, err := NewJSController(k8sClient, &NatsConfig{ServerURL: clientUrl}, &Config{ReadOnly: true}) Expect(err).NotTo(HaveOccurred()) controller = &ObjectStoreReconciler{ + Scheme: k8sClient.Scheme(), JetStreamController: readOnly, } }) @@ -278,9 +280,10 @@ var _ = Describe("ObjectStore Controller", func() { When("namespace restriction is enabled", func() { BeforeEach(func(ctx SpecContext) { By("setting a namespace on the resource") - namespaced, err := NewJSController(k8sClient, &NatsConfig{ServerURL: testServer.ClientURL()}, &Config{Namespace: "other-namespace"}) + namespaced, err := NewJSController(k8sClient, &NatsConfig{ServerURL: clientUrl}, &Config{Namespace: "other-namespace"}) Expect(err).NotTo(HaveOccurred()) controller = &ObjectStoreReconciler{ + Scheme: k8sClient.Scheme(), JetStreamController: namespaced, } }) @@ -360,12 +363,13 @@ var _ = Describe("ObjectStore Controller", func() { // Setup client for not running server // Use actual test server to ensure port not used by other service on test instance sv := CreateTestServer() - base, err := NewJSController(k8sClient, &NatsConfig{ServerURL: sv.ClientURL()}, &Config{}) + disconnectedController, err := NewJSController(k8sClient, &NatsConfig{ServerURL: sv.ClientURL()}, &Config{}) Expect(err).NotTo(HaveOccurred()) sv.Shutdown() controller := &ObjectStoreReconciler{ - base, + Scheme: k8sClient.Scheme(), + JetStreamController: disconnectedController, } By("reconciling resource") @@ -384,7 +388,7 @@ var _ = Describe("ObjectStore Controller", func() { assertReadyStateMatches( objectStore.Status.Conditions[0], v1.ConditionFalse, - "Errored", + stateErrored, "create or update objectstore:", time.Now(), ) @@ -468,9 +472,10 @@ var _ = Describe("ObjectStore Controller", func() { When("read only is set", func() { BeforeEach(func(ctx SpecContext) { By("setting read only on the controller") - readOnly, err := NewJSController(k8sClient, &NatsConfig{ServerURL: testServer.ClientURL()}, &Config{ReadOnly: true}) + readOnly, err := NewJSController(k8sClient, &NatsConfig{ServerURL: clientUrl}, &Config{ReadOnly: true}) Expect(err).NotTo(HaveOccurred()) controller = &ObjectStoreReconciler{ + Scheme: k8sClient.Scheme(), JetStreamController: readOnly, } }) @@ -493,9 +498,10 @@ var _ = Describe("ObjectStore Controller", func() { When("controller is restricted to different namespace", func() { BeforeEach(func(ctx SpecContext) { - namespaced, err := NewJSController(k8sClient, &NatsConfig{ServerURL: testServer.ClientURL()}, &Config{Namespace: "other-namespace"}) + namespaced, err := NewJSController(k8sClient, &NatsConfig{ServerURL: clientUrl}, &Config{Namespace: "other-namespace"}) Expect(err).NotTo(HaveOccurred()) controller = &ObjectStoreReconciler{ + Scheme: k8sClient.Scheme(), JetStreamController: namespaced, } }) @@ -584,13 +590,15 @@ func Test_mapobjectstoreSpecToConfig(t *testing.T) { "foo": "bar", }, BaseStreamConfig: api.BaseStreamConfig{ - Account: "", - Creds: "", PreventDelete: false, PreventUpdate: false, - Nkey: "", - Servers: nil, - TLS: api.TLS{}, + ConnectionOpts: api.ConnectionOpts{ + Account: "", + Creds: "", + Nkey: "", + Servers: nil, + TLS: api.TLS{}, + }, }, }, want: jetstream.ObjectStoreConfig{ diff --git a/internal/controller/register.go b/internal/controller/register.go index 8c5c8def..d51696d6 100644 --- a/internal/controller/register.go +++ b/internal/controller/register.go @@ -2,6 +2,7 @@ package controller import ( "fmt" + "time" ctrl "sigs.k8s.io/controller-runtime" ) @@ -12,33 +13,54 @@ import ( // // Namespace restricts the controller to resources of the given namespace. type Config struct { - ReadOnly bool - Namespace string + ReadOnly bool + Namespace string + RequeueInterval time.Duration + CacheDir string } // RegisterAll registers all available jetStream controllers to the manager. // natsCfg is specific to the nats server connection. // controllerCfg defines behaviour of the registered controllers. func RegisterAll(mgr ctrl.Manager, clientConfig *NatsConfig, config *Config) error { + scheme := mgr.GetScheme() + + // Register controllers baseController, err := NewJSController(mgr.GetClient(), clientConfig, config) if err != nil { return fmt.Errorf("create base jetstream controller: %w", err) } - // Register controllers if err := (&AccountReconciler{ - baseController, + Scheme: scheme, + JetStreamController: baseController, }).SetupWithManager(mgr); err != nil { return fmt.Errorf("unable to create account controller: %w", err) } if err := (&ConsumerReconciler{ - baseController, + Scheme: scheme, + JetStreamController: baseController, }).SetupWithManager(mgr); err != nil { return fmt.Errorf("unable to create consumer controller: %w", err) } + if err := (&KeyValueReconciler{ + Scheme: scheme, + JetStreamController: baseController, + }).SetupWithManager(mgr); err != nil { + return fmt.Errorf("unable to create key-value controller: %w", err) + } + + if err := (&ObjectStoreReconciler{ + Scheme: scheme, + JetStreamController: baseController, + }).SetupWithManager(mgr); err != nil { + return fmt.Errorf("unable to create object store controller: %w", err) + } + if err := (&StreamReconciler{ + Scheme: scheme, JetStreamController: baseController, }).SetupWithManager(mgr); err != nil { return fmt.Errorf("unable to create stream controller: %w", err) diff --git a/internal/controller/stream_controller.go b/internal/controller/stream_controller.go index 511b9e72..49d18554 100644 --- a/internal/controller/stream_controller.go +++ b/internal/controller/stream_controller.go @@ -18,6 +18,7 @@ package controller import ( "context" + "encoding/json" "errors" "fmt" "time" @@ -27,14 +28,18 @@ import ( "github.com/nats-io/nats.go/jetstream" v1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime" "k8s.io/klog/v2" ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/controller" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" "sigs.k8s.io/controller-runtime/pkg/predicate" ) // StreamReconciler reconciles a Stream object type StreamReconciler struct { + Scheme *runtime.Scheme + JetStreamController } @@ -61,7 +66,7 @@ func (r *StreamReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctr stream := &api.Stream{} if err := r.Get(ctx, req.NamespacedName, stream); err != nil { if apierrors.IsNotFound(err) { - log.Info("Stream resource not found. Ignoring since object must be deleted.") + log.Info("Stream deleted.", "streamName", req.NamespacedName.String()) return ctrl.Result{}, nil } return ctrl.Result{}, fmt.Errorf("get stream resource '%s': %w", req.NamespacedName.String(), err) @@ -72,7 +77,7 @@ func (r *StreamReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctr // Update ready status to unknown when no status is set if len(stream.Status.Conditions) == 0 { log.Info("Setting initial ready condition to unknown.") - stream.Status.Conditions = updateReadyCondition(stream.Status.Conditions, v1.ConditionUnknown, "Reconciling", "Starting reconciliation") + stream.Status.Conditions = updateReadyCondition(stream.Status.Conditions, v1.ConditionUnknown, stateReconciling, "Starting reconciliation") err := r.Status().Update(ctx, stream) if err != nil { return ctrl.Result{}, fmt.Errorf("set condition unknown: %w", err) @@ -80,19 +85,6 @@ func (r *StreamReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctr return ctrl.Result{Requeue: true}, nil } - // Add finalizer - if !controllerutil.ContainsFinalizer(stream, streamFinalizer) { - log.Info("Adding stream finalizer.") - if ok := controllerutil.AddFinalizer(stream, streamFinalizer); !ok { - return ctrl.Result{}, errors.New("failed to add finalizer to stream resource") - } - - if err := r.Update(ctx, stream); err != nil { - return ctrl.Result{}, fmt.Errorf("update stream resource to add finalizer: %w", err) - } - return ctrl.Result{}, nil - } - // Check Deletion markedForDeletion := stream.GetDeletionTimestamp() != nil if markedForDeletion { @@ -108,27 +100,56 @@ func (r *StreamReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctr return ctrl.Result{}, nil } + // Add finalizer + if !controllerutil.ContainsFinalizer(stream, streamFinalizer) { + log.Info("Adding stream finalizer.") + if ok := controllerutil.AddFinalizer(stream, streamFinalizer); !ok { + return ctrl.Result{}, errors.New("failed to add finalizer to stream resource") + } + + if err := r.Update(ctx, stream); err != nil { + return ctrl.Result{}, fmt.Errorf("update stream resource to add finalizer: %w", err) + } + return ctrl.Result{}, nil + } + // Create or update stream if err := r.createOrUpdate(ctx, log, stream); err != nil { return ctrl.Result{}, fmt.Errorf("create or update: %s", err) } - return ctrl.Result{}, nil + + return ctrl.Result{RequeueAfter: r.RequeueInterval()}, nil } func (r *StreamReconciler) deleteStream(ctx context.Context, log logr.Logger, stream *api.Stream) error { // Set status to false - stream.Status.Conditions = updateReadyCondition(stream.Status.Conditions, v1.ConditionFalse, "Finalizing", "Performing finalizer operations.") + stream.Status.Conditions = updateReadyCondition(stream.Status.Conditions, v1.ConditionFalse, stateFinalizing, "Performing finalizer operations.") if err := r.Status().Update(ctx, stream); err != nil { return fmt.Errorf("update ready condition: %w", err) } + storedState, err := getStoredStreamState(stream) + if err != nil { + log.Error(err, "Failed to fetch stored state.") + } + if !stream.Spec.PreventDelete && !r.ReadOnly() { log.Info("Deleting stream.") - err := r.WithJetStreamClient(streamConnOpts(stream.Spec), func(js jetstream.JetStream) error { + err := r.WithJetStreamClient(stream.Spec.ConnectionOpts, stream.Namespace, func(js jetstream.JetStream) error { + _, err := getServerStreamState(ctx, js, stream) + // If we have no known state for this stream it has never been reconciled. + // If we are also receiving an error fetching state, either the stream does not exist + // or this resource config is invalid. + if err != nil && storedState == nil { + return nil + } + return js.DeleteStream(ctx, stream.Spec.Name) }) if errors.Is(err, jetstream.ErrStreamNotFound) { log.Info("Stream does not exist, unable to delete.", "streamName", stream.Spec.Name) + } else if err != nil && storedState == nil { + log.Info("Stream not reconciled and no state received from server. Removing finalizer.") } else if err != nil { return fmt.Errorf("delete stream during finalization: %w", err) } @@ -152,13 +173,6 @@ func (r *StreamReconciler) deleteStream(ctx context.Context, log logr.Logger, st func (r *StreamReconciler) createOrUpdate(ctx context.Context, log logr.Logger, stream *api.Stream) error { // Create or Update the stream based on the spec - if r.ReadOnly() { - log.Info("Skipping stream creation or update.", - "read-only", r.ReadOnly(), - ) - return nil - } - // Map spec to stream targetConfig targetConfig, err := streamSpecToConfig(&stream.Spec) if err != nil { @@ -166,37 +180,78 @@ func (r *StreamReconciler) createOrUpdate(ctx context.Context, log logr.Logger, } // CreateOrUpdateStream is called on every reconciliation when the stream is not to be deleted. - // TODO(future-feature): Do we need to check if config differs? - err = r.WithJetStreamClient(streamConnOpts(stream.Spec), func(js jetstream.JetStream) error { - exists := false - _, err := js.Stream(ctx, targetConfig.Name) - if err == nil { - exists = true - } else if !errors.Is(err, jetstream.ErrStreamNotFound) { - return err + err = r.WithJetStreamClient(stream.Spec.ConnectionOpts, stream.Namespace, func(js jetstream.JetStream) error { + storedState, err := getStoredStreamState(stream) + if err != nil { + log.Error(err, "Failed to fetch stored stream state") } - if !exists { - log.Info("Creating Stream.") - _, err := js.CreateStream(ctx, targetConfig) + serverState, err := getServerStreamState(ctx, js, stream) + if err != nil { return err } - if !stream.Spec.PreventUpdate { + // Check against known state. Skip Update if converged. + // Storing returned state from the server avoids have to + // check default values or call Update on already converged resources + if storedState != nil && serverState != nil && stream.Status.ObservedGeneration == stream.Generation { + diff := compareConfigState(storedState, serverState) + + if diff == "" { + return nil + } + + log.Info("Stream config drifted from desired state.", "diff", diff) + } + + if r.ReadOnly() { + log.Info("Skipping stream creation or update.", + "read-only", r.ReadOnly(), + ) + return nil + } + + var updatedStream jetstream.Stream + err = nil + + if serverState == nil { + log.Info("Creating Stream.") + updatedStream, err = js.CreateStream(ctx, targetConfig) + if err != nil { + return err + } + } else if !stream.Spec.PreventUpdate { log.Info("Updating Stream.") - _, err := js.UpdateStream(ctx, targetConfig) - return err + updatedStream, err = js.UpdateStream(ctx, targetConfig) + if err != nil { + return err + } } else { log.Info("Skipping Stream update.", "preventUpdate", stream.Spec.PreventUpdate, ) } + if updatedStream != nil { + // Store known state in annotation + updatedState, err := json.Marshal(updatedStream.CachedInfo().Config) + if err != nil { + return err + } + + if stream.Annotations == nil { + stream.Annotations = map[string]string{} + } + stream.Annotations[stateAnnotationStream] = string(updatedState) + + return r.Update(ctx, stream) + } + return nil }) if err != nil { err = fmt.Errorf("create or update stream: %w", err) - stream.Status.Conditions = updateReadyCondition(stream.Status.Conditions, v1.ConditionFalse, "Errored", err.Error()) + stream.Status.Conditions = updateReadyCondition(stream.Status.Conditions, v1.ConditionFalse, stateErrored, err.Error()) if err := r.Status().Update(ctx, stream); err != nil { log.Error(err, "Failed to update ready condition to Errored.") } @@ -208,7 +263,7 @@ func (r *StreamReconciler) createOrUpdate(ctx context.Context, log logr.Logger, stream.Status.Conditions = updateReadyCondition( stream.Status.Conditions, v1.ConditionTrue, - "Reconciling", + stateReady, "Stream successfully created or updated.", ) err = r.Status().Update(ctx, stream) @@ -219,15 +274,30 @@ func (r *StreamReconciler) createOrUpdate(ctx context.Context, log logr.Logger, return nil } -// streamConnOpts extracts nats connection relevant fields from the given stream spec as connectionOptions. -func streamConnOpts(spec api.StreamSpec) *connectionOptions { - return &connectionOptions{ - Account: spec.Account, - Creds: spec.Creds, - Nkey: spec.Nkey, - Servers: spec.Servers, - TLS: spec.TLS, +func getStoredStreamState(stream *api.Stream) (*jetstream.StreamConfig, error) { + var storedState *jetstream.StreamConfig + if state, ok := stream.Annotations[stateAnnotationStream]; ok { + err := json.Unmarshal([]byte(state), &storedState) + if err != nil { + return nil, err + } } + + return storedState, nil +} + +// Fetch the current state of the stream from the server. +// ErrStreamNotFound is considered a valid response and does not return error +func getServerStreamState(ctx context.Context, js jetstream.JetStream, stream *api.Stream) (*jetstream.StreamConfig, error) { + s, err := js.Stream(ctx, stream.Spec.Name) + if errors.Is(err, jetstream.ErrStreamNotFound) { + return nil, nil + } + if err != nil { + return nil, err + } + + return &s.CachedInfo().Config, nil } // streamSpecToConfig creates a jetstream.StreamConfig matching the given stream resource spec @@ -260,7 +330,7 @@ func streamSpecToConfig(spec *api.StreamSpec) (jetstream.StreamConfig, error) { // retention if spec.Retention != "" { // Wrap string in " to be properly unmarshalled as json string - err := config.Retention.UnmarshalJSON(asJsonString(spec.Retention)) + err := config.Retention.UnmarshalJSON(jsonString(spec.Retention)) if err != nil { return jetstream.StreamConfig{}, fmt.Errorf("invalid retention policy: %w", err) } @@ -268,7 +338,7 @@ func streamSpecToConfig(spec *api.StreamSpec) (jetstream.StreamConfig, error) { // discard if spec.Discard != "" { - err := config.Discard.UnmarshalJSON(asJsonString(spec.Discard)) + err := config.Discard.UnmarshalJSON(jsonString(spec.Discard)) if err != nil { return jetstream.StreamConfig{}, fmt.Errorf("invalid retention policy: %w", err) } @@ -284,7 +354,7 @@ func streamSpecToConfig(spec *api.StreamSpec) (jetstream.StreamConfig, error) { } // storage if spec.Storage != "" { - err := config.Storage.UnmarshalJSON(asJsonString(spec.Storage)) + err := config.Storage.UnmarshalJSON(jsonString(spec.Storage)) if err != nil { return jetstream.StreamConfig{}, fmt.Errorf("invalid storage: %w", err) } @@ -330,7 +400,7 @@ func streamSpecToConfig(spec *api.StreamSpec) (jetstream.StreamConfig, error) { // compression if spec.Compression != "" { - err := config.Compression.UnmarshalJSON(asJsonString(spec.Compression)) + err := config.Compression.UnmarshalJSON(jsonString(spec.Compression)) if err != nil { return jetstream.StreamConfig{}, fmt.Errorf("invalid compression: %w", err) } @@ -406,8 +476,9 @@ func mapStreamSource(ss *api.StreamSource) (*jetstream.StreamSource, error) { func (r *StreamReconciler) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). For(&api.Stream{}). - Owns(&api.Stream{}). - // Only trigger on generation changes WithEventFilter(predicate.GenerationChangedPredicate{}). + WithOptions(controller.Options{ + MaxConcurrentReconciles: 1, + }). Complete(r) } diff --git a/internal/controller/stream_controller_test.go b/internal/controller/stream_controller_test.go index 8ba50b1b..b9ac6c6b 100644 --- a/internal/controller/stream_controller_test.go +++ b/internal/controller/stream_controller_test.go @@ -88,7 +88,8 @@ var _ = Describe("Stream Controller", func() { By("setting up the tested controller") controller = &StreamReconciler{ - baseController, + Scheme: k8sClient.Scheme(), + JetStreamController: baseController, } }) @@ -156,7 +157,7 @@ var _ = Describe("Stream Controller", func() { Expect(k8sClient.Get(ctx, typeNamespacedName, stream)).To(Succeed()) Expect(stream.Status.Conditions).To(HaveLen(1)) - assertReadyStateMatches(stream.Status.Conditions[0], v1.ConditionUnknown, "Reconciling", "Starting reconciliation", time.Now()) + assertReadyStateMatches(stream.Status.Conditions[0], v1.ConditionUnknown, stateReconciling, "Starting reconciliation", time.Now()) }) }) @@ -190,7 +191,7 @@ var _ = Describe("Stream Controller", func() { By("checking if the ready state was updated") Expect(stream.Status.Conditions).To(HaveLen(1)) - assertReadyStateMatches(stream.Status.Conditions[0], v1.ConditionTrue, "Reconciling", "created or updated", time.Now()) + assertReadyStateMatches(stream.Status.Conditions[0], v1.ConditionTrue, stateReady, "created or updated", time.Now()) By("checking if the observed generation matches") Expect(stream.Status.ObservedGeneration).To(Equal(stream.Generation)) @@ -254,9 +255,10 @@ var _ = Describe("Stream Controller", func() { When("read-only mode is enabled", func() { BeforeEach(func(ctx SpecContext) { By("setting read only on the controller") - readOnly, err := NewJSController(k8sClient, &NatsConfig{ServerURL: testServer.ClientURL()}, &Config{ReadOnly: true}) + readOnly, err := NewJSController(k8sClient, &NatsConfig{ServerURL: clientUrl}, &Config{ReadOnly: true}) Expect(err).NotTo(HaveOccurred()) controller = &StreamReconciler{ + Scheme: k8sClient.Scheme(), JetStreamController: readOnly, } }) @@ -291,9 +293,10 @@ var _ = Describe("Stream Controller", func() { When("namespace restriction is enabled", func() { BeforeEach(func(ctx SpecContext) { By("setting a namespace on the resource") - namespaced, err := NewJSController(k8sClient, &NatsConfig{ServerURL: testServer.ClientURL()}, &Config{Namespace: "other-namespace"}) + namespaced, err := NewJSController(k8sClient, &NatsConfig{ServerURL: clientUrl}, &Config{Namespace: "other-namespace"}) Expect(err).NotTo(HaveOccurred()) controller = &StreamReconciler{ + Scheme: k8sClient.Scheme(), JetStreamController: namespaced, } }) @@ -372,12 +375,13 @@ var _ = Describe("Stream Controller", func() { // Setup client for not running server // Use actual test server to ensure port not used by other service on test instance sv := CreateTestServer() - base, err := NewJSController(k8sClient, &NatsConfig{ServerURL: sv.ClientURL()}, &Config{}) + disconnectedController, err := NewJSController(k8sClient, &NatsConfig{ServerURL: sv.ClientURL()}, &Config{}) Expect(err).NotTo(HaveOccurred()) sv.Shutdown() controller := &StreamReconciler{ - base, + Scheme: k8sClient.Scheme(), + JetStreamController: disconnectedController, } By("reconciling resource") @@ -396,7 +400,7 @@ var _ = Describe("Stream Controller", func() { assertReadyStateMatches( stream.Status.Conditions[0], v1.ConditionFalse, - "Errored", + stateErrored, "create or update stream:", time.Now(), ) @@ -480,9 +484,10 @@ var _ = Describe("Stream Controller", func() { When("read only is set", func() { BeforeEach(func(ctx SpecContext) { By("setting read only on the controller") - readOnly, err := NewJSController(k8sClient, &NatsConfig{ServerURL: testServer.ClientURL()}, &Config{ReadOnly: true}) + readOnly, err := NewJSController(k8sClient, &NatsConfig{ServerURL: clientUrl}, &Config{ReadOnly: true}) Expect(err).NotTo(HaveOccurred()) controller = &StreamReconciler{ + Scheme: k8sClient.Scheme(), JetStreamController: readOnly, } }) @@ -505,9 +510,10 @@ var _ = Describe("Stream Controller", func() { When("controller is restricted to different namespace", func() { BeforeEach(func(ctx SpecContext) { - namespaced, err := NewJSController(k8sClient, &NatsConfig{ServerURL: testServer.ClientURL()}, &Config{Namespace: "other-namespace"}) + namespaced, err := NewJSController(k8sClient, &NatsConfig{ServerURL: clientUrl}, &Config{Namespace: "other-namespace"}) Expect(err).NotTo(HaveOccurred()) controller = &StreamReconciler{ + Scheme: k8sClient.Scheme(), JetStreamController: namespaced, } }) @@ -648,13 +654,15 @@ func Test_mapSpecToConfig(t *testing.T) { Storage: "file", Subjects: []string{"orders.*"}, BaseStreamConfig: api.BaseStreamConfig{ - Account: "", - Creds: "", - Nkey: "", PreventDelete: false, PreventUpdate: false, - Servers: nil, - TLS: api.TLS{}, + ConnectionOpts: api.ConnectionOpts{ + Account: "", + Creds: "", + Nkey: "", + Servers: nil, + TLS: api.TLS{}, + }, }, }, want: jetstream.StreamConfig{ diff --git a/internal/controller/suite_test.go b/internal/controller/suite_test.go index 75bf60f5..a445def9 100644 --- a/internal/controller/suite_test.go +++ b/internal/controller/suite_test.go @@ -36,7 +36,7 @@ import ( logf "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/log/zap" - jetstreamnatsiov1beta2 "github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2" + api "github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2" ) // These tests use Ginkgo (BDD-style Go testing framework). Refer to @@ -47,6 +47,7 @@ var ( k8sClient client.Client testEnv *envtest.Environment testServer *server.Server + clientUrl string jsClient jetstream.JetStream baseController JetStreamController ) @@ -80,7 +81,7 @@ var _ = BeforeSuite(func() { Expect(err).NotTo(HaveOccurred()) Expect(cfg).NotTo(BeNil()) - err = jetstreamnatsiov1beta2.AddToScheme(scheme.Scheme) + err = api.AddToScheme(scheme.Scheme) Expect(err).NotTo(HaveOccurred()) k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) @@ -91,7 +92,8 @@ var _ = BeforeSuite(func() { testServer = CreateTestServer() Expect(err).NotTo(HaveOccurred()) - testNatsConfig := &NatsConfig{ServerURL: testServer.ClientURL()} + clientUrl = testServer.ClientURL() + testNatsConfig := &NatsConfig{ServerURL: clientUrl} baseController, err = NewJSController(k8sClient, testNatsConfig, &Config{}) Expect(err).NotTo(HaveOccurred()) jsClient, _, err = CreateJetStreamClient(testNatsConfig, true) diff --git a/internal/controller/types.go b/internal/controller/types.go index bd4561de..b9587735 100644 --- a/internal/controller/types.go +++ b/internal/controller/types.go @@ -2,8 +2,19 @@ package controller const ( readyCondType = "Ready" + accountFinalizer = "account.nats.io/finalizer" streamFinalizer = "stream.nats.io/finalizer" keyValueFinalizer = "kv.nats.io/finalizer" objectStoreFinalizer = "objectstore.nats.io/finalizer" consumerFinalizer = "consumer.nats.io/finalizer" + + stateAnnotationConsumer = "consumer.nats.io/state" + stateAnnotationKV = "kv.nats.io/state" + stateAnnotationObj = "objectstore.nats.io/state" + stateAnnotationStream = "stream.nats.io/state" + + stateReady = "Ready" + stateReconciling = "Reconciling" + stateErrored = "Errored" + stateFinalizing = "Finalizing" ) diff --git a/pkg/jetstream/apis/jetstream/v1beta2/consumertypes.go b/pkg/jetstream/apis/jetstream/v1beta2/consumertypes.go index d565e5c5..7ec1ec59 100644 --- a/pkg/jetstream/apis/jetstream/v1beta2/consumertypes.go +++ b/pkg/jetstream/apis/jetstream/v1beta2/consumertypes.go @@ -22,41 +22,41 @@ func (c *Consumer) GetSpec() interface{} { // ConsumerSpec is the spec for a Consumer resource type ConsumerSpec struct { + DurableName string `json:"durableName"` // Maps to Durable + Description string `json:"description"` + DeliverPolicy string `json:"deliverPolicy"` + OptStartSeq int `json:"optStartSeq"` + OptStartTime string `json:"optStartTime"` AckPolicy string `json:"ackPolicy"` AckWait string `json:"ackWait"` + MaxDeliver int `json:"maxDeliver"` BackOff []string `json:"backoff"` - Creds string `json:"creds"` - DeliverGroup string `json:"deliverGroup"` - DeliverPolicy string `json:"deliverPolicy"` - DeliverSubject string `json:"deliverSubject"` - Description string `json:"description"` - PreventDelete bool `json:"preventDelete"` - PreventUpdate bool `json:"preventUpdate"` - DurableName string `json:"durableName"` FilterSubject string `json:"filterSubject"` - FilterSubjects []string `json:"filterSubjects"` - FlowControl bool `json:"flowControl"` - HeadersOnly bool `json:"headersOnly"` - HeartbeatInterval string `json:"heartbeatInterval"` + ReplayPolicy string `json:"replayPolicy"` + RateLimitBps int `json:"rateLimitBps"` // Maps to RateLimit + SampleFreq string `json:"sampleFreq"` // Maps to SampleFrequency + MaxWaiting int `json:"maxWaiting"` MaxAckPending int `json:"maxAckPending"` - MaxDeliver int `json:"maxDeliver"` + HeadersOnly bool `json:"headersOnly"` MaxRequestBatch int `json:"maxRequestBatch"` MaxRequestExpires string `json:"maxRequestExpires"` MaxRequestMaxBytes int `json:"maxRequestMaxBytes"` - MaxWaiting int `json:"maxWaiting"` - MemStorage bool `json:"memStorage"` - Nkey string `json:"nkey"` - OptStartSeq int `json:"optStartSeq"` - OptStartTime string `json:"optStartTime"` - RateLimitBps int `json:"rateLimitBps"` - ReplayPolicy string `json:"replayPolicy"` + InactiveThreshold string `json:"inactiveThreshold"` Replicas int `json:"replicas"` - SampleFreq string `json:"sampleFreq"` - Servers []string `json:"servers"` - StreamName string `json:"streamName"` - TLS TLS `json:"tls"` - Account string `json:"account"` + MemStorage bool `json:"memStorage"` // Maps to MemoryStorage + FilterSubjects []string `json:"filterSubjects"` Metadata map[string]string `json:"metadata"` + + // Legacy API options for Push Consumers. + // controller-runtime implementation moves to modern JetStream API over legacy + // which does not support Push Consumers + FlowControl bool `json:"flowControl"` + DeliverSubject string `json:"deliverSubject"` + DeliverGroup string `json:"deliverGroup"` + HeartbeatInterval string `json:"heartbeatInterval"` + + StreamName string `json:"streamName"` + BaseStreamConfig } // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object diff --git a/pkg/jetstream/apis/jetstream/v1beta2/types.go b/pkg/jetstream/apis/jetstream/v1beta2/types.go index 966389e8..c0d5cd80 100644 --- a/pkg/jetstream/apis/jetstream/v1beta2/types.go +++ b/pkg/jetstream/apis/jetstream/v1beta2/types.go @@ -23,13 +23,18 @@ type Condition struct { } type BaseStreamConfig struct { - Account string `json:"account"` - Creds string `json:"creds"` - Nkey string `json:"nkey"` - PreventDelete bool `json:"preventDelete"` - PreventUpdate bool `json:"preventUpdate"` - Servers []string `json:"servers"` - TLS TLS `json:"tls"` + PreventDelete bool `json:"preventDelete"` + PreventUpdate bool `json:"preventUpdate"` + ConnectionOpts +} + +type ConnectionOpts struct { + Account string `json:"account"` + Creds string `json:"creds"` + Nkey string `json:"nkey"` + Servers []string `json:"servers"` + TLS TLS `json:"tls"` + TLSFirst bool `json:"tlsFirst"` } type ConsumerLimits struct { @@ -51,8 +56,8 @@ type TLSSecret struct { } type CredsSecret struct { - File string `json:"file"` - Secret SecretRef `json:"secret"` + File string `json:"file"` + Secret *SecretRef `json:"secret"` } type TokenSecret struct { diff --git a/pkg/jetstream/apis/jetstream/v1beta2/zz_generated.deepcopy.go b/pkg/jetstream/apis/jetstream/v1beta2/zz_generated.deepcopy.go index 224a2b4d..f45b2f5c 100644 --- a/pkg/jetstream/apis/jetstream/v1beta2/zz_generated.deepcopy.go +++ b/pkg/jetstream/apis/jetstream/v1beta2/zz_generated.deepcopy.go @@ -99,7 +99,7 @@ func (in *AccountSpec) DeepCopyInto(out *AccountSpec) { if in.Creds != nil { in, out := &in.Creds, &out.Creds *out = new(CredsSecret) - **out = **in + (*in).DeepCopyInto(*out) } if in.Token != nil { in, out := &in.Token, &out.Token @@ -127,12 +127,7 @@ func (in *AccountSpec) DeepCopy() *AccountSpec { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *BaseStreamConfig) DeepCopyInto(out *BaseStreamConfig) { *out = *in - if in.Servers != nil { - in, out := &in.Servers, &out.Servers - *out = make([]string, len(*in)) - copy(*out, *in) - } - in.TLS.DeepCopyInto(&out.TLS) + in.ConnectionOpts.DeepCopyInto(&out.ConnectionOpts) return } @@ -162,6 +157,28 @@ func (in *Condition) DeepCopy() *Condition { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ConnectionOpts) DeepCopyInto(out *ConnectionOpts) { + *out = *in + if in.Servers != nil { + in, out := &in.Servers, &out.Servers + *out = make([]string, len(*in)) + copy(*out, *in) + } + in.TLS.DeepCopyInto(&out.TLS) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConnectionOpts. +func (in *ConnectionOpts) DeepCopy() *ConnectionOpts { + if in == nil { + return nil + } + out := new(ConnectionOpts) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Consumer) DeepCopyInto(out *Consumer) { *out = *in @@ -252,12 +269,6 @@ func (in *ConsumerSpec) DeepCopyInto(out *ConsumerSpec) { *out = make([]string, len(*in)) copy(*out, *in) } - if in.Servers != nil { - in, out := &in.Servers, &out.Servers - *out = make([]string, len(*in)) - copy(*out, *in) - } - in.TLS.DeepCopyInto(&out.TLS) if in.Metadata != nil { in, out := &in.Metadata, &out.Metadata *out = make(map[string]string, len(*in)) @@ -265,6 +276,7 @@ func (in *ConsumerSpec) DeepCopyInto(out *ConsumerSpec) { (*out)[key] = val } } + in.BaseStreamConfig.DeepCopyInto(&out.BaseStreamConfig) return } @@ -297,7 +309,11 @@ func (in *CredentialsSecret) DeepCopy() *CredentialsSecret { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *CredsSecret) DeepCopyInto(out *CredsSecret) { *out = *in - out.Secret = in.Secret + if in.Secret != nil { + in, out := &in.Secret, &out.Secret + *out = new(SecretRef) + **out = **in + } return } diff --git a/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/basestreamconfig.go b/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/basestreamconfig.go index 656d1e50..67ad6902 100644 --- a/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/basestreamconfig.go +++ b/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/basestreamconfig.go @@ -18,13 +18,8 @@ package v1beta2 // BaseStreamConfigApplyConfiguration represents a declarative configuration of the BaseStreamConfig type for use // with apply. type BaseStreamConfigApplyConfiguration struct { - Account *string `json:"account,omitempty"` - Creds *string `json:"creds,omitempty"` - Nkey *string `json:"nkey,omitempty"` - PreventDelete *bool `json:"preventDelete,omitempty"` - PreventUpdate *bool `json:"preventUpdate,omitempty"` - Servers []string `json:"servers,omitempty"` - TLS *TLSApplyConfiguration `json:"tls,omitempty"` + PreventDelete *bool `json:"preventDelete,omitempty"` + PreventUpdate *bool `json:"preventUpdate,omitempty"` } // BaseStreamConfigApplyConfiguration constructs a declarative configuration of the BaseStreamConfig type for use with @@ -33,30 +28,6 @@ func BaseStreamConfig() *BaseStreamConfigApplyConfiguration { return &BaseStreamConfigApplyConfiguration{} } -// WithAccount sets the Account field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Account field is set to the value of the last call. -func (b *BaseStreamConfigApplyConfiguration) WithAccount(value string) *BaseStreamConfigApplyConfiguration { - b.Account = &value - return b -} - -// WithCreds sets the Creds field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Creds field is set to the value of the last call. -func (b *BaseStreamConfigApplyConfiguration) WithCreds(value string) *BaseStreamConfigApplyConfiguration { - b.Creds = &value - return b -} - -// WithNkey sets the Nkey field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Nkey field is set to the value of the last call. -func (b *BaseStreamConfigApplyConfiguration) WithNkey(value string) *BaseStreamConfigApplyConfiguration { - b.Nkey = &value - return b -} - // WithPreventDelete sets the PreventDelete field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. // If called multiple times, the PreventDelete field is set to the value of the last call. @@ -72,21 +43,3 @@ func (b *BaseStreamConfigApplyConfiguration) WithPreventUpdate(value bool) *Base b.PreventUpdate = &value return b } - -// WithServers adds the given value to the Servers field in the declarative configuration -// and returns the receiver, so that objects can be build by chaining "With" function invocations. -// If called multiple times, values provided by each call will be appended to the Servers field. -func (b *BaseStreamConfigApplyConfiguration) WithServers(values ...string) *BaseStreamConfigApplyConfiguration { - for i := range values { - b.Servers = append(b.Servers, values[i]) - } - return b -} - -// WithTLS sets the TLS field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the TLS field is set to the value of the last call. -func (b *BaseStreamConfigApplyConfiguration) WithTLS(value *TLSApplyConfiguration) *BaseStreamConfigApplyConfiguration { - b.TLS = value - return b -} diff --git a/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/connectionopts.go b/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/connectionopts.go new file mode 100644 index 00000000..05888032 --- /dev/null +++ b/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/connectionopts.go @@ -0,0 +1,83 @@ +// Copyright 2025 The NATS Authors +// 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. + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1beta2 + +// ConnectionOptsApplyConfiguration represents a declarative configuration of the ConnectionOpts type for use +// with apply. +type ConnectionOptsApplyConfiguration struct { + Account *string `json:"account,omitempty"` + Creds *string `json:"creds,omitempty"` + Nkey *string `json:"nkey,omitempty"` + Servers []string `json:"servers,omitempty"` + TLS *TLSApplyConfiguration `json:"tls,omitempty"` + TLSFirst *bool `json:"tlsFirst,omitempty"` +} + +// ConnectionOptsApplyConfiguration constructs a declarative configuration of the ConnectionOpts type for use with +// apply. +func ConnectionOpts() *ConnectionOptsApplyConfiguration { + return &ConnectionOptsApplyConfiguration{} +} + +// WithAccount sets the Account field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Account field is set to the value of the last call. +func (b *ConnectionOptsApplyConfiguration) WithAccount(value string) *ConnectionOptsApplyConfiguration { + b.Account = &value + return b +} + +// WithCreds sets the Creds field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Creds field is set to the value of the last call. +func (b *ConnectionOptsApplyConfiguration) WithCreds(value string) *ConnectionOptsApplyConfiguration { + b.Creds = &value + return b +} + +// WithNkey sets the Nkey field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Nkey field is set to the value of the last call. +func (b *ConnectionOptsApplyConfiguration) WithNkey(value string) *ConnectionOptsApplyConfiguration { + b.Nkey = &value + return b +} + +// WithServers adds the given value to the Servers field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the Servers field. +func (b *ConnectionOptsApplyConfiguration) WithServers(values ...string) *ConnectionOptsApplyConfiguration { + for i := range values { + b.Servers = append(b.Servers, values[i]) + } + return b +} + +// WithTLS sets the TLS field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the TLS field is set to the value of the last call. +func (b *ConnectionOptsApplyConfiguration) WithTLS(value *TLSApplyConfiguration) *ConnectionOptsApplyConfiguration { + b.TLS = value + return b +} + +// WithTLSFirst sets the TLSFirst field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the TLSFirst field is set to the value of the last call. +func (b *ConnectionOptsApplyConfiguration) WithTLSFirst(value bool) *ConnectionOptsApplyConfiguration { + b.TLSFirst = &value + return b +} diff --git a/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/consumerspec.go b/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/consumerspec.go index ca97ae62..0604bf72 100644 --- a/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/consumerspec.go +++ b/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/consumerspec.go @@ -18,41 +18,35 @@ package v1beta2 // ConsumerSpecApplyConfiguration represents a declarative configuration of the ConsumerSpec type for use // with apply. type ConsumerSpecApplyConfiguration struct { - AckPolicy *string `json:"ackPolicy,omitempty"` - AckWait *string `json:"ackWait,omitempty"` - BackOff []string `json:"backoff,omitempty"` - Creds *string `json:"creds,omitempty"` - DeliverGroup *string `json:"deliverGroup,omitempty"` - DeliverPolicy *string `json:"deliverPolicy,omitempty"` - DeliverSubject *string `json:"deliverSubject,omitempty"` - Description *string `json:"description,omitempty"` - PreventDelete *bool `json:"preventDelete,omitempty"` - PreventUpdate *bool `json:"preventUpdate,omitempty"` - DurableName *string `json:"durableName,omitempty"` - FilterSubject *string `json:"filterSubject,omitempty"` - FilterSubjects []string `json:"filterSubjects,omitempty"` - FlowControl *bool `json:"flowControl,omitempty"` - HeadersOnly *bool `json:"headersOnly,omitempty"` - HeartbeatInterval *string `json:"heartbeatInterval,omitempty"` - MaxAckPending *int `json:"maxAckPending,omitempty"` - MaxDeliver *int `json:"maxDeliver,omitempty"` - MaxRequestBatch *int `json:"maxRequestBatch,omitempty"` - MaxRequestExpires *string `json:"maxRequestExpires,omitempty"` - MaxRequestMaxBytes *int `json:"maxRequestMaxBytes,omitempty"` - MaxWaiting *int `json:"maxWaiting,omitempty"` - MemStorage *bool `json:"memStorage,omitempty"` - Nkey *string `json:"nkey,omitempty"` - OptStartSeq *int `json:"optStartSeq,omitempty"` - OptStartTime *string `json:"optStartTime,omitempty"` - RateLimitBps *int `json:"rateLimitBps,omitempty"` - ReplayPolicy *string `json:"replayPolicy,omitempty"` - Replicas *int `json:"replicas,omitempty"` - SampleFreq *string `json:"sampleFreq,omitempty"` - Servers []string `json:"servers,omitempty"` - StreamName *string `json:"streamName,omitempty"` - TLS *TLSApplyConfiguration `json:"tls,omitempty"` - Account *string `json:"account,omitempty"` - Metadata map[string]string `json:"metadata,omitempty"` + DurableName *string `json:"durableName,omitempty"` + Description *string `json:"description,omitempty"` + DeliverPolicy *string `json:"deliverPolicy,omitempty"` + OptStartSeq *int `json:"optStartSeq,omitempty"` + OptStartTime *string `json:"optStartTime,omitempty"` + AckPolicy *string `json:"ackPolicy,omitempty"` + AckWait *string `json:"ackWait,omitempty"` + MaxDeliver *int `json:"maxDeliver,omitempty"` + BackOff []string `json:"backoff,omitempty"` + FilterSubject *string `json:"filterSubject,omitempty"` + ReplayPolicy *string `json:"replayPolicy,omitempty"` + RateLimitBps *int `json:"rateLimitBps,omitempty"` + SampleFreq *string `json:"sampleFreq,omitempty"` + MaxWaiting *int `json:"maxWaiting,omitempty"` + MaxAckPending *int `json:"maxAckPending,omitempty"` + HeadersOnly *bool `json:"headersOnly,omitempty"` + MaxRequestBatch *int `json:"maxRequestBatch,omitempty"` + MaxRequestExpires *string `json:"maxRequestExpires,omitempty"` + MaxRequestMaxBytes *int `json:"maxRequestMaxBytes,omitempty"` + InactiveThreshold *string `json:"inactiveThreshold,omitempty"` + Replicas *int `json:"replicas,omitempty"` + MemStorage *bool `json:"memStorage,omitempty"` + FilterSubjects []string `json:"filterSubjects,omitempty"` + Metadata map[string]string `json:"metadata,omitempty"` + FlowControl *bool `json:"flowControl,omitempty"` + DeliverSubject *string `json:"deliverSubject,omitempty"` + DeliverGroup *string `json:"deliverGroup,omitempty"` + HeartbeatInterval *string `json:"heartbeatInterval,omitempty"` + StreamName *string `json:"streamName,omitempty"` } // ConsumerSpecApplyConfiguration constructs a declarative configuration of the ConsumerSpec type for use with @@ -61,45 +55,19 @@ func ConsumerSpec() *ConsumerSpecApplyConfiguration { return &ConsumerSpecApplyConfiguration{} } -// WithAckPolicy sets the AckPolicy field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the AckPolicy field is set to the value of the last call. -func (b *ConsumerSpecApplyConfiguration) WithAckPolicy(value string) *ConsumerSpecApplyConfiguration { - b.AckPolicy = &value - return b -} - -// WithAckWait sets the AckWait field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the AckWait field is set to the value of the last call. -func (b *ConsumerSpecApplyConfiguration) WithAckWait(value string) *ConsumerSpecApplyConfiguration { - b.AckWait = &value - return b -} - -// WithBackOff adds the given value to the BackOff field in the declarative configuration -// and returns the receiver, so that objects can be build by chaining "With" function invocations. -// If called multiple times, values provided by each call will be appended to the BackOff field. -func (b *ConsumerSpecApplyConfiguration) WithBackOff(values ...string) *ConsumerSpecApplyConfiguration { - for i := range values { - b.BackOff = append(b.BackOff, values[i]) - } - return b -} - -// WithCreds sets the Creds field in the declarative configuration to the given value +// WithDurableName sets the DurableName field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Creds field is set to the value of the last call. -func (b *ConsumerSpecApplyConfiguration) WithCreds(value string) *ConsumerSpecApplyConfiguration { - b.Creds = &value +// If called multiple times, the DurableName field is set to the value of the last call. +func (b *ConsumerSpecApplyConfiguration) WithDurableName(value string) *ConsumerSpecApplyConfiguration { + b.DurableName = &value return b } -// WithDeliverGroup sets the DeliverGroup field in the declarative configuration to the given value +// WithDescription sets the Description field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the DeliverGroup field is set to the value of the last call. -func (b *ConsumerSpecApplyConfiguration) WithDeliverGroup(value string) *ConsumerSpecApplyConfiguration { - b.DeliverGroup = &value +// If called multiple times, the Description field is set to the value of the last call. +func (b *ConsumerSpecApplyConfiguration) WithDescription(value string) *ConsumerSpecApplyConfiguration { + b.Description = &value return b } @@ -111,43 +79,53 @@ func (b *ConsumerSpecApplyConfiguration) WithDeliverPolicy(value string) *Consum return b } -// WithDeliverSubject sets the DeliverSubject field in the declarative configuration to the given value +// WithOptStartSeq sets the OptStartSeq field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the DeliverSubject field is set to the value of the last call. -func (b *ConsumerSpecApplyConfiguration) WithDeliverSubject(value string) *ConsumerSpecApplyConfiguration { - b.DeliverSubject = &value +// If called multiple times, the OptStartSeq field is set to the value of the last call. +func (b *ConsumerSpecApplyConfiguration) WithOptStartSeq(value int) *ConsumerSpecApplyConfiguration { + b.OptStartSeq = &value return b } -// WithDescription sets the Description field in the declarative configuration to the given value +// WithOptStartTime sets the OptStartTime field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Description field is set to the value of the last call. -func (b *ConsumerSpecApplyConfiguration) WithDescription(value string) *ConsumerSpecApplyConfiguration { - b.Description = &value +// If called multiple times, the OptStartTime field is set to the value of the last call. +func (b *ConsumerSpecApplyConfiguration) WithOptStartTime(value string) *ConsumerSpecApplyConfiguration { + b.OptStartTime = &value return b } -// WithPreventDelete sets the PreventDelete field in the declarative configuration to the given value +// WithAckPolicy sets the AckPolicy field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the PreventDelete field is set to the value of the last call. -func (b *ConsumerSpecApplyConfiguration) WithPreventDelete(value bool) *ConsumerSpecApplyConfiguration { - b.PreventDelete = &value +// If called multiple times, the AckPolicy field is set to the value of the last call. +func (b *ConsumerSpecApplyConfiguration) WithAckPolicy(value string) *ConsumerSpecApplyConfiguration { + b.AckPolicy = &value return b } -// WithPreventUpdate sets the PreventUpdate field in the declarative configuration to the given value +// WithAckWait sets the AckWait field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the PreventUpdate field is set to the value of the last call. -func (b *ConsumerSpecApplyConfiguration) WithPreventUpdate(value bool) *ConsumerSpecApplyConfiguration { - b.PreventUpdate = &value +// If called multiple times, the AckWait field is set to the value of the last call. +func (b *ConsumerSpecApplyConfiguration) WithAckWait(value string) *ConsumerSpecApplyConfiguration { + b.AckWait = &value return b } -// WithDurableName sets the DurableName field in the declarative configuration to the given value +// WithMaxDeliver sets the MaxDeliver field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the DurableName field is set to the value of the last call. -func (b *ConsumerSpecApplyConfiguration) WithDurableName(value string) *ConsumerSpecApplyConfiguration { - b.DurableName = &value +// If called multiple times, the MaxDeliver field is set to the value of the last call. +func (b *ConsumerSpecApplyConfiguration) WithMaxDeliver(value int) *ConsumerSpecApplyConfiguration { + b.MaxDeliver = &value + return b +} + +// WithBackOff adds the given value to the BackOff field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the BackOff field. +func (b *ConsumerSpecApplyConfiguration) WithBackOff(values ...string) *ConsumerSpecApplyConfiguration { + for i := range values { + b.BackOff = append(b.BackOff, values[i]) + } return b } @@ -159,37 +137,35 @@ func (b *ConsumerSpecApplyConfiguration) WithFilterSubject(value string) *Consum return b } -// WithFilterSubjects adds the given value to the FilterSubjects field in the declarative configuration -// and returns the receiver, so that objects can be build by chaining "With" function invocations. -// If called multiple times, values provided by each call will be appended to the FilterSubjects field. -func (b *ConsumerSpecApplyConfiguration) WithFilterSubjects(values ...string) *ConsumerSpecApplyConfiguration { - for i := range values { - b.FilterSubjects = append(b.FilterSubjects, values[i]) - } +// WithReplayPolicy sets the ReplayPolicy field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ReplayPolicy field is set to the value of the last call. +func (b *ConsumerSpecApplyConfiguration) WithReplayPolicy(value string) *ConsumerSpecApplyConfiguration { + b.ReplayPolicy = &value return b } -// WithFlowControl sets the FlowControl field in the declarative configuration to the given value +// WithRateLimitBps sets the RateLimitBps field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the FlowControl field is set to the value of the last call. -func (b *ConsumerSpecApplyConfiguration) WithFlowControl(value bool) *ConsumerSpecApplyConfiguration { - b.FlowControl = &value +// If called multiple times, the RateLimitBps field is set to the value of the last call. +func (b *ConsumerSpecApplyConfiguration) WithRateLimitBps(value int) *ConsumerSpecApplyConfiguration { + b.RateLimitBps = &value return b } -// WithHeadersOnly sets the HeadersOnly field in the declarative configuration to the given value +// WithSampleFreq sets the SampleFreq field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the HeadersOnly field is set to the value of the last call. -func (b *ConsumerSpecApplyConfiguration) WithHeadersOnly(value bool) *ConsumerSpecApplyConfiguration { - b.HeadersOnly = &value +// If called multiple times, the SampleFreq field is set to the value of the last call. +func (b *ConsumerSpecApplyConfiguration) WithSampleFreq(value string) *ConsumerSpecApplyConfiguration { + b.SampleFreq = &value return b } -// WithHeartbeatInterval sets the HeartbeatInterval field in the declarative configuration to the given value +// WithMaxWaiting sets the MaxWaiting field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the HeartbeatInterval field is set to the value of the last call. -func (b *ConsumerSpecApplyConfiguration) WithHeartbeatInterval(value string) *ConsumerSpecApplyConfiguration { - b.HeartbeatInterval = &value +// If called multiple times, the MaxWaiting field is set to the value of the last call. +func (b *ConsumerSpecApplyConfiguration) WithMaxWaiting(value int) *ConsumerSpecApplyConfiguration { + b.MaxWaiting = &value return b } @@ -201,11 +177,11 @@ func (b *ConsumerSpecApplyConfiguration) WithMaxAckPending(value int) *ConsumerS return b } -// WithMaxDeliver sets the MaxDeliver field in the declarative configuration to the given value +// WithHeadersOnly sets the HeadersOnly field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the MaxDeliver field is set to the value of the last call. -func (b *ConsumerSpecApplyConfiguration) WithMaxDeliver(value int) *ConsumerSpecApplyConfiguration { - b.MaxDeliver = &value +// If called multiple times, the HeadersOnly field is set to the value of the last call. +func (b *ConsumerSpecApplyConfiguration) WithHeadersOnly(value bool) *ConsumerSpecApplyConfiguration { + b.HeadersOnly = &value return b } @@ -233,85 +209,83 @@ func (b *ConsumerSpecApplyConfiguration) WithMaxRequestMaxBytes(value int) *Cons return b } -// WithMaxWaiting sets the MaxWaiting field in the declarative configuration to the given value +// WithInactiveThreshold sets the InactiveThreshold field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the MaxWaiting field is set to the value of the last call. -func (b *ConsumerSpecApplyConfiguration) WithMaxWaiting(value int) *ConsumerSpecApplyConfiguration { - b.MaxWaiting = &value +// If called multiple times, the InactiveThreshold field is set to the value of the last call. +func (b *ConsumerSpecApplyConfiguration) WithInactiveThreshold(value string) *ConsumerSpecApplyConfiguration { + b.InactiveThreshold = &value return b } -// WithMemStorage sets the MemStorage field in the declarative configuration to the given value +// WithReplicas sets the Replicas field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the MemStorage field is set to the value of the last call. -func (b *ConsumerSpecApplyConfiguration) WithMemStorage(value bool) *ConsumerSpecApplyConfiguration { - b.MemStorage = &value +// If called multiple times, the Replicas field is set to the value of the last call. +func (b *ConsumerSpecApplyConfiguration) WithReplicas(value int) *ConsumerSpecApplyConfiguration { + b.Replicas = &value return b } -// WithNkey sets the Nkey field in the declarative configuration to the given value +// WithMemStorage sets the MemStorage field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Nkey field is set to the value of the last call. -func (b *ConsumerSpecApplyConfiguration) WithNkey(value string) *ConsumerSpecApplyConfiguration { - b.Nkey = &value +// If called multiple times, the MemStorage field is set to the value of the last call. +func (b *ConsumerSpecApplyConfiguration) WithMemStorage(value bool) *ConsumerSpecApplyConfiguration { + b.MemStorage = &value return b } -// WithOptStartSeq sets the OptStartSeq field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the OptStartSeq field is set to the value of the last call. -func (b *ConsumerSpecApplyConfiguration) WithOptStartSeq(value int) *ConsumerSpecApplyConfiguration { - b.OptStartSeq = &value +// WithFilterSubjects adds the given value to the FilterSubjects field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the FilterSubjects field. +func (b *ConsumerSpecApplyConfiguration) WithFilterSubjects(values ...string) *ConsumerSpecApplyConfiguration { + for i := range values { + b.FilterSubjects = append(b.FilterSubjects, values[i]) + } return b } -// WithOptStartTime sets the OptStartTime field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the OptStartTime field is set to the value of the last call. -func (b *ConsumerSpecApplyConfiguration) WithOptStartTime(value string) *ConsumerSpecApplyConfiguration { - b.OptStartTime = &value +// WithMetadata puts the entries into the Metadata field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, the entries provided by each call will be put on the Metadata field, +// overwriting an existing map entries in Metadata field with the same key. +func (b *ConsumerSpecApplyConfiguration) WithMetadata(entries map[string]string) *ConsumerSpecApplyConfiguration { + if b.Metadata == nil && len(entries) > 0 { + b.Metadata = make(map[string]string, len(entries)) + } + for k, v := range entries { + b.Metadata[k] = v + } return b } -// WithRateLimitBps sets the RateLimitBps field in the declarative configuration to the given value +// WithFlowControl sets the FlowControl field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the RateLimitBps field is set to the value of the last call. -func (b *ConsumerSpecApplyConfiguration) WithRateLimitBps(value int) *ConsumerSpecApplyConfiguration { - b.RateLimitBps = &value +// If called multiple times, the FlowControl field is set to the value of the last call. +func (b *ConsumerSpecApplyConfiguration) WithFlowControl(value bool) *ConsumerSpecApplyConfiguration { + b.FlowControl = &value return b } -// WithReplayPolicy sets the ReplayPolicy field in the declarative configuration to the given value +// WithDeliverSubject sets the DeliverSubject field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the ReplayPolicy field is set to the value of the last call. -func (b *ConsumerSpecApplyConfiguration) WithReplayPolicy(value string) *ConsumerSpecApplyConfiguration { - b.ReplayPolicy = &value +// If called multiple times, the DeliverSubject field is set to the value of the last call. +func (b *ConsumerSpecApplyConfiguration) WithDeliverSubject(value string) *ConsumerSpecApplyConfiguration { + b.DeliverSubject = &value return b } -// WithReplicas sets the Replicas field in the declarative configuration to the given value +// WithDeliverGroup sets the DeliverGroup field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Replicas field is set to the value of the last call. -func (b *ConsumerSpecApplyConfiguration) WithReplicas(value int) *ConsumerSpecApplyConfiguration { - b.Replicas = &value +// If called multiple times, the DeliverGroup field is set to the value of the last call. +func (b *ConsumerSpecApplyConfiguration) WithDeliverGroup(value string) *ConsumerSpecApplyConfiguration { + b.DeliverGroup = &value return b } -// WithSampleFreq sets the SampleFreq field in the declarative configuration to the given value +// WithHeartbeatInterval sets the HeartbeatInterval field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the SampleFreq field is set to the value of the last call. -func (b *ConsumerSpecApplyConfiguration) WithSampleFreq(value string) *ConsumerSpecApplyConfiguration { - b.SampleFreq = &value - return b -} - -// WithServers adds the given value to the Servers field in the declarative configuration -// and returns the receiver, so that objects can be build by chaining "With" function invocations. -// If called multiple times, values provided by each call will be appended to the Servers field. -func (b *ConsumerSpecApplyConfiguration) WithServers(values ...string) *ConsumerSpecApplyConfiguration { - for i := range values { - b.Servers = append(b.Servers, values[i]) - } +// If called multiple times, the HeartbeatInterval field is set to the value of the last call. +func (b *ConsumerSpecApplyConfiguration) WithHeartbeatInterval(value string) *ConsumerSpecApplyConfiguration { + b.HeartbeatInterval = &value return b } @@ -322,33 +296,3 @@ func (b *ConsumerSpecApplyConfiguration) WithStreamName(value string) *ConsumerS b.StreamName = &value return b } - -// WithTLS sets the TLS field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the TLS field is set to the value of the last call. -func (b *ConsumerSpecApplyConfiguration) WithTLS(value *TLSApplyConfiguration) *ConsumerSpecApplyConfiguration { - b.TLS = value - return b -} - -// WithAccount sets the Account field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Account field is set to the value of the last call. -func (b *ConsumerSpecApplyConfiguration) WithAccount(value string) *ConsumerSpecApplyConfiguration { - b.Account = &value - return b -} - -// WithMetadata puts the entries into the Metadata field in the declarative configuration -// and returns the receiver, so that objects can be build by chaining "With" function invocations. -// If called multiple times, the entries provided by each call will be put on the Metadata field, -// overwriting an existing map entries in Metadata field with the same key. -func (b *ConsumerSpecApplyConfiguration) WithMetadata(entries map[string]string) *ConsumerSpecApplyConfiguration { - if b.Metadata == nil && len(entries) > 0 { - b.Metadata = make(map[string]string, len(entries)) - } - for k, v := range entries { - b.Metadata[k] = v - } - return b -} diff --git a/pkg/jetstream/generated/applyconfiguration/utils.go b/pkg/jetstream/generated/applyconfiguration/utils.go index 7ccfa1ae..0f13a0e2 100644 --- a/pkg/jetstream/generated/applyconfiguration/utils.go +++ b/pkg/jetstream/generated/applyconfiguration/utils.go @@ -37,6 +37,8 @@ func ForKind(kind schema.GroupVersionKind) interface{} { return &jetstreamv1beta2.BaseStreamConfigApplyConfiguration{} case v1beta2.SchemeGroupVersion.WithKind("Condition"): return &jetstreamv1beta2.ConditionApplyConfiguration{} + case v1beta2.SchemeGroupVersion.WithKind("ConnectionOpts"): + return &jetstreamv1beta2.ConnectionOptsApplyConfiguration{} case v1beta2.SchemeGroupVersion.WithKind("Consumer"): return &jetstreamv1beta2.ConsumerApplyConfiguration{} case v1beta2.SchemeGroupVersion.WithKind("ConsumerLimits"): From af6372272429d9dd261c77afd7ebd48274cee6d0 Mon Sep 17 00:00:00 2001 From: Samuel Attwood Date: Thu, 16 Jan 2025 03:15:26 -0500 Subject: [PATCH 10/19] Deps --- go.mod | 34 +++++++++++++++---------------- go.sum | 64 +++++++++++++++++++++++++++++----------------------------- 2 files changed, 49 insertions(+), 49 deletions(-) diff --git a/go.mod b/go.mod index 3ceacd34..e4bd242e 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ toolchain go1.23.4 require ( github.com/fsnotify/fsnotify v1.8.0 github.com/go-logr/logr v1.4.2 + github.com/google/go-cmp v0.6.0 github.com/nats-io/jsm.go v0.1.2 github.com/nats-io/nats-server/v2 v2.10.24 github.com/nats-io/nats.go v1.38.0 @@ -15,12 +16,12 @@ require ( github.com/sirupsen/logrus v1.9.3 github.com/stretchr/testify v1.10.0 golang.org/x/sync v0.10.0 - k8s.io/api v0.32.0 - k8s.io/apimachinery v0.32.0 - k8s.io/client-go v0.32.0 - k8s.io/code-generator v0.32.0 + k8s.io/api v0.32.1 + k8s.io/apimachinery v0.32.1 + k8s.io/client-go v0.32.1 + k8s.io/code-generator v0.32.1 k8s.io/klog/v2 v2.130.1 - sigs.k8s.io/controller-runtime v0.19.3 + sigs.k8s.io/controller-runtime v0.19.4 sigs.k8s.io/structured-merge-diff/v4 v4.5.0 ) @@ -41,7 +42,6 @@ require ( github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/google/gnostic-models v0.6.9 // indirect - github.com/google/go-cmp v0.6.0 // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad // indirect github.com/google/uuid v1.6.0 // indirect @@ -66,23 +66,23 @@ require ( github.com/x448/float16 v0.8.4 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect - golang.org/x/crypto v0.31.0 // indirect - golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67 // indirect + golang.org/x/crypto v0.32.0 // indirect + golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 // indirect golang.org/x/mod v0.22.0 // indirect - golang.org/x/net v0.33.0 // indirect - golang.org/x/oauth2 v0.24.0 // indirect - golang.org/x/sys v0.28.0 // indirect - golang.org/x/term v0.27.0 // indirect + golang.org/x/net v0.34.0 // indirect + golang.org/x/oauth2 v0.25.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.8.0 // indirect - golang.org/x/tools v0.28.0 // indirect + golang.org/x/time v0.9.0 // indirect + golang.org/x/tools v0.29.0 // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect - google.golang.org/protobuf v1.36.1 // indirect + google.golang.org/protobuf v1.36.3 // indirect gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/apiextensions-apiserver v0.32.0 // indirect - k8s.io/gengo/v2 v2.0.0-20240911193312-2b36238f13e9 // indirect + k8s.io/apiextensions-apiserver v0.32.1 // indirect + k8s.io/gengo/v2 v2.0.0-20250106234829-0359904fc2a6 // indirect k8s.io/kube-openapi v0.0.0-20241212222426-2c72e554b1e7 // indirect k8s.io/utils v0.0.0-20241210054802-24370beab758 // indirect sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect diff --git a/go.sum b/go.sum index d89930d8..46225f7c 100644 --- a/go.sum +++ b/go.sum @@ -126,10 +126,10 @@ 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.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= -golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= -golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67 h1:1UoZQm6f0P/ZO0w1Ri+f+ifG/gXhegadRdwBIXEFWDo= -golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c= +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-20250106191152-7588d65b2ba8 h1:yqrTHse8TCMW1M1ZCP+VAR/l0kKxwaAIqN/il7x4voA= +golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU= 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.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= @@ -138,10 +138,10 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn 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-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= -golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= -golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE= -golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +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/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-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -152,30 +152,30 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= -golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q= -golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= +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/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 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.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg= -golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +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/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-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.28.0 h1:WuB6qZ4RPCQo5aP3WdKZS7i595EdWqWR8vqJTlwTVK8= -golang.org/x/tools v0.28.0/go.mod h1:dcIOrVd3mfQKTgrDVQHqCPMWy6lnhfhtX3hLXYVLfRw= +golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE= +golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588= 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= 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/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk= -google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/protobuf v1.36.3 h1:82DV7MYdb8anAVi3qge1wSnMDrnKK7ebr+I0hHRN1BU= +google.golang.org/protobuf v1.36.3/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/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= @@ -186,26 +186,26 @@ gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= 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= -k8s.io/api v0.32.0 h1:OL9JpbvAU5ny9ga2fb24X8H6xQlVp+aJMFlgtQjR9CE= -k8s.io/api v0.32.0/go.mod h1:4LEwHZEf6Q/cG96F3dqR965sYOfmPM7rq81BLgsE0p0= -k8s.io/apiextensions-apiserver v0.32.0 h1:S0Xlqt51qzzqjKPxfgX1xh4HBZE+p8KKBq+k2SWNOE0= -k8s.io/apiextensions-apiserver v0.32.0/go.mod h1:86hblMvN5yxMvZrZFX2OhIHAuFIMJIZ19bTvzkP+Fmw= -k8s.io/apimachinery v0.32.0 h1:cFSE7N3rmEEtv4ei5X6DaJPHHX0C+upp+v5lVPiEwpg= -k8s.io/apimachinery v0.32.0/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE= -k8s.io/client-go v0.32.0 h1:DimtMcnN/JIKZcrSrstiwvvZvLjG0aSxy8PxN8IChp8= -k8s.io/client-go v0.32.0/go.mod h1:boDWvdM1Drk4NJj/VddSLnx59X3OPgwrOo0vGbtq9+8= -k8s.io/code-generator v0.32.0 h1:s0lNN8VSWny8LBz5t5iy7MCdgwdOhdg7vAGVxvS+VWU= -k8s.io/code-generator v0.32.0/go.mod h1:b7Q7KMZkvsYFy72A79QYjiv4aTz3GvW0f1T3UfhFq4s= -k8s.io/gengo/v2 v2.0.0-20240911193312-2b36238f13e9 h1:si3PfKm8dDYxgfbeA6orqrtLkvvIeH8UqffFJDl0bz4= -k8s.io/gengo/v2 v2.0.0-20240911193312-2b36238f13e9/go.mod h1:EJykeLsmFC60UQbYJezXkEsG2FLrt0GPNkU5iK5GWxU= +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= +k8s.io/apiextensions-apiserver v0.32.1/go.mod h1:sxWIGuGiYov7Io1fAS2X06NjMIk5CbRHc2StSmbaQto= +k8s.io/apimachinery v0.32.1 h1:683ENpaCBjma4CYqsmZyhEzrGz6cjn1MY/X2jB2hkZs= +k8s.io/apimachinery v0.32.1/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE= +k8s.io/client-go v0.32.1 h1:otM0AxdhdBIaQh7l1Q0jQpmo7WOFIk5FFa4bg6YMdUU= +k8s.io/client-go v0.32.1/go.mod h1:aTTKZY7MdxUaJ/KiUs8D+GssR9zJZi77ZqtzcGXIiDg= +k8s.io/code-generator v0.32.1 h1:4lw1kFNDuFYXquTkB7Sl5EwPMUP2yyW9hh6BnFfRZFY= +k8s.io/code-generator v0.32.1/go.mod h1:zaILfm00CVyP/6/pJMJ3zxRepXkxyDfUV5SNG4CjZI4= +k8s.io/gengo/v2 v2.0.0-20250106234829-0359904fc2a6 h1:SdzkGIk4b5LFkVO36PuO0Bx4tpBDJDpNN0F1/v8JM5c= +k8s.io/gengo/v2 v2.0.0-20250106234829-0359904fc2a6/go.mod h1:EJykeLsmFC60UQbYJezXkEsG2FLrt0GPNkU5iK5GWxU= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/kube-openapi v0.0.0-20241212222426-2c72e554b1e7 h1:hcha5B1kVACrLujCKLbr8XWMxCxzQx42DY8QKYJrDLg= 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/controller-runtime v0.19.3 h1:XO2GvC9OPftRst6xWCpTgBZO04S2cbp0Qqkj8bX1sPw= -sigs.k8s.io/controller-runtime v0.19.3/go.mod h1:j4j87DqtsThvwTv5/Tc5NFRyyF/RF0ip4+62tbTSIUM= +sigs.k8s.io/controller-runtime v0.19.4 h1:SUmheabttt0nx8uJtoII4oIP27BVVvAKFvdvGFwV/Qo= +sigs.k8s.io/controller-runtime v0.19.4/go.mod h1:iRmWllt8IlaLjvTTDLhRBXIEtkCK6hwVBJJsYS9Ajf4= 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/structured-merge-diff/v4 v4.5.0 h1:nbCitCK2hfnhyiKo6uf2HxUPTCodY6Qaf85SbDIaMBk= From 4b8a5301620eff291991177ca78b7d8e2ec42f5e Mon Sep 17 00:00:00 2001 From: Samuel Attwood Date: Tue, 21 Jan 2025 05:44:33 -0500 Subject: [PATCH 11/19] Create configured cache dir if DNE --- cmd/jetstream-controller/main.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/cmd/jetstream-controller/main.go b/cmd/jetstream-controller/main.go index e58ff7ce..0d9d34d4 100644 --- a/cmd/jetstream-controller/main.go +++ b/cmd/jetstream-controller/main.go @@ -182,6 +182,13 @@ func runControlLoop(config *rest.Config, natsCfg *controller.NatsConfig, control } defer os.RemoveAll(cacheDir) controllerCfg.CacheDir = cacheDir + } else { + if _, err := os.Stat(controllerCfg.CacheDir); os.IsNotExist(err) { + err = os.MkdirAll(controllerCfg.CacheDir, 0o755) + if err != nil { + return fmt.Errorf("create cache dir: %w", err) + } + } } err = controller.RegisterAll(mgr, natsCfg, controllerCfg) From aee234bb2ddf0b212284c6dd70dea48ef0cc81e0 Mon Sep 17 00:00:00 2001 From: Samuel Attwood Date: Tue, 21 Jan 2025 03:09:25 -0500 Subject: [PATCH 12/19] Move stream controller to jsm.go for pedantic mode --- go.mod | 5 +- go.sum | 12 +- .../controller/account_controller_test.go | 2 +- internal/controller/client.go | 56 +++- internal/controller/consumer_controller.go | 2 +- .../controller/consumer_controller_test.go | 2 +- internal/controller/jetstream_controller.go | 53 ++++ .../controller/keyvalue_controller_test.go | 2 +- .../controller/objectstore_controller_test.go | 2 +- internal/controller/stream_controller.go | 286 ++++++++++++------ internal/controller/stream_controller_test.go | 90 +++--- internal/controller/suite_test.go | 2 +- .../apis/jetstream/v1beta2/streamtypes.go | 18 +- 13 files changed, 369 insertions(+), 163 deletions(-) diff --git a/go.mod b/go.mod index e4bd242e..8eb30738 100644 --- a/go.mod +++ b/go.mod @@ -8,8 +8,8 @@ require ( github.com/fsnotify/fsnotify v1.8.0 github.com/go-logr/logr v1.4.2 github.com/google/go-cmp v0.6.0 - github.com/nats-io/jsm.go v0.1.2 - github.com/nats-io/nats-server/v2 v2.10.24 + github.com/nats-io/jsm.go v0.1.1-0.20250120135113-0db99fd62ad9 + github.com/nats-io/nats-server/v2 v2.11.0-dev.0.20250109022000-c0ae95d0be26 github.com/nats-io/nats.go v1.38.0 github.com/onsi/ginkgo/v2 v2.22.2 github.com/onsi/gomega v1.36.2 @@ -42,6 +42,7 @@ require ( github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/google/gnostic-models v0.6.9 // indirect + github.com/google/go-tpm v0.9.3 // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad // indirect github.com/google/uuid v1.6.0 // indirect diff --git a/go.sum b/go.sum index 46225f7c..40c02964 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +github.com/antithesishq/antithesis-sdk-go v0.4.3-default-no-op h1:+OSa/t11TFhqfrX0EOSqQBDJ0YlpmK0rDSiB19dg9M0= +github.com/antithesishq/antithesis-sdk-go v0.4.3-default-no-op/go.mod h1:IUpT2DPAKh6i/YhSbt6Gl3v2yvUZjmKncl7U91fup7E= 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/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= @@ -41,6 +43,8 @@ github.com/google/gnostic-models v0.6.9/go.mod h1:CiWsm0s6BSQd1hRn8/QmxqB6BesYcb 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-tpm v0.9.3 h1:+yx0/anQuGzi+ssRqeD6WpXjW2L/V0dItUayO0i9sRc= +github.com/google/go-tpm v0.9.3/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY= 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= @@ -73,12 +77,12 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 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/nats-io/jsm.go v0.1.2 h1:T4Fq88a03sPAPWYwrOLQ85oanYsC2Bs6517rUiWBMpQ= -github.com/nats-io/jsm.go v0.1.2/go.mod h1:tnubE70CAKi5TNfQiq6XHFqWTuSIe1H7X4sDwfq6ZK8= +github.com/nats-io/jsm.go v0.1.1-0.20250120135113-0db99fd62ad9 h1:2DfL+nZjlB8kQh5GN5IEBdVroHbRaRod3BAi07Ag89Q= +github.com/nats-io/jsm.go v0.1.1-0.20250120135113-0db99fd62ad9/go.mod h1:w/SA3/rNK5xl6ZsCqKLVgQzVLfxbYNScle8LcQ5gSBM= github.com/nats-io/jwt/v2 v2.7.3 h1:6bNPK+FXgBeAqdj4cYQ0F8ViHRbi7woQLq4W29nUAzE= github.com/nats-io/jwt/v2 v2.7.3/go.mod h1:GvkcbHhKquj3pkioy5put1wvPxs78UlZ7D/pY+BgZk4= -github.com/nats-io/nats-server/v2 v2.10.24 h1:KcqqQAD0ZZcG4yLxtvSFJY7CYKVYlnlWoAiVZ6i/IY4= -github.com/nats-io/nats-server/v2 v2.10.24/go.mod h1:olvKt8E5ZlnjyqBGbAXtxvSQKsPodISK5Eo/euIta4s= +github.com/nats-io/nats-server/v2 v2.11.0-dev.0.20250109022000-c0ae95d0be26 h1:mN0eraizaHih90T32ItWe58+l31eVjoD6JOu99WqdNc= +github.com/nats-io/nats-server/v2 v2.11.0-dev.0.20250109022000-c0ae95d0be26/go.mod h1:nXRZ6eQo2lmNpZLVNIMDNwKM7FgbHgPJ1pIRcPOpVuk= github.com/nats-io/nats.go v1.38.0 h1:A7P+g7Wjp4/NWqDOOP/K6hfhr54DvdDQUznt5JFg9XA= github.com/nats-io/nats.go v1.38.0/go.mod h1:IGUM++TwokGnXPs82/wCuiHS02/aKrdYUQkU8If6yjw= github.com/nats-io/nkeys v0.4.9 h1:qe9Faq2Gxwi6RZnZMXfmGMZkg3afLLOtrU+gDZJ35b0= diff --git a/internal/controller/account_controller_test.go b/internal/controller/account_controller_test.go index 2067b758..e2388de8 100644 --- a/internal/controller/account_controller_test.go +++ b/internal/controller/account_controller_test.go @@ -1,5 +1,5 @@ /* -Copyright 2024. +Copyright 2025. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/internal/controller/client.go b/internal/controller/client.go index 7e79a658..7667cec7 100644 --- a/internal/controller/client.go +++ b/internal/controller/client.go @@ -3,6 +3,7 @@ package controller import ( "fmt" + "github.com/nats-io/jsm.go" "github.com/nats-io/nats.go" "github.com/nats-io/nats.go/jetstream" ) @@ -62,13 +63,55 @@ type Closable interface { Close() } +func CreateJSMClient(cfg *NatsConfig, pedantic bool) (*jsm.Manager, Closable, error) { + nc, err := createNatsConn(cfg, pedantic) + if err != nil { + return nil, nil, fmt.Errorf("create nats connection: %w", err) + } + + major, minor, _, err := versionComponents(nc.ConnectedServerVersion()) + if err != nil { + return nil, nil, fmt.Errorf("parse server version: %w", err) + } + + // JetStream pedantic mode unsupported prior to NATS Server 2.11 + if pedantic && major < 2 || (major == 2 && minor < 11) { + pedantic = false + } + + jsmOpts := make([]jsm.Option, 0) + if pedantic { + jsmOpts = append(jsmOpts, jsm.WithPedanticRequests()) + } + + jsmClient, err := jsm.New(nc, jsmOpts...) + if err != nil { + return nil, nil, fmt.Errorf("new jsm client: %w", err) + } + + return jsmClient, nc, nil +} + // CreateJetStreamClient creates new Jetstream client with a connection based on the given NatsConfig. // Returns a jetstream.Jetstream client and the Closable of the underlying connection. // Close should be called when the client is no longer used. func CreateJetStreamClient(cfg *NatsConfig, pedantic bool) (jetstream.JetStream, Closable, error) { + nc, err := createNatsConn(cfg, pedantic) + if err != nil { + return nil, nil, fmt.Errorf("create nats connection: %w", err) + } + + js, err := jetstream.New(nc) + if err != nil { + return nil, nil, fmt.Errorf("new jetstream: %w", err) + } + return js, nc, nil +} + +func createNatsConn(cfg *NatsConfig, pedantic bool) (*nats.Conn, error) { opts, err := cfg.buildOptions() if err != nil { - return nil, nil, fmt.Errorf("nats options: %w", err) + return nil, err } // Set pedantic option @@ -82,14 +125,5 @@ func CreateJetStreamClient(cfg *NatsConfig, pedantic bool) (jetstream.JetStream, // client should always attempt to reconnect opts = append(opts, nats.MaxReconnects(-1)) - nc, err := nats.Connect(cfg.ServerURL, opts...) - if err != nil { - return nil, nil, fmt.Errorf("nats connect: %w", err) - } - - js, err := jetstream.New(nc) - if err != nil { - return nil, nil, fmt.Errorf("new jetstream: %w", err) - } - return js, nc, nil + return nats.Connect(cfg.ServerURL, opts...) } diff --git a/internal/controller/consumer_controller.go b/internal/controller/consumer_controller.go index 21efa23f..f0aebbe2 100644 --- a/internal/controller/consumer_controller.go +++ b/internal/controller/consumer_controller.go @@ -1,5 +1,5 @@ /* -Copyright 2024. +Copyright 2025. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/internal/controller/consumer_controller_test.go b/internal/controller/consumer_controller_test.go index 5b72f579..fa11ba9f 100644 --- a/internal/controller/consumer_controller_test.go +++ b/internal/controller/consumer_controller_test.go @@ -1,5 +1,5 @@ /* -Copyright 2024. +Copyright 2025. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/internal/controller/jetstream_controller.go b/internal/controller/jetstream_controller.go index 05fa338b..202b6973 100644 --- a/internal/controller/jetstream_controller.go +++ b/internal/controller/jetstream_controller.go @@ -2,14 +2,19 @@ package controller import ( "context" + "errors" "fmt" "math/rand/v2" "os" "path/filepath" + "regexp" + "strconv" "strings" "time" "github.com/google/go-cmp/cmp" + "github.com/nats-io/jsm.go" + jsmapi "github.com/nats-io/jsm.go/api" js "github.com/nats-io/nack/controllers/jetstream" api "github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2" "github.com/nats-io/nats.go/jetstream" @@ -19,6 +24,8 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" ) +var semVerRe = regexp.MustCompile(`\Av?([0-9]+)\.?([0-9]+)?\.?([0-9]+)?`) + type JetStreamController interface { client.Client @@ -36,6 +43,9 @@ type JetStreamController interface { // Returns the error of the operation or errors during client setup. WithJetStreamClient(opts api.ConnectionOpts, ns string, op func(js jetstream.JetStream) error) error + // WithJSMClient provides a jsm.go client to the given operation. + WithJSMClient(opts api.ConnectionOpts, ns string, op func(jsm *jsm.Manager) error) error + RequeueInterval() time.Duration } @@ -76,6 +86,21 @@ func (c *jsController) ValidNamespace(namespace string) bool { return ns == "" || ns == namespace } +func (c *jsController) WithJSMClient(opts api.ConnectionOpts, ns string, op func(js *jsm.Manager) error) error { + cfg, err := c.natsConfigFromOpts(opts, ns) + if err != nil { + return err + } + + jsmClient, closer, err := CreateJSMClient(cfg, true) + if err != nil { + return fmt.Errorf("create jsm client: %w", err) + } + defer closer.Close() + + return op(jsmClient) +} + func (c *jsController) WithJetStreamClient(opts api.ConnectionOpts, ns string, op func(js jetstream.JetStream) error) error { // Build single use client // TODO(future-feature): Use client-pool instead of single use client @@ -284,3 +309,31 @@ func jsonString(v string) []byte { func compareConfigState(actual any, desired any) string { return cmp.Diff(actual, desired) } + +func getErrCode(err error) uint16 { + if apiErr, ok := err.(jsmapi.ApiError); ok { + return apiErr.NatsErrorCode() + } + + return 0 +} + +func versionComponents(version string) (major, minor, patch int, err error) { + m := semVerRe.FindStringSubmatch(version) + if m == nil { + return 0, 0, 0, errors.New("invalid semver") + } + major, err = strconv.Atoi(m[1]) + if err != nil { + return -1, -1, -1, err + } + minor, err = strconv.Atoi(m[2]) + if err != nil { + return -1, -1, -1, err + } + patch, err = strconv.Atoi(m[3]) + if err != nil { + return -1, -1, -1, err + } + return major, minor, patch, err +} diff --git a/internal/controller/keyvalue_controller_test.go b/internal/controller/keyvalue_controller_test.go index 82cac273..088a20c2 100644 --- a/internal/controller/keyvalue_controller_test.go +++ b/internal/controller/keyvalue_controller_test.go @@ -1,5 +1,5 @@ /* -Copyright 2024. +Copyright 2025. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/internal/controller/objectstore_controller_test.go b/internal/controller/objectstore_controller_test.go index e3a0d999..22de4d16 100644 --- a/internal/controller/objectstore_controller_test.go +++ b/internal/controller/objectstore_controller_test.go @@ -1,5 +1,5 @@ /* -Copyright 2024. +Copyright 2025. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/internal/controller/stream_controller.go b/internal/controller/stream_controller.go index 49d18554..4b516c47 100644 --- a/internal/controller/stream_controller.go +++ b/internal/controller/stream_controller.go @@ -1,5 +1,5 @@ /* -Copyright 2024. +Copyright 2025. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -24,6 +24,8 @@ import ( "time" "github.com/go-logr/logr" + "github.com/nats-io/jsm.go" + jsmapi "github.com/nats-io/jsm.go/api" api "github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2" "github.com/nats-io/nats.go/jetstream" v1 "k8s.io/api/core/v1" @@ -135,8 +137,8 @@ func (r *StreamReconciler) deleteStream(ctx context.Context, log logr.Logger, st if !stream.Spec.PreventDelete && !r.ReadOnly() { log.Info("Deleting stream.") - err := r.WithJetStreamClient(stream.Spec.ConnectionOpts, stream.Namespace, func(js jetstream.JetStream) error { - _, err := getServerStreamState(ctx, js, stream) + err := r.WithJSMClient(stream.Spec.ConnectionOpts, stream.Namespace, func(js *jsm.Manager) error { + _, err := getServerStreamState(js, stream) // If we have no known state for this stream it has never been reconciled. // If we are also receiving an error fetching state, either the stream does not exist // or this resource config is invalid. @@ -144,7 +146,7 @@ func (r *StreamReconciler) deleteStream(ctx context.Context, log logr.Logger, st return nil } - return js.DeleteStream(ctx, stream.Spec.Name) + return js.DeleteStream(stream.Spec.Name) }) if errors.Is(err, jetstream.ErrStreamNotFound) { log.Info("Stream does not exist, unable to delete.", "streamName", stream.Spec.Name) @@ -180,14 +182,15 @@ func (r *StreamReconciler) createOrUpdate(ctx context.Context, log logr.Logger, } // CreateOrUpdateStream is called on every reconciliation when the stream is not to be deleted. - err = r.WithJetStreamClient(stream.Spec.ConnectionOpts, stream.Namespace, func(js jetstream.JetStream) error { + err = r.WithJSMClient(stream.Spec.ConnectionOpts, stream.Namespace, func(js *jsm.Manager) error { storedState, err := getStoredStreamState(stream) if err != nil { log.Error(err, "Failed to fetch stored stream state") } - serverState, err := getServerStreamState(ctx, js, stream) + serverState, err := getServerStreamState(js, stream) if err != nil { + log.Info("get err", "err", err, "code", getErrCode(err)) return err } @@ -211,18 +214,28 @@ func (r *StreamReconciler) createOrUpdate(ctx context.Context, log logr.Logger, return nil } - var updatedStream jetstream.Stream + var updatedStream *jsm.Stream err = nil if serverState == nil { log.Info("Creating Stream.") - updatedStream, err = js.CreateStream(ctx, targetConfig) + updatedStream, err = js.NewStream(stream.Spec.Name, targetConfig...) if err != nil { return err } } else if !stream.Spec.PreventUpdate { log.Info("Updating Stream.") - updatedStream, err = js.UpdateStream(ctx, targetConfig) + s, err := js.LoadStream(stream.Spec.Name) + if err != nil { + return err + } + + err = s.UpdateConfiguration(*serverState, targetConfig...) + if err != nil { + return err + } + + updatedStream, err = js.LoadStream(stream.Spec.Name) if err != nil { return err } @@ -234,7 +247,7 @@ func (r *StreamReconciler) createOrUpdate(ctx context.Context, log logr.Logger, if updatedStream != nil { // Store known state in annotation - updatedState, err := json.Marshal(updatedStream.CachedInfo().Config) + updatedState, err := json.Marshal(updatedStream.Configuration()) if err != nil { return err } @@ -274,8 +287,8 @@ func (r *StreamReconciler) createOrUpdate(ctx context.Context, log logr.Logger, return nil } -func getStoredStreamState(stream *api.Stream) (*jetstream.StreamConfig, error) { - var storedState *jetstream.StreamConfig +func getStoredStreamState(stream *api.Stream) (*jsmapi.StreamConfig, error) { + var storedState *jsmapi.StreamConfig if state, ok := stream.Annotations[stateAnnotationStream]; ok { err := json.Unmarshal([]byte(state), &storedState) if err != nil { @@ -287,155 +300,218 @@ func getStoredStreamState(stream *api.Stream) (*jetstream.StreamConfig, error) { } // Fetch the current state of the stream from the server. -// ErrStreamNotFound is considered a valid response and does not return error -func getServerStreamState(ctx context.Context, js jetstream.JetStream, stream *api.Stream) (*jetstream.StreamConfig, error) { - s, err := js.Stream(ctx, stream.Spec.Name) - if errors.Is(err, jetstream.ErrStreamNotFound) { +// JSStreamNotFoundErr is considered a valid response and does not return error +func getServerStreamState(jsm *jsm.Manager, stream *api.Stream) (*jsmapi.StreamConfig, error) { + s, err := jsm.LoadStream(stream.Spec.Name) + // 10059 -> JSStreamNotFoundErr + if jsmapi.IsNatsErr(err, 10059) { return nil, nil } if err != nil { return nil, err } - return &s.CachedInfo().Config, nil + streamCfg := s.Configuration() + return &streamCfg, nil } // streamSpecToConfig creates a jetstream.StreamConfig matching the given stream resource spec -func streamSpecToConfig(spec *api.StreamSpec) (jetstream.StreamConfig, error) { - // Set directly mapped fields - config := jetstream.StreamConfig{ - Name: spec.Name, - Description: spec.Description, - Subjects: spec.Subjects, - MaxConsumers: spec.MaxConsumers, - MaxMsgs: int64(spec.MaxMsgs), - MaxBytes: int64(spec.MaxBytes), - DiscardNewPerSubject: spec.DiscardPerSubject, - MaxMsgsPerSubject: int64(spec.MaxMsgsPerSubject), - MaxMsgSize: int32(spec.MaxMsgSize), - Replicas: spec.Replicas, - NoAck: spec.NoAck, - Sealed: spec.Sealed, - DenyDelete: spec.DenyDelete, - DenyPurge: spec.DenyPurge, - AllowRollup: spec.AllowRollup, - FirstSeq: spec.FirstSequence, - AllowDirect: spec.AllowDirect, - MirrorDirect: spec.MirrorDirect, - Metadata: spec.Metadata, +func streamSpecToConfig(spec *api.StreamSpec) ([]jsm.StreamOption, error) { + opts := []jsm.StreamOption{ + jsm.StreamDescription(spec.Description), + jsm.Subjects(spec.Subjects...), + jsm.MaxConsumers(spec.MaxConsumers), + jsm.MaxMessages(int64(spec.MaxMsgs)), + jsm.MaxBytes(int64(spec.MaxBytes)), + jsm.MaxMessageSize(int32(spec.MaxMsgSize)), + jsm.Replicas(spec.Replicas), } // Set not directly mapped fields // retention - if spec.Retention != "" { - // Wrap string in " to be properly unmarshalled as json string - err := config.Retention.UnmarshalJSON(jsonString(spec.Retention)) - if err != nil { - return jetstream.StreamConfig{}, fmt.Errorf("invalid retention policy: %w", err) - } - } - - // discard - if spec.Discard != "" { - err := config.Discard.UnmarshalJSON(jsonString(spec.Discard)) - if err != nil { - return jetstream.StreamConfig{}, fmt.Errorf("invalid retention policy: %w", err) - } + switch spec.Retention { + case "limits": + opts = append(opts, jsm.LimitsRetention()) + case "interest": + opts = append(opts, jsm.InterestRetention()) + case "workqueue": + opts = append(opts, jsm.WorkQueueRetention()) + } + + // maxMsgsPerSubject + if spec.MaxMsgsPerSubject > 0 { + opts = append(opts, func(o *jsmapi.StreamConfig) error { + o.MaxMsgsPer = int64(spec.MaxMsgsPerSubject) + return nil + }) } // maxAge if spec.MaxAge != "" { d, err := time.ParseDuration(spec.MaxAge) if err != nil { - return jetstream.StreamConfig{}, fmt.Errorf("parse max age: %w", err) + return nil, fmt.Errorf("parse max age: %w", err) } - config.MaxAge = d + opts = append(opts, jsm.MaxAge(d)) } + // storage - if spec.Storage != "" { - err := config.Storage.UnmarshalJSON(jsonString(spec.Storage)) - if err != nil { - return jetstream.StreamConfig{}, fmt.Errorf("invalid storage: %w", err) - } + switch spec.Storage { + case "file": + opts = append(opts, jsm.FileStorage()) + case "memory": + opts = append(opts, jsm.MemoryStorage()) } - // duplicates + // discard + switch spec.Discard { + case "old": + opts = append(opts, jsm.DiscardOld()) + case "new": + opts = append(opts, jsm.DiscardNew()) + } + + // noAck + if spec.NoAck { + opts = append(opts, jsm.NoAck()) + } + + // duplicateWindow if spec.DuplicateWindow != "" { d, err := time.ParseDuration(spec.DuplicateWindow) if err != nil { - return jetstream.StreamConfig{}, fmt.Errorf("parse duplicate window: %w", err) + return nil, fmt.Errorf("parse duplicate window: %w", err) } - config.Duplicates = d + opts = append(opts, jsm.DuplicateWindow(d)) } // placement if spec.Placement != nil { - config.Placement = &jetstream.Placement{ - Cluster: spec.Placement.Cluster, - Tags: spec.Placement.Tags, + if spec.Placement.Cluster != "" { + opts = append(opts, jsm.PlacementCluster(spec.Placement.Cluster)) + } + if spec.Placement.Tags != nil { + opts = append(opts, jsm.PlacementTags(spec.Placement.Tags...)) } } // mirror if spec.Mirror != nil { - ss, err := mapStreamSource(spec.Mirror) + ss, err := mapJSMStreamSource(spec.Mirror) if err != nil { - return jetstream.StreamConfig{}, fmt.Errorf("map mirror stream source: %w", err) + return nil, fmt.Errorf("map mirror stream source: %w", err) } - config.Mirror = ss + opts = append(opts, jsm.Mirror(ss)) } // sources if spec.Sources != nil { - config.Sources = []*jetstream.StreamSource{} + streamSources := make([]*jsmapi.StreamSource, 0) for _, source := range spec.Sources { - s, err := mapStreamSource(source) + ss, err := mapJSMStreamSource(source) if err != nil { - return jetstream.StreamConfig{}, fmt.Errorf("map stream source: %w", err) + return nil, fmt.Errorf("map stream source: %w", err) } - config.Sources = append(config.Sources, s) + streamSources = append(streamSources, ss) } + + opts = append(opts, jsm.Sources(streamSources...)) } // compression - if spec.Compression != "" { - err := config.Compression.UnmarshalJSON(jsonString(spec.Compression)) - if err != nil { - return jetstream.StreamConfig{}, fmt.Errorf("invalid compression: %w", err) - } + switch spec.Compression { + case "s2": + opts = append(opts, jsm.Compression(jsmapi.S2Compression)) + case "none": + opts = append(opts, jsm.Compression(jsmapi.NoCompression)) } // subjectTransform if spec.SubjectTransform != nil { - config.SubjectTransform = &jetstream.SubjectTransformConfig{ + st := &jsmapi.SubjectTransformConfig{ Source: spec.SubjectTransform.Source, Destination: spec.SubjectTransform.Dest, } + + opts = append(opts, jsm.SubjectTransform(st)) } // rePublish if spec.RePublish != nil { - config.RePublish = &jetstream.RePublish{ + r := &jsmapi.RePublish{ Source: spec.RePublish.Source, Destination: spec.RePublish.Destination, HeadersOnly: spec.RePublish.HeadersOnly, } + + opts = append(opts, jsm.Republish(r)) + } + + if spec.Sealed { + opts = append(opts, func(o *jsmapi.StreamConfig) error { + o.Sealed = spec.Sealed + return nil + }) + } + + // denyDelete + if spec.DenyDelete { + opts = append(opts, jsm.DenyDelete()) + } + + // denyPurge + if spec.DenyPurge { + opts = append(opts, jsm.DenyPurge()) + } + + // allowDirect + if spec.AllowDirect { + opts = append(opts, jsm.AllowDirect()) + } + + // allowRollup + if spec.AllowRollup { + opts = append(opts, jsm.AllowRollup()) + } + + // mirrorDirect + if spec.MirrorDirect { + opts = append(opts, jsm.MirrorDirect()) + } + + // discardPerSubject + if spec.DiscardPerSubject { + opts = append(opts, jsm.DiscardNewPerSubject()) + } + + // firstSequence + if spec.FirstSequence > 0 { + opts = append(opts, jsm.FirstSequence(spec.FirstSequence)) + } + + // metadata + if spec.Metadata != nil { + opts = append(opts, jsm.StreamMetadata(spec.Metadata)) } // consumerLimits if spec.ConsumerLimits != nil { - inactiveThreshold, err := time.ParseDuration(spec.ConsumerLimits.InactiveThreshold) - if err != nil { - return jetstream.StreamConfig{}, fmt.Errorf("parse inactive threshold: %w", err) + cl := jsmapi.StreamConsumerLimits{ + MaxAckPending: spec.ConsumerLimits.MaxAckPending, } - config.ConsumerLimits = jetstream.StreamConsumerLimits{ - InactiveThreshold: inactiveThreshold, - MaxAckPending: spec.ConsumerLimits.MaxAckPending, + if spec.ConsumerLimits.InactiveThreshold != "" { + inactiveThreshold, err := time.ParseDuration(spec.ConsumerLimits.InactiveThreshold) + if err != nil { + return nil, fmt.Errorf("parse inactive threshold: %w", err) + } + cl.InactiveThreshold = inactiveThreshold } + + opts = append(opts, jsm.ConsumerLimits(cl)) } - return config, nil + return opts, nil } func mapStreamSource(ss *api.StreamSource) (*jetstream.StreamSource, error) { @@ -447,6 +523,7 @@ func mapStreamSource(ss *api.StreamSource) (*jetstream.StreamSource, error) { if ss.OptStartSeq > 0 { jss.OptStartSeq = uint64(ss.OptStartSeq) } + if ss.OptStartTime != "" { t, err := time.Parse(time.RFC3339, ss.OptStartTime) if err != nil { @@ -472,6 +549,41 @@ func mapStreamSource(ss *api.StreamSource) (*jetstream.StreamSource, error) { return jss, nil } +func mapJSMStreamSource(ss *api.StreamSource) (*jsmapi.StreamSource, error) { + jss := &jsmapi.StreamSource{ + Name: ss.Name, + FilterSubject: ss.FilterSubject, + } + + if ss.OptStartSeq > 0 { + jss.OptStartSeq = uint64(ss.OptStartSeq) + } + + if ss.OptStartTime != "" { + t, err := time.Parse(time.RFC3339, ss.OptStartTime) + if err != nil { + return nil, fmt.Errorf("parse opt start time: %w", err) + } + jss.OptStartTime = &t + } + + if ss.ExternalAPIPrefix != "" || ss.ExternalDeliverPrefix != "" { + jss.External = &jsmapi.ExternalStream{ + ApiPrefix: ss.ExternalAPIPrefix, + DeliverPrefix: ss.ExternalDeliverPrefix, + } + } + + for _, transform := range ss.SubjectTransforms { + jss.SubjectTransforms = append(jss.SubjectTransforms, jsmapi.SubjectTransformConfig{ + Source: transform.Source, + Destination: transform.Dest, + }) + } + + return jss, nil +} + // SetupWithManager sets up the controller with the Manager. func (r *StreamReconciler) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). diff --git a/internal/controller/stream_controller_test.go b/internal/controller/stream_controller_test.go index b9ac6c6b..c37e158f 100644 --- a/internal/controller/stream_controller_test.go +++ b/internal/controller/stream_controller_test.go @@ -1,5 +1,5 @@ /* -Copyright 2024. +Copyright 2025. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -20,6 +20,7 @@ import ( "testing" "time" + jsmapi "github.com/nats-io/jsm.go/api" "github.com/nats-io/nats.go/jetstream" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" @@ -215,7 +216,7 @@ var _ = Describe("Stream Controller", func() { }) It("should not create the stream", func(ctx SpecContext) { _, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName}) - Expect(err.Error()).To(HaveSuffix("can not be sealed")) + Expect(err.Error()).To(HaveSuffix("can not be sealed (10052)")) }) }) @@ -579,13 +580,13 @@ func Test_mapSpecToConfig(t *testing.T) { tests := []struct { name string spec *api.StreamSpec - want jetstream.StreamConfig + want jsmapi.StreamConfig wantErr bool }{ { name: "empty spec", spec: &api.StreamSpec{}, - want: jetstream.StreamConfig{}, + want: jsmapi.StreamConfig{}, wantErr: false, }, { @@ -617,7 +618,6 @@ func Test_mapSpecToConfig(t *testing.T) { Dest: "transform-dest", }}, }, - Name: "stream-name", NoAck: true, Placement: &api.StreamPlacement{ Cluster: "test-cluster", @@ -665,75 +665,72 @@ func Test_mapSpecToConfig(t *testing.T) { }, }, }, - want: jetstream.StreamConfig{ - Name: "stream-name", - Description: "stream description", - Subjects: []string{"orders.*"}, - Retention: jetstream.InterestPolicy, - MaxConsumers: -1, - MaxMsgs: -1, - MaxBytes: -1, - Discard: jetstream.DiscardNew, - DiscardNewPerSubject: true, - MaxAge: time.Second * 30, - MaxMsgsPerSubject: 10, - MaxMsgSize: -1, - Storage: jetstream.FileStorage, - Replicas: 3, - NoAck: true, - Duplicates: time.Second * 5, - Placement: &jetstream.Placement{ + want: jsmapi.StreamConfig{ + Description: "stream description", + Subjects: []string{"orders.*"}, + Retention: jsmapi.InterestPolicy, + MaxConsumers: -1, + MaxMsgs: -1, + MaxBytes: -1, + Discard: jsmapi.DiscardNew, + DiscardNewPer: true, + MaxAge: time.Second * 30, + MaxMsgsPer: 10, + MaxMsgSize: -1, + Storage: jsmapi.FileStorage, + Replicas: 3, + NoAck: true, + Duplicates: time.Second * 5, + Placement: &jsmapi.Placement{ Cluster: "test-cluster", Tags: []string{"tag"}, }, - Mirror: &jetstream.StreamSource{ + Mirror: &jsmapi.StreamSource{ Name: "mirror", OptStartSeq: 5, OptStartTime: &date, FilterSubject: "orders", - SubjectTransforms: []jetstream.SubjectTransformConfig{{ + SubjectTransforms: []jsmapi.SubjectTransformConfig{{ Source: "transform-source", Destination: "transform-dest", }}, - External: &jetstream.ExternalStream{ - APIPrefix: "api", + External: &jsmapi.ExternalStream{ + ApiPrefix: "api", DeliverPrefix: "deliver", }, - Domain: "", }, - Sources: []*jetstream.StreamSource{{ + Sources: []*jsmapi.StreamSource{{ Name: "source", OptStartSeq: 5, OptStartTime: &date, FilterSubject: "orders", - SubjectTransforms: []jetstream.SubjectTransformConfig{{ + SubjectTransforms: []jsmapi.SubjectTransformConfig{{ Source: "transform-source", Destination: "transform-dest", }}, - External: &jetstream.ExternalStream{ - APIPrefix: "api", + External: &jsmapi.ExternalStream{ + ApiPrefix: "api", DeliverPrefix: "deliver", }, - Domain: "", }}, - Sealed: false, - DenyDelete: true, - DenyPurge: true, - AllowRollup: true, - Compression: jetstream.S2Compression, - FirstSeq: 42, - SubjectTransform: &jetstream.SubjectTransformConfig{ + Sealed: false, + DenyDelete: true, + DenyPurge: true, + RollupAllowed: true, + Compression: jsmapi.S2Compression, + FirstSeq: 42, + SubjectTransform: &jsmapi.SubjectTransformConfig{ Source: "transform-source", Destination: "transform-dest", }, - RePublish: &jetstream.RePublish{ + RePublish: &jsmapi.RePublish{ Source: "re-publish-source", Destination: "re-publish-dest", HeadersOnly: true, }, AllowDirect: true, MirrorDirect: false, - ConsumerLimits: jetstream.StreamConsumerLimits{}, + ConsumerLimits: jsmapi.StreamConsumerLimits{}, Metadata: map[string]string{ "meta": "data", }, @@ -745,14 +742,19 @@ func Test_mapSpecToConfig(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { assert := assert.New(t) - got, err := streamSpecToConfig(tt.spec) + sOpts, err := streamSpecToConfig(tt.spec) if (err != nil) != tt.wantErr { t.Errorf("streamSpecToConfig() error = %v, wantErr %v", err, tt.wantErr) return } + got := &jsmapi.StreamConfig{} + for _, o := range sOpts { + o(got) + } + // Compare nested structs - assert.EqualValues(tt.want, got) + assert.EqualValues(tt.want, *got) }) } } diff --git a/internal/controller/suite_test.go b/internal/controller/suite_test.go index a445def9..562cb278 100644 --- a/internal/controller/suite_test.go +++ b/internal/controller/suite_test.go @@ -1,5 +1,5 @@ /* -Copyright 2024. +Copyright 2025. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/pkg/jetstream/apis/jetstream/v1beta2/streamtypes.go b/pkg/jetstream/apis/jetstream/v1beta2/streamtypes.go index 964a5e27..3476318a 100644 --- a/pkg/jetstream/apis/jetstream/v1beta2/streamtypes.go +++ b/pkg/jetstream/apis/jetstream/v1beta2/streamtypes.go @@ -27,32 +27,32 @@ type StreamSpec struct { Subjects []string `json:"subjects"` Retention string `json:"retention"` MaxConsumers int `json:"maxConsumers"` + MaxMsgsPerSubject int `json:"maxMsgsPerSubject"` MaxMsgs int `json:"maxMsgs"` MaxBytes int `json:"maxBytes"` - Discard string `json:"discard"` - DiscardPerSubject bool `json:"discardPerSubject"` // Maps to DiscardNewPerSubject MaxAge string `json:"maxAge"` - MaxMsgsPerSubject int `json:"maxMsgsPerSubject"` MaxMsgSize int `json:"maxMsgSize"` Storage string `json:"storage"` + Discard string `json:"discard"` Replicas int `json:"replicas"` NoAck bool `json:"noAck"` DuplicateWindow string `json:"duplicateWindow"` // Maps to Duplicates Placement *StreamPlacement `json:"placement"` Mirror *StreamSource `json:"mirror"` Sources []*StreamSource `json:"sources"` - Sealed bool `json:"sealed"` - DenyDelete bool `json:"denyDelete"` - DenyPurge bool `json:"denyPurge"` - AllowRollup bool `json:"allowRollup"` Compression string `json:"compression"` - FirstSequence uint64 `json:"firstSequence"` // Maps to FirstSeq SubjectTransform *SubjectTransform `json:"subjectTransform"` RePublish *RePublish `json:"republish"` + Sealed bool `json:"sealed"` + DenyDelete bool `json:"denyDelete"` + DenyPurge bool `json:"denyPurge"` AllowDirect bool `json:"allowDirect"` + AllowRollup bool `json:"allowRollup"` // Maps to RollupAllowed MirrorDirect bool `json:"mirrorDirect"` - ConsumerLimits *ConsumerLimits `json:"consumerLimits"` + DiscardPerSubject bool `json:"discardPerSubject"` // Maps to DiscardNewPer + FirstSequence uint64 `json:"firstSequence"` // Maps to FirstSeq Metadata map[string]string `json:"metadata"` + ConsumerLimits *ConsumerLimits `json:"consumerLimits"` BaseStreamConfig } From f9e8121d8b00d4a8ff92d1cfb4c2fb0f31615184 Mon Sep 17 00:00:00 2001 From: Samuel Attwood Date: Tue, 21 Jan 2025 05:34:23 -0500 Subject: [PATCH 13/19] Move consumer controller to jsm.go for pedantic mode --- internal/controller/consumer_controller.go | 220 ++++++++++++------ .../controller/consumer_controller_test.go | 104 ++++++++- internal/controller/jetstream_controller.go | 5 + internal/controller/stream_controller.go | 5 +- .../apis/jetstream/v1beta2/consumertypes.go | 32 ++- .../v1beta2/zz_generated.deepcopy.go | 18 +- .../jetstream/v1beta2/consumerspec.go | 206 ++++++++-------- .../jetstream/v1beta2/streamspec.go | 154 ++++++------ 8 files changed, 452 insertions(+), 292 deletions(-) diff --git a/internal/controller/consumer_controller.go b/internal/controller/consumer_controller.go index f0aebbe2..13fe0864 100644 --- a/internal/controller/consumer_controller.go +++ b/internal/controller/consumer_controller.go @@ -21,10 +21,14 @@ import ( "encoding/json" "errors" "fmt" + "strconv" + "strings" "time" + "github.com/nats-io/jsm.go" + jsmapi "github.com/nats-io/jsm.go/api" + "github.com/go-logr/logr" - "github.com/nats-io/nats.go/jetstream" v1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" @@ -149,8 +153,8 @@ func (r *ConsumerReconciler) deleteConsumer(ctx context.Context, log logr.Logger } if !consumer.Spec.PreventDelete && !r.ReadOnly() { - err := r.WithJetStreamClient(consumer.Spec.ConnectionOpts, consumer.Namespace, func(js jetstream.JetStream) error { - _, err := getServerConsumerState(ctx, js, consumer) + err := r.WithJSMClient(consumer.Spec.ConnectionOpts, consumer.Namespace, func(js *jsm.Manager) error { + _, err := getServerConsumerState(js, consumer) // If we have no known state for this consumer it has never been reconciled. // If we are also receiving an error fetching state, either the consumer does not exist // or this resource config is invalid. @@ -158,12 +162,12 @@ func (r *ConsumerReconciler) deleteConsumer(ctx context.Context, log logr.Logger return nil } - return js.DeleteConsumer(ctx, consumer.Spec.StreamName, consumer.Spec.DurableName) + return js.DeleteConsumer(consumer.Spec.StreamName, consumer.Spec.DurableName) }) switch { - case errors.Is(err, jetstream.ErrConsumerNotFound): + case jsm.IsNatsError(err, JSConsumerNotFoundErr): log.Info("Consumer does not exist. Unable to delete.") - case errors.Is(err, jetstream.ErrStreamNotFound): + case jsm.IsNatsError(err, JSStreamNotFoundErr): log.Info("Stream of consumer does not exist. Unable to delete.") case err != nil: if storedState == nil { @@ -201,13 +205,13 @@ func (r *ConsumerReconciler) createOrUpdate(ctx context.Context, log klog.Logger return fmt.Errorf("map consumer spec to target config: %w", err) } - err = r.WithJetStreamClient(consumer.Spec.ConnectionOpts, consumer.Namespace, func(js jetstream.JetStream) error { + err = r.WithJSMClient(consumer.Spec.ConnectionOpts, consumer.Namespace, func(js *jsm.Manager) error { storedState, err := getStoredConsumerState(consumer) if err != nil { log.Error(err, "Failed to fetch stored consumer state.") } - serverState, err := getServerConsumerState(ctx, js, consumer) + serverState, err := getServerConsumerState(js, consumer) if err != nil { return err } @@ -232,18 +236,28 @@ func (r *ConsumerReconciler) createOrUpdate(ctx context.Context, log klog.Logger return nil } - var updatedConsumer jetstream.Consumer + var updatedConsumer *jsm.Consumer err = nil if serverState == nil { log.Info("Creating Consumer.") - updatedConsumer, err = js.CreateConsumer(ctx, consumer.Spec.StreamName, *targetConfig) + updatedConsumer, err = js.NewConsumer(consumer.Spec.StreamName, targetConfig...) if err != nil { return err } } else if !consumer.Spec.PreventUpdate { log.Info("Updating Consumer.") - updatedConsumer, err = js.UpdateConsumer(ctx, consumer.Spec.StreamName, *targetConfig) + c, err := js.LoadConsumer(consumer.Spec.StreamName, consumer.Spec.DurableName) + if err != nil { + return err + } + + err = c.UpdateConfiguration(targetConfig...) + if err != nil { + return err + } + + updatedConsumer, err = js.LoadConsumer(consumer.Spec.StreamName, consumer.Spec.DurableName) if err != nil { return err } @@ -255,7 +269,7 @@ func (r *ConsumerReconciler) createOrUpdate(ctx context.Context, log klog.Logger if updatedConsumer != nil { // Store known state in annotation - updatedState, err := json.Marshal(updatedConsumer.CachedInfo().Config) + updatedState, err := json.Marshal(updatedConsumer.Configuration()) if err != nil { return err } @@ -295,8 +309,8 @@ func (r *ConsumerReconciler) createOrUpdate(ctx context.Context, log klog.Logger return nil } -func getStoredConsumerState(consumer *api.Consumer) (*jetstream.ConsumerConfig, error) { - var storedState *jetstream.ConsumerConfig +func getStoredConsumerState(consumer *api.Consumer) (*jsmapi.ConsumerConfig, error) { + var storedState *jsmapi.ConsumerConfig if state, ok := consumer.Annotations[stateAnnotationConsumer]; ok { err := json.Unmarshal([]byte(state), &storedState) if err != nil { @@ -309,88 +323,148 @@ func getStoredConsumerState(consumer *api.Consumer) (*jetstream.ConsumerConfig, // Fetch the current state of the consumer from the server. // ErrConsumerNotFound is considered a valid response and does not return error -func getServerConsumerState(ctx context.Context, js jetstream.JetStream, consumer *api.Consumer) (*jetstream.ConsumerConfig, error) { - c, err := js.Consumer(ctx, consumer.Spec.StreamName, consumer.Spec.DurableName) - if errors.Is(err, jetstream.ErrConsumerNotFound) { +func getServerConsumerState(js *jsm.Manager, consumer *api.Consumer) (*jsmapi.ConsumerConfig, error) { + c, err := js.LoadConsumer(consumer.Spec.StreamName, consumer.Spec.DurableName) + if jsm.IsNatsError(err, JSConsumerNotFoundErr) { return nil, nil } if err != nil { return nil, err } - return &c.CachedInfo().Config, nil + consumerCfg := c.Configuration() + return &consumerCfg, nil } -func consumerSpecToConfig(spec *api.ConsumerSpec) (*jetstream.ConsumerConfig, error) { - config := &jetstream.ConsumerConfig{ - Durable: spec.DurableName, - Description: spec.Description, - OptStartSeq: uint64(spec.OptStartSeq), - MaxDeliver: spec.MaxDeliver, - FilterSubject: spec.FilterSubject, - RateLimit: uint64(spec.RateLimitBps), - SampleFrequency: spec.SampleFreq, - MaxWaiting: spec.MaxWaiting, - MaxAckPending: spec.MaxAckPending, - HeadersOnly: spec.HeadersOnly, - MaxRequestBatch: spec.MaxRequestBatch, - MaxRequestMaxBytes: spec.MaxRequestMaxBytes, - Replicas: spec.Replicas, - MemoryStorage: spec.MemStorage, - FilterSubjects: spec.FilterSubjects, - Metadata: spec.Metadata, - } - - // DeliverPolicy - if spec.DeliverPolicy != "" { - err := config.DeliverPolicy.UnmarshalJSON(jsonString(spec.DeliverPolicy)) +func consumerSpecToConfig(spec *api.ConsumerSpec) ([]jsm.ConsumerOption, error) { + opts := []jsm.ConsumerOption{ + jsm.ConsumerDescription(spec.Description), + jsm.DeliverySubject(spec.DeliverSubject), + jsm.DeliverGroup(spec.DeliverGroup), + jsm.DurableName(spec.DurableName), + jsm.MaxAckPending(uint(spec.MaxAckPending)), + jsm.MaxWaiting(uint(spec.MaxWaiting)), + jsm.RateLimitBitsPerSecond(uint64(spec.RateLimitBps)), + jsm.MaxRequestBatch(uint(spec.MaxRequestBatch)), + jsm.MaxRequestMaxBytes(spec.MaxRequestMaxBytes), + jsm.ConsumerOverrideReplicas(spec.Replicas), + jsm.ConsumerMetadata(spec.Metadata), + } + + // ackPolicy + switch spec.AckPolicy { + case "none": + opts = append(opts, jsm.AcknowledgeNone()) + case "all": + opts = append(opts, jsm.AcknowledgeAll()) + case "explicit": + opts = append(opts, jsm.AcknowledgeExplicit()) + case "": + default: + return nil, fmt.Errorf("invalid value for 'ackPolicy': '%s'. Must be one of 'none', 'all', 'explicit'", spec.AckPolicy) + } + + // ackWait + if spec.AckWait != "" { + d, err := time.ParseDuration(spec.AckWait) if err != nil { - return nil, fmt.Errorf("invalid delivery policy: %w", err) + return nil, fmt.Errorf("invalid ack wait duration: %w", err) } + opts = append(opts, jsm.AckWait(d)) } - // OptStartTime RFC3339 - if spec.OptStartTime != "" { + // deliverPolicy + switch spec.DeliverPolicy { + case "all": + opts = append(opts, jsm.DeliverAllAvailable()) + case "last": + opts = append(opts, jsm.StartWithLastReceived()) + case "new": + opts = append(opts, jsm.StartWithNextReceived()) + case "byStartSequence": + opts = append(opts, jsm.StartAtSequence(uint64(spec.OptStartSeq))) + case "byStartTime": + if spec.OptStartTime == "" { + return nil, fmt.Errorf("'optStartTime' is required for deliver policy 'byStartTime'") + } t, err := time.Parse(time.RFC3339, spec.OptStartTime) if err != nil { - return nil, fmt.Errorf("invalid opt start time: %w", err) + return nil, err } - config.OptStartTime = &t + opts = append(opts, jsm.StartAtTime(t)) + case "": + default: + return nil, fmt.Errorf("invalid value for 'deliverPolicy': '%s'. Must be one of 'all', 'last', 'new', 'byStartSequence', 'byStartTime'", spec.DeliverPolicy) } - // AckPolicy - if spec.AckPolicy != "" { - err := config.AckPolicy.UnmarshalJSON(jsonString(spec.AckPolicy)) - if err != nil { - return nil, fmt.Errorf("invalid ack policy: %w", err) - } + // filterSubject + if spec.FilterSubject != "" && len(spec.FilterSubjects) > 0 { + return nil, errors.New("cannot set both 'filterSubject' and 'filterSubjects'") } - // AckWait - if spec.AckWait != "" { - d, err := time.ParseDuration(spec.AckWait) + if spec.FilterSubject != "" { + opts = append(opts, jsm.FilterStreamBySubject(spec.FilterSubject)) + } else if len(spec.FilterSubjects) > 0 { + opts = append(opts, jsm.FilterStreamBySubject(spec.FilterSubjects...)) + } + + // flowControl + if spec.FlowControl { + opts = append(opts, jsm.PushFlowControl()) + } + + // heartbeatInterval + if spec.HeartbeatInterval != "" { + d, err := time.ParseDuration(spec.HeartbeatInterval) if err != nil { - return nil, fmt.Errorf("invalid ack wait duration: %w", err) + return nil, fmt.Errorf("invalid heartbeat interval: %w", err) } - config.AckWait = d + + opts = append(opts, jsm.IdleHeartbeat(d)) } - // BackOff - for _, bo := range spec.BackOff { - d, err := time.ParseDuration(bo) - if err != nil { - return nil, fmt.Errorf("invalid backoff: %w", err) + // maxDeliver + if spec.MaxDeliver != 0 { + opts = append(opts, jsm.MaxDeliveryAttempts(spec.MaxDeliver)) + } + + // backoff + if len(spec.BackOff) > 0 { + backoffs := make([]time.Duration, 0) + for _, bo := range spec.BackOff { + d, err := time.ParseDuration(bo) + if err != nil { + return nil, fmt.Errorf("invalid backoff: %w", err) + } + backoffs = append(backoffs, d) } - config.BackOff = append(config.BackOff, d) + opts = append(opts, jsm.BackoffIntervals(backoffs...)) } - // ReplayPolicy - if spec.ReplayPolicy != "" { - err := config.ReplayPolicy.UnmarshalJSON(jsonString(spec.ReplayPolicy)) + // replayPolicy + switch spec.ReplayPolicy { + case "instant": + opts = append(opts, jsm.ReplayInstantly()) + case "original": + opts = append(opts, jsm.ReplayAsReceived()) + case "": + default: + return nil, fmt.Errorf("invalid value for 'replayPolicy': '%s'. Must be one of 'instant', 'original'", spec.ReplayPolicy) + } + + if spec.SampleFreq != "" { + n, err := strconv.Atoi( + strings.TrimSuffix(spec.SampleFreq, "%"), + ) if err != nil { - return nil, fmt.Errorf("invalid replay policy: %w", err) + return nil, err } + opts = append(opts, jsm.SamplePercent(n)) + } + + if spec.HeadersOnly { + opts = append(opts, jsm.DeliverHeadersOnly()) } // MaxRequestExpires @@ -399,18 +473,24 @@ func consumerSpecToConfig(spec *api.ConsumerSpec) (*jetstream.ConsumerConfig, er if err != nil { return nil, fmt.Errorf("invalid opt start time: %w", err) } - config.MaxRequestExpires = d + opts = append(opts, jsm.MaxRequestExpires(d)) } + // inactiveThreshold if spec.InactiveThreshold != "" { d, err := time.ParseDuration(spec.InactiveThreshold) if err != nil { return nil, fmt.Errorf("invalid inactive threshold: %w", err) } - config.InactiveThreshold = d + opts = append(opts, jsm.InactiveThreshold(d)) + } + + // memStorage + if spec.MemStorage { + opts = append(opts, jsm.ConsumerOverrideMemoryStorage()) } - return config, nil + return opts, nil } // SetupWithManager sets up the controller with the Manager. diff --git a/internal/controller/consumer_controller_test.go b/internal/controller/consumer_controller_test.go index fa11ba9f..778c0832 100644 --- a/internal/controller/consumer_controller_test.go +++ b/internal/controller/consumer_controller_test.go @@ -20,6 +20,7 @@ import ( "testing" "time" + jsmapi "github.com/nats-io/jsm.go/api" "github.com/nats-io/nats.go/jetstream" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" @@ -584,13 +585,13 @@ func Test_consumerSpecToConfig(t *testing.T) { tests := []struct { name string spec *api.ConsumerSpec - want *jetstream.ConsumerConfig + want *jsmapi.ConsumerConfig wantErr bool }{ { name: "empty spec", spec: &api.ConsumerSpec{}, - want: &jetstream.ConsumerConfig{}, + want: &jsmapi.ConsumerConfig{}, wantErr: false, }, { @@ -600,11 +601,11 @@ func Test_consumerSpecToConfig(t *testing.T) { AckWait: "10ns", BackOff: []string{"1s", "5m"}, DeliverGroup: "", - DeliverPolicy: "new", + DeliverPolicy: "byStartSequence", DeliverSubject: "", Description: "test consumer", DurableName: "test-consumer", - FilterSubject: "time.us.>", + FilterSubject: "", FilterSubjects: []string{"time.us.east", "time.us.west"}, FlowControl: false, HeadersOnly: true, @@ -617,7 +618,7 @@ func Test_consumerSpecToConfig(t *testing.T) { MaxWaiting: 5, MemStorage: true, OptStartSeq: 17, - OptStartTime: dateString, + OptStartTime: "", RateLimitBps: 512, ReplayPolicy: "instant", Replicas: 9, @@ -638,18 +639,17 @@ func Test_consumerSpecToConfig(t *testing.T) { }, }, }, - want: &jetstream.ConsumerConfig{ + want: &jsmapi.ConsumerConfig{ Durable: "test-consumer", Description: "test consumer", - DeliverPolicy: jetstream.DeliverNewPolicy, + DeliverPolicy: jsmapi.DeliverByStartSequence, OptStartSeq: 17, - OptStartTime: &date, - AckPolicy: jetstream.AckExplicitPolicy, + AckPolicy: jsmapi.AckExplicit, AckWait: 10 * time.Nanosecond, MaxDeliver: 3, BackOff: []time.Duration{time.Second, 5 * time.Minute}, - FilterSubject: "time.us.>", - ReplayPolicy: jetstream.ReplayInstantPolicy, + FilterSubject: "", + ReplayPolicy: jsmapi.ReplayInstant, RateLimit: 512, SampleFrequency: "25%", MaxWaiting: 5, @@ -668,14 +668,94 @@ func Test_consumerSpecToConfig(t *testing.T) { }, wantErr: false, }, + { + name: "full spec alt", + spec: &api.ConsumerSpec{ + AckPolicy: "all", + AckWait: "20ns", + BackOff: []string{"1s", "5m"}, + DeliverGroup: "", + DeliverPolicy: "byStartTime", + DeliverSubject: "", + Description: "test consumer", + DurableName: "test-consumer", + FilterSubject: "time.us.>", + FlowControl: true, + HeadersOnly: false, + HeartbeatInterval: "", + MaxAckPending: 5, + MaxDeliver: 6, + MaxRequestBatch: 7, + MaxRequestExpires: "8s", + MaxRequestMaxBytes: 1024, + MaxWaiting: 5, + MemStorage: false, + OptStartSeq: 17, + OptStartTime: dateString, + RateLimitBps: 1024, + ReplayPolicy: "original", + Replicas: 9, + SampleFreq: "30%", + StreamName: "", + Metadata: map[string]string{ + "meta": "data", + }, + BaseStreamConfig: api.BaseStreamConfig{ + PreventDelete: false, + PreventUpdate: false, + ConnectionOpts: api.ConnectionOpts{ + Account: "", + Creds: "", + Nkey: "", + TLS: api.TLS{}, + Servers: nil, + }, + }, + }, + want: &jsmapi.ConsumerConfig{ + Durable: "test-consumer", + Description: "test consumer", + DeliverPolicy: jsmapi.DeliverByStartTime, + OptStartSeq: 0, + OptStartTime: &date, + AckPolicy: jsmapi.AckAll, + AckWait: 20 * time.Nanosecond, + MaxDeliver: 6, + BackOff: []time.Duration{time.Second, 5 * time.Minute}, + FlowControl: true, + FilterSubject: "time.us.>", + ReplayPolicy: jsmapi.ReplayOriginal, + RateLimit: 1024, + SampleFrequency: "30%", + MaxWaiting: 5, + MaxAckPending: 5, + HeadersOnly: false, + MaxRequestBatch: 7, + MaxRequestExpires: 8 * time.Second, + MaxRequestMaxBytes: 1024, + InactiveThreshold: 0, // TODO no value? + Replicas: 9, + MemoryStorage: false, + Metadata: map[string]string{ + "meta": "data", + }, + }, + wantErr: false, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, err := consumerSpecToConfig(tt.spec) + cOpts, err := consumerSpecToConfig(tt.spec) if (err != nil) != tt.wantErr { t.Errorf("consumerSpecToConfig() error = %v, wantErr %v", err, tt.wantErr) return } + + got := &jsmapi.ConsumerConfig{} + for _, o := range cOpts { + o(got) + } + assert.EqualValues(t, tt.want, got, "consumerSpecToConfig(%v)", tt.spec) }) } diff --git a/internal/controller/jetstream_controller.go b/internal/controller/jetstream_controller.go index 202b6973..9d8d9005 100644 --- a/internal/controller/jetstream_controller.go +++ b/internal/controller/jetstream_controller.go @@ -24,6 +24,11 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" ) +const ( + JSConsumerNotFoundErr uint16 = 10014 + JSStreamNotFoundErr uint16 = 10059 +) + var semVerRe = regexp.MustCompile(`\Av?([0-9]+)\.?([0-9]+)?\.?([0-9]+)?`) type JetStreamController interface { diff --git a/internal/controller/stream_controller.go b/internal/controller/stream_controller.go index 4b516c47..42c14c74 100644 --- a/internal/controller/stream_controller.go +++ b/internal/controller/stream_controller.go @@ -148,7 +148,7 @@ func (r *StreamReconciler) deleteStream(ctx context.Context, log logr.Logger, st return js.DeleteStream(stream.Spec.Name) }) - if errors.Is(err, jetstream.ErrStreamNotFound) { + if jsmapi.IsNatsErr(err, JSStreamNotFoundErr) { log.Info("Stream does not exist, unable to delete.", "streamName", stream.Spec.Name) } else if err != nil && storedState == nil { log.Info("Stream not reconciled and no state received from server. Removing finalizer.") @@ -303,8 +303,7 @@ func getStoredStreamState(stream *api.Stream) (*jsmapi.StreamConfig, error) { // JSStreamNotFoundErr is considered a valid response and does not return error func getServerStreamState(jsm *jsm.Manager, stream *api.Stream) (*jsmapi.StreamConfig, error) { s, err := jsm.LoadStream(stream.Spec.Name) - // 10059 -> JSStreamNotFoundErr - if jsmapi.IsNatsErr(err, 10059) { + if jsmapi.IsNatsErr(err, JSStreamNotFoundErr) { return nil, nil } if err != nil { diff --git a/pkg/jetstream/apis/jetstream/v1beta2/consumertypes.go b/pkg/jetstream/apis/jetstream/v1beta2/consumertypes.go index 7ec1ec59..07d66af9 100644 --- a/pkg/jetstream/apis/jetstream/v1beta2/consumertypes.go +++ b/pkg/jetstream/apis/jetstream/v1beta2/consumertypes.go @@ -22,21 +22,26 @@ func (c *Consumer) GetSpec() interface{} { // ConsumerSpec is the spec for a Consumer resource type ConsumerSpec struct { - DurableName string `json:"durableName"` // Maps to Durable Description string `json:"description"` - DeliverPolicy string `json:"deliverPolicy"` - OptStartSeq int `json:"optStartSeq"` - OptStartTime string `json:"optStartTime"` AckPolicy string `json:"ackPolicy"` AckWait string `json:"ackWait"` + DeliverPolicy string `json:"deliverPolicy"` + DeliverSubject string `json:"deliverSubject"` + DeliverGroup string `json:"deliverGroup"` + DurableName string `json:"durableName"` // Maps to Durable + FilterSubject string `json:"filterSubject"` + FilterSubjects []string `json:"filterSubjects"` + FlowControl bool `json:"flowControl"` + HeartbeatInterval string `json:"heartbeatInterval"` // Maps to Heartbeat + MaxAckPending int `json:"maxAckPending"` MaxDeliver int `json:"maxDeliver"` BackOff []string `json:"backoff"` - FilterSubject string `json:"filterSubject"` - ReplayPolicy string `json:"replayPolicy"` - RateLimitBps int `json:"rateLimitBps"` // Maps to RateLimit - SampleFreq string `json:"sampleFreq"` // Maps to SampleFrequency MaxWaiting int `json:"maxWaiting"` - MaxAckPending int `json:"maxAckPending"` + OptStartSeq int `json:"optStartSeq"` + OptStartTime string `json:"optStartTime"` + RateLimitBps int `json:"rateLimitBps"` // Maps to RateLimit + ReplayPolicy string `json:"replayPolicy"` + SampleFreq string `json:"sampleFreq"` // Maps to SampleFrequency HeadersOnly bool `json:"headersOnly"` MaxRequestBatch int `json:"maxRequestBatch"` MaxRequestExpires string `json:"maxRequestExpires"` @@ -44,17 +49,8 @@ type ConsumerSpec struct { InactiveThreshold string `json:"inactiveThreshold"` Replicas int `json:"replicas"` MemStorage bool `json:"memStorage"` // Maps to MemoryStorage - FilterSubjects []string `json:"filterSubjects"` Metadata map[string]string `json:"metadata"` - // Legacy API options for Push Consumers. - // controller-runtime implementation moves to modern JetStream API over legacy - // which does not support Push Consumers - FlowControl bool `json:"flowControl"` - DeliverSubject string `json:"deliverSubject"` - DeliverGroup string `json:"deliverGroup"` - HeartbeatInterval string `json:"heartbeatInterval"` - StreamName string `json:"streamName"` BaseStreamConfig } diff --git a/pkg/jetstream/apis/jetstream/v1beta2/zz_generated.deepcopy.go b/pkg/jetstream/apis/jetstream/v1beta2/zz_generated.deepcopy.go index f45b2f5c..c639bbda 100644 --- a/pkg/jetstream/apis/jetstream/v1beta2/zz_generated.deepcopy.go +++ b/pkg/jetstream/apis/jetstream/v1beta2/zz_generated.deepcopy.go @@ -259,13 +259,13 @@ func (in *ConsumerList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ConsumerSpec) DeepCopyInto(out *ConsumerSpec) { *out = *in - if in.BackOff != nil { - in, out := &in.BackOff, &out.BackOff + if in.FilterSubjects != nil { + in, out := &in.FilterSubjects, &out.FilterSubjects *out = make([]string, len(*in)) copy(*out, *in) } - if in.FilterSubjects != nil { - in, out := &in.FilterSubjects, &out.FilterSubjects + if in.BackOff != nil { + in, out := &in.BackOff, &out.BackOff *out = make([]string, len(*in)) copy(*out, *in) } @@ -722,11 +722,6 @@ func (in *StreamSpec) DeepCopyInto(out *StreamSpec) { *out = new(RePublish) **out = **in } - if in.ConsumerLimits != nil { - in, out := &in.ConsumerLimits, &out.ConsumerLimits - *out = new(ConsumerLimits) - **out = **in - } if in.Metadata != nil { in, out := &in.Metadata, &out.Metadata *out = make(map[string]string, len(*in)) @@ -734,6 +729,11 @@ func (in *StreamSpec) DeepCopyInto(out *StreamSpec) { (*out)[key] = val } } + if in.ConsumerLimits != nil { + in, out := &in.ConsumerLimits, &out.ConsumerLimits + *out = new(ConsumerLimits) + **out = **in + } in.BaseStreamConfig.DeepCopyInto(&out.BaseStreamConfig) return } diff --git a/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/consumerspec.go b/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/consumerspec.go index 0604bf72..38bbcafd 100644 --- a/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/consumerspec.go +++ b/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/consumerspec.go @@ -18,21 +18,26 @@ package v1beta2 // ConsumerSpecApplyConfiguration represents a declarative configuration of the ConsumerSpec type for use // with apply. type ConsumerSpecApplyConfiguration struct { - DurableName *string `json:"durableName,omitempty"` Description *string `json:"description,omitempty"` - DeliverPolicy *string `json:"deliverPolicy,omitempty"` - OptStartSeq *int `json:"optStartSeq,omitempty"` - OptStartTime *string `json:"optStartTime,omitempty"` AckPolicy *string `json:"ackPolicy,omitempty"` AckWait *string `json:"ackWait,omitempty"` + DeliverPolicy *string `json:"deliverPolicy,omitempty"` + DeliverSubject *string `json:"deliverSubject,omitempty"` + DeliverGroup *string `json:"deliverGroup,omitempty"` + DurableName *string `json:"durableName,omitempty"` + FilterSubject *string `json:"filterSubject,omitempty"` + FilterSubjects []string `json:"filterSubjects,omitempty"` + FlowControl *bool `json:"flowControl,omitempty"` + HeartbeatInterval *string `json:"heartbeatInterval,omitempty"` + MaxAckPending *int `json:"maxAckPending,omitempty"` MaxDeliver *int `json:"maxDeliver,omitempty"` BackOff []string `json:"backoff,omitempty"` - FilterSubject *string `json:"filterSubject,omitempty"` - ReplayPolicy *string `json:"replayPolicy,omitempty"` + MaxWaiting *int `json:"maxWaiting,omitempty"` + OptStartSeq *int `json:"optStartSeq,omitempty"` + OptStartTime *string `json:"optStartTime,omitempty"` RateLimitBps *int `json:"rateLimitBps,omitempty"` + ReplayPolicy *string `json:"replayPolicy,omitempty"` SampleFreq *string `json:"sampleFreq,omitempty"` - MaxWaiting *int `json:"maxWaiting,omitempty"` - MaxAckPending *int `json:"maxAckPending,omitempty"` HeadersOnly *bool `json:"headersOnly,omitempty"` MaxRequestBatch *int `json:"maxRequestBatch,omitempty"` MaxRequestExpires *string `json:"maxRequestExpires,omitempty"` @@ -40,12 +45,7 @@ type ConsumerSpecApplyConfiguration struct { InactiveThreshold *string `json:"inactiveThreshold,omitempty"` Replicas *int `json:"replicas,omitempty"` MemStorage *bool `json:"memStorage,omitempty"` - FilterSubjects []string `json:"filterSubjects,omitempty"` Metadata map[string]string `json:"metadata,omitempty"` - FlowControl *bool `json:"flowControl,omitempty"` - DeliverSubject *string `json:"deliverSubject,omitempty"` - DeliverGroup *string `json:"deliverGroup,omitempty"` - HeartbeatInterval *string `json:"heartbeatInterval,omitempty"` StreamName *string `json:"streamName,omitempty"` } @@ -55,14 +55,6 @@ func ConsumerSpec() *ConsumerSpecApplyConfiguration { return &ConsumerSpecApplyConfiguration{} } -// WithDurableName sets the DurableName field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the DurableName field is set to the value of the last call. -func (b *ConsumerSpecApplyConfiguration) WithDurableName(value string) *ConsumerSpecApplyConfiguration { - b.DurableName = &value - return b -} - // WithDescription sets the Description field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. // If called multiple times, the Description field is set to the value of the last call. @@ -71,6 +63,22 @@ func (b *ConsumerSpecApplyConfiguration) WithDescription(value string) *Consumer return b } +// WithAckPolicy sets the AckPolicy field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the AckPolicy field is set to the value of the last call. +func (b *ConsumerSpecApplyConfiguration) WithAckPolicy(value string) *ConsumerSpecApplyConfiguration { + b.AckPolicy = &value + return b +} + +// WithAckWait sets the AckWait field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the AckWait field is set to the value of the last call. +func (b *ConsumerSpecApplyConfiguration) WithAckWait(value string) *ConsumerSpecApplyConfiguration { + b.AckWait = &value + return b +} + // WithDeliverPolicy sets the DeliverPolicy field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. // If called multiple times, the DeliverPolicy field is set to the value of the last call. @@ -79,35 +87,69 @@ func (b *ConsumerSpecApplyConfiguration) WithDeliverPolicy(value string) *Consum return b } -// WithOptStartSeq sets the OptStartSeq field in the declarative configuration to the given value +// WithDeliverSubject sets the DeliverSubject field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the OptStartSeq field is set to the value of the last call. -func (b *ConsumerSpecApplyConfiguration) WithOptStartSeq(value int) *ConsumerSpecApplyConfiguration { - b.OptStartSeq = &value +// If called multiple times, the DeliverSubject field is set to the value of the last call. +func (b *ConsumerSpecApplyConfiguration) WithDeliverSubject(value string) *ConsumerSpecApplyConfiguration { + b.DeliverSubject = &value return b } -// WithOptStartTime sets the OptStartTime field in the declarative configuration to the given value +// WithDeliverGroup sets the DeliverGroup field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the OptStartTime field is set to the value of the last call. -func (b *ConsumerSpecApplyConfiguration) WithOptStartTime(value string) *ConsumerSpecApplyConfiguration { - b.OptStartTime = &value +// If called multiple times, the DeliverGroup field is set to the value of the last call. +func (b *ConsumerSpecApplyConfiguration) WithDeliverGroup(value string) *ConsumerSpecApplyConfiguration { + b.DeliverGroup = &value return b } -// WithAckPolicy sets the AckPolicy field in the declarative configuration to the given value +// WithDurableName sets the DurableName field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the AckPolicy field is set to the value of the last call. -func (b *ConsumerSpecApplyConfiguration) WithAckPolicy(value string) *ConsumerSpecApplyConfiguration { - b.AckPolicy = &value +// If called multiple times, the DurableName field is set to the value of the last call. +func (b *ConsumerSpecApplyConfiguration) WithDurableName(value string) *ConsumerSpecApplyConfiguration { + b.DurableName = &value return b } -// WithAckWait sets the AckWait field in the declarative configuration to the given value +// WithFilterSubject sets the FilterSubject field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the AckWait field is set to the value of the last call. -func (b *ConsumerSpecApplyConfiguration) WithAckWait(value string) *ConsumerSpecApplyConfiguration { - b.AckWait = &value +// If called multiple times, the FilterSubject field is set to the value of the last call. +func (b *ConsumerSpecApplyConfiguration) WithFilterSubject(value string) *ConsumerSpecApplyConfiguration { + b.FilterSubject = &value + return b +} + +// WithFilterSubjects adds the given value to the FilterSubjects field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the FilterSubjects field. +func (b *ConsumerSpecApplyConfiguration) WithFilterSubjects(values ...string) *ConsumerSpecApplyConfiguration { + for i := range values { + b.FilterSubjects = append(b.FilterSubjects, values[i]) + } + return b +} + +// WithFlowControl sets the FlowControl field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the FlowControl field is set to the value of the last call. +func (b *ConsumerSpecApplyConfiguration) WithFlowControl(value bool) *ConsumerSpecApplyConfiguration { + b.FlowControl = &value + return b +} + +// WithHeartbeatInterval sets the HeartbeatInterval field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the HeartbeatInterval field is set to the value of the last call. +func (b *ConsumerSpecApplyConfiguration) WithHeartbeatInterval(value string) *ConsumerSpecApplyConfiguration { + b.HeartbeatInterval = &value + return b +} + +// WithMaxAckPending sets the MaxAckPending field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the MaxAckPending field is set to the value of the last call. +func (b *ConsumerSpecApplyConfiguration) WithMaxAckPending(value int) *ConsumerSpecApplyConfiguration { + b.MaxAckPending = &value return b } @@ -129,51 +171,51 @@ func (b *ConsumerSpecApplyConfiguration) WithBackOff(values ...string) *Consumer return b } -// WithFilterSubject sets the FilterSubject field in the declarative configuration to the given value +// WithMaxWaiting sets the MaxWaiting field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the FilterSubject field is set to the value of the last call. -func (b *ConsumerSpecApplyConfiguration) WithFilterSubject(value string) *ConsumerSpecApplyConfiguration { - b.FilterSubject = &value +// If called multiple times, the MaxWaiting field is set to the value of the last call. +func (b *ConsumerSpecApplyConfiguration) WithMaxWaiting(value int) *ConsumerSpecApplyConfiguration { + b.MaxWaiting = &value return b } -// WithReplayPolicy sets the ReplayPolicy field in the declarative configuration to the given value +// WithOptStartSeq sets the OptStartSeq field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the ReplayPolicy field is set to the value of the last call. -func (b *ConsumerSpecApplyConfiguration) WithReplayPolicy(value string) *ConsumerSpecApplyConfiguration { - b.ReplayPolicy = &value +// If called multiple times, the OptStartSeq field is set to the value of the last call. +func (b *ConsumerSpecApplyConfiguration) WithOptStartSeq(value int) *ConsumerSpecApplyConfiguration { + b.OptStartSeq = &value return b } -// WithRateLimitBps sets the RateLimitBps field in the declarative configuration to the given value +// WithOptStartTime sets the OptStartTime field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the RateLimitBps field is set to the value of the last call. -func (b *ConsumerSpecApplyConfiguration) WithRateLimitBps(value int) *ConsumerSpecApplyConfiguration { - b.RateLimitBps = &value +// If called multiple times, the OptStartTime field is set to the value of the last call. +func (b *ConsumerSpecApplyConfiguration) WithOptStartTime(value string) *ConsumerSpecApplyConfiguration { + b.OptStartTime = &value return b } -// WithSampleFreq sets the SampleFreq field in the declarative configuration to the given value +// WithRateLimitBps sets the RateLimitBps field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the SampleFreq field is set to the value of the last call. -func (b *ConsumerSpecApplyConfiguration) WithSampleFreq(value string) *ConsumerSpecApplyConfiguration { - b.SampleFreq = &value +// If called multiple times, the RateLimitBps field is set to the value of the last call. +func (b *ConsumerSpecApplyConfiguration) WithRateLimitBps(value int) *ConsumerSpecApplyConfiguration { + b.RateLimitBps = &value return b } -// WithMaxWaiting sets the MaxWaiting field in the declarative configuration to the given value +// WithReplayPolicy sets the ReplayPolicy field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the MaxWaiting field is set to the value of the last call. -func (b *ConsumerSpecApplyConfiguration) WithMaxWaiting(value int) *ConsumerSpecApplyConfiguration { - b.MaxWaiting = &value +// If called multiple times, the ReplayPolicy field is set to the value of the last call. +func (b *ConsumerSpecApplyConfiguration) WithReplayPolicy(value string) *ConsumerSpecApplyConfiguration { + b.ReplayPolicy = &value return b } -// WithMaxAckPending sets the MaxAckPending field in the declarative configuration to the given value +// WithSampleFreq sets the SampleFreq field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the MaxAckPending field is set to the value of the last call. -func (b *ConsumerSpecApplyConfiguration) WithMaxAckPending(value int) *ConsumerSpecApplyConfiguration { - b.MaxAckPending = &value +// If called multiple times, the SampleFreq field is set to the value of the last call. +func (b *ConsumerSpecApplyConfiguration) WithSampleFreq(value string) *ConsumerSpecApplyConfiguration { + b.SampleFreq = &value return b } @@ -233,16 +275,6 @@ func (b *ConsumerSpecApplyConfiguration) WithMemStorage(value bool) *ConsumerSpe return b } -// WithFilterSubjects adds the given value to the FilterSubjects field in the declarative configuration -// and returns the receiver, so that objects can be build by chaining "With" function invocations. -// If called multiple times, values provided by each call will be appended to the FilterSubjects field. -func (b *ConsumerSpecApplyConfiguration) WithFilterSubjects(values ...string) *ConsumerSpecApplyConfiguration { - for i := range values { - b.FilterSubjects = append(b.FilterSubjects, values[i]) - } - return b -} - // WithMetadata puts the entries into the Metadata field in the declarative configuration // and returns the receiver, so that objects can be build by chaining "With" function invocations. // If called multiple times, the entries provided by each call will be put on the Metadata field, @@ -257,38 +289,6 @@ func (b *ConsumerSpecApplyConfiguration) WithMetadata(entries map[string]string) return b } -// WithFlowControl sets the FlowControl field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the FlowControl field is set to the value of the last call. -func (b *ConsumerSpecApplyConfiguration) WithFlowControl(value bool) *ConsumerSpecApplyConfiguration { - b.FlowControl = &value - return b -} - -// WithDeliverSubject sets the DeliverSubject field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the DeliverSubject field is set to the value of the last call. -func (b *ConsumerSpecApplyConfiguration) WithDeliverSubject(value string) *ConsumerSpecApplyConfiguration { - b.DeliverSubject = &value - return b -} - -// WithDeliverGroup sets the DeliverGroup field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the DeliverGroup field is set to the value of the last call. -func (b *ConsumerSpecApplyConfiguration) WithDeliverGroup(value string) *ConsumerSpecApplyConfiguration { - b.DeliverGroup = &value - return b -} - -// WithHeartbeatInterval sets the HeartbeatInterval field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the HeartbeatInterval field is set to the value of the last call. -func (b *ConsumerSpecApplyConfiguration) WithHeartbeatInterval(value string) *ConsumerSpecApplyConfiguration { - b.HeartbeatInterval = &value - return b -} - // WithStreamName sets the StreamName field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. // If called multiple times, the StreamName field is set to the value of the last call. diff --git a/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/streamspec.go b/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/streamspec.go index 1831f3de..8d8e1d09 100644 --- a/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/streamspec.go +++ b/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/streamspec.go @@ -27,32 +27,32 @@ type StreamSpecApplyConfiguration struct { Subjects []string `json:"subjects,omitempty"` Retention *string `json:"retention,omitempty"` MaxConsumers *int `json:"maxConsumers,omitempty"` + MaxMsgsPerSubject *int `json:"maxMsgsPerSubject,omitempty"` MaxMsgs *int `json:"maxMsgs,omitempty"` MaxBytes *int `json:"maxBytes,omitempty"` - Discard *string `json:"discard,omitempty"` - DiscardPerSubject *bool `json:"discardPerSubject,omitempty"` MaxAge *string `json:"maxAge,omitempty"` - MaxMsgsPerSubject *int `json:"maxMsgsPerSubject,omitempty"` MaxMsgSize *int `json:"maxMsgSize,omitempty"` Storage *string `json:"storage,omitempty"` + Discard *string `json:"discard,omitempty"` Replicas *int `json:"replicas,omitempty"` NoAck *bool `json:"noAck,omitempty"` DuplicateWindow *string `json:"duplicateWindow,omitempty"` Placement *StreamPlacementApplyConfiguration `json:"placement,omitempty"` Mirror *StreamSourceApplyConfiguration `json:"mirror,omitempty"` Sources []*jetstreamv1beta2.StreamSource `json:"sources,omitempty"` - Sealed *bool `json:"sealed,omitempty"` - DenyDelete *bool `json:"denyDelete,omitempty"` - DenyPurge *bool `json:"denyPurge,omitempty"` - AllowRollup *bool `json:"allowRollup,omitempty"` Compression *string `json:"compression,omitempty"` - FirstSequence *uint64 `json:"firstSequence,omitempty"` SubjectTransform *SubjectTransformApplyConfiguration `json:"subjectTransform,omitempty"` RePublish *RePublishApplyConfiguration `json:"republish,omitempty"` + Sealed *bool `json:"sealed,omitempty"` + DenyDelete *bool `json:"denyDelete,omitempty"` + DenyPurge *bool `json:"denyPurge,omitempty"` AllowDirect *bool `json:"allowDirect,omitempty"` + AllowRollup *bool `json:"allowRollup,omitempty"` MirrorDirect *bool `json:"mirrorDirect,omitempty"` - ConsumerLimits *ConsumerLimitsApplyConfiguration `json:"consumerLimits,omitempty"` + DiscardPerSubject *bool `json:"discardPerSubject,omitempty"` + FirstSequence *uint64 `json:"firstSequence,omitempty"` Metadata map[string]string `json:"metadata,omitempty"` + ConsumerLimits *ConsumerLimitsApplyConfiguration `json:"consumerLimits,omitempty"` } // StreamSpecApplyConfiguration constructs a declarative configuration of the StreamSpec type for use with @@ -103,6 +103,14 @@ func (b *StreamSpecApplyConfiguration) WithMaxConsumers(value int) *StreamSpecAp return b } +// WithMaxMsgsPerSubject sets the MaxMsgsPerSubject field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the MaxMsgsPerSubject field is set to the value of the last call. +func (b *StreamSpecApplyConfiguration) WithMaxMsgsPerSubject(value int) *StreamSpecApplyConfiguration { + b.MaxMsgsPerSubject = &value + return b +} + // WithMaxMsgs sets the MaxMsgs field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. // If called multiple times, the MaxMsgs field is set to the value of the last call. @@ -119,22 +127,6 @@ func (b *StreamSpecApplyConfiguration) WithMaxBytes(value int) *StreamSpecApplyC return b } -// WithDiscard sets the Discard field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Discard field is set to the value of the last call. -func (b *StreamSpecApplyConfiguration) WithDiscard(value string) *StreamSpecApplyConfiguration { - b.Discard = &value - return b -} - -// WithDiscardPerSubject sets the DiscardPerSubject field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the DiscardPerSubject field is set to the value of the last call. -func (b *StreamSpecApplyConfiguration) WithDiscardPerSubject(value bool) *StreamSpecApplyConfiguration { - b.DiscardPerSubject = &value - return b -} - // WithMaxAge sets the MaxAge field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. // If called multiple times, the MaxAge field is set to the value of the last call. @@ -143,14 +135,6 @@ func (b *StreamSpecApplyConfiguration) WithMaxAge(value string) *StreamSpecApply return b } -// WithMaxMsgsPerSubject sets the MaxMsgsPerSubject field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the MaxMsgsPerSubject field is set to the value of the last call. -func (b *StreamSpecApplyConfiguration) WithMaxMsgsPerSubject(value int) *StreamSpecApplyConfiguration { - b.MaxMsgsPerSubject = &value - return b -} - // WithMaxMsgSize sets the MaxMsgSize field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. // If called multiple times, the MaxMsgSize field is set to the value of the last call. @@ -167,6 +151,14 @@ func (b *StreamSpecApplyConfiguration) WithStorage(value string) *StreamSpecAppl return b } +// WithDiscard sets the Discard field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Discard field is set to the value of the last call. +func (b *StreamSpecApplyConfiguration) WithDiscard(value string) *StreamSpecApplyConfiguration { + b.Discard = &value + return b +} + // WithReplicas sets the Replicas field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. // If called multiple times, the Replicas field is set to the value of the last call. @@ -220,38 +212,6 @@ func (b *StreamSpecApplyConfiguration) WithSources(values ...**jetstreamv1beta2. return b } -// WithSealed sets the Sealed field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Sealed field is set to the value of the last call. -func (b *StreamSpecApplyConfiguration) WithSealed(value bool) *StreamSpecApplyConfiguration { - b.Sealed = &value - return b -} - -// WithDenyDelete sets the DenyDelete field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the DenyDelete field is set to the value of the last call. -func (b *StreamSpecApplyConfiguration) WithDenyDelete(value bool) *StreamSpecApplyConfiguration { - b.DenyDelete = &value - return b -} - -// WithDenyPurge sets the DenyPurge field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the DenyPurge field is set to the value of the last call. -func (b *StreamSpecApplyConfiguration) WithDenyPurge(value bool) *StreamSpecApplyConfiguration { - b.DenyPurge = &value - return b -} - -// WithAllowRollup sets the AllowRollup field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the AllowRollup field is set to the value of the last call. -func (b *StreamSpecApplyConfiguration) WithAllowRollup(value bool) *StreamSpecApplyConfiguration { - b.AllowRollup = &value - return b -} - // WithCompression sets the Compression field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. // If called multiple times, the Compression field is set to the value of the last call. @@ -260,14 +220,6 @@ func (b *StreamSpecApplyConfiguration) WithCompression(value string) *StreamSpec return b } -// WithFirstSequence sets the FirstSequence field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the FirstSequence field is set to the value of the last call. -func (b *StreamSpecApplyConfiguration) WithFirstSequence(value uint64) *StreamSpecApplyConfiguration { - b.FirstSequence = &value - return b -} - // WithSubjectTransform sets the SubjectTransform field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. // If called multiple times, the SubjectTransform field is set to the value of the last call. @@ -284,6 +236,30 @@ func (b *StreamSpecApplyConfiguration) WithRePublish(value *RePublishApplyConfig return b } +// WithSealed sets the Sealed field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Sealed field is set to the value of the last call. +func (b *StreamSpecApplyConfiguration) WithSealed(value bool) *StreamSpecApplyConfiguration { + b.Sealed = &value + return b +} + +// WithDenyDelete sets the DenyDelete field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the DenyDelete field is set to the value of the last call. +func (b *StreamSpecApplyConfiguration) WithDenyDelete(value bool) *StreamSpecApplyConfiguration { + b.DenyDelete = &value + return b +} + +// WithDenyPurge sets the DenyPurge field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the DenyPurge field is set to the value of the last call. +func (b *StreamSpecApplyConfiguration) WithDenyPurge(value bool) *StreamSpecApplyConfiguration { + b.DenyPurge = &value + return b +} + // WithAllowDirect sets the AllowDirect field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. // If called multiple times, the AllowDirect field is set to the value of the last call. @@ -292,6 +268,14 @@ func (b *StreamSpecApplyConfiguration) WithAllowDirect(value bool) *StreamSpecAp return b } +// WithAllowRollup sets the AllowRollup field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the AllowRollup field is set to the value of the last call. +func (b *StreamSpecApplyConfiguration) WithAllowRollup(value bool) *StreamSpecApplyConfiguration { + b.AllowRollup = &value + return b +} + // WithMirrorDirect sets the MirrorDirect field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. // If called multiple times, the MirrorDirect field is set to the value of the last call. @@ -300,11 +284,19 @@ func (b *StreamSpecApplyConfiguration) WithMirrorDirect(value bool) *StreamSpecA return b } -// WithConsumerLimits sets the ConsumerLimits field in the declarative configuration to the given value +// WithDiscardPerSubject sets the DiscardPerSubject field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the ConsumerLimits field is set to the value of the last call. -func (b *StreamSpecApplyConfiguration) WithConsumerLimits(value *ConsumerLimitsApplyConfiguration) *StreamSpecApplyConfiguration { - b.ConsumerLimits = value +// If called multiple times, the DiscardPerSubject field is set to the value of the last call. +func (b *StreamSpecApplyConfiguration) WithDiscardPerSubject(value bool) *StreamSpecApplyConfiguration { + b.DiscardPerSubject = &value + return b +} + +// WithFirstSequence sets the FirstSequence field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the FirstSequence field is set to the value of the last call. +func (b *StreamSpecApplyConfiguration) WithFirstSequence(value uint64) *StreamSpecApplyConfiguration { + b.FirstSequence = &value return b } @@ -321,3 +313,11 @@ func (b *StreamSpecApplyConfiguration) WithMetadata(entries map[string]string) * } return b } + +// WithConsumerLimits sets the ConsumerLimits field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ConsumerLimits field is set to the value of the last call. +func (b *StreamSpecApplyConfiguration) WithConsumerLimits(value *ConsumerLimitsApplyConfiguration) *StreamSpecApplyConfiguration { + b.ConsumerLimits = value + return b +} From b0f8736c12b87afdc5d1faf9918e021388ed2637 Mon Sep 17 00:00:00 2001 From: Samuel Attwood Date: Tue, 21 Jan 2025 05:54:47 -0500 Subject: [PATCH 14/19] Remove debug log entry --- internal/controller/jetstream_controller.go | 9 --------- internal/controller/stream_controller.go | 7 +------ 2 files changed, 1 insertion(+), 15 deletions(-) diff --git a/internal/controller/jetstream_controller.go b/internal/controller/jetstream_controller.go index 9d8d9005..7954a83a 100644 --- a/internal/controller/jetstream_controller.go +++ b/internal/controller/jetstream_controller.go @@ -14,7 +14,6 @@ import ( "github.com/google/go-cmp/cmp" "github.com/nats-io/jsm.go" - jsmapi "github.com/nats-io/jsm.go/api" js "github.com/nats-io/nack/controllers/jetstream" api "github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2" "github.com/nats-io/nats.go/jetstream" @@ -315,14 +314,6 @@ func compareConfigState(actual any, desired any) string { return cmp.Diff(actual, desired) } -func getErrCode(err error) uint16 { - if apiErr, ok := err.(jsmapi.ApiError); ok { - return apiErr.NatsErrorCode() - } - - return 0 -} - func versionComponents(version string) (major, minor, patch int, err error) { m := semVerRe.FindStringSubmatch(version) if m == nil { diff --git a/internal/controller/stream_controller.go b/internal/controller/stream_controller.go index 42c14c74..b3fd664b 100644 --- a/internal/controller/stream_controller.go +++ b/internal/controller/stream_controller.go @@ -190,7 +190,6 @@ func (r *StreamReconciler) createOrUpdate(ctx context.Context, log logr.Logger, serverState, err := getServerStreamState(js, stream) if err != nil { - log.Info("get err", "err", err, "code", getErrCode(err)) return err } @@ -324,6 +323,7 @@ func streamSpecToConfig(spec *api.StreamSpec) ([]jsm.StreamOption, error) { jsm.MaxBytes(int64(spec.MaxBytes)), jsm.MaxMessageSize(int32(spec.MaxMsgSize)), jsm.Replicas(spec.Replicas), + jsm.StreamMetadata(spec.Metadata), } // Set not directly mapped fields @@ -489,11 +489,6 @@ func streamSpecToConfig(spec *api.StreamSpec) ([]jsm.StreamOption, error) { opts = append(opts, jsm.FirstSequence(spec.FirstSequence)) } - // metadata - if spec.Metadata != nil { - opts = append(opts, jsm.StreamMetadata(spec.Metadata)) - } - // consumerLimits if spec.ConsumerLimits != nil { cl := jsmapi.StreamConsumerLimits{ From 5db0cff6e8a96f5ca1b1e71021e19d611ec9a3e4 Mon Sep 17 00:00:00 2001 From: Samuel Attwood Date: Fri, 24 Jan 2025 01:45:30 -0500 Subject: [PATCH 15/19] deps --- go.mod | 8 ++++---- go.sum | 16 ++++++++-------- .../jetstream/v1beta2/tokensecret.go | 6 +++--- .../applyconfiguration/jetstream/v1beta2/user.go | 6 +++--- 4 files changed, 18 insertions(+), 18 deletions(-) diff --git a/go.mod b/go.mod index 8eb30738..96edf33f 100644 --- a/go.mod +++ b/go.mod @@ -21,7 +21,7 @@ require ( k8s.io/client-go v0.32.1 k8s.io/code-generator v0.32.1 k8s.io/klog/v2 v2.130.1 - sigs.k8s.io/controller-runtime v0.19.4 + sigs.k8s.io/controller-runtime v0.20.1 sigs.k8s.io/structured-merge-diff/v4 v4.5.0 ) @@ -41,10 +41,11 @@ require ( github.com/go-task/slim-sprig/v3 v3.0.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.4 // indirect + github.com/google/btree v1.1.3 // indirect github.com/google/gnostic-models v0.6.9 // indirect github.com/google/go-tpm v0.9.3 // indirect github.com/google/gofuzz v1.2.0 // indirect - github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad // indirect + github.com/google/pprof v0.0.0-20250121033306-997b0b79cac0 // indirect github.com/google/uuid v1.6.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect @@ -61,14 +62,13 @@ require ( github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_golang v1.20.5 // indirect github.com/prometheus/client_model v0.6.1 // indirect - github.com/prometheus/common v0.61.0 // indirect + github.com/prometheus/common v0.62.0 // indirect github.com/prometheus/procfs v0.15.1 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/x448/float16 v0.8.4 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect golang.org/x/crypto v0.32.0 // indirect - golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 // indirect golang.org/x/mod v0.22.0 // indirect golang.org/x/net v0.34.0 // indirect golang.org/x/oauth2 v0.25.0 // indirect diff --git a/go.sum b/go.sum index 40c02964..0f4a04cc 100644 --- a/go.sum +++ b/go.sum @@ -38,6 +38,8 @@ 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/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= +github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= 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.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= @@ -48,8 +50,8 @@ github.com/google/go-tpm v0.9.3/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u 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-20241210010833-40e02aabc2ad h1:a6HEuzUHeKH6hwfN/ZoQgRgVIWFJljSWa/zetS2WTvg= -github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= +github.com/google/pprof v0.0.0-20250121033306-997b0b79cac0 h1:EinjE47mmVVsxcjIwVKQWNY+3P+5R2BhkbULjhEDThc= +github.com/google/pprof v0.0.0-20250121033306-997b0b79cac0/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= 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/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= @@ -102,8 +104,8 @@ github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+ github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= 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.61.0 h1:3gv/GThfX0cV2lpO7gkTUwZru38mxevy90Bj8YFSRQQ= -github.com/prometheus/common v0.61.0/go.mod h1:zr29OCN/2BsJRaFwG8QOBr41D6kkchKbpeNH7pAjb/s= +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= @@ -132,8 +134,6 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U 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-20250106191152-7588d65b2ba8 h1:yqrTHse8TCMW1M1ZCP+VAR/l0kKxwaAIqN/il7x4voA= -golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU= 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.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= @@ -208,8 +208,8 @@ 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/controller-runtime v0.19.4 h1:SUmheabttt0nx8uJtoII4oIP27BVVvAKFvdvGFwV/Qo= -sigs.k8s.io/controller-runtime v0.19.4/go.mod h1:iRmWllt8IlaLjvTTDLhRBXIEtkCK6hwVBJJsYS9Ajf4= +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/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/structured-merge-diff/v4 v4.5.0 h1:nbCitCK2hfnhyiKo6uf2HxUPTCodY6Qaf85SbDIaMBk= diff --git a/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/tokensecret.go b/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/tokensecret.go index 4ca03c17..55fe3aee 100644 --- a/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/tokensecret.go +++ b/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/tokensecret.go @@ -1,4 +1,4 @@ -// Copyright 2020 The NATS Authors +// Copyright 2025 The NATS Authors // 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 @@ -15,14 +15,14 @@ package v1beta2 -// TokenSecretApplyConfiguration represents an declarative configuration of the TokenSecret type for use +// TokenSecretApplyConfiguration represents a declarative configuration of the TokenSecret type for use // with apply. type TokenSecretApplyConfiguration struct { Token *string `json:"token,omitempty"` Secret *SecretRefApplyConfiguration `json:"secret,omitempty"` } -// TokenSecretApplyConfiguration constructs an declarative configuration of the TokenSecret type for use with +// TokenSecretApplyConfiguration constructs a declarative configuration of the TokenSecret type for use with // apply. func TokenSecret() *TokenSecretApplyConfiguration { return &TokenSecretApplyConfiguration{} diff --git a/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/user.go b/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/user.go index 68483048..b334ace9 100644 --- a/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/user.go +++ b/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/user.go @@ -1,4 +1,4 @@ -// Copyright 2020 The NATS Authors +// Copyright 2025 The NATS Authors // 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 @@ -15,7 +15,7 @@ package v1beta2 -// UserApplyConfiguration represents an declarative configuration of the User type for use +// UserApplyConfiguration represents a declarative configuration of the User type for use // with apply. type UserApplyConfiguration struct { User *string `json:"user,omitempty"` @@ -23,7 +23,7 @@ type UserApplyConfiguration struct { Secret *SecretRefApplyConfiguration `json:"secret,omitempty"` } -// UserApplyConfiguration constructs an declarative configuration of the User type for use with +// UserApplyConfiguration constructs a declarative configuration of the User type for use with // apply. func User() *UserApplyConfiguration { return &UserApplyConfiguration{} From ad4cf12922bce66fe6102604d17a762a14cc1e3f Mon Sep 17 00:00:00 2001 From: Samuel Attwood Date: Fri, 31 Jan 2025 03:55:46 -0500 Subject: [PATCH 16/19] Improve connection config priority. Add missing option from consumer CRD. --- cmd/jetstream-controller/main.go | 6 +- controllers/jetstream/controller.go | 77 +++++----- deploy/crds.yml | 3 + internal/controller/client.go | 82 +++++++++-- internal/controller/consumer_controller.go | 2 +- internal/controller/jetstream_controller.go | 139 ++++++++++++------ internal/controller/keyvalue_controller.go | 2 +- internal/controller/objectstore_controller.go | 2 +- internal/controller/stream_controller.go | 2 +- 9 files changed, 214 insertions(+), 101 deletions(-) diff --git a/cmd/jetstream-controller/main.go b/cmd/jetstream-controller/main.go index 0d9d34d4..9890ddef 100644 --- a/cmd/jetstream-controller/main.go +++ b/cmd/jetstream-controller/main.go @@ -37,6 +37,7 @@ import ( ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/healthz" + "sigs.k8s.io/controller-runtime/pkg/log" ) var ( @@ -94,7 +95,6 @@ func run() error { klog.Warning("Starting jetStream controller in experimental control loop mode") natsCfg := &controller.NatsConfig{ - CRDConnect: *crdConnect, ClientName: "jetstream-controller", Credentials: *creds, NKey: *nkey, @@ -167,9 +167,11 @@ func runControlLoop(config *rest.Config, natsCfg *controller.NatsConfig, control utilruntime.Must(clientgoscheme.AddToScheme(scheme)) utilruntime.Must(v1beta2.AddToScheme(scheme)) + log.SetLogger(klog.NewKlogr()) + mgr, err := ctrl.NewManager(config, ctrl.Options{ Scheme: scheme, - Logger: klog.NewKlogr().WithName("controller-runtime"), + Logger: log.Log, }) if err != nil { return fmt.Errorf("unable to start manager: %w", err) diff --git a/controllers/jetstream/controller.go b/controllers/jetstream/controller.go index d9dba8b8..ea95c780 100644 --- a/controllers/jetstream/controller.go +++ b/controllers/jetstream/controller.go @@ -459,40 +459,40 @@ func (c *Controller) getAccountOverrides(account string, ns string) (*accountOve return nil, err } - filesToWrite := make(map[string]string) + var certData, keyData []byte + var certPath, keyPath string - getSecretValue := func(key string) string { - value, ok := secret.Data[key] - if !ok { - return "" + for k, v := range secret.Data { + switch k { + case acc.Spec.TLS.ClientCert: + certPath = filepath.Join(accDir, k) + certData = v + case acc.Spec.TLS.ClientKey: + keyPath = filepath.Join(accDir, k) + keyData = v + case acc.Spec.TLS.RootCAs: + overrides.remoteRootCA = filepath.Join(accDir, k) + if err := os.WriteFile(overrides.remoteRootCA, v, 0o644); err != nil { + return nil, err + } } - return string(value) - } - - remoteClientCertValue := getSecretValue(acc.Spec.TLS.ClientCert) - remoteClientKeyValue := getSecretValue(acc.Spec.TLS.ClientKey) - if remoteClientCertValue != "" && remoteClientKeyValue != "" { - overrides.remoteClientCert = filepath.Join(accDir, acc.Spec.TLS.ClientCert) - overrides.remoteClientKey = filepath.Join(accDir, acc.Spec.TLS.ClientKey) - - filesToWrite[acc.Spec.TLS.ClientCert] = remoteClientCertValue - filesToWrite[acc.Spec.TLS.ClientKey] = remoteClientKeyValue } - remoteRootCAValue := getSecretValue(acc.Spec.TLS.RootCAs) - if remoteRootCAValue != "" { - overrides.remoteRootCA = filepath.Join(accDir, acc.Spec.TLS.RootCAs) - filesToWrite[acc.Spec.TLS.RootCAs] = remoteRootCAValue - } + if certData != nil && keyData != nil { + overrides.remoteClientCert = certPath + overrides.remoteClientKey = keyPath - for file, v := range filesToWrite { - if err := os.WriteFile(filepath.Join(accDir, file), []byte(v), 0o644); err != nil { + if err := os.WriteFile(certPath, certData, 0o644); err != nil { + return nil, err + } + if err := os.WriteFile(keyPath, keyData, 0o644); err != nil { return nil, err } } } + // Lookup the UserCredentials. - if acc.Spec.Creds != nil { + if acc.Spec.Creds != nil && acc.Spec.Creds.Secret != nil { secretName := acc.Spec.Creds.Secret.Name secret, err := c.ki.Secrets(ns).Get(c.ctx, secretName, k8smeta.GetOptions{}) if err != nil { @@ -504,12 +504,11 @@ func (c *Controller) getAccountOverrides(account string, ns string) (*accountOve if err := os.MkdirAll(accDir, 0o755); err != nil { return nil, err } - for k, v := range secret.Data { - if k == acc.Spec.Creds.File { - overrides.userCreds = filepath.Join(c.cacheDir, ns, account, k) - if err := os.WriteFile(filepath.Join(accDir, k), v, 0o644); err != nil { - return nil, err - } + + if credsBytes, ok := secret.Data[acc.Spec.Creds.File]; ok { + overrides.userCreds = filepath.Join(accDir, acc.Spec.Creds.File) + if err := os.WriteFile(overrides.userCreds, credsBytes, 0o644); err != nil { + return nil, err } } } @@ -522,10 +521,8 @@ func (c *Controller) getAccountOverrides(account string, ns string) (*accountOve return nil, err } - for k, v := range secret.Data { - if k == acc.Spec.Token.Token { - overrides.token = string(v) - } + if token, ok := secret.Data[acc.Spec.Token.Token]; ok { + overrides.token = string(token) } } @@ -537,13 +534,11 @@ func (c *Controller) getAccountOverrides(account string, ns string) (*accountOve return nil, err } - for k, v := range secret.Data { - if k == acc.Spec.User.User { - overrides.user = string(v) - } - if k == acc.Spec.User.Password { - overrides.password = string(v) - } + userBytes := secret.Data[acc.Spec.User.User] + passwordBytes := secret.Data[acc.Spec.User.Password] + if userBytes != nil && passwordBytes != nil { + overrides.user = string(userBytes) + overrides.password = string(passwordBytes) } } diff --git a/deploy/crds.yml b/deploy/crds.yml index 055c1dcb..0327fb58 100644 --- a/deploy/crds.yml +++ b/deploy/crds.yml @@ -618,6 +618,9 @@ spec: maxRequestMaxBytes: description: The maximum max_bytes value that maybe set when dong a pull on a Pull Consumer. type: integer + inactiveThreshold: + description: The idle time an Ephemeral Consumer allows before it is removed. + type: string replicas: description: When set do not inherit the replica count from the stream but specifically set it to this amount. type: integer diff --git a/internal/controller/client.go b/internal/controller/client.go index 7667cec7..95a89bb9 100644 --- a/internal/controller/client.go +++ b/internal/controller/client.go @@ -9,31 +9,93 @@ import ( ) type NatsConfig struct { - CRDConnect bool ClientName string - Credentials string - NKey string ServerURL string - CAs []string Certificate string Key string TLSFirst bool + CAs []string + Credentials string + NKey string + Token string + User string + Password string +} + +func (o *NatsConfig) Overlay(overlay *NatsConfig) { + if overlay.ClientName != "" { + o.ClientName = overlay.ClientName + } + + if overlay.ServerURL != "" { + o.ServerURL = overlay.ServerURL + } + + if overlay.Certificate != "" && overlay.Key != "" { + o.Certificate = overlay.Certificate + o.Key = overlay.Key + } + + if len(overlay.CAs) > 0 { + o.CAs = overlay.CAs + } + + if overlay.TLSFirst { + o.TLSFirst = overlay.TLSFirst + } + + if !overlay.HasAuth() { + return + } + + o.UnsetAuth() + + if overlay.Credentials != "" { + o.Credentials = overlay.Credentials + } else if overlay.NKey != "" { + o.NKey = overlay.NKey + } else if overlay.Token != "" { + o.Token = overlay.Token + } else if overlay.User != "" && overlay.Password != "" { + o.User = overlay.User + o.Password = overlay.Password + } +} + +func (o *NatsConfig) HasAuth() bool { + return o.Credentials != "" || o.NKey != "" || o.Token != "" || (o.User != "" && o.Password != "") +} + +func (o *NatsConfig) UnsetAuth() { + o.Credentials = "" + o.NKey = "" + o.User = "" + o.Password = "" + o.Token = "" } // buildOptions creates options from the config to be used in nats.Connect. func (o *NatsConfig) buildOptions() ([]nats.Option, error) { opts := make([]nats.Option, 0) + if o.ClientName != "" { + opts = append(opts, nats.Name(o.ClientName)) + } + if o.ServerURL == "" { return nil, fmt.Errorf("server url is required") } + if o.Certificate != "" && o.Key != "" { + opts = append(opts, nats.ClientCert(o.Certificate, o.Key)) + } + if o.TLSFirst { opts = append(opts, nats.TLSHandshakeFirst()) } - if o.ClientName != "" { - opts = append(opts, nats.Name(o.ClientName)) + if len(o.CAs) > 0 { + opts = append(opts, nats.RootCAs(o.CAs...)) } if o.Credentials != "" { @@ -48,12 +110,12 @@ func (o *NatsConfig) buildOptions() ([]nats.Option, error) { opts = append(opts, opt) } - if o.Certificate != "" && o.Key != "" { - opts = append(opts, nats.ClientCert(o.Certificate, o.Key)) + if o.Token != "" { + opts = append(opts, nats.Token(o.Token)) } - if len(o.CAs) > 0 { - opts = append(opts, nats.RootCAs(o.CAs...)) + if o.User != "" && o.Password != "" { + opts = append(opts, nats.UserInfo(o.User, o.Password)) } return opts, nil diff --git a/internal/controller/consumer_controller.go b/internal/controller/consumer_controller.go index 13fe0864..cbb40848 100644 --- a/internal/controller/consumer_controller.go +++ b/internal/controller/consumer_controller.go @@ -66,7 +66,7 @@ func (r *ConsumerReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c consumer := &api.Consumer{} if err := r.Get(ctx, req.NamespacedName, consumer); err != nil { if apierrors.IsNotFound(err) { - log.Info("Consumer deleted.", "consumerName", req.NamespacedName.String()) + log.Info("Consumer resource deleted.", "consumerName", req.NamespacedName.String()) return ctrl.Result{}, nil } return ctrl.Result{}, fmt.Errorf("get consumer resource '%s': %w", req.NamespacedName.String(), err) diff --git a/internal/controller/jetstream_controller.go b/internal/controller/jetstream_controller.go index 7954a83a..a7da7362 100644 --- a/internal/controller/jetstream_controller.go +++ b/internal/controller/jetstream_controller.go @@ -127,27 +127,19 @@ func (c *jsController) natsConfigFromOpts(opts api.ConnectionOpts, ns string) (* ctx, done := context.WithTimeout(context.Background(), 5*time.Second) defer done() - natsConfig := &NatsConfig{ - ClientName: c.config.ClientName, - Credentials: c.config.Credentials, - NKey: c.config.NKey, - ServerURL: c.config.ServerURL, - CAs: c.config.CAs, - TLSFirst: c.config.TLSFirst, - } - - if c.config.Certificate != "" && c.config.Key != "" { - natsConfig.Certificate = c.config.Certificate - natsConfig.Key = c.config.Key - } + natsConfig := &NatsConfig{} + natsConfig.Overlay(c.config) if opts.Account == "" { - return applyConnOpts(*natsConfig, opts), nil + natsConfig.Overlay(natsConfigFromOpts(opts)) + return natsConfig, nil } // Apply Account options first, over global. // Apply remaining CRD options last + accountOverlay := &NatsConfig{} + account := &api.Account{} err := c.Get(ctx, types.NamespacedName{ @@ -161,7 +153,7 @@ func (c *jsController) natsConfigFromOpts(opts api.ConnectionOpts, ns string) (* } if len(account.Spec.Servers) > 0 { - natsConfig.ServerURL = strings.Join(account.Spec.Servers, ",") + accountOverlay.ServerURL = strings.Join(account.Spec.Servers, ",") } if account.Spec.TLS != nil && account.Spec.TLS.Secret != nil { @@ -182,29 +174,43 @@ func (c *jsController) natsConfigFromOpts(opts api.ConnectionOpts, ns string) (* return nil, err } + var certData, keyData []byte + var certPath, keyPath string + for k, v := range tlsSecret.Data { - filePath := "" switch k { case account.Spec.TLS.ClientCert: - filePath = filepath.Join(accDir, account.Spec.TLS.ClientCert) - natsConfig.Certificate = filePath + certPath = filepath.Join(accDir, k) + certData = v case account.Spec.TLS.ClientKey: - filePath = filepath.Join(accDir, account.Spec.TLS.ClientKey) - natsConfig.Key = filePath + keyPath = filepath.Join(accDir, k) + keyData = v case account.Spec.TLS.RootCAs: - filePath = filepath.Join(accDir, account.Spec.TLS.RootCAs) - natsConfig.CAs = append(natsConfig.CAs, filePath) - default: - return nil, fmt.Errorf("key in TLS secret does not match any of the expected values") + rootCAPath := filepath.Join(accDir, k) + accountOverlay.CAs = append(accountOverlay.CAs, rootCAPath) + if err := os.WriteFile(rootCAPath, v, 0o644); err != nil { + return nil, err + } } - if err := os.WriteFile(filePath, v, 0o600); err != nil { + } + + if certData != nil && keyData != nil { + accountOverlay.Certificate = certPath + accountOverlay.Key = keyPath + + if err := os.WriteFile(certPath, certData, 0o644); err != nil { + return nil, err + } + if err := os.WriteFile(keyPath, keyData, 0o644); err != nil { return nil, err } } } else if account.Spec.TLS != nil { - natsConfig.Certificate = account.Spec.TLS.ClientCert - natsConfig.Key = account.Spec.TLS.ClientKey - natsConfig.CAs = []string{account.Spec.TLS.RootCAs} + if account.Spec.TLS.ClientCert != "" && account.Spec.TLS.ClientKey != "" { + accountOverlay.Certificate = account.Spec.TLS.ClientCert + accountOverlay.Key = account.Spec.TLS.ClientKey + } + accountOverlay.CAs = []string{account.Spec.TLS.RootCAs} } if account.Spec.Creds != nil && account.Spec.Creds.Secret != nil { @@ -225,28 +231,69 @@ func (c *jsController) natsConfigFromOpts(opts api.ConnectionOpts, ns string) (* return nil, err } - for k, v := range credsSecret.Data { - filePath := "" - switch k { - case account.Spec.Creds.File: - filePath = filepath.Join(accDir, account.Spec.Creds.File) - natsConfig.Credentials = filePath - default: - return nil, fmt.Errorf("key in Creds secret does not match any of the expected values") - } - if err := os.WriteFile(filePath, v, 0o600); err != nil { + if credsBytes, ok := credsSecret.Data[account.Spec.Creds.File]; ok { + filePath := filepath.Join(accDir, account.Spec.Creds.File) + accountOverlay.Credentials = filePath + if err := os.WriteFile(filePath, credsBytes, 0o600); err != nil { return nil, err } } - } else if account.Spec.Creds.File != "" { - natsConfig.Credentials = account.Spec.Creds.File + } else if account.Spec.Creds != nil { + accountOverlay.Credentials = account.Spec.Creds.File } - return applyConnOpts(*natsConfig, opts), nil + if account.Spec.User != nil { + userSecret := &v1.Secret{} + err := c.Get(ctx, + types.NamespacedName{ + Name: account.Spec.User.Secret.Name, + Namespace: ns, + }, + userSecret, + ) + if err != nil { + return nil, err + } + + userName := userSecret.Data[account.Spec.User.User] + userPassword := userSecret.Data[account.Spec.User.Password] + + if userName != nil && userPassword != nil { + accountOverlay.User = string(userName) + accountOverlay.Password = string(userPassword) + } + } + + if account.Spec.Token != nil { + tokenSecret := &v1.Secret{} + err := c.Get(ctx, + types.NamespacedName{ + Name: account.Spec.Token.Secret.Name, + Namespace: ns, + }, + tokenSecret, + ) + if err != nil { + return nil, err + } + + if token := tokenSecret.Data[account.Spec.Token.Token]; token != nil { + accountOverlay.Token = string(token) + } + } + + // Overlay Account Config + natsConfig.Overlay(accountOverlay) + + // Overlay Spec Config + natsConfig.Overlay(natsConfigFromOpts(opts)) + + return natsConfig, nil } -func applyConnOpts(baseConfig NatsConfig, opts api.ConnectionOpts) *NatsConfig { - natsConfig := baseConfig +func natsConfigFromOpts(opts api.ConnectionOpts) *NatsConfig { + natsConfig := &NatsConfig{} + if len(opts.Servers) > 0 { natsConfig.ServerURL = strings.Join(opts.Servers, ",") } @@ -261,6 +308,10 @@ func applyConnOpts(baseConfig NatsConfig, opts api.ConnectionOpts) *NatsConfig { natsConfig.Credentials = opts.Creds } + if opts.Nkey != "" { + natsConfig.NKey = opts.Nkey + } + if len(opts.TLS.RootCAs) > 0 { natsConfig.CAs = opts.TLS.RootCAs } @@ -270,7 +321,7 @@ func applyConnOpts(baseConfig NatsConfig, opts api.ConnectionOpts) *NatsConfig { natsConfig.Key = opts.TLS.ClientKey } - return &natsConfig + return natsConfig } // updateReadyCondition returns the given conditions with an added or updated ready condition. diff --git a/internal/controller/keyvalue_controller.go b/internal/controller/keyvalue_controller.go index 11a4261f..8ccf94f2 100644 --- a/internal/controller/keyvalue_controller.go +++ b/internal/controller/keyvalue_controller.go @@ -69,7 +69,7 @@ func (r *KeyValueReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c keyValue := &api.KeyValue{} if err := r.Get(ctx, req.NamespacedName, keyValue); err != nil { if apierrors.IsNotFound(err) { - log.Info("KeyValue deleted.", "keyValueName", req.NamespacedName.String()) + log.Info("KeyValue resource deleted.", "keyValueName", req.NamespacedName.String()) return ctrl.Result{}, nil } return ctrl.Result{}, fmt.Errorf("get keyvalue resource '%s': %w", req.NamespacedName.String(), err) diff --git a/internal/controller/objectstore_controller.go b/internal/controller/objectstore_controller.go index 4f88fb69..ca5e7e33 100644 --- a/internal/controller/objectstore_controller.go +++ b/internal/controller/objectstore_controller.go @@ -70,7 +70,7 @@ func (r *ObjectStoreReconciler) Reconcile(ctx context.Context, req ctrl.Request) objectStore := &api.ObjectStore{} if err := r.Get(ctx, req.NamespacedName, objectStore); err != nil { if apierrors.IsNotFound(err) { - log.Info("ObjectStore deleted.", "objectStoreName", req.NamespacedName.String()) + log.Info("ObjectStore resource deleted.", "objectStoreName", req.NamespacedName.String()) return ctrl.Result{}, nil } return ctrl.Result{}, fmt.Errorf("get objectstore resource '%s': %w", req.NamespacedName.String(), err) diff --git a/internal/controller/stream_controller.go b/internal/controller/stream_controller.go index b3fd664b..348ddfed 100644 --- a/internal/controller/stream_controller.go +++ b/internal/controller/stream_controller.go @@ -68,7 +68,7 @@ func (r *StreamReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctr stream := &api.Stream{} if err := r.Get(ctx, req.NamespacedName, stream); err != nil { if apierrors.IsNotFound(err) { - log.Info("Stream deleted.", "streamName", req.NamespacedName.String()) + log.Info("Stream resource deleted.", "streamName", req.NamespacedName.String()) return ctrl.Result{}, nil } return ctrl.Result{}, fmt.Errorf("get stream resource '%s': %w", req.NamespacedName.String(), err) From 18dec439bd005b08de0949b845c6b4ff4da0dc0f Mon Sep 17 00:00:00 2001 From: Samuel Attwood Date: Fri, 31 Jan 2025 05:23:53 -0500 Subject: [PATCH 17/19] Deps. Fix placement config enforcement --- cicd/Dockerfile | 2 +- cicd/Dockerfile_goreleaser | 2 +- go.mod | 14 ++++++------- go.sum | 20 +++++++++---------- internal/controller/jetstream_controller.go | 2 +- internal/controller/stream_controller.go | 4 ++++ internal/controller/stream_controller_test.go | 8 +++++--- 7 files changed, 28 insertions(+), 24 deletions(-) diff --git a/cicd/Dockerfile b/cicd/Dockerfile index f141b1fe..ad614b27 100644 --- a/cicd/Dockerfile +++ b/cicd/Dockerfile @@ -1,4 +1,4 @@ -#syntax=docker/dockerfile-upstream:1.5 +#syntax=docker/dockerfile-upstream:1.13 ARG GO_APP FROM alpine:3.21.2 as deps diff --git a/cicd/Dockerfile_goreleaser b/cicd/Dockerfile_goreleaser index 74df055a..24f9dbde 100644 --- a/cicd/Dockerfile_goreleaser +++ b/cicd/Dockerfile_goreleaser @@ -1,4 +1,4 @@ -#syntax=docker/dockerfile-upstream:1.5 +#syntax=docker/dockerfile-upstream:1.13 FROM --platform=$BUILDPLATFORM golang:1.23.5-bullseye as build RUN < Date: Fri, 31 Jan 2025 05:59:12 -0500 Subject: [PATCH 18/19] Bump jsm.go. Fix typo --- cmd/jetstream-controller/main.go | 2 +- deploy/crds.yml | 8 ++++---- go.mod | 4 ++-- go.sum | 4 ++++ 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/cmd/jetstream-controller/main.go b/cmd/jetstream-controller/main.go index 9890ddef..9e13959c 100644 --- a/cmd/jetstream-controller/main.go +++ b/cmd/jetstream-controller/main.go @@ -92,7 +92,7 @@ func run() error { } if *controlLoop { - klog.Warning("Starting jetStream controller in experimental control loop mode") + klog.Warning("Starting JetStream controller in experimental control loop mode") natsCfg := &controller.NatsConfig{ ClientName: "jetstream-controller", diff --git a/deploy/crds.yml b/deploy/crds.yml index 0327fb58..94b7f2d6 100644 --- a/deploy/crds.yml +++ b/deploy/crds.yml @@ -251,7 +251,7 @@ spec: type: string pattern: '^[^.*>]*$' creds: - description: NATS user credentials for connecting to servers. Please make sure your controller has mounted the cerds on its path. + description: NATS user credentials for connecting to servers. Please make sure your controller has mounted the creds on its path. type: string default: '' nkey: @@ -638,7 +638,7 @@ spec: type: string pattern: '^[^.*>]*$' creds: - description: NATS user credentials for connecting to servers. Please make sure your controller has mounted the cerds on its path. + description: NATS user credentials for connecting to servers. Please make sure your controller has mounted the creds on its path. type: string default: '' nkey: @@ -1240,7 +1240,7 @@ spec: type: string pattern: '^[^.*>]*$' creds: - description: NATS user credentials for connecting to servers. Please make sure your controller has mounted the cerds on its path. + description: NATS user credentials for connecting to servers. Please make sure your controller has mounted the creds on its path. type: string default: '' nkey: @@ -1380,7 +1380,7 @@ spec: type: string pattern: '^[^.*>]*$' creds: - description: NATS user credentials for connecting to servers. Please make sure your controller has mounted the cerds on its path. + description: NATS user credentials for connecting to servers. Please make sure your controller has mounted the creds on its path. type: string default: '' nkey: diff --git a/go.mod b/go.mod index 05a4cfc4..27d85a1b 100644 --- a/go.mod +++ b/go.mod @@ -6,8 +6,8 @@ require ( github.com/fsnotify/fsnotify v1.8.0 github.com/go-logr/logr v1.4.2 github.com/google/go-cmp v0.6.0 - github.com/nats-io/jsm.go v0.1.1-0.20250120135113-0db99fd62ad9 - github.com/nats-io/nats-server/v2 v2.11.0-dev.0.20250109022000-c0ae95d0be26 + github.com/nats-io/jsm.go v0.1.1-0.20250127160126-62a6bf0def83 + github.com/nats-io/nats-server/v2 v2.11.0-dev.0.20250126014539-f2eb5650d200 github.com/nats-io/nats.go v1.38.0 github.com/onsi/ginkgo/v2 v2.22.2 github.com/onsi/gomega v1.36.2 diff --git a/go.sum b/go.sum index 817a0e35..a2ac98a5 100644 --- a/go.sum +++ b/go.sum @@ -81,10 +81,14 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/nats-io/jsm.go v0.1.1-0.20250120135113-0db99fd62ad9 h1:2DfL+nZjlB8kQh5GN5IEBdVroHbRaRod3BAi07Ag89Q= github.com/nats-io/jsm.go v0.1.1-0.20250120135113-0db99fd62ad9/go.mod h1:w/SA3/rNK5xl6ZsCqKLVgQzVLfxbYNScle8LcQ5gSBM= +github.com/nats-io/jsm.go v0.1.1-0.20250127160126-62a6bf0def83 h1:0NXdeCr1J2bytMkdGgCLW//ymhAM716HFnnSU8OxnVc= +github.com/nats-io/jsm.go v0.1.1-0.20250127160126-62a6bf0def83/go.mod h1:+wiVkC1oBBVXnJaWdbsLwXN/vH5rZgjkkfsu5a76q3s= github.com/nats-io/jwt/v2 v2.7.3 h1:6bNPK+FXgBeAqdj4cYQ0F8ViHRbi7woQLq4W29nUAzE= github.com/nats-io/jwt/v2 v2.7.3/go.mod h1:GvkcbHhKquj3pkioy5put1wvPxs78UlZ7D/pY+BgZk4= github.com/nats-io/nats-server/v2 v2.11.0-dev.0.20250109022000-c0ae95d0be26 h1:mN0eraizaHih90T32ItWe58+l31eVjoD6JOu99WqdNc= github.com/nats-io/nats-server/v2 v2.11.0-dev.0.20250109022000-c0ae95d0be26/go.mod h1:nXRZ6eQo2lmNpZLVNIMDNwKM7FgbHgPJ1pIRcPOpVuk= +github.com/nats-io/nats-server/v2 v2.11.0-dev.0.20250126014539-f2eb5650d200 h1:JTZQwrehqUHpEO+DRjm0734B4a0porO1Cb5ACT0nSJY= +github.com/nats-io/nats-server/v2 v2.11.0-dev.0.20250126014539-f2eb5650d200/go.mod h1:NLseHFkD5ZPPkHVYn4JEG8LguxveaOXJPiIfswZugHg= github.com/nats-io/nats.go v1.38.0 h1:A7P+g7Wjp4/NWqDOOP/K6hfhr54DvdDQUznt5JFg9XA= github.com/nats-io/nats.go v1.38.0/go.mod h1:IGUM++TwokGnXPs82/wCuiHS02/aKrdYUQkU8If6yjw= github.com/nats-io/nkeys v0.4.9 h1:qe9Faq2Gxwi6RZnZMXfmGMZkg3afLLOtrU+gDZJ35b0= From 577ce79820c55aa03ea9097a2b37f800d93060a4 Mon Sep 17 00:00:00 2001 From: Samuel Attwood Date: Fri, 31 Jan 2025 06:31:57 -0500 Subject: [PATCH 19/19] Log diff on resource update --- go.sum | 4 ---- internal/controller/consumer_controller.go | 3 +++ internal/controller/keyvalue_controller.go | 8 ++++++++ internal/controller/objectstore_controller.go | 8 ++++++++ internal/controller/stream_controller.go | 3 +++ 5 files changed, 22 insertions(+), 4 deletions(-) diff --git a/go.sum b/go.sum index a2ac98a5..5468bd90 100644 --- a/go.sum +++ b/go.sum @@ -79,14 +79,10 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 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/nats-io/jsm.go v0.1.1-0.20250120135113-0db99fd62ad9 h1:2DfL+nZjlB8kQh5GN5IEBdVroHbRaRod3BAi07Ag89Q= -github.com/nats-io/jsm.go v0.1.1-0.20250120135113-0db99fd62ad9/go.mod h1:w/SA3/rNK5xl6ZsCqKLVgQzVLfxbYNScle8LcQ5gSBM= github.com/nats-io/jsm.go v0.1.1-0.20250127160126-62a6bf0def83 h1:0NXdeCr1J2bytMkdGgCLW//ymhAM716HFnnSU8OxnVc= github.com/nats-io/jsm.go v0.1.1-0.20250127160126-62a6bf0def83/go.mod h1:+wiVkC1oBBVXnJaWdbsLwXN/vH5rZgjkkfsu5a76q3s= github.com/nats-io/jwt/v2 v2.7.3 h1:6bNPK+FXgBeAqdj4cYQ0F8ViHRbi7woQLq4W29nUAzE= github.com/nats-io/jwt/v2 v2.7.3/go.mod h1:GvkcbHhKquj3pkioy5put1wvPxs78UlZ7D/pY+BgZk4= -github.com/nats-io/nats-server/v2 v2.11.0-dev.0.20250109022000-c0ae95d0be26 h1:mN0eraizaHih90T32ItWe58+l31eVjoD6JOu99WqdNc= -github.com/nats-io/nats-server/v2 v2.11.0-dev.0.20250109022000-c0ae95d0be26/go.mod h1:nXRZ6eQo2lmNpZLVNIMDNwKM7FgbHgPJ1pIRcPOpVuk= github.com/nats-io/nats-server/v2 v2.11.0-dev.0.20250126014539-f2eb5650d200 h1:JTZQwrehqUHpEO+DRjm0734B4a0porO1Cb5ACT0nSJY= github.com/nats-io/nats-server/v2 v2.11.0-dev.0.20250126014539-f2eb5650d200/go.mod h1:NLseHFkD5ZPPkHVYn4JEG8LguxveaOXJPiIfswZugHg= github.com/nats-io/nats.go v1.38.0 h1:A7P+g7Wjp4/NWqDOOP/K6hfhr54DvdDQUznt5JFg9XA= diff --git a/internal/controller/consumer_controller.go b/internal/controller/consumer_controller.go index cbb40848..221e47d0 100644 --- a/internal/controller/consumer_controller.go +++ b/internal/controller/consumer_controller.go @@ -261,6 +261,9 @@ func (r *ConsumerReconciler) createOrUpdate(ctx context.Context, log klog.Logger if err != nil { return err } + + diff := compareConfigState(updatedConsumer.Configuration(), *serverState) + log.Info("Updated Consumer.", "diff", diff) } else { log.Info("Skipping Consumer update.", "preventUpdate", consumer.Spec.PreventUpdate, diff --git a/internal/controller/keyvalue_controller.go b/internal/controller/keyvalue_controller.go index 8ccf94f2..bbef95b9 100644 --- a/internal/controller/keyvalue_controller.go +++ b/internal/controller/keyvalue_controller.go @@ -229,6 +229,14 @@ func (r *KeyValueReconciler) createOrUpdate(ctx context.Context, log logr.Logger if err != nil { return err } + + updatedKeyValue, err := getServerKeyValueState(ctx, js, keyValue) + if err != nil { + log.Error(err, "Failed to fetch updated KeyValue state") + } else { + diff := compareConfigState(updatedKeyValue, serverState) + log.Info("Updated KeyValue.", "diff", diff) + } } else { log.Info("Skipping KeyValue update.", "preventUpdate", keyValue.Spec.PreventUpdate, diff --git a/internal/controller/objectstore_controller.go b/internal/controller/objectstore_controller.go index ca5e7e33..9684c680 100644 --- a/internal/controller/objectstore_controller.go +++ b/internal/controller/objectstore_controller.go @@ -230,6 +230,14 @@ func (r *ObjectStoreReconciler) createOrUpdate(ctx context.Context, log logr.Log if err != nil { return err } + + updatedObjectStore, err := getServerObjectStoreState(ctx, js, objectStore) + if err != nil { + log.Error(err, "Failed to fetch updated objectstore state") + } else { + diff := compareConfigState(updatedObjectStore, serverState) + log.Info("Updated ObjectStore.", "diff", diff) + } } else { log.Info("Skipping ObjectStore update.", "preventUpdate", objectStore.Spec.PreventUpdate, diff --git a/internal/controller/stream_controller.go b/internal/controller/stream_controller.go index 49e97fa3..1a4b73d4 100644 --- a/internal/controller/stream_controller.go +++ b/internal/controller/stream_controller.go @@ -238,6 +238,9 @@ func (r *StreamReconciler) createOrUpdate(ctx context.Context, log logr.Logger, if err != nil { return err } + + diff := compareConfigState(updatedStream.Configuration(), *serverState) + log.Info("Updated Stream.", "diff", diff) } else { log.Info("Skipping Stream update.", "preventUpdate", stream.Spec.PreventUpdate,