From 5253b94a1b9f32defca79e28b8c30abb9192cb41 Mon Sep 17 00:00:00 2001 From: Rui Fu Date: Fri, 10 Apr 2026 22:27:22 +0800 Subject: [PATCH 1/8] feat: upgrade pulsar-client-go v0.12.0 --- go.mod | 33 +++--- go.sum | 110 ++++-------------- pkg/cmdutils/rest.go | 68 +++++++++++ pkg/ctl/brokers/healthcheck.go | 3 +- pkg/ctl/namespace/set_replication_clusters.go | 4 +- pkg/ctl/subscription/get_message_by_id.go | 5 +- .../topic/get_subscription_dispatch_rate.go | 29 ++++- .../remove_subscription_dispatch_rate.go | 10 +- .../topic/set_subscription_dispatch_rate.go | 11 +- pkg/ctl/topic/stats.go | 46 +++++--- pkg/ctl/topic/stats_test.go | 23 ---- 11 files changed, 184 insertions(+), 158 deletions(-) create mode 100644 pkg/cmdutils/rest.go diff --git a/go.mod b/go.mod index f8b9cda6d..c07453dd2 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/streamnative/pulsarctl go 1.25.8 require ( - github.com/apache/pulsar-client-go v0.18.0-candidate-1.0.20251222030102-3bb7d4eff361 + github.com/apache/pulsar-client-go v0.12.0 github.com/docker/go-connections v0.5.0 github.com/fatih/color v1.7.0 github.com/ghodss/yaml v1.0.0 @@ -24,32 +24,35 @@ require ( require ( dario.cat/mergo v1.0.0 // indirect - github.com/AthenZ/athenz v1.12.31 // indirect + github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect + github.com/99designs/keyring v1.2.1 // indirect + github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 // indirect github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect - github.com/DataDog/zstd v1.5.7 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect - github.com/RoaringBitmap/roaring/v2 v2.14.4 // indirect - github.com/bits-and-blooms/bitset v1.24.4 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/containerd/log v0.1.0 // indirect github.com/containerd/platforms v0.2.1 // indirect github.com/cpuguy83/dockercfg v0.3.2 // indirect + github.com/danieljoos/wincred v1.1.2 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/distribution/reference v0.6.0 // indirect github.com/docker/docker v28.0.0+incompatible // indirect github.com/docker/go-units v0.5.0 // indirect + github.com/dvsekhvalnov/jose2go v1.6.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/fsnotify/fsnotify v1.9.0 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-ole/go-ole v1.2.6 // indirect + github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang-jwt/jwt/v5 v5.3.0 // indirect + github.com/golang-jwt/jwt v3.2.1+incompatible // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/google/go-cmp v0.7.0 // indirect github.com/google/uuid v1.6.0 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 // indirect - github.com/hamba/avro/v2 v2.30.0 // indirect + github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/klauspost/compress v1.18.2 // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect @@ -62,17 +65,13 @@ require ( github.com/moby/sys/user v0.3.0 // indirect github.com/moby/sys/userns v0.1.0 // indirect github.com/moby/term v0.5.0 // indirect - github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect github.com/morikuni/aec v1.0.0 // indirect + github.com/mtibben/percent v0.2.1 // indirect github.com/onsi/ginkgo/v2 v2.27.2 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0 // indirect - github.com/pierrec/lz4/v4 v4.1.23 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect - github.com/prometheus/client_golang v1.23.2 // indirect - github.com/prometheus/common v0.67.4 // indirect - github.com/prometheus/procfs v0.19.2 // indirect github.com/shirou/gopsutil/v3 v3.23.12 // indirect github.com/shoenig/go-m1cpu v0.1.6 // indirect github.com/sirupsen/logrus v1.9.3 // indirect @@ -82,8 +81,6 @@ require ( go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0 // indirect go.opentelemetry.io/otel v1.39.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.39.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.39.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0 // indirect go.opentelemetry.io/otel/metric v1.39.0 // indirect go.opentelemetry.io/otel/trace v1.39.0 // indirect @@ -94,14 +91,12 @@ require ( golang.org/x/net v0.48.0 // indirect golang.org/x/oauth2 v0.34.0 // indirect golang.org/x/sys v0.39.0 // indirect + golang.org/x/term v0.38.0 // indirect golang.org/x/text v0.32.0 // indirect + golang.org/x/time v0.12.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20251222181119-0a764e51fe1b // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b // indirect + google.golang.org/grpc v1.77.0 // indirect google.golang.org/protobuf v1.36.11 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/apimachinery v0.35.0 // indirect - k8s.io/client-go v0.35.0 // indirect - k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e // indirect - k8s.io/utils v0.0.0-20251222233032-718f0e51e6d2 // indirect - sigs.k8s.io/structured-merge-diff/v6 v6.3.1 // indirect ) diff --git a/go.sum b/go.sum index 91c0b6e48..c4ef78bbb 100644 --- a/go.sum +++ b/go.sum @@ -1,32 +1,21 @@ dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= -github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU= -github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= -github.com/AthenZ/athenz v1.12.31 h1:GQnRDLgivPlVvklSpH9gp+t/dho9DJTtt+hlLYo5TX8= -github.com/AthenZ/athenz v1.12.31/go.mod h1:6Siq4JOA4OjgYVgtTVIeHrb4HB2hEL8i4fx7aOFrgfY= +github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 h1:/vQbFIOMbk2FiG/kXiLl8BRyzTWDw7gX/Hz7Dd5eDMs= +github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4/go.mod h1:hN7oaIRCjzsZ2dE+yG5k+rsdt3qcwykqK6HVGcKwsw4= +github.com/99designs/keyring v1.2.1 h1:tYLp1ULvO7i3fI5vE21ReQuj99QFSs7lGm0xWyJo87o= +github.com/99designs/keyring v1.2.1/go.mod h1:fc+wB5KTk9wQ9sDx0kFXB3A0MaeGHM9AwRStKOQ5vOA= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= -github.com/DataDog/zstd v1.5.7 h1:ybO8RBeh29qrxIhCA9E8gKY6xfONU9T6G6aP9DTKfLE= -github.com/DataDog/zstd v1.5.7/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0= github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= -github.com/RoaringBitmap/roaring/v2 v2.14.4 h1:4aKySrrg9G/5oRtJ3TrZLObVqxgQ9f1znCRBwEwjuVw= -github.com/RoaringBitmap/roaring/v2 v2.14.4/go.mod h1:oMvV6omPWr+2ifRdeZvVJyaz+aoEUopyv5iH0u/+wbY= -github.com/apache/pulsar-client-go v0.18.0-candidate-1.0.20251222030102-3bb7d4eff361 h1:Fb4j4v85TPq64FRp+QMLWaW3/Hg1Jg7TBWaZwPcSO9Y= -github.com/apache/pulsar-client-go v0.18.0-candidate-1.0.20251222030102-3bb7d4eff361/go.mod h1:/Zf8Q8bSSc6ndEJ8V1muIHf6ZWsMrHoQU+98Ww9pOeI= -github.com/ardielle/ardielle-go v1.5.2 h1:TilHTpHIQJ27R1Tl/iITBzMwiUGSlVfiVhwDNGM3Zj4= -github.com/ardielle/ardielle-go v1.5.2/go.mod h1:I4hy1n795cUhaVt/ojz83SNVCYIGsAFAONtv2Dr7HUI= -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/bits-and-blooms/bitset v1.24.4 h1:95H15Og1clikBrKr/DuzMXkQzECs1M6hhoGXLwLQOZE= -github.com/bits-and-blooms/bitset v1.24.4/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= -github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= +github.com/apache/pulsar-client-go v0.12.0 h1:rrMlwpr6IgLRPXLRRh2vSlcw5tGV2PUSjZwmqgh2B2I= +github.com/apache/pulsar-client-go v0.12.0/go.mod h1:dkutuH4oS2pXiGm+Ti7fQZ4MRjrMPZ8IJeEGAWMeckk= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= -github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= -github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= 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/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= @@ -38,6 +27,8 @@ github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHf github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= +github.com/danieljoos/wincred v1.1.2 h1:QLdCxFs1/Yl4zduvBdcHB8goaYk9RARS2SgLLRuAyr0= +github.com/danieljoos/wincred v1.1.2/go.mod h1:GijpziifJoIBfYh+S7BbkdUTU4LfM+QnGqR5Vl2tAx0= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= @@ -50,20 +41,16 @@ github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= -github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= +github.com/dvsekhvalnov/jose2go v1.6.0 h1:Y9gnSnP4qEI0+/uQkHvFXeD2PLPJeXEL+ySMEA2EjTY= +github.com/dvsekhvalnov/jose2go v1.6.0/go.mod h1:QsHjhyTlD/lAVqn/NSbVZmSCGeDehTB/mPZadG+mhXU= github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= -github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= -github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs= -github.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= @@ -73,14 +60,14 @@ github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= 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/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs= -github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= +github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 h1:ZpnhV/YsD2/4cESfV5+Hoeu/iUR3ruzNvZ+yQfO03a0= +github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang-jwt/jwt v3.2.1+incompatible h1:73Z+4BJcrTC+KczS6WvTPvRGOp1WmfEP4Q1lOd9Z/+c= +github.com/golang-jwt/jwt v3.2.1+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI= github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= -github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo= -github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= @@ -90,26 +77,24 @@ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8= github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= -github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= -github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 h1:NmZ1PKzSTQbuGHw9DGPFomqkkLWMC+vZCkfs+FHv1Vg= github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3/go.mod h1:zQrxl1YP88HQlA6i9c63DSVPFklWpGX4OWAc9bFuaH4= -github.com/hamba/avro/v2 v2.30.0 h1:OaIdh0+dZIJ331FO/+YYBwZZRdGVyyHuRSyHsjZLJoA= -github.com/hamba/avro/v2 v2.30.0/go.mod h1:X6gDhYv6DQVAT56VqOKuW+PLnQrEQqGB9l1nhlMdAdQ= +github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c h1:6rhixN/i8ZofjG1Y75iExal34USq5p+wiN1tpie8IrU= +github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c/go.mod h1:NMPJylDgVpX0MLRlPy15sqSwOFv/U1GZ2m21JhFfek0= github.com/imdario/mergo v0.3.8 h1:CGgOkSJeqMRmt0D9XLWExdT4m4F1vd3FV3VPt+0VxkQ= github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= -github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/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.18.2 h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uqnapk= github.com/klauspost/compress v1.18.2/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4= 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/kris-nova/logger v0.0.0-20181127235838-fd0d87064b06 h1:vN4d3jSss3ExzUn2cE0WctxztfOgiKvMKnDrydBsg00= @@ -138,16 +123,11 @@ github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28= github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8= -github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= -github.com/mschoch/smat v0.2.0 h1:8imxQsjDm8yFEAVBe7azKmKSgzSkZXDuKkSq9374khM= -github.com/mschoch/smat v0.2.0/go.mod h1:kc9mz7DoBKqDyiRL7VZN8KvXQMWeTaVnttLRXOlotKw= -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/mtibben/percent v0.2.1 h1:5gssi8Nqo8QU/r2pynCm+hBQHpkB/uNK7BJCFogWdzs= +github.com/mtibben/percent v0.2.1/go.mod h1:KG9uO+SZkUp+VkRHsCdYQV3XSZrrSpR3O9ibNBTZrns= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/olekukonko/tablewriter v0.0.1 h1:b3iUnf1v+ppJiOfNX4yxxqfWKMQPZR5yoh8urCTFX88= @@ -162,8 +142,6 @@ github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8 github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= -github.com/pierrec/lz4/v4 v4.1.23 h1:oJE7T90aYBGtFNrI8+KbETnPymobAhzRrR8Mu8n1yfU= -github.com/pierrec/lz4/v4 v4.1.23/go.mod h1:EoQMVJgeeEOMsCqCzqFm2O0cJvljX2nGZjcRIPL34O4= 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= @@ -171,14 +149,6 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= -github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= -github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= -github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= -github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= -github.com/prometheus/common v0.67.4 h1:yR3NqWO1/UyO1w2PhUvXlGQs/PtFmoveVO0KZ4+Lvsc= -github.com/prometheus/common v0.67.4/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI= -github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws= -github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= @@ -190,8 +160,6 @@ github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU= github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= -github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= @@ -210,14 +178,10 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/testcontainers/testcontainers-go v0.35.0 h1:uADsZpTKFAtp8SLK+hMwSaa+X+JiERHtd4sQAFmXeMo= github.com/testcontainers/testcontainers-go v0.35.0/go.mod h1:oEVBj5zrfJTrgjwONs1SsRbnBtH9OKl+IGl3UMcr2B4= -github.com/theparanoids/crypki v1.20.11 h1:0FZfFmmIoSenyT1SnvnyBJmK9kvKlyHmAXBH00MW7Kk= -github.com/theparanoids/crypki v1.20.11/go.mod h1:xtnD/Nk357e6DiLOQjFAFi93bM8On83QScnoj3QA6oU= github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= -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/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw= @@ -228,10 +192,6 @@ go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0 h1:ssfIgGN go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0/go.mod h1:GQ/474YrbE4Jx8gZ4q5I4hrhUzM6UPzyrqJYV2AqPoQ= go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.39.0 h1:cEf8jF6WbuGQWUVcqgyWtTR0kOOAWY1DYZ+UhvdmQPw= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.39.0/go.mod h1:k1lzV5n5U3HkGvTCJHraTAGJ7MqsgL1wrGwTj1Isfiw= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.39.0 h1:nKP4Z2ejtHn3yShBb+2KawiXgpn8In5cT7aO2wXuOTE= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.39.0/go.mod h1:NwjeBbNigsO4Aj9WgM0C+cKIrxsZUaRmZUO7A8I7u8o= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 h1:1fTNlAIJZGWLP5FVu0fikVry1IsiUnXjf7QFvoNN3Xw= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0/go.mod h1:zjPK58DtkqQFn+YUMbx0M2XV3QgKU0gS9LeGohREyK4= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0 h1:xJ2qHD0C1BeYVTLLR9sX12+Qb95kfeD/byKj6Ky1pXg= @@ -246,10 +206,6 @@ go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6 go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A= go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4= -go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= -go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= -go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= -go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -281,6 +237,7 @@ golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210819135213-f52c844e1c1c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -314,10 +271,9 @@ google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHh google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/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/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= -gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= @@ -327,19 +283,3 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= -k8s.io/apimachinery v0.35.0 h1:Z2L3IHvPVv/MJ7xRxHEtk6GoJElaAqDCCU0S6ncYok8= -k8s.io/apimachinery v0.35.0/go.mod h1:jQCgFZFR1F4Ik7hvr2g84RTJSZegBc8yHgFWKn//hns= -k8s.io/client-go v0.35.0 h1:IAW0ifFbfQQwQmga0UdoH0yvdqrbwMdq9vIFEhRpxBE= -k8s.io/client-go v0.35.0/go.mod h1:q2E5AAyqcbeLGPdoRB+Nxe3KYTfPce1Dnu1myQdqz9o= -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-20251125145642-4e65d59e963e h1:iW9ChlU0cU16w8MpVYjXk12dqQ4BPFBEgif+ap7/hqQ= -k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= -k8s.io/utils v0.0.0-20251222233032-718f0e51e6d2 h1:OfgiEo21hGiwx1oJUU5MpEaeOEg6coWndBkZF/lkFuE= -k8s.io/utils v0.0.0-20251222233032-718f0e51e6d2/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk= -sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= -sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= -sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= -sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= -sigs.k8s.io/structured-merge-diff/v6 v6.3.1 h1:JrhdFMqOd/+3ByqlP2I45kTOZmTRLBUm5pvRjeheg7E= -sigs.k8s.io/structured-merge-diff/v6 v6.3.1/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= diff --git a/pkg/cmdutils/rest.go b/pkg/cmdutils/rest.go new file mode 100644 index 000000000..52cbc4bb8 --- /dev/null +++ b/pkg/cmdutils/rest.go @@ -0,0 +1,68 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 cmdutils + +import ( + "net/http" + "net/url" + "path" + + "github.com/apache/pulsar-client-go/pulsaradmin/pkg/admin" + "github.com/apache/pulsar-client-go/pulsaradmin/pkg/admin/auth" + "github.com/apache/pulsar-client-go/pulsaradmin/pkg/admin/config" + "github.com/apache/pulsar-client-go/pulsaradmin/pkg/rest" + "github.com/apache/pulsar-client-go/pulsaradmin/pkg/utils" +) + +func NewPulsarRESTClient() (*rest.Client, error) { + return NewPulsarRESTClientWithAPIVersion(config.V2) +} + +func NewPulsarRESTClientWithAPIVersion(version config.APIVersion) (*rest.Client, error) { + cfg := config.Config(*PulsarCtlConfig) + cfg.PulsarAPIVersion = version + if len(cfg.WebServiceURL) == 0 { + cfg.WebServiceURL = admin.DefaultWebServiceURL + } + + authProvider, err := auth.GetAuthProvider(&cfg) + if err != nil { + return nil, err + } + + return &rest.Client{ + ServiceURL: cfg.WebServiceURL, + VersionInfo: admin.ReleaseVersion, + HTTPClient: &http.Client{ + Timeout: admin.DefaultHTTPTimeOutDuration, + Transport: authProvider, + }, + }, nil +} + +func BuildAdminEndpoint(version config.APIVersion, componentPath string, parts ...string) string { + escapedParts := make([]string, len(parts)) + for i, part := range parts { + escapedParts[i] = url.PathEscape(part) + } + + return path.Join( + utils.MakeHTTPPath(version.String(), componentPath), + path.Join(escapedParts...), + ) +} diff --git a/pkg/ctl/brokers/healthcheck.go b/pkg/ctl/brokers/healthcheck.go index d6087d273..844eed38e 100644 --- a/pkg/ctl/brokers/healthcheck.go +++ b/pkg/ctl/brokers/healthcheck.go @@ -18,7 +18,6 @@ package brokers import ( - "github.com/apache/pulsar-client-go/pulsaradmin/pkg/utils" "github.com/streamnative/pulsarctl/pkg/cmdutils" ) @@ -57,7 +56,7 @@ func healthCheckCmd(vc *cmdutils.VerbCmd) { func doHealthCheck(vc *cmdutils.VerbCmd) error { admin := cmdutils.NewPulsarClient() - err := admin.Brokers().HealthCheckWithTopicVersion(utils.TopicVersionV1) + err := admin.Brokers().HealthCheck() if err == nil { vc.Command.Println("ok") } diff --git a/pkg/ctl/namespace/set_replication_clusters.go b/pkg/ctl/namespace/set_replication_clusters.go index 608550f0e..1198ccde0 100644 --- a/pkg/ctl/namespace/set_replication_clusters.go +++ b/pkg/ctl/namespace/set_replication_clusters.go @@ -87,7 +87,7 @@ func setReplicationClusters(vc *cmdutils.VerbCmd) { vc.FlagSetGroup.InFlagSet("Namespaces", func(flagSet *pflag.FlagSet) { flagSet.StringVarP( - &data.ClusterIDs, + &data.ClusterIds, "clusters", "c", "", @@ -102,7 +102,7 @@ func doSetReplicationClusters(vc *cmdutils.VerbCmd, data utils.NamespacesData) e ns := vc.NameArg admin := cmdutils.NewPulsarClient() - clusters := strings.Split(data.ClusterIDs, ",") + clusters := strings.Split(data.ClusterIds, ",") err := admin.Namespaces().SetNamespaceReplicationClusters(ns, clusters) if err == nil { vc.Command.Printf("Set replication clusters successfully for %s\n", ns) diff --git a/pkg/ctl/subscription/get_message_by_id.go b/pkg/ctl/subscription/get_message_by_id.go index 3b1c8dafa..328223bad 100644 --- a/pkg/ctl/subscription/get_message_by_id.go +++ b/pkg/ctl/subscription/get_message_by_id.go @@ -84,14 +84,13 @@ func doGetMessageByID(vc *cmdutils.VerbCmd, ledgerID int64, entryID int64) error } client := cmdutils.NewPulsarClient() - messages, err := client.Subscriptions().GetMessagesByID(*topic, ledgerID, entryID) + message, err := client.Subscriptions().GetMessageByID(*topic, ledgerID, entryID) if err != nil { return err } - if len(messages) == 0 { + if message == nil { return fmt.Errorf("no message found with the given ledgerID and entryID") } - message := messages[0] propertiesJSON, err := json.Marshal(message.GetProperties()) if err != nil { diff --git a/pkg/ctl/topic/get_subscription_dispatch_rate.go b/pkg/ctl/topic/get_subscription_dispatch_rate.go index 7db3943ea..93114a1b8 100644 --- a/pkg/ctl/topic/get_subscription_dispatch_rate.go +++ b/pkg/ctl/topic/get_subscription_dispatch_rate.go @@ -18,6 +18,9 @@ package topic import ( + "encoding/json" + + "github.com/apache/pulsar-client-go/pulsaradmin/pkg/admin/config" "github.com/apache/pulsar-client-go/pulsaradmin/pkg/utils" "github.com/streamnative/pulsarctl/pkg/cmdutils" @@ -73,11 +76,25 @@ func doGetSubscriptionDispatchRate(vc *cmdutils.VerbCmd) error { return err } - admin := cmdutils.NewPulsarClient() - dispatchRateData, err := admin.Topics().GetSubscriptionDispatchRate(*topic) - if err == nil { - oc := cmdutils.NewOutputContent().WithObject(dispatchRateData) - err = vc.OutputConfig.WriteOutput(vc.Command.OutOrStdout(), oc) + client, err := cmdutils.NewPulsarRESTClientWithAPIVersion(config.V2) + if err != nil { + return err + } + + endpoint := cmdutils.BuildAdminEndpoint(config.V2, "/persistent", topic.GetRestPath(), "subscriptionDispatchRate") + body, err := client.GetWithQueryParams(endpoint, nil, nil, false) + if err != nil { + return err } - return err + + var dispatchRateData *utils.DispatchRateData + if len(body) > 0 { + dispatchRateData = new(utils.DispatchRateData) + if err := json.Unmarshal(body, dispatchRateData); err != nil { + return err + } + } + + oc := cmdutils.NewOutputContent().WithObject(dispatchRateData) + return vc.OutputConfig.WriteOutput(vc.Command.OutOrStdout(), oc) } diff --git a/pkg/ctl/topic/remove_subscription_dispatch_rate.go b/pkg/ctl/topic/remove_subscription_dispatch_rate.go index 2216c2da0..e90f7b3ad 100644 --- a/pkg/ctl/topic/remove_subscription_dispatch_rate.go +++ b/pkg/ctl/topic/remove_subscription_dispatch_rate.go @@ -18,6 +18,7 @@ package topic import ( + "github.com/apache/pulsar-client-go/pulsaradmin/pkg/admin/config" "github.com/apache/pulsar-client-go/pulsaradmin/pkg/utils" "github.com/streamnative/pulsarctl/pkg/cmdutils" @@ -71,8 +72,13 @@ func doRemoveSubscriptionDispatchRate(vc *cmdutils.VerbCmd) error { return err } - admin := cmdutils.NewPulsarClient() - err = admin.Topics().RemoveSubscriptionDispatchRate(*topic) + client, err := cmdutils.NewPulsarRESTClientWithAPIVersion(config.V2) + if err != nil { + return err + } + + endpoint := cmdutils.BuildAdminEndpoint(config.V2, "/persistent", topic.GetRestPath(), "subscriptionDispatchRate") + err = client.Delete(endpoint) if err == nil { vc.Command.Printf("Remove subscription message dispatch rate successfully for [%s]\n", topic.String()) } diff --git a/pkg/ctl/topic/set_subscription_dispatch_rate.go b/pkg/ctl/topic/set_subscription_dispatch_rate.go index 5e3f2aded..0de6f35a4 100644 --- a/pkg/ctl/topic/set_subscription_dispatch_rate.go +++ b/pkg/ctl/topic/set_subscription_dispatch_rate.go @@ -18,6 +18,7 @@ package topic import ( + "github.com/apache/pulsar-client-go/pulsaradmin/pkg/admin/config" "github.com/apache/pulsar-client-go/pulsaradmin/pkg/utils" "github.com/spf13/pflag" @@ -101,8 +102,14 @@ func doSetSubscriptionDispatchRate(vc *cmdutils.VerbCmd, dispatchRateData *utils if err != nil { return err } - admin := cmdutils.NewPulsarClient() - err = admin.Topics().SetSubscriptionDispatchRate(*topic, *dispatchRateData) + + client, err := cmdutils.NewPulsarRESTClientWithAPIVersion(config.V2) + if err != nil { + return err + } + + endpoint := cmdutils.BuildAdminEndpoint(config.V2, "/persistent", topic.GetRestPath(), "subscriptionDispatchRate") + err = client.Post(endpoint, dispatchRateData) if err == nil { vc.Command.Printf("Set subscription message dispatch rate successfully for [%s]\n", topic.String()) } diff --git a/pkg/ctl/topic/stats.go b/pkg/ctl/topic/stats.go index a3d248f73..7a9749a5a 100644 --- a/pkg/ctl/topic/stats.go +++ b/pkg/ctl/topic/stats.go @@ -18,6 +18,7 @@ package topic import ( + "github.com/apache/pulsar-client-go/pulsaradmin/pkg/admin/config" "github.com/apache/pulsar-client-go/pulsaradmin/pkg/utils" "github.com/spf13/pflag" @@ -176,27 +177,44 @@ func doGetStats(vc *cmdutils.VerbCmd, partitionedTopic, perPartition bool, getPr return err } - getStatsOptions := utils.GetStatsOptions{ - GetPreciseBacklog: getPreciseBacklog, - SubscriptionBacklogSize: subscriptionBacklogSize, - GetEarliestTimeInBacklog: getEarliestTimeInBacklog, + client, err := cmdutils.NewPulsarRESTClientWithAPIVersion(config.V2) + if err != nil { + return err } - admin := cmdutils.NewPulsarClient() + params := map[string]string{ + "getPreciseBacklog": boolString(getPreciseBacklog), + "subscriptionBacklogSize": boolString(subscriptionBacklogSize), + "getEarliestTimeInBacklog": boolString(getEarliestTimeInBacklog), + } if partitionedTopic { - stats, err := admin.Topics().GetPartitionedStatsWithOption(*topic, perPartition, getStatsOptions) - if err == nil { - oc := cmdutils.NewOutputContent().WithObject(stats) - err = vc.OutputConfig.WriteOutput(vc.Command.OutOrStdout(), oc) + params["perPartition"] = boolString(perPartition) + stats := utils.PartitionedTopicStats{} + endpoint := cmdutils.BuildAdminEndpoint(config.V2, "/persistent", topic.GetRestPath(), "partitioned-stats") + _, err := client.GetWithQueryParams(endpoint, &stats, params, true) + if err != nil { + return err } + + oc := cmdutils.NewOutputContent().WithObject(stats) + return vc.OutputConfig.WriteOutput(vc.Command.OutOrStdout(), oc) + } + + topicStats := utils.TopicStats{} + endpoint := cmdutils.BuildAdminEndpoint(config.V2, "/persistent", topic.GetRestPath(), "stats") + _, err = client.GetWithQueryParams(endpoint, &topicStats, params, true) + if err != nil { return err } - topicStats, err := admin.Topics().GetStatsWithOption(*topic, getStatsOptions) - if err == nil { - oc := cmdutils.NewOutputContent().WithObject(topicStats) - err = vc.OutputConfig.WriteOutput(vc.Command.OutOrStdout(), oc) + oc := cmdutils.NewOutputContent().WithObject(topicStats) + return vc.OutputConfig.WriteOutput(vc.Command.OutOrStdout(), oc) +} + +func boolString(value bool) string { + if value { + return "true" } - return err + return "false" } diff --git a/pkg/ctl/topic/stats_test.go b/pkg/ctl/topic/stats_test.go index b43ce52e1..c62430ef7 100644 --- a/pkg/ctl/topic/stats_test.go +++ b/pkg/ctl/topic/stats_test.go @@ -52,8 +52,6 @@ func TestGetStatsCmd(t *testing.T) { if err != nil { t.Fatal(err) } - assert.Greaterf(t, stats.TopicCreationTimeStamp, int64(0), "TopicCreationTimeStamp should be greater than 0") - stats.TopicCreationTimeStamp = 0 // reset to zero for comparison assert.Equal(t, defaultStats, stats) } @@ -110,7 +108,6 @@ func TestGetPartitionedStatsCmd(t *testing.T) { assert.Equal(t, "", stats.DeDuplicationStatus) assert.Equal(t, 2, stats.Metadata.Partitions) assert.Equal(t, 0, len(stats.Partitions)) - assert.Greater(t, stats.TopicCreationTimeStamp, int64(0)) } func TestGetPerPartitionedStatsCmd(t *testing.T) { @@ -128,14 +125,8 @@ func TestGetPerPartitionedStatsCmd(t *testing.T) { t.Fatal(err) } - assert.Greater(t, stats.TopicCreationTimeStamp, int64(0)) - stats.TopicCreationTimeStamp = 0 partitionKey := "persistent://public/default/test-topic-per-partitioned-stats-partition-0" assert.Contains(t, stats.Partitions, partitionKey) - assert.Greaterf(t, stats.Partitions[partitionKey].TopicCreationTimeStamp, int64(0), "TopicCreationTimeStamp should be greater than 0") - v := stats.Partitions[partitionKey] - v.TopicCreationTimeStamp = 0 - stats.Partitions[partitionKey] = v defaultStats := utils.PartitionedTopicStats{ MsgRateIn: 0, @@ -207,8 +198,6 @@ func TestGetStatsWithPreciseBacklog(t *testing.T) { if err != nil { t.Fatal(err) } - assert.Greaterf(t, stats.TopicCreationTimeStamp, int64(0), "TopicCreationTimeStamp should be greater than 0") - stats.TopicCreationTimeStamp = 0 assert.Equal(t, defaultStats, stats) } @@ -227,8 +216,6 @@ func TestGetStatsWithoutSubscriptionBacklogSize(t *testing.T) { if err != nil { t.Fatal(err) } - assert.Greaterf(t, stats.TopicCreationTimeStamp, int64(0), "TopicCreationTimeStamp should be greater than 0") - stats.TopicCreationTimeStamp = 0 assert.Equal(t, defaultStats, stats) } @@ -247,8 +234,6 @@ func TestGetStatsWithEarliestTimeInBacklog(t *testing.T) { if err != nil { t.Fatal(err) } - assert.Greaterf(t, stats.TopicCreationTimeStamp, int64(0), "TopicCreationTimeStamp should be greater than 0") - stats.TopicCreationTimeStamp = 0 assert.Equal(t, defaultStats, stats) } @@ -267,8 +252,6 @@ func TestGetStatsWithMultipleNewFlags(t *testing.T) { if err != nil { t.Fatal(err) } - assert.Greaterf(t, stats.TopicCreationTimeStamp, int64(0), "TopicCreationTimeStamp should be greater than 0") - stats.TopicCreationTimeStamp = 0 assert.Equal(t, defaultStats, stats) } @@ -317,14 +300,8 @@ func TestGetPerPartitionStatsWithNewFlags(t *testing.T) { t.Fatal(err) } - assert.Greater(t, stats.TopicCreationTimeStamp, int64(0)) - stats.TopicCreationTimeStamp = 0 partitionKey := "persistent://public/default/test-per-part-stats-new-flags-partition-0" assert.Contains(t, stats.Partitions, partitionKey) - assert.Greaterf(t, stats.Partitions[partitionKey].TopicCreationTimeStamp, int64(0), "TopicCreationTimeStamp should be greater than 0") - v := stats.Partitions[partitionKey] - v.TopicCreationTimeStamp = 0 - stats.Partitions[partitionKey] = v defaultStats := utils.PartitionedTopicStats{ MsgRateIn: 0, From f1513a57ea227ca7d6bba8a66bdb8f0bbe3ac273 Mon Sep 17 00:00:00 2001 From: Rui Fu Date: Fri, 10 Apr 2026 22:48:27 +0800 Subject: [PATCH 2/8] Revert "feat: upgrade pulsar-client-go v0.12.0" This reverts commit 5253b94a1b9f32defca79e28b8c30abb9192cb41. --- go.mod | 33 +++--- go.sum | 110 ++++++++++++++---- pkg/cmdutils/rest.go | 68 ----------- pkg/ctl/brokers/healthcheck.go | 3 +- pkg/ctl/namespace/set_replication_clusters.go | 4 +- pkg/ctl/subscription/get_message_by_id.go | 5 +- .../topic/get_subscription_dispatch_rate.go | 29 +---- .../remove_subscription_dispatch_rate.go | 10 +- .../topic/set_subscription_dispatch_rate.go | 11 +- pkg/ctl/topic/stats.go | 46 +++----- pkg/ctl/topic/stats_test.go | 23 ++++ 11 files changed, 158 insertions(+), 184 deletions(-) delete mode 100644 pkg/cmdutils/rest.go diff --git a/go.mod b/go.mod index c07453dd2..f8b9cda6d 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/streamnative/pulsarctl go 1.25.8 require ( - github.com/apache/pulsar-client-go v0.12.0 + github.com/apache/pulsar-client-go v0.18.0-candidate-1.0.20251222030102-3bb7d4eff361 github.com/docker/go-connections v0.5.0 github.com/fatih/color v1.7.0 github.com/ghodss/yaml v1.0.0 @@ -24,35 +24,32 @@ require ( require ( dario.cat/mergo v1.0.0 // indirect - github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect - github.com/99designs/keyring v1.2.1 // indirect - github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 // indirect + github.com/AthenZ/athenz v1.12.31 // indirect github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect + github.com/DataDog/zstd v1.5.7 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect + github.com/RoaringBitmap/roaring/v2 v2.14.4 // indirect + github.com/bits-and-blooms/bitset v1.24.4 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/containerd/log v0.1.0 // indirect github.com/containerd/platforms v0.2.1 // indirect github.com/cpuguy83/dockercfg v0.3.2 // indirect - github.com/danieljoos/wincred v1.1.2 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/distribution/reference v0.6.0 // indirect github.com/docker/docker v28.0.0+incompatible // indirect github.com/docker/go-units v0.5.0 // indirect - github.com/dvsekhvalnov/jose2go v1.6.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect - github.com/fsnotify/fsnotify v1.9.0 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-ole/go-ole v1.2.6 // indirect - github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang-jwt/jwt v3.2.1+incompatible // indirect + github.com/golang-jwt/jwt/v5 v5.3.0 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/google/go-cmp v0.7.0 // indirect github.com/google/uuid v1.6.0 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 // indirect - github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c // indirect + github.com/hamba/avro/v2 v2.30.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/klauspost/compress v1.18.2 // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect @@ -65,13 +62,17 @@ require ( github.com/moby/sys/user v0.3.0 // indirect github.com/moby/sys/userns v0.1.0 // indirect github.com/moby/term v0.5.0 // indirect + github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect github.com/morikuni/aec v1.0.0 // indirect - github.com/mtibben/percent v0.2.1 // indirect github.com/onsi/ginkgo/v2 v2.27.2 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0 // indirect + github.com/pierrec/lz4/v4 v4.1.23 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect + github.com/prometheus/client_golang v1.23.2 // indirect + github.com/prometheus/common v0.67.4 // indirect + github.com/prometheus/procfs v0.19.2 // indirect github.com/shirou/gopsutil/v3 v3.23.12 // indirect github.com/shoenig/go-m1cpu v0.1.6 // indirect github.com/sirupsen/logrus v1.9.3 // indirect @@ -81,6 +82,8 @@ require ( go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0 // indirect go.opentelemetry.io/otel v1.39.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.39.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.39.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0 // indirect go.opentelemetry.io/otel/metric v1.39.0 // indirect go.opentelemetry.io/otel/trace v1.39.0 // indirect @@ -91,12 +94,14 @@ require ( golang.org/x/net v0.48.0 // indirect golang.org/x/oauth2 v0.34.0 // indirect golang.org/x/sys v0.39.0 // indirect - golang.org/x/term v0.38.0 // indirect golang.org/x/text v0.32.0 // indirect - golang.org/x/time v0.12.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20251222181119-0a764e51fe1b // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b // indirect - google.golang.org/grpc v1.77.0 // indirect google.golang.org/protobuf v1.36.11 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect + k8s.io/apimachinery v0.35.0 // indirect + k8s.io/client-go v0.35.0 // indirect + k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e // indirect + k8s.io/utils v0.0.0-20251222233032-718f0e51e6d2 // indirect + sigs.k8s.io/structured-merge-diff/v6 v6.3.1 // indirect ) diff --git a/go.sum b/go.sum index c4ef78bbb..91c0b6e48 100644 --- a/go.sum +++ b/go.sum @@ -1,21 +1,32 @@ dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= -github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 h1:/vQbFIOMbk2FiG/kXiLl8BRyzTWDw7gX/Hz7Dd5eDMs= -github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4/go.mod h1:hN7oaIRCjzsZ2dE+yG5k+rsdt3qcwykqK6HVGcKwsw4= -github.com/99designs/keyring v1.2.1 h1:tYLp1ULvO7i3fI5vE21ReQuj99QFSs7lGm0xWyJo87o= -github.com/99designs/keyring v1.2.1/go.mod h1:fc+wB5KTk9wQ9sDx0kFXB3A0MaeGHM9AwRStKOQ5vOA= -github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk= -github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= +github.com/AthenZ/athenz v1.12.31 h1:GQnRDLgivPlVvklSpH9gp+t/dho9DJTtt+hlLYo5TX8= +github.com/AthenZ/athenz v1.12.31/go.mod h1:6Siq4JOA4OjgYVgtTVIeHrb4HB2hEL8i4fx7aOFrgfY= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/DataDog/zstd v1.5.7 h1:ybO8RBeh29qrxIhCA9E8gKY6xfONU9T6G6aP9DTKfLE= +github.com/DataDog/zstd v1.5.7/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0= github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= -github.com/apache/pulsar-client-go v0.12.0 h1:rrMlwpr6IgLRPXLRRh2vSlcw5tGV2PUSjZwmqgh2B2I= -github.com/apache/pulsar-client-go v0.12.0/go.mod h1:dkutuH4oS2pXiGm+Ti7fQZ4MRjrMPZ8IJeEGAWMeckk= +github.com/RoaringBitmap/roaring/v2 v2.14.4 h1:4aKySrrg9G/5oRtJ3TrZLObVqxgQ9f1znCRBwEwjuVw= +github.com/RoaringBitmap/roaring/v2 v2.14.4/go.mod h1:oMvV6omPWr+2ifRdeZvVJyaz+aoEUopyv5iH0u/+wbY= +github.com/apache/pulsar-client-go v0.18.0-candidate-1.0.20251222030102-3bb7d4eff361 h1:Fb4j4v85TPq64FRp+QMLWaW3/Hg1Jg7TBWaZwPcSO9Y= +github.com/apache/pulsar-client-go v0.18.0-candidate-1.0.20251222030102-3bb7d4eff361/go.mod h1:/Zf8Q8bSSc6ndEJ8V1muIHf6ZWsMrHoQU+98Ww9pOeI= +github.com/ardielle/ardielle-go v1.5.2 h1:TilHTpHIQJ27R1Tl/iITBzMwiUGSlVfiVhwDNGM3Zj4= +github.com/ardielle/ardielle-go v1.5.2/go.mod h1:I4hy1n795cUhaVt/ojz83SNVCYIGsAFAONtv2Dr7HUI= +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/bits-and-blooms/bitset v1.24.4 h1:95H15Og1clikBrKr/DuzMXkQzECs1M6hhoGXLwLQOZE= +github.com/bits-and-blooms/bitset v1.24.4/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= +github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= +github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= 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/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= @@ -27,8 +38,6 @@ github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHf github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= -github.com/danieljoos/wincred v1.1.2 h1:QLdCxFs1/Yl4zduvBdcHB8goaYk9RARS2SgLLRuAyr0= -github.com/danieljoos/wincred v1.1.2/go.mod h1:GijpziifJoIBfYh+S7BbkdUTU4LfM+QnGqR5Vl2tAx0= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= @@ -41,16 +50,20 @@ github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/dvsekhvalnov/jose2go v1.6.0 h1:Y9gnSnP4qEI0+/uQkHvFXeD2PLPJeXEL+ySMEA2EjTY= -github.com/dvsekhvalnov/jose2go v1.6.0/go.mod h1:QsHjhyTlD/lAVqn/NSbVZmSCGeDehTB/mPZadG+mhXU= +github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= +github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= +github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs= +github.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= @@ -60,14 +73,14 @@ github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= 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/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 h1:ZpnhV/YsD2/4cESfV5+Hoeu/iUR3ruzNvZ+yQfO03a0= -github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= +github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs= +github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang-jwt/jwt v3.2.1+incompatible h1:73Z+4BJcrTC+KczS6WvTPvRGOp1WmfEP4Q1lOd9Z/+c= -github.com/golang-jwt/jwt v3.2.1+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI= github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo= +github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= @@ -77,24 +90,26 @@ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8= github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 h1:NmZ1PKzSTQbuGHw9DGPFomqkkLWMC+vZCkfs+FHv1Vg= github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3/go.mod h1:zQrxl1YP88HQlA6i9c63DSVPFklWpGX4OWAc9bFuaH4= -github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c h1:6rhixN/i8ZofjG1Y75iExal34USq5p+wiN1tpie8IrU= -github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c/go.mod h1:NMPJylDgVpX0MLRlPy15sqSwOFv/U1GZ2m21JhFfek0= +github.com/hamba/avro/v2 v2.30.0 h1:OaIdh0+dZIJ331FO/+YYBwZZRdGVyyHuRSyHsjZLJoA= +github.com/hamba/avro/v2 v2.30.0/go.mod h1:X6gDhYv6DQVAT56VqOKuW+PLnQrEQqGB9l1nhlMdAdQ= github.com/imdario/mergo v0.3.8 h1:CGgOkSJeqMRmt0D9XLWExdT4m4F1vd3FV3VPt+0VxkQ= github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/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.18.2 h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uqnapk= github.com/klauspost/compress v1.18.2/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4= 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/kris-nova/logger v0.0.0-20181127235838-fd0d87064b06 h1:vN4d3jSss3ExzUn2cE0WctxztfOgiKvMKnDrydBsg00= @@ -123,11 +138,16 @@ github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28= github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8= +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= -github.com/mtibben/percent v0.2.1 h1:5gssi8Nqo8QU/r2pynCm+hBQHpkB/uNK7BJCFogWdzs= -github.com/mtibben/percent v0.2.1/go.mod h1:KG9uO+SZkUp+VkRHsCdYQV3XSZrrSpR3O9ibNBTZrns= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/mschoch/smat v0.2.0 h1:8imxQsjDm8yFEAVBe7azKmKSgzSkZXDuKkSq9374khM= +github.com/mschoch/smat v0.2.0/go.mod h1:kc9mz7DoBKqDyiRL7VZN8KvXQMWeTaVnttLRXOlotKw= +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/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/olekukonko/tablewriter v0.0.1 h1:b3iUnf1v+ppJiOfNX4yxxqfWKMQPZR5yoh8urCTFX88= @@ -142,6 +162,8 @@ github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8 github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= +github.com/pierrec/lz4/v4 v4.1.23 h1:oJE7T90aYBGtFNrI8+KbETnPymobAhzRrR8Mu8n1yfU= +github.com/pierrec/lz4/v4 v4.1.23/go.mod h1:EoQMVJgeeEOMsCqCzqFm2O0cJvljX2nGZjcRIPL34O4= 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= @@ -149,6 +171,14 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= +github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= +github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= +github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= +github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= +github.com/prometheus/common v0.67.4 h1:yR3NqWO1/UyO1w2PhUvXlGQs/PtFmoveVO0KZ4+Lvsc= +github.com/prometheus/common v0.67.4/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI= +github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws= +github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= @@ -160,6 +190,8 @@ github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU= github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= +github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= @@ -178,10 +210,14 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/testcontainers/testcontainers-go v0.35.0 h1:uADsZpTKFAtp8SLK+hMwSaa+X+JiERHtd4sQAFmXeMo= github.com/testcontainers/testcontainers-go v0.35.0/go.mod h1:oEVBj5zrfJTrgjwONs1SsRbnBtH9OKl+IGl3UMcr2B4= +github.com/theparanoids/crypki v1.20.11 h1:0FZfFmmIoSenyT1SnvnyBJmK9kvKlyHmAXBH00MW7Kk= +github.com/theparanoids/crypki v1.20.11/go.mod h1:xtnD/Nk357e6DiLOQjFAFi93bM8On83QScnoj3QA6oU= github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= +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/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw= @@ -192,6 +228,10 @@ go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0 h1:ssfIgGN go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0/go.mod h1:GQ/474YrbE4Jx8gZ4q5I4hrhUzM6UPzyrqJYV2AqPoQ= go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.39.0 h1:cEf8jF6WbuGQWUVcqgyWtTR0kOOAWY1DYZ+UhvdmQPw= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.39.0/go.mod h1:k1lzV5n5U3HkGvTCJHraTAGJ7MqsgL1wrGwTj1Isfiw= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.39.0 h1:nKP4Z2ejtHn3yShBb+2KawiXgpn8In5cT7aO2wXuOTE= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.39.0/go.mod h1:NwjeBbNigsO4Aj9WgM0C+cKIrxsZUaRmZUO7A8I7u8o= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 h1:1fTNlAIJZGWLP5FVu0fikVry1IsiUnXjf7QFvoNN3Xw= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0/go.mod h1:zjPK58DtkqQFn+YUMbx0M2XV3QgKU0gS9LeGohREyK4= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0 h1:xJ2qHD0C1BeYVTLLR9sX12+Qb95kfeD/byKj6Ky1pXg= @@ -206,6 +246,10 @@ go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6 go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A= go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4= +go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= +go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= +go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -237,7 +281,6 @@ golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210819135213-f52c844e1c1c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -271,9 +314,10 @@ google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHh google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/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/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= @@ -283,3 +327,19 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= +k8s.io/apimachinery v0.35.0 h1:Z2L3IHvPVv/MJ7xRxHEtk6GoJElaAqDCCU0S6ncYok8= +k8s.io/apimachinery v0.35.0/go.mod h1:jQCgFZFR1F4Ik7hvr2g84RTJSZegBc8yHgFWKn//hns= +k8s.io/client-go v0.35.0 h1:IAW0ifFbfQQwQmga0UdoH0yvdqrbwMdq9vIFEhRpxBE= +k8s.io/client-go v0.35.0/go.mod h1:q2E5AAyqcbeLGPdoRB+Nxe3KYTfPce1Dnu1myQdqz9o= +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-20251125145642-4e65d59e963e h1:iW9ChlU0cU16w8MpVYjXk12dqQ4BPFBEgif+ap7/hqQ= +k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= +k8s.io/utils v0.0.0-20251222233032-718f0e51e6d2 h1:OfgiEo21hGiwx1oJUU5MpEaeOEg6coWndBkZF/lkFuE= +k8s.io/utils v0.0.0-20251222233032-718f0e51e6d2/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= +sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= +sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= +sigs.k8s.io/structured-merge-diff/v6 v6.3.1 h1:JrhdFMqOd/+3ByqlP2I45kTOZmTRLBUm5pvRjeheg7E= +sigs.k8s.io/structured-merge-diff/v6 v6.3.1/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= diff --git a/pkg/cmdutils/rest.go b/pkg/cmdutils/rest.go deleted file mode 100644 index 52cbc4bb8..000000000 --- a/pkg/cmdutils/rest.go +++ /dev/null @@ -1,68 +0,0 @@ -// Licensed to the Apache Software Foundation (ASF) under one -// or more contributor license agreements. See the NOTICE file -// distributed with this work for additional information -// regarding copyright ownership. The ASF licenses this file -// to you 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 cmdutils - -import ( - "net/http" - "net/url" - "path" - - "github.com/apache/pulsar-client-go/pulsaradmin/pkg/admin" - "github.com/apache/pulsar-client-go/pulsaradmin/pkg/admin/auth" - "github.com/apache/pulsar-client-go/pulsaradmin/pkg/admin/config" - "github.com/apache/pulsar-client-go/pulsaradmin/pkg/rest" - "github.com/apache/pulsar-client-go/pulsaradmin/pkg/utils" -) - -func NewPulsarRESTClient() (*rest.Client, error) { - return NewPulsarRESTClientWithAPIVersion(config.V2) -} - -func NewPulsarRESTClientWithAPIVersion(version config.APIVersion) (*rest.Client, error) { - cfg := config.Config(*PulsarCtlConfig) - cfg.PulsarAPIVersion = version - if len(cfg.WebServiceURL) == 0 { - cfg.WebServiceURL = admin.DefaultWebServiceURL - } - - authProvider, err := auth.GetAuthProvider(&cfg) - if err != nil { - return nil, err - } - - return &rest.Client{ - ServiceURL: cfg.WebServiceURL, - VersionInfo: admin.ReleaseVersion, - HTTPClient: &http.Client{ - Timeout: admin.DefaultHTTPTimeOutDuration, - Transport: authProvider, - }, - }, nil -} - -func BuildAdminEndpoint(version config.APIVersion, componentPath string, parts ...string) string { - escapedParts := make([]string, len(parts)) - for i, part := range parts { - escapedParts[i] = url.PathEscape(part) - } - - return path.Join( - utils.MakeHTTPPath(version.String(), componentPath), - path.Join(escapedParts...), - ) -} diff --git a/pkg/ctl/brokers/healthcheck.go b/pkg/ctl/brokers/healthcheck.go index 844eed38e..d6087d273 100644 --- a/pkg/ctl/brokers/healthcheck.go +++ b/pkg/ctl/brokers/healthcheck.go @@ -18,6 +18,7 @@ package brokers import ( + "github.com/apache/pulsar-client-go/pulsaradmin/pkg/utils" "github.com/streamnative/pulsarctl/pkg/cmdutils" ) @@ -56,7 +57,7 @@ func healthCheckCmd(vc *cmdutils.VerbCmd) { func doHealthCheck(vc *cmdutils.VerbCmd) error { admin := cmdutils.NewPulsarClient() - err := admin.Brokers().HealthCheck() + err := admin.Brokers().HealthCheckWithTopicVersion(utils.TopicVersionV1) if err == nil { vc.Command.Println("ok") } diff --git a/pkg/ctl/namespace/set_replication_clusters.go b/pkg/ctl/namespace/set_replication_clusters.go index 1198ccde0..608550f0e 100644 --- a/pkg/ctl/namespace/set_replication_clusters.go +++ b/pkg/ctl/namespace/set_replication_clusters.go @@ -87,7 +87,7 @@ func setReplicationClusters(vc *cmdutils.VerbCmd) { vc.FlagSetGroup.InFlagSet("Namespaces", func(flagSet *pflag.FlagSet) { flagSet.StringVarP( - &data.ClusterIds, + &data.ClusterIDs, "clusters", "c", "", @@ -102,7 +102,7 @@ func doSetReplicationClusters(vc *cmdutils.VerbCmd, data utils.NamespacesData) e ns := vc.NameArg admin := cmdutils.NewPulsarClient() - clusters := strings.Split(data.ClusterIds, ",") + clusters := strings.Split(data.ClusterIDs, ",") err := admin.Namespaces().SetNamespaceReplicationClusters(ns, clusters) if err == nil { vc.Command.Printf("Set replication clusters successfully for %s\n", ns) diff --git a/pkg/ctl/subscription/get_message_by_id.go b/pkg/ctl/subscription/get_message_by_id.go index 328223bad..3b1c8dafa 100644 --- a/pkg/ctl/subscription/get_message_by_id.go +++ b/pkg/ctl/subscription/get_message_by_id.go @@ -84,13 +84,14 @@ func doGetMessageByID(vc *cmdutils.VerbCmd, ledgerID int64, entryID int64) error } client := cmdutils.NewPulsarClient() - message, err := client.Subscriptions().GetMessageByID(*topic, ledgerID, entryID) + messages, err := client.Subscriptions().GetMessagesByID(*topic, ledgerID, entryID) if err != nil { return err } - if message == nil { + if len(messages) == 0 { return fmt.Errorf("no message found with the given ledgerID and entryID") } + message := messages[0] propertiesJSON, err := json.Marshal(message.GetProperties()) if err != nil { diff --git a/pkg/ctl/topic/get_subscription_dispatch_rate.go b/pkg/ctl/topic/get_subscription_dispatch_rate.go index 93114a1b8..7db3943ea 100644 --- a/pkg/ctl/topic/get_subscription_dispatch_rate.go +++ b/pkg/ctl/topic/get_subscription_dispatch_rate.go @@ -18,9 +18,6 @@ package topic import ( - "encoding/json" - - "github.com/apache/pulsar-client-go/pulsaradmin/pkg/admin/config" "github.com/apache/pulsar-client-go/pulsaradmin/pkg/utils" "github.com/streamnative/pulsarctl/pkg/cmdutils" @@ -76,25 +73,11 @@ func doGetSubscriptionDispatchRate(vc *cmdutils.VerbCmd) error { return err } - client, err := cmdutils.NewPulsarRESTClientWithAPIVersion(config.V2) - if err != nil { - return err - } - - endpoint := cmdutils.BuildAdminEndpoint(config.V2, "/persistent", topic.GetRestPath(), "subscriptionDispatchRate") - body, err := client.GetWithQueryParams(endpoint, nil, nil, false) - if err != nil { - return err + admin := cmdutils.NewPulsarClient() + dispatchRateData, err := admin.Topics().GetSubscriptionDispatchRate(*topic) + if err == nil { + oc := cmdutils.NewOutputContent().WithObject(dispatchRateData) + err = vc.OutputConfig.WriteOutput(vc.Command.OutOrStdout(), oc) } - - var dispatchRateData *utils.DispatchRateData - if len(body) > 0 { - dispatchRateData = new(utils.DispatchRateData) - if err := json.Unmarshal(body, dispatchRateData); err != nil { - return err - } - } - - oc := cmdutils.NewOutputContent().WithObject(dispatchRateData) - return vc.OutputConfig.WriteOutput(vc.Command.OutOrStdout(), oc) + return err } diff --git a/pkg/ctl/topic/remove_subscription_dispatch_rate.go b/pkg/ctl/topic/remove_subscription_dispatch_rate.go index e90f7b3ad..2216c2da0 100644 --- a/pkg/ctl/topic/remove_subscription_dispatch_rate.go +++ b/pkg/ctl/topic/remove_subscription_dispatch_rate.go @@ -18,7 +18,6 @@ package topic import ( - "github.com/apache/pulsar-client-go/pulsaradmin/pkg/admin/config" "github.com/apache/pulsar-client-go/pulsaradmin/pkg/utils" "github.com/streamnative/pulsarctl/pkg/cmdutils" @@ -72,13 +71,8 @@ func doRemoveSubscriptionDispatchRate(vc *cmdutils.VerbCmd) error { return err } - client, err := cmdutils.NewPulsarRESTClientWithAPIVersion(config.V2) - if err != nil { - return err - } - - endpoint := cmdutils.BuildAdminEndpoint(config.V2, "/persistent", topic.GetRestPath(), "subscriptionDispatchRate") - err = client.Delete(endpoint) + admin := cmdutils.NewPulsarClient() + err = admin.Topics().RemoveSubscriptionDispatchRate(*topic) if err == nil { vc.Command.Printf("Remove subscription message dispatch rate successfully for [%s]\n", topic.String()) } diff --git a/pkg/ctl/topic/set_subscription_dispatch_rate.go b/pkg/ctl/topic/set_subscription_dispatch_rate.go index 0de6f35a4..5e3f2aded 100644 --- a/pkg/ctl/topic/set_subscription_dispatch_rate.go +++ b/pkg/ctl/topic/set_subscription_dispatch_rate.go @@ -18,7 +18,6 @@ package topic import ( - "github.com/apache/pulsar-client-go/pulsaradmin/pkg/admin/config" "github.com/apache/pulsar-client-go/pulsaradmin/pkg/utils" "github.com/spf13/pflag" @@ -102,14 +101,8 @@ func doSetSubscriptionDispatchRate(vc *cmdutils.VerbCmd, dispatchRateData *utils if err != nil { return err } - - client, err := cmdutils.NewPulsarRESTClientWithAPIVersion(config.V2) - if err != nil { - return err - } - - endpoint := cmdutils.BuildAdminEndpoint(config.V2, "/persistent", topic.GetRestPath(), "subscriptionDispatchRate") - err = client.Post(endpoint, dispatchRateData) + admin := cmdutils.NewPulsarClient() + err = admin.Topics().SetSubscriptionDispatchRate(*topic, *dispatchRateData) if err == nil { vc.Command.Printf("Set subscription message dispatch rate successfully for [%s]\n", topic.String()) } diff --git a/pkg/ctl/topic/stats.go b/pkg/ctl/topic/stats.go index 7a9749a5a..a3d248f73 100644 --- a/pkg/ctl/topic/stats.go +++ b/pkg/ctl/topic/stats.go @@ -18,7 +18,6 @@ package topic import ( - "github.com/apache/pulsar-client-go/pulsaradmin/pkg/admin/config" "github.com/apache/pulsar-client-go/pulsaradmin/pkg/utils" "github.com/spf13/pflag" @@ -177,44 +176,27 @@ func doGetStats(vc *cmdutils.VerbCmd, partitionedTopic, perPartition bool, getPr return err } - client, err := cmdutils.NewPulsarRESTClientWithAPIVersion(config.V2) - if err != nil { - return err + getStatsOptions := utils.GetStatsOptions{ + GetPreciseBacklog: getPreciseBacklog, + SubscriptionBacklogSize: subscriptionBacklogSize, + GetEarliestTimeInBacklog: getEarliestTimeInBacklog, } - params := map[string]string{ - "getPreciseBacklog": boolString(getPreciseBacklog), - "subscriptionBacklogSize": boolString(subscriptionBacklogSize), - "getEarliestTimeInBacklog": boolString(getEarliestTimeInBacklog), - } + admin := cmdutils.NewPulsarClient() if partitionedTopic { - params["perPartition"] = boolString(perPartition) - stats := utils.PartitionedTopicStats{} - endpoint := cmdutils.BuildAdminEndpoint(config.V2, "/persistent", topic.GetRestPath(), "partitioned-stats") - _, err := client.GetWithQueryParams(endpoint, &stats, params, true) - if err != nil { - return err + stats, err := admin.Topics().GetPartitionedStatsWithOption(*topic, perPartition, getStatsOptions) + if err == nil { + oc := cmdutils.NewOutputContent().WithObject(stats) + err = vc.OutputConfig.WriteOutput(vc.Command.OutOrStdout(), oc) } - - oc := cmdutils.NewOutputContent().WithObject(stats) - return vc.OutputConfig.WriteOutput(vc.Command.OutOrStdout(), oc) - } - - topicStats := utils.TopicStats{} - endpoint := cmdutils.BuildAdminEndpoint(config.V2, "/persistent", topic.GetRestPath(), "stats") - _, err = client.GetWithQueryParams(endpoint, &topicStats, params, true) - if err != nil { return err } - oc := cmdutils.NewOutputContent().WithObject(topicStats) - return vc.OutputConfig.WriteOutput(vc.Command.OutOrStdout(), oc) -} - -func boolString(value bool) string { - if value { - return "true" + topicStats, err := admin.Topics().GetStatsWithOption(*topic, getStatsOptions) + if err == nil { + oc := cmdutils.NewOutputContent().WithObject(topicStats) + err = vc.OutputConfig.WriteOutput(vc.Command.OutOrStdout(), oc) } - return "false" + return err } diff --git a/pkg/ctl/topic/stats_test.go b/pkg/ctl/topic/stats_test.go index c62430ef7..b43ce52e1 100644 --- a/pkg/ctl/topic/stats_test.go +++ b/pkg/ctl/topic/stats_test.go @@ -52,6 +52,8 @@ func TestGetStatsCmd(t *testing.T) { if err != nil { t.Fatal(err) } + assert.Greaterf(t, stats.TopicCreationTimeStamp, int64(0), "TopicCreationTimeStamp should be greater than 0") + stats.TopicCreationTimeStamp = 0 // reset to zero for comparison assert.Equal(t, defaultStats, stats) } @@ -108,6 +110,7 @@ func TestGetPartitionedStatsCmd(t *testing.T) { assert.Equal(t, "", stats.DeDuplicationStatus) assert.Equal(t, 2, stats.Metadata.Partitions) assert.Equal(t, 0, len(stats.Partitions)) + assert.Greater(t, stats.TopicCreationTimeStamp, int64(0)) } func TestGetPerPartitionedStatsCmd(t *testing.T) { @@ -125,8 +128,14 @@ func TestGetPerPartitionedStatsCmd(t *testing.T) { t.Fatal(err) } + assert.Greater(t, stats.TopicCreationTimeStamp, int64(0)) + stats.TopicCreationTimeStamp = 0 partitionKey := "persistent://public/default/test-topic-per-partitioned-stats-partition-0" assert.Contains(t, stats.Partitions, partitionKey) + assert.Greaterf(t, stats.Partitions[partitionKey].TopicCreationTimeStamp, int64(0), "TopicCreationTimeStamp should be greater than 0") + v := stats.Partitions[partitionKey] + v.TopicCreationTimeStamp = 0 + stats.Partitions[partitionKey] = v defaultStats := utils.PartitionedTopicStats{ MsgRateIn: 0, @@ -198,6 +207,8 @@ func TestGetStatsWithPreciseBacklog(t *testing.T) { if err != nil { t.Fatal(err) } + assert.Greaterf(t, stats.TopicCreationTimeStamp, int64(0), "TopicCreationTimeStamp should be greater than 0") + stats.TopicCreationTimeStamp = 0 assert.Equal(t, defaultStats, stats) } @@ -216,6 +227,8 @@ func TestGetStatsWithoutSubscriptionBacklogSize(t *testing.T) { if err != nil { t.Fatal(err) } + assert.Greaterf(t, stats.TopicCreationTimeStamp, int64(0), "TopicCreationTimeStamp should be greater than 0") + stats.TopicCreationTimeStamp = 0 assert.Equal(t, defaultStats, stats) } @@ -234,6 +247,8 @@ func TestGetStatsWithEarliestTimeInBacklog(t *testing.T) { if err != nil { t.Fatal(err) } + assert.Greaterf(t, stats.TopicCreationTimeStamp, int64(0), "TopicCreationTimeStamp should be greater than 0") + stats.TopicCreationTimeStamp = 0 assert.Equal(t, defaultStats, stats) } @@ -252,6 +267,8 @@ func TestGetStatsWithMultipleNewFlags(t *testing.T) { if err != nil { t.Fatal(err) } + assert.Greaterf(t, stats.TopicCreationTimeStamp, int64(0), "TopicCreationTimeStamp should be greater than 0") + stats.TopicCreationTimeStamp = 0 assert.Equal(t, defaultStats, stats) } @@ -300,8 +317,14 @@ func TestGetPerPartitionStatsWithNewFlags(t *testing.T) { t.Fatal(err) } + assert.Greater(t, stats.TopicCreationTimeStamp, int64(0)) + stats.TopicCreationTimeStamp = 0 partitionKey := "persistent://public/default/test-per-part-stats-new-flags-partition-0" assert.Contains(t, stats.Partitions, partitionKey) + assert.Greaterf(t, stats.Partitions[partitionKey].TopicCreationTimeStamp, int64(0), "TopicCreationTimeStamp should be greater than 0") + v := stats.Partitions[partitionKey] + v.TopicCreationTimeStamp = 0 + stats.Partitions[partitionKey] = v defaultStats := utils.PartitionedTopicStats{ MsgRateIn: 0, From acf900b225f32ad9421e2fa6f59f56fc33c5a8b7 Mon Sep 17 00:00:00 2001 From: Rui Fu Date: Fri, 10 Apr 2026 23:30:54 +0800 Subject: [PATCH 3/8] refactor(namespace): simplify cluster handling logic --- go.mod | 3 ++- go.sum | 8 ++++---- pkg/ctl/brokerstats/load_report_test.go | 1 + pkg/ctl/namespace/set_replication_clusters.go | 11 +++++------ 4 files changed, 12 insertions(+), 11 deletions(-) diff --git a/go.mod b/go.mod index f8b9cda6d..2f21e32c9 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/streamnative/pulsarctl go 1.25.8 require ( - github.com/apache/pulsar-client-go v0.18.0-candidate-1.0.20251222030102-3bb7d4eff361 + github.com/apache/pulsar-client-go v0.19.0-candidate-1 github.com/docker/go-connections v0.5.0 github.com/fatih/color v1.7.0 github.com/ghodss/yaml v1.0.0 @@ -24,6 +24,7 @@ require ( require ( dario.cat/mergo v1.0.0 // indirect + github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 // indirect github.com/AthenZ/athenz v1.12.31 // indirect github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect github.com/DataDog/zstd v1.5.7 // indirect diff --git a/go.sum b/go.sum index 91c0b6e48..e825ecf34 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,7 @@ dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= -github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU= -github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= github.com/AthenZ/athenz v1.12.31 h1:GQnRDLgivPlVvklSpH9gp+t/dho9DJTtt+hlLYo5TX8= github.com/AthenZ/athenz v1.12.31/go.mod h1:6Siq4JOA4OjgYVgtTVIeHrb4HB2hEL8i4fx7aOFrgfY= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= @@ -14,8 +14,8 @@ github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERo github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/RoaringBitmap/roaring/v2 v2.14.4 h1:4aKySrrg9G/5oRtJ3TrZLObVqxgQ9f1znCRBwEwjuVw= github.com/RoaringBitmap/roaring/v2 v2.14.4/go.mod h1:oMvV6omPWr+2ifRdeZvVJyaz+aoEUopyv5iH0u/+wbY= -github.com/apache/pulsar-client-go v0.18.0-candidate-1.0.20251222030102-3bb7d4eff361 h1:Fb4j4v85TPq64FRp+QMLWaW3/Hg1Jg7TBWaZwPcSO9Y= -github.com/apache/pulsar-client-go v0.18.0-candidate-1.0.20251222030102-3bb7d4eff361/go.mod h1:/Zf8Q8bSSc6ndEJ8V1muIHf6ZWsMrHoQU+98Ww9pOeI= +github.com/apache/pulsar-client-go v0.19.0-candidate-1 h1:FtOmcndcFzmleMZmkQEO1OEZ0HroCchqPMpIflKC1lY= +github.com/apache/pulsar-client-go v0.19.0-candidate-1/go.mod h1:/Zf8Q8bSSc6ndEJ8V1muIHf6ZWsMrHoQU+98Ww9pOeI= github.com/ardielle/ardielle-go v1.5.2 h1:TilHTpHIQJ27R1Tl/iITBzMwiUGSlVfiVhwDNGM3Zj4= github.com/ardielle/ardielle-go v1.5.2/go.mod h1:I4hy1n795cUhaVt/ojz83SNVCYIGsAFAONtv2Dr7HUI= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= diff --git a/pkg/ctl/brokerstats/load_report_test.go b/pkg/ctl/brokerstats/load_report_test.go index aff2b88af..4fe48a98d 100644 --- a/pkg/ctl/brokerstats/load_report_test.go +++ b/pkg/ctl/brokerstats/load_report_test.go @@ -37,5 +37,6 @@ func TestDumpLoadReport(t *testing.T) { t.FailNow() } defaultBrokerData := utils.NewLocalBrokerData() + defaultBrokerData.LastStats = nil assert.Equal(t, defaultBrokerData, getBrokerData) } diff --git a/pkg/ctl/namespace/set_replication_clusters.go b/pkg/ctl/namespace/set_replication_clusters.go index 608550f0e..1230ab949 100644 --- a/pkg/ctl/namespace/set_replication_clusters.go +++ b/pkg/ctl/namespace/set_replication_clusters.go @@ -20,7 +20,6 @@ package namespace import ( "strings" - "github.com/apache/pulsar-client-go/pulsaradmin/pkg/utils" "github.com/spf13/cobra" "github.com/spf13/pflag" @@ -79,15 +78,15 @@ func setReplicationClusters(vc *cmdutils.VerbCmd) { "set-clusters", ) - var data utils.NamespacesData + var clusterIDs string vc.SetRunFuncWithNameArg(func() error { - return doSetReplicationClusters(vc, data) + return doSetReplicationClusters(vc, clusterIDs) }, "the cluster name is not specified or the cluster name is specified more than one") vc.FlagSetGroup.InFlagSet("Namespaces", func(flagSet *pflag.FlagSet) { flagSet.StringVarP( - &data.ClusterIDs, + &clusterIDs, "clusters", "c", "", @@ -98,11 +97,11 @@ func setReplicationClusters(vc *cmdutils.VerbCmd) { vc.EnableOutputFlagSet() } -func doSetReplicationClusters(vc *cmdutils.VerbCmd, data utils.NamespacesData) error { +func doSetReplicationClusters(vc *cmdutils.VerbCmd, clusterIDs string) error { ns := vc.NameArg admin := cmdutils.NewPulsarClient() - clusters := strings.Split(data.ClusterIDs, ",") + clusters := strings.Split(clusterIDs, ",") err := admin.Namespaces().SetNamespaceReplicationClusters(ns, clusters) if err == nil { vc.Command.Printf("Set replication clusters successfully for %s\n", ns) From 66ae3ef6e1dd93dfdd5470d057c5e08843c23e29 Mon Sep 17 00:00:00 2001 From: Rui Fu Date: Sat, 11 Apr 2026 00:12:49 +0800 Subject: [PATCH 4/8] feat: add multiple new namespace, topic, and schema management commands --- pkg/ctl/brokers/broker.go | 1 + pkg/ctl/brokers/leader_broker.go | 67 ++++ pkg/ctl/brokers/leader_broker_test.go | 57 +++ pkg/ctl/namespace/bookie_affinity_group.go | 166 +++++++++ .../namespace/bookie_affinity_group_test.go | 71 ++++ pkg/ctl/namespace/max_topics_per_namespace.go | 188 ++++++++++ .../max_topics_per_namespace_test.go | 69 ++++ pkg/ctl/namespace/namespace.go | 23 ++ pkg/ctl/namespace/properties.go | 339 ++++++++++++++++++ pkg/ctl/namespace/properties_test.go | 105 ++++++ pkg/ctl/namespace/remove_settings.go | 136 +++++++ pkg/ctl/namespace/remove_settings_test.go | 169 +++++++++ .../schema_compatibility_strategy.go | 139 +++++++ .../schema_compatibility_strategy_test.go | 75 ++++ .../namespace/subscription_expiration_time.go | 188 ++++++++++ .../subscription_expiration_time_test.go | 69 ++++ pkg/ctl/schemas/compatibility.go | 83 +++++ pkg/ctl/schemas/delete.go | 28 +- pkg/ctl/schemas/get.go | 29 +- pkg/ctl/schemas/new_commands_test.go | 142 ++++++++ pkg/ctl/schemas/schema_test.go | 15 + pkg/ctl/schemas/schemas.go | 1 + pkg/ctl/topic/create.go | 16 +- pkg/ctl/topic/properties.go | 82 +++++ pkg/ctl/topic/schema_validation_enforce.go | 70 ++++ pkg/ctl/topic/topic.go | 33 ++ pkg/ctl/topicpolicies/common.go | 69 ++++ pkg/ctl/topicpolicies/max_limits.go | 281 +++++++++++++++ pkg/ctl/topicpolicies/offload_policies.go | 99 +++++ pkg/ctl/topicpolicies/replication_clusters.go | 157 ++++++++ .../schema_compatibility_strategy.go | 99 +++++ pkg/ctl/topicpolicies/topic_policies.go | 61 ++++ pkg/pulsarctl.go | 2 + 33 files changed, 3119 insertions(+), 10 deletions(-) create mode 100644 pkg/ctl/brokers/leader_broker.go create mode 100644 pkg/ctl/brokers/leader_broker_test.go create mode 100644 pkg/ctl/namespace/bookie_affinity_group.go create mode 100644 pkg/ctl/namespace/bookie_affinity_group_test.go create mode 100644 pkg/ctl/namespace/max_topics_per_namespace.go create mode 100644 pkg/ctl/namespace/max_topics_per_namespace_test.go create mode 100644 pkg/ctl/namespace/properties.go create mode 100644 pkg/ctl/namespace/properties_test.go create mode 100644 pkg/ctl/namespace/remove_settings.go create mode 100644 pkg/ctl/namespace/remove_settings_test.go create mode 100644 pkg/ctl/namespace/schema_compatibility_strategy.go create mode 100644 pkg/ctl/namespace/schema_compatibility_strategy_test.go create mode 100644 pkg/ctl/namespace/subscription_expiration_time.go create mode 100644 pkg/ctl/namespace/subscription_expiration_time_test.go create mode 100644 pkg/ctl/schemas/compatibility.go create mode 100644 pkg/ctl/schemas/new_commands_test.go create mode 100644 pkg/ctl/topic/properties.go create mode 100644 pkg/ctl/topic/schema_validation_enforce.go create mode 100644 pkg/ctl/topicpolicies/common.go create mode 100644 pkg/ctl/topicpolicies/max_limits.go create mode 100644 pkg/ctl/topicpolicies/offload_policies.go create mode 100644 pkg/ctl/topicpolicies/replication_clusters.go create mode 100644 pkg/ctl/topicpolicies/schema_compatibility_strategy.go create mode 100644 pkg/ctl/topicpolicies/topic_policies.go diff --git a/pkg/ctl/brokers/broker.go b/pkg/ctl/brokers/broker.go index 0985a175f..e2a43f721 100644 --- a/pkg/ctl/brokers/broker.go +++ b/pkg/ctl/brokers/broker.go @@ -30,6 +30,7 @@ func Command(flagGrouping *cmdutils.FlagGrouping) *cobra.Command { "broker") cmdutils.AddVerbCmd(flagGrouping, resourceCmd, getBrokerListCmd) + cmdutils.AddVerbCmd(flagGrouping, resourceCmd, leaderBrokerCmd) cmdutils.AddVerbCmd(flagGrouping, resourceCmd, getDynamicConfigListNameCmd) cmdutils.AddVerbCmd(flagGrouping, resourceCmd, getOwnedNamespacesCmd) cmdutils.AddVerbCmd(flagGrouping, resourceCmd, updateDynamicConfig) diff --git a/pkg/ctl/brokers/leader_broker.go b/pkg/ctl/brokers/leader_broker.go new file mode 100644 index 000000000..470eda413 --- /dev/null +++ b/pkg/ctl/brokers/leader_broker.go @@ -0,0 +1,67 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 brokers + +import "github.com/streamnative/pulsarctl/pkg/cmdutils" + +func leaderBrokerCmd(vc *cmdutils.VerbCmd) { + desc := cmdutils.LongDescription{} + desc.CommandUsedFor = "Get the information of the leader broker" + desc.CommandPermission = "This command requires super-user permissions." + + var examples []cmdutils.Example + get := cmdutils.Example{ + Desc: desc.CommandUsedFor, + Command: "pulsarctl brokers leader-broker", + } + examples = append(examples, get) + desc.CommandExamples = examples + + var out []cmdutils.Output + successOut := cmdutils.Output{ + Desc: "normal output", + Out: "{\n" + + " \"brokerId\": \"broker-1\",\n" + + " \"serviceUrl\": \"http://127.0.0.1:8080\"\n" + + "}", + } + out = append(out, successOut) + desc.CommandOutput = out + + vc.SetDescription( + "leader-broker", + desc.CommandUsedFor, + desc.ToString(), + desc.ExampleToString(), + "leader-broker") + + vc.SetRunFunc(func() error { + return doGetLeaderBroker(vc) + }) + vc.EnableOutputFlagSet() +} + +func doGetLeaderBroker(vc *cmdutils.VerbCmd) error { + admin := cmdutils.NewPulsarClient() + info, err := admin.Brokers().GetLeaderBroker() + if err == nil { + oc := cmdutils.NewOutputContent().WithObject(info) + err = vc.OutputConfig.WriteOutput(vc.Command.OutOrStdout(), oc) + } + return err +} diff --git a/pkg/ctl/brokers/leader_broker_test.go b/pkg/ctl/brokers/leader_broker_test.go new file mode 100644 index 000000000..db5ff743b --- /dev/null +++ b/pkg/ctl/brokers/leader_broker_test.go @@ -0,0 +1,57 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 brokers + +import ( + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + + "github.com/apache/pulsar-client-go/pulsaradmin/pkg/utils" + "github.com/stretchr/testify/assert" + + "github.com/streamnative/pulsarctl/pkg/cmdutils" +) + +func TestLeaderBroker(t *testing.T) { + oldURL := cmdutils.PulsarCtlConfig.WebServiceURL + defer func() { + cmdutils.PulsarCtlConfig.WebServiceURL = oldURL + }() + + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, http.MethodGet, r.Method) + assert.Equal(t, "/admin/v2/brokers/leaderBroker", r.URL.Path) + _, _ = w.Write([]byte(`{"brokerId":"broker-1","serviceUrl":"http://127.0.0.1:8080"}`)) + })) + defer srv.Close() + + cmdutils.PulsarCtlConfig.WebServiceURL = srv.URL + + args := []string{"leader-broker"} + out, execErr, _, err := TestBrokersCommands(leaderBrokerCmd, args) + assert.Nil(t, err) + assert.Nil(t, execErr) + + var info utils.BrokerInfo + err = json.Unmarshal(out.Bytes(), &info) + assert.Nil(t, err) + assert.Equal(t, "broker-1", info.BrokerID) + assert.Equal(t, "http://127.0.0.1:8080", info.ServiceURL) +} diff --git a/pkg/ctl/namespace/bookie_affinity_group.go b/pkg/ctl/namespace/bookie_affinity_group.go new file mode 100644 index 000000000..ea7a403ee --- /dev/null +++ b/pkg/ctl/namespace/bookie_affinity_group.go @@ -0,0 +1,166 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 namespace + +import ( + "github.com/apache/pulsar-client-go/pulsaradmin/pkg/utils" + "github.com/spf13/cobra" + "github.com/spf13/pflag" + + "github.com/streamnative/pulsarctl/pkg/cmdutils" +) + +func GetBookieAffinityGroupCmd(vc *cmdutils.VerbCmd) { + var desc cmdutils.LongDescription + desc.CommandUsedFor = "Get bookie affinity group configured for a namespace" + desc.CommandPermission = "This command requires super-user permissions." + + var examples []cmdutils.Example + get := cmdutils.Example{ + Desc: "Get bookie affinity group configured for a namespace", + Command: "pulsarctl namespaces get-bookie-affinity-group tenant/namespace", + } + examples = append(examples, get) + desc.CommandExamples = examples + + var out []cmdutils.Output + successOut := cmdutils.Output{ + Desc: "normal output", + Out: "{\n \"bookkeeperAffinityGroupPrimary\": \"primary\",\n \"bookkeeperAffinityGroupSecondary\": \"secondary\"\n}", + } + out = append(out, successOut, ArgError, NsNotExistError) + out = append(out, NsErrors...) + desc.CommandOutput = out + + vc.SetDescription( + "get-bookie-affinity-group", + "Get bookie affinity group configured for a namespace", + desc.ToString(), + desc.ExampleToString(), + ) + + vc.EnableOutputFlagSet() + vc.SetRunFuncWithNameArg(func() error { + return doGetBookieAffinityGroup(vc) + }, "the namespace name is not specified or the namespace name is specified more than one") +} + +func doGetBookieAffinityGroup(vc *cmdutils.VerbCmd) error { + admin := cmdutils.NewPulsarClient() + group, err := admin.Namespaces().GetBookieAffinityGroup(vc.NameArg) + if err == nil { + oc := cmdutils.NewOutputContent().WithObject(group) + err = vc.OutputConfig.WriteOutput(vc.Command.OutOrStdout(), oc) + } + return err +} + +func SetBookieAffinityGroupCmd(vc *cmdutils.VerbCmd) { + var desc cmdutils.LongDescription + desc.CommandUsedFor = "Set bookie affinity group configured for a namespace" + desc.CommandPermission = "This command requires super-user permissions." + + var examples []cmdutils.Example + set := cmdutils.Example{ + Desc: "Set bookie affinity group configured for a namespace", + Command: "pulsarctl namespaces set-bookie-affinity-group tenant/namespace \n" + + "\t--primary-group primary-group \n" + + "\t--secondary-group secondary-group", + } + examples = append(examples, set) + desc.CommandExamples = examples + + var out []cmdutils.Output + successOut := cmdutils.Output{ + Desc: "normal output", + Out: "Set bookie affinity group successfully for [tenant/namespace]", + } + out = append(out, successOut, ArgError, NsNotExistError) + out = append(out, NsErrors...) + desc.CommandOutput = out + + vc.SetDescription( + "set-bookie-affinity-group", + "Set bookie affinity group configured for a namespace", + desc.ToString(), + desc.ExampleToString(), + ) + + data := utils.BookieAffinityGroupData{} + vc.FlagSetGroup.InFlagSet("Bookie Affinity Group", func(set *pflag.FlagSet) { + set.StringVar(&data.BookkeeperAffinityGroupPrimary, "primary-group", "", "primary affinity group") + set.StringVar(&data.BookkeeperAffinityGroupSecondary, "secondary-group", "", "secondary affinity group") + _ = cobra.MarkFlagRequired(set, "primary-group") + }) + + vc.SetRunFuncWithNameArg(func() error { + return doSetBookieAffinityGroup(vc, data) + }, "the namespace name is not specified or the namespace name is specified more than one") +} + +func doSetBookieAffinityGroup(vc *cmdutils.VerbCmd, data utils.BookieAffinityGroupData) error { + admin := cmdutils.NewPulsarClient() + err := admin.Namespaces().SetBookieAffinityGroup(vc.NameArg, data) + if err == nil { + vc.Command.Printf("Set bookie affinity group successfully for [%s]\n", vc.NameArg) + } + return err +} + +func DeleteBookieAffinityGroupCmd(vc *cmdutils.VerbCmd) { + var desc cmdutils.LongDescription + desc.CommandUsedFor = "Delete bookie affinity group configured for a namespace" + desc.CommandPermission = "This command requires super-user permissions." + + var examples []cmdutils.Example + del := cmdutils.Example{ + Desc: "Delete bookie affinity group configured for a namespace", + Command: "pulsarctl namespaces delete-bookie-affinity-group tenant/namespace", + } + examples = append(examples, del) + desc.CommandExamples = examples + + var out []cmdutils.Output + successOut := cmdutils.Output{ + Desc: "normal output", + Out: "Deleted bookie affinity group successfully for [tenant/namespace]", + } + out = append(out, successOut, ArgError, NsNotExistError) + out = append(out, NsErrors...) + desc.CommandOutput = out + + vc.SetDescription( + "delete-bookie-affinity-group", + "Delete bookie affinity group configured for a namespace", + desc.ToString(), + desc.ExampleToString(), + ) + + vc.SetRunFuncWithNameArg(func() error { + return doDeleteBookieAffinityGroup(vc) + }, "the namespace name is not specified or the namespace name is specified more than one") +} + +func doDeleteBookieAffinityGroup(vc *cmdutils.VerbCmd) error { + admin := cmdutils.NewPulsarClient() + err := admin.Namespaces().DeleteBookieAffinityGroup(vc.NameArg) + if err == nil { + vc.Command.Printf("Deleted bookie affinity group successfully for [%s]\n", vc.NameArg) + } + return err +} diff --git a/pkg/ctl/namespace/bookie_affinity_group_test.go b/pkg/ctl/namespace/bookie_affinity_group_test.go new file mode 100644 index 000000000..37baa20cb --- /dev/null +++ b/pkg/ctl/namespace/bookie_affinity_group_test.go @@ -0,0 +1,71 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 namespace + +import ( + "encoding/json" + "fmt" + "testing" + + "github.com/apache/pulsar-client-go/pulsaradmin/pkg/utils" + "github.com/stretchr/testify/assert" +) + +func TestBookieAffinityGroupCmd(t *testing.T) { + ns := "public/test-bookie-affinity-group" + args := []string{"create", ns} + _, execErr, _, _ := TestNamespaceCommands(createNs, args) + assert.Nil(t, execErr) + + args = []string{"get-bookie-affinity-group", ns} + getOut, execErr, _, _ := TestNamespaceCommands(GetBookieAffinityGroupCmd, args) + assert.Nil(t, execErr) + var initialGroup *utils.BookieAffinityGroupData + err := json.Unmarshal(getOut.Bytes(), &initialGroup) + assert.Nil(t, err) + + args = []string{"set-bookie-affinity-group", ns, "--primary-group", "primary", "--secondary-group", "secondary"} + setOut, execErr, _, _ := TestNamespaceCommands(SetBookieAffinityGroupCmd, args) + assert.Nil(t, execErr) + assert.Equal(t, fmt.Sprintf("Set bookie affinity group successfully for [%s]\n", ns), setOut.String()) + + args = []string{"get-bookie-affinity-group", ns} + getOut, execErr, _, _ = TestNamespaceCommands(GetBookieAffinityGroupCmd, args) + assert.Nil(t, execErr) + var currentGroup *utils.BookieAffinityGroupData + err = json.Unmarshal(getOut.Bytes(), ¤tGroup) + assert.Nil(t, err) + if !assert.NotNil(t, currentGroup) { + return + } + assert.Equal(t, "primary", currentGroup.BookkeeperAffinityGroupPrimary) + assert.Equal(t, "secondary", currentGroup.BookkeeperAffinityGroupSecondary) + + args = []string{"delete-bookie-affinity-group", ns} + delOut, execErr, _, _ := TestNamespaceCommands(DeleteBookieAffinityGroupCmd, args) + assert.Nil(t, execErr) + assert.Equal(t, fmt.Sprintf("Deleted bookie affinity group successfully for [%s]\n", ns), delOut.String()) + + args = []string{"get-bookie-affinity-group", ns} + getOut, execErr, _, _ = TestNamespaceCommands(GetBookieAffinityGroupCmd, args) + assert.Nil(t, execErr) + var afterDeleteGroup *utils.BookieAffinityGroupData + err = json.Unmarshal(getOut.Bytes(), &afterDeleteGroup) + assert.Nil(t, err) + assert.Equal(t, initialGroup, afterDeleteGroup) +} diff --git a/pkg/ctl/namespace/max_topics_per_namespace.go b/pkg/ctl/namespace/max_topics_per_namespace.go new file mode 100644 index 000000000..f82849aaa --- /dev/null +++ b/pkg/ctl/namespace/max_topics_per_namespace.go @@ -0,0 +1,188 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 namespace + +import ( + "github.com/apache/pulsar-client-go/pulsaradmin/pkg/utils" + "github.com/pkg/errors" + "github.com/spf13/cobra" + "github.com/spf13/pflag" + + "github.com/streamnative/pulsarctl/pkg/cmdutils" +) + +func GetMaxTopicsPerNamespaceCmd(vc *cmdutils.VerbCmd) { + var desc cmdutils.LongDescription + desc.CommandUsedFor = "This command is used for getting the max topics per namespace of a namespace." + desc.CommandPermission = "This command requires tenant admin permissions." + + var examples []cmdutils.Example + get := cmdutils.Example{ + Desc: "Get the max topics per namespace of the namespace (namespace-name)", + Command: "pulsarctl namespaces get-max-topics-per-namespace (namespace-name)", + } + examples = append(examples, get) + desc.CommandExamples = examples + + var out []cmdutils.Output + successOut := cmdutils.Output{ + Desc: "normal output", + Out: "The max topics per namespace of the namespace (namespace-name) is (size)", + } + out = append(out, successOut, ArgError, NsNotExistError) + out = append(out, NsErrors...) + desc.CommandOutput = out + + vc.SetDescription( + "get-max-topics-per-namespace", + "Get the max topics per namespace of a namespace", + desc.ToString(), + desc.ExampleToString(), + ) + + vc.SetRunFuncWithNameArg(func() error { + return doGetMaxTopicsPerNamespace(vc) + }, "the namespace name is not specified or the namespace name is specified more than one") +} + +func doGetMaxTopicsPerNamespace(vc *cmdutils.VerbCmd) error { + ns, err := utils.GetNamespaceName(vc.NameArg) + if err != nil { + return err + } + + admin := cmdutils.NewPulsarClient() + max, err := admin.Namespaces().GetMaxTopicsPerNamespace(*ns) + if err == nil { + if max == -1 { + vc.Command.Printf("The max topics per namespace of the namespace %s is not set\n", ns.String()) + } else { + vc.Command.Printf("The max topics per namespace of the namespace %s is %d\n", ns.String(), max) + } + } + return err +} + +func SetMaxTopicsPerNamespaceCmd(vc *cmdutils.VerbCmd) { + var desc cmdutils.LongDescription + desc.CommandUsedFor = "This command is used for setting the max topics per namespace of a namespace." + desc.CommandPermission = "This command requires super-user permissions and broker has write policies permission." + + var examples []cmdutils.Example + set := cmdutils.Example{ + Desc: "Set the max topics per namespace of the namespace (namespace-name) to (size)", + Command: "pulsarctl namespaces set-max-topics-per-namespace --max-topics-per-namespace (size) (namespace-name)", + } + examples = append(examples, set) + desc.CommandExamples = examples + + var out []cmdutils.Output + successOut := cmdutils.Output{ + Desc: "normal output", + Out: "Successfully set the max topics per namespace of the namespace (namespace-name) to (size)", + } + out = append(out, successOut, ArgError, NsNotExistError) + out = append(out, NsErrors...) + desc.CommandOutput = out + + vc.SetDescription( + "set-max-topics-per-namespace", + "Set the max topics per namespace of a namespace", + desc.ToString(), + desc.ExampleToString(), + ) + + var max int + vc.FlagSetGroup.InFlagSet("Max Topics Per Namespace", func(set *pflag.FlagSet) { + set.IntVarP(&max, "max-topics-per-namespace", "t", -1, "max topics per namespace") + _ = cobra.MarkFlagRequired(set, "max-topics-per-namespace") + }) + + vc.SetRunFuncWithNameArg(func() error { + return doSetMaxTopicsPerNamespace(vc, max) + }, "the namespace name is not specified or the namespace name is specified more than one") + + vc.EnableOutputFlagSet() +} + +func doSetMaxTopicsPerNamespace(vc *cmdutils.VerbCmd, max int) error { + ns, err := utils.GetNamespaceName(vc.NameArg) + if err != nil { + return err + } + + if max < 0 { + return errors.New("the specified max topics value must bigger than 0") + } + + admin := cmdutils.NewPulsarClient() + err = admin.Namespaces().SetMaxTopicsPerNamespace(*ns, max) + if err == nil { + vc.Command.Printf("Successfully set the max topics per namespace of the namespace %s to %d\n", + ns.String(), max) + } + return err +} + +func RemoveMaxTopicsPerNamespaceCmd(vc *cmdutils.VerbCmd) { + var desc cmdutils.LongDescription + desc.CommandUsedFor = "This command is used for removing the max topics per namespace of a namespace." + desc.CommandPermission = "This command requires tenant admin permissions." + + var examples []cmdutils.Example + remove := cmdutils.Example{ + Desc: "Remove the max topics per namespace of the namespace (namespace-name)", + Command: "pulsarctl namespaces remove-max-topics-per-namespace (namespace-name)", + } + examples = append(examples, remove) + desc.CommandExamples = examples + + var out []cmdutils.Output + successOut := cmdutils.Output{ + Desc: "normal output", + Out: "Successfully removed the max topics per namespace of the namespace (namespace-name)", + } + out = append(out, successOut, ArgError, NsNotExistError) + out = append(out, NsErrors...) + desc.CommandOutput = out + + vc.SetDescription( + "remove-max-topics-per-namespace", + "Remove the max topics per namespace of a namespace", + desc.ToString(), + desc.ExampleToString(), + ) + + vc.SetRunFuncWithNameArg(func() error { + return doRemoveMaxTopicsPerNamespace(vc) + }, "the namespace name is not specified or the namespace name is specified more than one") +} + +func doRemoveMaxTopicsPerNamespace(vc *cmdutils.VerbCmd) error { + ns, err := utils.GetNamespaceName(vc.NameArg) + if err != nil { + return err + } + + admin := cmdutils.NewPulsarClient() + err = admin.Namespaces().RemoveMaxTopicsPerNamespace(*ns) + if err == nil { + vc.Command.Printf("Successfully removed the max topics per namespace of the namespace %s\n", ns.String()) + } + return err +} diff --git a/pkg/ctl/namespace/max_topics_per_namespace_test.go b/pkg/ctl/namespace/max_topics_per_namespace_test.go new file mode 100644 index 000000000..bc66231ac --- /dev/null +++ b/pkg/ctl/namespace/max_topics_per_namespace_test.go @@ -0,0 +1,69 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 namespace + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestMaxTopicsPerNamespaceCmd(t *testing.T) { + ns := "public/test-max-topics-per-namespace" + args := []string{"create", ns} + _, execErr, _, _ := TestNamespaceCommands(createNs, args) + assert.Nil(t, execErr) + + args = []string{"get-max-topics-per-namespace", ns} + initialOut, execErr, _, _ := TestNamespaceCommands(GetMaxTopicsPerNamespaceCmd, args) + assert.Nil(t, execErr) + + args = []string{"set-max-topics-per-namespace", "--max-topics-per-namespace", "11", ns} + setOut, execErr, _, _ := TestNamespaceCommands(SetMaxTopicsPerNamespaceCmd, args) + assert.Nil(t, execErr) + assert.Equal(t, + fmt.Sprintf("Successfully set the max topics per namespace of the namespace %s to %d\n", ns, 11), + setOut.String()) + + args = []string{"get-max-topics-per-namespace", ns} + getOut, execErr, _, _ := TestNamespaceCommands(GetMaxTopicsPerNamespaceCmd, args) + assert.Nil(t, execErr) + assert.Equal(t, + fmt.Sprintf("The max topics per namespace of the namespace %s is %d\n", ns, 11), + getOut.String()) + + args = []string{"remove-max-topics-per-namespace", ns} + removeOut, execErr, _, _ := TestNamespaceCommands(RemoveMaxTopicsPerNamespaceCmd, args) + assert.Nil(t, execErr) + assert.Equal(t, + fmt.Sprintf("Successfully removed the max topics per namespace of the namespace %s\n", ns), + removeOut.String()) + + args = []string{"get-max-topics-per-namespace", ns} + getOut, execErr, _, _ = TestNamespaceCommands(GetMaxTopicsPerNamespaceCmd, args) + assert.Nil(t, execErr) + assert.Equal(t, initialOut.String(), getOut.String()) +} + +func TestSetMaxTopicsPerNamespaceWithInvalidSize(t *testing.T) { + args := []string{"set-max-topics-per-namespace", "--max-topics-per-namespace", "-1", "public/invalid-max-topics"} + _, execErr, _, _ := TestNamespaceCommands(SetMaxTopicsPerNamespaceCmd, args) + assert.NotNil(t, execErr) + assert.Equal(t, "the specified max topics value must bigger than 0", execErr.Error()) +} diff --git a/pkg/ctl/namespace/namespace.go b/pkg/ctl/namespace/namespace.go index 116f260f3..8726298fb 100644 --- a/pkg/ctl/namespace/namespace.go +++ b/pkg/ctl/namespace/namespace.go @@ -38,8 +38,10 @@ func Command(flagGrouping *cmdutils.FlagGrouping) *cobra.Command { cmdutils.AddVerbCmd(flagGrouping, resourceCmd, deleteNs) cmdutils.AddVerbCmd(flagGrouping, resourceCmd, setMessageTTL) cmdutils.AddVerbCmd(flagGrouping, resourceCmd, getMessageTTL) + cmdutils.AddVerbCmd(flagGrouping, resourceCmd, removeMessageTTL) cmdutils.AddVerbCmd(flagGrouping, resourceCmd, getRetention) cmdutils.AddVerbCmd(flagGrouping, resourceCmd, setRetention) + cmdutils.AddVerbCmd(flagGrouping, resourceCmd, removeRetention) cmdutils.AddVerbCmd(flagGrouping, resourceCmd, getBacklogQuota) cmdutils.AddVerbCmd(flagGrouping, resourceCmd, setBacklogQuota) cmdutils.AddVerbCmd(flagGrouping, resourceCmd, removeBacklogQuota) @@ -60,14 +62,18 @@ func Command(flagGrouping *cmdutils.FlagGrouping) *cobra.Command { cmdutils.AddVerbCmd(flagGrouping, resourceCmd, GetMaxConsumersPerSubscriptionCmd) cmdutils.AddVerbCmd(flagGrouping, resourceCmd, SetMaxConsumersPerTopicCmd) cmdutils.AddVerbCmd(flagGrouping, resourceCmd, GetMaxConsumersPerTopicCmd) + cmdutils.AddVerbCmd(flagGrouping, resourceCmd, RemoveMaxConsumersPerSubscriptionCmd) + cmdutils.AddVerbCmd(flagGrouping, resourceCmd, RemoveMaxConsumersPerTopicCmd) cmdutils.AddVerbCmd(flagGrouping, resourceCmd, SetMaxProducersPerTopicCmd) cmdutils.AddVerbCmd(flagGrouping, resourceCmd, GetMaxProducersPerTopicCmd) + cmdutils.AddVerbCmd(flagGrouping, resourceCmd, RemoveMaxProducersPerTopicCmd) cmdutils.AddVerbCmd(flagGrouping, resourceCmd, getAntiAffinityGroup) cmdutils.AddVerbCmd(flagGrouping, resourceCmd, setAntiAffinityGroup) cmdutils.AddVerbCmd(flagGrouping, resourceCmd, deleteAntiAffinityGroup) cmdutils.AddVerbCmd(flagGrouping, resourceCmd, getAntiAffinityNamespaces) cmdutils.AddVerbCmd(flagGrouping, resourceCmd, getPersistence) cmdutils.AddVerbCmd(flagGrouping, resourceCmd, setPersistence) + cmdutils.AddVerbCmd(flagGrouping, resourceCmd, removePersistence) cmdutils.AddVerbCmd(flagGrouping, resourceCmd, setDeduplication) cmdutils.AddVerbCmd(flagGrouping, resourceCmd, setReplicationClusters) cmdutils.AddVerbCmd(flagGrouping, resourceCmd, getReplicationClusters) @@ -97,5 +103,22 @@ func Command(flagGrouping *cmdutils.FlagGrouping) *cobra.Command { cmdutils.AddVerbCmd(flagGrouping, resourceCmd, GetInactiveTopicCmd) cmdutils.AddVerbCmd(flagGrouping, resourceCmd, SetInactiveTopicCmd) cmdutils.AddVerbCmd(flagGrouping, resourceCmd, RemoveInactiveTopicCmd) + cmdutils.AddVerbCmd(flagGrouping, resourceCmd, GetMaxTopicsPerNamespaceCmd) + cmdutils.AddVerbCmd(flagGrouping, resourceCmd, SetMaxTopicsPerNamespaceCmd) + cmdutils.AddVerbCmd(flagGrouping, resourceCmd, RemoveMaxTopicsPerNamespaceCmd) + cmdutils.AddVerbCmd(flagGrouping, resourceCmd, GetSubscriptionExpirationTimeCmd) + cmdutils.AddVerbCmd(flagGrouping, resourceCmd, SetSubscriptionExpirationTimeCmd) + cmdutils.AddVerbCmd(flagGrouping, resourceCmd, RemoveSubscriptionExpirationTimeCmd) + cmdutils.AddVerbCmd(flagGrouping, resourceCmd, GetBookieAffinityGroupCmd) + cmdutils.AddVerbCmd(flagGrouping, resourceCmd, SetBookieAffinityGroupCmd) + cmdutils.AddVerbCmd(flagGrouping, resourceCmd, DeleteBookieAffinityGroupCmd) + cmdutils.AddVerbCmd(flagGrouping, resourceCmd, GetSchemaCompatibilityStrategyCmd) + cmdutils.AddVerbCmd(flagGrouping, resourceCmd, SetSchemaCompatibilityStrategyCmd) + cmdutils.AddVerbCmd(flagGrouping, resourceCmd, GetPropertiesCmd) + cmdutils.AddVerbCmd(flagGrouping, resourceCmd, SetPropertiesCmd) + cmdutils.AddVerbCmd(flagGrouping, resourceCmd, ClearPropertiesCmd) + cmdutils.AddVerbCmd(flagGrouping, resourceCmd, GetPropertyCmd) + cmdutils.AddVerbCmd(flagGrouping, resourceCmd, SetPropertyCmd) + cmdutils.AddVerbCmd(flagGrouping, resourceCmd, RemovePropertyCmd) return resourceCmd } diff --git a/pkg/ctl/namespace/properties.go b/pkg/ctl/namespace/properties.go new file mode 100644 index 000000000..357db20ac --- /dev/null +++ b/pkg/ctl/namespace/properties.go @@ -0,0 +1,339 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 namespace + +import ( + "fmt" + "io" + + "github.com/apache/pulsar-client-go/pulsaradmin/pkg/utils" + "github.com/spf13/cobra" + "github.com/spf13/pflag" + + "github.com/streamnative/pulsarctl/pkg/cmdutils" +) + +func getNamespaceProperties(namespace string) (utils.NameSpaceName, map[string]string, error) { + ns, err := utils.GetNamespaceName(namespace) + if err != nil { + return utils.NameSpaceName{}, nil, err + } + admin := cmdutils.NewPulsarClient() + properties, err := admin.Namespaces().GetProperties(*ns) + if err != nil { + return utils.NameSpaceName{}, nil, err + } + if properties == nil { + properties = map[string]string{} + } + return *ns, properties, nil +} + +func updateNamespaceProperties(ns utils.NameSpaceName, properties map[string]string) error { + admin := cmdutils.NewPulsarClient() + return admin.Namespaces().UpdateProperties(ns, properties) +} + +func removeNamespaceProperties(ns utils.NameSpaceName) error { + admin := cmdutils.NewPulsarClient() + return admin.Namespaces().RemoveProperties(ns) +} + +func GetPropertiesCmd(vc *cmdutils.VerbCmd) { + var desc cmdutils.LongDescription + desc.CommandUsedFor = "Get properties of a namespace" + desc.CommandPermission = "This command requires tenant admin permissions." + + var examples []cmdutils.Example + examples = append(examples, cmdutils.Example{ + Desc: "Get properties of a namespace", + Command: "pulsarctl namespaces get-properties tenant/namespace", + }) + desc.CommandExamples = examples + desc.CommandOutput = append(desc.CommandOutput, ArgError, NsNotExistError) + desc.CommandOutput = append(desc.CommandOutput, NsErrors...) + + vc.SetDescription( + "get-properties", + "Get properties of a namespace", + desc.ToString(), + desc.ExampleToString(), + ) + vc.EnableOutputFlagSet() + vc.SetRunFuncWithNameArg(func() error { + return doGetProperties(vc) + }, "the namespace name is not specified or the namespace name is specified more than one") +} + +func doGetProperties(vc *cmdutils.VerbCmd) error { + _, properties, err := getNamespaceProperties(vc.NameArg) + if err != nil { + return err + } + oc := cmdutils.NewOutputContent().WithObject(properties) + return vc.OutputConfig.WriteOutput(vc.Command.OutOrStdout(), oc) +} + +func SetPropertiesCmd(vc *cmdutils.VerbCmd) { + var desc cmdutils.LongDescription + desc.CommandUsedFor = "Set properties of a namespace" + desc.CommandPermission = "This command requires tenant admin permissions." + + var examples []cmdutils.Example + examples = append(examples, cmdutils.Example{ + Desc: "Set properties of a namespace", + Command: "pulsarctl namespaces set-properties tenant/namespace -p k1=v1,k2=v2", + }) + desc.CommandExamples = examples + desc.CommandOutput = append(desc.CommandOutput, ArgError, NsNotExistError) + desc.CommandOutput = append(desc.CommandOutput, NsErrors...) + + vc.SetDescription( + "set-properties", + "Set properties of a namespace", + desc.ToString(), + desc.ExampleToString(), + ) + + properties := map[string]string{} + vc.FlagSetGroup.InFlagSet("Properties", func(set *pflag.FlagSet) { + set.StringToStringVarP(&properties, "properties", "p", nil, "comma separated key=value pairs") + _ = cobra.MarkFlagRequired(set, "properties") + }) + + vc.SetRunFuncWithNameArg(func() error { + return doSetProperties(vc, properties) + }, "the namespace name is not specified or the namespace name is specified more than one") +} + +func doSetProperties(vc *cmdutils.VerbCmd, properties map[string]string) error { + ns, err := utils.GetNamespaceName(vc.NameArg) + if err != nil { + return err + } + err = updateNamespaceProperties(*ns, properties) + if err == nil { + vc.Command.Printf("Updated properties successfully for [%s]\n", ns.String()) + } + return err +} + +func ClearPropertiesCmd(vc *cmdutils.VerbCmd) { + var desc cmdutils.LongDescription + desc.CommandUsedFor = "Clear properties of a namespace" + desc.CommandPermission = "This command requires tenant admin permissions." + + var examples []cmdutils.Example + examples = append(examples, cmdutils.Example{ + Desc: "Clear properties of a namespace", + Command: "pulsarctl namespaces clear-properties tenant/namespace", + }) + desc.CommandExamples = examples + desc.CommandOutput = append(desc.CommandOutput, ArgError, NsNotExistError) + desc.CommandOutput = append(desc.CommandOutput, NsErrors...) + + vc.SetDescription( + "clear-properties", + "Clear properties of a namespace", + desc.ToString(), + desc.ExampleToString(), + ) + vc.SetRunFuncWithNameArg(func() error { + return doClearProperties(vc) + }, "the namespace name is not specified or the namespace name is specified more than one") +} + +func doClearProperties(vc *cmdutils.VerbCmd) error { + ns, err := utils.GetNamespaceName(vc.NameArg) + if err != nil { + return err + } + err = removeNamespaceProperties(*ns) + if err == nil { + vc.Command.Printf("Cleared properties successfully for [%s]\n", ns.String()) + } + return err +} + +func GetPropertyCmd(vc *cmdutils.VerbCmd) { + var desc cmdutils.LongDescription + desc.CommandUsedFor = "Get a single property of a namespace" + desc.CommandPermission = "This command requires tenant admin permissions." + + var examples []cmdutils.Example + examples = append(examples, cmdutils.Example{ + Desc: "Get a single property of a namespace", + Command: "pulsarctl namespaces get-property tenant/namespace -k key", + }) + desc.CommandExamples = examples + desc.CommandOutput = append(desc.CommandOutput, ArgError, NsNotExistError) + desc.CommandOutput = append(desc.CommandOutput, NsErrors...) + + vc.SetDescription( + "get-property", + "Get a single property of a namespace", + desc.ToString(), + desc.ExampleToString(), + ) + + var key string + vc.FlagSetGroup.InFlagSet("Properties", func(set *pflag.FlagSet) { + set.StringVarP(&key, "key", "k", "", "property key") + _ = cobra.MarkFlagRequired(set, "key") + }) + vc.EnableOutputFlagSet() + + vc.SetRunFuncWithNameArg(func() error { + return doGetProperty(vc, key) + }, "the namespace name is not specified or the namespace name is specified more than one") +} + +func doGetProperty(vc *cmdutils.VerbCmd, key string) error { + _, properties, err := getNamespaceProperties(vc.NameArg) + if err != nil { + return err + } + value, ok := properties[key] + if !ok { + return writeNullablePropertyValue(vc, key, nil) + } + return writeNullablePropertyValue(vc, key, &value) +} + +func SetPropertyCmd(vc *cmdutils.VerbCmd) { + var desc cmdutils.LongDescription + desc.CommandUsedFor = "Set a single property of a namespace" + desc.CommandPermission = "This command requires tenant admin permissions." + + var examples []cmdutils.Example + examples = append(examples, cmdutils.Example{ + Desc: "Set a single property of a namespace", + Command: "pulsarctl namespaces set-property tenant/namespace -k key -v value", + }) + desc.CommandExamples = examples + desc.CommandOutput = append(desc.CommandOutput, ArgError, NsNotExistError) + desc.CommandOutput = append(desc.CommandOutput, NsErrors...) + + vc.SetDescription( + "set-property", + "Set a single property of a namespace", + desc.ToString(), + desc.ExampleToString(), + ) + + var key string + var value string + vc.FlagSetGroup.InFlagSet("Properties", func(set *pflag.FlagSet) { + set.StringVarP(&key, "key", "k", "", "property key") + set.StringVarP(&value, "value", "v", "", "property value") + _ = cobra.MarkFlagRequired(set, "key") + _ = cobra.MarkFlagRequired(set, "value") + }) + + vc.SetRunFuncWithNameArg(func() error { + return doSetProperty(vc, key, value) + }, "the namespace name is not specified or the namespace name is specified more than one") +} + +func doSetProperty(vc *cmdutils.VerbCmd, key, value string) error { + ns, properties, err := getNamespaceProperties(vc.NameArg) + if err != nil { + return err + } + properties[key] = value + err = updateNamespaceProperties(ns, properties) + if err == nil { + vc.Command.Printf("Set property %q successfully for [%s]\n", key, ns.String()) + } + return err +} + +func RemovePropertyCmd(vc *cmdutils.VerbCmd) { + var desc cmdutils.LongDescription + desc.CommandUsedFor = "Remove a single property of a namespace" + desc.CommandPermission = "This command requires tenant admin permissions." + + var examples []cmdutils.Example + examples = append(examples, cmdutils.Example{ + Desc: "Remove a single property of a namespace", + Command: "pulsarctl namespaces remove-property tenant/namespace -k key", + }) + desc.CommandExamples = examples + desc.CommandOutput = append(desc.CommandOutput, ArgError, NsNotExistError) + desc.CommandOutput = append(desc.CommandOutput, NsErrors...) + + vc.SetDescription( + "remove-property", + "Remove a single property of a namespace", + desc.ToString(), + desc.ExampleToString(), + ) + + var key string + vc.FlagSetGroup.InFlagSet("Properties", func(set *pflag.FlagSet) { + set.StringVarP(&key, "key", "k", "", "property key") + _ = cobra.MarkFlagRequired(set, "key") + }) + vc.EnableOutputFlagSet() + + vc.SetRunFuncWithNameArg(func() error { + return doRemoveProperty(vc, key) + }, "the namespace name is not specified or the namespace name is specified more than one") +} + +func doRemoveProperty(vc *cmdutils.VerbCmd, key string) error { + ns, properties, err := getNamespaceProperties(vc.NameArg) + if err != nil { + return err + } + value, ok := properties[key] + if !ok { + return writeNullablePropertyValue(vc, key, nil) + } + delete(properties, key) + + if len(properties) == 0 { + err = removeNamespaceProperties(ns) + } else { + err = updateNamespaceProperties(ns, properties) + } + if err == nil { + return writeNullablePropertyValue(vc, key, &value) + } + return err +} + +func writeNullablePropertyValue(vc *cmdutils.VerbCmd, key string, value *string) error { + var obj interface{} + if value != nil { + obj = map[string]string{key: *value} + } + + oc := cmdutils.NewOutputContent(). + WithObject(obj). + WithTextFunc(func(w io.Writer) error { + if value == nil { + _, err := io.WriteString(w, "null\n") + return err + } + _, err := fmt.Fprintln(w, *value) + return err + }) + + return vc.OutputConfig.WriteOutput(vc.Command.OutOrStdout(), oc) +} diff --git a/pkg/ctl/namespace/properties_test.go b/pkg/ctl/namespace/properties_test.go new file mode 100644 index 000000000..6c4555948 --- /dev/null +++ b/pkg/ctl/namespace/properties_test.go @@ -0,0 +1,105 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 namespace + +import ( + "encoding/json" + "fmt" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestNamespacePropertiesCmd(t *testing.T) { + ns := "public/test-namespace-properties" + args := []string{"create", ns} + _, execErr, _, _ := TestNamespaceCommands(createNs, args) + assert.Nil(t, execErr) + + args = []string{"clear-properties", ns} + _, execErr, _, _ = TestNamespaceCommands(ClearPropertiesCmd, args) + assert.Nil(t, execErr) + + args = []string{"set-properties", ns, "-p", "k1=v1,k2=v2"} + setPropertiesOut, execErr, _, _ := TestNamespaceCommands(SetPropertiesCmd, args) + assert.Nil(t, execErr) + assert.Equal(t, fmt.Sprintf("Updated properties successfully for [%s]\n", ns), setPropertiesOut.String()) + + args = []string{"get-properties", ns} + getPropertiesOut, execErr, _, _ := TestNamespaceCommands(GetPropertiesCmd, args) + assert.Nil(t, execErr) + properties := map[string]string{} + err := json.Unmarshal(getPropertiesOut.Bytes(), &properties) + assert.Nil(t, err) + assert.Equal(t, map[string]string{"k1": "v1", "k2": "v2"}, properties) + + args = []string{"get-property", ns, "-k", "k1"} + getPropertyOut, execErr, _, _ := TestNamespaceCommands(GetPropertyCmd, args) + assert.Nil(t, execErr) + assert.Equal(t, "v1\n", getPropertyOut.String()) + + args = []string{"set-property", ns, "-k", "k3", "-v", "v3"} + setPropertyOut, execErr, _, _ := TestNamespaceCommands(SetPropertyCmd, args) + assert.Nil(t, execErr) + assert.Equal(t, fmt.Sprintf("Set property %q successfully for [%s]\n", "k3", ns), setPropertyOut.String()) + + args = []string{"get-properties", ns} + getPropertiesOut, execErr, _, _ = TestNamespaceCommands(GetPropertiesCmd, args) + assert.Nil(t, execErr) + properties = map[string]string{} + err = json.Unmarshal(getPropertiesOut.Bytes(), &properties) + assert.Nil(t, err) + assert.Equal(t, map[string]string{"k1": "v1", "k2": "v2", "k3": "v3"}, properties) + + args = []string{"remove-property", ns, "-k", "k2"} + removePropertyOut, execErr, _, _ := TestNamespaceCommands(RemovePropertyCmd, args) + assert.Nil(t, execErr) + assert.Equal(t, "v2\n", removePropertyOut.String()) + + args = []string{"remove-property", ns, "-k", "k1"} + _, execErr, _, _ = TestNamespaceCommands(RemovePropertyCmd, args) + assert.Nil(t, execErr) + + args = []string{"remove-property", ns, "-k", "k3"} + _, execErr, _, _ = TestNamespaceCommands(RemovePropertyCmd, args) + assert.Nil(t, execErr) + + args = []string{"get-properties", ns} + getPropertiesOut, execErr, _, _ = TestNamespaceCommands(GetPropertiesCmd, args) + assert.Nil(t, execErr) + properties = map[string]string{} + err = json.Unmarshal(getPropertiesOut.Bytes(), &properties) + assert.Nil(t, err) + assert.Equal(t, map[string]string{}, properties) +} + +func TestGetMissingPropertyCmd(t *testing.T) { + ns := "public/test-missing-namespace-property" + args := []string{"create", ns} + _, execErr, _, _ := TestNamespaceCommands(createNs, args) + assert.Nil(t, execErr) + + args = []string{"clear-properties", ns} + _, execErr, _, _ = TestNamespaceCommands(ClearPropertiesCmd, args) + assert.Nil(t, execErr) + + args = []string{"get-property", ns, "-k", "missing"} + out, execErr, _, _ := TestNamespaceCommands(GetPropertyCmd, args) + assert.Nil(t, execErr) + assert.Equal(t, "null\n", out.String()) +} diff --git a/pkg/ctl/namespace/remove_settings.go b/pkg/ctl/namespace/remove_settings.go new file mode 100644 index 000000000..289bc4861 --- /dev/null +++ b/pkg/ctl/namespace/remove_settings.go @@ -0,0 +1,136 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 namespace + +import ( + "github.com/apache/pulsar-client-go/pulsaradmin/pkg/utils" + + "github.com/streamnative/pulsarctl/pkg/cmdutils" +) + +func newNamespaceRemoveCmd(use, short string, run func(*cmdutils.VerbCmd) error) func(*cmdutils.VerbCmd) { + return func(vc *cmdutils.VerbCmd) { + var desc cmdutils.LongDescription + desc.CommandUsedFor = short + desc.CommandPermission = "This command requires tenant admin permissions." + desc.CommandOutput = append(desc.CommandOutput, ArgError) + desc.CommandOutput = append(desc.CommandOutput, NsErrors...) + desc.CommandOutput = append(desc.CommandOutput, NsNotExistError) + + vc.SetDescription(use, short, desc.ToString(), desc.ExampleToString()) + vc.SetRunFuncWithNameArg(func() error { + return run(vc) + }, "the namespace name is not specified or the namespace name is specified more than one") + } +} + +var removeMessageTTL = newNamespaceRemoveCmd( + "remove-message-ttl", + "Remove message TTL for a namespace", + func(vc *cmdutils.VerbCmd) error { + admin := cmdutils.NewPulsarClient() + err := admin.Namespaces().RemoveNamespaceMessageTTL(vc.NameArg) + if err == nil { + vc.Command.Printf("Removed message TTL successfully for [%s]\n", vc.NameArg) + } + return err + }, +) + +var removeRetention = newNamespaceRemoveCmd( + "remove-retention", + "Remove retention for a namespace", + func(vc *cmdutils.VerbCmd) error { + admin := cmdutils.NewPulsarClient() + err := admin.Namespaces().RemoveRetention(vc.NameArg) + if err == nil { + vc.Command.Printf("Removed retention successfully for [%s]\n", vc.NameArg) + } + return err + }, +) + +var removePersistence = newNamespaceRemoveCmd( + "remove-persistence", + "Remove persistence for a namespace", + func(vc *cmdutils.VerbCmd) error { + admin := cmdutils.NewPulsarClient() + err := admin.Namespaces().RemovePersistence(vc.NameArg) + if err == nil { + vc.Command.Printf("Removed persistence successfully for [%s]\n", vc.NameArg) + } + return err + }, +) + +func removeMaxConsumersPerSubscription(vc *cmdutils.VerbCmd) error { + ns, err := utils.GetNamespaceName(vc.NameArg) + if err != nil { + return err + } + admin := cmdutils.NewPulsarClient() + err = admin.Namespaces().RemoveMaxConsumersPerSubscription(*ns) + if err == nil { + vc.Command.Printf("Removed max consumers per subscription successfully for [%s]\n", ns.String()) + } + return err +} + +var RemoveMaxConsumersPerSubscriptionCmd = newNamespaceRemoveCmd( + "remove-max-consumers-per-subscription", + "Remove the max consumers per subscription of a namespace", + removeMaxConsumersPerSubscription, +) + +func removeMaxConsumersPerTopic(vc *cmdutils.VerbCmd) error { + ns, err := utils.GetNamespaceName(vc.NameArg) + if err != nil { + return err + } + admin := cmdutils.NewPulsarClient() + err = admin.Namespaces().RemoveMaxConsumersPerTopic(*ns) + if err == nil { + vc.Command.Printf("Removed max consumers per topic successfully for [%s]\n", ns.String()) + } + return err +} + +var RemoveMaxConsumersPerTopicCmd = newNamespaceRemoveCmd( + "remove-max-consumers-per-topic", + "Remove the max consumers per topic of a namespace", + removeMaxConsumersPerTopic, +) + +func removeMaxProducersPerTopic(vc *cmdutils.VerbCmd) error { + ns, err := utils.GetNamespaceName(vc.NameArg) + if err != nil { + return err + } + admin := cmdutils.NewPulsarClient() + err = admin.Namespaces().RemoveMaxProducersPerTopic(*ns) + if err == nil { + vc.Command.Printf("Removed max producers per topic successfully for [%s]\n", ns.String()) + } + return err +} + +var RemoveMaxProducersPerTopicCmd = newNamespaceRemoveCmd( + "remove-max-producers-per-topic", + "Remove the max producers per topic of a namespace", + removeMaxProducersPerTopic, +) diff --git a/pkg/ctl/namespace/remove_settings_test.go b/pkg/ctl/namespace/remove_settings_test.go new file mode 100644 index 000000000..eeaada43e --- /dev/null +++ b/pkg/ctl/namespace/remove_settings_test.go @@ -0,0 +1,169 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 namespace + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestRemoveMessageTTLCmd(t *testing.T) { + ns := "public/test-remove-message-ttl" + args := []string{"create", ns} + _, execErr, _, _ := TestNamespaceCommands(createNs, args) + assert.Nil(t, execErr) + + args = []string{"get-message-ttl", ns} + initialOut, execErr, _, _ := TestNamespaceCommands(getMessageTTL, args) + assert.Nil(t, execErr) + + args = []string{"set-message-ttl", ns, "-t", "123"} + _, execErr, _, _ = TestNamespaceCommands(setMessageTTL, args) + assert.Nil(t, execErr) + + args = []string{"remove-message-ttl", ns} + removeOut, execErr, _, _ := TestNamespaceCommands(removeMessageTTL, args) + assert.Nil(t, execErr) + assert.Equal(t, fmt.Sprintf("Removed message TTL successfully for [%s]\n", ns), removeOut.String()) + + args = []string{"get-message-ttl", ns} + currentOut, execErr, _, _ := TestNamespaceCommands(getMessageTTL, args) + assert.Nil(t, execErr) + assert.Equal(t, initialOut.String(), currentOut.String()) +} + +func TestRemoveRetentionCmd(t *testing.T) { + ns := "public/test-remove-retention" + args := []string{"create", ns} + _, execErr, _, _ := TestNamespaceCommands(createNs, args) + assert.Nil(t, execErr) + + args = []string{"get-retention", ns} + initialOut, execErr, _, _ := TestNamespaceCommands(getRetention, args) + assert.Nil(t, execErr) + + args = []string{"set-retention", ns, "--time", "10m", "--size", "10M"} + _, execErr, _, _ = TestNamespaceCommands(setRetention, args) + assert.Nil(t, execErr) + + args = []string{"remove-retention", ns} + removeOut, execErr, _, _ := TestNamespaceCommands(removeRetention, args) + assert.Nil(t, execErr) + assert.Equal(t, fmt.Sprintf("Removed retention successfully for [%s]\n", ns), removeOut.String()) + + args = []string{"get-retention", ns} + currentOut, execErr, _, _ := TestNamespaceCommands(getRetention, args) + assert.Nil(t, execErr) + assert.Equal(t, initialOut.String(), currentOut.String()) +} + +func TestRemovePersistenceCmd(t *testing.T) { + ns := "public/test-remove-persistence" + args := []string{"create", ns} + _, execErr, _, _ := TestNamespaceCommands(createNs, args) + assert.Nil(t, execErr) + + args = []string{"get-persistence", ns} + initialOut, execErr, _, _ := TestNamespaceCommands(getPersistence, args) + assert.Nil(t, execErr) + + args = []string{"set-persistence", ns, + "--ensemble-size", "2", + "--write-quorum-size", "2", + "--ack-quorum-size", "2", + "--ml-mark-delete-max-rate", "1.5", + } + _, execErr, _, _ = TestNamespaceCommands(setPersistence, args) + assert.Nil(t, execErr) + + args = []string{"remove-persistence", ns} + removeOut, execErr, _, _ := TestNamespaceCommands(removePersistence, args) + assert.Nil(t, execErr) + assert.Equal(t, fmt.Sprintf("Removed persistence successfully for [%s]\n", ns), removeOut.String()) + + args = []string{"get-persistence", ns} + currentOut, execErr, _, _ := TestNamespaceCommands(getPersistence, args) + assert.Nil(t, execErr) + assert.Equal(t, initialOut.String(), currentOut.String()) +} + +func TestRemoveMaxConsumersAndProducersCmds(t *testing.T) { + ns := "public/test-remove-max-consumers-and-producers" + args := []string{"create", ns} + _, execErr, _, _ := TestNamespaceCommands(createNs, args) + assert.Nil(t, execErr) + + args = []string{"get-max-consumers-per-topic", ns} + initialConsumersPerTopicOut, execErr, _, _ := TestNamespaceCommands(GetMaxConsumersPerTopicCmd, args) + assert.Nil(t, execErr) + + args = []string{"set-max-consumers-per-topic", "--size", "10", ns} + _, execErr, _, _ = TestNamespaceCommands(SetMaxConsumersPerTopicCmd, args) + assert.Nil(t, execErr) + + args = []string{"remove-max-consumers-per-topic", ns} + removeConsumersPerTopicOut, execErr, _, _ := TestNamespaceCommands(RemoveMaxConsumersPerTopicCmd, args) + assert.Nil(t, execErr) + assert.Equal(t, fmt.Sprintf("Removed max consumers per topic successfully for [%s]\n", ns), + removeConsumersPerTopicOut.String()) + + args = []string{"get-max-consumers-per-topic", ns} + currentConsumersPerTopicOut, execErr, _, _ := TestNamespaceCommands(GetMaxConsumersPerTopicCmd, args) + assert.Nil(t, execErr) + assert.Equal(t, initialConsumersPerTopicOut.String(), currentConsumersPerTopicOut.String()) + + args = []string{"get-max-consumers-per-subscription", ns} + initialConsumersPerSubOut, execErr, _, _ := TestNamespaceCommands(GetMaxConsumersPerSubscriptionCmd, args) + assert.Nil(t, execErr) + + args = []string{"set-max-consumers-per-subscription", "--size", "9", ns} + _, execErr, _, _ = TestNamespaceCommands(SetMaxConsumersPerSubscriptionCmd, args) + assert.Nil(t, execErr) + + args = []string{"remove-max-consumers-per-subscription", ns} + removeConsumersPerSubOut, execErr, _, _ := TestNamespaceCommands(RemoveMaxConsumersPerSubscriptionCmd, args) + assert.Nil(t, execErr) + assert.Equal(t, fmt.Sprintf("Removed max consumers per subscription successfully for [%s]\n", ns), + removeConsumersPerSubOut.String()) + + args = []string{"get-max-consumers-per-subscription", ns} + currentConsumersPerSubOut, execErr, _, _ := TestNamespaceCommands(GetMaxConsumersPerSubscriptionCmd, args) + assert.Nil(t, execErr) + assert.Equal(t, initialConsumersPerSubOut.String(), currentConsumersPerSubOut.String()) + + args = []string{"get-max-producers-per-topic", ns} + initialProducersPerTopicOut, execErr, _, _ := TestNamespaceCommands(GetMaxProducersPerTopicCmd, args) + assert.Nil(t, execErr) + + args = []string{"set-max-producers-per-topic", "--size", "8", ns} + _, execErr, _, _ = TestNamespaceCommands(SetMaxProducersPerTopicCmd, args) + assert.Nil(t, execErr) + + args = []string{"remove-max-producers-per-topic", ns} + removeProducersPerTopicOut, execErr, _, _ := TestNamespaceCommands(RemoveMaxProducersPerTopicCmd, args) + assert.Nil(t, execErr) + assert.Equal(t, fmt.Sprintf("Removed max producers per topic successfully for [%s]\n", ns), + removeProducersPerTopicOut.String()) + + args = []string{"get-max-producers-per-topic", ns} + currentProducersPerTopicOut, execErr, _, _ := TestNamespaceCommands(GetMaxProducersPerTopicCmd, args) + assert.Nil(t, execErr) + assert.Equal(t, initialProducersPerTopicOut.String(), currentProducersPerTopicOut.String()) +} diff --git a/pkg/ctl/namespace/schema_compatibility_strategy.go b/pkg/ctl/namespace/schema_compatibility_strategy.go new file mode 100644 index 000000000..050de0fd9 --- /dev/null +++ b/pkg/ctl/namespace/schema_compatibility_strategy.go @@ -0,0 +1,139 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 namespace + +import ( + "strings" + + "github.com/apache/pulsar-client-go/pulsaradmin/pkg/utils" + "github.com/spf13/cobra" + "github.com/spf13/pflag" + + "github.com/streamnative/pulsarctl/pkg/cmdutils" +) + +func GetSchemaCompatibilityStrategyCmd(vc *cmdutils.VerbCmd) { + var desc cmdutils.LongDescription + desc.CommandUsedFor = "This command is used for getting the schema compatibility strategy of a namespace." + desc.CommandPermission = "This command requires super-user permissions and broker has write policies permission." + + var examples []cmdutils.Example + get := cmdutils.Example{ + Desc: "Get the schema compatibility strategy of the namespace (namespace-name)", + Command: "pulsarctl namespaces get-schema-compatibility-strategy (namespace-name)", + } + examples = append(examples, get) + desc.CommandExamples = examples + + var out []cmdutils.Output + successOut := cmdutils.Output{ + Desc: "normal output", + Out: "The schema compatibility strategy of the namespace (namespace-name) is (strategy)", + } + out = append(out, successOut, ArgError, NsNotExistError) + out = append(out, NsErrors...) + desc.CommandOutput = out + + vc.SetDescription( + "get-schema-compatibility-strategy", + "Get the schema compatibility strategy of a namespace", + desc.ToString(), + desc.ExampleToString(), + ) + + vc.SetRunFuncWithNameArg(func() error { + return doGetSchemaCompatibilityStrategy(vc) + }, "the namespace name is not specified or the namespace name is specified more than one") +} + +func doGetSchemaCompatibilityStrategy(vc *cmdutils.VerbCmd) error { + ns, err := utils.GetNamespaceName(vc.NameArg) + if err != nil { + return err + } + + admin := cmdutils.NewPulsarClient() + s, err := admin.Namespaces().GetSchemaCompatibilityStrategy(*ns) + if err == nil { + vc.Command.Printf("The schema compatibility strategy of the namespace %s is %s\n", ns.String(), s.String()) + } + return err +} + +func SetSchemaCompatibilityStrategyCmd(vc *cmdutils.VerbCmd) { + var desc cmdutils.LongDescription + desc.CommandUsedFor = "This command is used for setting the schema compatibility strategy of a namespace." + desc.CommandPermission = "This command requires super-user permissions and broker has write policies permission." + + var examples []cmdutils.Example + set := cmdutils.Example{ + Desc: "Set the schema compatibility strategy to (strategy)", + Command: "pulsarctl namespaces set-schema-compatibility-strategy --compatibility (strategy) (namespace-name)", + } + examples = append(examples, set) + desc.CommandExamples = examples + + var out []cmdutils.Output + successOut := cmdutils.Output{ + Desc: "normal output", + Out: "Successfully set the schema compatibility strategy of the namespace (namespace-name) to (strategy)", + } + out = append(out, successOut, ArgError, NsNotExistError) + out = append(out, NsErrors...) + desc.CommandOutput = out + + vc.SetDescription( + "set-schema-compatibility-strategy", + "Set the schema compatibility strategy of a namespace", + desc.ToString(), + desc.ExampleToString(), + ) + + var strategy string + vc.FlagSetGroup.InFlagSet("Schema Compatibility Strategy", func(set *pflag.FlagSet) { + set.StringVarP(&strategy, "compatibility", "c", "", + "Compatibility level required for new schemas created via a Producer. Possible values "+ + "(UNDEFINED, ALWAYS_INCOMPATIBLE, ALWAYS_COMPATIBLE, BACKWARD, FORWARD, FULL, "+ + "BACKWARD_TRANSITIVE, FORWARD_TRANSITIVE, FULL_TRANSITIVE)") + _ = cobra.MarkFlagRequired(set, "compatibility") + }) + + vc.SetRunFuncWithNameArg(func() error { + return doSetSchemaCompatibilityStrategy(vc, strategy) + }, "the namespace name is not specified or the namespace name is specified more than one") +} + +func doSetSchemaCompatibilityStrategy(vc *cmdutils.VerbCmd, strategy string) error { + ns, err := utils.GetNamespaceName(vc.NameArg) + if err != nil { + return err + } + + s, err := utils.ParseSchemaCompatibilityStrategy(strings.ToUpper(strategy)) + if err != nil { + return err + } + + admin := cmdutils.NewPulsarClient() + err = admin.Namespaces().SetSchemaCompatibilityStrategy(*ns, s) + if err == nil { + vc.Command.Printf("Successfully set the schema compatibility strategy of the namespace %s to %s\n", + ns.String(), s.String()) + } + return err +} diff --git a/pkg/ctl/namespace/schema_compatibility_strategy_test.go b/pkg/ctl/namespace/schema_compatibility_strategy_test.go new file mode 100644 index 000000000..a25940b09 --- /dev/null +++ b/pkg/ctl/namespace/schema_compatibility_strategy_test.go @@ -0,0 +1,75 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 namespace + +import ( + "bytes" + "fmt" + "testing" + + "github.com/apache/pulsar-client-go/pulsaradmin/pkg/utils" + "github.com/stretchr/testify/assert" +) + +func TestSchemaCompatibilityStrategyCmd(t *testing.T) { + ns := "public/test-schema-compatibility-strategy" + args := []string{"create", ns} + _, execErr, _, _ := TestNamespaceCommands(createNs, args) + assert.Nil(t, execErr) + + args = []string{"get-schema-compatibility-strategy", ns} + initialOut, execErr, _, _ := TestNamespaceCommands(GetSchemaCompatibilityStrategyCmd, args) + assert.Nil(t, execErr) + assert.Contains(t, initialOut.String(), ns) + + var setOut, getOut *bytes.Buffer + args = []string{"set-schema-compatibility-strategy", "--compatibility", "FULL_TRANSITIVE", ns} + setOut, execErr, _, _ = TestNamespaceCommands(SetSchemaCompatibilityStrategyCmd, args) + assert.Nil(t, execErr) + assert.Equal(t, + fmt.Sprintf("Successfully set the schema compatibility strategy of the namespace %s to %s\n", + ns, utils.SchemaCompatibilityStrategyFullTransitive.String()), + setOut.String()) + + args = []string{"get-schema-compatibility-strategy", ns} + getOut, execErr, _, _ = TestNamespaceCommands(GetSchemaCompatibilityStrategyCmd, args) + assert.Nil(t, execErr) + assert.Equal(t, + fmt.Sprintf("The schema compatibility strategy of the namespace %s is %s\n", + ns, utils.SchemaCompatibilityStrategyFullTransitive.String()), + getOut.String()) + + args = []string{"set-schema-compatibility-strategy", ns} + _, execErr, _, _ = TestNamespaceCommands(SetSchemaCompatibilityStrategyCmd, args) + assert.NotNil(t, execErr) + assert.Contains(t, execErr.Error(), "required flag(s) \"compatibility\" not set") + + args = []string{"set-schema-compatibility-strategy", "--compatibility", "INVALID", ns} + _, execErr, _, _ = TestNamespaceCommands(SetSchemaCompatibilityStrategyCmd, args) + assert.NotNil(t, execErr) + assert.Equal(t, "Invalid schema compatibility strategy INVALID", execErr.Error()) + + args = []string{"get-schema-compatibility-strategy", ns} + getOut, execErr, _, _ = TestNamespaceCommands(GetSchemaCompatibilityStrategyCmd, args) + assert.Nil(t, execErr) + assert.NotEqual(t, initialOut.String(), "") + assert.Equal(t, + fmt.Sprintf("The schema compatibility strategy of the namespace %s is %s\n", + ns, utils.SchemaCompatibilityStrategyFullTransitive.String()), + getOut.String()) +} diff --git a/pkg/ctl/namespace/subscription_expiration_time.go b/pkg/ctl/namespace/subscription_expiration_time.go new file mode 100644 index 000000000..2d7e091bf --- /dev/null +++ b/pkg/ctl/namespace/subscription_expiration_time.go @@ -0,0 +1,188 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 namespace + +import ( + "github.com/apache/pulsar-client-go/pulsaradmin/pkg/utils" + "github.com/pkg/errors" + "github.com/spf13/cobra" + "github.com/spf13/pflag" + + "github.com/streamnative/pulsarctl/pkg/cmdutils" +) + +func GetSubscriptionExpirationTimeCmd(vc *cmdutils.VerbCmd) { + var desc cmdutils.LongDescription + desc.CommandUsedFor = "This command is used for getting the subscription expiration time of a namespace." + desc.CommandPermission = "This command requires tenant admin permissions." + + var examples []cmdutils.Example + get := cmdutils.Example{ + Desc: "Get the subscription expiration time of the namespace (namespace-name)", + Command: "pulsarctl namespaces get-subscription-expiration-time (namespace-name)", + } + examples = append(examples, get) + desc.CommandExamples = examples + + var out []cmdutils.Output + successOut := cmdutils.Output{ + Desc: "normal output", + Out: "The subscription expiration time of the namespace (namespace-name) is (time)", + } + out = append(out, successOut, ArgError, NsNotExistError) + out = append(out, NsErrors...) + desc.CommandOutput = out + + vc.SetDescription( + "get-subscription-expiration-time", + "Get the subscription expiration time of a namespace", + desc.ToString(), + desc.ExampleToString(), + ) + + vc.SetRunFuncWithNameArg(func() error { + return doGetSubscriptionExpirationTime(vc) + }, "the namespace name is not specified or the namespace name is specified more than one") +} + +func doGetSubscriptionExpirationTime(vc *cmdutils.VerbCmd) error { + ns, err := utils.GetNamespaceName(vc.NameArg) + if err != nil { + return err + } + + admin := cmdutils.NewPulsarClient() + expirationTime, err := admin.Namespaces().GetSubscriptionExpirationTime(*ns) + if err == nil { + if expirationTime == -1 { + vc.Command.Printf("The subscription expiration time of the namespace %s is not set\n", ns.String()) + } else { + vc.Command.Printf("The subscription expiration time of the namespace %s is %d\n", ns.String(), expirationTime) + } + } + return err +} + +func SetSubscriptionExpirationTimeCmd(vc *cmdutils.VerbCmd) { + var desc cmdutils.LongDescription + desc.CommandUsedFor = "This command is used for setting the subscription expiration time of a namespace." + desc.CommandPermission = "This command requires super-user permissions and broker has write policies permission." + + var examples []cmdutils.Example + set := cmdutils.Example{ + Desc: "Set the subscription expiration time of the namespace (namespace-name) to (time)", + Command: "pulsarctl namespaces set-subscription-expiration-time --time (time) (namespace-name)", + } + examples = append(examples, set) + desc.CommandExamples = examples + + var out []cmdutils.Output + successOut := cmdutils.Output{ + Desc: "normal output", + Out: "Successfully set the subscription expiration time of the namespace (namespace-name) to (time)", + } + out = append(out, successOut, ArgError, NsNotExistError) + out = append(out, NsErrors...) + desc.CommandOutput = out + + vc.SetDescription( + "set-subscription-expiration-time", + "Set the subscription expiration time of a namespace", + desc.ToString(), + desc.ExampleToString(), + ) + + var expirationTime int + vc.FlagSetGroup.InFlagSet("Subscription Expiration Time", func(set *pflag.FlagSet) { + set.IntVarP(&expirationTime, "time", "t", -1, "subscription expiration time in minutes") + _ = cobra.MarkFlagRequired(set, "time") + }) + + vc.SetRunFuncWithNameArg(func() error { + return doSetSubscriptionExpirationTime(vc, expirationTime) + }, "the namespace name is not specified or the namespace name is specified more than one") + + vc.EnableOutputFlagSet() +} + +func doSetSubscriptionExpirationTime(vc *cmdutils.VerbCmd, expirationTime int) error { + ns, err := utils.GetNamespaceName(vc.NameArg) + if err != nil { + return err + } + + if expirationTime < 0 { + return errors.New("the specified subscription expiration time must bigger than or equal to 0") + } + + admin := cmdutils.NewPulsarClient() + err = admin.Namespaces().SetSubscriptionExpirationTime(*ns, expirationTime) + if err == nil { + vc.Command.Printf("Successfully set the subscription expiration time of the namespace %s to %d\n", + ns.String(), expirationTime) + } + return err +} + +func RemoveSubscriptionExpirationTimeCmd(vc *cmdutils.VerbCmd) { + var desc cmdutils.LongDescription + desc.CommandUsedFor = "This command is used for removing the subscription expiration time of a namespace." + desc.CommandPermission = "This command requires tenant admin permissions." + + var examples []cmdutils.Example + remove := cmdutils.Example{ + Desc: "Remove the subscription expiration time of the namespace (namespace-name)", + Command: "pulsarctl namespaces remove-subscription-expiration-time (namespace-name)", + } + examples = append(examples, remove) + desc.CommandExamples = examples + + var out []cmdutils.Output + successOut := cmdutils.Output{ + Desc: "normal output", + Out: "Successfully removed the subscription expiration time of the namespace (namespace-name)", + } + out = append(out, successOut, ArgError, NsNotExistError) + out = append(out, NsErrors...) + desc.CommandOutput = out + + vc.SetDescription( + "remove-subscription-expiration-time", + "Remove the subscription expiration time of a namespace", + desc.ToString(), + desc.ExampleToString(), + ) + + vc.SetRunFuncWithNameArg(func() error { + return doRemoveSubscriptionExpirationTime(vc) + }, "the namespace name is not specified or the namespace name is specified more than one") +} + +func doRemoveSubscriptionExpirationTime(vc *cmdutils.VerbCmd) error { + ns, err := utils.GetNamespaceName(vc.NameArg) + if err != nil { + return err + } + + admin := cmdutils.NewPulsarClient() + err = admin.Namespaces().RemoveSubscriptionExpirationTime(*ns) + if err == nil { + vc.Command.Printf("Successfully removed the subscription expiration time of the namespace %s\n", ns.String()) + } + return err +} diff --git a/pkg/ctl/namespace/subscription_expiration_time_test.go b/pkg/ctl/namespace/subscription_expiration_time_test.go new file mode 100644 index 000000000..97bf59e43 --- /dev/null +++ b/pkg/ctl/namespace/subscription_expiration_time_test.go @@ -0,0 +1,69 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 namespace + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestSubscriptionExpirationTimeCmd(t *testing.T) { + ns := "public/test-subscription-expiration-time" + args := []string{"create", ns} + _, execErr, _, _ := TestNamespaceCommands(createNs, args) + assert.Nil(t, execErr) + + args = []string{"get-subscription-expiration-time", ns} + initialOut, execErr, _, _ := TestNamespaceCommands(GetSubscriptionExpirationTimeCmd, args) + assert.Nil(t, execErr) + + args = []string{"set-subscription-expiration-time", "--time", "60", ns} + setOut, execErr, _, _ := TestNamespaceCommands(SetSubscriptionExpirationTimeCmd, args) + assert.Nil(t, execErr) + assert.Equal(t, + fmt.Sprintf("Successfully set the subscription expiration time of the namespace %s to %d\n", ns, 60), + setOut.String()) + + args = []string{"get-subscription-expiration-time", ns} + getOut, execErr, _, _ := TestNamespaceCommands(GetSubscriptionExpirationTimeCmd, args) + assert.Nil(t, execErr) + assert.Equal(t, + fmt.Sprintf("The subscription expiration time of the namespace %s is %d\n", ns, 60), + getOut.String()) + + args = []string{"remove-subscription-expiration-time", ns} + removeOut, execErr, _, _ := TestNamespaceCommands(RemoveSubscriptionExpirationTimeCmd, args) + assert.Nil(t, execErr) + assert.Equal(t, + fmt.Sprintf("Successfully removed the subscription expiration time of the namespace %s\n", ns), + removeOut.String()) + + args = []string{"get-subscription-expiration-time", ns} + getOut, execErr, _, _ = TestNamespaceCommands(GetSubscriptionExpirationTimeCmd, args) + assert.Nil(t, execErr) + assert.Equal(t, initialOut.String(), getOut.String()) +} + +func TestSetSubscriptionExpirationTimeWithInvalidValue(t *testing.T) { + args := []string{"set-subscription-expiration-time", "--time", "-1", "public/test-invalid-expiration-time"} + _, execErr, _, _ := TestNamespaceCommands(SetSubscriptionExpirationTimeCmd, args) + assert.NotNil(t, execErr) + assert.Equal(t, "the specified subscription expiration time must bigger than or equal to 0", execErr.Error()) +} diff --git a/pkg/ctl/schemas/compatibility.go b/pkg/ctl/schemas/compatibility.go new file mode 100644 index 000000000..0be2852bb --- /dev/null +++ b/pkg/ctl/schemas/compatibility.go @@ -0,0 +1,83 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 schemas + +import ( + "encoding/json" + "os" + + "github.com/apache/pulsar-client-go/pulsaradmin/pkg/utils" + "github.com/spf13/cobra" + "github.com/spf13/pflag" + + "github.com/streamnative/pulsarctl/pkg/cmdutils" +) + +func testCompatibility(vc *cmdutils.VerbCmd) { + desc := cmdutils.LongDescription{} + desc.CommandUsedFor = "Test schema compatibility" + desc.CommandPermission = "This command requires namespace admin permissions." + + var examples []cmdutils.Example + examples = append(examples, cmdutils.Example{ + Desc: desc.CommandUsedFor, + Command: "pulsarctl schemas compatibility (topic name) --filename (schema file path)", + }) + desc.CommandExamples = examples + + vc.SetDescription( + "compatibility", + desc.CommandUsedFor, + desc.ToString(), + desc.ExampleToString(), + "compatibility", + ) + + schemaData := &utils.SchemaData{} + vc.FlagSetGroup.InFlagSet("SchemaConfig", func(flagSet *pflag.FlagSet) { + flagSet.StringVarP(&schemaData.Filename, "filename", "f", "", "filename") + _ = cobra.MarkFlagRequired(flagSet, "filename") + }) + vc.EnableOutputFlagSet() + vc.SetRunFuncWithNameArg(func() error { + return doTestCompatibility(vc, schemaData) + }, "the topic name is not specified or the topic name is specified more than one") +} + +func doTestCompatibility(vc *cmdutils.VerbCmd, schemaData *utils.SchemaData) error { + var payload utils.PostSchemaPayload + + file, err := os.ReadFile(schemaData.Filename) + if err != nil { + return err + } + if err = json.Unmarshal(file, &payload); err != nil { + return err + } + + admin := cmdutils.NewPulsarClient() + compatibility, err := admin.Schemas().TestCompatibilityWithPostSchemaPayload(vc.NameArg, payload) + if err != nil { + return err + } + + return vc.OutputConfig.WriteOutput( + vc.Command.OutOrStdout(), + cmdutils.NewOutputContent().WithObject(compatibility), + ) +} diff --git a/pkg/ctl/schemas/delete.go b/pkg/ctl/schemas/delete.go index e9255e6e5..3c8fccc8b 100644 --- a/pkg/ctl/schemas/delete.go +++ b/pkg/ctl/schemas/delete.go @@ -18,6 +18,7 @@ package schemas import ( + "github.com/spf13/pflag" "github.com/streamnative/pulsarctl/pkg/cmdutils" ) @@ -31,7 +32,11 @@ func deleteSchema(vc *cmdutils.VerbCmd) { Desc: "Delete the latest schema for a topic", Command: "pulsarctl schemas delete (topic name)", } - examples = append(examples, del) + forceDel := cmdutils.Example{ + Desc: "Delete all schema resources for a topic", + Command: "pulsarctl schemas delete (topic name) --force", + } + examples = append(examples, del, forceDel) desc.CommandExamples = examples var out []cmdutils.Output @@ -54,16 +59,31 @@ func deleteSchema(vc *cmdutils.VerbCmd) { desc.ExampleToString(), "delete", ) + var force bool + vc.FlagSetGroup.InFlagSet("SchemaConfig", func(flagSet *pflag.FlagSet) { + flagSet.BoolVarP( + &force, + "force", + "f", + false, + "delete schema completely", + ) + }) vc.SetRunFuncWithNameArg(func() error { - return doDeleteSchema(vc) + return doDeleteSchema(vc, force) }, "the topic name is not specified or the topic name is specified more than one") } -func doDeleteSchema(vc *cmdutils.VerbCmd) error { +func doDeleteSchema(vc *cmdutils.VerbCmd, force bool) error { topic := vc.NameArg admin := cmdutils.NewPulsarClient() - err := admin.Schemas().DeleteSchema(topic) + var err error + if force { + err = admin.Schemas().ForceDeleteSchema(topic) + } else { + err = admin.Schemas().DeleteSchema(topic) + } if err == nil { vc.Command.Printf("Deleted %s successfully\n", topic) } diff --git a/pkg/ctl/schemas/get.go b/pkg/ctl/schemas/get.go index 2325fa562..bdec682fb 100644 --- a/pkg/ctl/schemas/get.go +++ b/pkg/ctl/schemas/get.go @@ -18,6 +18,7 @@ package schemas import ( + "errors" "io" "github.com/apache/pulsar-client-go/pulsaradmin/pkg/utils" @@ -49,8 +50,13 @@ func getSchema(vc *cmdutils.VerbCmd) { Command: "pulsarctl schemas get (topic name) \n" + "\t--version 2", } + delWithAllVersion := cmdutils.Example{ + Desc: "Get all schema versions for a topic", + Command: "pulsarctl schemas get (topic name) \n" + + "\t--all-version", + } - examples = append(examples, del, delWithVersion) + examples = append(examples, del, delWithVersion, delWithAllVersion) desc.CommandExamples = examples var out []cmdutils.Output @@ -105,9 +111,10 @@ func getSchema(vc *cmdutils.VerbCmd) { ) schemaData := &utils.SchemaData{} + var allVersion bool vc.SetRunFuncWithNameArg(func() error { - return doGetSchema(vc, schemaData) + return doGetSchema(vc, schemaData, allVersion) }, "the topic name is not specified or the topic name is specified more than one") vc.FlagSetGroup.InFlagSet("SchemaConfig", func(flagSet *pflag.FlagSet) { @@ -116,14 +123,30 @@ func getSchema(vc *cmdutils.VerbCmd) { "version", 0, "the schema version info") + flagSet.BoolVarP( + &allVersion, + "all-version", + "a", + false, + "get all schema versions") }) vc.EnableOutputFlagSet() } -func doGetSchema(vc *cmdutils.VerbCmd, schemaData *utils.SchemaData) error { +func doGetSchema(vc *cmdutils.VerbCmd, schemaData *utils.SchemaData, allVersion bool) error { topic := vc.NameArg + if allVersion && vc.Command.Flag("version").Changed { + return errors.New("only one or neither of --version and --all-version can be specified") + } admin := cmdutils.NewPulsarClient() + if allVersion { + schemas, err := admin.Schemas().GetAllSchemas(topic) + if err == nil { + err = vc.OutputConfig.WriteOutput(vc.Command.OutOrStdout(), cmdutils.NewOutputContent().WithObject(schemas)) + } + return err + } if !vc.Command.Flag("version").Changed { schemaInfoWithVersion, err := admin.Schemas().GetSchemaInfoWithVersion(topic) if err == nil { diff --git a/pkg/ctl/schemas/new_commands_test.go b/pkg/ctl/schemas/new_commands_test.go new file mode 100644 index 000000000..60ead183f --- /dev/null +++ b/pkg/ctl/schemas/new_commands_test.go @@ -0,0 +1,142 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 schemas + +import ( + "encoding/json" + "net/http" + "net/http/httptest" + "os" + "path/filepath" + "testing" + + "github.com/apache/pulsar-client-go/pulsaradmin/pkg/utils" + "github.com/stretchr/testify/assert" + + "github.com/streamnative/pulsarctl/pkg/cmdutils" +) + +func withAdminURLForTest(t *testing.T, webServiceURL string) { + t.Helper() + oldURL := cmdutils.PulsarCtlConfig.WebServiceURL + cmdutils.PulsarCtlConfig.WebServiceURL = webServiceURL + t.Cleanup(func() { + cmdutils.PulsarCtlConfig.WebServiceURL = oldURL + }) +} + +func TestGetSchemaAllVersion(t *testing.T) { + topic := "persistent://public/default/test-schema" + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, http.MethodGet, r.Method) + assert.Equal(t, "/admin/v2/schemas/public/default/test-schema/schemas", r.URL.Path) + _, _ = w.Write([]byte(`{ + "getSchemaResponses": [ + { + "version": 2, + "type": "AVRO", + "timestamp": 1, + "data": "{\"type\":\"record\",\"name\":\"Test\",\"fields\":[]}", + "properties": {} + } + ] +}`)) + })) + defer srv.Close() + withAdminURLForTest(t, srv.URL) + + args := []string{"get", topic, "--all-version"} + out, execErr, err := TestSchemasCommands(getSchema, args) + assert.Nil(t, err) + assert.Nil(t, execErr) + + var infos []utils.SchemaInfoWithVersion + err = json.Unmarshal(out.Bytes(), &infos) + assert.Nil(t, err) + assert.Len(t, infos, 1) + assert.Equal(t, int64(2), infos[0].Version) + assert.Equal(t, "test-schema", infos[0].SchemaInfo.Name) +} + +func TestGetSchemaVersionConflict(t *testing.T) { + args := []string{"get", "persistent://public/default/test-schema", "--version", "1", "--all-version"} + _, execErr, err := TestSchemasCommands(getSchema, args) + assert.Nil(t, err) + assert.NotNil(t, execErr) + assert.Contains(t, execErr.Error(), "--version and --all-version") +} + +func TestDeleteSchemaWithForce(t *testing.T) { + topic := "persistent://public/default/test-schema" + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, http.MethodDelete, r.Method) + assert.Equal(t, "/admin/v2/schemas/public/default/test-schema/schema", r.URL.Path) + assert.Equal(t, "true", r.URL.Query().Get("force")) + w.WriteHeader(http.StatusNoContent) + })) + defer srv.Close() + withAdminURLForTest(t, srv.URL) + + args := []string{"delete", topic, "--force"} + out, execErr, err := TestSchemasCommands(deleteSchema, args) + assert.Nil(t, err) + assert.Nil(t, execErr) + assert.Equal(t, "Deleted persistent://public/default/test-schema successfully\n", out.String()) +} + +func TestSchemaCompatibility(t *testing.T) { + topic := "persistent://public/default/test-schema" + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, http.MethodPost, r.Method) + assert.Equal(t, "/admin/v2/schemas/public/default/test-schema/compatibility", r.URL.Path) + var payload utils.PostSchemaPayload + err := json.NewDecoder(r.Body).Decode(&payload) + assert.Nil(t, err) + assert.Equal(t, "AVRO", payload.SchemaType) + _, _ = w.Write([]byte(`{"compatibility":true,"schemaCompatibilityStrategy":"FULL"}`)) + })) + defer srv.Close() + withAdminURLForTest(t, srv.URL) + + tmpFile := filepath.Join(t.TempDir(), "schema.json") + err := os.WriteFile(tmpFile, []byte(`{ + "type":"AVRO", + "schema":"{\"type\":\"record\",\"name\":\"Test\",\"fields\":[]}", + "properties":{} +}`), 0o644) + assert.Nil(t, err) + + args := []string{"compatibility", topic, "-f", tmpFile} + out, execErr, err := TestSchemasCommands(testCompatibility, args) + assert.Nil(t, err) + assert.Nil(t, execErr) + + var result utils.IsCompatibility + err = json.Unmarshal(out.Bytes(), &result) + assert.Nil(t, err) + assert.True(t, result.IsCompatibility) + assert.Equal(t, utils.SchemaCompatibilityStrategyFull, result.SchemaCompatibilityStrategy) +} + +func TestSchemaCompatibilityMissingFile(t *testing.T) { + args := []string{"compatibility", "persistent://public/default/test-schema", "-f", "not-exist.json"} + _, execErr, err := TestSchemasCommands(testCompatibility, args) + assert.Nil(t, err) + assert.NotNil(t, execErr) + assert.Contains(t, execErr.Error(), "no such file or directory") +} diff --git a/pkg/ctl/schemas/schema_test.go b/pkg/ctl/schemas/schema_test.go index 62d44f42c..7621c74d7 100644 --- a/pkg/ctl/schemas/schema_test.go +++ b/pkg/ctl/schemas/schema_test.go @@ -64,6 +64,21 @@ func TestSchema(t *testing.T) { delOut, _, err := TestSchemasCommands(deleteSchema, delArgs) assert.Nil(t, err) assert.Equal(t, delOut.String(), "Deleted test-schema successfully\n") + + allArgs := []string{"get", "test-schema", "--all-version"} + allOut, _, err := TestSchemasCommands(getSchema, allArgs) + assert.NoError(t, err) + assert.True(t, strings.Contains(allOut.String(), "version")) + + compatibilityArgs := []string{"compatibility", "test-schema", "-f", fileName} + compatibilityOut, _, err := TestSchemasCommands(testCompatibility, compatibilityArgs) + assert.NoError(t, err) + assert.True(t, strings.Contains(compatibilityOut.String(), "compatibility")) + + forceDeleteArgs := []string{"delete", "test-schema", "--force"} + forceDeleteOut, _, err := TestSchemasCommands(deleteSchema, forceDeleteArgs) + assert.Nil(t, err) + assert.Equal(t, forceDeleteOut.String(), "Deleted test-schema successfully\n") } func TestFailSchema(t *testing.T) { diff --git a/pkg/ctl/schemas/schemas.go b/pkg/ctl/schemas/schemas.go index df93bba99..780c26a75 100644 --- a/pkg/ctl/schemas/schemas.go +++ b/pkg/ctl/schemas/schemas.go @@ -40,6 +40,7 @@ func Command(flagGrouping *cmdutils.FlagGrouping) *cobra.Command { cmdutils.AddVerbCmd(flagGrouping, resourceCmd, getSchema) cmdutils.AddVerbCmd(flagGrouping, resourceCmd, deleteSchema) cmdutils.AddVerbCmd(flagGrouping, resourceCmd, uploadSchema) + cmdutils.AddVerbCmd(flagGrouping, resourceCmd, testCompatibility) return resourceCmd } diff --git a/pkg/ctl/topic/create.go b/pkg/ctl/topic/create.go index 24654eae1..9f7fdf1b0 100644 --- a/pkg/ctl/topic/create.go +++ b/pkg/ctl/topic/create.go @@ -22,6 +22,7 @@ import ( "github.com/apache/pulsar-client-go/pulsaradmin/pkg/utils" "github.com/pkg/errors" + "github.com/spf13/pflag" "github.com/streamnative/pulsarctl/pkg/cmdutils" ) @@ -64,12 +65,17 @@ func CreateTopicCmd(vc *cmdutils.VerbCmd) { desc.ExampleToString(), "c") + metadata := map[string]string{} + vc.FlagSetGroup.InFlagSet("CreateTopic", func(set *pflag.FlagSet) { + set.StringToStringVarP(&metadata, "metadata", "m", nil, "topic metadata in key=value,key2=value2 format") + }) + vc.SetRunFuncWithMultiNameArgs(func() error { - return doCreateTopic(vc) + return doCreateTopic(vc, metadata) }, CheckTopicNameTwoArgs) } -func doCreateTopic(vc *cmdutils.VerbCmd) error { +func doCreateTopic(vc *cmdutils.VerbCmd, metadata map[string]string) error { // for testing if vc.NameError != nil { return vc.NameError @@ -86,7 +92,11 @@ func doCreateTopic(vc *cmdutils.VerbCmd) error { } admin := cmdutils.NewPulsarClient() - err = admin.Topics().Create(*topic, partitions) + if len(metadata) == 0 { + err = admin.Topics().Create(*topic, partitions) + } else { + err = admin.Topics().CreateWithProperties(*topic, partitions, metadata) + } if err == nil { vc.Command.Printf("Create topic %s with %d partitions successfully\n", topic.String(), partitions) } diff --git a/pkg/ctl/topic/properties.go b/pkg/ctl/topic/properties.go new file mode 100644 index 000000000..de6a8c26d --- /dev/null +++ b/pkg/ctl/topic/properties.go @@ -0,0 +1,82 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 topic + +import ( + "github.com/apache/pulsar-client-go/pulsaradmin/pkg/utils" + "github.com/spf13/pflag" + + "github.com/streamnative/pulsarctl/pkg/cmdutils" +) + +func getPropertiesCmd(vc *cmdutils.VerbCmd) { + vc.SetDescription("get-properties", "Get the topic properties", "Get the topic properties", "") + vc.EnableOutputFlagSet() + vc.SetRunFuncWithNameArg(func() error { + topic, err := utils.GetTopicName(vc.NameArg) + if err != nil { + return err + } + admin := cmdutils.NewPulsarClient() + properties, err := admin.Topics().GetProperties(*topic) + if err != nil { + return err + } + return vc.OutputConfig.WriteOutput(vc.Command.OutOrStdout(), cmdutils.NewOutputContent().WithObject(properties)) + }, "the topic name is not specified or the topic name is specified more than one") +} + +func updatePropertiesCmd(vc *cmdutils.VerbCmd) { + properties := map[string]string{} + vc.SetDescription("update-properties", "Update the topic properties", "Update the topic properties", "") + vc.FlagSetGroup.InFlagSet("TopicProperties", func(set *pflag.FlagSet) { + set.StringToStringVarP(&properties, "property", "p", nil, "properties in key=value,key2=value2 format") + }) + vc.SetRunFuncWithNameArg(func() error { + topic, err := utils.GetTopicName(vc.NameArg) + if err != nil { + return err + } + admin := cmdutils.NewPulsarClient() + err = admin.Topics().UpdateProperties(*topic, properties) + if err == nil { + vc.Command.Printf("Updated properties successfully for [%s]\n", topic.String()) + } + return err + }, "the topic name is not specified or the topic name is specified more than one") +} + +func removePropertiesCmd(vc *cmdutils.VerbCmd) { + var key string + vc.SetDescription("remove-properties", "Remove a property from a topic", "Remove a property from a topic", "") + vc.FlagSetGroup.InFlagSet("TopicProperties", func(set *pflag.FlagSet) { + set.StringVarP(&key, "key", "k", "", "property key") + }) + vc.SetRunFuncWithNameArg(func() error { + topic, err := utils.GetTopicName(vc.NameArg) + if err != nil { + return err + } + admin := cmdutils.NewPulsarClient() + err = admin.Topics().RemoveProperty(*topic, key) + if err == nil { + vc.Command.Printf("Removed property %s successfully for [%s]\n", key, topic.String()) + } + return err + }, "the topic name is not specified or the topic name is specified more than one") +} diff --git a/pkg/ctl/topic/schema_validation_enforce.go b/pkg/ctl/topic/schema_validation_enforce.go new file mode 100644 index 000000000..fa9ed2d11 --- /dev/null +++ b/pkg/ctl/topic/schema_validation_enforce.go @@ -0,0 +1,70 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 topic + +import ( + "errors" + + "github.com/apache/pulsar-client-go/pulsaradmin/pkg/utils" + "github.com/spf13/pflag" + + "github.com/streamnative/pulsarctl/pkg/cmdutils" +) + +func getSchemaValidationEnforceCmd(vc *cmdutils.VerbCmd) { + vc.SetDescription("get-schema-validation-enforce", "Get schema validation enforce flag for a topic", + "Get schema validation enforce flag for a topic", "") + vc.SetRunFuncWithNameArg(func() error { + topic, err := utils.GetTopicName(vc.NameArg) + if err != nil { + return err + } + admin := cmdutils.NewPulsarClient() + enabled, err := admin.Topics().GetSchemaValidationEnforced(*topic) + if err == nil { + vc.Command.Printf("%t\n", enabled) + } + return err + }, "the topic name is not specified or the topic name is specified more than one") +} + +func setSchemaValidationEnforceCmd(vc *cmdutils.VerbCmd) { + var enable bool + var disable bool + vc.SetDescription("set-schema-validation-enforce", "Set schema validation enforce flag for a topic", + "Set schema validation enforce flag for a topic", "") + vc.FlagSetGroup.InFlagSet("SchemaValidationEnforce", func(set *pflag.FlagSet) { + set.BoolVarP(&enable, "enable", "e", false, "enable schema validation enforce") + set.BoolVarP(&disable, "disable", "d", false, "disable schema validation enforce") + }) + vc.SetRunFuncWithNameArg(func() error { + topic, err := utils.GetTopicName(vc.NameArg) + if err != nil { + return err + } + if enable == disable { + return errors.New("need to specify either --enable or --disable") + } + admin := cmdutils.NewPulsarClient() + err = admin.Topics().SetSchemaValidationEnforced(*topic, enable) + if err == nil { + vc.Command.Printf("Set schema validation enforce successfully for [%s]\n", topic.String()) + } + return err + }, "the topic name is not specified or the topic name is specified more than one") +} diff --git a/pkg/ctl/topic/topic.go b/pkg/ctl/topic/topic.go index f81b661eb..a6862f08f 100644 --- a/pkg/ctl/topic/topic.go +++ b/pkg/ctl/topic/topic.go @@ -19,6 +19,7 @@ package topic import ( "github.com/streamnative/pulsarctl/pkg/cmdutils" + "github.com/streamnative/pulsarctl/pkg/ctl/topicpolicies" "github.com/spf13/cobra" ) @@ -93,11 +94,43 @@ func Command(flagGrouping *cmdutils.FlagGrouping) *cobra.Command { GetPublishRateCmd, SetPublishRateCmd, RemovePublishRateCmd, + getPropertiesCmd, + updatePropertiesCmd, + removePropertiesCmd, + getSchemaValidationEnforceCmd, + setSchemaValidationEnforceCmd, GetInactiveTopicCmd, SetInactiveTopicCmd, RemoveInactiveTopicCmd, } + commands = append(commands, + topicpolicies.GetMaxMessageSizeCmd, + topicpolicies.SetMaxMessageSizeCmd, + topicpolicies.RemoveMaxMessageSizeCmd, + topicpolicies.GetMaxSubscriptionsPerTopicCmd, + topicpolicies.SetMaxSubscriptionsPerTopicCmd, + topicpolicies.RemoveMaxSubscriptionsPerTopicCmd, + topicpolicies.GetDeduplicationSnapshotIntervalCmd, + topicpolicies.SetDeduplicationSnapshotIntervalCmd, + topicpolicies.RemoveDeduplicationSnapshotIntervalCmd, + topicpolicies.GetReplicatorDispatchRateCmd, + topicpolicies.SetReplicatorDispatchRateCmd, + topicpolicies.RemoveReplicatorDispatchRateCmd, + topicpolicies.GetOffloadPoliciesCmd, + topicpolicies.SetOffloadPoliciesCmd, + topicpolicies.RemoveOffloadPoliciesCmd, + topicpolicies.GetAutoSubscriptionCreationCmd, + topicpolicies.SetAutoSubscriptionCreationCmd, + topicpolicies.RemoveAutoSubscriptionCreationCmd, + topicpolicies.GetSchemaCompatibilityStrategyCmd, + topicpolicies.SetSchemaCompatibilityStrategyCmd, + topicpolicies.RemoveSchemaCompatibilityStrategyCmd, + topicpolicies.GetReplicationClustersCmd, + topicpolicies.SetReplicationClustersCmd, + topicpolicies.RemoveReplicationClustersCmd, + ) + cmdutils.AddVerbCmds(flagGrouping, resourceCmd, commands...) return resourceCmd diff --git a/pkg/ctl/topicpolicies/common.go b/pkg/ctl/topicpolicies/common.go new file mode 100644 index 000000000..ee37a7363 --- /dev/null +++ b/pkg/ctl/topicpolicies/common.go @@ -0,0 +1,69 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 topicpolicies + +import ( + "fmt" + "io" + + adminpkg "github.com/apache/pulsar-client-go/pulsaradmin/pkg/admin" + "github.com/apache/pulsar-client-go/pulsaradmin/pkg/utils" + "github.com/spf13/pflag" + "github.com/streamnative/pulsarctl/pkg/cmdutils" +) + +func topicName(vc *cmdutils.VerbCmd) (*utils.TopicName, error) { + if vc.NameError != nil { + return nil, vc.NameError + } + return utils.GetTopicName(vc.NameArg) +} + +func topicPolicies(global bool) (adminpkg.TopicPolicies, error) { + return adminpkg.TopicPoliciesOf(cmdutils.NewPulsarClient(), global) +} + +func addScopeFlags(vc *cmdutils.VerbCmd, global *bool, applied *bool) { + vc.FlagSetGroup.InFlagSet("TopicPolicyScope", func(set *pflag.FlagSet) { + if global != nil { + set.BoolVarP(global, "global", "g", false, "use global topic policies") + } + if applied != nil { + set.BoolVarP(applied, "applied", "a", false, "get the applied policy for the topic") + } + }) +} + +func writePolicyOutput(vc *cmdutils.VerbCmd, obj interface{}, text string, args ...interface{}) error { + if vc.OutputConfig == nil { + vc.EnableOutputFlagSet() + } + return vc.OutputConfig.WriteOutput( + vc.Command.OutOrStdout(), + cmdutils.NewOutputContent(). + WithObject(obj). + WithTextFunc(func(w io.Writer) error { + if text == "" { + _, err := fmt.Fprintln(w, obj) + return err + } + _, err := fmt.Fprintf(w, text, args...) + return err + }), + ) +} diff --git a/pkg/ctl/topicpolicies/max_limits.go b/pkg/ctl/topicpolicies/max_limits.go new file mode 100644 index 000000000..58eea18fe --- /dev/null +++ b/pkg/ctl/topicpolicies/max_limits.go @@ -0,0 +1,281 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 topicpolicies + +import ( + "github.com/apache/pulsar-client-go/pulsaradmin/pkg/utils" + "github.com/spf13/pflag" + "github.com/streamnative/pulsarctl/pkg/cmdutils" +) + +func getIntPolicyCmd(vc *cmdutils.VerbCmd, use, short string, getter func(admin bool, applied bool) (int, error)) { + var global bool + var applied bool + vc.SetDescription(use, short, short, "", use) + addScopeFlags(vc, &global, &applied) + vc.EnableOutputFlagSet() + vc.SetRunFuncWithNameArg(func() error { + value, err := getter(global, applied) + if err != nil { + return err + } + return writePolicyOutput(vc, value, "%d\n", value) + }, "the topic name is not specified or the topic name is specified more than one") +} + +func setIntPolicyCmd(vc *cmdutils.VerbCmd, use, short, flagName string, setter func(global bool, value int) error) { + var global bool + var value int + vc.SetDescription(use, short, short, "", use) + addScopeFlags(vc, &global, nil) + vc.FlagSetGroup.InFlagSet("Policy", func(set *pflag.FlagSet) { + set.IntVarP(&value, flagName, "m", 0, flagName) + }) + vc.SetRunFuncWithNameArg(func() error { + err := setter(global, value) + if err == nil { + vc.Command.Printf("%s successfully for [%s]\n", short, vc.NameArg) + } + return err + }, "the topic name is not specified or the topic name is specified more than one") +} + +func removePolicyCmd(vc *cmdutils.VerbCmd, use, short string, remover func(global bool) error) { + var global bool + vc.SetDescription(use, short, short, "", use) + addScopeFlags(vc, &global, nil) + vc.SetRunFuncWithNameArg(func() error { + err := remover(global) + if err == nil { + vc.Command.Printf("%s successfully for [%s]\n", short, vc.NameArg) + } + return err + }, "the topic name is not specified or the topic name is specified more than one") +} + +func GetMaxMessageSizeCmd(vc *cmdutils.VerbCmd) { + getIntPolicyCmd(vc, "get-max-message-size", "Get max message size for a topic", func(global bool, applied bool) (int, error) { + topic, err := topicName(vc) + if err != nil { + return 0, err + } + policies, err := topicPolicies(global) + if err != nil { + return 0, err + } + value, err := policies.GetMaxMessageSize(vc.Command.Context(), *topic, applied) + if err != nil { + return 0, err + } + if value == nil { + return -1, nil + } + return *value, nil + }) +} + +func SetMaxMessageSizeCmd(vc *cmdutils.VerbCmd) { + setIntPolicyCmd(vc, "set-max-message-size", "Set max message size for a topic", "max-message-size", func(global bool, value int) error { + topic, err := topicName(vc) + if err != nil { + return err + } + policies, err := topicPolicies(global) + if err != nil { + return err + } + return policies.SetMaxMessageSize(vc.Command.Context(), *topic, value) + }) +} + +func RemoveMaxMessageSizeCmd(vc *cmdutils.VerbCmd) { + removePolicyCmd(vc, "remove-max-message-size", "Removed max message size for a topic", func(global bool) error { + topic, err := topicName(vc) + if err != nil { + return err + } + policies, err := topicPolicies(global) + if err != nil { + return err + } + return policies.RemoveMaxMessageSize(vc.Command.Context(), *topic) + }) +} + +func GetMaxSubscriptionsPerTopicCmd(vc *cmdutils.VerbCmd) { + getIntPolicyCmd(vc, "get-max-subscriptions-per-topic", "Get max subscriptions per topic", func(global bool, applied bool) (int, error) { + topic, err := topicName(vc) + if err != nil { + return 0, err + } + policies, err := topicPolicies(global) + if err != nil { + return 0, err + } + value, err := policies.GetMaxSubscriptionsPerTopic(vc.Command.Context(), *topic, applied) + if err != nil { + return 0, err + } + if value == nil { + return -1, nil + } + return *value, nil + }) +} + +func SetMaxSubscriptionsPerTopicCmd(vc *cmdutils.VerbCmd) { + setIntPolicyCmd(vc, "set-max-subscriptions-per-topic", "Set max subscriptions per topic", "max-subscriptions-per-topic", func(global bool, value int) error { + topic, err := topicName(vc) + if err != nil { + return err + } + policies, err := topicPolicies(global) + if err != nil { + return err + } + return policies.SetMaxSubscriptionsPerTopic(vc.Command.Context(), *topic, value) + }) +} + +func RemoveMaxSubscriptionsPerTopicCmd(vc *cmdutils.VerbCmd) { + removePolicyCmd(vc, "remove-max-subscriptions-per-topic", "Removed max subscriptions per topic", func(global bool) error { + topic, err := topicName(vc) + if err != nil { + return err + } + policies, err := topicPolicies(global) + if err != nil { + return err + } + return policies.RemoveMaxSubscriptionsPerTopic(vc.Command.Context(), *topic) + }) +} + +func GetDeduplicationSnapshotIntervalCmd(vc *cmdutils.VerbCmd) { + getIntPolicyCmd(vc, "get-deduplication-snapshot-interval", "Get deduplication snapshot interval", func(global bool, applied bool) (int, error) { + topic, err := topicName(vc) + if err != nil { + return 0, err + } + policies, err := topicPolicies(global) + if err != nil { + return 0, err + } + value, err := policies.GetDeduplicationSnapshotInterval(vc.Command.Context(), *topic, applied) + if err != nil { + return 0, err + } + if value == nil { + return -1, nil + } + return *value, nil + }) +} + +func SetDeduplicationSnapshotIntervalCmd(vc *cmdutils.VerbCmd) { + setIntPolicyCmd(vc, "set-deduplication-snapshot-interval", "Set deduplication snapshot interval", "interval", func(global bool, value int) error { + topic, err := topicName(vc) + if err != nil { + return err + } + policies, err := topicPolicies(global) + if err != nil { + return err + } + return policies.SetDeduplicationSnapshotInterval(vc.Command.Context(), *topic, value) + }) +} + +func RemoveDeduplicationSnapshotIntervalCmd(vc *cmdutils.VerbCmd) { + removePolicyCmd(vc, "remove-deduplication-snapshot-interval", "Removed deduplication snapshot interval", func(global bool) error { + topic, err := topicName(vc) + if err != nil { + return err + } + policies, err := topicPolicies(global) + if err != nil { + return err + } + return policies.RemoveDeduplicationSnapshotInterval(vc.Command.Context(), *topic) + }) +} + +func GetReplicatorDispatchRateCmd(vc *cmdutils.VerbCmd) { + var global bool + var applied bool + vc.SetDescription("get-replicator-dispatch-rate", "Get replicator dispatch rate for a topic", "Get replicator dispatch rate for a topic", "") + addScopeFlags(vc, &global, &applied) + vc.EnableOutputFlagSet() + vc.SetRunFuncWithNameArg(func() error { + topic, err := topicName(vc) + if err != nil { + return err + } + policies, err := topicPolicies(global) + if err != nil { + return err + } + rate, err := policies.GetReplicatorDispatchRate(vc.Command.Context(), *topic, applied) + if err != nil { + return err + } + return writePolicyOutput(vc, rate, "") + }, "the topic name is not specified or the topic name is specified more than one") +} + +func SetReplicatorDispatchRateCmd(vc *cmdutils.VerbCmd) { + var global bool + data := utils.DispatchRateData{} + vc.SetDescription("set-replicator-dispatch-rate", "Set replicator dispatch rate for a topic", "Set replicator dispatch rate for a topic", "") + addScopeFlags(vc, &global, nil) + vc.FlagSetGroup.InFlagSet("ReplicatorDispatchRate", func(set *pflag.FlagSet) { + set.Int64VarP(&data.DispatchThrottlingRateInMsg, "msg-dispatch-rate", "", -1, "message dispatch rate") + set.Int64VarP(&data.DispatchThrottlingRateInByte, "byte-dispatch-rate", "", -1, "byte dispatch rate") + set.Int64VarP(&data.RatePeriodInSecond, "dispatch-rate-period", "", 1, "dispatch rate period in seconds") + set.BoolVarP(&data.RelativeToPublishRate, "relative-to-publish-rate", "", false, "relative to publish rate") + }) + vc.SetRunFuncWithNameArg(func() error { + topic, err := topicName(vc) + if err != nil { + return err + } + policies, err := topicPolicies(global) + if err != nil { + return err + } + err = policies.SetReplicatorDispatchRate(vc.Command.Context(), *topic, data) + if err == nil { + vc.Command.Printf("Set replicator dispatch rate successfully for [%s]\n", topic.String()) + } + return err + }, "the topic name is not specified or the topic name is specified more than one") +} + +func RemoveReplicatorDispatchRateCmd(vc *cmdutils.VerbCmd) { + removePolicyCmd(vc, "remove-replicator-dispatch-rate", "Removed replicator dispatch rate", func(global bool) error { + topic, err := topicName(vc) + if err != nil { + return err + } + policies, err := topicPolicies(global) + if err != nil { + return err + } + return policies.RemoveReplicatorDispatchRate(vc.Command.Context(), *topic) + }) +} diff --git a/pkg/ctl/topicpolicies/offload_policies.go b/pkg/ctl/topicpolicies/offload_policies.go new file mode 100644 index 000000000..be8e72920 --- /dev/null +++ b/pkg/ctl/topicpolicies/offload_policies.go @@ -0,0 +1,99 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 topicpolicies + +import ( + "github.com/apache/pulsar-client-go/pulsaradmin/pkg/utils" + "github.com/spf13/pflag" + "github.com/streamnative/pulsarctl/pkg/cmdutils" +) + +func GetOffloadPoliciesCmd(vc *cmdutils.VerbCmd) { + var global bool + var applied bool + vc.SetDescription("get-offload-policies", "Get offload policies", "Get offload policies", "") + addScopeFlags(vc, &global, &applied) + vc.EnableOutputFlagSet() + vc.SetRunFuncWithNameArg(func() error { + topic, err := topicName(vc) + if err != nil { + return err + } + policies, err := topicPolicies(global) + if err != nil { + return err + } + value, err := policies.GetOffloadPolicies(vc.Command.Context(), *topic, applied) + if err != nil { + return err + } + return writePolicyOutput(vc, value, "") + }, "the topic name is not specified or the topic name is specified more than one") +} + +func SetOffloadPoliciesCmd(vc *cmdutils.VerbCmd) { + var global bool + policy := utils.NewOffloadPolicies() + vc.SetDescription("set-offload-policies", "Set offload policies", "Set offload policies", "") + addScopeFlags(vc, &global, nil) + vc.FlagSetGroup.InFlagSet("OffloadPolicies", func(set *pflag.FlagSet) { + set.StringVarP(&policy.ManagedLedgerOffloadDriver, "driver", "", "", "offload driver") + set.IntVarP(&policy.ManagedLedgerOffloadMaxThreads, "max-threads", "", 2, "max offload threads") + set.Int64VarP(&policy.ManagedLedgerOffloadThresholdInBytes, "threshold-bytes", "", -1, "offload threshold in bytes") + set.Int64VarP(&policy.ManagedLedgerOffloadDeletionLagInMillis, "deletion-lag-millis", "", 14400000, "offload deletion lag in millis") + set.Int64VarP(&policy.ManagedLedgerOffloadAutoTriggerSizeThresholdBytes, "auto-trigger-size-threshold-bytes", "", -1, "auto trigger size threshold bytes") + set.StringVarP(&policy.S3ManagedLedgerOffloadBucket, "bucket", "", "", "S3 bucket") + set.StringVarP(&policy.S3ManagedLedgerOffloadRegion, "region", "", "", "S3 region") + set.StringVarP(&policy.S3ManagedLedgerOffloadServiceEndpoint, "service-endpoint", "", "", "S3 service endpoint") + set.StringVarP(&policy.S3ManagedLedgerOffloadCredentialID, "credential-id", "", "", "credential id") + set.StringVarP(&policy.S3ManagedLedgerOffloadCredentialSecret, "credential-secret", "", "", "credential secret") + set.StringVarP(&policy.S3ManagedLedgerOffloadRole, "role", "", "", "S3 role") + set.StringVarP(&policy.S3ManagedLedgerOffloadRoleSessionName, "role-session-name", "", "", "S3 role session name") + set.StringVarP(&policy.OffloadersDirectory, "offloaders-directory", "", "", "offloaders directory") + set.StringToStringVarP(&policy.ManagedLedgerOffloadDriverMetadata, "driver-metadata", "", nil, "driver metadata in key=value,key2=value2 format") + }) + vc.SetRunFuncWithNameArg(func() error { + topic, err := topicName(vc) + if err != nil { + return err + } + policies, err := topicPolicies(global) + if err != nil { + return err + } + err = policies.SetOffloadPolicies(vc.Command.Context(), *topic, *policy) + if err == nil { + vc.Command.Printf("Set offload policies successfully for [%s]\n", topic.String()) + } + return err + }, "the topic name is not specified or the topic name is specified more than one") +} + +func RemoveOffloadPoliciesCmd(vc *cmdutils.VerbCmd) { + removePolicyCmd(vc, "remove-offload-policies", "Removed offload policies", func(global bool) error { + topic, err := topicName(vc) + if err != nil { + return err + } + policies, err := topicPolicies(global) + if err != nil { + return err + } + return policies.RemoveOffloadPolicies(vc.Command.Context(), *topic) + }) +} diff --git a/pkg/ctl/topicpolicies/replication_clusters.go b/pkg/ctl/topicpolicies/replication_clusters.go new file mode 100644 index 000000000..8fa70a196 --- /dev/null +++ b/pkg/ctl/topicpolicies/replication_clusters.go @@ -0,0 +1,157 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 topicpolicies + +import ( + "errors" + "strings" + + "github.com/apache/pulsar-client-go/pulsaradmin/pkg/utils" + "github.com/spf13/pflag" + "github.com/streamnative/pulsarctl/pkg/cmdutils" +) + +func GetReplicationClustersCmd(vc *cmdutils.VerbCmd) { + var global bool + var applied bool + vc.SetDescription("get-replication-clusters", "Get replication clusters for a topic", "Get replication clusters for a topic", "") + addScopeFlags(vc, &global, &applied) + vc.EnableOutputFlagSet() + vc.SetRunFuncWithNameArg(func() error { + topic, err := topicName(vc) + if err != nil { + return err + } + policies, err := topicPolicies(global) + if err != nil { + return err + } + clusters, err := policies.GetReplicationClusters(vc.Command.Context(), *topic, applied) + if err != nil { + return err + } + return writePolicyOutput(vc, clusters, "") + }, "the topic name is not specified or the topic name is specified more than one") +} + +func SetReplicationClustersCmd(vc *cmdutils.VerbCmd) { + var global bool + var clusterIDs string + vc.SetDescription("set-replication-clusters", "Set replication clusters for a topic", "Set replication clusters for a topic", "") + addScopeFlags(vc, &global, nil) + vc.FlagSetGroup.InFlagSet("ReplicationClusters", func(set *pflag.FlagSet) { + set.StringVarP(&clusterIDs, "clusters", "c", "", "comma separated cluster names") + }) + vc.SetRunFuncWithNameArg(func() error { + topic, err := topicName(vc) + if err != nil { + return err + } + policies, err := topicPolicies(global) + if err != nil { + return err + } + err = policies.SetReplicationClusters(vc.Command.Context(), *topic, strings.Split(clusterIDs, ",")) + if err == nil { + vc.Command.Printf("Set replication clusters successfully for [%s]\n", topic.String()) + } + return err + }, "the topic name is not specified or the topic name is specified more than one") +} + +func RemoveReplicationClustersCmd(vc *cmdutils.VerbCmd) { + removePolicyCmd(vc, "remove-replication-clusters", "Removed replication clusters", func(global bool) error { + topic, err := topicName(vc) + if err != nil { + return err + } + policies, err := topicPolicies(global) + if err != nil { + return err + } + return policies.RemoveReplicationClusters(vc.Command.Context(), *topic) + }) +} + +func GetAutoSubscriptionCreationCmd(vc *cmdutils.VerbCmd) { + var global bool + var applied bool + vc.SetDescription("get-auto-subscription-creation", "Get auto subscription creation policy", "Get auto subscription creation policy", "") + addScopeFlags(vc, &global, &applied) + vc.EnableOutputFlagSet() + vc.SetRunFuncWithNameArg(func() error { + topic, err := topicName(vc) + if err != nil { + return err + } + policies, err := topicPolicies(global) + if err != nil { + return err + } + override, err := policies.GetAutoSubscriptionCreation(vc.Command.Context(), *topic, applied) + if err != nil { + return err + } + return writePolicyOutput(vc, override, "") + }, "the topic name is not specified or the topic name is specified more than one") +} + +func SetAutoSubscriptionCreationCmd(vc *cmdutils.VerbCmd) { + var global bool + var enable bool + var disable bool + vc.SetDescription("set-auto-subscription-creation", "Set auto subscription creation policy", "Set auto subscription creation policy", "") + addScopeFlags(vc, &global, nil) + vc.FlagSetGroup.InFlagSet("AutoSubscriptionCreation", func(set *pflag.FlagSet) { + set.BoolVarP(&enable, "enable", "e", false, "enable auto subscription creation") + set.BoolVarP(&disable, "disable", "d", false, "disable auto subscription creation") + }) + vc.SetRunFuncWithNameArg(func() error { + topic, err := topicName(vc) + if err != nil { + return err + } + if enable == disable { + return errors.New("need to specify either --enable or --disable") + } + policies, err := topicPolicies(global) + if err != nil { + return err + } + override := utils.AutoSubscriptionCreationOverride{AllowAutoSubscriptionCreation: enable} + err = policies.SetAutoSubscriptionCreation(vc.Command.Context(), *topic, override) + if err == nil { + vc.Command.Printf("Set auto subscription creation successfully for [%s]\n", topic.String()) + } + return err + }, "the topic name is not specified or the topic name is specified more than one") +} + +func RemoveAutoSubscriptionCreationCmd(vc *cmdutils.VerbCmd) { + removePolicyCmd(vc, "remove-auto-subscription-creation", "Removed auto subscription creation policy", func(global bool) error { + topic, err := topicName(vc) + if err != nil { + return err + } + policies, err := topicPolicies(global) + if err != nil { + return err + } + return policies.RemoveAutoSubscriptionCreation(vc.Command.Context(), *topic) + }) +} diff --git a/pkg/ctl/topicpolicies/schema_compatibility_strategy.go b/pkg/ctl/topicpolicies/schema_compatibility_strategy.go new file mode 100644 index 000000000..e3cd9c133 --- /dev/null +++ b/pkg/ctl/topicpolicies/schema_compatibility_strategy.go @@ -0,0 +1,99 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 topicpolicies + +import ( + "fmt" + + "github.com/apache/pulsar-client-go/pulsaradmin/pkg/utils" + "github.com/spf13/pflag" + "github.com/streamnative/pulsarctl/pkg/cmdutils" +) + +func GetSchemaCompatibilityStrategyCmd(vc *cmdutils.VerbCmd) { + var global bool + var applied bool + vc.SetDescription("get-schema-compatibility-strategy", "Get schema compatibility strategy", "Get schema compatibility strategy", "") + addScopeFlags(vc, &global, &applied) + vc.EnableOutputFlagSet() + vc.SetRunFuncWithNameArg(func() error { + topic, err := topicName(vc) + if err != nil { + return err + } + policies, err := topicPolicies(global) + if err != nil { + return err + } + value, err := policies.GetSchemaCompatibilityStrategy(vc.Command.Context(), *topic, applied) + if err != nil { + return err + } + if value == nil { + return writePolicyOutput(vc, "", "\n") + } + return writePolicyOutput(vc, value.String(), "%s\n", value.String()) + }, "the topic name is not specified or the topic name is specified more than one") +} + +func SetSchemaCompatibilityStrategyCmd(vc *cmdutils.VerbCmd) { + var global bool + var strategy string + vc.SetDescription("set-schema-compatibility-strategy", "Set schema compatibility strategy", "Set schema compatibility strategy", "") + addScopeFlags(vc, &global, nil) + vc.FlagSetGroup.InFlagSet("SchemaCompatibilityStrategy", func(set *pflag.FlagSet) { + set.StringVarP(&strategy, "compatibility", "c", "", "schema compatibility strategy") + }) + vc.SetRunFuncWithNameArg(func() error { + topic, err := topicName(vc) + if err != nil { + return err + } + parsed, err := utils.ParseSchemaCompatibilityStrategy(strategy) + if err != nil { + return err + } + policies, err := topicPolicies(global) + if err != nil { + return err + } + err = policies.SetSchemaCompatibilityStrategy(vc.Command.Context(), *topic, parsed) + if err == nil { + vc.Command.Printf("Set schema compatibility strategy successfully for [%s]\n", topic.String()) + } + return err + }, "the topic name is not specified or the topic name is specified more than one") +} + +func RemoveSchemaCompatibilityStrategyCmd(vc *cmdutils.VerbCmd) { + removePolicyCmd(vc, "remove-schema-compatibility-strategy", "Removed schema compatibility strategy", func(global bool) error { + topic, err := topicName(vc) + if err != nil { + return err + } + policies, err := topicPolicies(global) + if err != nil { + return err + } + return policies.RemoveSchemaCompatibilityStrategy(vc.Command.Context(), *topic) + }) +} + +func writePolicyOutputString(vc *cmdutils.VerbCmd, value string) error { + return writePolicyOutput(vc, value, fmt.Sprintf("%%s\n"), value) +} diff --git a/pkg/ctl/topicpolicies/topic_policies.go b/pkg/ctl/topicpolicies/topic_policies.go new file mode 100644 index 000000000..2ebfe1499 --- /dev/null +++ b/pkg/ctl/topicpolicies/topic_policies.go @@ -0,0 +1,61 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 topicpolicies + +import ( + "github.com/spf13/cobra" + "github.com/streamnative/pulsarctl/pkg/cmdutils" +) + +func Command(flagGrouping *cmdutils.FlagGrouping) *cobra.Command { + resourceCmd := cmdutils.NewResourceCmd( + "topic-policies", + "Operations about topic policies", + "", + "topic-policy", + ) + + cmdutils.AddVerbCmds(flagGrouping, resourceCmd, + GetMaxMessageSizeCmd, + SetMaxMessageSizeCmd, + RemoveMaxMessageSizeCmd, + GetMaxSubscriptionsPerTopicCmd, + SetMaxSubscriptionsPerTopicCmd, + RemoveMaxSubscriptionsPerTopicCmd, + GetDeduplicationSnapshotIntervalCmd, + SetDeduplicationSnapshotIntervalCmd, + RemoveDeduplicationSnapshotIntervalCmd, + GetReplicatorDispatchRateCmd, + SetReplicatorDispatchRateCmd, + RemoveReplicatorDispatchRateCmd, + GetOffloadPoliciesCmd, + SetOffloadPoliciesCmd, + RemoveOffloadPoliciesCmd, + GetAutoSubscriptionCreationCmd, + SetAutoSubscriptionCreationCmd, + RemoveAutoSubscriptionCreationCmd, + GetSchemaCompatibilityStrategyCmd, + SetSchemaCompatibilityStrategyCmd, + RemoveSchemaCompatibilityStrategyCmd, + GetReplicationClustersCmd, + SetReplicationClustersCmd, + RemoveReplicationClustersCmd, + ) + + return resourceCmd +} diff --git a/pkg/pulsarctl.go b/pkg/pulsarctl.go index 171bb20a7..d4eecd979 100644 --- a/pkg/pulsarctl.go +++ b/pkg/pulsarctl.go @@ -36,6 +36,7 @@ import ( "github.com/streamnative/pulsarctl/pkg/ctl/tenant" "github.com/streamnative/pulsarctl/pkg/ctl/token" "github.com/streamnative/pulsarctl/pkg/ctl/topic" + "github.com/streamnative/pulsarctl/pkg/ctl/topicpolicies" "github.com/streamnative/pulsarctl/pkg/oauth2" function "github.com/streamnative/pulsarctl/pkg/ctl/functions" @@ -119,6 +120,7 @@ func NewPulsarctlCmd() *cobra.Command { rootCmd.AddCommand(source.Command(flagGrouping)) rootCmd.AddCommand(sink.Command(flagGrouping)) rootCmd.AddCommand(topic.Command(flagGrouping)) + rootCmd.AddCommand(topicpolicies.Command(flagGrouping)) rootCmd.AddCommand(namespace.Command(flagGrouping)) rootCmd.AddCommand(schema.Command(flagGrouping)) rootCmd.AddCommand(subscription.Command(flagGrouping)) From 08c00dd8540828625a31e68786707b16f5050128 Mon Sep 17 00:00:00 2001 From: Rui Fu Date: Sat, 11 Apr 2026 08:11:10 +0800 Subject: [PATCH 5/8] feat: add multiple admin getter commands for pulsarctl --- pkg/ctl/namespace/get_topic_auto_creation.go | 63 +++ .../namespace/get_topic_auto_creation_test.go | 30 ++ pkg/ctl/namespace/namespace.go | 2 + pkg/ctl/namespace/subscription_permission.go | 63 +++ .../namespace/subscription_permission_test.go | 30 ++ pkg/ctl/sinks/reload.go | 50 +++ pkg/ctl/sinks/sinks.go | 1 + pkg/ctl/sources/reload.go | 50 +++ pkg/ctl/sources/sources.go | 1 + pkg/ctl/topic/get_message_id.go | 82 ++++ pkg/ctl/topic/get_message_id_test.go | 36 ++ .../topic/max_consumers_per_subscription.go | 113 +++++ pkg/ctl/topic/subscribe_rate.go | 106 +++++ pkg/ctl/topic/subscribe_rate_test.go | 36 ++ pkg/ctl/topic/topic.go | 7 + pkg/ctl/topicpolicies/common.go | 127 +++++- pkg/ctl/topicpolicies/data_policies.go | 396 ++++++++++++++++++ pkg/ctl/topicpolicies/rate_policies.go | 232 ++++++++++ pkg/ctl/topicpolicies/scalar_policies.go | 374 +++++++++++++++++ .../schema_compatibility_strategy.go | 2 +- pkg/ctl/topicpolicies/test_help.go | 65 +++ pkg/ctl/topicpolicies/topic_policies.go | 51 +++ pkg/ctl/topicpolicies/topic_policies_test.go | 42 ++ 23 files changed, 1952 insertions(+), 7 deletions(-) create mode 100644 pkg/ctl/namespace/get_topic_auto_creation.go create mode 100644 pkg/ctl/namespace/get_topic_auto_creation_test.go create mode 100644 pkg/ctl/namespace/subscription_permission.go create mode 100644 pkg/ctl/namespace/subscription_permission_test.go create mode 100644 pkg/ctl/sinks/reload.go create mode 100644 pkg/ctl/sources/reload.go create mode 100644 pkg/ctl/topic/get_message_id.go create mode 100644 pkg/ctl/topic/get_message_id_test.go create mode 100644 pkg/ctl/topic/max_consumers_per_subscription.go create mode 100644 pkg/ctl/topic/subscribe_rate.go create mode 100644 pkg/ctl/topic/subscribe_rate_test.go create mode 100644 pkg/ctl/topicpolicies/data_policies.go create mode 100644 pkg/ctl/topicpolicies/rate_policies.go create mode 100644 pkg/ctl/topicpolicies/scalar_policies.go create mode 100644 pkg/ctl/topicpolicies/test_help.go create mode 100644 pkg/ctl/topicpolicies/topic_policies_test.go diff --git a/pkg/ctl/namespace/get_topic_auto_creation.go b/pkg/ctl/namespace/get_topic_auto_creation.go new file mode 100644 index 000000000..c2ec76a31 --- /dev/null +++ b/pkg/ctl/namespace/get_topic_auto_creation.go @@ -0,0 +1,63 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 namespace + +import ( + "github.com/apache/pulsar-client-go/pulsaradmin/pkg/utils" + "github.com/streamnative/pulsarctl/pkg/cmdutils" +) + +func getTopicAutoCreation(vc *cmdutils.VerbCmd) { + desc := cmdutils.LongDescription{} + desc.CommandUsedFor = "Get topic auto-creation config for a namespace" + desc.CommandPermission = "This command requires tenant admin permissions." + + desc.CommandExamples = []cmdutils.Example{ + { + Desc: "Get topic auto-creation config for a namespace", + Command: "pulsarctl namespaces get-auto-topic-creation tenant/namespace", + }, + } + + vc.SetDescription( + "get-auto-topic-creation", + "Get topic auto-creation config for a namespace", + desc.ToString(), + desc.ExampleToString(), + "get-auto-topic-creation", + ) + + vc.SetRunFuncWithNameArg(func() error { + return doGetTopicAutoCreation(vc) + }, "the namespace name is not specified or the namespace name is specified more than one") + vc.EnableOutputFlagSet() +} + +func doGetTopicAutoCreation(vc *cmdutils.VerbCmd) error { + ns, err := utils.GetNamespaceName(vc.NameArg) + if err != nil { + return err + } + + admin := cmdutils.NewPulsarClient() + config, err := admin.Namespaces().GetTopicAutoCreation(*ns) + if err == nil { + err = vc.OutputConfig.WriteOutput(vc.Command.OutOrStdout(), cmdutils.NewOutputContent().WithObject(config)) + } + return err +} diff --git a/pkg/ctl/namespace/get_topic_auto_creation_test.go b/pkg/ctl/namespace/get_topic_auto_creation_test.go new file mode 100644 index 000000000..7605c3e79 --- /dev/null +++ b/pkg/ctl/namespace/get_topic_auto_creation_test.go @@ -0,0 +1,30 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 namespace + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestGetTopicAutoCreationNameError(t *testing.T) { + _, _, nameErr, _ := TestNamespaceCommands(getTopicAutoCreation, []string{"get-auto-topic-creation"}) + assert.NotNil(t, nameErr) + assert.Equal(t, "the namespace name is not specified or the namespace name is specified more than one", nameErr.Error()) +} diff --git a/pkg/ctl/namespace/namespace.go b/pkg/ctl/namespace/namespace.go index 8726298fb..c8a175eaa 100644 --- a/pkg/ctl/namespace/namespace.go +++ b/pkg/ctl/namespace/namespace.go @@ -46,6 +46,7 @@ func Command(flagGrouping *cmdutils.FlagGrouping) *cobra.Command { cmdutils.AddVerbCmd(flagGrouping, resourceCmd, setBacklogQuota) cmdutils.AddVerbCmd(flagGrouping, resourceCmd, removeBacklogQuota) cmdutils.AddVerbCmd(flagGrouping, resourceCmd, setTopicAutoCreation) + cmdutils.AddVerbCmd(flagGrouping, resourceCmd, getTopicAutoCreation) cmdutils.AddVerbCmd(flagGrouping, resourceCmd, removeTopicAutoCreation) cmdutils.AddVerbCmd(flagGrouping, resourceCmd, SetSchemaValidationEnforcedCmd) cmdutils.AddVerbCmd(flagGrouping, resourceCmd, GetSchemaValidationEnforcedCmd) @@ -84,6 +85,7 @@ func Command(flagGrouping *cmdutils.FlagGrouping) *cobra.Command { cmdutils.AddVerbCmd(flagGrouping, resourceCmd, RevokePermissionsCmd) cmdutils.AddVerbCmd(flagGrouping, resourceCmd, GrantSubPermissionsCmd) cmdutils.AddVerbCmd(flagGrouping, resourceCmd, RevokeSubPermissionsCmd) + cmdutils.AddVerbCmd(flagGrouping, resourceCmd, GetSubPermissionsCmd) cmdutils.AddVerbCmd(flagGrouping, resourceCmd, ClearBacklogCmd) cmdutils.AddVerbCmd(flagGrouping, resourceCmd, GetDispatchRateCmd) cmdutils.AddVerbCmd(flagGrouping, resourceCmd, SetDispatchRateCmd) diff --git a/pkg/ctl/namespace/subscription_permission.go b/pkg/ctl/namespace/subscription_permission.go new file mode 100644 index 000000000..72e1a1bed --- /dev/null +++ b/pkg/ctl/namespace/subscription_permission.go @@ -0,0 +1,63 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 namespace + +import ( + "github.com/apache/pulsar-client-go/pulsaradmin/pkg/utils" + "github.com/streamnative/pulsarctl/pkg/cmdutils" +) + +func GetSubPermissionsCmd(vc *cmdutils.VerbCmd) { + desc := cmdutils.LongDescription{} + desc.CommandUsedFor = "Get permissions to access subscription admin API" + desc.CommandPermission = "This command requires tenant admin permissions." + + desc.CommandExamples = []cmdutils.Example{ + { + Desc: "Get permissions to access subscription admin API", + Command: "pulsarctl namespaces subscription-permission tenant/namespace", + }, + } + + vc.SetDescription( + "subscription-permission", + "Get permissions to access subscription admin API", + desc.ToString(), + desc.ExampleToString(), + "subscription-permission", + ) + + vc.SetRunFuncWithNameArg(func() error { + return doGetSubPermissions(vc) + }, "the namespace name is not specified or the namespace name is specified more than one") + vc.EnableOutputFlagSet() +} + +func doGetSubPermissions(vc *cmdutils.VerbCmd) error { + ns, err := utils.GetNamespaceName(vc.NameArg) + if err != nil { + return err + } + + admin := cmdutils.NewPulsarClient() + permissions, err := admin.Namespaces().GetSubPermissions(*ns) + if err == nil { + err = vc.OutputConfig.WriteOutput(vc.Command.OutOrStdout(), cmdutils.NewOutputContent().WithObject(permissions)) + } + return err +} diff --git a/pkg/ctl/namespace/subscription_permission_test.go b/pkg/ctl/namespace/subscription_permission_test.go new file mode 100644 index 000000000..d4b303cd5 --- /dev/null +++ b/pkg/ctl/namespace/subscription_permission_test.go @@ -0,0 +1,30 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 namespace + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestSubscriptionPermissionNameError(t *testing.T) { + _, _, nameErr, _ := TestNamespaceCommands(GetSubPermissionsCmd, []string{"subscription-permission"}) + assert.NotNil(t, nameErr) + assert.Equal(t, "the namespace name is not specified or the namespace name is specified more than one", nameErr.Error()) +} diff --git a/pkg/ctl/sinks/reload.go b/pkg/ctl/sinks/reload.go new file mode 100644 index 000000000..36fd64f41 --- /dev/null +++ b/pkg/ctl/sinks/reload.go @@ -0,0 +1,50 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 sinks + +import ( + "github.com/apache/pulsar-client-go/pulsaradmin/pkg/admin/config" + "github.com/streamnative/pulsarctl/pkg/cmdutils" +) + +func reloadSinksCmd(vc *cmdutils.VerbCmd) { + desc := cmdutils.LongDescription{} + desc.CommandUsedFor = "Reload built-in Pulsar IO sinks" + desc.CommandPermission = "This command requires tenant admin permissions." + + desc.CommandExamples = []cmdutils.Example{ + { + Desc: "Reload built-in Pulsar IO sinks", + Command: "pulsarctl sinks reload", + }, + } + + vc.SetDescription("reload", "Reload built-in Pulsar IO sinks", desc.ToString(), desc.ExampleToString(), "reload") + vc.SetRunFunc(func() error { + return doReloadSinks(vc) + }) +} + +func doReloadSinks(vc *cmdutils.VerbCmd) error { + admin := cmdutils.NewPulsarClientWithAPIVersion(config.V3) + err := admin.Sinks().ReloadBuiltInSinks() + if err == nil { + vc.Command.Println("Reloaded built-in sinks successfully") + } + return err +} diff --git a/pkg/ctl/sinks/sinks.go b/pkg/ctl/sinks/sinks.go index fc18881c5..5950f5975 100644 --- a/pkg/ctl/sinks/sinks.go +++ b/pkg/ctl/sinks/sinks.go @@ -41,6 +41,7 @@ func Command(flagGrouping *cmdutils.FlagGrouping) *cobra.Command { cmdutils.AddVerbCmd(flagGrouping, resourceCmd, restartSinksCmd) cmdutils.AddVerbCmd(flagGrouping, resourceCmd, statusSinksCmd) cmdutils.AddVerbCmd(flagGrouping, resourceCmd, listBuiltInSinksCmd) + cmdutils.AddVerbCmd(flagGrouping, resourceCmd, reloadSinksCmd) return resourceCmd } diff --git a/pkg/ctl/sources/reload.go b/pkg/ctl/sources/reload.go new file mode 100644 index 000000000..80b86863a --- /dev/null +++ b/pkg/ctl/sources/reload.go @@ -0,0 +1,50 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 sources + +import ( + "github.com/apache/pulsar-client-go/pulsaradmin/pkg/admin/config" + "github.com/streamnative/pulsarctl/pkg/cmdutils" +) + +func reloadSourcesCmd(vc *cmdutils.VerbCmd) { + desc := cmdutils.LongDescription{} + desc.CommandUsedFor = "Reload built-in Pulsar IO sources" + desc.CommandPermission = "This command requires tenant admin permissions." + + desc.CommandExamples = []cmdutils.Example{ + { + Desc: "Reload built-in Pulsar IO sources", + Command: "pulsarctl sources reload", + }, + } + + vc.SetDescription("reload", "Reload built-in Pulsar IO sources", desc.ToString(), desc.ExampleToString(), "reload") + vc.SetRunFunc(func() error { + return doReloadSources(vc) + }) +} + +func doReloadSources(vc *cmdutils.VerbCmd) error { + admin := cmdutils.NewPulsarClientWithAPIVersion(config.V3) + err := admin.Sources().ReloadBuiltInSources() + if err == nil { + vc.Command.Println("Reloaded built-in sources successfully") + } + return err +} diff --git a/pkg/ctl/sources/sources.go b/pkg/ctl/sources/sources.go index db1210c33..383ebf108 100644 --- a/pkg/ctl/sources/sources.go +++ b/pkg/ctl/sources/sources.go @@ -41,6 +41,7 @@ func Command(flagGrouping *cmdutils.FlagGrouping) *cobra.Command { cmdutils.AddVerbCmd(flagGrouping, resourceCmd, restartSourcesCmd) cmdutils.AddVerbCmd(flagGrouping, resourceCmd, statusSourcesCmd) cmdutils.AddVerbCmd(flagGrouping, resourceCmd, listBuiltInSourcesCmd) + cmdutils.AddVerbCmd(flagGrouping, resourceCmd, reloadSourcesCmd) return resourceCmd } diff --git a/pkg/ctl/topic/get_message_id.go b/pkg/ctl/topic/get_message_id.go new file mode 100644 index 000000000..1e2ec38c6 --- /dev/null +++ b/pkg/ctl/topic/get_message_id.go @@ -0,0 +1,82 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 topic + +import ( + "time" + + "github.com/apache/pulsar-client-go/pulsaradmin/pkg/utils" + "github.com/spf13/cobra" + "github.com/spf13/pflag" + "github.com/streamnative/pulsarctl/pkg/cmdutils" +) + +func GetMessageIDCmd(vc *cmdutils.VerbCmd) { + desc := cmdutils.LongDescription{} + desc.CommandUsedFor = "Get message id by datetime for a topic." + desc.CommandPermission = "This command requires tenant admin permissions." + + desc.CommandExamples = []cmdutils.Example{ + { + Desc: "Get message id by datetime for a topic", + Command: "pulsarctl topics get-message-id --datetime 2021-06-28T16:53:08Z persistent://public/default/topic", + }, + } + + vc.SetDescription( + "get-message-id", + "Get message id by datetime for a topic", + desc.ToString(), + desc.ExampleToString(), + "get-message-id", + ) + + var datetime string + vc.FlagSetGroup.InFlagSet("GetMessageID", func(set *pflag.FlagSet) { + set.StringVarP(&datetime, "datetime", "d", "", "datetime in RFC3339 or RFC3339Nano format") + _ = cobra.MarkFlagRequired(set, "datetime") + }) + + vc.SetRunFuncWithNameArg(func() error { + return doGetMessageID(vc, datetime) + }, "the topic name is not specified or the topic name is specified more than one") + vc.EnableOutputFlagSet() +} + +func doGetMessageID(vc *cmdutils.VerbCmd, datetime string) error { + if vc.NameError != nil { + return vc.NameError + } + + topic, err := utils.GetTopicName(vc.NameArg) + if err != nil { + return err + } + + timestamp, err := time.Parse(time.RFC3339Nano, datetime) + if err != nil { + return err + } + + admin := cmdutils.NewPulsarClient() + messageID, err := admin.Topics().GetMessageID(*topic, timestamp.UnixMilli()) + if err == nil { + err = vc.OutputConfig.WriteOutput(vc.Command.OutOrStdout(), cmdutils.NewOutputContent().WithObject(messageID)) + } + return err +} diff --git a/pkg/ctl/topic/get_message_id_test.go b/pkg/ctl/topic/get_message_id_test.go new file mode 100644 index 000000000..e48aedd65 --- /dev/null +++ b/pkg/ctl/topic/get_message_id_test.go @@ -0,0 +1,36 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 topic + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestGetMessageIDRequiresDatetime(t *testing.T) { + _, _, _, err := TestTopicCommands(GetMessageIDCmd, []string{"get-message-id", "persistent://public/default/test"}) + assert.NotNil(t, err) + assert.Equal(t, "required flag(s) \"datetime\" not set", err.Error()) +} + +func TestGetMessageIDNameError(t *testing.T) { + _, _, nameErr, _ := TestTopicCommands(GetMessageIDCmd, []string{"get-message-id", "--datetime", "2021-06-28T16:53:08Z"}) + assert.NotNil(t, nameErr) + assert.Equal(t, "the topic name is not specified or the topic name is specified more than one", nameErr.Error()) +} diff --git a/pkg/ctl/topic/max_consumers_per_subscription.go b/pkg/ctl/topic/max_consumers_per_subscription.go new file mode 100644 index 000000000..2ba2080c5 --- /dev/null +++ b/pkg/ctl/topic/max_consumers_per_subscription.go @@ -0,0 +1,113 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 topic + +import ( + "github.com/apache/pulsar-client-go/pulsaradmin/pkg/utils" + "github.com/pkg/errors" + "github.com/spf13/cobra" + "github.com/spf13/pflag" + "github.com/streamnative/pulsarctl/pkg/cmdutils" +) + +func GetMaxConsumersPerSubscriptionCmd(vc *cmdutils.VerbCmd) { + vc.SetDescription("get-max-consumers-per-subscription", "Get max consumers per subscription for a topic", "Get max consumers per subscription for a topic", "", "get-max-consumers-per-subscription") + vc.SetRunFuncWithNameArg(func() error { + return doGetMaxConsumersPerSubscription(vc) + }, "the topic name is not specified or the topic name is specified more than one") +} + +func doGetMaxConsumersPerSubscription(vc *cmdutils.VerbCmd) error { + if vc.NameError != nil { + return vc.NameError + } + + topic, err := utils.GetTopicName(vc.NameArg) + if err != nil { + return err + } + + admin := cmdutils.NewPulsarClient() + value, err := admin.Topics().GetMaxConsumersPerSubscription(*topic) + if err == nil { + if value == -1 { + vc.Command.Printf("The max consumers per subscription of the topic %s is not set\n", topic.String()) + return nil + } + vc.Command.Printf("%d\n", value) + } + return err +} + +func SetMaxConsumersPerSubscriptionCmd(vc *cmdutils.VerbCmd) { + var size int + vc.SetDescription("set-max-consumers-per-subscription", "Set max consumers per subscription for a topic", "Set max consumers per subscription for a topic", "", "set-max-consumers-per-subscription") + vc.FlagSetGroup.InFlagSet("MaxConsumersPerSubscription", func(set *pflag.FlagSet) { + set.IntVar(&size, "size", -1, "max consumers per subscription") + _ = cobra.MarkFlagRequired(set, "size") + }) + vc.SetRunFuncWithNameArg(func() error { + return doSetMaxConsumersPerSubscription(vc, size) + }, "the topic name is not specified or the topic name is specified more than one") +} + +func doSetMaxConsumersPerSubscription(vc *cmdutils.VerbCmd, size int) error { + if vc.NameError != nil { + return vc.NameError + } + if size < 0 { + return errors.New("the specified consumers value must bigger than 0") + } + + topic, err := utils.GetTopicName(vc.NameArg) + if err != nil { + return err + } + + admin := cmdutils.NewPulsarClient() + err = admin.Topics().SetMaxConsumersPerSubscription(*topic, size) + if err == nil { + vc.Command.Printf("Set max consumers per subscription successfully for [%s]\n", topic.String()) + } + return err +} + +func RemoveMaxConsumersPerSubscriptionCmd(vc *cmdutils.VerbCmd) { + vc.SetDescription("remove-max-consumers-per-subscription", "Remove max consumers per subscription for a topic", "Remove max consumers per subscription for a topic", "", "remove-max-consumers-per-subscription") + vc.SetRunFuncWithNameArg(func() error { + return doRemoveMaxConsumersPerSubscription(vc) + }, "the topic name is not specified or the topic name is specified more than one") +} + +func doRemoveMaxConsumersPerSubscription(vc *cmdutils.VerbCmd) error { + if vc.NameError != nil { + return vc.NameError + } + + topic, err := utils.GetTopicName(vc.NameArg) + if err != nil { + return err + } + + admin := cmdutils.NewPulsarClient() + err = admin.Topics().RemoveMaxConsumersPerSubscription(*topic) + if err == nil { + vc.Command.Printf("Removed max consumers per subscription successfully for [%s]\n", topic.String()) + } + return err +} diff --git a/pkg/ctl/topic/subscribe_rate.go b/pkg/ctl/topic/subscribe_rate.go new file mode 100644 index 000000000..acbc0861f --- /dev/null +++ b/pkg/ctl/topic/subscribe_rate.go @@ -0,0 +1,106 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 topic + +import ( + "github.com/apache/pulsar-client-go/pulsaradmin/pkg/utils" + "github.com/spf13/pflag" + "github.com/streamnative/pulsarctl/pkg/cmdutils" +) + +func GetSubscribeRateCmd(vc *cmdutils.VerbCmd) { + vc.SetDescription("get-subscribe-rate", "Get subscribe rate for a topic", "Get subscribe rate for a topic", "", "get-subscribe-rate") + vc.SetRunFuncWithNameArg(func() error { + return doGetSubscribeRate(vc) + }, "the topic name is not specified or the topic name is specified more than one") + vc.EnableOutputFlagSet() +} + +func doGetSubscribeRate(vc *cmdutils.VerbCmd) error { + if vc.NameError != nil { + return vc.NameError + } + + topic, err := utils.GetTopicName(vc.NameArg) + if err != nil { + return err + } + + admin := cmdutils.NewPulsarClient() + rate, err := admin.Topics().GetSubscribeRate(*topic) + if err == nil { + err = vc.OutputConfig.WriteOutput(vc.Command.OutOrStdout(), cmdutils.NewOutputContent().WithObject(rate)) + } + return err +} + +func SetSubscribeRateCmd(vc *cmdutils.VerbCmd) { + data := *utils.NewSubscribeRate() + vc.SetDescription("set-subscribe-rate", "Set subscribe rate for a topic", "Set subscribe rate for a topic", "", "set-subscribe-rate") + vc.FlagSetGroup.InFlagSet("SubscribeRate", func(set *pflag.FlagSet) { + set.IntVarP(&data.SubscribeThrottlingRatePerConsumer, "subscribe-rate", "m", -1, "message dispatch rate") + set.IntVarP(&data.RatePeriodInSecond, "period", "p", 30, "dispatch rate period") + }) + vc.SetRunFuncWithNameArg(func() error { + return doSetSubscribeRate(vc, data) + }, "the topic name is not specified or the topic name is specified more than one") + vc.EnableOutputFlagSet() +} + +func doSetSubscribeRate(vc *cmdutils.VerbCmd, rate utils.SubscribeRate) error { + if vc.NameError != nil { + return vc.NameError + } + + topic, err := utils.GetTopicName(vc.NameArg) + if err != nil { + return err + } + + admin := cmdutils.NewPulsarClient() + err = admin.Topics().SetSubscribeRate(*topic, rate) + if err == nil { + vc.Command.Printf("Set subscribe rate successfully for [%s]\n", topic.String()) + } + return err +} + +func RemoveSubscribeRateCmd(vc *cmdutils.VerbCmd) { + vc.SetDescription("remove-subscribe-rate", "Remove subscribe rate for a topic", "Remove subscribe rate for a topic", "", "remove-subscribe-rate") + vc.SetRunFuncWithNameArg(func() error { + return doRemoveSubscribeRate(vc) + }, "the topic name is not specified or the topic name is specified more than one") +} + +func doRemoveSubscribeRate(vc *cmdutils.VerbCmd) error { + if vc.NameError != nil { + return vc.NameError + } + + topic, err := utils.GetTopicName(vc.NameArg) + if err != nil { + return err + } + + admin := cmdutils.NewPulsarClient() + err = admin.Topics().RemoveSubscribeRate(*topic) + if err == nil { + vc.Command.Printf("Removed subscribe rate successfully for [%s]\n", topic.String()) + } + return err +} diff --git a/pkg/ctl/topic/subscribe_rate_test.go b/pkg/ctl/topic/subscribe_rate_test.go new file mode 100644 index 000000000..74292ef44 --- /dev/null +++ b/pkg/ctl/topic/subscribe_rate_test.go @@ -0,0 +1,36 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 topic + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestRemoveSubscribeRateNameError(t *testing.T) { + _, _, nameErr, _ := TestTopicCommands(RemoveSubscribeRateCmd, []string{"remove-subscribe-rate"}) + assert.NotNil(t, nameErr) + assert.Equal(t, "the topic name is not specified or the topic name is specified more than one", nameErr.Error()) +} + +func TestSetMaxConsumersPerSubscriptionRejectsNegativeValue(t *testing.T) { + _, execErr, _, _ := TestTopicCommands(SetMaxConsumersPerSubscriptionCmd, []string{"set-max-consumers-per-subscription", "--size", "-1", "persistent://public/default/test"}) + assert.NotNil(t, execErr) + assert.Equal(t, "the specified consumers value must bigger than 0", execErr.Error()) +} diff --git a/pkg/ctl/topic/topic.go b/pkg/ctl/topic/topic.go index a6862f08f..cbf7046f7 100644 --- a/pkg/ctl/topic/topic.go +++ b/pkg/ctl/topic/topic.go @@ -49,6 +49,7 @@ func Command(flagGrouping *cmdutils.FlagGrouping) *cobra.Command { LookUpTopicCmd, GetBundleRangeCmd, GetLastMessageIDCmd, + GetMessageIDCmd, GetStatsCmd, GetInternalStatsCmd, GetInternalInfoCmd, @@ -61,6 +62,9 @@ func Command(flagGrouping *cmdutils.FlagGrouping) *cobra.Command { GetMaxConsumersCmd, SetMaxConsumersCmd, RemoveMaxConsumersCmd, + GetMaxConsumersPerSubscriptionCmd, + SetMaxConsumersPerSubscriptionCmd, + RemoveMaxConsumersPerSubscriptionCmd, GetMaxUnackMessagesPerConsumerCmd, SetMaxUnackMessagesPerConsumerCmd, RemoveMaxUnackMessagesPerConsumerCmd, @@ -94,6 +98,9 @@ func Command(flagGrouping *cmdutils.FlagGrouping) *cobra.Command { GetPublishRateCmd, SetPublishRateCmd, RemovePublishRateCmd, + GetSubscribeRateCmd, + SetSubscribeRateCmd, + RemoveSubscribeRateCmd, getPropertiesCmd, updatePropertiesCmd, removePropertiesCmd, diff --git a/pkg/ctl/topicpolicies/common.go b/pkg/ctl/topicpolicies/common.go index ee37a7363..6664aff11 100644 --- a/pkg/ctl/topicpolicies/common.go +++ b/pkg/ctl/topicpolicies/common.go @@ -18,6 +18,7 @@ package topicpolicies import ( + "context" "fmt" "io" @@ -38,6 +39,18 @@ func topicPolicies(global bool) (adminpkg.TopicPolicies, error) { return adminpkg.TopicPoliciesOf(cmdutils.NewPulsarClient(), global) } +func topicPolicyResources(vc *cmdutils.VerbCmd, global bool) (adminpkg.TopicPolicies, *utils.TopicName, error) { + topic, err := topicName(vc) + if err != nil { + return nil, nil, err + } + policies, err := topicPolicies(global) + if err != nil { + return nil, nil, err + } + return policies, topic, nil +} + func addScopeFlags(vc *cmdutils.VerbCmd, global *bool, applied *bool) { vc.FlagSetGroup.InFlagSet("TopicPolicyScope", func(set *pflag.FlagSet) { if global != nil { @@ -53,17 +66,119 @@ func writePolicyOutput(vc *cmdutils.VerbCmd, obj interface{}, text string, args if vc.OutputConfig == nil { vc.EnableOutputFlagSet() } + oc := cmdutils.NewOutputContent().WithObject(obj) + if text == "" { + return vc.OutputConfig.WriteOutput(vc.Command.OutOrStdout(), oc) + } return vc.OutputConfig.WriteOutput( vc.Command.OutOrStdout(), - cmdutils.NewOutputContent(). - WithObject(obj). + oc. WithTextFunc(func(w io.Writer) error { - if text == "" { - _, err := fmt.Fprintln(w, obj) - return err - } _, err := fmt.Fprintf(w, text, args...) return err }), ) } + +func getOptionalIntPolicyCmd( + vc *cmdutils.VerbCmd, + use string, + short string, + getter func(context.Context, adminpkg.TopicPolicies, utils.TopicName, bool) (*int, error), +) { + var global bool + var applied bool + vc.SetDescription(use, short, short, "", use) + addScopeFlags(vc, &global, &applied) + vc.EnableOutputFlagSet() + vc.SetRunFuncWithNameArg(func() error { + policies, topic, err := topicPolicyResources(vc, global) + if err != nil { + return err + } + value, err := getter(vc.Command.Context(), policies, *topic, applied) + if err != nil { + return err + } + return writePolicyOutput(vc, value, "") + }, "the topic name is not specified or the topic name is specified more than one") +} + +func getOptionalInt64PolicyCmd( + vc *cmdutils.VerbCmd, + use string, + short string, + getter func(context.Context, adminpkg.TopicPolicies, utils.TopicName, bool) (*int64, error), +) { + var global bool + var applied bool + vc.SetDescription(use, short, short, "", use) + addScopeFlags(vc, &global, &applied) + vc.EnableOutputFlagSet() + vc.SetRunFuncWithNameArg(func() error { + policies, topic, err := topicPolicyResources(vc, global) + if err != nil { + return err + } + value, err := getter(vc.Command.Context(), policies, *topic, applied) + if err != nil { + return err + } + return writePolicyOutput(vc, value, "") + }, "the topic name is not specified or the topic name is specified more than one") +} + +func getOptionalBoolPolicyCmd( + vc *cmdutils.VerbCmd, + use string, + short string, + getter func(context.Context, adminpkg.TopicPolicies, utils.TopicName, bool) (*bool, error), +) { + var global bool + var applied bool + vc.SetDescription(use, short, short, "", use) + addScopeFlags(vc, &global, &applied) + vc.EnableOutputFlagSet() + vc.SetRunFuncWithNameArg(func() error { + policies, topic, err := topicPolicyResources(vc, global) + if err != nil { + return err + } + value, err := getter(vc.Command.Context(), policies, *topic, applied) + if err != nil { + return err + } + return writePolicyOutput(vc, value, "") + }, "the topic name is not specified or the topic name is specified more than one") +} + +func setEnableDisablePolicyCmd( + vc *cmdutils.VerbCmd, + use string, + short string, + setter func(context.Context, adminpkg.TopicPolicies, utils.TopicName, bool) error, +) { + var global bool + var enable bool + var disable bool + vc.SetDescription(use, short, short, "", use) + addScopeFlags(vc, &global, nil) + vc.FlagSetGroup.InFlagSet("Policy", func(set *pflag.FlagSet) { + set.BoolVarP(&enable, "enable", "e", false, "enable policy") + set.BoolVarP(&disable, "disable", "d", false, "disable policy") + }) + vc.SetRunFuncWithNameArg(func() error { + if enable == disable { + return fmt.Errorf("need to specify either --enable or --disable") + } + policies, topic, err := topicPolicyResources(vc, global) + if err != nil { + return err + } + err = setter(vc.Command.Context(), policies, *topic, enable) + if err == nil { + vc.Command.Printf("%s successfully for [%s]\n", short, topic.String()) + } + return err + }, "the topic name is not specified or the topic name is specified more than one") +} diff --git a/pkg/ctl/topicpolicies/data_policies.go b/pkg/ctl/topicpolicies/data_policies.go new file mode 100644 index 000000000..ad8bfac04 --- /dev/null +++ b/pkg/ctl/topicpolicies/data_policies.go @@ -0,0 +1,396 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 topicpolicies + +import ( + "errors" + + util "github.com/apache/pulsar-client-go/pulsaradmin/pkg/utils" + "github.com/spf13/cobra" + "github.com/spf13/pflag" + "github.com/streamnative/pulsarctl/pkg/cmdutils" + ctlutils "github.com/streamnative/pulsarctl/pkg/ctl/utils" +) + +type backlogQuotaArgs struct { + limitSize string + limitTime int64 + policy string + quotaType string +} + +type inactiveTopicPoliciesArgs struct { + enableDeleteWhileInactive bool + disableDeleteWhileInactive bool + maxInactiveDuration string + deleteMode string +} + +func GetRetentionCmd(vc *cmdutils.VerbCmd) { + var global bool + var applied bool + vc.SetDescription("get-retention", "Get retention policy for a topic", "Get retention policy for a topic", "", "get-retention") + addScopeFlags(vc, &global, &applied) + vc.EnableOutputFlagSet() + vc.SetRunFuncWithNameArg(func() error { + policies, topic, err := topicPolicyResources(vc, global) + if err != nil { + return err + } + retention, err := policies.GetRetention(vc.Command.Context(), *topic, applied) + if err != nil { + return err + } + return writePolicyOutput(vc, retention, "") + }, "the topic name is not specified or the topic name is specified more than one") +} + +func SetRetentionCmd(vc *cmdutils.VerbCmd) { + var global bool + var timeStr string + var sizeStr string + vc.SetDescription("set-retention", "Set retention policy for a topic", "Set retention policy for a topic", "", "set-retention") + addScopeFlags(vc, &global, nil) + vc.FlagSetGroup.InFlagSet("Retention", func(set *pflag.FlagSet) { + set.StringVarP(&timeStr, "time", "t", "", "retention time with optional time unit suffix") + set.StringVarP(&sizeStr, "size", "s", "", "retention size limit") + _ = cobra.MarkFlagRequired(set, "time") + _ = cobra.MarkFlagRequired(set, "size") + }) + vc.SetRunFuncWithNameArg(func() error { + policies, topic, err := topicPolicyResources(vc, global) + if err != nil { + return err + } + retentionTimeInSeconds, err := ctlutils.ParseRelativeTimeInSeconds(timeStr) + if err != nil { + return err + } + sizeLimit, err := ctlutils.ValidateSizeString(sizeStr) + if err != nil { + return err + } + retentionTimeInMin := -1 + if retentionTimeInSeconds != -1 { + retentionTimeInMin = int(retentionTimeInSeconds.Minutes()) + } + retentionSizeInMB := -1 + if sizeLimit != -1 { + retentionSizeInMB = int(sizeLimit / (1024 * 1024)) + } + err = policies.SetRetention(vc.Command.Context(), *topic, util.NewRetentionPolicies(retentionTimeInMin, retentionSizeInMB)) + if err == nil { + vc.Command.Printf("Set retention policy successfully for [%s]\n", topic.String()) + } + return err + }, "the topic name is not specified or the topic name is specified more than one") +} + +func RemoveRetentionCmd(vc *cmdutils.VerbCmd) { + removePolicyCmd(vc, "remove-retention", "Removed retention policy for a topic", func(global bool) error { + policies, topic, err := topicPolicyResources(vc, global) + if err != nil { + return err + } + return policies.RemoveRetention(vc.Command.Context(), *topic) + }) +} + +func GetBacklogQuotaCmd(vc *cmdutils.VerbCmd) { + var global bool + var applied bool + vc.SetDescription("get-backlog-quota", "Get backlog quota for a topic", "Get backlog quota for a topic", "", "get-backlog-quota") + addScopeFlags(vc, &global, &applied) + vc.EnableOutputFlagSet() + vc.SetRunFuncWithNameArg(func() error { + policies, topic, err := topicPolicyResources(vc, global) + if err != nil { + return err + } + quota, err := policies.GetBacklogQuotaMap(vc.Command.Context(), *topic, applied) + if err != nil { + return err + } + return writePolicyOutput(vc, quota, "") + }, "the topic name is not specified or the topic name is specified more than one") +} + +func SetBacklogQuotaCmd(vc *cmdutils.VerbCmd) { + var global bool + args := backlogQuotaArgs{} + vc.SetDescription("set-backlog-quota", "Set backlog quota for a topic", "Set backlog quota for a topic", "", "set-backlog-quota") + addScopeFlags(vc, &global, nil) + vc.FlagSetGroup.InFlagSet("BacklogQuota", func(set *pflag.FlagSet) { + set.StringVarP(&args.limitSize, "limit-size", "", "", "size limit") + set.Int64VarP(&args.limitTime, "limit-time", "", -1, "time limit in seconds") + set.StringVarP(&args.policy, "policy", "p", "", "retention policy") + set.StringVarP(&args.quotaType, "type", "t", string(util.DestinationStorage), "backlog quota type") + _ = cobra.MarkFlagRequired(set, "policy") + }) + vc.SetRunFuncWithNameArg(func() error { + policies, topic, err := topicPolicyResources(vc, global) + if err != nil { + return err + } + sizeLimit := int64(-1) + if args.limitSize != "" { + sizeLimit, err = ctlutils.ValidateSizeString(args.limitSize) + if err != nil { + return err + } + } + policy, err := util.ParseRetentionPolicy(args.policy) + if err != nil { + return err + } + quotaType, err := util.ParseBacklogQuotaType(args.quotaType) + if err != nil { + return err + } + err = policies.SetBacklogQuota( + vc.Command.Context(), + *topic, + util.NewBacklogQuota(sizeLimit, args.limitTime, policy), + quotaType, + ) + if err == nil { + vc.Command.Printf("Set backlog quota successfully for [%s]\n", topic.String()) + } + return err + }, "the topic name is not specified or the topic name is specified more than one") +} + +func RemoveBacklogQuotaCmd(vc *cmdutils.VerbCmd) { + var global bool + var quotaType string + vc.SetDescription("remove-backlog-quota", "Remove backlog quota for a topic", "Remove backlog quota for a topic", "", "remove-backlog-quota") + addScopeFlags(vc, &global, nil) + vc.FlagSetGroup.InFlagSet("BacklogQuota", func(set *pflag.FlagSet) { + set.StringVarP("aType, "type", "t", string(util.DestinationStorage), "backlog quota type") + }) + vc.SetRunFuncWithNameArg(func() error { + policies, topic, err := topicPolicyResources(vc, global) + if err != nil { + return err + } + parsedType, err := util.ParseBacklogQuotaType(quotaType) + if err != nil { + return err + } + err = policies.RemoveBacklogQuota(vc.Command.Context(), *topic, parsedType) + if err == nil { + vc.Command.Printf("Removed backlog quota successfully for [%s]\n", topic.String()) + } + return err + }, "the topic name is not specified or the topic name is specified more than one") +} + +func GetPersistenceCmd(vc *cmdutils.VerbCmd) { + var global bool + var applied bool + vc.SetDescription("get-persistence", "Get persistence policy for a topic", "Get persistence policy for a topic", "", "get-persistence") + addScopeFlags(vc, &global, &applied) + vc.EnableOutputFlagSet() + vc.SetRunFuncWithNameArg(func() error { + policies, topic, err := topicPolicyResources(vc, global) + if err != nil { + return err + } + persistence, err := policies.GetPersistence(vc.Command.Context(), *topic, applied) + if err != nil { + return err + } + return writePolicyOutput(vc, persistence, "") + }, "the topic name is not specified or the topic name is specified more than one") +} + +func SetPersistenceCmd(vc *cmdutils.VerbCmd) { + var global bool + data := util.PersistenceData{} + vc.SetDescription("set-persistence", "Set persistence policy for a topic", "Set persistence policy for a topic", "", "set-persistence") + addScopeFlags(vc, &global, nil) + vc.FlagSetGroup.InFlagSet("Persistence", func(set *pflag.FlagSet) { + set.Int64VarP(&data.BookkeeperEnsemble, "bookkeeper-ensemble", "e", 0, "number of bookies") + set.Int64VarP(&data.BookkeeperWriteQuorum, "bookkeeper-write-quorum", "w", 0, "bookkeeper write quorum") + set.Int64VarP(&data.BookkeeperAckQuorum, "bookkeeper-ack-quorum", "a", 0, "bookkeeper ack quorum") + set.Float64VarP(&data.ManagedLedgerMaxMarkDeleteRate, "ml-mark-delete-max-rate", "r", 0.0, "managed ledger max mark delete rate") + }) + vc.SetRunFuncWithNameArg(func() error { + if data.BookkeeperEnsemble <= 0 || data.BookkeeperWriteQuorum <= 0 || data.BookkeeperAckQuorum <= 0 { + return errors.New("[--bookkeeper-ensemble], [--bookkeeper-write-quorum] and [--bookkeeper-ack-quorum] must greater than 0") + } + policies, topic, err := topicPolicyResources(vc, global) + if err != nil { + return err + } + err = policies.SetPersistence(vc.Command.Context(), *topic, data) + if err == nil { + vc.Command.Printf("Set persistence policy successfully for [%s]\n", topic.String()) + } + return err + }, "the topic name is not specified or the topic name is specified more than one") +} + +func RemovePersistenceCmd(vc *cmdutils.VerbCmd) { + removePolicyCmd(vc, "remove-persistence", "Removed persistence policy for a topic", func(global bool) error { + policies, topic, err := topicPolicyResources(vc, global) + if err != nil { + return err + } + return policies.RemovePersistence(vc.Command.Context(), *topic) + }) +} + +func GetDelayedDeliveryCmd(vc *cmdutils.VerbCmd) { + var global bool + var applied bool + vc.SetDescription("get-delayed-delivery", "Get delayed delivery policy for a topic", "Get delayed delivery policy for a topic", "", "get-delayed-delivery") + addScopeFlags(vc, &global, &applied) + vc.EnableOutputFlagSet() + vc.SetRunFuncWithNameArg(func() error { + policies, topic, err := topicPolicyResources(vc, global) + if err != nil { + return err + } + delayed, err := policies.GetDelayedDelivery(vc.Command.Context(), *topic, applied) + if err != nil { + return err + } + return writePolicyOutput(vc, delayed, "") + }, "the topic name is not specified or the topic name is specified more than one") +} + +func SetDelayedDeliveryCmd(vc *cmdutils.VerbCmd) { + var global bool + var enable bool + var disable bool + var tickTime string + var maxDelay string + vc.SetDescription("set-delayed-delivery", "Set delayed delivery policy for a topic", "Set delayed delivery policy for a topic", "", "set-delayed-delivery") + addScopeFlags(vc, &global, nil) + vc.FlagSetGroup.InFlagSet("DelayedDelivery", func(set *pflag.FlagSet) { + set.BoolVarP(&enable, "enable", "e", false, "enable delayed delivery messages") + set.BoolVarP(&disable, "disable", "d", false, "disable delayed delivery messages") + set.StringVarP(&tickTime, "time", "t", "1s", "tick time for delayed delivery") + set.StringVarP(&maxDelay, "max-delay", "", "0s", "max allowed delay for delayed delivery") + }) + vc.SetRunFuncWithNameArg(func() error { + if enable == disable { + return errors.New("need to specify either --enable or --disable") + } + policies, topic, err := topicPolicyResources(vc, global) + if err != nil { + return err + } + tickTimeInSeconds, err := ctlutils.ParseRelativeTimeInSeconds(tickTime) + if err != nil { + return err + } + maxDelayInSeconds, err := ctlutils.ParseRelativeTimeInSeconds(maxDelay) + if err != nil { + return err + } + data := util.NewDelayedDeliveryDataWithMaxDelay( + tickTimeInSeconds.Seconds()*1000, + enable, + int64(maxDelayInSeconds.Seconds()*1000), + ) + err = policies.SetDelayedDelivery(vc.Command.Context(), *topic, *data) + if err == nil { + vc.Command.Printf("Set delayed delivery policy successfully for [%s]\n", topic.String()) + } + return err + }, "the topic name is not specified or the topic name is specified more than one") +} + +func RemoveDelayedDeliveryCmd(vc *cmdutils.VerbCmd) { + removePolicyCmd(vc, "remove-delayed-delivery", "Removed delayed delivery policy for a topic", func(global bool) error { + policies, topic, err := topicPolicyResources(vc, global) + if err != nil { + return err + } + return policies.RemoveDelayedDelivery(vc.Command.Context(), *topic) + }) +} + +func GetInactiveTopicPoliciesCmd(vc *cmdutils.VerbCmd) { + var global bool + var applied bool + vc.SetDescription("get-inactive-topic-policies", "Get inactive topic policies for a topic", "Get inactive topic policies for a topic", "", "get-inactive-topic-policies") + addScopeFlags(vc, &global, &applied) + vc.EnableOutputFlagSet() + vc.SetRunFuncWithNameArg(func() error { + policies, topic, err := topicPolicyResources(vc, global) + if err != nil { + return err + } + inactive, err := policies.GetInactiveTopicPolicies(vc.Command.Context(), *topic, applied) + if err != nil { + return err + } + return writePolicyOutput(vc, inactive, "") + }, "the topic name is not specified or the topic name is specified more than one") +} + +func SetInactiveTopicPoliciesCmd(vc *cmdutils.VerbCmd) { + var global bool + args := inactiveTopicPoliciesArgs{} + vc.SetDescription("set-inactive-topic-policies", "Set inactive topic policies for a topic", "Set inactive topic policies for a topic", "", "set-inactive-topic-policies") + addScopeFlags(vc, &global, nil) + vc.FlagSetGroup.InFlagSet("InactiveTopicPolicies", func(set *pflag.FlagSet) { + set.BoolVarP(&args.enableDeleteWhileInactive, "enable-delete-while-inactive", "e", false, "enable delete while inactive") + set.BoolVarP(&args.disableDeleteWhileInactive, "disable-delete-while-inactive", "d", false, "disable delete while inactive") + set.StringVarP(&args.maxInactiveDuration, "max-inactive-duration", "t", "", "max inactive duration") + set.StringVarP(&args.deleteMode, "delete-mode", "m", "", "delete mode") + _ = cobra.MarkFlagRequired(set, "max-inactive-duration") + _ = cobra.MarkFlagRequired(set, "delete-mode") + }) + vc.SetRunFuncWithNameArg(func() error { + if args.enableDeleteWhileInactive == args.disableDeleteWhileInactive { + return errors.New("need to specify either --enable-delete-while-inactive or --disable-delete-while-inactive") + } + policies, topic, err := topicPolicyResources(vc, global) + if err != nil { + return err + } + mode, err := util.ParseInactiveTopicDeleteMode(args.deleteMode) + if err != nil { + return err + } + maxInactiveDuration, err := ctlutils.ParseRelativeTimeInSeconds(args.maxInactiveDuration) + if err != nil { + return err + } + body := util.NewInactiveTopicPolicies(&mode, int(maxInactiveDuration.Seconds()), args.enableDeleteWhileInactive) + err = policies.SetInactiveTopicPolicies(vc.Command.Context(), *topic, body) + if err == nil { + vc.Command.Printf("Set inactive topic policies successfully for [%s]\n", topic.String()) + } + return err + }, "the topic name is not specified or the topic name is specified more than one") +} + +func RemoveInactiveTopicPoliciesCmd(vc *cmdutils.VerbCmd) { + removePolicyCmd(vc, "remove-inactive-topic-policies", "Removed inactive topic policies for a topic", func(global bool) error { + policies, topic, err := topicPolicyResources(vc, global) + if err != nil { + return err + } + return policies.RemoveInactiveTopicPolicies(vc.Command.Context(), *topic) + }) +} diff --git a/pkg/ctl/topicpolicies/rate_policies.go b/pkg/ctl/topicpolicies/rate_policies.go new file mode 100644 index 000000000..3f70c00ad --- /dev/null +++ b/pkg/ctl/topicpolicies/rate_policies.go @@ -0,0 +1,232 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 topicpolicies + +import ( + "github.com/apache/pulsar-client-go/pulsaradmin/pkg/utils" + "github.com/spf13/pflag" + "github.com/streamnative/pulsarctl/pkg/cmdutils" +) + +func GetDispatchRateCmd(vc *cmdutils.VerbCmd) { + var global bool + var applied bool + vc.SetDescription("get-dispatch-rate", "Get message dispatch rate for a topic", "Get message dispatch rate for a topic", "", "get-dispatch-rate") + addScopeFlags(vc, &global, &applied) + vc.EnableOutputFlagSet() + vc.SetRunFuncWithNameArg(func() error { + policies, topic, err := topicPolicyResources(vc, global) + if err != nil { + return err + } + rate, err := policies.GetDispatchRate(vc.Command.Context(), *topic, applied) + if err != nil { + return err + } + return writePolicyOutput(vc, rate, "") + }, "the topic name is not specified or the topic name is specified more than one") +} + +func SetDispatchRateCmd(vc *cmdutils.VerbCmd) { + var global bool + data := utils.DispatchRateData{} + vc.SetDescription("set-dispatch-rate", "Set message dispatch rate for a topic", "Set message dispatch rate for a topic", "", "set-dispatch-rate") + addScopeFlags(vc, &global, nil) + vc.FlagSetGroup.InFlagSet("DispatchRate", func(set *pflag.FlagSet) { + set.Int64VarP(&data.DispatchThrottlingRateInMsg, "msg-dispatch-rate", "", -1, "message dispatch rate") + set.Int64VarP(&data.DispatchThrottlingRateInByte, "byte-dispatch-rate", "", -1, "byte dispatch rate") + set.Int64VarP(&data.RatePeriodInSecond, "dispatch-rate-period", "", 1, "dispatch rate period in seconds") + set.BoolVarP(&data.RelativeToPublishRate, "relative-to-publish-rate", "", false, "relative to publish rate") + }) + vc.SetRunFuncWithNameArg(func() error { + policies, topic, err := topicPolicyResources(vc, global) + if err != nil { + return err + } + err = policies.SetDispatchRate(vc.Command.Context(), *topic, data) + if err == nil { + vc.Command.Printf("Set dispatch rate successfully for [%s]\n", topic.String()) + } + return err + }, "the topic name is not specified or the topic name is specified more than one") +} + +func RemoveDispatchRateCmd(vc *cmdutils.VerbCmd) { + removePolicyCmd(vc, "remove-dispatch-rate", "Removed dispatch rate for a topic", func(global bool) error { + policies, topic, err := topicPolicyResources(vc, global) + if err != nil { + return err + } + return policies.RemoveDispatchRate(vc.Command.Context(), *topic) + }) +} + +func GetSubscriptionDispatchRateCmd(vc *cmdutils.VerbCmd) { + var global bool + var applied bool + vc.SetDescription("get-subscription-dispatch-rate", "Get subscription dispatch rate for a topic", "Get subscription dispatch rate for a topic", "", "get-subscription-dispatch-rate") + addScopeFlags(vc, &global, &applied) + vc.EnableOutputFlagSet() + vc.SetRunFuncWithNameArg(func() error { + policies, topic, err := topicPolicyResources(vc, global) + if err != nil { + return err + } + rate, err := policies.GetSubscriptionDispatchRate(vc.Command.Context(), *topic, applied) + if err != nil { + return err + } + return writePolicyOutput(vc, rate, "") + }, "the topic name is not specified or the topic name is specified more than one") +} + +func SetSubscriptionDispatchRateCmd(vc *cmdutils.VerbCmd) { + var global bool + data := utils.DispatchRateData{} + vc.SetDescription("set-subscription-dispatch-rate", "Set subscription dispatch rate for a topic", "Set subscription dispatch rate for a topic", "", "set-subscription-dispatch-rate") + addScopeFlags(vc, &global, nil) + vc.FlagSetGroup.InFlagSet("SubscriptionDispatchRate", func(set *pflag.FlagSet) { + set.Int64VarP(&data.DispatchThrottlingRateInMsg, "msg-dispatch-rate", "", -1, "message dispatch rate") + set.Int64VarP(&data.DispatchThrottlingRateInByte, "byte-dispatch-rate", "", -1, "byte dispatch rate") + set.Int64VarP(&data.RatePeriodInSecond, "dispatch-rate-period", "", 1, "dispatch rate period in seconds") + set.BoolVarP(&data.RelativeToPublishRate, "relative-to-publish-rate", "", false, "relative to publish rate") + }) + vc.SetRunFuncWithNameArg(func() error { + policies, topic, err := topicPolicyResources(vc, global) + if err != nil { + return err + } + err = policies.SetSubscriptionDispatchRate(vc.Command.Context(), *topic, data) + if err == nil { + vc.Command.Printf("Set subscription dispatch rate successfully for [%s]\n", topic.String()) + } + return err + }, "the topic name is not specified or the topic name is specified more than one") +} + +func RemoveSubscriptionDispatchRateCmd(vc *cmdutils.VerbCmd) { + removePolicyCmd(vc, "remove-subscription-dispatch-rate", "Removed subscription dispatch rate for a topic", func(global bool) error { + policies, topic, err := topicPolicyResources(vc, global) + if err != nil { + return err + } + return policies.RemoveSubscriptionDispatchRate(vc.Command.Context(), *topic) + }) +} + +func GetPublishRateCmd(vc *cmdutils.VerbCmd) { + var global bool + var applied bool + vc.SetDescription("get-publish-rate", "Get publish rate for a topic", "Get publish rate for a topic", "", "get-publish-rate") + addScopeFlags(vc, &global, &applied) + vc.EnableOutputFlagSet() + vc.SetRunFuncWithNameArg(func() error { + policies, topic, err := topicPolicyResources(vc, global) + if err != nil { + return err + } + rate, err := policies.GetPublishRate(vc.Command.Context(), *topic, applied) + if err != nil { + return err + } + return writePolicyOutput(vc, rate, "") + }, "the topic name is not specified or the topic name is specified more than one") +} + +func SetPublishRateCmd(vc *cmdutils.VerbCmd) { + var global bool + data := utils.PublishRateData{} + vc.SetDescription("set-publish-rate", "Set publish rate for a topic", "Set publish rate for a topic", "", "set-publish-rate") + addScopeFlags(vc, &global, nil) + vc.FlagSetGroup.InFlagSet("PublishRate", func(set *pflag.FlagSet) { + set.Int64VarP(&data.PublishThrottlingRateInMsg, "msg-publish-rate", "", -1, "message publish rate") + set.Int64VarP(&data.PublishThrottlingRateInByte, "byte-publish-rate", "", -1, "byte publish rate") + }) + vc.SetRunFuncWithNameArg(func() error { + policies, topic, err := topicPolicyResources(vc, global) + if err != nil { + return err + } + err = policies.SetPublishRate(vc.Command.Context(), *topic, data) + if err == nil { + vc.Command.Printf("Set publish rate successfully for [%s]\n", topic.String()) + } + return err + }, "the topic name is not specified or the topic name is specified more than one") +} + +func RemovePublishRateCmd(vc *cmdutils.VerbCmd) { + removePolicyCmd(vc, "remove-publish-rate", "Removed publish rate for a topic", func(global bool) error { + policies, topic, err := topicPolicyResources(vc, global) + if err != nil { + return err + } + return policies.RemovePublishRate(vc.Command.Context(), *topic) + }) +} + +func GetSubscribeRateCmd(vc *cmdutils.VerbCmd) { + var global bool + var applied bool + vc.SetDescription("get-subscribe-rate", "Get subscribe rate for a topic", "Get subscribe rate for a topic", "", "get-subscribe-rate") + addScopeFlags(vc, &global, &applied) + vc.EnableOutputFlagSet() + vc.SetRunFuncWithNameArg(func() error { + policies, topic, err := topicPolicyResources(vc, global) + if err != nil { + return err + } + rate, err := policies.GetSubscribeRate(vc.Command.Context(), *topic, applied) + if err != nil { + return err + } + return writePolicyOutput(vc, rate, "") + }, "the topic name is not specified or the topic name is specified more than one") +} + +func SetSubscribeRateCmd(vc *cmdutils.VerbCmd) { + var global bool + data := *utils.NewSubscribeRate() + vc.SetDescription("set-subscribe-rate", "Set subscribe rate for a topic", "Set subscribe rate for a topic", "", "set-subscribe-rate") + addScopeFlags(vc, &global, nil) + vc.FlagSetGroup.InFlagSet("SubscribeRate", func(set *pflag.FlagSet) { + set.IntVarP(&data.SubscribeThrottlingRatePerConsumer, "subscribe-rate", "m", -1, "message dispatch rate") + set.IntVarP(&data.RatePeriodInSecond, "period", "p", 30, "dispatch rate period") + }) + vc.SetRunFuncWithNameArg(func() error { + policies, topic, err := topicPolicyResources(vc, global) + if err != nil { + return err + } + err = policies.SetSubscribeRate(vc.Command.Context(), *topic, data) + if err == nil { + vc.Command.Printf("Set subscribe rate successfully for [%s]\n", topic.String()) + } + return err + }, "the topic name is not specified or the topic name is specified more than one") +} + +func RemoveSubscribeRateCmd(vc *cmdutils.VerbCmd) { + removePolicyCmd(vc, "remove-subscribe-rate", "Removed subscribe rate for a topic", func(global bool) error { + policies, topic, err := topicPolicyResources(vc, global) + if err != nil { + return err + } + return policies.RemoveSubscribeRate(vc.Command.Context(), *topic) + }) +} diff --git a/pkg/ctl/topicpolicies/scalar_policies.go b/pkg/ctl/topicpolicies/scalar_policies.go new file mode 100644 index 000000000..0fb051a68 --- /dev/null +++ b/pkg/ctl/topicpolicies/scalar_policies.go @@ -0,0 +1,374 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 topicpolicies + +import ( + "context" + + "github.com/apache/pulsar-client-go/pulsaradmin/pkg/admin" + "github.com/apache/pulsar-client-go/pulsaradmin/pkg/utils" + "github.com/spf13/cobra" + "github.com/spf13/pflag" + "github.com/streamnative/pulsarctl/pkg/cmdutils" + ctlutils "github.com/streamnative/pulsarctl/pkg/ctl/utils" +) + +func GetMessageTTLCmd(vc *cmdutils.VerbCmd) { + getOptionalIntPolicyCmd( + vc, + "get-message-ttl", + "Get message TTL for a topic", + func(ctx context.Context, policies admin.TopicPolicies, topic utils.TopicName, applied bool) (*int, error) { + return policies.GetMessageTTL(ctx, topic, applied) + }, + ) +} + +func SetMessageTTLCmd(vc *cmdutils.VerbCmd) { + var global bool + var ttl string + vc.SetDescription("set-message-ttl", "Set message TTL for a topic", "Set message TTL for a topic", "", "set-message-ttl") + addScopeFlags(vc, &global, nil) + vc.FlagSetGroup.InFlagSet("MessageTTL", func(set *pflag.FlagSet) { + set.StringVarP(&ttl, "ttl", "t", "", "message TTL for topic with optional time unit suffix") + _ = cobra.MarkFlagRequired(set, "ttl") + }) + vc.SetRunFuncWithNameArg(func() error { + policies, topic, err := topicPolicyResources(vc, global) + if err != nil { + return err + } + seconds, err := ctlutils.ParseRelativeTimeInSeconds(ttl) + if err != nil { + return err + } + messageTTL := -1 + if seconds != -1 { + messageTTL = int(seconds.Seconds()) + } + err = policies.SetMessageTTL(vc.Command.Context(), *topic, messageTTL) + if err == nil { + vc.Command.Printf("Set message TTL successfully for [%s]\n", topic.String()) + } + return err + }, "the topic name is not specified or the topic name is specified more than one") +} + +func RemoveMessageTTLCmd(vc *cmdutils.VerbCmd) { + removePolicyCmd(vc, "remove-message-ttl", "Removed message TTL for a topic", func(global bool) error { + policies, topic, err := topicPolicyResources(vc, global) + if err != nil { + return err + } + return policies.RemoveMessageTTL(vc.Command.Context(), *topic) + }) +} + +func GetMaxUnackMessagesPerConsumerCmd(vc *cmdutils.VerbCmd) { + getOptionalIntPolicyCmd( + vc, + "get-max-unacked-messages-per-consumer", + "Get max unacked messages per consumer for a topic", + func(ctx context.Context, policies admin.TopicPolicies, topic utils.TopicName, applied bool) (*int, error) { + return policies.GetMaxUnackMessagesPerConsumer(ctx, topic, applied) + }, + ) +} + +func SetMaxUnackMessagesPerConsumerCmd(vc *cmdutils.VerbCmd) { + var global bool + var maxNum int + vc.SetDescription("set-max-unacked-messages-per-consumer", "Set max unacked messages per consumer for a topic", "Set max unacked messages per consumer for a topic", "", "set-max-unacked-messages-per-consumer") + addScopeFlags(vc, &global, nil) + vc.FlagSetGroup.InFlagSet("MaxUnackedMessagesPerConsumer", func(set *pflag.FlagSet) { + set.IntVarP(&maxNum, "maxNum", "m", 0, "max unacked messages num on consumer") + _ = cobra.MarkFlagRequired(set, "maxNum") + }) + vc.SetRunFuncWithNameArg(func() error { + policies, topic, err := topicPolicyResources(vc, global) + if err != nil { + return err + } + err = policies.SetMaxUnackMessagesPerConsumer(vc.Command.Context(), *topic, maxNum) + if err == nil { + vc.Command.Printf("Set max unacked messages per consumer successfully for [%s]\n", topic.String()) + } + return err + }, "the topic name is not specified or the topic name is specified more than one") +} + +func RemoveMaxUnackMessagesPerConsumerCmd(vc *cmdutils.VerbCmd) { + removePolicyCmd(vc, "remove-max-unacked-messages-per-consumer", "Removed max unacked messages per consumer for a topic", func(global bool) error { + policies, topic, err := topicPolicyResources(vc, global) + if err != nil { + return err + } + return policies.RemoveMaxUnackMessagesPerConsumer(vc.Command.Context(), *topic) + }) +} + +func GetMaxUnackMessagesPerSubscriptionCmd(vc *cmdutils.VerbCmd) { + getOptionalIntPolicyCmd( + vc, + "get-max-unacked-messages-per-subscription", + "Get max unacked messages per subscription for a topic", + func(ctx context.Context, policies admin.TopicPolicies, topic utils.TopicName, applied bool) (*int, error) { + return policies.GetMaxUnackMessagesPerSubscription(ctx, topic, applied) + }, + ) +} + +func SetMaxUnackMessagesPerSubscriptionCmd(vc *cmdutils.VerbCmd) { + var global bool + var maxNum int + vc.SetDescription("set-max-unacked-messages-per-subscription", "Set max unacked messages per subscription for a topic", "Set max unacked messages per subscription for a topic", "", "set-max-unacked-messages-per-subscription") + addScopeFlags(vc, &global, nil) + vc.FlagSetGroup.InFlagSet("MaxUnackedMessagesPerSubscription", func(set *pflag.FlagSet) { + set.IntVarP(&maxNum, "maxNum", "m", 0, "max unacked messages num on subscription") + _ = cobra.MarkFlagRequired(set, "maxNum") + }) + vc.SetRunFuncWithNameArg(func() error { + policies, topic, err := topicPolicyResources(vc, global) + if err != nil { + return err + } + err = policies.SetMaxUnackMessagesPerSubscription(vc.Command.Context(), *topic, maxNum) + if err == nil { + vc.Command.Printf("Set max unacked messages per subscription successfully for [%s]\n", topic.String()) + } + return err + }, "the topic name is not specified or the topic name is specified more than one") +} + +func RemoveMaxUnackMessagesPerSubscriptionCmd(vc *cmdutils.VerbCmd) { + removePolicyCmd(vc, "remove-max-unacked-messages-per-subscription", "Removed max unacked messages per subscription for a topic", func(global bool) error { + policies, topic, err := topicPolicyResources(vc, global) + if err != nil { + return err + } + return policies.RemoveMaxUnackMessagesPerSubscription(vc.Command.Context(), *topic) + }) +} + +func GetMaxConsumersPerSubscriptionCmd(vc *cmdutils.VerbCmd) { + getOptionalIntPolicyCmd( + vc, + "get-max-consumers-per-subscription", + "Get max consumers per subscription for a topic", + func(ctx context.Context, policies admin.TopicPolicies, topic utils.TopicName, applied bool) (*int, error) { + return policies.GetMaxConsumersPerSubscription(ctx, topic, applied) + }, + ) +} + +func SetMaxConsumersPerSubscriptionCmd(vc *cmdutils.VerbCmd) { + var global bool + var maxConsumers int + vc.SetDescription("set-max-consumers-per-subscription", "Set max consumers per subscription for a topic", "Set max consumers per subscription for a topic", "", "set-max-consumers-per-subscription") + addScopeFlags(vc, &global, nil) + vc.FlagSetGroup.InFlagSet("MaxConsumersPerSubscription", func(set *pflag.FlagSet) { + set.IntVarP(&maxConsumers, "max-consumers-per-subscription", "c", 0, "max consumers per subscription for a topic") + _ = cobra.MarkFlagRequired(set, "max-consumers-per-subscription") + }) + vc.SetRunFuncWithNameArg(func() error { + policies, topic, err := topicPolicyResources(vc, global) + if err != nil { + return err + } + err = policies.SetMaxConsumersPerSubscription(vc.Command.Context(), *topic, maxConsumers) + if err == nil { + vc.Command.Printf("Set max consumers per subscription successfully for [%s]\n", topic.String()) + } + return err + }, "the topic name is not specified or the topic name is specified more than one") +} + +func RemoveMaxConsumersPerSubscriptionCmd(vc *cmdutils.VerbCmd) { + removePolicyCmd(vc, "remove-max-consumers-per-subscription", "Removed max consumers per subscription for a topic", func(global bool) error { + policies, topic, err := topicPolicyResources(vc, global) + if err != nil { + return err + } + return policies.RemoveMaxConsumersPerSubscription(vc.Command.Context(), *topic) + }) +} + +func GetMaxConsumersCmd(vc *cmdutils.VerbCmd) { + getOptionalIntPolicyCmd( + vc, + "get-max-consumers", + "Get max consumers for a topic", + func(ctx context.Context, policies admin.TopicPolicies, topic utils.TopicName, applied bool) (*int, error) { + return policies.GetMaxConsumers(ctx, topic, applied) + }, + ) +} + +func SetMaxConsumersCmd(vc *cmdutils.VerbCmd) { + var global bool + var maxConsumers int + vc.SetDescription("set-max-consumers", "Set max consumers for a topic", "Set max consumers for a topic", "", "set-max-consumers") + addScopeFlags(vc, &global, nil) + vc.FlagSetGroup.InFlagSet("MaxConsumers", func(set *pflag.FlagSet) { + set.IntVarP(&maxConsumers, "max-consumers", "c", 0, "max consumers for a topic") + _ = cobra.MarkFlagRequired(set, "max-consumers") + }) + vc.SetRunFuncWithNameArg(func() error { + policies, topic, err := topicPolicyResources(vc, global) + if err != nil { + return err + } + err = policies.SetMaxConsumers(vc.Command.Context(), *topic, maxConsumers) + if err == nil { + vc.Command.Printf("Set max consumers successfully for [%s]\n", topic.String()) + } + return err + }, "the topic name is not specified or the topic name is specified more than one") +} + +func RemoveMaxConsumersCmd(vc *cmdutils.VerbCmd) { + removePolicyCmd(vc, "remove-max-consumers", "Removed max consumers for a topic", func(global bool) error { + policies, topic, err := topicPolicyResources(vc, global) + if err != nil { + return err + } + return policies.RemoveMaxConsumers(vc.Command.Context(), *topic) + }) +} + +func GetMaxProducersCmd(vc *cmdutils.VerbCmd) { + getOptionalIntPolicyCmd( + vc, + "get-max-producers", + "Get max producers for a topic", + func(ctx context.Context, policies admin.TopicPolicies, topic utils.TopicName, applied bool) (*int, error) { + return policies.GetMaxProducers(ctx, topic, applied) + }, + ) +} + +func SetMaxProducersCmd(vc *cmdutils.VerbCmd) { + var global bool + var maxProducers int + vc.SetDescription("set-max-producers", "Set max producers for a topic", "Set max producers for a topic", "", "set-max-producers") + addScopeFlags(vc, &global, nil) + vc.FlagSetGroup.InFlagSet("MaxProducers", func(set *pflag.FlagSet) { + set.IntVarP(&maxProducers, "max-producers", "p", 0, "max producers for a topic") + _ = cobra.MarkFlagRequired(set, "max-producers") + }) + vc.SetRunFuncWithNameArg(func() error { + policies, topic, err := topicPolicyResources(vc, global) + if err != nil { + return err + } + err = policies.SetMaxProducers(vc.Command.Context(), *topic, maxProducers) + if err == nil { + vc.Command.Printf("Set max producers successfully for [%s]\n", topic.String()) + } + return err + }, "the topic name is not specified or the topic name is specified more than one") +} + +func RemoveMaxProducersCmd(vc *cmdutils.VerbCmd) { + removePolicyCmd(vc, "remove-max-producers", "Removed max producers for a topic", func(global bool) error { + policies, topic, err := topicPolicyResources(vc, global) + if err != nil { + return err + } + return policies.RemoveMaxProducers(vc.Command.Context(), *topic) + }) +} + +func GetCompactionThresholdCmd(vc *cmdutils.VerbCmd) { + getOptionalInt64PolicyCmd( + vc, + "get-compaction-threshold", + "Get compaction threshold for a topic", + func(ctx context.Context, policies admin.TopicPolicies, topic utils.TopicName, applied bool) (*int64, error) { + return policies.GetCompactionThreshold(ctx, topic, applied) + }, + ) +} + +func SetCompactionThresholdCmd(vc *cmdutils.VerbCmd) { + var global bool + var threshold string + vc.SetDescription("set-compaction-threshold", "Set compaction threshold for a topic", "Set compaction threshold for a topic", "", "set-compaction-threshold") + addScopeFlags(vc, &global, nil) + vc.FlagSetGroup.InFlagSet("CompactionThreshold", func(set *pflag.FlagSet) { + set.StringVarP(&threshold, "threshold", "t", "", "maximum backlog before compaction is triggered") + _ = cobra.MarkFlagRequired(set, "threshold") + }) + vc.SetRunFuncWithNameArg(func() error { + policies, topic, err := topicPolicyResources(vc, global) + if err != nil { + return err + } + value, err := ctlutils.ValidateSizeString(threshold) + if err != nil { + return err + } + err = policies.SetCompactionThreshold(vc.Command.Context(), *topic, value) + if err == nil { + vc.Command.Printf("Set compaction threshold successfully for [%s]\n", topic.String()) + } + return err + }, "the topic name is not specified or the topic name is specified more than one") +} + +func RemoveCompactionThresholdCmd(vc *cmdutils.VerbCmd) { + removePolicyCmd(vc, "remove-compaction-threshold", "Removed compaction threshold for a topic", func(global bool) error { + policies, topic, err := topicPolicyResources(vc, global) + if err != nil { + return err + } + return policies.RemoveCompactionThreshold(vc.Command.Context(), *topic) + }) +} + +func GetDeduplicationCmd(vc *cmdutils.VerbCmd) { + getOptionalBoolPolicyCmd( + vc, + "get-deduplication", + "Get deduplication status for a topic", + func(ctx context.Context, policies admin.TopicPolicies, topic utils.TopicName, applied bool) (*bool, error) { + return policies.GetDeduplicationStatus(ctx, topic, applied) + }, + ) +} + +func SetDeduplicationCmd(vc *cmdutils.VerbCmd) { + setEnableDisablePolicyCmd( + vc, + "set-deduplication", + "Set deduplication status for a topic", + func(ctx context.Context, policies admin.TopicPolicies, topic utils.TopicName, enabled bool) error { + return policies.SetDeduplicationStatus(ctx, topic, enabled) + }, + ) +} + +func RemoveDeduplicationCmd(vc *cmdutils.VerbCmd) { + removePolicyCmd(vc, "remove-deduplication", "Removed deduplication status for a topic", func(global bool) error { + policies, topic, err := topicPolicyResources(vc, global) + if err != nil { + return err + } + return policies.RemoveDeduplicationStatus(vc.Command.Context(), *topic) + }) +} diff --git a/pkg/ctl/topicpolicies/schema_compatibility_strategy.go b/pkg/ctl/topicpolicies/schema_compatibility_strategy.go index e3cd9c133..25c3b69e9 100644 --- a/pkg/ctl/topicpolicies/schema_compatibility_strategy.go +++ b/pkg/ctl/topicpolicies/schema_compatibility_strategy.go @@ -45,7 +45,7 @@ func GetSchemaCompatibilityStrategyCmd(vc *cmdutils.VerbCmd) { return err } if value == nil { - return writePolicyOutput(vc, "", "\n") + return writePolicyOutput(vc, nil, "") } return writePolicyOutput(vc, value.String(), "%s\n", value.String()) }, "the topic name is not specified or the topic name is specified more than one") diff --git a/pkg/ctl/topicpolicies/test_help.go b/pkg/ctl/topicpolicies/test_help.go new file mode 100644 index 000000000..307b5bcdf --- /dev/null +++ b/pkg/ctl/topicpolicies/test_help.go @@ -0,0 +1,65 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 topicpolicies + +import ( + "bytes" + + "github.com/kris-nova/logger" + "github.com/spf13/cobra" + "github.com/streamnative/pulsarctl/pkg/cmdutils" +) + +func TestTopicPoliciesCommands(newVerb func(cmd *cmdutils.VerbCmd), args []string) (out *bytes.Buffer, execErr, nameErr, err error) { + var execError error + cmdutils.ExecErrorHandler = func(err error) { + execError = err + } + + var parsedNameError error + cmdutils.CheckNameArgError = func(err error) { + parsedNameError = err + } + + rootCmd := &cobra.Command{ + Use: "pulsarctl [command]", + Short: "a CLI for Apache Pulsar", + Run: func(cmd *cobra.Command, _ []string) { + if err := cmd.Help(); err != nil { + logger.Debug("ignoring error %q", err.Error()) + } + }, + } + + buf := new(bytes.Buffer) + rootCmd.SetOut(buf) + rootCmd.SetArgs(append([]string{"topic-policies"}, args...)) + + resourceCmd := cmdutils.NewResourceCmd( + "topic-policies", + "Operations about topic policies", + "", + "topic-policy", + ) + flagGrouping := cmdutils.NewGrouping() + cmdutils.AddVerbCmd(flagGrouping, resourceCmd, newVerb) + rootCmd.AddCommand(resourceCmd) + err = rootCmd.Execute() + + return buf, execError, parsedNameError, err +} diff --git a/pkg/ctl/topicpolicies/topic_policies.go b/pkg/ctl/topicpolicies/topic_policies.go index 2ebfe1499..266b321eb 100644 --- a/pkg/ctl/topicpolicies/topic_policies.go +++ b/pkg/ctl/topicpolicies/topic_policies.go @@ -31,6 +31,57 @@ func Command(flagGrouping *cmdutils.FlagGrouping) *cobra.Command { ) cmdutils.AddVerbCmds(flagGrouping, resourceCmd, + GetMessageTTLCmd, + SetMessageTTLCmd, + RemoveMessageTTLCmd, + GetMaxUnackMessagesPerConsumerCmd, + SetMaxUnackMessagesPerConsumerCmd, + RemoveMaxUnackMessagesPerConsumerCmd, + GetMaxConsumersPerSubscriptionCmd, + SetMaxConsumersPerSubscriptionCmd, + RemoveMaxConsumersPerSubscriptionCmd, + GetRetentionCmd, + SetRetentionCmd, + RemoveRetentionCmd, + GetBacklogQuotaCmd, + SetBacklogQuotaCmd, + RemoveBacklogQuotaCmd, + GetMaxProducersCmd, + SetMaxProducersCmd, + RemoveMaxProducersCmd, + GetDeduplicationCmd, + SetDeduplicationCmd, + RemoveDeduplicationCmd, + GetPersistenceCmd, + SetPersistenceCmd, + RemovePersistenceCmd, + GetSubscriptionDispatchRateCmd, + SetSubscriptionDispatchRateCmd, + RemoveSubscriptionDispatchRateCmd, + GetPublishRateCmd, + SetPublishRateCmd, + RemovePublishRateCmd, + GetCompactionThresholdCmd, + SetCompactionThresholdCmd, + RemoveCompactionThresholdCmd, + GetSubscribeRateCmd, + SetSubscribeRateCmd, + RemoveSubscribeRateCmd, + GetMaxConsumersCmd, + SetMaxConsumersCmd, + RemoveMaxConsumersCmd, + GetDelayedDeliveryCmd, + SetDelayedDeliveryCmd, + RemoveDelayedDeliveryCmd, + GetDispatchRateCmd, + SetDispatchRateCmd, + RemoveDispatchRateCmd, + GetMaxUnackMessagesPerSubscriptionCmd, + SetMaxUnackMessagesPerSubscriptionCmd, + RemoveMaxUnackMessagesPerSubscriptionCmd, + GetInactiveTopicPoliciesCmd, + SetInactiveTopicPoliciesCmd, + RemoveInactiveTopicPoliciesCmd, GetMaxMessageSizeCmd, SetMaxMessageSizeCmd, RemoveMaxMessageSizeCmd, diff --git a/pkg/ctl/topicpolicies/topic_policies_test.go b/pkg/ctl/topicpolicies/topic_policies_test.go new file mode 100644 index 000000000..9f6d383f3 --- /dev/null +++ b/pkg/ctl/topicpolicies/topic_policies_test.go @@ -0,0 +1,42 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 topicpolicies + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestTopicPoliciesNameError(t *testing.T) { + _, _, nameErr, _ := TestTopicPoliciesCommands(GetMessageTTLCmd, []string{"get-message-ttl"}) + assert.NotNil(t, nameErr) + assert.Equal(t, "the topic name is not specified or the topic name is specified more than one", nameErr.Error()) +} + +func TestTopicPoliciesRequiredFlag(t *testing.T) { + _, _, _, err := TestTopicPoliciesCommands(SetMessageTTLCmd, []string{"set-message-ttl", "persistent://public/default/test"}) + assert.NotNil(t, err) + assert.Equal(t, "required flag(s) \"ttl\" not set", err.Error()) +} + +func TestTopicPoliciesEnableDisableValidation(t *testing.T) { + _, execErr, _, _ := TestTopicPoliciesCommands(SetDeduplicationCmd, []string{"set-deduplication", "persistent://public/default/test"}) + assert.NotNil(t, execErr) + assert.Equal(t, "need to specify either --enable or --disable", execErr.Error()) +} From ee5525adea75cae650b1a986874fe865844f9e94 Mon Sep 17 00:00:00 2001 From: Rui Fu Date: Sat, 11 Apr 2026 08:51:22 +0800 Subject: [PATCH 6/8] test(topicpolicies): add tests for set-retention help --- pkg/ctl/topicpolicies/data_policies.go | 2 +- pkg/ctl/topicpolicies/topic_policies_test.go | 7 ++++ pkg/pulsarctl_test.go | 39 ++++++++++++++++++++ 3 files changed, 47 insertions(+), 1 deletion(-) create mode 100644 pkg/pulsarctl_test.go diff --git a/pkg/ctl/topicpolicies/data_policies.go b/pkg/ctl/topicpolicies/data_policies.go index ad8bfac04..958f457ab 100644 --- a/pkg/ctl/topicpolicies/data_policies.go +++ b/pkg/ctl/topicpolicies/data_policies.go @@ -68,7 +68,7 @@ func SetRetentionCmd(vc *cmdutils.VerbCmd) { addScopeFlags(vc, &global, nil) vc.FlagSetGroup.InFlagSet("Retention", func(set *pflag.FlagSet) { set.StringVarP(&timeStr, "time", "t", "", "retention time with optional time unit suffix") - set.StringVarP(&sizeStr, "size", "s", "", "retention size limit") + set.StringVar(&sizeStr, "size", "", "retention size limit") _ = cobra.MarkFlagRequired(set, "time") _ = cobra.MarkFlagRequired(set, "size") }) diff --git a/pkg/ctl/topicpolicies/topic_policies_test.go b/pkg/ctl/topicpolicies/topic_policies_test.go index 9f6d383f3..1560c74f7 100644 --- a/pkg/ctl/topicpolicies/topic_policies_test.go +++ b/pkg/ctl/topicpolicies/topic_policies_test.go @@ -40,3 +40,10 @@ func TestTopicPoliciesEnableDisableValidation(t *testing.T) { assert.NotNil(t, execErr) assert.Equal(t, "need to specify either --enable or --disable", execErr.Error()) } + +func TestTopicPoliciesSetRetentionHelp(t *testing.T) { + assert.NotPanics(t, func() { + _, _, _, err := TestTopicPoliciesCommands(SetRetentionCmd, []string{"set-retention", "--help"}) + assert.NoError(t, err) + }) +} diff --git a/pkg/pulsarctl_test.go b/pkg/pulsarctl_test.go new file mode 100644 index 000000000..3c4402c6a --- /dev/null +++ b/pkg/pulsarctl_test.go @@ -0,0 +1,39 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 pkg + +import ( + "bytes" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestNewPulsarctlCmdBuildsAndShowsHelp(t *testing.T) { + var cmd = NewPulsarctlCmd() + + var stdout bytes.Buffer + var stderr bytes.Buffer + cmd.SetOut(&stdout) + cmd.SetErr(&stderr) + cmd.SetArgs([]string{"topic-policies", "set-retention", "--help"}) + + require.NotPanics(t, func() { + require.NoError(t, cmd.Execute()) + }) +} From ad82a034057780f81f91304b0546c7456e9d2826 Mon Sep 17 00:00:00 2001 From: Rui Fu Date: Sat, 11 Apr 2026 11:47:14 +0800 Subject: [PATCH 7/8] feat(topic): add support for removing schema validation --- pkg/ctl/topic/schema_validation_enforce.go | 17 ++++++++ .../topic/schema_validation_enforce_test.go | 32 +++++++++++++++ pkg/ctl/topic/topic.go | 1 + pkg/ctl/topicpolicies/scalar_policies.go | 32 +++++++++++++++ .../schema_validation_enforced_test.go | 41 +++++++++++++++++++ pkg/ctl/topicpolicies/topic_policies.go | 3 ++ pkg/pulsarctl_test.go | 28 +++++++++++++ 7 files changed, 154 insertions(+) create mode 100644 pkg/ctl/topic/schema_validation_enforce_test.go create mode 100644 pkg/ctl/topicpolicies/schema_validation_enforced_test.go diff --git a/pkg/ctl/topic/schema_validation_enforce.go b/pkg/ctl/topic/schema_validation_enforce.go index fa9ed2d11..30e353c93 100644 --- a/pkg/ctl/topic/schema_validation_enforce.go +++ b/pkg/ctl/topic/schema_validation_enforce.go @@ -68,3 +68,20 @@ func setSchemaValidationEnforceCmd(vc *cmdutils.VerbCmd) { return err }, "the topic name is not specified or the topic name is specified more than one") } + +func RemoveSchemaValidationEnforceCmd(vc *cmdutils.VerbCmd) { + vc.SetDescription("remove-schema-validation-enforce", "Remove schema validation enforce flag for a topic", + "Remove schema validation enforce flag for a topic", "") + vc.SetRunFuncWithNameArg(func() error { + topic, err := utils.GetTopicName(vc.NameArg) + if err != nil { + return err + } + admin := cmdutils.NewPulsarClient() + err = admin.Topics().RemoveSchemaValidationEnforced(*topic) + if err == nil { + vc.Command.Printf("Remove schema validation enforce successfully for [%s]\n", topic.String()) + } + return err + }, "the topic name is not specified or the topic name is specified more than one") +} diff --git a/pkg/ctl/topic/schema_validation_enforce_test.go b/pkg/ctl/topic/schema_validation_enforce_test.go new file mode 100644 index 000000000..eb022f9d5 --- /dev/null +++ b/pkg/ctl/topic/schema_validation_enforce_test.go @@ -0,0 +1,32 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 topic + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestRemoveSchemaValidationEnforceNameError(t *testing.T) { + _, _, nameErr, _ := TestTopicCommands(RemoveSchemaValidationEnforceCmd, []string{ + "remove-schema-validation-enforce", + }) + assert.NotNil(t, nameErr) + assert.Equal(t, "the topic name is not specified or the topic name is specified more than one", nameErr.Error()) +} diff --git a/pkg/ctl/topic/topic.go b/pkg/ctl/topic/topic.go index cbf7046f7..db5c4f1c5 100644 --- a/pkg/ctl/topic/topic.go +++ b/pkg/ctl/topic/topic.go @@ -106,6 +106,7 @@ func Command(flagGrouping *cmdutils.FlagGrouping) *cobra.Command { removePropertiesCmd, getSchemaValidationEnforceCmd, setSchemaValidationEnforceCmd, + RemoveSchemaValidationEnforceCmd, GetInactiveTopicCmd, SetInactiveTopicCmd, RemoveInactiveTopicCmd, diff --git a/pkg/ctl/topicpolicies/scalar_policies.go b/pkg/ctl/topicpolicies/scalar_policies.go index 0fb051a68..ab26e4f14 100644 --- a/pkg/ctl/topicpolicies/scalar_policies.go +++ b/pkg/ctl/topicpolicies/scalar_policies.go @@ -372,3 +372,35 @@ func RemoveDeduplicationCmd(vc *cmdutils.VerbCmd) { return policies.RemoveDeduplicationStatus(vc.Command.Context(), *topic) }) } + +func GetSchemaValidationEnforcedCmd(vc *cmdutils.VerbCmd) { + getOptionalBoolPolicyCmd( + vc, + "get-schema-validation-enforced", + "Get schema validation enforced for a topic", + func(ctx context.Context, policies admin.TopicPolicies, topic utils.TopicName, applied bool) (*bool, error) { + return policies.GetSchemaValidationEnforced(ctx, topic, applied) + }, + ) +} + +func SetSchemaValidationEnforcedCmd(vc *cmdutils.VerbCmd) { + setEnableDisablePolicyCmd( + vc, + "set-schema-validation-enforced", + "Set schema validation enforced for a topic", + func(ctx context.Context, policies admin.TopicPolicies, topic utils.TopicName, enabled bool) error { + return policies.SetSchemaValidationEnforced(ctx, topic, enabled) + }, + ) +} + +func RemoveSchemaValidationEnforcedCmd(vc *cmdutils.VerbCmd) { + removePolicyCmd(vc, "remove-schema-validation-enforced", "Removed schema validation enforced for a topic", func(global bool) error { + policies, topic, err := topicPolicyResources(vc, global) + if err != nil { + return err + } + return policies.RemoveSchemaValidationEnforced(vc.Command.Context(), *topic) + }) +} diff --git a/pkg/ctl/topicpolicies/schema_validation_enforced_test.go b/pkg/ctl/topicpolicies/schema_validation_enforced_test.go new file mode 100644 index 000000000..6ad5ae192 --- /dev/null +++ b/pkg/ctl/topicpolicies/schema_validation_enforced_test.go @@ -0,0 +1,41 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 topicpolicies + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestTopicPoliciesSetSchemaValidationEnforcedValidation(t *testing.T) { + _, execErr, _, _ := TestTopicPoliciesCommands(SetSchemaValidationEnforcedCmd, []string{ + "set-schema-validation-enforced", + "persistent://public/default/test", + }) + assert.NotNil(t, execErr) + assert.Equal(t, "need to specify either --enable or --disable", execErr.Error()) +} + +func TestTopicPoliciesRemoveSchemaValidationEnforcedNameError(t *testing.T) { + _, _, nameErr, _ := TestTopicPoliciesCommands(RemoveSchemaValidationEnforcedCmd, []string{ + "remove-schema-validation-enforced", + }) + assert.NotNil(t, nameErr) + assert.Equal(t, "the topic name is not specified or the topic name is specified more than one", nameErr.Error()) +} diff --git a/pkg/ctl/topicpolicies/topic_policies.go b/pkg/ctl/topicpolicies/topic_policies.go index 266b321eb..71b99e3cb 100644 --- a/pkg/ctl/topicpolicies/topic_policies.go +++ b/pkg/ctl/topicpolicies/topic_policies.go @@ -88,6 +88,9 @@ func Command(flagGrouping *cmdutils.FlagGrouping) *cobra.Command { GetMaxSubscriptionsPerTopicCmd, SetMaxSubscriptionsPerTopicCmd, RemoveMaxSubscriptionsPerTopicCmd, + GetSchemaValidationEnforcedCmd, + SetSchemaValidationEnforcedCmd, + RemoveSchemaValidationEnforcedCmd, GetDeduplicationSnapshotIntervalCmd, SetDeduplicationSnapshotIntervalCmd, RemoveDeduplicationSnapshotIntervalCmd, diff --git a/pkg/pulsarctl_test.go b/pkg/pulsarctl_test.go index 3c4402c6a..9160b689a 100644 --- a/pkg/pulsarctl_test.go +++ b/pkg/pulsarctl_test.go @@ -37,3 +37,31 @@ func TestNewPulsarctlCmdBuildsAndShowsHelp(t *testing.T) { require.NoError(t, cmd.Execute()) }) } + +func TestTopicPoliciesHelpShowsSchemaValidationEnforced(t *testing.T) { + var cmd = NewPulsarctlCmd() + + var stdout bytes.Buffer + var stderr bytes.Buffer + cmd.SetOut(&stdout) + cmd.SetErr(&stderr) + cmd.SetArgs([]string{"topic-policies", "--help"}) + + require.NoError(t, cmd.Execute()) + require.Contains(t, stdout.String(), "get-schema-validation-enforced") + require.Contains(t, stdout.String(), "set-schema-validation-enforced") + require.Contains(t, stdout.String(), "remove-schema-validation-enforced") +} + +func TestTopicsHelpShowsRemoveSchemaValidationEnforce(t *testing.T) { + var cmd = NewPulsarctlCmd() + + var stdout bytes.Buffer + var stderr bytes.Buffer + cmd.SetOut(&stdout) + cmd.SetErr(&stderr) + cmd.SetArgs([]string{"topics", "--help"}) + + require.NoError(t, cmd.Execute()) + require.Contains(t, stdout.String(), "remove-schema-validation-enforce") +} From 5b30589575c939caf8d578ca79105a6b572a2df2 Mon Sep 17 00:00:00 2001 From: Rui Fu Date: Sat, 11 Apr 2026 15:42:57 +0800 Subject: [PATCH 8/8] refactor(namespace): adjust flag handling in commands --- pkg/ctl/brokerstats/load_report_test.go | 1 - pkg/ctl/namespace/properties.go | 4 ++-- pkg/ctl/namespace/properties_test.go | 2 +- .../schema_compatibility_strategy_test.go | 6 +++--- pkg/ctl/topicpolicies/data_policies.go | 14 +++++++------- .../topicpolicies/schema_compatibility_strategy.go | 6 ------ site/gen-pulsarctldocs/generators/v1_1/toc.yaml | 1 + 7 files changed, 14 insertions(+), 20 deletions(-) diff --git a/pkg/ctl/brokerstats/load_report_test.go b/pkg/ctl/brokerstats/load_report_test.go index 4fe48a98d..aff2b88af 100644 --- a/pkg/ctl/brokerstats/load_report_test.go +++ b/pkg/ctl/brokerstats/load_report_test.go @@ -37,6 +37,5 @@ func TestDumpLoadReport(t *testing.T) { t.FailNow() } defaultBrokerData := utils.NewLocalBrokerData() - defaultBrokerData.LastStats = nil assert.Equal(t, defaultBrokerData, getBrokerData) } diff --git a/pkg/ctl/namespace/properties.go b/pkg/ctl/namespace/properties.go index 357db20ac..48674c222 100644 --- a/pkg/ctl/namespace/properties.go +++ b/pkg/ctl/namespace/properties.go @@ -223,7 +223,7 @@ func SetPropertyCmd(vc *cmdutils.VerbCmd) { var examples []cmdutils.Example examples = append(examples, cmdutils.Example{ Desc: "Set a single property of a namespace", - Command: "pulsarctl namespaces set-property tenant/namespace -k key -v value", + Command: "pulsarctl namespaces set-property tenant/namespace -k key --value value", }) desc.CommandExamples = examples desc.CommandOutput = append(desc.CommandOutput, ArgError, NsNotExistError) @@ -240,7 +240,7 @@ func SetPropertyCmd(vc *cmdutils.VerbCmd) { var value string vc.FlagSetGroup.InFlagSet("Properties", func(set *pflag.FlagSet) { set.StringVarP(&key, "key", "k", "", "property key") - set.StringVarP(&value, "value", "v", "", "property value") + set.StringVar(&value, "value", "", "property value") _ = cobra.MarkFlagRequired(set, "key") _ = cobra.MarkFlagRequired(set, "value") }) diff --git a/pkg/ctl/namespace/properties_test.go b/pkg/ctl/namespace/properties_test.go index 6c4555948..e43d42228 100644 --- a/pkg/ctl/namespace/properties_test.go +++ b/pkg/ctl/namespace/properties_test.go @@ -53,7 +53,7 @@ func TestNamespacePropertiesCmd(t *testing.T) { assert.Nil(t, execErr) assert.Equal(t, "v1\n", getPropertyOut.String()) - args = []string{"set-property", ns, "-k", "k3", "-v", "v3"} + args = []string{"set-property", ns, "-k", "k3", "--value", "v3"} setPropertyOut, execErr, _, _ := TestNamespaceCommands(SetPropertyCmd, args) assert.Nil(t, execErr) assert.Equal(t, fmt.Sprintf("Set property %q successfully for [%s]\n", "k3", ns), setPropertyOut.String()) diff --git a/pkg/ctl/namespace/schema_compatibility_strategy_test.go b/pkg/ctl/namespace/schema_compatibility_strategy_test.go index a25940b09..a564c53c3 100644 --- a/pkg/ctl/namespace/schema_compatibility_strategy_test.go +++ b/pkg/ctl/namespace/schema_compatibility_strategy_test.go @@ -55,9 +55,9 @@ func TestSchemaCompatibilityStrategyCmd(t *testing.T) { getOut.String()) args = []string{"set-schema-compatibility-strategy", ns} - _, execErr, _, _ = TestNamespaceCommands(SetSchemaCompatibilityStrategyCmd, args) - assert.NotNil(t, execErr) - assert.Contains(t, execErr.Error(), "required flag(s) \"compatibility\" not set") + _, _, _, err := TestNamespaceCommands(SetSchemaCompatibilityStrategyCmd, args) + assert.NotNil(t, err) + assert.Contains(t, err.Error(), "required flag(s) \"compatibility\" not set") args = []string{"set-schema-compatibility-strategy", "--compatibility", "INVALID", ns} _, execErr, _, _ = TestNamespaceCommands(SetSchemaCompatibilityStrategyCmd, args) diff --git a/pkg/ctl/topicpolicies/data_policies.go b/pkg/ctl/topicpolicies/data_policies.go index 958f457ab..e37be91c7 100644 --- a/pkg/ctl/topicpolicies/data_policies.go +++ b/pkg/ctl/topicpolicies/data_policies.go @@ -77,7 +77,7 @@ func SetRetentionCmd(vc *cmdutils.VerbCmd) { if err != nil { return err } - retentionTimeInSeconds, err := ctlutils.ParseRelativeTimeInSeconds(timeStr) + retentionDuration, err := ctlutils.ParseRelativeTimeInSeconds(timeStr) if err != nil { return err } @@ -86,8 +86,8 @@ func SetRetentionCmd(vc *cmdutils.VerbCmd) { return err } retentionTimeInMin := -1 - if retentionTimeInSeconds != -1 { - retentionTimeInMin = int(retentionTimeInSeconds.Minutes()) + if retentionDuration != -1 { + retentionTimeInMin = int(retentionDuration.Minutes()) } retentionSizeInMB := -1 if sizeLimit != -1 { @@ -297,18 +297,18 @@ func SetDelayedDeliveryCmd(vc *cmdutils.VerbCmd) { if err != nil { return err } - tickTimeInSeconds, err := ctlutils.ParseRelativeTimeInSeconds(tickTime) + tickTimeDuration, err := ctlutils.ParseRelativeTimeInSeconds(tickTime) if err != nil { return err } - maxDelayInSeconds, err := ctlutils.ParseRelativeTimeInSeconds(maxDelay) + maxDelayDuration, err := ctlutils.ParseRelativeTimeInSeconds(maxDelay) if err != nil { return err } data := util.NewDelayedDeliveryDataWithMaxDelay( - tickTimeInSeconds.Seconds()*1000, + tickTimeDuration.Seconds()*1000, enable, - int64(maxDelayInSeconds.Seconds()*1000), + int64(maxDelayDuration.Seconds()*1000), ) err = policies.SetDelayedDelivery(vc.Command.Context(), *topic, *data) if err == nil { diff --git a/pkg/ctl/topicpolicies/schema_compatibility_strategy.go b/pkg/ctl/topicpolicies/schema_compatibility_strategy.go index 25c3b69e9..5c80b38ff 100644 --- a/pkg/ctl/topicpolicies/schema_compatibility_strategy.go +++ b/pkg/ctl/topicpolicies/schema_compatibility_strategy.go @@ -18,8 +18,6 @@ package topicpolicies import ( - "fmt" - "github.com/apache/pulsar-client-go/pulsaradmin/pkg/utils" "github.com/spf13/pflag" "github.com/streamnative/pulsarctl/pkg/cmdutils" @@ -93,7 +91,3 @@ func RemoveSchemaCompatibilityStrategyCmd(vc *cmdutils.VerbCmd) { return policies.RemoveSchemaCompatibilityStrategy(vc.Command.Context(), *topic) }) } - -func writePolicyOutputString(vc *cmdutils.VerbCmd, value string) error { - return writePolicyOutput(vc, value, fmt.Sprintf("%%s\n"), value) -} diff --git a/site/gen-pulsarctldocs/generators/v1_1/toc.yaml b/site/gen-pulsarctldocs/generators/v1_1/toc.yaml index 651f00382..c8d894320 100644 --- a/site/gen-pulsarctldocs/generators/v1_1/toc.yaml +++ b/site/gen-pulsarctldocs/generators/v1_1/toc.yaml @@ -25,6 +25,7 @@ categories: - completion - functions - namespaces + - topic-policies - schemas - sinks - sources