diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..b65be8c --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,2 @@ +# This team will own the entire repository +* @qdrant/cloud-unit-platform diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..5819e02 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,16 @@ +version: 2 +updates: + - package-ecosystem: "gomod" + directory: "/" + schedule: + interval: "weekly" + + - package-ecosystem: "docker" + directory: "/" + schedule: + interval: "weekly" + + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" \ No newline at end of file diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml new file mode 100644 index 0000000..c3c3cc4 --- /dev/null +++ b/.github/release-drafter.yml @@ -0,0 +1,20 @@ +categories: + - title: '๐Ÿš€ Features' + labels: + - 'feature' + - 'feat' + - 'enhancement' + - 'enh' + - title: '๐Ÿ› Bug Fixes' + labels: + - 'fix' + - 'bugfix' + - 'bug' + - title: '๐Ÿงฐ Maintenance' + labels: + - 'chore' + - 'dependencies' +template: | + # Whatโ€™s Changed + + $CHANGES diff --git a/.github/workflows/label-enforcer.yaml b/.github/workflows/label-enforcer.yaml new file mode 100644 index 0000000..7733e8c --- /dev/null +++ b/.github/workflows/label-enforcer.yaml @@ -0,0 +1,19 @@ +name: Enforce Labels + +on: + pull_request: + types: [opened, edited, labeled, unlabeled, synchronize] + +permissions: + contents: read + +jobs: + enforce-label: + permissions: + contents: read # for TimonVS/pr-labeler-action to read config file + pull-requests: write # for TimonVS/pr-labeler-action to add labels in PR + runs-on: ubuntu-latest + steps: + - uses: TimonVS/pr-labeler-action@v5 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/pr-workflow.yaml b/.github/workflows/pr-workflow.yaml new file mode 100644 index 0000000..c5562a8 --- /dev/null +++ b/.github/workflows/pr-workflow.yaml @@ -0,0 +1,61 @@ +name: PR Workflow +on: + pull_request: + types: [synchronize, opened, reopened] + branches: ["main"] +env: + GOPRIVATE: github.com/qdrant/qdrant-cloud-public-api + +permissions: + contents: read + +jobs: + linter: + name: Linter + runs-on: ubuntu-latest + timeout-minutes: 10 # Sets a timeout of 10 minutes for this job (default is 1 minute) + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + persist-credentials: false + + - name: Setup access for private go modules + run: | + git config --global url.'https://${{ secrets.GH_REPO_READ_TOKEN }}@github.com'.insteadOf 'https://github.com' + + - uses: actions/setup-go@v5 + with: + go-version: "^1.24" + cache: false + + - name: Check Go Formatting + run: | + files=$(gofmt -l .) && echo $files && [ -z "$files" ] + + - name: Golang CI Lint + uses: golangci/golangci-lint-action@v7 + with: + version: v2.0.1 + args: --timeout 10m + + unit-tests: + name: Unit Tests + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + persist-credentials: false + + - name: Setup access for private go modules + run: | + git config --global url.'https://${{ secrets.GH_REPO_READ_TOKEN }}@github.com'.insteadOf 'https://github.com' + + - uses: actions/setup-go@v5 + with: + go-version: "^1.24" + + - name: Unit tests + run: | + make test_unit diff --git a/.gitignore b/.gitignore index 6f72f89..225ecfc 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,9 @@ go.work.sum # env file .env + +.DS_Store +.idea +*.log +tmp/ +bin/ diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..2ffa2ae --- /dev/null +++ b/Makefile @@ -0,0 +1,72 @@ +.PHONY: help +help: ## Display this help. + @awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m\033[0m\n"} /^[a-zA-Z_0-9\/-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST) + +##@ Development + +.PHONY: fmt +fmt: ## Run go fmt and gci against code. + go fmt ./... + $(GCI) write ./ --skip-generated -s standard -s default -s 'prefix(github.com/qdrant)' -s 'prefix(github.com/qdrant/qdrant-cloud-buf-plugins/)' + +.PHONY: vet +vet: ## Run go vet (static analysis tool) against code. + go vet ./... + +.PHONY: lint +lint: bootstrap ## Run project linters + $(GOLANGCI_LINT) run + +.PHONY: test +test: fmt vet test_unit ## Run all tests. + +.PHONY: test_unit +test_unit: ## Run unit tests. + echo "Running Go unit tests..." + go test ./... -coverprofile cover.out -v + +##@ Dependencies + +## Location to install dependencies to +LOCALBIN ?= $(shell pwd)/bin +$(LOCALBIN): + mkdir -p $(LOCALBIN) + +## Tool Binaries +GO ?= go +GOLANGCI_LINT = $(LOCALBIN)/golangci-lint +GCI = $(LOCALBIN)/gci + +## Tool Versions +GOLANGCI_LINT_VERSION ?= v2.0.1 +GCI_VERSION ?= v0.13.5 + +.PHONY: bootstrap +bootstrap: install/gci install/golangci-lint ## Install required dependencies to work with this project + +.PHONY: golangci-lint +install/golangci-lint: $(GOLANGCI_LINT) ## Download golangci-lint locally if necessary. +$(GOLANGCI_LINT): $(LOCALBIN) + $(call go-install-tool,$(GOLANGCI_LINT),github.com/golangci/golangci-lint/v2/cmd/golangci-lint,$(GOLANGCI_LINT_VERSION)) + +.PHONY: install/gci +install/gci: $(GCI) ## Download gci locally if necessary. +$(GCI): $(LOCALBIN) + $(call go-install-tool,$(GCI),github.com/daixiang0/gci,$(GCI_VERSION)) + +# copied from kube-builder +# go-install-tool will 'go install' any package with custom target and name of binary, if it doesn't exist +# $1 - target path with name of binary +# $2 - package url which can be installed +# $3 - specific version of package +define go-install-tool +@[ -f "$(1)-$(3)" ] || { \ +set -e; \ +package=$(2)@$(3) ;\ +echo "Downloading $${package}" ;\ +rm -f $(1) || true ;\ +GOBIN=$(LOCALBIN) go install $${package} ;\ +mv $(1) $(1)-$(3) ;\ +} ;\ +ln -sf $(1)-$(3) $(1) +endef diff --git a/README.md b/README.md new file mode 100644 index 0000000..a391952 --- /dev/null +++ b/README.md @@ -0,0 +1,37 @@ +# qdrant-cloud-buf-plugins + +Collection of [Buf plugins](https://buf.build/docs/cli/buf-plugins/overview/) used by Qdrant Cloud APIs. + +## Development + +This project leverages Make to automate common development tasks. To view all available commands, run: + +``` sh +make help +``` + +### Setup + +To work with this project locally, you need to have [Go](https://go.dev/doc/install) installed. +Additionally, there are other required dependencies that you can install running: + +``` sh +make bootstrap +``` + +### Running tests + +To run the tests, execute: + +``` sh +make test +``` + +### Formatting & linting code + +To format and lint the code of the project, execute: + +``` sh +make fmt +make lint +``` diff --git a/cmd/buf-plugin-method-options/main.go b/cmd/buf-plugin-method-options/main.go new file mode 100644 index 0000000..a4d73a3 --- /dev/null +++ b/cmd/buf-plugin-method-options/main.go @@ -0,0 +1,75 @@ +// Package main implements a plugin that checks that all rpc methods set the +// required options (permissions, http). +// +// To use this plugin: +// +// # buf.yaml +// version: v2 +// lint: +// use: +// - STANDARD # omit if you do not want to use the rules builtin to buf +// - QDRANT_CLOUD_METHOD_OPTIONS +// plugins: +// - plugin: buf-plugin-method-options +package main + +import ( + "context" + + "buf.build/go/bufplugin/check" + "buf.build/go/bufplugin/check/checkutil" + "buf.build/go/bufplugin/info" + googleann "google.golang.org/genproto/googleapis/api/annotations" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + + commonv1 "github.com/qdrant/qdrant-cloud-public-api/gen/go/qdrant/cloud/common/v1" +) + +const ( + methodOptionsRuleID = "QDRANT_CLOUD_METHOD_OPTIONS" +) + +var ( + methodOptionsRuleSpec = &check.RuleSpec{ + ID: methodOptionsRuleID, + Default: true, + Purpose: `Checks that all rpc methods define a set of required options.`, + Type: check.RuleTypeLint, + Handler: checkutil.NewMethodRuleHandler(checkMethodOptions, checkutil.WithoutImports()), + } + spec = &check.Spec{ + Rules: []*check.RuleSpec{ + methodOptionsRuleSpec, + }, + Info: &info.Spec{ + Documentation: `A plugin that checks that all rpc methods define a set of required options.`, + SPDXLicenseID: "", + LicenseURL: "", + }, + } + requiredMethodOptionExtensions = []*protoimpl.ExtensionInfo{ + commonv1.E_Permissions, + googleann.E_Http, + } +) + +func main() { + check.Main(spec) +} + +func checkMethodOptions(ctx context.Context, responseWriter check.ResponseWriter, request check.Request, methodDescriptor protoreflect.MethodDescriptor) error { + options := methodDescriptor.Options() + + for _, extension := range requiredMethodOptionExtensions { + if !proto.HasExtension(options, extension) { + responseWriter.AddAnnotation( + check.WithMessagef("Method %q does not define the %q option", methodDescriptor.FullName(), extension.TypeDescriptor().FullName()), + check.WithDescriptor(methodDescriptor), + ) + } + } + + return nil +} diff --git a/cmd/buf-plugin-method-options/main_test.go b/cmd/buf-plugin-method-options/main_test.go new file mode 100644 index 0000000..f537f23 --- /dev/null +++ b/cmd/buf-plugin-method-options/main_test.go @@ -0,0 +1,64 @@ +package main + +import ( + "testing" + + "buf.build/go/bufplugin/check/checktest" +) + +func TestSpec(t *testing.T) { + t.Parallel() + checktest.SpecTest(t, spec) +} + +func TestSimpleSuccess(t *testing.T) { + t.Parallel() + + checktest.CheckTest{ + Request: &checktest.RequestSpec{ + Files: &checktest.ProtoFileSpec{ + DirPaths: []string{"testdata/simple_success"}, + FilePaths: []string{"simple.proto"}, + }, + }, + Spec: spec, + }.Run(t) +} + +func TestSimpleFailure(t *testing.T) { + t.Parallel() + + checktest.CheckTest{ + Request: &checktest.RequestSpec{ + Files: &checktest.ProtoFileSpec{ + DirPaths: []string{"testdata/simple_failure"}, + FilePaths: []string{"simple.proto"}, + }, + }, + Spec: spec, + ExpectedAnnotations: []checktest.ExpectedAnnotation{ + { + RuleID: methodOptionsRuleID, + Message: "Method \"simple.GreeterService.HelloWorld\" does not define the \"google.api.http\" option", + FileLocation: &checktest.ExpectedFileLocation{ + FileName: "simple.proto", + StartLine: 9, + StartColumn: 4, + EndLine: 12, + EndColumn: 5, + }, + }, + { + RuleID: methodOptionsRuleID, + Message: "Method \"simple.GreeterService.HelloWorld\" does not define the \"qdrant.cloud.common.v1.permissions\" option", + FileLocation: &checktest.ExpectedFileLocation{ + FileName: "simple.proto", + StartLine: 9, + StartColumn: 4, + EndLine: 12, + EndColumn: 5, + }, + }, + }, + }.Run(t) +} diff --git a/cmd/buf-plugin-method-options/testdata/common.proto b/cmd/buf-plugin-method-options/testdata/common.proto new file mode 100644 index 0000000..ea10e42 --- /dev/null +++ b/cmd/buf-plugin-method-options/testdata/common.proto @@ -0,0 +1,13 @@ +syntax = "proto3"; + +// As a commodity, we re-define it here to avoid relying on the real dependency. + +package qdrant.cloud.common.v1; + +import "google/protobuf/descriptor.proto"; + +// The extension for adding permissions to the system +extend google.protobuf.MethodOptions { + // A list of permissions which ALL need to be met by the current user. + repeated string permissions = 50001; +} diff --git a/cmd/buf-plugin-method-options/testdata/google.proto b/cmd/buf-plugin-method-options/testdata/google.proto new file mode 100644 index 0000000..a4f5e59 --- /dev/null +++ b/cmd/buf-plugin-method-options/testdata/google.proto @@ -0,0 +1,15 @@ +syntax = "proto3"; + +// As a commodity, we re-define it here to avoid relying on the real dependency. + +package google.api; + +import "google/protobuf/descriptor.proto"; + +extend google.protobuf.MethodOptions { + HttpRule http = 72295728; +} + +message HttpRule { + string get = 1; +} diff --git a/cmd/buf-plugin-method-options/testdata/simple_failure/simple.proto b/cmd/buf-plugin-method-options/testdata/simple_failure/simple.proto new file mode 100644 index 0000000..76239f4 --- /dev/null +++ b/cmd/buf-plugin-method-options/testdata/simple_failure/simple.proto @@ -0,0 +1,14 @@ +syntax = "proto3"; + +package simple; + +import "google/protobuf/empty.proto"; +import "google/protobuf/descriptor.proto"; +import "../common.proto"; + +service GreeterService { + rpc HelloWorld(google.protobuf.Empty) returns (google.protobuf.Empty) { + // missing qdrant.cloud.common.v1.permissions + // missing google.api.http + } +} diff --git a/cmd/buf-plugin-method-options/testdata/simple_success/simple.proto b/cmd/buf-plugin-method-options/testdata/simple_success/simple.proto new file mode 100644 index 0000000..84d8468 --- /dev/null +++ b/cmd/buf-plugin-method-options/testdata/simple_success/simple.proto @@ -0,0 +1,16 @@ +syntax = "proto3"; + +package simple; + +import "google/protobuf/empty.proto"; +import "google/protobuf/descriptor.proto"; +import "../common.proto"; +import "../google.proto"; + +service GreeterService { + rpc HelloWorld(google.protobuf.Empty) returns (google.protobuf.Empty) { + // permissions + option (qdrant.cloud.common.v1.permissions) = "read:api_keys"; + option (google.api.http) = {get: "/api/hello-world"}; + } +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..6137a18 --- /dev/null +++ b/go.mod @@ -0,0 +1,35 @@ +module github.com/qdrant/qdrant-cloud-buf-plugins + +go 1.24.1 + +require ( + buf.build/go/bufplugin v0.8.0 + github.com/qdrant/qdrant-cloud-public-api v0.0.0-20250328115247-eb8f8e124f26 + google.golang.org/genproto/googleapis/api v0.0.0-20250127172529-29210b9bc287 + google.golang.org/protobuf v1.36.6 +) + +require ( + buf.build/gen/go/bufbuild/bufplugin/protocolbuffers/go v1.36.3-20250121211742-6d880cc6cc8d.1 // indirect + buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.4-20250130201111-63bb56e20495.1 // indirect + buf.build/gen/go/pluginrpc/pluginrpc/protocolbuffers/go v1.36.3-20241007202033-cf42259fcbfc.1 // indirect + buf.build/go/spdx v0.2.0 // indirect + cel.dev/expr v0.19.2 // indirect + github.com/antlr4-go/antlr/v4 v4.13.1 // indirect + github.com/bufbuild/protocompile v0.14.1 // indirect + github.com/bufbuild/protovalidate-go v0.9.1 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/google/cel-go v0.23.1 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/stoewer/go-strcase v1.3.0 // indirect + github.com/stretchr/testify v1.10.0 // indirect + golang.org/x/exp v0.0.0-20250128182459-e0ece0dbea4c // indirect + golang.org/x/sync v0.11.0 // indirect + golang.org/x/sys v0.30.0 // indirect + golang.org/x/text v0.22.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250127172529-29210b9bc287 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + pluginrpc.com/pluginrpc v0.5.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..5adcaee --- /dev/null +++ b/go.sum @@ -0,0 +1,72 @@ +buf.build/gen/go/bufbuild/bufplugin/protocolbuffers/go v1.36.3-20250121211742-6d880cc6cc8d.1 h1:1v+ez1GRKKKdI1IwDDQqV98lGKo8489+Ekql+prUW6c= +buf.build/gen/go/bufbuild/bufplugin/protocolbuffers/go v1.36.3-20250121211742-6d880cc6cc8d.1/go.mod h1:MYDFm9IHRP085R5Bis68mLc0mIqp5Q27Uk4o8YXjkAI= +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.4-20250130201111-63bb56e20495.1 h1:4erM3WLgEG/HIBrpBDmRbs1puhd7p0z7kNXDuhHthwM= +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.4-20250130201111-63bb56e20495.1/go.mod h1:novQBstnxcGpfKf8qGRATqn1anQKwMJIbH5Q581jibU= +buf.build/gen/go/pluginrpc/pluginrpc/protocolbuffers/go v1.36.3-20241007202033-cf42259fcbfc.1 h1:NOipq02MS20WQCr6rfAG1o0n2AuQnY4Xg9avLl16csA= +buf.build/gen/go/pluginrpc/pluginrpc/protocolbuffers/go v1.36.3-20241007202033-cf42259fcbfc.1/go.mod h1:jceo5esD5zSbflHHGad57RXzBpRrcPaiLrLQRA+Mbec= +buf.build/go/bufplugin v0.8.0 h1:YgR1+CNGmzR69jt85oRWTa5FioZoX/tOrHV+JxfNnnk= +buf.build/go/bufplugin v0.8.0/go.mod h1:rcm0Esd3P/GM2rtYTvz3+9Gf8w9zdo7rG8dKSxYHHIE= +buf.build/go/spdx v0.2.0 h1:IItqM0/cMxvFJJumcBuP8NrsIzMs/UYjp/6WSpq8LTw= +buf.build/go/spdx v0.2.0/go.mod h1:bXdwQFem9Si3nsbNy8aJKGPoaPi5DKwdeEp5/ArZ6w8= +cel.dev/expr v0.19.2 h1:V354PbqIXr9IQdwy4SYA4xa0HXaWq1BUPAGzugBY5V4= +cel.dev/expr v0.19.2/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw= +github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= +github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= +github.com/bufbuild/protocompile v0.14.1 h1:iA73zAf/fyljNjQKwYzUHD6AD4R8KMasmwa/FBatYVw= +github.com/bufbuild/protocompile v0.14.1/go.mod h1:ppVdAIhbr2H8asPk6k4pY7t9zB1OU5DoEw9xY/FUi1c= +github.com/bufbuild/protovalidate-go v0.9.1 h1:cdrIA33994yCcJyEIZRL36ZGTe9UDM/WHs5MBHEimiE= +github.com/bufbuild/protovalidate-go v0.9.1/go.mod h1:5jptBxfvlY51RhX32zR6875JfPBRXUsQjyZjm/NqkLQ= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8= +github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU= +github.com/google/cel-go v0.23.1 h1:91ThhEZlBcE5rB2adBVXqvDoqdL8BG2oyhd0bK1I/r4= +github.com/google/cel-go v0.23.1/go.mod h1:52Pb6QsDbC5kvgxvZhiL9QX1oZEkcUF/ZqaPx1J5Wwo= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/qdrant/qdrant-cloud-public-api v0.0.0-20250328115247-eb8f8e124f26 h1:EX37aw1kPNlvXIlpx2/40qJHZ+ndZCSQkIof31m2iUM= +github.com/qdrant/qdrant-cloud-public-api v0.0.0-20250328115247-eb8f8e124f26/go.mod h1:M9Hfik3bZ9TfLgUmeLIMbssAsMcotmTBiY36utav9+I= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stoewer/go-strcase v1.3.0 h1:g0eASXYtp+yvN9fK8sH94oCIk0fau9uV1/ZdJ0AVEzs= +github.com/stoewer/go-strcase v1.3.0/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +golang.org/x/exp v0.0.0-20250128182459-e0ece0dbea4c h1:KL/ZBHXgKGVmuZBZ01Lt57yE5ws8ZPSkkihmEyq7FXc= +golang.org/x/exp v0.0.0-20250128182459-e0ece0dbea4c/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU= +golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= +golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= +golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= +golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= +google.golang.org/genproto/googleapis/api v0.0.0-20250127172529-29210b9bc287 h1:A2ni10G3UlplFrWdCDJTl7D7mJ7GSRm37S+PDimaKRw= +google.golang.org/genproto/googleapis/api v0.0.0-20250127172529-29210b9bc287/go.mod h1:iYONQfRdizDB8JJBybql13nArx91jcUk7zCXEsOofM4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250127172529-29210b9bc287 h1:J1H9f+LEdWAfHcez/4cvaVBox7cOYT+IU6rgqj5x++8= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250127172529-29210b9bc287/go.mod h1:8BS3B93F/U1juMFq9+EDk+qOT5CO1R9IzXxG3PTqiRk= +google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= +google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +pluginrpc.com/pluginrpc v0.5.0 h1:tOQj2D35hOmvHyPu8e7ohW2/QvAnEtKscy2IJYWQ2yo= +pluginrpc.com/pluginrpc v0.5.0/go.mod h1:UNWZ941hcVAoOZUn8YZsMmOZBzbUjQa3XMns8RQLp9o=