diff --git a/.betterer.results b/.betterer.results index bba659d251f..053ef6bfd02 100644 --- a/.betterer.results +++ b/.betterer.results @@ -1870,11 +1870,12 @@ exports[`better eslint`] = { [0, 0, 0, "No untranslated strings. Wrap text with ", "4"] ], "public/app/features/alerting/unified/components/rule-editor/QueryRows.tsx:5381": [ - [0, 0, 0, "No untranslated strings. Wrap text with ", "0"], + [0, 0, 0, "Do not use any type assertions.", "0"], [0, 0, 0, "No untranslated strings. Wrap text with ", "1"], [0, 0, 0, "No untranslated strings. Wrap text with ", "2"], [0, 0, 0, "No untranslated strings. Wrap text with ", "3"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "4"] + [0, 0, 0, "No untranslated strings. Wrap text with ", "4"], + [0, 0, 0, "No untranslated strings. Wrap text with ", "5"] ], "public/app/features/alerting/unified/components/rule-editor/QueryWrapper.tsx:5381": [ [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"], @@ -1917,7 +1918,7 @@ exports[`better eslint`] = { ], "public/app/features/alerting/unified/components/rule-editor/alert-rule-form/simplifiedRouting/AlertManagerRouting.tsx:5381": [ [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "1"] + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "1"] ], "public/app/features/alerting/unified/components/rule-editor/alert-rule-form/simplifiedRouting/contactPoint/ContactPointSelector.tsx:5381": [ [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"], @@ -6556,9 +6557,6 @@ exports[`better eslint`] = { "public/app/plugins/datasource/tempo/SearchTraceQLEditor/DurationInput.tsx:5381": [ [0, 0, 0, "\'HorizontalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"] ], - "public/app/plugins/datasource/tempo/SearchTraceQLEditor/GroupByField.tsx:5381": [ - [0, 0, 0, "\'HorizontalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"] - ], "public/app/plugins/datasource/tempo/SearchTraceQLEditor/SearchField.tsx:5381": [ [0, 0, 0, "\'HorizontalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"] ], @@ -6788,6 +6786,11 @@ exports[`better eslint`] = { "public/app/plugins/panel/histogram/utils.ts:5381": [ [0, 0, 0, "\'@grafana/data/src/transformations/transformers/histogram\' import is restricted from being used by a pattern. Import from the public export instead.", "0"] ], + "public/app/plugins/panel/live/LiveChannelEditor.tsx:5381": [ + [0, 0, 0, "Do not use any type assertions.", "0"], + [0, 0, 0, "Do not use any type assertions.", "1"], + [0, 0, 0, "Unexpected any. Specify a different type.", "2"] + ], "public/app/plugins/panel/live/LivePanel.tsx:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"] ], diff --git a/.bingo/README.md b/.bingo/README.md index 1d8a1360ccf..7a5c2d4f6da 100644 --- a/.bingo/README.md +++ b/.bingo/README.md @@ -2,13 +2,13 @@ This is directory which stores Go modules with pinned buildable package that is used within this repository, managed by https://github.com/bwplotka/bingo. -- Run `bingo get` to install all tools having each own module file in this directory. -- Run `bingo get ` to install that have own module file in this directory. -- For Makefile: Make sure to put `include .bingo/Variables.mk` in your Makefile, then use $() variable where is the .bingo/.mod. -- For shell: Run `source .bingo/variables.env` to source all environment variable for each tool. -- For go: Import `.bingo/variables.go` to for variable names. -- See https://github.com/bwplotka/bingo or -h on how to add, remove or change binaries dependencies. +* Run `bingo get` to install all tools having each own module file in this directory. +* Run `bingo get ` to install that have own module file in this directory. +* For Makefile: Make sure to put `include .bingo/Variables.mk` in your Makefile, then use $() variable where is the .bingo/.mod. +* For shell: Run `source .bingo/variables.env` to source all environment variable for each tool. +* For go: Import `.bingo/variables.go` to for variable names. +* See https://github.com/bwplotka/bingo or -h on how to add, remove or change binaries dependencies. ## Requirements -- Go 1.14+ +* Go 1.14+ diff --git a/.bingo/Variables.mk b/.bingo/Variables.mk index 80b5946b497..4160da8e0f7 100644 --- a/.bingo/Variables.mk +++ b/.bingo/Variables.mk @@ -5,68 +5,21 @@ GOPATH ?= $(shell go env GOPATH) GOBIN ?= $(firstword $(subst :, ,${GOPATH}))/bin GO ?= $(shell which go) -# Add this near the top of the file, after the initial variable definitions -ifndef VARIABLES_MK -VARIABLES_MK := 1 - # Below generated variables ensure that every time a tool under each variable is invoked, the correct version # will be used; reinstalling only if needed. -# For example for bra variable: +# For example for drone variable: # # In your main Makefile (for non array binaries): # #include .bingo/Variables.mk # Assuming -dir was set to .bingo . # -#command: $(BRA) -# @echo "Running bra" -# @$(BRA) +#command: $(DRONE) +# @echo "Running drone" +# @$(DRONE) # -BRA := $(GOBIN)/bra-v0.0.0-20200517080246-1e3013ecaff8 -$(BRA): $(BINGO_DIR)/bra.mod - @# Install binary/ries using Go 1.14+ build command. This is using bwplotka/bingo-controlled, separate go module with pinned dependencies. - @echo "(re)installing $(GOBIN)/bra-v0.0.0-20200517080246-1e3013ecaff8" - @cd $(BINGO_DIR) && GOWORK=off $(GO) build -mod=mod -modfile=bra.mod -o=$(GOBIN)/bra-v0.0.0-20200517080246-1e3013ecaff8 "github.com/unknwon/bra" - -COG := $(GOBIN)/cog-v0.0.15 -$(COG): $(BINGO_DIR)/cog.mod - @# Install binary/ries using Go 1.14+ build command. This is using bwplotka/bingo-controlled, separate go module with pinned dependencies. - @echo "(re)installing $(GOBIN)/cog-v0.0.15" - @cd $(BINGO_DIR) && GOWORK=off $(GO) build -mod=mod -modfile=cog.mod -o=$(GOBIN)/cog-v0.0.15 "github.com/grafana/cog/cmd/cli" - -CUE := $(GOBIN)/cue-v0.5.0 -$(CUE): $(BINGO_DIR)/cue.mod - @# Install binary/ries using Go 1.14+ build command. This is using bwplotka/bingo-controlled, separate go module with pinned dependencies. - @echo "(re)installing $(GOBIN)/cue-v0.5.0" - @cd $(BINGO_DIR) && GOWORK=off $(GO) build -mod=mod -modfile=cue.mod -o=$(GOBIN)/cue-v0.5.0 "cuelang.org/go/cmd/cue" - DRONE := $(GOBIN)/drone-v1.5.0 $(DRONE): $(BINGO_DIR)/drone.mod @# Install binary/ries using Go 1.14+ build command. This is using bwplotka/bingo-controlled, separate go module with pinned dependencies. @echo "(re)installing $(GOBIN)/drone-v1.5.0" @cd $(BINGO_DIR) && GOWORK=off CGO_ENABLED=0 $(GO) build -mod=mod -modfile=drone.mod -o=$(GOBIN)/drone-v1.5.0 "github.com/drone/drone-cli/drone" -GOLANGCI_LINT := $(GOBIN)/golangci-lint-v1.62.0 -$(GOLANGCI_LINT): $(BINGO_DIR)/golangci-lint.mod - @# Install binary/ries using Go 1.14+ build command. This is using bwplotka/bingo-controlled, separate go module with pinned dependencies. - @echo "(re)installing $(GOBIN)/golangci-lint-v1.62.0" - @cd $(BINGO_DIR) && GOWORK=off $(GO) build -mod=mod -modfile=golangci-lint.mod -o=$(GOBIN)/golangci-lint-v1.62.0 "github.com/golangci/golangci-lint/cmd/golangci-lint" - -JB := $(GOBIN)/jb-v0.5.1 -$(JB): $(BINGO_DIR)/jb.mod - @# Install binary/ries using Go 1.14+ build command. This is using bwplotka/bingo-controlled, separate go module with pinned dependencies. - @echo "(re)installing $(GOBIN)/jb-v0.5.1" - @cd $(BINGO_DIR) && GOWORK=off $(GO) build -mod=mod -modfile=jb.mod -o=$(GOBIN)/jb-v0.5.1 "github.com/jsonnet-bundler/jsonnet-bundler/cmd/jb" - -LEFTHOOK := $(GOBIN)/lefthook-v1.4.8 -$(LEFTHOOK): $(BINGO_DIR)/lefthook.mod - @# Install binary/ries using Go 1.14+ build command. This is using bwplotka/bingo-controlled, separate go module with pinned dependencies. - @echo "(re)installing $(GOBIN)/lefthook-v1.4.8" - @cd $(BINGO_DIR) && GOWORK=off $(GO) build -mod=mod -modfile=lefthook.mod -o=$(GOBIN)/lefthook-v1.4.8 "github.com/evilmartians/lefthook" - -SWAGGER := $(GOBIN)/swagger-v0.30.6-0.20240310114303-db51e79a0e37 -$(SWAGGER): $(BINGO_DIR)/swagger.mod - @# Install binary/ries using Go 1.14+ build command. This is using bwplotka/bingo-controlled, separate go module with pinned dependencies. - @echo "(re)installing $(GOBIN)/swagger-v0.30.6-0.20240310114303-db51e79a0e37" - @cd $(BINGO_DIR) && GOWORK=off $(GO) build -mod=mod -modfile=swagger.mod -o=$(GOBIN)/swagger-v0.30.6-0.20240310114303-db51e79a0e37 "github.com/go-swagger/go-swagger/cmd/swagger" - -endif diff --git a/.bingo/bra.mod b/.bingo/bra.mod deleted file mode 100644 index 0a64dc1765f..00000000000 --- a/.bingo/bra.mod +++ /dev/null @@ -1,7 +0,0 @@ -module _ // Auto generated by https://github.com/bwplotka/bingo. DO NOT EDIT - -go 1.17 - -replace golang.org/x/sys => golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c - -require github.com/unknwon/bra v0.0.0-20200517080246-1e3013ecaff8 diff --git a/.bingo/bra.sum b/.bingo/bra.sum deleted file mode 100644 index d4e9da39ec8..00000000000 --- a/.bingo/bra.sum +++ /dev/null @@ -1,30 +0,0 @@ -github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY= -github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= -github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= -github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= -github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= -github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= -github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s= -github.com/unknwon/bra v0.0.0-20200517080246-1e3013ecaff8 h1:aVGB3YnaS/JNfOW3tiHIlmNmTDg618va+eT0mVomgyI= -github.com/unknwon/bra v0.0.0-20200517080246-1e3013ecaff8/go.mod h1:fVle4kNr08ydeohzYafr20oZzbAkhQT39gKK/pFQ5M4= -github.com/unknwon/com v1.0.1 h1:3d1LTxD+Lnf3soQiD4Cp/0BRB+Rsa/+RTvz8GMMzIXs= -github.com/unknwon/com v1.0.1/go.mod h1:tOOxU81rwgoCLoOVVPHb6T/wt8HZygqH5id+GNnlCXM= -github.com/unknwon/log v0.0.0-20150304194804-e617c87089d3 h1:4EYQaWAatQokdji3zqZloVIW/Ke1RQjYw2zHULyrHJg= -github.com/unknwon/log v0.0.0-20150304194804-e617c87089d3/go.mod h1:1xEUf2abjfP92w2GZTV+GgaRxXErwRXcClbUwrNJffU= -github.com/urfave/cli v1.22.1 h1:+mkCCcOFKPnCmVYVcURKps1Xe+3zP90gSYGNfRkjoIY= -github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= -golang.org/x/sys v0.0.0-20191020152052-9984515f0562 h1:wOweSabW7qssfcg63CEDHHA4zyoqRlGU6eYV7IUMCq0= -golang.org/x/sys v0.0.0-20191020152052-9984515f0562/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/fsnotify/fsnotify.v1 v1.4.7 h1:XNNYLJHt73EyYiCZi6+xjupS9CpvmiDgjPTAjrBlQbo= -gopkg.in/fsnotify/fsnotify.v1 v1.4.7/go.mod h1:Fyux9zXlo4rWoMSIzpn9fDAYjalPqJ/K1qJ27s+7ltE= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c h1:aFV+BgZ4svzjfabn8ERpuB4JI4N6/rdy1iusx77G3oU= -golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= \ No newline at end of file diff --git a/.bingo/cog.mod b/.bingo/cog.mod deleted file mode 100644 index c6c7b46ef7d..00000000000 --- a/.bingo/cog.mod +++ /dev/null @@ -1,5 +0,0 @@ -module _ // Auto generated by https://github.com/bwplotka/bingo. DO NOT EDIT - -go 1.23.4 - -require github.com/grafana/cog v0.0.15 // cmd/cli diff --git a/.bingo/cog.sum b/.bingo/cog.sum deleted file mode 100644 index 8280a813d47..00000000000 --- a/.bingo/cog.sum +++ /dev/null @@ -1,86 +0,0 @@ -cuelabs.dev/go/oci/ociregistry v0.0.0-20240906074133-82eb438dd565 h1:R5wwEcbEZSBmeyg91MJZTxfd7WpBo2jPof3AYjRbxwY= -cuelabs.dev/go/oci/ociregistry v0.0.0-20240906074133-82eb438dd565/go.mod h1:5A4xfTzHTXfeVJBU6RAUf+QrlfTCW+017q/QiW+sMLg= -cuelang.org/go v0.11.0 h1:2af2nhipqlUHtXk2dtOP5xnMm1ObGvKqIsJUJL1sRE4= -cuelang.org/go v0.11.0/go.mod h1:PBY6XvPUswPPJ2inpvUozP9mebDVTXaeehQikhZPBz0= -github.com/cockroachdb/apd/v3 v3.2.1 h1:U+8j7t0axsIgvQUqthuNm82HIrYXodOV2iWLWtEaIwg= -github.com/cockroachdb/apd/v3 v3.2.1/go.mod h1:klXJcjp+FffLTHlhIG69tezTDvdP065naDsHzKhYSqc= -github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/emicklei/proto v1.13.2 h1:z/etSFO3uyXeuEsVPzfl56WNgzcvIr42aQazXaQmFZY= -github.com/emicklei/proto v1.13.2/go.mod h1:rn1FgRS/FANiZdD2djyH7TMA9jdRDcYQ9IEN9yvjX0A= -github.com/expr-lang/expr v1.16.9 h1:WUAzmR0JNI9JCiF0/ewwHB1gmcGw5wW7nWt8gc6PpCI= -github.com/expr-lang/expr v1.16.9/go.mod h1:8/vRC7+7HBzESEqt5kKpYXxrxkr31SaO8r40VO/1IT4= -github.com/getkin/kin-openapi v0.128.0 h1:jqq3D9vC9pPq1dGcOCv7yOp1DaEe7c/T1vzcLbITSp4= -github.com/getkin/kin-openapi v0.128.0/go.mod h1:OZrfXzUfGrNbsKj+xmFBx6E5c6yH3At/tAKSc2UszXM= -github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= -github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= -github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= -github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/grafana/codejen v0.0.4-0.20230321061741-77f656893a3d h1:hrXbGJ5jgp6yNITzs5o+zXq0V5yT3siNJ+uM8LGwWKk= -github.com/grafana/codejen v0.0.4-0.20230321061741-77f656893a3d/go.mod h1:zmwwM/DRyQB7pfuBjTWII3CWtxcXh8LTwAYGfDfpR6s= -github.com/grafana/cog v0.0.14 h1:sBK89oSu9BK4S9l3G9ewVJnGYnNQJTHFBC/01DZDRZs= -github.com/grafana/cog v0.0.14/go.mod h1:HwJbc60fZ+viayROClLGdDwO5w/JjBOpO9wjGnAfMLc= -github.com/grafana/cog v0.0.15 h1:e2pMY+Hf2nS22HcKJuguEzl0BVmV9DSINwCfWt+dFZQ= -github.com/grafana/cog v0.0.15/go.mod h1:jrS9indvWuDs60RHEZpLaAkmZdgyoLKMOEUT0jiB1t0= -github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= -github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= -github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= -github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI= -github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= -github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= -github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/invopop/yaml v0.3.1 h1:f0+ZpmhfBSS4MhG+4HYseMdJhoeeopbSKbq5Rpeelso= -github.com/invopop/yaml v0.3.1/go.mod h1:PMOp3nn4/12yEZUFfmOuNHJsZToEEOwoWsT+D81KkeA= -github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= -github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= -github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= -github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= -github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= -github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= -github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= -github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= -github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= -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/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= -github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= -github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s= -github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= -github.com/protocolbuffers/txtpbfmt v0.0.0-20241112170944-20d2c9ebc01d h1:HWfigq7lB31IeJL8iy7jkUmU/PG1Sr8jVGhS749dbUA= -github.com/protocolbuffers/txtpbfmt v0.0.0-20241112170944-20d2c9ebc01d/go.mod h1:jgxiZysxFPM+iWKwQwPR+y+Jvo54ARd4EisXxKYpB5c= -github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= -github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= -github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 h1:lZUw3E0/J3roVtGQ+SCrUrg3ON6NgVqpn3+iol9aGu4= -github.com/santhosh-tekuri/jsonschema/v5 v5.3.1/go.mod h1:uToXkOrWAZ6/Oc07xWQrPOhJotwFIyu2bBVN41fcDUY= -github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= -github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/yalue/merged_fs v1.3.0 h1:qCeh9tMPNy/i8cwDsQTJ5bLr6IRxbs6meakNE5O+wyY= -github.com/yalue/merged_fs v1.3.0/go.mod h1:WqqchfVYQyclV2tnR7wtRhBddzBvLVR83Cjw9BKQw0M= -golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= -golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= -golang.org/x/net v0.32.0 h1:ZqPmj8Kzc+Y6e0+skZsuACbx+wzMgo5MQsJh9Qd6aYI= -golang.org/x/net v0.32.0/go.mod h1:CwU0IoeOlnQQWJ6ioyFrfRuomB8GKF6KbYXZVyeXNfs= -golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= -golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= -golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE= -golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= -golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= -golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= -golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= -golang.org/x/tools v0.28.0 h1:WuB6qZ4RPCQo5aP3WdKZS7i595EdWqWR8vqJTlwTVK8= -golang.org/x/tools v0.28.0/go.mod h1:dcIOrVd3mfQKTgrDVQHqCPMWy6lnhfhtX3hLXYVLfRw= -golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE= -golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/.bingo/cue.mod b/.bingo/cue.mod deleted file mode 100644 index 6e13b6d3eec..00000000000 --- a/.bingo/cue.mod +++ /dev/null @@ -1,5 +0,0 @@ -module _ // Auto generated by https://github.com/bwplotka/bingo. DO NOT EDIT - -go 1.19 - -require cuelang.org/go v0.5.0 // cmd/cue diff --git a/.bingo/cue.sum b/.bingo/cue.sum deleted file mode 100644 index 5e9012b2946..00000000000 --- a/.bingo/cue.sum +++ /dev/null @@ -1,58 +0,0 @@ -cuelang.org/go v0.4.3 h1:W3oBBjDTm7+IZfCKZAmC8uDG0eYfJL4Pp/xbbCMKaVo= -cuelang.org/go v0.4.3/go.mod h1:7805vR9H+VoBNdWFdI7jyDR3QLUPp4+naHfbcgp55HI= -cuelang.org/go v0.5.0-beta.2 h1:am5M7jGvNTJ0rnjrFNyvE7fucL/wRqb0emK4XxdThQI= -cuelang.org/go v0.5.0-beta.2/go.mod h1:okjJBHFQFer+a41sAe2SaGm1glWS8oEb6CmJvn5Zdws= -cuelang.org/go v0.5.0 h1:D6N0UgTGJCOxFKU8RU+qYvavKNsVc/+ZobmifStVJzU= -cuelang.org/go v0.5.0/go.mod h1:okjJBHFQFer+a41sAe2SaGm1glWS8oEb6CmJvn5Zdws= -github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= -github.com/cockroachdb/apd/v2 v2.0.1 h1:y1Rh3tEU89D+7Tgbw+lp52T6p/GJLpDmNvr10UWqLTE= -github.com/cockroachdb/apd/v2 v2.0.2 h1:weh8u7Cneje73dDh+2tEVLUvyBc89iwepWCD8b8034E= -github.com/cockroachdb/apd/v2 v2.0.2/go.mod h1:DDxRlzC2lo3/vSlmSoS7JkqbbrARPuFOGr0B9pvN3Gw= -github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/emicklei/proto v1.6.15 h1:XbpwxmuOPrdES97FrSfpyy67SSCV/wBIKXqgJzh6hNw= -github.com/emicklei/proto v1.10.0 h1:pDGyFRVV5RvV+nkBK9iy3q67FBy9Xa7vwrOTE+g5aGw= -github.com/emicklei/proto v1.10.0/go.mod h1:rn1FgRS/FANiZdD2djyH7TMA9jdRDcYQ9IEN9yvjX0A= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= -github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs= -github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= -github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= -github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= -github.com/mpvl/unique v0.0.0-20150818121801-cbe035fff7de h1:D5x39vF5KCwKQaw+OC9ZPiLVHXz3UFw2+psEX+gYcto= -github.com/mpvl/unique v0.0.0-20150818121801-cbe035fff7de/go.mod h1:kJun4WP5gFuHZgRjZUWWuH1DTxCtxbHDOIJsudS8jzY= -github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/protocolbuffers/txtpbfmt v0.0.0-20201118171849-f6a6b3f636fc h1:gSVONBi2HWMFXCa9jFdYvYk7IwW/mTLxWOF7rXS4LO0= -github.com/protocolbuffers/txtpbfmt v0.0.0-20220428173112-74888fd59c2b h1:zd/2RNzIRkoGGMjE+YIsZ85CnDIz672JK2F3Zl4vux4= -github.com/protocolbuffers/txtpbfmt v0.0.0-20220428173112-74888fd59c2b/go.mod h1:KjY0wibdYKc4DYkerHSbguaf3JeIPGhNJBp2BNiFH78= -github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/spf13/cobra v1.4.0 h1:y+wJpx64xcgO1V+RcnwW0LEHxTKRi2ZDPSBjWnrg88Q= -github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -golang.org/x/mod v0.3.1-0.20200828183125-ce943fd02449 h1:xUIPaMhvROX9dhPvRCenIJtU78+lbEenGbgqB5hfHCQ= -golang.org/x/mod v0.6.0-dev.0.20220818022119-ed83ed61efb9 h1:VtCrPQXM5Wo9l7XN64SjBMczl48j8mkP+2e3OhYlz+0= -golang.org/x/mod v0.6.0-dev.0.20220818022119-ed83ed61efb9/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/net v0.0.0-20200226121028-0de0cce0169b h1:0mm1VjtFUOIlE1SbDlwjYaDxZVDP2S5ou6y0gSgXHu8= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b h1:PxfKdU9lEEDYjdIzOtC4qFWgkU2rGHdKlKowJSMN9h0= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= -golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY= -golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= -golang.org/x/tools v0.0.0-20200612220849-54c614fe050c h1:g6oFfz6Cmw68izP3xsdud3Oxu145IPkeFzyRg58AKHM= -golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/.bingo/golangci-lint.mod b/.bingo/golangci-lint.mod deleted file mode 100644 index 73e12b0e2ed..00000000000 --- a/.bingo/golangci-lint.mod +++ /dev/null @@ -1,5 +0,0 @@ -module _ // Auto generated by https://github.com/bwplotka/bingo. DO NOT EDIT - -go 1.23 - -require github.com/golangci/golangci-lint v1.62.0 // cmd/golangci-lint diff --git a/.bingo/golangci-lint.sum b/.bingo/golangci-lint.sum deleted file mode 100644 index 6e18a455fa9..00000000000 --- a/.bingo/golangci-lint.sum +++ /dev/null @@ -1,1174 +0,0 @@ -4d63.com/gocheckcompilerdirectives v1.2.1 h1:AHcMYuw56NPjq/2y615IGg2kYkBdTvOaojYCBcRE7MA= -4d63.com/gocheckcompilerdirectives v1.2.1/go.mod h1:yjDJSxmDTtIHHCqX0ufRYZDL6vQtMG7tJdKVeWwsqvs= -4d63.com/gochecknoglobals v0.2.1 h1:1eiorGsgHOFOuoOiJDy2psSrQbRdIHrlge0IJIkUgDc= -4d63.com/gochecknoglobals v0.2.1/go.mod h1:KRE8wtJB3CXCsb1xy421JfTHIIbmT3U5ruxw2Qu8fSU= -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= -cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= -cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= -cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= -cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= -cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= -cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= -cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= -cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= -cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= -cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= -cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= -cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= -cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= -cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= -cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= -cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= -cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= -cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= -cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= -cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= -cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= -cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= -cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= -cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= -cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= -cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= -cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= -cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= -dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/4meepo/tagalign v1.3.3 h1:ZsOxcwGD/jP4U/aw7qeWu58i7dwYemfy5Y+IF1ACoNw= -github.com/4meepo/tagalign v1.3.3/go.mod h1:Q9c1rYMZJc9dPRkbQPpcBNCLEmY2njbAsXhQOZFE2dE= -github.com/4meepo/tagalign v1.3.4 h1:P51VcvBnf04YkHzjfclN6BbsopfJR5rxs1n+5zHt+w8= -github.com/4meepo/tagalign v1.3.4/go.mod h1:M+pnkHH2vG8+qhE5bVc/zeP7HS/j910Fwa9TUSyZVI0= -github.com/Abirdcfly/dupword v0.0.14 h1:3U4ulkc8EUo+CaT105/GJ1BQwtgyj6+VaBVbAX11Ba8= -github.com/Abirdcfly/dupword v0.0.14/go.mod h1:VKDAbxdY8YbKUByLGg8EETzYSuC4crm9WwI6Y3S0cLI= -github.com/Abirdcfly/dupword v0.1.1 h1:Bsxe0fIw6OwBtXMIncaTxCLHYO5BB+3mcsR5E8VXloY= -github.com/Abirdcfly/dupword v0.1.1/go.mod h1:B49AcJdTYYkpd4HjgAcutNGG9HZ2JWwKunH9Y2BA6sM= -github.com/Abirdcfly/dupword v0.1.3 h1:9Pa1NuAsZvpFPi9Pqkd93I7LIYRURj+A//dFd5tgBeE= -github.com/Abirdcfly/dupword v0.1.3/go.mod h1:8VbB2t7e10KRNdwTVoxdBaxla6avbhGzb8sCTygUMhw= -github.com/Antonboom/errname v0.1.12 h1:oh9ak2zUtsLp5oaEd/erjB4GPu9w19NyoIskZClDcQY= -github.com/Antonboom/errname v0.1.12/go.mod h1:bK7todrzvlaZoQagP1orKzWXv59X/x0W0Io2XT1Ssro= -github.com/Antonboom/errname v0.1.13 h1:JHICqsewj/fNckzrfVSe+T33svwQxmjC+1ntDsHOVvM= -github.com/Antonboom/errname v0.1.13/go.mod h1:uWyefRYRN54lBg6HseYCFhs6Qjcy41Y3Jl/dVhA87Ns= -github.com/Antonboom/errname v1.0.0 h1:oJOOWR07vS1kRusl6YRSlat7HFnb3mSfMl6sDMRoTBA= -github.com/Antonboom/errname v1.0.0/go.mod h1:gMOBFzK/vrTiXN9Oh+HFs+e6Ndl0eTFbtsRTSRdXyGI= -github.com/Antonboom/nilnil v0.1.7 h1:ofgL+BA7vlA1K2wNQOsHzLJ2Pw5B5DpWRLdDAVvvTow= -github.com/Antonboom/nilnil v0.1.7/go.mod h1:TP+ScQWVEq0eSIxqU8CbdT5DFWoHp0MbP+KMUO1BKYQ= -github.com/Antonboom/nilnil v0.1.9 h1:eKFMejSxPSA9eLSensFmjW2XTgTwJMjZ8hUHtV4s/SQ= -github.com/Antonboom/nilnil v0.1.9/go.mod h1:iGe2rYwCq5/Me1khrysB4nwI7swQvjclR8/YRPl5ihQ= -github.com/Antonboom/nilnil v1.0.0 h1:n+v+B12dsE5tbAqRODXmEKfZv9j2KcTBrp+LkoM4HZk= -github.com/Antonboom/nilnil v1.0.0/go.mod h1:fDJ1FSFoLN6yoG65ANb1WihItf6qt9PJVTn/s2IrcII= -github.com/Antonboom/testifylint v1.2.0 h1:015bxD8zc5iY8QwTp4+RG9I4kIbqwvGX9TrBbb7jGdM= -github.com/Antonboom/testifylint v1.2.0/go.mod h1:rkmEqjqVnHDRNsinyN6fPSLnoajzFwsCcguJgwADBkw= -github.com/Antonboom/testifylint v1.3.0 h1:UiqrddKs1W3YK8R0TUuWwrVKlVAnS07DTUVWWs9c+y4= -github.com/Antonboom/testifylint v1.3.0/go.mod h1:NV0hTlteCkViPW9mSR4wEMfwp+Hs1T3dY60bkvSfhpM= -github.com/Antonboom/testifylint v1.3.1 h1:Uam4q1Q+2b6H7gvk9RQFw6jyVDdpzIirFOOrbs14eG4= -github.com/Antonboom/testifylint v1.3.1/go.mod h1:NV0hTlteCkViPW9mSR4wEMfwp+Hs1T3dY60bkvSfhpM= -github.com/Antonboom/testifylint v1.4.3 h1:ohMt6AHuHgttaQ1xb6SSnxCeK4/rnK7KKzbvs7DmEck= -github.com/Antonboom/testifylint v1.4.3/go.mod h1:+8Q9+AOLsz5ZiQiiYujJKs9mNz398+M6UgslP4qgJLA= -github.com/Antonboom/testifylint v1.5.0 h1:dlUIsDMtCrZWUnvkaCz3quJCoIjaGi41GzjPBGkkJ8A= -github.com/Antonboom/testifylint v1.5.0/go.mod h1:wqaJbu0Blb5Wag2wv7Z5xt+CIV+eVLxtGZrlK13z3AE= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= -github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= -github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0= -github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= -github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c h1:pxW6RcqyfI9/kWtOwnv/G+AzdKuy2ZrqINhenH4HyNs= -github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= -github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/Crocmagnon/fatcontext v0.2.2 h1:OrFlsDdOj9hW/oBEJBNSuH7QWf+E9WPVHw+x52bXVbk= -github.com/Crocmagnon/fatcontext v0.2.2/go.mod h1:WSn/c/+MMNiD8Pri0ahRj0o9jVpeowzavOQplBJw6u0= -github.com/Crocmagnon/fatcontext v0.4.0 h1:4ykozu23YHA0JB6+thiuEv7iT6xq995qS1vcuWZq0tg= -github.com/Crocmagnon/fatcontext v0.4.0/go.mod h1:ZtWrXkgyfsYPzS6K3O88va6t2GEglG93vnII/F94WC0= -github.com/Crocmagnon/fatcontext v0.5.2 h1:vhSEg8Gqng8awhPju2w7MKHqMlg4/NI+gSDHtR3xgwA= -github.com/Crocmagnon/fatcontext v0.5.2/go.mod h1:87XhRMaInHP44Q7Tlc7jkgKKB7kZAOPiDkFMdKCC+74= -github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 h1:sHglBQTwgx+rWPdisA5ynNEsoARbiCBOyGcJM4/OzsM= -github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24/go.mod h1:4UJr5HIiMZrwgkSPdsjy2uOQExX/WEILpIrO9UPGuXs= -github.com/GaijinEntertainment/go-exhaustruct/v3 v3.2.0 h1:sATXp1x6/axKxz2Gjxv8MALP0bXaNRfQinEwyfMcx8c= -github.com/GaijinEntertainment/go-exhaustruct/v3 v3.2.0/go.mod h1:Nl76DrGNJTA1KJ0LePKBw/vznBX1EHbAZX8mwjR82nI= -github.com/GaijinEntertainment/go-exhaustruct/v3 v3.3.0 h1:/fTUt5vmbkAcMBt4YQiuC23cV0kEsN1MVMNqeOW43cU= -github.com/GaijinEntertainment/go-exhaustruct/v3 v3.3.0/go.mod h1:ONJg5sxcbsdQQ4pOW8TGdTidT2TMAUy/2Xhr8mrYaao= -github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= -github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= -github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0= -github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= -github.com/Masterminds/semver/v3 v3.3.0 h1:B8LGeaivUe71a5qox1ICM/JLl0NqZSW5CHyL+hmvYS0= -github.com/Masterminds/semver/v3 v3.3.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= -github.com/OpenPeeDeeP/depguard/v2 v2.2.0 h1:vDfG60vDtIuf0MEOhmLlLLSzqaRM8EMcgJPdp74zmpA= -github.com/OpenPeeDeeP/depguard/v2 v2.2.0/go.mod h1:CIzddKRvLBC4Au5aYP/i3nyaWQ+ClszLIuVocRiCYFQ= -github.com/alecthomas/go-check-sumtype v0.1.4 h1:WCvlB3l5Vq5dZQTFmodqL2g68uHiSwwlWcT5a2FGK0c= -github.com/alecthomas/go-check-sumtype v0.1.4/go.mod h1:WyYPfhfkdhyrdaligV6svFopZV8Lqdzn5pyVBaV6jhQ= -github.com/alecthomas/go-check-sumtype v0.2.0 h1:Bo+e4DFf3rs7ME9w/0SU/g6nmzJaphduP8Cjiz0gbwY= -github.com/alecthomas/go-check-sumtype v0.2.0/go.mod h1:WyYPfhfkdhyrdaligV6svFopZV8Lqdzn5pyVBaV6jhQ= -github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= -github.com/alexkohler/nakedret/v2 v2.0.4 h1:yZuKmjqGi0pSmjGpOC016LtPJysIL0WEUiaXW5SUnNg= -github.com/alexkohler/nakedret/v2 v2.0.4/go.mod h1:bF5i0zF2Wo2o4X4USt9ntUWve6JbFv02Ff4vlkmS/VU= -github.com/alexkohler/nakedret/v2 v2.0.5 h1:fP5qLgtwbx9EJE8dGEERT02YwS8En4r9nnZ71RK+EVU= -github.com/alexkohler/nakedret/v2 v2.0.5/go.mod h1:bF5i0zF2Wo2o4X4USt9ntUWve6JbFv02Ff4vlkmS/VU= -github.com/alexkohler/prealloc v1.0.0 h1:Hbq0/3fJPQhNkN0dR95AVrr6R7tou91y0uHG5pOcUuw= -github.com/alexkohler/prealloc v1.0.0/go.mod h1:VetnK3dIgFBBKmg0YnD9F9x6Icjd+9cvfHR56wJVlKE= -github.com/alingse/asasalint v0.0.11 h1:SFwnQXJ49Kx/1GghOFz1XGqHYKp21Kq1nHad/0WQRnw= -github.com/alingse/asasalint v0.0.11/go.mod h1:nCaoMhw7a9kSJObvQyVzNTPBDbNpdocqrSP7t/cW5+I= -github.com/ashanbrown/forbidigo v1.6.0 h1:D3aewfM37Yb3pxHujIPSpTf6oQk9sc9WZi8gerOIVIY= -github.com/ashanbrown/forbidigo v1.6.0/go.mod h1:Y8j9jy9ZYAEHXdu723cUlraTqbzjKF1MUyfOKL+AjcU= -github.com/ashanbrown/makezero v1.1.1 h1:iCQ87C0V0vSyO+M9E/FZYbu65auqH0lnsOkf5FcB28s= -github.com/ashanbrown/makezero v1.1.1/go.mod h1:i1bJLCRSCHOcOa9Y6MyF2FTfMZMFdHvxKHxgO5Z1axI= -github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= -github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= -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/bkielbasa/cyclop v1.2.1 h1:AeF71HZDob1P2/pRm1so9cd1alZnrpyc4q2uP2l0gJY= -github.com/bkielbasa/cyclop v1.2.1/go.mod h1:K/dT/M0FPAiYjBgQGau7tz+3TMh4FWAEqlMhzFWCrgM= -github.com/bkielbasa/cyclop v1.2.3 h1:faIVMIGDIANuGPWH031CZJTi2ymOQBULs9H21HSMa5w= -github.com/bkielbasa/cyclop v1.2.3/go.mod h1:kHTwA9Q0uZqOADdupvcFJQtp/ksSnytRMe8ztxG8Fuo= -github.com/blizzy78/varnamelen v0.8.0 h1:oqSblyuQvFsW1hbBHh1zfwrKe3kcSj0rnXkKzsQ089M= -github.com/blizzy78/varnamelen v0.8.0/go.mod h1:V9TzQZ4fLJ1DSrjVDfl89H7aMnTvKkApdHeyESmyR7k= -github.com/bombsimon/wsl/v4 v4.2.1 h1:Cxg6u+XDWff75SIFFmNsqnIOgob+Q9hG6y/ioKbRFiM= -github.com/bombsimon/wsl/v4 v4.2.1/go.mod h1:Xu/kDxGZTofQcDGCtQe9KCzhHphIe0fDuyWTxER9Feo= -github.com/bombsimon/wsl/v4 v4.4.1 h1:jfUaCkN+aUpobrMO24zwyAMwMAV5eSziCkOKEauOLdw= -github.com/bombsimon/wsl/v4 v4.4.1/go.mod h1:Xu/kDxGZTofQcDGCtQe9KCzhHphIe0fDuyWTxER9Feo= -github.com/breml/bidichk v0.2.7 h1:dAkKQPLl/Qrk7hnP6P+E0xOodrq8Us7+U0o4UBOAlQY= -github.com/breml/bidichk v0.2.7/go.mod h1:YodjipAGI9fGcYM7II6wFvGhdMYsC5pHDlGzqvEW3tQ= -github.com/breml/bidichk v0.3.2 h1:xV4flJ9V5xWTqxL+/PMFF6dtJPvZLPsyixAoPe8BGJs= -github.com/breml/bidichk v0.3.2/go.mod h1:VzFLBxuYtT23z5+iVkamXO386OB+/sVwZOpIj6zXGos= -github.com/breml/errchkjson v0.3.6 h1:VLhVkqSBH96AvXEyclMR37rZslRrY2kcyq+31HCsVrA= -github.com/breml/errchkjson v0.3.6/go.mod h1:jhSDoFheAF2RSDOlCfhHO9KqhZgAYLyvHe7bRCX8f/U= -github.com/breml/errchkjson v0.4.0 h1:gftf6uWZMtIa/Is3XJgibewBm2ksAQSY/kABDNFTAdk= -github.com/breml/errchkjson v0.4.0/go.mod h1:AuBOSTHyLSaaAFlWsRSuRBIroCh3eh7ZHh5YeelDIk8= -github.com/butuzov/ireturn v0.3.0 h1:hTjMqWw3y5JC3kpnC5vXmFJAWI/m31jaCYQqzkS6PL0= -github.com/butuzov/ireturn v0.3.0/go.mod h1:A09nIiwiqzN/IoVo9ogpa0Hzi9fex1kd9PSD6edP5ZA= -github.com/butuzov/mirror v1.1.0 h1:ZqX54gBVMXu78QLoiqdwpl2mgmoOJTk7s4p4o+0avZI= -github.com/butuzov/mirror v1.1.0/go.mod h1:8Q0BdQU6rC6WILDiBM60DBfvV78OLJmMmixe7GF45AE= -github.com/butuzov/mirror v1.2.0 h1:9YVK1qIjNspaqWutSv8gsge2e/Xpq1eqEkslEUHy5cs= -github.com/butuzov/mirror v1.2.0/go.mod h1:DqZZDtzm42wIAIyHXeN8W/qb1EPlb9Qn/if9icBOpdQ= -github.com/catenacyber/perfsprint v0.7.1 h1:PGW5G/Kxn+YrN04cRAZKC+ZuvlVwolYMrIyyTJ/rMmc= -github.com/catenacyber/perfsprint v0.7.1/go.mod h1:/wclWYompEyjUD2FuIIDVKNkqz7IgBIWXIH3V0Zol50= -github.com/ccojocar/zxcvbn-go v1.0.2 h1:na/czXU8RrhXO4EZme6eQJLR4PzcGsahsBOAwU6I3Vg= -github.com/ccojocar/zxcvbn-go v1.0.2/go.mod h1:g1qkXtUSvHP8lhHp5GrSmTz6uWALGRMQdw6Qnz/hi60= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= -github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/charithe/durationcheck v0.0.10 h1:wgw73BiocdBDQPik+zcEoBG/ob8uyBHf2iyoHGPf5w4= -github.com/charithe/durationcheck v0.0.10/go.mod h1:bCWXb7gYRysD1CU3C+u4ceO49LoGOY1C1L6uouGNreQ= -github.com/chavacava/garif v0.1.0 h1:2JHa3hbYf5D9dsgseMKAmc/MZ109otzgNFk5s87H9Pc= -github.com/chavacava/garif v0.1.0/go.mod h1:XMyYCkEL58DF0oyW4qDjjnPWONs2HBqYKI+UIPD+Gww= -github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= -github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/ckaznocha/intrange v0.1.0 h1:ZiGBhvrdsKpoEfzh9CjBfDSZof6QB0ORY5tXasUtiew= -github.com/ckaznocha/intrange v0.1.0/go.mod h1:Vwa9Ekex2BrEQMg6zlrWwbs/FtYw7eS5838Q7UjK7TQ= -github.com/ckaznocha/intrange v0.1.2 h1:3Y4JAxcMntgb/wABQ6e8Q8leMd26JbX2790lIss9MTI= -github.com/ckaznocha/intrange v0.1.2/go.mod h1:RWffCw/vKBwHeOEwWdCikAtY0q4gGt8VhJZEEA5n+RE= -github.com/ckaznocha/intrange v0.2.0 h1:FykcZuJ8BD7oX93YbO1UY9oZtkRbp+1/kJcDjkefYLs= -github.com/ckaznocha/intrange v0.2.0/go.mod h1:r5I7nUlAAG56xmkOpw4XVr16BXhwYTUdcuRFeevn1oE= -github.com/ckaznocha/intrange v0.2.1 h1:M07spnNEQoALOJhwrImSrJLaxwuiQK+hA2DeajBlwYk= -github.com/ckaznocha/intrange v0.2.1/go.mod h1:7NEhVyf8fzZO5Ds7CRaqPEm52Ut83hsTiL5zbER/HYk= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/curioswitch/go-reassign v0.2.0 h1:G9UZyOcpk/d7Gd6mqYgd8XYWFMw/znxwGDUstnC9DIo= -github.com/curioswitch/go-reassign v0.2.0/go.mod h1:x6OpXuWvgfQaMGks2BZybTngWjT84hqJfKoO8Tt/Roc= -github.com/daixiang0/gci v0.12.3 h1:yOZI7VAxAGPQmkb1eqt5g/11SUlwoat1fSblGLmdiQc= -github.com/daixiang0/gci v0.12.3/go.mod h1:xtHP9N7AHdNvtRNfcx9gwTDfw7FRJx4bZUsiEfiNNAI= -github.com/daixiang0/gci v0.13.4 h1:61UGkmpoAcxHM2hhNkZEf5SzwQtWJXTSws7jaPyqwlw= -github.com/daixiang0/gci v0.13.4/go.mod h1:12etP2OniiIdP4q+kjUGrC/rUagga7ODbqsom5Eo5Yk= -github.com/daixiang0/gci v0.13.5 h1:kThgmH1yBmZSBCh1EJVxQ7JsHpm5Oms0AMed/0LaH4c= -github.com/daixiang0/gci v0.13.5/go.mod h1:12etP2OniiIdP4q+kjUGrC/rUagga7ODbqsom5Eo5Yk= -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/denis-tingaikin/go-header v0.5.0 h1:SRdnP5ZKvcO9KKRP1KJrhFR3RrlGuD+42t4429eC9k8= -github.com/denis-tingaikin/go-header v0.5.0/go.mod h1:mMenU5bWrok6Wl2UsZjy+1okegmwQ3UgWl4V1D8gjlY= -github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/ettle/strcase v0.2.0 h1:fGNiVF21fHXpX1niBgk0aROov1LagYsOwV/xqKDKR/Q= -github.com/ettle/strcase v0.2.0/go.mod h1:DajmHElDSaX76ITe3/VHVyMin4LWSJN5Z909Wp+ED1A= -github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= -github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= -github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4= -github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI= -github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= -github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= -github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4= -github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94= -github.com/firefart/nonamedreturns v1.0.4 h1:abzI1p7mAEPYuR4A+VLKn4eNDOycjYo2phmY9sfv40Y= -github.com/firefart/nonamedreturns v1.0.4/go.mod h1:TDhe/tjI1BXo48CmYbUduTV7BdIga8MAO/xbKdcVsGI= -github.com/firefart/nonamedreturns v1.0.5 h1:tM+Me2ZaXs8tfdDw3X6DOX++wMCOqzYUho6tUTYIdRA= -github.com/firefart/nonamedreturns v1.0.5/go.mod h1:gHJjDqhGM4WyPt639SOZs+G89Ko7QKH5R5BhnO6xJhw= -github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= -github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= -github.com/fzipp/gocyclo v0.6.0 h1:lsblElZG7d3ALtGMx9fmxeTKZaLLpU8mET09yN4BBLo= -github.com/fzipp/gocyclo v0.6.0/go.mod h1:rXPyn8fnlpa0R2csP/31uerbiVBugk5whMdlyaLkLoA= -github.com/ghostiam/protogetter v0.3.5 h1:+f7UiF8XNd4w3a//4DnusQ2SZjPkUjxkMEfjbxOK4Ug= -github.com/ghostiam/protogetter v0.3.5/go.mod h1:7lpeDnEJ1ZjL/YtyoN99ljO4z0pd3H0d18/t2dPBxHw= -github.com/ghostiam/protogetter v0.3.6 h1:R7qEWaSgFCsy20yYHNIJsU9ZOb8TziSRRxuAOTVKeOk= -github.com/ghostiam/protogetter v0.3.6/go.mod h1:7lpeDnEJ1ZjL/YtyoN99ljO4z0pd3H0d18/t2dPBxHw= -github.com/ghostiam/protogetter v0.3.8 h1:LYcXbYvybUyTIxN2Mj9h6rHrDZBDwZloPoKctWrFyJY= -github.com/ghostiam/protogetter v0.3.8/go.mod h1:WZ0nw9pfzsgxuRsPOFQomgDVSWtDLJRfQJEhsGbmQMA= -github.com/go-critic/go-critic v0.11.2 h1:81xH/2muBphEgPtcwH1p6QD+KzXl2tMSi3hXjBSxDnM= -github.com/go-critic/go-critic v0.11.2/go.mod h1:OePaicfjsf+KPy33yq4gzv6CO7TEQ9Rom6ns1KsJnl8= -github.com/go-critic/go-critic v0.11.4 h1:O7kGOCx0NDIni4czrkRIXTnit0mkyKOCePh3My6OyEU= -github.com/go-critic/go-critic v0.11.4/go.mod h1:2QAdo4iuLik5S9YG0rT4wcZ8QxwHYkrr6/2MWAiv/vc= -github.com/go-critic/go-critic v0.11.5 h1:TkDTOn5v7EEngMxu8KbuFqFR43USaaH8XRJLz1jhVYA= -github.com/go-critic/go-critic v0.11.5/go.mod h1:wu6U7ny9PiaHaZHcvMDmdysMqvDem162Rh3zWTrqk8M= -github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= -github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= -github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= -github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= -github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/go-toolsmith/astcast v1.1.0 h1:+JN9xZV1A+Re+95pgnMgDboWNVnIMMQXwfBwLRPgSC8= -github.com/go-toolsmith/astcast v1.1.0/go.mod h1:qdcuFWeGGS2xX5bLM/c3U9lewg7+Zu4mr+xPwZIB4ZU= -github.com/go-toolsmith/astcopy v1.1.0 h1:YGwBN0WM+ekI/6SS6+52zLDEf8Yvp3n2seZITCUBt5s= -github.com/go-toolsmith/astcopy v1.1.0/go.mod h1:hXM6gan18VA1T/daUEHCFcYiW8Ai1tIwIzHY6srfEAw= -github.com/go-toolsmith/astequal v1.0.3/go.mod h1:9Ai4UglvtR+4up+bAD4+hCj7iTo4m/OXVTSLnCyTAx4= -github.com/go-toolsmith/astequal v1.1.0/go.mod h1:sedf7VIdCL22LD8qIvv7Nn9MuWJruQA/ysswh64lffQ= -github.com/go-toolsmith/astequal v1.2.0 h1:3Fs3CYZ1k9Vo4FzFhwwewC3CHISHDnVUPC4x0bI2+Cw= -github.com/go-toolsmith/astequal v1.2.0/go.mod h1:c8NZ3+kSFtFY/8lPso4v8LuJjdJiUFVnSuU3s0qrrDY= -github.com/go-toolsmith/astfmt v1.1.0 h1:iJVPDPp6/7AaeLJEruMsBUlOYCmvg0MoCfJprsOmcco= -github.com/go-toolsmith/astfmt v1.1.0/go.mod h1:OrcLlRwu0CuiIBp/8b5PYF9ktGVZUjlNMV634mhwuQ4= -github.com/go-toolsmith/astp v1.1.0 h1:dXPuCl6u2llURjdPLLDxJeZInAeZ0/eZwFJmqZMnpQA= -github.com/go-toolsmith/astp v1.1.0/go.mod h1:0T1xFGz9hicKs8Z5MfAqSUitoUYS30pDMsRVIDHs8CA= -github.com/go-toolsmith/strparse v1.0.0/go.mod h1:YI2nUKP9YGZnL/L1/DLFBfixrcjslWct4wyljWhSRy8= -github.com/go-toolsmith/strparse v1.1.0 h1:GAioeZUK9TGxnLS+qfdqNbA4z0SSm5zVNtCQiyP2Bvw= -github.com/go-toolsmith/strparse v1.1.0/go.mod h1:7ksGy58fsaQkGQlY8WVoBFNyEPMGuJin1rfoPS4lBSQ= -github.com/go-toolsmith/typep v1.1.0 h1:fIRYDyF+JywLfqzyhdiHzRop/GQDxxNhLGQ6gFUNHus= -github.com/go-toolsmith/typep v1.1.0/go.mod h1:fVIw+7zjdsMxDA3ITWnH1yOiw1rnTQKCsF/sk2H/qig= -github.com/go-viper/mapstructure/v2 v2.0.0-alpha.1 h1:TQcrn6Wq+sKGkpyPvppOz99zsMBaUOKXq6HSv655U1c= -github.com/go-viper/mapstructure/v2 v2.0.0-alpha.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= -github.com/go-viper/mapstructure/v2 v2.0.0 h1:dhn8MZ1gZ0mzeodTG3jt5Vj/o87xZKuNAprG2mQfMfc= -github.com/go-viper/mapstructure/v2 v2.0.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= -github.com/go-viper/mapstructure/v2 v2.1.0 h1:gHnMa2Y/pIxElCH2GlZZ1lZSsn6XMtufpGyP1XxdC/w= -github.com/go-viper/mapstructure/v2 v2.1.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= -github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss= -github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= -github.com/go-xmlfmt/xmlfmt v1.1.2 h1:Nea7b4icn8s57fTx1M5AI4qQT5HEM3rVUO8MuE6g80U= -github.com/go-xmlfmt/xmlfmt v1.1.2/go.mod h1:aUCEOzzezBEjDBbFBoSiya/gduyIiWYRP6CnSFIV8AM= -github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= -github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= -github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= -github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= -github.com/gofrs/flock v0.12.1 h1:MTLVXXHf8ekldpJk3AKicLij9MdwOWkZ+a/jHHZby9E= -github.com/gofrs/flock v0.12.1/go.mod h1:9zxTsyu5xtJ9DK+1tFZyibEV7y3uwDxPPfbxeeHCoD0= -github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= -github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= -github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= -github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= -github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= -github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= -github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= -github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= -github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a h1:w8hkcTqaFpzKqonE9uMCefW1WDie15eSP/4MssdenaM= -github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a/go.mod h1:ryS0uhF+x9jgbj/N71xsEqODy9BN81/GonCZiOzirOk= -github.com/golangci/go-printf-func-name v0.1.0 h1:dVokQP+NMTO7jwO4bwsRwLWeudOVUPPyAKJuzv8pEJU= -github.com/golangci/go-printf-func-name v0.1.0/go.mod h1:wqhWFH5mUdJQhweRnldEywnR5021wTdZSNgwYceV14s= -github.com/golangci/gofmt v0.0.0-20231018234816-f50ced29576e h1:ULcKCDV1LOZPFxGZaA6TlQbiM3J2GCPnkx/bGF6sX/g= -github.com/golangci/gofmt v0.0.0-20231018234816-f50ced29576e/go.mod h1:Pm5KhLPA8gSnQwrQ6ukebRcapGb/BG9iUkdaiCcGHJM= -github.com/golangci/gofmt v0.0.0-20240816233607-d8596aa466a9 h1:/1322Qns6BtQxUZDTAT4SdcoxknUki7IAoK4SAXr8ME= -github.com/golangci/gofmt v0.0.0-20240816233607-d8596aa466a9/go.mod h1:Oesb/0uFAyWoaw1U1qS5zyjCg5NP9C9iwjnI4tIsXEE= -github.com/golangci/golangci-lint v1.57.1 h1:cqhpzkzjDwdN12rfMf1SUyyKyp88a1SltNqEYGS0nJw= -github.com/golangci/golangci-lint v1.57.1/go.mod h1:zLcHhz3NHc88T5zV2j75lyc0zH3LdOPOybblYa4p0oI= -github.com/golangci/golangci-lint v1.59.0 h1:st69YDnAH/v2QXDcgUaZ0seQajHScPALBVkyitYLXEk= -github.com/golangci/golangci-lint v1.59.0/go.mod h1:QNA32UWdUdHXnu+Ap5/ZU4WVwyp2tL94UxEXrSErjg0= -github.com/golangci/golangci-lint v1.59.1 h1:CRRLu1JbhK5avLABFJ/OHVSQ0Ie5c4ulsOId1h3TTks= -github.com/golangci/golangci-lint v1.59.1/go.mod h1:jX5Oif4C7P0j9++YB2MMJmoNrb01NJ8ITqKWNLewThg= -github.com/golangci/golangci-lint v1.60.1 h1:DRKNqNTQRLBJZ1il5u4fvgLQCjQc7QFs0DbhksJtVJE= -github.com/golangci/golangci-lint v1.60.1/go.mod h1:jDIPN1rYaIA+ijp9OZcUmUCoQOtZ76pOlFbi15FlLJY= -github.com/golangci/golangci-lint v1.61.0 h1:VvbOLaRVWmyxCnUIMTbf1kDsaJbTzH20FAMXTAlQGu8= -github.com/golangci/golangci-lint v1.61.0/go.mod h1:e4lztIrJJgLPhWvFPDkhiMwEFRrWlmFbrZea3FsJyN8= -github.com/golangci/golangci-lint v1.62.0 h1:/G0g+bi1BhmGJqLdNQkKBWjcim8HjOPc4tsKuHDOhcI= -github.com/golangci/golangci-lint v1.62.0/go.mod h1:jtoOhQcKTz8B6dGNFyfQV3WZkQk+YvBDewDtNpiAJts= -github.com/golangci/misspell v0.4.1 h1:+y73iSicVy2PqyX7kmUefHusENlrP9YwuHZHPLGQj/g= -github.com/golangci/misspell v0.4.1/go.mod h1:9mAN1quEo3DlpbaIKKyEvRxK1pwqR9s/Sea1bJCtlNI= -github.com/golangci/misspell v0.5.1 h1:/SjR1clj5uDjNLwYzCahHwIOPmQgoH04AyQIiWGbhCM= -github.com/golangci/misspell v0.5.1/go.mod h1:keMNyY6R9isGaSAu+4Q8NMBwMPkh15Gtc8UCVoDtAWo= -github.com/golangci/misspell v0.6.0 h1:JCle2HUTNWirNlDIAUO44hUsKhOFqGPoC4LZxlaSXDs= -github.com/golangci/misspell v0.6.0/go.mod h1:keMNyY6R9isGaSAu+4Q8NMBwMPkh15Gtc8UCVoDtAWo= -github.com/golangci/modinfo v0.3.4 h1:oU5huX3fbxqQXdfspamej74DFX0kyGLkw1ppvXoJ8GA= -github.com/golangci/modinfo v0.3.4/go.mod h1:wytF1M5xl9u0ij8YSvhkEVPP3M5Mc7XLl1pxH3B2aUM= -github.com/golangci/plugin-module-register v0.1.1 h1:TCmesur25LnyJkpsVrupv1Cdzo+2f7zX0H6Jkw1Ol6c= -github.com/golangci/plugin-module-register v0.1.1/go.mod h1:TTpqoB6KkwOJMV8u7+NyXMrkwwESJLOkfl9TxR1DGFc= -github.com/golangci/revgrep v0.5.2 h1:EndcWoRhcnfj2NHQ+28hyuXpLMF+dQmCN+YaeeIl4FU= -github.com/golangci/revgrep v0.5.2/go.mod h1:bjAMA+Sh/QUfTDcHzxfyHxr4xKvllVr/0sCv2e7jJHA= -github.com/golangci/revgrep v0.5.3 h1:3tL7c1XBMtWHHqVpS5ChmiAAoe4PF/d5+ULzV9sLAzs= -github.com/golangci/revgrep v0.5.3/go.mod h1:U4R/s9dlXZsg8uJmaR1GrloUr14D7qDl8gi2iPXJH8k= -github.com/golangci/unconvert v0.0.0-20240309020433-c5143eacb3ed h1:IURFTjxeTfNFP0hTEi1YKjB/ub8zkpaOqFFMApi2EAs= -github.com/golangci/unconvert v0.0.0-20240309020433-c5143eacb3ed/go.mod h1:XLXN8bNw4CGRPaqgl3bv/lhz7bsGPh4/xSaMTbo2vkQ= -github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= -github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= -github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/gordonklaus/ineffassign v0.1.0 h1:y2Gd/9I7MdY1oEIt+n+rowjBNDcLQq3RsH5hwJd0f9s= -github.com/gordonklaus/ineffassign v0.1.0/go.mod h1:Qcp2HIAYhR7mNUVSIxZww3Guk4it82ghYcEXIAk+QT0= -github.com/gostaticanalysis/analysisutil v0.7.1 h1:ZMCjoue3DtDWQ5WyU16YbjbQEQ3VuzwxALrpYd+HeKk= -github.com/gostaticanalysis/analysisutil v0.7.1/go.mod h1:v21E3hY37WKMGSnbsw2S/ojApNWb6C1//mXO48CXbVc= -github.com/gostaticanalysis/comment v1.4.1/go.mod h1:ih6ZxzTHLdadaiSnF5WY3dxUoXfXAlTaRzuaNDlSado= -github.com/gostaticanalysis/comment v1.4.2 h1:hlnx5+S2fY9Zo9ePo4AhgYsYHbM2+eAv8m/s1JiCd6Q= -github.com/gostaticanalysis/comment v1.4.2/go.mod h1:KLUTGDv6HOCotCH8h2erHKmpci2ZoR8VPu34YA2uzdM= -github.com/gostaticanalysis/forcetypeassert v0.1.0 h1:6eUflI3DiGusXGK6X7cCcIgVCpZ2CiZ1Q7jl6ZxNV70= -github.com/gostaticanalysis/forcetypeassert v0.1.0/go.mod h1:qZEedyP/sY1lTGV1uJ3VhWZ2mqag3IkWsDHVbplHXak= -github.com/gostaticanalysis/nilerr v0.1.1 h1:ThE+hJP0fEp4zWLkWHWcRyI2Od0p7DlgYG3Uqrmrcpk= -github.com/gostaticanalysis/nilerr v0.1.1/go.mod h1:wZYb6YI5YAxxq0i1+VJbY0s2YONW0HU0GPE3+5PWN4A= -github.com/gostaticanalysis/testutil v0.3.1-0.20210208050101-bfb5c8eec0e4/go.mod h1:D+FIZ+7OahH3ePw/izIEeH5I06eKs1IKI4Xr64/Am3M= -github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= -github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY= -github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= -github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= -github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= -github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= -github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/jgautheron/goconst v1.7.0 h1:cEqH+YBKLsECnRSd4F4TK5ri8t/aXtt/qoL0Ft252B0= -github.com/jgautheron/goconst v1.7.0/go.mod h1:aAosetZ5zaeC/2EfMeRswtxUFBpe2Hr7HzkgX4fanO4= -github.com/jgautheron/goconst v1.7.1 h1:VpdAG7Ca7yvvJk5n8dMwQhfEZJh95kl/Hl9S1OI5Jkk= -github.com/jgautheron/goconst v1.7.1/go.mod h1:aAosetZ5zaeC/2EfMeRswtxUFBpe2Hr7HzkgX4fanO4= -github.com/jingyugao/rowserrcheck v1.1.1 h1:zibz55j/MJtLsjP1OF4bSdgXxwL1b+Vn7Tjzq7gFzUs= -github.com/jingyugao/rowserrcheck v1.1.1/go.mod h1:4yvlZSDb3IyDTUZJUmpZfm2Hwok+Dtp+nu2qOq+er9c= -github.com/jirfag/go-printf-func-name v0.0.0-20200119135958-7558a9eaa5af h1:KA9BjwUk7KlCh6S9EAGWBt1oExIUv9WyNCiRz5amv48= -github.com/jirfag/go-printf-func-name v0.0.0-20200119135958-7558a9eaa5af/go.mod h1:HEWGJkRDzjJY2sqdDwxccsGicWEf9BQOZsq2tV+xzM0= -github.com/jjti/go-spancheck v0.5.3 h1:vfq4s2IB8T3HvbpiwDTYgVPj1Ze/ZSXrTtaZRTc7CuM= -github.com/jjti/go-spancheck v0.5.3/go.mod h1:eQdOX1k3T+nAKvZDyLC3Eby0La4dZ+I19iOl5NzSPFE= -github.com/jjti/go-spancheck v0.6.1 h1:ZK/wE5Kyi1VX3PJpUO2oEgeoI4FWOUm7Shb2Gbv5obI= -github.com/jjti/go-spancheck v0.6.1/go.mod h1:vF1QkOO159prdo6mHRxak2CpzDpHAfKiPUDP/NeRnX8= -github.com/jjti/go-spancheck v0.6.2 h1:iYtoxqPMzHUPp7St+5yA8+cONdyXD3ug6KK15n7Pklk= -github.com/jjti/go-spancheck v0.6.2/go.mod h1:+X7lvIrR5ZdUTkxFYqzJ0abr8Sb5LOo80uOhWNqIrYA= -github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= -github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= -github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= -github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= -github.com/julz/importas v0.1.0 h1:F78HnrsjY3cR7j0etXy5+TU1Zuy7Xt08X/1aJnH5xXY= -github.com/julz/importas v0.1.0/go.mod h1:oSFU2R4XK/P7kNBrnL/FEQlDGN1/6WoxXEjSSXO0DV0= -github.com/karamaru-alpha/copyloopvar v1.0.8 h1:gieLARwuByhEMxRwM3GRS/juJqFbLraftXIKDDNJ50Q= -github.com/karamaru-alpha/copyloopvar v1.0.8/go.mod h1:u7CIfztblY0jZLOQZgH3oYsJzpC2A7S6u/lfgSXHy0k= -github.com/karamaru-alpha/copyloopvar v1.1.0 h1:x7gNyKcC2vRBO1H2Mks5u1VxQtYvFiym7fCjIP8RPos= -github.com/karamaru-alpha/copyloopvar v1.1.0/go.mod h1:u7CIfztblY0jZLOQZgH3oYsJzpC2A7S6u/lfgSXHy0k= -github.com/kisielk/errcheck v1.7.0 h1:+SbscKmWJ5mOK/bO1zS60F5I9WwZDWOfRsC4RwfwRV0= -github.com/kisielk/errcheck v1.7.0/go.mod h1:1kLL+jV4e+CFfueBmI1dSK2ADDyQnlrnrY/FqKluHJQ= -github.com/kisielk/errcheck v1.8.0 h1:ZX/URYa7ilESY19ik/vBmCn6zdGQLxACwjAcWbHlYlg= -github.com/kisielk/errcheck v1.8.0/go.mod h1:1kLL+jV4e+CFfueBmI1dSK2ADDyQnlrnrY/FqKluHJQ= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/kkHAIKE/contextcheck v1.1.4 h1:B6zAaLhOEEcjvUgIYEqystmnFk1Oemn8bvJhbt0GMb8= -github.com/kkHAIKE/contextcheck v1.1.4/go.mod h1:1+i/gWqokIa+dm31mqGLZhZJ7Uh44DJGZVmr6QRBNJg= -github.com/kkHAIKE/contextcheck v1.1.5 h1:CdnJh63tcDe53vG+RebdpdXJTc9atMgGqdx8LXxiilg= -github.com/kkHAIKE/contextcheck v1.1.5/go.mod h1:O930cpht4xb1YQpK+1+AgoM3mFsvxr7uyFptcnWTYUA= -github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -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/kulti/thelper v0.6.3 h1:ElhKf+AlItIu+xGnI990no4cE2+XaSu1ULymV2Yulxs= -github.com/kulti/thelper v0.6.3/go.mod h1:DsqKShOvP40epevkFrvIwkCMNYxMeTNjdWL4dqWHZ6I= -github.com/kunwardeep/paralleltest v1.0.10 h1:wrodoaKYzS2mdNVnc4/w31YaXFtsc21PCTdvWJ/lDDs= -github.com/kunwardeep/paralleltest v1.0.10/go.mod h1:2C7s65hONVqY7Q5Efj5aLzRCNLjw2h4eMc9EcypGjcY= -github.com/kyoh86/exportloopref v0.1.11 h1:1Z0bcmTypkL3Q4k+IDHMWTcnCliEZcaPiIe0/ymEyhQ= -github.com/kyoh86/exportloopref v0.1.11/go.mod h1:qkV4UF1zGl6EkF1ox8L5t9SwyeBAZ3qLMd6up458uqA= -github.com/lasiar/canonicalheader v1.1.1 h1:wC+dY9ZfiqiPwAexUApFush/csSPXeIi4QqyxXmng8I= -github.com/lasiar/canonicalheader v1.1.1/go.mod h1:cXkb3Dlk6XXy+8MVQnF23CYKWlyA7kfQhSw2CcZtZb0= -github.com/lasiar/canonicalheader v1.1.2 h1:vZ5uqwvDbyJCnMhmFYimgMZnJMjwljN5VGY0VKbMXb4= -github.com/lasiar/canonicalheader v1.1.2/go.mod h1:qJCeLFS0G/QlLQ506T+Fk/fWMa2VmBUiEI2cuMK4djI= -github.com/ldez/gomoddirectives v0.2.3 h1:y7MBaisZVDYmKvt9/l1mjNCiSA1BVn34U0ObUcJwlhA= -github.com/ldez/gomoddirectives v0.2.3/go.mod h1:cpgBogWITnCfRq2qGoDkKMEVSaarhdBr6g8G04uz6d0= -github.com/ldez/gomoddirectives v0.2.4 h1:j3YjBIjEBbqZ0NKtBNzr8rtMHTOrLPeiwTkfUJZ3alg= -github.com/ldez/gomoddirectives v0.2.4/go.mod h1:oWu9i62VcQDYp9EQ0ONTfqLNh+mDLWWDO+SO0qSQw5g= -github.com/ldez/tagliatelle v0.5.0 h1:epgfuYt9v0CG3fms0pEgIMNPuFf/LpPIfjk4kyqSioo= -github.com/ldez/tagliatelle v0.5.0/go.mod h1:rj1HmWiL1MiKQuOONhd09iySTEkUuE/8+5jtPYz9xa4= -github.com/leonklingele/grouper v1.1.1 h1:suWXRU57D4/Enn6pXR0QVqqWWrnJ9Osrz+5rjt8ivzU= -github.com/leonklingele/grouper v1.1.1/go.mod h1:uk3I3uDfi9B6PeUjsCKi6ndcf63Uy7snXgR4yDYQVDY= -github.com/leonklingele/grouper v1.1.2 h1:o1ARBDLOmmasUaNDesWqWCIFH3u7hoFlM84YrjT3mIY= -github.com/leonklingele/grouper v1.1.2/go.mod h1:6D0M/HVkhs2yRKRFZUoGjeDy7EZTfFBE9gl4kjmIGkA= -github.com/lufeee/execinquery v1.2.1 h1:hf0Ems4SHcUGBxpGN7Jz78z1ppVkP/837ZlETPCEtOM= -github.com/lufeee/execinquery v1.2.1/go.mod h1:EC7DrEKView09ocscGHC+apXMIaorh4xqSxS/dy8SbM= -github.com/macabu/inamedparam v0.1.3 h1:2tk/phHkMlEL/1GNe/Yf6kkR/hkcUdAEY3L0hjYV1Mk= -github.com/macabu/inamedparam v0.1.3/go.mod h1:93FLICAIk/quk7eaPPQvbzihUdn/QkGDwIZEoLtpH6I= -github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= -github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= -github.com/maratori/testableexamples v1.0.0 h1:dU5alXRrD8WKSjOUnmJZuzdxWOEQ57+7s93SLMxb2vI= -github.com/maratori/testableexamples v1.0.0/go.mod h1:4rhjL1n20TUTT4vdh3RDqSizKLyXp7K2u6HgraZCGzE= -github.com/maratori/testpackage v1.1.1 h1:S58XVV5AD7HADMmD0fNnziNHqKvSdDuEKdPD1rNTU04= -github.com/maratori/testpackage v1.1.1/go.mod h1:s4gRK/ym6AMrqpOa/kEbQTV4Q4jb7WeLZzVhVVVOQMc= -github.com/matoous/godox v0.0.0-20230222163458-006bad1f9d26 h1:gWg6ZQ4JhDfJPqlo2srm/LN17lpybq15AryXIRcWYLE= -github.com/matoous/godox v0.0.0-20230222163458-006bad1f9d26/go.mod h1:1BELzlh859Sh1c6+90blK8lbYy0kwQf1bYlBhBysy1s= -github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= -github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= -github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= -github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= -github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= -github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= -github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/mgechev/revive v1.3.7 h1:502QY0vQGe9KtYJ9FpxMz9rL+Fc/P13CI5POL4uHCcE= -github.com/mgechev/revive v1.3.7/go.mod h1:RJ16jUbF0OWC3co/+XTxmFNgEpUPwnnA0BRllX2aDNA= -github.com/mgechev/revive v1.3.9 h1:18Y3R4a2USSBF+QZKFQwVkBROUda7uoBlkEuBD+YD1A= -github.com/mgechev/revive v1.3.9/go.mod h1:+uxEIr5UH0TjXWHTno3xh4u7eg6jDpXKzQccA9UGhHU= -github.com/mgechev/revive v1.5.0 h1:oaSmjA7rP8+HyoRuCgC531VHwnLH1AlJdjj+1AnQceQ= -github.com/mgechev/revive v1.5.0/go.mod h1:L6T3H8EoerRO86c7WuGpvohIUmiploGiyoYbtIWFmV8= -github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= -github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= -github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/moricho/tparallel v0.3.1 h1:fQKD4U1wRMAYNngDonW5XupoB/ZGJHdpzrWqgyg9krA= -github.com/moricho/tparallel v0.3.1/go.mod h1:leENX2cUv7Sv2qDgdi0D0fCftN8fRC67Bcn8pqzeYNI= -github.com/moricho/tparallel v0.3.2 h1:odr8aZVFA3NZrNybggMkYO3rgPRcqjeQUlBBFVxKHTI= -github.com/moricho/tparallel v0.3.2/go.mod h1:OQ+K3b4Ln3l2TZveGCywybl68glfLEwFGqvnjok8b+U= -github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/nakabonne/nestif v0.3.1 h1:wm28nZjhQY5HyYPx+weN3Q65k6ilSBxDb8v5S81B81U= -github.com/nakabonne/nestif v0.3.1/go.mod h1:9EtoZochLn5iUprVDmDjqGKPofoUEBL8U4Ngq6aY7OE= -github.com/nishanths/exhaustive v0.12.0 h1:vIY9sALmw6T/yxiASewa4TQcFsVYZQQRUQJhKRf3Swg= -github.com/nishanths/exhaustive v0.12.0/go.mod h1:mEZ95wPIZW+x8kC4TgC+9YCUgiST7ecevsVDTgc2obs= -github.com/nishanths/predeclared v0.2.2 h1:V2EPdZPliZymNAn79T8RkNApBjMmVKh5XRpLm/w98Vk= -github.com/nishanths/predeclared v0.2.2/go.mod h1:RROzoN6TnGQupbC+lqggsOlcgysk3LMK/HI84Mp280c= -github.com/nunnatsa/ginkgolinter v0.16.1 h1:uDIPSxgVHZ7PgbJElRDGzymkXH+JaF7mjew+Thjnt6Q= -github.com/nunnatsa/ginkgolinter v0.16.1/go.mod h1:4tWRinDN1FeJgU+iJANW/kz7xKN5nYRAOfJDQUS9dOQ= -github.com/nunnatsa/ginkgolinter v0.16.2 h1:8iLqHIZvN4fTLDC0Ke9tbSZVcyVHoBs0HIbnVSxfHJk= -github.com/nunnatsa/ginkgolinter v0.16.2/go.mod h1:4tWRinDN1FeJgU+iJANW/kz7xKN5nYRAOfJDQUS9dOQ= -github.com/nunnatsa/ginkgolinter v0.18.0 h1:ZXO1wKhPg3A6LpbN5dMuqwhfOjN5c3ous8YdKOuqk9k= -github.com/nunnatsa/ginkgolinter v0.18.0/go.mod h1:vPrWafSULmjMGCMsfGA908if95VnHQNAahvSBOjTuWs= -github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= -github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= -github.com/otiai10/copy v1.2.0/go.mod h1:rrF5dJ5F0t/EWSYODDu4j9/vEeYHMkc8jt0zJChqQWw= -github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE= -github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs= -github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo= -github.com/otiai10/mint v1.3.1/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc= -github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= -github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= -github.com/pelletier/go-toml/v2 v2.2.0 h1:QLgLl2yMN7N+ruc31VynXs1vhMZa7CeHHejIeBAsoHo= -github.com/pelletier/go-toml/v2 v2.2.0/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= -github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= -github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= -github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= -github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= -github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/polyfloyd/go-errorlint v1.4.8 h1:jiEjKDH33ouFktyez7sckv6pHWif9B7SuS8cutDXFHw= -github.com/polyfloyd/go-errorlint v1.4.8/go.mod h1:NNCxFcFjZcw3xNjVdCchERkEM6Oz7wta2XJVxRftwO4= -github.com/polyfloyd/go-errorlint v1.5.1 h1:5gHxDjLyyWij7fhfrjYNNlHsUNQeyx0LFQKUelO3RBo= -github.com/polyfloyd/go-errorlint v1.5.1/go.mod h1:sH1QC1pxxi0fFecsVIzBmxtrgd9IF/SkJpA6wqyKAJs= -github.com/polyfloyd/go-errorlint v1.5.2 h1:SJhVik3Umsjh7mte1vE0fVZ5T1gznasQG3PV7U5xFdA= -github.com/polyfloyd/go-errorlint v1.5.2/go.mod h1:sH1QC1pxxi0fFecsVIzBmxtrgd9IF/SkJpA6wqyKAJs= -github.com/polyfloyd/go-errorlint v1.6.0 h1:tftWV9DE7txiFzPpztTAwyoRLKNj9gpVm2cg8/OwcYY= -github.com/polyfloyd/go-errorlint v1.6.0/go.mod h1:HR7u8wuP1kb1NeN1zqTd1ZMlqUKPPHF+Id4vIPvDqVw= -github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= -github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= -github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= -github.com/prometheus/client_golang v1.12.1 h1:ZiaPsmm9uiBeaSMRznKsCDNtPCS0T3JVDGF+06gjBzk= -github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= -github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= -github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= -github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= -github.com/prometheus/common v0.32.1 h1:hWIdL3N2HoUx3B8j3YN9mWor0qhY/NlEKZEaXxuIRh4= -github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= -github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= -github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU= -github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/quasilyte/go-ruleguard v0.4.2 h1:htXcXDK6/rO12kiTHKfHuqR4kr3Y4M0J0rOL6CH/BYs= -github.com/quasilyte/go-ruleguard v0.4.2/go.mod h1:GJLgqsLeo4qgavUoL8JeGFNS7qcisx3awV/w9eWTmNI= -github.com/quasilyte/go-ruleguard v0.4.3-0.20240823090925-0fe6f58b47b1 h1:+Wl/0aFp0hpuHM3H//KMft64WQ1yX9LdJY64Qm/gFCo= -github.com/quasilyte/go-ruleguard v0.4.3-0.20240823090925-0fe6f58b47b1/go.mod h1:GJLgqsLeo4qgavUoL8JeGFNS7qcisx3awV/w9eWTmNI= -github.com/quasilyte/go-ruleguard/dsl v0.3.22 h1:wd8zkOhSNr+I+8Qeciml08ivDt1pSXe60+5DqOpCjPE= -github.com/quasilyte/go-ruleguard/dsl v0.3.22/go.mod h1:KeCP03KrjuSO0H1kTuZQCWlQPulDV6YMIXmpQss17rU= -github.com/quasilyte/gogrep v0.5.0 h1:eTKODPXbI8ffJMN+W2aE0+oL0z/nh8/5eNdiO34SOAo= -github.com/quasilyte/gogrep v0.5.0/go.mod h1:Cm9lpz9NZjEoL1tgZ2OgeUKPIxL1meE7eo60Z6Sk+Ng= -github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 h1:TCg2WBOl980XxGFEZSS6KlBGIV0diGdySzxATTWoqaU= -github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727/go.mod h1:rlzQ04UMyJXu/aOvhd8qT+hvDrFpiwqp8MRXDY9szc0= -github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 h1:M8mH9eK4OUR4lu7Gd+PU1fV2/qnDNfzT635KRSObncs= -github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567/go.mod h1:DWNGW8A4Y+GyBgPuaQJuWiy0XYftx4Xm/y5Jqk9I6VQ= -github.com/raeperd/recvcheck v0.1.2 h1:SjdquRsRXJc26eSonWIo8b7IMtKD3OAT2Lb5G3ZX1+4= -github.com/raeperd/recvcheck v0.1.2/go.mod h1:n04eYkwIR0JbgD73wT8wL4JjPC3wm0nFtzBnWNocnYU= -github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= -github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= -github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= -github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= -github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/ryancurrah/gomodguard v1.3.1 h1:fH+fUg+ngsQO0ruZXXHnA/2aNllWA1whly4a6UvyzGE= -github.com/ryancurrah/gomodguard v1.3.1/go.mod h1:DGFHzEhi6iJ0oIDfMuo3TgrS+L9gZvrEfmjjuelnRU0= -github.com/ryancurrah/gomodguard v1.3.2 h1:CuG27ulzEB1Gu5Dk5gP8PFxSOZ3ptSdP5iI/3IXxM18= -github.com/ryancurrah/gomodguard v1.3.2/go.mod h1:LqdemiFomEjcxOqirbQCb3JFvSxH2JUYMerTFd3sF2o= -github.com/ryancurrah/gomodguard v1.3.3 h1:eiSQdJVNr9KTNxY2Niij8UReSwR8Xrte3exBrAZfqpg= -github.com/ryancurrah/gomodguard v1.3.3/go.mod h1:rsKQjj4l3LXe8N344Ow7agAy5p9yjsWOtRzUMYmA0QY= -github.com/ryancurrah/gomodguard v1.3.5 h1:cShyguSwUEeC0jS7ylOiG/idnd1TpJ1LfHGpV3oJmPU= -github.com/ryancurrah/gomodguard v1.3.5/go.mod h1:MXlEPQRxgfPQa62O8wzK3Ozbkv9Rkqr+wKjSxTdsNJE= -github.com/ryanrolds/sqlclosecheck v0.5.1 h1:dibWW826u0P8jNLsLN+En7+RqWWTYrjCB9fJfSfdyCU= -github.com/ryanrolds/sqlclosecheck v0.5.1/go.mod h1:2g3dUjoS6AL4huFdv6wn55WpLIDjY7ZgUR4J8HOO/XQ= -github.com/sanposhiho/wastedassign/v2 v2.0.7 h1:J+6nrY4VW+gC9xFzUc+XjPD3g3wF3je/NsJFwFK7Uxc= -github.com/sanposhiho/wastedassign/v2 v2.0.7/go.mod h1:KyZ0MWTwxxBmfwn33zh3k1dmsbF2ud9pAAGfoLfjhtI= -github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 h1:lZUw3E0/J3roVtGQ+SCrUrg3ON6NgVqpn3+iol9aGu4= -github.com/santhosh-tekuri/jsonschema/v5 v5.3.1/go.mod h1:uToXkOrWAZ6/Oc07xWQrPOhJotwFIyu2bBVN41fcDUY= -github.com/sashamelentyev/interfacebloat v1.1.0 h1:xdRdJp0irL086OyW1H/RTZTr1h/tMEOsumirXcOJqAw= -github.com/sashamelentyev/interfacebloat v1.1.0/go.mod h1:+Y9yU5YdTkrNvoX0xHc84dxiN1iBi9+G8zZIhPVoNjQ= -github.com/sashamelentyev/usestdlibvars v1.25.0 h1:IK8SI2QyFzy/2OD2PYnhy84dpfNo9qADrRt6LH8vSzU= -github.com/sashamelentyev/usestdlibvars v1.25.0/go.mod h1:9nl0jgOfHKWNFS43Ojw0i7aRoS4j6EBye3YBhmAIRF8= -github.com/sashamelentyev/usestdlibvars v1.26.0 h1:LONR2hNVKxRmzIrZR0PhSF3mhCAzvnr+DcUiHgREfXE= -github.com/sashamelentyev/usestdlibvars v1.26.0/go.mod h1:9nl0jgOfHKWNFS43Ojw0i7aRoS4j6EBye3YBhmAIRF8= -github.com/sashamelentyev/usestdlibvars v1.27.0 h1:t/3jZpSXtRPRf2xr0m63i32ZrusyurIGT9E5wAvXQnI= -github.com/sashamelentyev/usestdlibvars v1.27.0/go.mod h1:9nl0jgOfHKWNFS43Ojw0i7aRoS4j6EBye3YBhmAIRF8= -github.com/securego/gosec/v2 v2.19.0 h1:gl5xMkOI0/E6Hxx0XCY2XujA3V7SNSefA8sC+3f1gnk= -github.com/securego/gosec/v2 v2.19.0/go.mod h1:hOkDcHz9J/XIgIlPDXalxjeVYsHxoWUc5zJSHxcB8YM= -github.com/securego/gosec/v2 v2.20.1-0.20240525090044-5f0084eb01a9 h1:rnO6Zp1YMQwv8AyxzuwsVohljJgp4L0ZqiCgtACsPsc= -github.com/securego/gosec/v2 v2.20.1-0.20240525090044-5f0084eb01a9/go.mod h1:dg7lPlu/xK/Ut9SedURCoZbVCR4yC7fM65DtH9/CDHs= -github.com/securego/gosec/v2 v2.21.2 h1:deZp5zmYf3TWwU7A7cR2+SolbTpZ3HQiwFqnzQyEl3M= -github.com/securego/gosec/v2 v2.21.2/go.mod h1:au33kg78rNseF5PwPnTWhuYBFf534bvJRvOrgZ/bFzU= -github.com/securego/gosec/v2 v2.21.4 h1:Le8MSj0PDmOnHJgUATjD96PaXRvCpKC+DGJvwyy0Mlk= -github.com/securego/gosec/v2 v2.21.4/go.mod h1:Jtb/MwRQfRxCXyCm1rfM1BEiiiTfUOdyzzAhlr6lUTA= -github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c h1:W65qqJCIOVP4jpqPQ0YvHYKwcMEMVWIzWC5iNQQfBTU= -github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c/go.mod h1:/PevMnwAxekIXwN8qQyfc5gl2NlkB3CQlkizAbOkeBs= -github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= -github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ= -github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= -github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= -github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/sivchari/containedctx v1.0.3 h1:x+etemjbsh2fB5ewm5FeLNi5bUjK0V8n0RB+Wwfd0XE= -github.com/sivchari/containedctx v1.0.3/go.mod h1:c1RDvCbnJLtH4lLcYD/GqwiBSSf4F5Qk0xld2rBqzJ4= -github.com/sivchari/tenv v1.7.1 h1:PSpuD4bu6fSmtWMxSGWcvqUUgIn7k3yOJhOIzVWn8Ak= -github.com/sivchari/tenv v1.7.1/go.mod h1:64yStXKSOxDfX47NlhVwND4dHwfZDdbp2Lyl018Icvg= -github.com/sivchari/tenv v1.10.0 h1:g/hzMA+dBCKqGXgW8AV/1xIWhAvDrx0zFKNR48NFMg0= -github.com/sivchari/tenv v1.10.0/go.mod h1:tdY24masnVoZFxYrHv/nD6Tc8FbkEtAQEEziXpyMgqY= -github.com/sivchari/tenv v1.12.1 h1:+E0QzjktdnExv/wwsnnyk4oqZBUfuh89YMQT1cyuvSY= -github.com/sivchari/tenv v1.12.1/go.mod h1:1LjSOUCc25snIr5n3DtGGrENhX3LuWefcplwVGC24mw= -github.com/sonatard/noctx v0.0.2 h1:L7Dz4De2zDQhW8S0t+KUjY0MAQJd6SgVwhzNIc4ok00= -github.com/sonatard/noctx v0.0.2/go.mod h1:kzFz+CzWSjQ2OzIm46uJZoXuBpa2+0y3T36U18dWqIo= -github.com/sonatard/noctx v0.1.0 h1:JjqOc2WN16ISWAjAk8M5ej0RfExEXtkEyExl2hLW+OM= -github.com/sonatard/noctx v0.1.0/go.mod h1:0RvBxqY8D4j9cTTTWE8ylt2vqj2EPI8fHmrxHdsaZ2c= -github.com/sourcegraph/go-diff v0.7.0 h1:9uLlrd5T46OXs5qpp8L/MTltk0zikUGi0sNNyCpA8G0= -github.com/sourcegraph/go-diff v0.7.0/go.mod h1:iBszgVvyxdc8SFZ7gm69go2KDdt3ag071iBaWPF6cjs= -github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= -github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= -github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= -github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= -github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= -github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= -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/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= -github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= -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/spf13/viper v1.12.0 h1:CZ7eSOd3kZoaYDLbXnmzgQI5RlciuXBMA+18HwHRfZQ= -github.com/spf13/viper v1.12.0/go.mod h1:b6COn30jlNxbm/V2IqWiNWkJ+vZNiMNksliPCiuKtSI= -github.com/ssgreg/nlreturn/v2 v2.2.1 h1:X4XDI7jstt3ySqGU86YGAURbxw3oTDPK9sPEi6YEwQ0= -github.com/ssgreg/nlreturn/v2 v2.2.1/go.mod h1:E/iiPB78hV7Szg2YfRgyIrk1AD6JVMTRkkxBiELzh2I= -github.com/stbenjam/no-sprintf-host-port v0.1.1 h1:tYugd/yrm1O0dV+ThCbaKZh195Dfm07ysF0U6JQXczc= -github.com/stbenjam/no-sprintf-host-port v0.1.1/go.mod h1:TLhvtIvONRzdmkFiio4O8LHsN9N74I+PhRquPsxpL0I= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= -github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs= -github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= -github.com/t-yuki/gocover-cobertura v0.0.0-20180217150009-aaee18c8195c h1:+aPplBwWcHBo6q9xrfWdMrT9o4kltkmmvpemgIjep/8= -github.com/t-yuki/gocover-cobertura v0.0.0-20180217150009-aaee18c8195c/go.mod h1:SbErYREK7xXdsRiigaQiQkI9McGRzYMvlKYaP3Nimdk= -github.com/tdakkota/asciicheck v0.2.0 h1:o8jvnUANo0qXtnslk2d3nMKTFNlOnJjRrNcj0j9qkHM= -github.com/tdakkota/asciicheck v0.2.0/go.mod h1:Qb7Y9EgjCLJGup51gDHFzbI08/gbGhL/UVhYIPWG2rg= -github.com/tenntenn/modver v1.0.1/go.mod h1:bePIyQPb7UeioSRkw3Q0XeMhYZSMx9B8ePqg6SAMGH0= -github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3/go.mod h1:ON8b8w4BN/kE1EOhwT0o+d62W65a6aPw1nouo9LMgyY= -github.com/tetafro/godot v1.4.16 h1:4ChfhveiNLk4NveAZ9Pu2AN8QZ2nkUGFuadM9lrr5D0= -github.com/tetafro/godot v1.4.16/go.mod h1:2oVxTBSftRTh4+MVfUaUXR6bn2GDXCaMcOG4Dk3rfio= -github.com/tetafro/godot v1.4.17 h1:pGzu+Ye7ZUEFx7LHU0dAKmCOXWsPjl7qA6iMGndsjPs= -github.com/tetafro/godot v1.4.17/go.mod h1:2oVxTBSftRTh4+MVfUaUXR6bn2GDXCaMcOG4Dk3rfio= -github.com/tetafro/godot v1.4.18 h1:ouX3XGiziKDypbpXqShBfnNLTSjR8r3/HVzrtJ+bHlI= -github.com/tetafro/godot v1.4.18/go.mod h1:2oVxTBSftRTh4+MVfUaUXR6bn2GDXCaMcOG4Dk3rfio= -github.com/timakin/bodyclose v0.0.0-20230421092635-574207250966 h1:quvGphlmUVU+nhpFa4gg4yJyTRJ13reZMDHrKwYw53M= -github.com/timakin/bodyclose v0.0.0-20230421092635-574207250966/go.mod h1:27bSVNWSBOHm+qRp1T9qzaIpsWEP6TbUnei/43HK+PQ= -github.com/timonwong/loggercheck v0.9.4 h1:HKKhqrjcVj8sxL7K77beXh0adEm6DLjV/QOGeMXEVi4= -github.com/timonwong/loggercheck v0.9.4/go.mod h1:caz4zlPcgvpEkXgVnAJGowHAMW2NwHaNlpS8xDbVhTg= -github.com/timonwong/loggercheck v0.10.1 h1:uVZYClxQFpw55eh+PIoqM7uAOHMrhVcDoWDery9R8Lg= -github.com/timonwong/loggercheck v0.10.1/go.mod h1:HEAWU8djynujaAVX7QI65Myb8qgfcZ1uKbdpg3ZzKl8= -github.com/tomarrell/wrapcheck/v2 v2.8.3 h1:5ov+Cbhlgi7s/a42BprYoxsr73CbdMUTzE3bRDFASUs= -github.com/tomarrell/wrapcheck/v2 v2.8.3/go.mod h1:g9vNIyhb5/9TQgumxQyOEqDHsmGYcGsVMOx/xGkqdMo= -github.com/tomarrell/wrapcheck/v2 v2.9.0 h1:801U2YCAjLhdN8zhZ/7tdjB3EnAoRlJHt/s+9hijLQ4= -github.com/tomarrell/wrapcheck/v2 v2.9.0/go.mod h1:g9vNIyhb5/9TQgumxQyOEqDHsmGYcGsVMOx/xGkqdMo= -github.com/tommy-muehle/go-mnd/v2 v2.5.1 h1:NowYhSdyE/1zwK9QCLeRb6USWdoif80Ie+v+yU8u1Zw= -github.com/tommy-muehle/go-mnd/v2 v2.5.1/go.mod h1:WsUAkMJMYww6l/ufffCD3m+P7LEvr8TnZn9lwVDlgzw= -github.com/ultraware/funlen v0.1.0 h1:BuqclbkY6pO+cvxoq7OsktIXZpgBSkYTQtmwhAK81vI= -github.com/ultraware/funlen v0.1.0/go.mod h1:XJqmOQja6DpxarLj6Jj1U7JuoS8PvL4nEqDaQhy22p4= -github.com/ultraware/whitespace v0.1.0 h1:O1HKYoh0kIeqE8sFqZf1o0qbORXUCOQFrlaQyZsczZw= -github.com/ultraware/whitespace v0.1.0/go.mod h1:/se4r3beMFNmewJ4Xmz0nMQ941GJt+qmSHGP9emHYe0= -github.com/ultraware/whitespace v0.1.1 h1:bTPOGejYFulW3PkcrqkeQwOd6NKOOXvmGD9bo/Gk8VQ= -github.com/ultraware/whitespace v0.1.1/go.mod h1:XcP1RLD81eV4BW8UhQlpaR+SDc2givTvyI8a586WjW8= -github.com/uudashr/gocognit v1.1.2 h1:l6BAEKJqQH2UpKAPKdMfZf5kE4W/2xk8pfU1OVLvniI= -github.com/uudashr/gocognit v1.1.2/go.mod h1:aAVdLURqcanke8h3vg35BC++eseDm66Z7KmchI5et4k= -github.com/uudashr/gocognit v1.1.3 h1:l+a111VcDbKfynh+airAy/DJQKaXh2m9vkoysMPSZyM= -github.com/uudashr/gocognit v1.1.3/go.mod h1:aKH8/e8xbTRBwjbCkwZ8qt4l2EpKXl31KMHgSS+lZ2U= -github.com/uudashr/iface v1.2.0 h1:ECJjh5q/1Zmnv/2yFpWV6H3oMg5+Mo+vL0aqw9Gjazo= -github.com/uudashr/iface v1.2.0/go.mod h1:Ux/7d/rAF3owK4m53cTVXL4YoVHKNqnoOeQHn2xrlp0= -github.com/xen0n/gosmopolitan v1.2.2 h1:/p2KTnMzwRexIW8GlKawsTWOxn7UHA+jCMF/V8HHtvU= -github.com/xen0n/gosmopolitan v1.2.2/go.mod h1:7XX7Mj61uLYrj0qmeN0zi7XDon9JRAEhYQqAPLVNTeg= -github.com/yagipy/maintidx v1.0.0 h1:h5NvIsCz+nRDapQ0exNv4aJ0yXSI0420omVANTv3GJM= -github.com/yagipy/maintidx v1.0.0/go.mod h1:0qNf/I/CCZXSMhsRsrEPDZ+DkekpKLXAJfsTACwgXLk= -github.com/yeya24/promlinter v0.2.0 h1:xFKDQ82orCU5jQujdaD8stOHiv8UN68BSdn2a8u8Y3o= -github.com/yeya24/promlinter v0.2.0/go.mod h1:u54lkmBOZrpEbQQ6gox2zWKKLKu2SGe+2KOiextY+IA= -github.com/yeya24/promlinter v0.3.0 h1:JVDbMp08lVCP7Y6NP3qHroGAO6z2yGKQtS5JsjqtoFs= -github.com/yeya24/promlinter v0.3.0/go.mod h1:cDfJQQYv9uYciW60QT0eeHlFodotkYZlL+YcPQN+mW4= -github.com/ykadowak/zerologlint v0.1.5 h1:Gy/fMz1dFQN9JZTPjv1hxEk+sRWm05row04Yoolgdiw= -github.com/ykadowak/zerologlint v0.1.5/go.mod h1:KaUskqF3e/v59oPmdq1U1DnKcuHokl2/K1U4pmIELKg= -github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -gitlab.com/bosi/decorder v0.4.1 h1:VdsdfxhstabyhZovHafFw+9eJ6eU0d2CkFNJcZz/NU4= -gitlab.com/bosi/decorder v0.4.1/go.mod h1:jecSqWUew6Yle1pCr2eLWTensJMmsxHsBwt+PVbkAqA= -gitlab.com/bosi/decorder v0.4.2 h1:qbQaV3zgwnBZ4zPMhGLW4KZe7A7NwxEhJx39R3shffo= -gitlab.com/bosi/decorder v0.4.2/go.mod h1:muuhHoaJkA9QLcYHq4Mj8FJUwDZ+EirSHRiaTcTf6T8= -go-simpler.org/musttag v0.9.0 h1:Dzt6/tyP9ONr5g9h9P3cnYWCxeBFRkd0uJL/w+1Mxos= -go-simpler.org/musttag v0.9.0/go.mod h1:gA9nThnalvNSKpEoyp3Ko4/vCX2xTpqKoUtNqXOnVR4= -go-simpler.org/musttag v0.12.2 h1:J7lRc2ysXOq7eM8rwaTYnNrHd5JwjppzB6mScysB2Cs= -go-simpler.org/musttag v0.12.2/go.mod h1:uN1DVIasMTQKk6XSik7yrJoEysGtR2GRqvWnI9S7TYM= -go-simpler.org/musttag v0.13.0 h1:Q/YAW0AHvaoaIbsPj3bvEI5/QFP7w696IMUpnKXQfCE= -go-simpler.org/musttag v0.13.0/go.mod h1:FTzIGeK6OkKlUDVpj0iQUXZLUO1Js9+mvykDQy9C5yM= -go-simpler.org/sloglint v0.5.0 h1:2YCcd+YMuYpuqthCgubcF5lBSjb6berc5VMOYUHKrpY= -go-simpler.org/sloglint v0.5.0/go.mod h1:EUknX5s8iXqf18KQxKnaBHUPVriiPnOrPjjJcsaTcSQ= -go-simpler.org/sloglint v0.7.0 h1:rMZRxD9MbaGoRFobIOicMxZzum7AXNFDlez6xxJs5V4= -go-simpler.org/sloglint v0.7.0/go.mod h1:g9SXiSWY0JJh4LS39/Q0GxzP/QX2cVcbTOYhDpXrJEs= -go-simpler.org/sloglint v0.7.1 h1:qlGLiqHbN5islOxjeLXoPtUdZXb669RW+BDQ+xOSNoU= -go-simpler.org/sloglint v0.7.1/go.mod h1:OlaVDRh/FKKd4X4sIMbsz8st97vomydceL146Fthh/c= -go-simpler.org/sloglint v0.7.2 h1:Wc9Em/Zeuu7JYpl+oKoYOsQSy2X560aVueCW/m6IijY= -go-simpler.org/sloglint v0.7.2/go.mod h1:US+9C80ppl7VsThQclkM7BkCHQAzuz8kHLsW3ppuluo= -go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= -go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= -go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= -go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/automaxprocs v1.5.3 h1:kWazyxZUrS3Gs4qUpbwo5kEIMGe/DAvi5Z4tl2NW4j8= -go.uber.org/automaxprocs v1.5.3/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0= -go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs= -go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8= -go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= -go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= -go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= -go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= -golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= -golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= -golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= -golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= -golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc h1:ao2WRsKSzW6KuUY9IWPwWahcHCgR0s52IfwutMfEbdM= -golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI= -golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e h1:I88y4caeGeuDQxgdoFPUq097j7kNfw6uvuiNxUBfcBk= -golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ= -golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk= -golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY= -golang.org/x/exp/typeparams v0.0.0-20220428152302-39d4317da171/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= -golang.org/x/exp/typeparams v0.0.0-20230203172020-98cc5a0785f9/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= -golang.org/x/exp/typeparams v0.0.0-20240314144324-c7f7c6466f7f h1:phY1HzDcf18Aq9A8KkmRtY9WvOFIxN8wgfvy6Zm1DV8= -golang.org/x/exp/typeparams v0.0.0-20240314144324-c7f7c6466f7f/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= -golang.org/x/exp/typeparams v0.0.0-20240909161429-701f63a606c0 h1:bVwtbF629Xlyxk6xLQq2TDYmqP0uiWaet5LwRebuY0k= -golang.org/x/exp/typeparams v0.0.0-20240909161429-701f63a606c0/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= -golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= -golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= -golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= -golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= -golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= -golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= -golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI= -golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic= -golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= -golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0= -golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= -golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= -golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= -golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= -golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= -golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= -golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= -golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= -golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= -golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ= -golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211105183446-c75c47738b0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220702020025-31831981b65f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= -golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= -golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= -golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM= -golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= -golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= -golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= -golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= -golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= -golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= -golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= -golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190321232350-e250d351ecad/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190910044552-dd2b5c81c578/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200324003944-a576cf524670/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= -golang.org/x/tools v0.0.0-20200329025819-fd4102a86c65/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= -golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= -golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200724022722-7017fd6b1305/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200820010801-b793a1359eac/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20201023174141-c8cfbd0f21e6/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= -golang.org/x/tools v0.1.1-0.20210205202024-ef80cdb6ec6d/go.mod h1:9bzcO0MWcOuT0tm1iBGzDVPshzfwoVvREIui8C+MHqU= -golang.org/x/tools v0.1.1-0.20210302220138-2ac05c832e1a/go.mod h1:9bzcO0MWcOuT0tm1iBGzDVPshzfwoVvREIui8C+MHqU= -golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= -golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= -golang.org/x/tools v0.1.11/go.mod h1:SgwaegtQh8clINPpECJMqnxLv9I09HLqnW3RMqW0CA4= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA= -golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k= -golang.org/x/tools v0.5.0/go.mod h1:N+Kgy78s5I24c24dU8OfWNEotWjutIs8SnJvn5IDq+k= -golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw= -golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc= -golang.org/x/tools v0.21.0 h1:qc0xYgIbsSDt9EyWz05J5wfa7LOVW0YTLOXrqdLAWIw= -golang.org/x/tools v0.21.0/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= -golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA= -golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c= -golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= -golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= -golang.org/x/tools v0.27.0 h1:qEKojBykQkQ4EynWy4S8Weg69NumxKdn40Fce3uc/8o= -golang.org/x/tools v0.27.0/go.mod h1:sUi0ZgbwW9ZPAq26Ekut+weQPR5eIM6GQLQ1Yjm1H0Q= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= -google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= -google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= -google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= -google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= -google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= -google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= -google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= -google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= -google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= -google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= -google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= -google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= -google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= -google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= -google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= -google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= -google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= -google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= -google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= -google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= -gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= -gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.4.7 h1:9MDAWxMoSnB6QoSqiVr7P5mtkT9pOc1kSxchzPCnqJs= -honnef.co/go/tools v0.4.7/go.mod h1:+rnGS1THNh8zMwnd2oVOTL9QF6vmfyG6ZXBULae2uc0= -honnef.co/go/tools v0.5.0 h1:29uoiIormS3Z6R+t56STz/oI4v+mB51TSmEOdJPgRnE= -honnef.co/go/tools v0.5.0/go.mod h1:e9irvo83WDG9/irijV44wr3tbhcFeRnfpVlRqVwpzMs= -honnef.co/go/tools v0.5.1 h1:4bH5o3b5ZULQ4UrBmP+63W9r7qIkqJClEA9ko5YKx+I= -honnef.co/go/tools v0.5.1/go.mod h1:e9irvo83WDG9/irijV44wr3tbhcFeRnfpVlRqVwpzMs= -mvdan.cc/gofumpt v0.6.0 h1:G3QvahNDmpD+Aek/bNOLrFR2XC6ZAdo62dZu65gmwGo= -mvdan.cc/gofumpt v0.6.0/go.mod h1:4L0wf+kgIPZtcCWXynNS2e6bhmj73umwnuXSZarixzA= -mvdan.cc/gofumpt v0.7.0 h1:bg91ttqXmi9y2xawvkuMXyvAA/1ZGJqYAEGjXuP0JXU= -mvdan.cc/gofumpt v0.7.0/go.mod h1:txVFJy/Sc/mvaycET54pV8SW8gWxTlUuGHVEcncmNUo= -mvdan.cc/unparam v0.0.0-20240104100049-c549a3470d14 h1:zCr3iRRgdk5eIikZNDphGcM6KGVTx3Yu+/Uu9Es254w= -mvdan.cc/unparam v0.0.0-20240104100049-c549a3470d14/go.mod h1:ZzZjEpJDOmx8TdVU6umamY3Xy0UAQUI2DHbf05USVbI= -mvdan.cc/unparam v0.0.0-20240427195214-063aff900ca1 h1:Nykk7fggxChwLK4rUPYESzeIwqsuxXXlFEAh5YhaMRo= -mvdan.cc/unparam v0.0.0-20240427195214-063aff900ca1/go.mod h1:ZzZjEpJDOmx8TdVU6umamY3Xy0UAQUI2DHbf05USVbI= -mvdan.cc/unparam v0.0.0-20240528143540-8a5130ca722f h1:lMpcwN6GxNbWtbpI1+xzFLSW8XzX0u72NttUGVFjO3U= -mvdan.cc/unparam v0.0.0-20240528143540-8a5130ca722f/go.mod h1:RSLa7mKKCNeTTMHBw5Hsy2rfJmd6O2ivt9Dw9ZqCQpQ= -rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= -rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= -rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/.bingo/jb.mod b/.bingo/jb.mod deleted file mode 100644 index 732a8054017..00000000000 --- a/.bingo/jb.mod +++ /dev/null @@ -1,5 +0,0 @@ -module _ // Auto generated by https://github.com/bwplotka/bingo. DO NOT EDIT - -go 1.18 - -require github.com/jsonnet-bundler/jsonnet-bundler v0.5.1 // cmd/jb diff --git a/.bingo/jb.sum b/.bingo/jb.sum deleted file mode 100644 index 59c58ef41f1..00000000000 --- a/.bingo/jb.sum +++ /dev/null @@ -1,50 +0,0 @@ -github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM= -github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 h1:s6gZFSlWYmbqAuRjVTiNNhvNRfY2Wxp9nhfyel4rklc= -github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE= -github.com/campoy/embedmd v1.0.0/go.mod h1:oxyr9RCiSXg0M3VJ3ks0UGfp98BpSSGr0kpiX3MzVl8= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -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/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= -github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= -github.com/jsonnet-bundler/jsonnet-bundler v0.5.1 h1:eUd6EA1Qzz73Q4NLNLOrNkMb96+6NTTERbX9lqaxVwk= -github.com/jsonnet-bundler/jsonnet-bundler v0.5.1/go.mod h1:Qrdw/7mOFS2SKCOALKFfEH8gdvXJi8XZjw9g5ilpf4I= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= -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/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= -github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= -github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= -github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= -github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= -github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= -github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= -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/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.4/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c h1:aFV+BgZ4svzjfabn8ERpuB4JI4N6/rdy1iusx77G3oU= -golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc= -gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/.bingo/lefthook.mod b/.bingo/lefthook.mod deleted file mode 100644 index 557c374fd91..00000000000 --- a/.bingo/lefthook.mod +++ /dev/null @@ -1,5 +0,0 @@ -module _ // Auto generated by https://github.com/bwplotka/bingo. DO NOT EDIT - -go 1.20 - -require github.com/evilmartians/lefthook v1.4.8 diff --git a/.bingo/lefthook.sum b/.bingo/lefthook.sum deleted file mode 100644 index 4f9fd11d4c6..00000000000 --- a/.bingo/lefthook.sum +++ /dev/null @@ -1,523 +0,0 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= -cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= -cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= -cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= -cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= -cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= -cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= -cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= -cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= -cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= -cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= -cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= -cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= -cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= -cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= -cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= -cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= -cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= -cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= -cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= -cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= -cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= -cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= -cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= -cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= -cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= -cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= -cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= -cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= -cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= -cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= -cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= -cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= -dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= -github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= -github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= -github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= -github.com/briandowns/spinner v1.23.0 h1:alDF2guRWqa/FOZZYWjlMIx2L6H0wyewPxo/CH4Pt2A= -github.com/briandowns/spinner v1.23.0/go.mod h1:rPG4gmXeN3wQV/TsAY4w8lPdIM6RX3yqeBQJSrbXjuE= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/charmbracelet/lipgloss v0.6.0 h1:1StyZB9vBSOyuZxQUcUwGr17JmojPNm87inij9N3wJY= -github.com/charmbracelet/lipgloss v0.6.0/go.mod h1:tHh2wr34xcHjC2HCXIlGSG1jaDF0S0atAUvBMP6Ppuk= -github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= -github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cpuguy83/go-md2man/v2 v2.0.2/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/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/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= -github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/evilmartians/lefthook v1.4.8 h1:8FmXWtfFiEZw3w18JbhVrp3g+Iy/j2XEo6gcC25+4KA= -github.com/evilmartians/lefthook v1.4.8/go.mod h1:anwwu2QiCEnsOCBHfRgGOB3/sd9FMVNhmY8l9DDQAG8= -github.com/fatih/color v1.14.1 h1:qfhVLaG5s+nCROl1zJsZRxFeYrHLqWroPOQ8BWiNb4w= -github.com/fatih/color v1.14.1/go.mod h1:2oHN61fhTpgcxD3TSWCgKDiH1+x4OiDVVGH8WlgGZGg= -github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= -github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= -github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= -github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= -github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= -github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= -github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= -github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= -github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= -github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= -github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= -github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= -github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= -github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= -github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= -github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -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/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= -github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= -github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= -github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= -github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= -github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= -github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= -github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= -github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= -github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/muesli/reflow v0.2.1-0.20210115123740-9e1d0d53df68/go.mod h1:Xk+z4oIWdQqJzsxyjgl3P22oYZnHdZ8FFTHAQQt5BMQ= -github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= -github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= -github.com/muesli/termenv v0.11.1-0.20220204035834-5ac8409525e0/go.mod h1:Bd5NYQ7pd+SrtBSrSNoBBmXlcY8+Xj4BMJgh8qcZrvs= -github.com/muesli/termenv v0.15.1 h1:UzuTb/+hhlBugQz28rpzey4ZuKcZ03MeKsoG7IJZIxs= -github.com/muesli/termenv v0.15.1/go.mod h1:HeAQPTzpfs016yGtA4g00CsdYnVLJvxsS4ANqrZs2sQ= -github.com/pelletier/go-toml/v2 v2.0.9 h1:uH2qQXheeefCCkuBBSLi7jCiSmj3VRh2+Goq2N7Xxu0= -github.com/pelletier/go-toml/v2 v2.0.9/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= -github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/spf13/afero v1.9.5 h1:stMpOSZFs//0Lv29HduCmli3GUfpFoF3Y1Q/aXj/wVM= -github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= -github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA= -github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48= -github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= -github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= -github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= -github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= -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/spf13/viper v1.16.0 h1:rGGH0XDZhdUOryiDWjmIvUSWpbNqisK8Wk0Vyefw8hc= -github.com/spf13/viper v1.16.0/go.mod h1:yg78JgCJcbrQOvV9YLXgkLaZqUidkY9K+Dd1FofRzQg= -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.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= -github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= -github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= -go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= -go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= -golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= -golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= -golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= -golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= -golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= -golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= -golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= -golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= -golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= -golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= -golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY= -golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= -golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= -golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= -golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= -google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= -google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= -google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= -google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= -google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= -google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= -google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= -google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= -google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= -google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= -google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= -google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= -google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= -google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= -google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= -google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= -google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= -google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= -google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= -google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= -google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= -gopkg.in/alessio/shellescape.v1 v1.0.0-20170105083845-52074bc9df61 h1:8ajkpB4hXVftY5ko905id+dOnmorcS2CHNxxHLLDcFM= -gopkg.in/alessio/shellescape.v1 v1.0.0-20170105083845-52074bc9df61/go.mod h1:IfMagxm39Ys4ybJrDb7W3Ob8RwxftP0Yy+or/NVz1O8= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= -gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -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= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= -rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= -rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/.bingo/swagger.mod b/.bingo/swagger.mod deleted file mode 100644 index ef5b4b6e2c6..00000000000 --- a/.bingo/swagger.mod +++ /dev/null @@ -1,5 +0,0 @@ -module _ // Auto generated by https://github.com/bwplotka/bingo. DO NOT EDIT - -go 1.18 - -require github.com/go-swagger/go-swagger v0.30.6-0.20240310114303-db51e79a0e37 // cmd/swagger diff --git a/.bingo/swagger.sum b/.bingo/swagger.sum deleted file mode 100644 index 0dc152e6e87..00000000000 --- a/.bingo/swagger.sum +++ /dev/null @@ -1,810 +0,0 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= -cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= -cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= -cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= -cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= -cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= -cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= -cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= -cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= -cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= -cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= -cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= -cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= -cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= -cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= -cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= -cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= -cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= -cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= -cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= -cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= -cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= -cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= -cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= -cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= -cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= -cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= -cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= -cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= -cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= -cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= -cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= -cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= -dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= -github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= -github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= -github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= -github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= -github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0= -github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= -github.com/Masterminds/sprig/v3 v3.2.2 h1:17jRggJu518dr3QaafizSXOjKYp94wKfABxUmyxvxX8= -github.com/Masterminds/sprig/v3 v3.2.2/go.mod h1:UoaO7Yp8KlPnJIYWTFkMaqPUYKTfGFPhxNuwnnxkKlk= -github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA= -github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM= -github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= -github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= -github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= -github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= -github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= -github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d h1:Byv0BzEl3/e6D5CLfI0j/7hiIEtvGVFPCZ7Ei2oq8iQ= -github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= -github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= -github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= -github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -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/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= -github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/felixge/httpsnoop v1.0.2 h1:+nS9g82KMXccJ/wp0zyRW9ZBHFETmMGtkk+2CTTrW4o= -github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk= -github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -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.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI= -github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= -github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= -github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= -github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= -github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-openapi/analysis v0.21.2 h1:hXFrOYFHUAMQdu6zwAiKKJHJQ8kqZs1ux/ru1P1wLJU= -github.com/go-openapi/analysis v0.21.2/go.mod h1:HZwRk4RRisyG8vx2Oe6aqeSQcoxRp47Xkp3+K6q+LdY= -github.com/go-openapi/analysis v0.21.4 h1:ZDFLvSNxpDaomuCueM0BlSXxpANBlFYiBvr+GXrvIHc= -github.com/go-openapi/analysis v0.21.4/go.mod h1:4zQ35W4neeZTqh3ol0rv/O8JBbka9QyAgQRPp9y3pfo= -github.com/go-openapi/analysis v0.23.0 h1:aGday7OWupfMs+LbmLZG4k0MYXIANxcuBTYUC03zFCU= -github.com/go-openapi/analysis v0.23.0/go.mod h1:9mz9ZWaSlV8TvjQHLl2mUW2PbZtemkE8yA5v22ohupo= -github.com/go-openapi/errors v0.19.8/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M= -github.com/go-openapi/errors v0.19.9/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M= -github.com/go-openapi/errors v0.20.2 h1:dxy7PGTqEh94zj2E3h1cUmQQWiM1+aeCROfAr02EmK8= -github.com/go-openapi/errors v0.20.2/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M= -github.com/go-openapi/errors v0.20.3 h1:rz6kiC84sqNQoqrtulzaL/VERgkoCyB6WdEkc2ujzUc= -github.com/go-openapi/errors v0.20.3/go.mod h1:Z3FlZ4I8jEGxjUK+bugx3on2mIAk4txuAOhlsB1FSgk= -github.com/go-openapi/errors v0.22.0 h1:c4xY/OLxUBSTiepAg3j/MHuAv5mJhnf53LLMWFB+u/w= -github.com/go-openapi/errors v0.22.0/go.mod h1:J3DmZScxCDufmIMsdOuDHxJbdOGC0xtUynjIx092vXE= -github.com/go-openapi/inflect v0.19.0 h1:9jCH9scKIbHeV9m12SmPilScz6krDxKRasNNSNPXu/4= -github.com/go-openapi/inflect v0.19.0/go.mod h1:lHpZVlpIQqLyKwJ4N+YSc9hchQy/i12fJykb83CRBH4= -github.com/go-openapi/inflect v0.21.0 h1:FoBjBTQEcbg2cJUWX6uwL9OyIW8eqc9k4KhN4lfbeYk= -github.com/go-openapi/inflect v0.21.0/go.mod h1:INezMuUu7SJQc2AyR3WO0DqqYUJSj8Kb4hBd7WtjlAw= -github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= -github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= -github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= -github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= -github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= -github.com/go-openapi/jsonreference v0.19.6 h1:UBIxjkht+AWIgYzCDSv2GN+E/togfwXUJFRTWhl2Jjs= -github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns= -github.com/go-openapi/jsonreference v0.20.0 h1:MYlu0sBgChmCfJxxUKZ8g1cPWFOB37YSZqewK7OKeyA= -github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo= -github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ= -github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4= -github.com/go-openapi/loads v0.21.0 h1:jYtUO4wwP7psAweisP/MDoOpdzsYEESdoPcsWjHDR68= -github.com/go-openapi/loads v0.21.1/go.mod h1:/DtAMXXneXFjbQMGEtbamCZb+4x7eGwkvZCvBmwUG+g= -github.com/go-openapi/loads v0.21.2 h1:r2a/xFIYeZ4Qd2TnGpWDIQNcP80dIaZgf704za8enro= -github.com/go-openapi/loads v0.21.2/go.mod h1:Jq58Os6SSGz0rzh62ptiu8Z31I+OTHqmULx5e/gJbNw= -github.com/go-openapi/loads v0.22.0 h1:ECPGd4jX1U6NApCGG1We+uEozOAvXvJSF4nnwHZ8Aco= -github.com/go-openapi/loads v0.22.0/go.mod h1:yLsaTCS92mnSAZX5WWoxszLj0u+Ojl+Zs5Stn1oF+rs= -github.com/go-openapi/runtime v0.21.1 h1:/KIG00BzA2x2HRStX2tnhbqbQdPcFlkgsYCiNY20FZs= -github.com/go-openapi/runtime v0.24.1 h1:Sml5cgQKGYQHF+M7yYSHaH1eOjvTykrddTE/KtQVjqo= -github.com/go-openapi/runtime v0.24.1/go.mod h1:AKurw9fNre+h3ELZfk6ILsfvPN+bvvlaU/M9q/r9hpk= -github.com/go-openapi/runtime v0.28.0 h1:gpPPmWSNGo214l6n8hzdXYhPuJcGtziTOgUpvsFWGIQ= -github.com/go-openapi/runtime v0.28.0/go.mod h1:QN7OzcS+XuYmkQLw05akXk0jRH/eZ3kb18+1KwW9gyc= -github.com/go-openapi/spec v0.20.4 h1:O8hJrt0UMnhHcluhIdUgCLRWyM2x7QkBXRvOs7m+O1M= -github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7FOEWeq8I= -github.com/go-openapi/spec v0.20.6/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6VaaBKcWA= -github.com/go-openapi/spec v0.20.7 h1:1Rlu/ZrOCCob0n+JKKJAWhNWMPW8bOZRg8FJaY+0SKI= -github.com/go-openapi/spec v0.20.7/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6VaaBKcWA= -github.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9ZY= -github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk= -github.com/go-openapi/strfmt v0.21.0/go.mod h1:ZRQ409bWMj+SOgXofQAGTIo2Ebu72Gs+WaRADcS5iNg= -github.com/go-openapi/strfmt v0.21.1 h1:G6s2t5V5kGCHLVbSdZ/6lI8Wm4OzoPFkc3/cjAsKQrM= -github.com/go-openapi/strfmt v0.21.1/go.mod h1:I/XVKeLc5+MM5oPNN7P6urMOpuLXEcNrCX/rPGuWb0k= -github.com/go-openapi/strfmt v0.21.2/go.mod h1:I/XVKeLc5+MM5oPNN7P6urMOpuLXEcNrCX/rPGuWb0k= -github.com/go-openapi/strfmt v0.21.3 h1:xwhj5X6CjXEZZHMWy1zKJxvW9AfHC9pkyUjLvHtKG7o= -github.com/go-openapi/strfmt v0.21.3/go.mod h1:k+RzNO0Da+k3FrrynSNN8F7n/peCmQQqbbXjtDfvmGg= -github.com/go-openapi/strfmt v0.23.0 h1:nlUS6BCqcnAk0pyhi9Y+kdDVZdZMHfEKQiS4HaMgO/c= -github.com/go-openapi/strfmt v0.23.0/go.mod h1:NrtIpfKtWIygRkKVsxh7XQMDQW5HKQl6S5ik2elW+K4= -github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= -github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM= -github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= -github.com/go-openapi/swag v0.21.1/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= -github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g= -github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= -github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= -github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= -github.com/go-openapi/validate v0.20.3 h1:GZPPhhKSZrE8HjB4eEkoYAZmoWA4+tCemSgINH1/vKw= -github.com/go-openapi/validate v0.21.0/go.mod h1:rjnrwK57VJ7A8xqfpAOEKRH8yQSGUriMu5/zuPSQ1hg= -github.com/go-openapi/validate v0.22.0 h1:b0QecH6VslW/TxtpKgzpO1SNG7GU2FsaqKdP1E2T50Y= -github.com/go-openapi/validate v0.22.0/go.mod h1:rjnrwK57VJ7A8xqfpAOEKRH8yQSGUriMu5/zuPSQ1hg= -github.com/go-openapi/validate v0.24.0 h1:LdfDKwNbpB6Vn40xhTdNZAnfLECL81w+VX3BumrGD58= -github.com/go-openapi/validate v0.24.0/go.mod h1:iyeX1sEufmv3nPbBdX3ieNviWnOZaJ1+zquzJEf2BAQ= -github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw= -github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4= -github.com/go-swagger/go-swagger v0.29.0 h1:z3YoZtLvS1Y8TE/PCat1VypcZxM0IgKLt0NvZxQyNl8= -github.com/go-swagger/go-swagger v0.29.0/go.mod h1:Z4GJzI+bHKKkGB2Ji1rawpi3/ldXX8CkzGIa9HAC5EE= -github.com/go-swagger/go-swagger v0.30.2 h1:23odPUyQZdkNFZZSBJ3mqYYcdh+LnuReEbdWN18OMRo= -github.com/go-swagger/go-swagger v0.30.2/go.mod h1:neDPes8r8PCz2JPvHRDj8BTULLh4VJUt7n6MpQqxhHM= -github.com/go-swagger/go-swagger v0.30.6-0.20240310114303-db51e79a0e37 h1:KFcZmKdZmapAog2+eL1buervAYrYolBZk7fMecPPDmo= -github.com/go-swagger/go-swagger v0.30.6-0.20240310114303-db51e79a0e37/go.mod h1:i1/E+d8iPNReSE7y04FaVu5OPKB3il5cn+T1Egogg3I= -github.com/go-swagger/go-swagger v0.30.6-0.20240418033037-c46c303aaa02 h1:J6YiT/eg3gAfKMdVCkWXe6khsO+nxa8W4URZ4AUqzbA= -github.com/go-swagger/go-swagger v0.30.6-0.20240418033037-c46c303aaa02/go.mod h1:i1/E+d8iPNReSE7y04FaVu5OPKB3il5cn+T1Egogg3I= -github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0= -github.com/gobuffalo/depgen v0.0.0-20190329151759-d478694a28d3/go.mod h1:3STtPUQYuzV0gBVOY3vy6CfMm/ljR4pABfrTeHNLHUY= -github.com/gobuffalo/depgen v0.1.0/go.mod h1:+ifsuy7fhi15RWncXQQKjWS9JPkdah5sZvtHc2RXGlg= -github.com/gobuffalo/envy v1.6.15/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= -github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= -github.com/gobuffalo/flect v0.1.0/go.mod h1:d2ehjJqGOH/Kjqcoz+F7jHTBbmDb38yXA598Hb50EGs= -github.com/gobuffalo/flect v0.1.1/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI= -github.com/gobuffalo/flect v0.1.3/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI= -github.com/gobuffalo/genny v0.0.0-20190329151137-27723ad26ef9/go.mod h1:rWs4Z12d1Zbf19rlsn0nurr75KqhYp52EAGGxTbBhNk= -github.com/gobuffalo/genny v0.0.0-20190403191548-3ca520ef0d9e/go.mod h1:80lIj3kVJWwOrXWWMRzzdhW3DsrdjILVil/SFKBzF28= -github.com/gobuffalo/genny v0.1.0/go.mod h1:XidbUqzak3lHdS//TPu2OgiFB+51Ur5f7CSnXZ/JDvo= -github.com/gobuffalo/genny v0.1.1/go.mod h1:5TExbEyY48pfunL4QSXxlDOmdsD44RRq4mVZ0Ex28Xk= -github.com/gobuffalo/gitgen v0.0.0-20190315122116-cc086187d211/go.mod h1:vEHJk/E9DmhejeLeNt7UVvlSGv3ziL+djtTr3yyzcOw= -github.com/gobuffalo/gogen v0.0.0-20190315121717-8f38393713f5/go.mod h1:V9QVDIxsgKNZs6L2IYiGR8datgMhB577vzTDqypH360= -github.com/gobuffalo/gogen v0.1.0/go.mod h1:8NTelM5qd8RZ15VjQTFkAW6qOMx5wBbW4dSCS3BY8gg= -github.com/gobuffalo/gogen v0.1.1/go.mod h1:y8iBtmHmGc4qa3urIyo1shvOD8JftTtfcKi+71xfDNE= -github.com/gobuffalo/logger v0.0.0-20190315122211-86e12af44bc2/go.mod h1:QdxcLw541hSGtBnhUc4gaNIXRjiDppFGaDqzbrBd3v8= -github.com/gobuffalo/mapi v1.0.1/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= -github.com/gobuffalo/mapi v1.0.2/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= -github.com/gobuffalo/packd v0.0.0-20190315124812-a385830c7fc0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4= -github.com/gobuffalo/packd v0.1.0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4= -github.com/gobuffalo/packr/v2 v2.0.9/go.mod h1:emmyGweYTm6Kdper+iywB6YK5YzuKchGtJQZ0Odn4pQ= -github.com/gobuffalo/packr/v2 v2.2.0/go.mod h1:CaAwI0GPIAv+5wKLtv8Afwl+Cm78K/I/VCm/3ptBN+0= -github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= -github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= -github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= -github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= -github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= -github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= -github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= -github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= -github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= -github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= -github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= -github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4= -github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= -github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyEE= -github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w= -github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= -github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= -github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw= -github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= -github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= -github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU= -github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= -github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= -github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= -github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= -github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= -github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= -github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/jessevdk/go-flags v1.5.0 h1:1jKYvbxEjfUl0fmqTCOfonvskHHXMjBySTLW4y9LFvc= -github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= -github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= -github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= -github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= -github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= -github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4= -github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= -github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= -github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= -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/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls= -github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= -github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= -github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= -github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= -github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= -github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= -github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= -github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE= -github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= -github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= -github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= -github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= -github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/mitchellh/mapstructure v1.4.3 h1:OVowDSCllw/YjdLkam3/sm7wEtOy59d8ndGgCcyj8cs= -github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= -github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= -github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= -github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= -github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= -github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= -github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= -github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE= -github.com/pelletier/go-toml v1.9.4 h1:tjENF6MfZAg8e4ZmZTeWaWiT2vXtsoO6+iuOjFhECwM= -github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= -github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= -github.com/pelletier/go-toml/v2 v2.0.1 h1:8e3L2cCQzLFi2CR4g7vGFuFxX7Jl1kKX8gW+iV0GUKU= -github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo= -github.com/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI= -github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= -github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= -github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= -github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg= -github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= -github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= -github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= -github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= -github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= -github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= -github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= -github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= -github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= -github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= -github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= -github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= -github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= -github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= -github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= -github.com/spf13/afero v1.8.0 h1:5MmtuhAgYeU6qpa7w7bP0dv6MBYuup0vekhSpSkoq60= -github.com/spf13/afero v1.8.2 h1:xehSyVa0YnHWsJ49JFljMpg1HX19V6NDZ1fkm1Xznbo= -github.com/spf13/afero v1.8.2/go.mod h1:CtAatgMJh6bJEIs48Ay/FOnkljP3WeGUG0MC1RfAqwo= -github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= -github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= -github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cast v1.4.1 h1:s0hze+J0196ZfEMTs80N7UlFt0BDuQ7Q+JDnHiMWKdA= -github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= -github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= -github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= -github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= -github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= -github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= -github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= -github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -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/spf13/viper v1.10.1 h1:nuJZuYpG7gTj/XqiUwg8bA0cp1+M2mC3J4g5luUYBKk= -github.com/spf13/viper v1.12.0 h1:CZ7eSOd3kZoaYDLbXnmzgQI5RlciuXBMA+18HwHRfZQ= -github.com/spf13/viper v1.12.0/go.mod h1:b6COn30jlNxbm/V2IqWiNWkJ+vZNiMNksliPCiuKtSI= -github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ= -github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1/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.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= -github.com/subosito/gotenv v1.3.0 h1:mjC+YW8QpAdXibNi+vNWgzmgBH4+5l5dCXv8cNysBLI= -github.com/subosito/gotenv v1.3.0/go.mod h1:YzJjq/33h7nrwdY+iHMhEOEEbW0ovIz0tB6t6PwAXzs= -github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= -github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= -github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= -github.com/toqueteos/webbrowser v1.2.0 h1:tVP/gpK69Fx+qMJKsLE7TD8LuGWPnEV71wBN9rrstGQ= -github.com/toqueteos/webbrowser v1.2.0/go.mod h1:XWoZq4cyp9WeUeak7w7LXRUQf1F1ATJMir8RTqb4ayM= -github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= -github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs= -github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g= -github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM= -github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8= -github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= -github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -go.mongodb.org/mongo-driver v1.7.3/go.mod h1:NqaYOwnXWr5Pm7AOpO5QFxKJ503nbMse/R79oO62zWg= -go.mongodb.org/mongo-driver v1.7.5/go.mod h1:VXEWRZ6URJIkUq2SCAyapmhH0ZLRBP+FT4xhp5Zvxng= -go.mongodb.org/mongo-driver v1.8.2 h1:8ssUXufb90ujcIvR6MyE1SchaNj0SFxsakiZgxIyrMk= -go.mongodb.org/mongo-driver v1.8.3/go.mod h1:0sQWfOeY63QTntERDJJ/0SuKK0T1uVSgKCuAROlKEPY= -go.mongodb.org/mongo-driver v1.10.0/go.mod h1:wsihk0Kdgv8Kqu1Anit4sfK+22vSFbUrAVEYRhCXrA8= -go.mongodb.org/mongo-driver v1.10.1 h1:NujsPveKwHaWuKUer/ceo9DzEe7HIj1SlJ6uvXZG0S4= -go.mongodb.org/mongo-driver v1.10.1/go.mod h1:z4XpeoU6w+9Vht+jAFyLgVrD+jGSQQe0+CBWFHNiHt8= -go.mongodb.org/mongo-driver v1.14.0 h1:P98w8egYRjYe3XDjxhYJagTokP/H6HzlsnojRgZRd80= -go.mongodb.org/mongo-driver v1.14.0/go.mod h1:Vzb0Mk/pa7e6cWw85R4F/endUC3u0U9jGcNU603k65c= -go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= -go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= -go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= -go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= -go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= -golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20201216223049-8b5274cf687f/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= -golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 h1:Y/gsMcFOcR+6S6f3YeMKl5g+dZMEWqcz5Czj/GWYbkM= -golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= -golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= -golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= -golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= -golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= -golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= -golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 h1:LfspQV/FYTatPTr/3HzIcmiUFH7PGP+OQ6mgDYo3yuQ= -golang.org/x/exp v0.0.0-20240222234643-814bf88cf225/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc= -golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= -golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= -golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= -golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= -golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= -golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.5.1 h1:OJxoQ/rynoF0dcCdI7cLPktw/hR2cueqYfjm43oqK38= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic= -golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM= -golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220121210141-e204ce36a2ba h1:6u6sik+bn/y7vILcYkK3iwTBWN7WtBvB0+SZswQnbf8= -golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190412183630-56d357773e84/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= -golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190419153524-e8e3143a4f4a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190531175056-4c3a928424d2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 h1:XfKQ4OlFl8okEOr5UvAqFRVj8pY/4yfcXrddB8qAbU0= -golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220829200755-d48e67d00261 h1:v6hYoSR9T5oet+pMXwUWkbiVqx/63mlHjefrHmxwfeY= -golang.org/x/sys v0.0.0-20220829200755-d48e67d00261/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= -golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= -golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190329151228-23e29df326fe/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190416151739-9c9e1878f421/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190420181800-aa740d480789/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= -golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= -golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= -golang.org/x/tools v0.1.8 h1:P1HhGGuLW4aAclzjtmJdf0mJOjVUZUzOTqkAkWL+l6w= -golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw= -golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= -google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= -google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= -google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= -google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= -google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= -google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= -google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= -google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= -google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= -google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= -google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= -google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= -google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= -google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= -google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= -google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= -google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= -google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= -google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= -google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= -google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/ini.v1 v1.66.3 h1:jRskFVxYaMGAMUbN0UZ7niA9gzL9B49DOqE78vg0k3w= -gopkg.in/ini.v1 v1.66.4 h1:SsAcf+mM7mRZo2nJNGt8mZCjG8ZRaNGMURJw7BsIST4= -gopkg.in/ini.v1 v1.66.4/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= -gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/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= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= -rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= -rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/.bingo/variables.env b/.bingo/variables.env index 03b44adfca5..b4e2c715f3e 100644 --- a/.bingo/variables.env +++ b/.bingo/variables.env @@ -8,19 +8,5 @@ if [ -z "$GOBIN" ]; then fi -BRA="${GOBIN}/bra-v0.0.0-20200517080246-1e3013ecaff8" - -COG="${GOBIN}/cog-v0.0.15" - -CUE="${GOBIN}/cue-v0.5.0" - DRONE="${GOBIN}/drone-v1.5.0" -GOLANGCI_LINT="${GOBIN}/golangci-lint-v1.62.0" - -JB="${GOBIN}/jb-v0.5.1" - -LEFTHOOK="${GOBIN}/lefthook-v1.4.8" - -SWAGGER="${GOBIN}/swagger-v0.30.6-0.20240310114303-db51e79a0e37" - diff --git a/.citools/bra/go.mod b/.citools/bra/go.mod index 7562899364f..66f123067be 100644 --- a/.citools/bra/go.mod +++ b/.citools/bra/go.mod @@ -5,7 +5,7 @@ go 1.24.1 tool github.com/unknwon/bra require ( - github.com/BurntSushi/toml v1.4.0 // indirect + github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c // indirect github.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/fsnotify/fsnotify v1.8.0 // indirect diff --git a/.citools/bra/go.sum b/.citools/bra/go.sum index c96783ededd..1b0afe3b97c 100644 --- a/.citools/bra/go.sum +++ b/.citools/bra/go.sum @@ -1,6 +1,7 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0= github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= +github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c h1:pxW6RcqyfI9/kWtOwnv/G+AzdKuy2ZrqINhenH4HyNs= +github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.6 h1:XJtiaUW6dEEqVuZiMTn1ldk455QWwEIsMIJlo5vtkx0= diff --git a/.citools/drone/go.mod b/.citools/drone/go.mod deleted file mode 100644 index 4c1fba053f9..00000000000 --- a/.citools/drone/go.mod +++ /dev/null @@ -1,77 +0,0 @@ -module drone - -go 1.24.1 - -tool github.com/drone/drone-cli/drone - -replace github.com/docker/docker => github.com/moby/moby v27.5.1+incompatible - -require ( - github.com/99designs/httpsignatures-go v0.0.0-20170731043157-88528bf4ca7e // indirect - github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect - github.com/Microsoft/go-winio v0.6.2 // indirect - github.com/bmatcuk/doublestar v1.3.4 // indirect - github.com/buildkite/yaml v2.1.0+incompatible // indirect - github.com/containerd/log v0.1.0 // indirect - github.com/coreos/go-semver v0.3.1 // indirect - github.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect - github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - github.com/dchest/uniuri v1.2.0 // indirect - github.com/distribution/reference v0.6.0 // indirect - github.com/docker/distribution v2.8.2+incompatible // indirect - github.com/docker/docker v27.5.1+incompatible // indirect - github.com/docker/go-connections v0.5.0 // indirect - github.com/docker/go-units v0.5.0 // indirect - github.com/drone-runners/drone-runner-docker v1.8.3 // indirect - github.com/drone/drone-cli v1.8.0 // indirect - github.com/drone/drone-go v1.7.1 // indirect - github.com/drone/envsubst v1.0.3 // indirect - github.com/drone/funcmap v0.0.0-20220929084810-72602997d16f // indirect - github.com/drone/runner-go v1.12.0 // indirect - github.com/drone/signal v1.0.0 // indirect - github.com/fatih/color v1.18.0 // indirect - github.com/felixge/httpsnoop v1.0.4 // indirect - github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32 // indirect - github.com/go-logr/logr v1.4.2 // indirect - github.com/go-logr/stdr v1.2.2 // indirect - github.com/gogo/protobuf v1.3.2 // indirect - github.com/google/go-jsonnet v0.18.0 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1 // indirect - github.com/hashicorp/errwrap v1.1.0 // indirect - github.com/hashicorp/go-multierror v1.1.1 // indirect - github.com/jackspirou/syscerts v0.0.0-20160531025014-b68f5469dff1 // indirect - github.com/joho/godotenv v1.5.1 // indirect - github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.20 // indirect - github.com/moby/docker-image-spec v1.3.1 // indirect - github.com/moby/term v0.5.0 // indirect - github.com/natessilva/dag v0.0.0-20180124060714-7194b8dcc5c4 // indirect - github.com/opencontainers/go-digest v1.0.0 // indirect - github.com/opencontainers/image-spec v1.1.0 // indirect - github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect - github.com/pkg/errors v0.9.1 // indirect - github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/russross/blackfriday/v2 v2.1.0 // indirect - github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect - github.com/sirupsen/logrus v1.9.3 // indirect - github.com/urfave/cli v1.22.16 // indirect - go.opentelemetry.io/auto/sdk v1.1.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect - go.opentelemetry.io/otel v1.35.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.34.0 // indirect - go.opentelemetry.io/otel/metric v1.35.0 // indirect - go.opentelemetry.io/otel/trace v1.35.0 // indirect - go.starlark.net v0.0.0-20230525235612-a134d8f9ddca // indirect - golang.org/x/net v0.36.0 // indirect - golang.org/x/oauth2 v0.27.0 // indirect - golang.org/x/sync v0.11.0 // indirect - golang.org/x/sys v0.30.0 // indirect - golang.org/x/time v0.9.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20250204164813-702378808489 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250207221924-e9438ea467c6 // indirect - google.golang.org/grpc v1.70.0 // indirect - google.golang.org/protobuf v1.36.5 // indirect - gopkg.in/yaml.v2 v2.4.0 // indirect - gotest.tools/v3 v3.5.1 // indirect - sigs.k8s.io/yaml v1.4.0 // indirect -) diff --git a/.citools/drone/go.sum b/.citools/drone/go.sum deleted file mode 100644 index 4e6b0dba301..00000000000 --- a/.citools/drone/go.sum +++ /dev/null @@ -1,338 +0,0 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -github.com/99designs/basicauth-go v0.0.0-20160802081356-2a93ba0f464d/go.mod h1:3cARGAK9CfW3HoxCy1a0G4TKrdiKke8ftOMEOHyySYs= -github.com/99designs/httpsignatures-go v0.0.0-20170731043157-88528bf4ca7e h1:rl2Aq4ZODqTDkeSqQBy+fzpZPamacO1Srp8zq7jf2Sc= -github.com/99designs/httpsignatures-go v0.0.0-20170731043157-88528bf4ca7e/go.mod h1:Xa6lInWHNQnuWoF0YPSsx+INFA9qk7/7pTjwb3PInkY= -github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= -github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= -github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= -github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= -github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= -github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= -github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w= -github.com/bmatcuk/doublestar v1.3.4 h1:gPypJ5xD31uhX6Tf54sDPUOBXTqKH4c9aPY66CyQrS0= -github.com/bmatcuk/doublestar v1.3.4/go.mod h1:wiQtGV+rzVYxB7WIlirSN++5HPtPlXEo9MEoZQC/PmE= -github.com/buildkite/yaml v2.1.0+incompatible h1:xirI+ql5GzfikVNDmt+yeiXpf/v1Gt03qXTtT5WXdr8= -github.com/buildkite/yaml v2.1.0+incompatible/go.mod h1:UoU8vbcwu1+vjZq01+KrpSeLBgQQIjL/H7Y6KwikUrI= -github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= -github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= -github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/containerd/containerd v1.3.4/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= -github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= -github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4= -github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec= -github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/cpuguy83/go-md2man/v2 v2.0.6 h1:XJtiaUW6dEEqVuZiMTn1ldk455QWwEIsMIJlo5vtkx0= -github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= -github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dchest/uniuri v0.0.0-20160212164326-8902c56451e9/go.mod h1:GgB8SF9nRG+GqaDtLcwJZsQFhcogVCJ79j4EdT0c2V4= -github.com/dchest/uniuri v1.2.0 h1:koIcOUdrTIivZgSLhHQvKgqdWZq5d7KdMEWF1Ud6+5g= -github.com/dchest/uniuri v1.2.0/go.mod h1:fSzm4SLHzNZvWLvWJew423PhAzkpNQYq+uNLq4kxhkY= -github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= -github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8= -github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/go-connections v0.3.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= -github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= -github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= -github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -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/drone-runners/drone-runner-docker v1.8.3 h1:uUnC45C1JMSLW+9uy6RoKG5ugzeXWN89pygs9BMLObY= -github.com/drone-runners/drone-runner-docker v1.8.3/go.mod h1:JR3pZeVZKKpkbTajiq0YtAx9WutkODdVKZGNR83kEwE= -github.com/drone/drone-cli v1.8.0 h1:tpp+GPonS87IKMZCGbIoa+zfDwiuJDL3NIC6S7neNrU= -github.com/drone/drone-cli v1.8.0/go.mod h1:zu6/7OpQjWBw/5VG0M3K4iJc6kSoTrjnY7CRLBrGH84= -github.com/drone/drone-go v1.7.1 h1:ZX+3Rs8YHUSUQ5mkuMLmm1zr1ttiiE2YGNxF3AnyDKw= -github.com/drone/drone-go v1.7.1/go.mod h1:fxCf9jAnXDZV1yDr0ckTuWd1intvcQwfJmTRpTZ1mXg= -github.com/drone/envsubst v1.0.2/go.mod h1:bkZbnc/2vh1M12Ecn7EYScpI4YGYU0etwLJICOWi8Z0= -github.com/drone/envsubst v1.0.3 h1:PCIBwNDYjs50AsLZPYdfhSATKaRg/FJmDc2D6+C2x8g= -github.com/drone/envsubst v1.0.3/go.mod h1:N2jZmlMufstn1KEqvbHjw40h1KyTmnVzHcSc9bFiJ2g= -github.com/drone/funcmap v0.0.0-20220929084810-72602997d16f h1:/jEs7lulqVO2u1+XI5rW4oFwIIusxuDOVKD9PAzlW2E= -github.com/drone/funcmap v0.0.0-20220929084810-72602997d16f/go.mod h1:nDRkX7PHq+p39AD5/usv3KZMerxZTYU/9rfLS5IDspU= -github.com/drone/runner-go v1.12.0 h1:zUjDj9ylsJ4n4Mvy4znddq/Z4EBzcUXzTltpzokKtgs= -github.com/drone/runner-go v1.12.0/go.mod h1:vu4pPPYDoeN6vdYQAY01GGGsAIW4aLganJNaa8Fx8zE= -github.com/drone/signal v1.0.0 h1:NrnM2M/4yAuU/tXs6RP1a1ZfxnaHwYkd0kJurA1p6uI= -github.com/drone/signal v1.0.0/go.mod h1:S8t92eFT0g4WUgEc/LxG+LCuiskpMNsG0ajAMGnyZpc= -github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= -github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= -github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= -github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= -github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32 h1:Mn26/9ZMNWSw9C9ERFA1PUxfmGpolnw2v0bKOREu5ew= -github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32/go.mod h1:GIjDIg/heH5DOkXY3YJ/wNhfHsQHoXGjl8G8amsYQ1I= -github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= -github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/gogo/protobuf v0.0.0-20170307180453-100ba4e88506/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= -github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= -github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= -github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= -github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= -github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= -github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/google/go-jsonnet v0.18.0 h1:/6pTy6g+Jh1a1I2UMoAODkqELFiVIdOxbNwv0DDzoOg= -github.com/google/go-jsonnet v0.18.0/go.mod h1:C3fTzyVJDslXdiTqw/bTFk7vSGyCtH3MGRbDfvEwGd0= -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/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1 h1:e9Rjr40Z98/clHv5Yg79Is0NtosR5LXRvdr7o/6NwbA= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1/go.mod h1:tIxuGz/9mpox++sgp9fJjHO0+q1X9/UOWd798aAm22M= -github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= -github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= -github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= -github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= -github.com/jackspirou/syscerts v0.0.0-20160531025014-b68f5469dff1 h1:9Xm8CKtMZIXgcopfdWk/qZ1rt0HjMgfMR9nxxSeK6vk= -github.com/jackspirou/syscerts v0.0.0-20160531025014-b68f5469dff1/go.mod h1:zuHl3Hh+e9P6gmBPvcqR1HjkaWHC/csgyskg6IaFKFo= -github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= -github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= -github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= -github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg= -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/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= -github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= -github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -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/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= -github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= -github.com/moby/moby v27.5.1+incompatible h1:/pN59F/t3U7Q4FPzV88nzqf7Fp0qqCSL2KzhZaiKcKw= -github.com/moby/moby v27.5.1+incompatible/go.mod h1:fDXVQ6+S340veQPv35CzDahGBmHsiclFwfEygB/TWMc= -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/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= -github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= -github.com/natessilva/dag v0.0.0-20180124060714-7194b8dcc5c4 h1:dnMxwus89s86tI8rcGVp2HwZzlz7c5o92VOy7dSckBQ= -github.com/natessilva/dag v0.0.0-20180124060714-7194b8dcc5c4/go.mod h1:cojhOHk1gbMeklOyDP2oKKLftefXoJreOQGOrXk+Z38= -github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= -github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= -github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= -github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= -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/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= -github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= -github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= -github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= -github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= -github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= -github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= -github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= -github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= -github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/urfave/cli v1.22.16 h1:MH0k6uJxdwdeWQTwhSO42Pwr4YLrNLwBtg1MRgTqPdQ= -github.com/urfave/cli v1.22.16/go.mod h1:EeJR6BKodywf4zciqrdw6hpCPk68JO9z5LazXZMn5Po= -github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= -go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 h1:sbiXRNDSWJOTobXh5HyQKjq6wUC5tNybqjIqDpAY4CU= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0/go.mod h1:69uWxva0WgAA/4bu2Yy70SLDBwZXuQ6PbBpbsa5iZrQ= -go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= -go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 h1:OeNbIYk/2C15ckl7glBlOBp5+WlYsOElzTNmiPW/x60= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0/go.mod h1:7Bept48yIeqxP2OZ9/AqIpYS94h2or0aB4FypJTc8ZM= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.34.0 h1:BEj3SPM81McUZHYjRS5pEgNgnmzGJ5tRpU5krWnV8Bs= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.34.0/go.mod h1:9cKLGBDzI/F3NoHLQGm4ZrYdIHsvGt6ej6hUowxY0J4= -go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= -go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= -go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY= -go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg= -go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5JpUCaEqEI9o= -go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w= -go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= -go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= -go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4= -go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4= -go.starlark.net v0.0.0-20230525235612-a134d8f9ddca h1:VdD38733bfYv5tUZwEIskMM93VanwNIi5bIKnDrJdEY= -go.starlark.net v0.0.0-20230525235612-a134d8f9ddca/go.mod h1:jxU+3+j+71eXOW14274+SmmuW82qJzl6iZSeqEtTGds= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.36.0 h1:vWF2fRbw4qslQsQzgFqZff+BItCvGFQqKzKIzx1rmoA= -golang.org/x/net v0.36.0/go.mod h1:bFmbeoIPfrw4sMHNhb4J9f6+tPziuGjq7Jk/38fxi1I= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M= -golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= -golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -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/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= -golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= -golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= -golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto/googleapis/api v0.0.0-20250204164813-702378808489 h1:fCuMM4fowGzigT89NCIsW57Pk9k2D12MMi2ODn+Nk+o= -google.golang.org/genproto/googleapis/api v0.0.0-20250204164813-702378808489/go.mod h1:iYONQfRdizDB8JJBybql13nArx91jcUk7zCXEsOofM4= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250207221924-e9438ea467c6 h1:2duwAxN2+k0xLNpjnHTXoMUgnv6VPSp5fiqTuwSxjmI= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250207221924-e9438ea467c6/go.mod h1:8BS3B93F/U1juMFq9+EDk+qOT5CO1R9IzXxG3PTqiRk= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= -google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= -google.golang.org/grpc v1.70.0 h1:pWFv03aZoHzlRKHWicjsZytKAiYCtNS0dHbXnIdq7jQ= -google.golang.org/grpc v1.70.0/go.mod h1:ofIJqVKDXx/JiXrwr2IG4/zwdH9txy3IlF40RmcJSQw= -google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= -google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= -google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= -google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= -google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= -google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= -google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= -gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= -gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= -gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= -sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= -sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= -sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= diff --git a/.citools/golangci-lint/go.mod b/.citools/golangci-lint/go.mod index 93df12ec8c3..46e327dbd7f 100644 --- a/.citools/golangci-lint/go.mod +++ b/.citools/golangci-lint/go.mod @@ -34,19 +34,19 @@ require ( github.com/butuzov/mirror v1.2.0 // indirect github.com/catenacyber/perfsprint v0.7.1 // indirect github.com/ccojocar/zxcvbn-go v1.0.2 // indirect - github.com/cespare/xxhash/v2 v2.1.2 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/charithe/durationcheck v0.0.10 // indirect github.com/chavacava/garif v0.1.0 // indirect github.com/ckaznocha/intrange v0.2.1 // indirect github.com/curioswitch/go-reassign v0.2.0 // indirect github.com/daixiang0/gci v0.13.5 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/denis-tingaikin/go-header v0.5.0 // indirect github.com/ettle/strcase v0.2.0 // indirect github.com/fatih/color v1.18.0 // indirect github.com/fatih/structtag v1.2.0 // indirect github.com/firefart/nonamedreturns v1.0.5 // indirect - github.com/fsnotify/fsnotify v1.5.4 // indirect + github.com/fsnotify/fsnotify v1.8.0 // indirect github.com/fzipp/gocyclo v0.6.0 // indirect github.com/ghostiam/protogetter v0.3.8 // indirect github.com/go-critic/go-critic v0.11.5 // indirect @@ -61,7 +61,6 @@ require ( github.com/go-xmlfmt/xmlfmt v1.1.2 // indirect github.com/gobwas/glob v0.2.3 // indirect github.com/gofrs/flock v0.12.1 // indirect - github.com/golang/protobuf v1.5.3 // indirect github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a // indirect github.com/golangci/go-printf-func-name v0.1.0 // indirect github.com/golangci/gofmt v0.0.0-20240816233607-d8596aa466a9 // indirect @@ -71,7 +70,8 @@ require ( github.com/golangci/plugin-module-register v0.1.1 // indirect github.com/golangci/revgrep v0.5.3 // indirect github.com/golangci/unconvert v0.0.0-20240309020433-c5143eacb3ed // indirect - github.com/google/go-cmp v0.6.0 // indirect + github.com/google/go-cmp v0.7.0 // indirect + github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad // indirect github.com/gordonklaus/ineffassign v0.1.0 // indirect github.com/gostaticanalysis/analysisutil v0.7.1 // indirect github.com/gostaticanalysis/comment v1.4.2 // indirect @@ -96,31 +96,33 @@ require ( github.com/ldez/tagliatelle v0.5.0 // indirect github.com/leonklingele/grouper v1.1.2 // indirect github.com/macabu/inamedparam v0.1.3 // indirect - github.com/magiconair/properties v1.8.6 // indirect + github.com/magiconair/properties v1.8.7 // indirect github.com/maratori/testableexamples v1.0.0 // indirect github.com/maratori/testpackage v1.1.1 // indirect github.com/matoous/godox v0.0.0-20230222163458-006bad1f9d26 // indirect + github.com/matryer/is v1.4.1 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.16 // indirect - github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect github.com/mgechev/revive v1.5.0 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect - github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c // indirect github.com/moricho/tparallel v0.3.2 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/nakabonne/nestif v0.3.1 // indirect github.com/nishanths/exhaustive v0.12.0 // indirect github.com/nishanths/predeclared v0.2.2 // indirect github.com/nunnatsa/ginkgolinter v0.18.0 // indirect github.com/olekukonko/tablewriter v0.0.5 // indirect - github.com/pelletier/go-toml v1.9.5 // indirect + github.com/onsi/ginkgo/v2 v2.22.0 // indirect + github.com/onsi/gomega v1.36.1 // indirect github.com/pelletier/go-toml/v2 v2.2.3 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/polyfloyd/go-errorlint v1.6.0 // indirect - github.com/prometheus/client_golang v1.12.1 // indirect - github.com/prometheus/client_model v0.2.0 // indirect - github.com/prometheus/common v0.32.1 // indirect - github.com/prometheus/procfs v0.7.3 // indirect + github.com/prometheus/client_golang v1.21.0 // indirect + github.com/prometheus/client_model v0.6.1 // indirect + github.com/prometheus/common v0.62.0 // indirect + github.com/prometheus/procfs v0.15.1 // indirect github.com/quasilyte/go-ruleguard v0.4.3-0.20240823090925-0fe6f58b47b1 // indirect github.com/quasilyte/go-ruleguard/dsl v0.3.22 // indirect github.com/quasilyte/gogrep v0.5.0 // indirect @@ -131,6 +133,8 @@ require ( github.com/rogpeppe/go-internal v1.13.1 // indirect github.com/ryancurrah/gomodguard v1.3.5 // indirect github.com/ryanrolds/sqlclosecheck v0.5.1 // indirect + github.com/sagikazarmark/locafero v0.4.0 // indirect + github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/sanposhiho/wastedassign/v2 v2.0.7 // indirect github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 // indirect github.com/sashamelentyev/interfacebloat v1.1.0 // indirect @@ -141,18 +145,18 @@ require ( github.com/sivchari/containedctx v1.0.3 // indirect github.com/sivchari/tenv v1.12.1 // indirect github.com/sonatard/noctx v0.1.0 // indirect + github.com/sourcegraph/conc v0.3.0 // indirect github.com/sourcegraph/go-diff v0.7.0 // indirect github.com/spf13/afero v1.11.0 // indirect - github.com/spf13/cast v1.5.0 // indirect - github.com/spf13/cobra v1.8.1 // indirect - github.com/spf13/jwalterweatherman v1.1.0 // indirect - github.com/spf13/pflag v1.0.5 // indirect - github.com/spf13/viper v1.12.0 // indirect + github.com/spf13/cast v1.7.0 // indirect + github.com/spf13/cobra v1.9.1 // indirect + github.com/spf13/pflag v1.0.6 // indirect + github.com/spf13/viper v1.19.0 // indirect github.com/ssgreg/nlreturn/v2 v2.2.1 // indirect github.com/stbenjam/no-sprintf-host-port v0.1.1 // indirect github.com/stretchr/objx v0.5.2 // indirect - github.com/stretchr/testify v1.9.0 // indirect - github.com/subosito/gotenv v1.4.1 // indirect + github.com/stretchr/testify v1.10.0 // indirect + github.com/subosito/gotenv v1.6.0 // indirect github.com/tdakkota/asciicheck v0.2.0 // indirect github.com/tetafro/godot v1.4.18 // indirect github.com/timakin/bodyclose v0.0.0-20230421092635-574207250966 // indirect @@ -170,18 +174,18 @@ require ( gitlab.com/bosi/decorder v0.4.2 // indirect go-simpler.org/musttag v0.13.0 // indirect go-simpler.org/sloglint v0.7.2 // indirect - go.uber.org/atomic v1.7.0 // indirect go.uber.org/automaxprocs v1.6.0 // indirect - go.uber.org/multierr v1.6.0 // indirect - go.uber.org/zap v1.24.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + go.uber.org/zap v1.27.0 // indirect golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 // indirect golang.org/x/exp/typeparams v0.0.0-20240909161429-701f63a606c0 // indirect - golang.org/x/mod v0.22.0 // indirect - golang.org/x/sync v0.9.0 // indirect - golang.org/x/sys v0.27.0 // indirect - golang.org/x/text v0.18.0 // indirect - golang.org/x/tools v0.27.0 // indirect - google.golang.org/protobuf v1.34.2 // indirect + golang.org/x/mod v0.23.0 // indirect + golang.org/x/net v0.36.0 // 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 + golang.org/x/tools v0.30.0 // indirect + google.golang.org/protobuf v1.36.5 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/.citools/golangci-lint/go.sum b/.citools/golangci-lint/go.sum index c8205a75d73..bc25b962103 100644 --- a/.citools/golangci-lint/go.sum +++ b/.citools/golangci-lint/go.sum @@ -2,39 +2,6 @@ 4d63.com/gocheckcompilerdirectives v1.2.1/go.mod h1:yjDJSxmDTtIHHCqX0ufRYZDL6vQtMG7tJdKVeWwsqvs= 4d63.com/gochecknoglobals v0.2.1 h1:1eiorGsgHOFOuoOiJDy2psSrQbRdIHrlge0IJIkUgDc= 4d63.com/gochecknoglobals v0.2.1/go.mod h1:KRE8wtJB3CXCsb1xy421JfTHIIbmT3U5ruxw2Qu8fSU= -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= -cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= -cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= -cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= -cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= -cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= -cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= -cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= -cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= -cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= -cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= -cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= -cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= -cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= -cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= -cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= -cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= -cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= -cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= -cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= -cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= -cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= -cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= -cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= -cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= -cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= -cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= -cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= -cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= -dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/4meepo/tagalign v1.3.4 h1:P51VcvBnf04YkHzjfclN6BbsopfJR5rxs1n+5zHt+w8= github.com/4meepo/tagalign v1.3.4/go.mod h1:M+pnkHH2vG8+qhE5bVc/zeP7HS/j910Fwa9TUSyZVI0= github.com/Abirdcfly/dupword v0.1.3 h1:9Pa1NuAsZvpFPi9Pqkd93I7LIYRURj+A//dFd5tgBeE= @@ -45,10 +12,8 @@ github.com/Antonboom/nilnil v1.0.0 h1:n+v+B12dsE5tbAqRODXmEKfZv9j2KcTBrp+LkoM4HZ github.com/Antonboom/nilnil v1.0.0/go.mod h1:fDJ1FSFoLN6yoG65ANb1WihItf6qt9PJVTn/s2IrcII= github.com/Antonboom/testifylint v1.5.0 h1:dlUIsDMtCrZWUnvkaCz3quJCoIjaGi41GzjPBGkkJ8A= github.com/Antonboom/testifylint v1.5.0/go.mod h1:wqaJbu0Blb5Wag2wv7Z5xt+CIV+eVLxtGZrlK13z3AE= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c h1:pxW6RcqyfI9/kWtOwnv/G+AzdKuy2ZrqINhenH4HyNs= github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= -github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/Crocmagnon/fatcontext v0.5.2 h1:vhSEg8Gqng8awhPju2w7MKHqMlg4/NI+gSDHtR3xgwA= github.com/Crocmagnon/fatcontext v0.5.2/go.mod h1:87XhRMaInHP44Q7Tlc7jkgKKB7kZAOPiDkFMdKCC+74= github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 h1:sHglBQTwgx+rWPdisA5ynNEsoARbiCBOyGcJM4/OzsM= @@ -65,11 +30,6 @@ github.com/alecthomas/go-check-sumtype v0.2.0 h1:Bo+e4DFf3rs7ME9w/0SU/g6nmzJaphd github.com/alecthomas/go-check-sumtype v0.2.0/go.mod h1:WyYPfhfkdhyrdaligV6svFopZV8Lqdzn5pyVBaV6jhQ= github.com/alecthomas/repr v0.2.0 h1:HAzS41CIzNW5syS8Mf9UwXhNH1J9aix/BvDRf1Ml2Yk= github.com/alecthomas/repr v0.2.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= -github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/alexkohler/nakedret/v2 v2.0.5 h1:fP5qLgtwbx9EJE8dGEERT02YwS8En4r9nnZ71RK+EVU= github.com/alexkohler/nakedret/v2 v2.0.5/go.mod h1:bF5i0zF2Wo2o4X4USt9ntUWve6JbFv02Ff4vlkmS/VU= github.com/alexkohler/prealloc v1.0.0 h1:Hbq0/3fJPQhNkN0dR95AVrr6R7tou91y0uHG5pOcUuw= @@ -80,10 +40,6 @@ github.com/ashanbrown/forbidigo v1.6.0 h1:D3aewfM37Yb3pxHujIPSpTf6oQk9sc9WZi8ger github.com/ashanbrown/forbidigo v1.6.0/go.mod h1:Y8j9jy9ZYAEHXdu723cUlraTqbzjKF1MUyfOKL+AjcU= github.com/ashanbrown/makezero v1.1.1 h1:iCQ87C0V0vSyO+M9E/FZYbu65auqH0lnsOkf5FcB28s= github.com/ashanbrown/makezero v1.1.1/go.mod h1:i1bJLCRSCHOcOa9Y6MyF2FTfMZMFdHvxKHxgO5Z1axI= -github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= -github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= -github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= -github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= 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/bkielbasa/cyclop v1.2.3 h1:faIVMIGDIANuGPWH031CZJTi2ymOQBULs9H21HSMa5w= @@ -104,35 +60,25 @@ github.com/catenacyber/perfsprint v0.7.1 h1:PGW5G/Kxn+YrN04cRAZKC+ZuvlVwolYMrIyy github.com/catenacyber/perfsprint v0.7.1/go.mod h1:/wclWYompEyjUD2FuIIDVKNkqz7IgBIWXIH3V0Zol50= github.com/ccojocar/zxcvbn-go v1.0.2 h1:na/czXU8RrhXO4EZme6eQJLR4PzcGsahsBOAwU6I3Vg= github.com/ccojocar/zxcvbn-go v1.0.2/go.mod h1:g1qkXtUSvHP8lhHp5GrSmTz6uWALGRMQdw6Qnz/hi60= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= -github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +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/charithe/durationcheck v0.0.10 h1:wgw73BiocdBDQPik+zcEoBG/ob8uyBHf2iyoHGPf5w4= github.com/charithe/durationcheck v0.0.10/go.mod h1:bCWXb7gYRysD1CU3C+u4ceO49LoGOY1C1L6uouGNreQ= github.com/chavacava/garif v0.1.0 h1:2JHa3hbYf5D9dsgseMKAmc/MZ109otzgNFk5s87H9Pc= github.com/chavacava/garif v0.1.0/go.mod h1:XMyYCkEL58DF0oyW4qDjjnPWONs2HBqYKI+UIPD+Gww= -github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= -github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/ckaznocha/intrange v0.2.1 h1:M07spnNEQoALOJhwrImSrJLaxwuiQK+hA2DeajBlwYk= github.com/ckaznocha/intrange v0.2.1/go.mod h1:7NEhVyf8fzZO5Ds7CRaqPEm52Ut83hsTiL5zbER/HYk= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/curioswitch/go-reassign v0.2.0 h1:G9UZyOcpk/d7Gd6mqYgd8XYWFMw/znxwGDUstnC9DIo= github.com/curioswitch/go-reassign v0.2.0/go.mod h1:x6OpXuWvgfQaMGks2BZybTngWjT84hqJfKoO8Tt/Roc= github.com/daixiang0/gci v0.13.5 h1:kThgmH1yBmZSBCh1EJVxQ7JsHpm5Oms0AMed/0LaH4c= github.com/daixiang0/gci v0.13.5/go.mod h1:12etP2OniiIdP4q+kjUGrC/rUagga7ODbqsom5Eo5Yk= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/denis-tingaikin/go-header v0.5.0 h1:SRdnP5ZKvcO9KKRP1KJrhFR3RrlGuD+42t4429eC9k8= github.com/denis-tingaikin/go-header v0.5.0/go.mod h1:mMenU5bWrok6Wl2UsZjy+1okegmwQ3UgWl4V1D8gjlY= -github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/ettle/strcase v0.2.0 h1:fGNiVF21fHXpX1niBgk0aROov1LagYsOwV/xqKDKR/Q= github.com/ettle/strcase v0.2.0/go.mod h1:DajmHElDSaX76ITe3/VHVyMin4LWSJN5Z909Wp+ED1A= github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= @@ -141,30 +87,20 @@ github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4 github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94= github.com/firefart/nonamedreturns v1.0.5 h1:tM+Me2ZaXs8tfdDw3X6DOX++wMCOqzYUho6tUTYIdRA= github.com/firefart/nonamedreturns v1.0.5/go.mod h1:gHJjDqhGM4WyPt639SOZs+G89Ko7QKH5R5BhnO6xJhw= -github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= -github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps= -github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= -github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= +github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/fzipp/gocyclo v0.6.0 h1:lsblElZG7d3ALtGMx9fmxeTKZaLLpU8mET09yN4BBLo= github.com/fzipp/gocyclo v0.6.0/go.mod h1:rXPyn8fnlpa0R2csP/31uerbiVBugk5whMdlyaLkLoA= github.com/ghostiam/protogetter v0.3.8 h1:LYcXbYvybUyTIxN2Mj9h6rHrDZBDwZloPoKctWrFyJY= github.com/ghostiam/protogetter v0.3.8/go.mod h1:WZ0nw9pfzsgxuRsPOFQomgDVSWtDLJRfQJEhsGbmQMA= github.com/go-critic/go-critic v0.11.5 h1:TkDTOn5v7EEngMxu8KbuFqFR43USaaH8XRJLz1jhVYA= github.com/go-critic/go-critic v0.11.5/go.mod h1:wu6U7ny9PiaHaZHcvMDmdysMqvDem162Rh3zWTrqk8M= -github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= -github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= -github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= -github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI= github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow= -github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= 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-toolsmith/astcast v1.1.0 h1:+JN9xZV1A+Re+95pgnMgDboWNVnIMMQXwfBwLRPgSC8= @@ -194,36 +130,6 @@ github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= github.com/gofrs/flock v0.12.1 h1:MTLVXXHf8ekldpJk3AKicLij9MdwOWkZ+a/jHHZby9E= github.com/gofrs/flock v0.12.1/go.mod h1:9zxTsyu5xtJ9DK+1tFZyibEV7y3uwDxPPfbxeeHCoD0= -github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= -github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= -github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= -github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= -github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= -github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= -github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= -github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= -github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a h1:w8hkcTqaFpzKqonE9uMCefW1WDie15eSP/4MssdenaM= github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a/go.mod h1:ryS0uhF+x9jgbj/N71xsEqODy9BN81/GonCZiOzirOk= github.com/golangci/go-printf-func-name v0.1.0 h1:dVokQP+NMTO7jwO4bwsRwLWeudOVUPPyAKJuzv8pEJU= @@ -242,37 +148,15 @@ github.com/golangci/revgrep v0.5.3 h1:3tL7c1XBMtWHHqVpS5ChmiAAoe4PF/d5+ULzV9sLAz github.com/golangci/revgrep v0.5.3/go.mod h1:U4R/s9dlXZsg8uJmaR1GrloUr14D7qDl8gi2iPXJH8k= github.com/golangci/unconvert v0.0.0-20240309020433-c5143eacb3ed h1:IURFTjxeTfNFP0hTEi1YKjB/ub8zkpaOqFFMApi2EAs= github.com/golangci/unconvert v0.0.0-20240309020433-c5143eacb3ed/go.mod h1:XLXN8bNw4CGRPaqgl3bv/lhz7bsGPh4/xSaMTbo2vkQ= -github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= -github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20240827171923-fa2c70bbbfe5 h1:5iH8iuqE5apketRbSFBy+X1V0o+l+8NF1avt4HWl7cA= -github.com/google/pprof v0.0.0-20240827171923-fa2c70bbbfe5/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= -github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= -github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +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-20241210010833-40e02aabc2ad h1:a6HEuzUHeKH6hwfN/ZoQgRgVIWFJljSWa/zetS2WTvg= +github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= github.com/gordonklaus/ineffassign v0.1.0 h1:y2Gd/9I7MdY1oEIt+n+rowjBNDcLQq3RsH5hwJd0f9s= github.com/gordonklaus/ineffassign v0.1.0/go.mod h1:Qcp2HIAYhR7mNUVSIxZww3Guk4it82ghYcEXIAk+QT0= github.com/gostaticanalysis/analysisutil v0.7.1 h1:ZMCjoue3DtDWQ5WyU16YbjbQEQ3VuzwxALrpYd+HeKk= @@ -290,13 +174,10 @@ github.com/gostaticanalysis/testutil v0.4.0/go.mod h1:bLIoPefWXrRi/ssLFWX1dx7Rep github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY= github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= -github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jgautheron/goconst v1.7.1 h1:VpdAG7Ca7yvvJk5n8dMwQhfEZJh95kl/Hl9S1OI5Jkk= @@ -305,32 +186,18 @@ github.com/jingyugao/rowserrcheck v1.1.1 h1:zibz55j/MJtLsjP1OF4bSdgXxwL1b+Vn7Tjz github.com/jingyugao/rowserrcheck v1.1.1/go.mod h1:4yvlZSDb3IyDTUZJUmpZfm2Hwok+Dtp+nu2qOq+er9c= github.com/jjti/go-spancheck v0.6.2 h1:iYtoxqPMzHUPp7St+5yA8+cONdyXD3ug6KK15n7Pklk= github.com/jjti/go-spancheck v0.6.2/go.mod h1:+X7lvIrR5ZdUTkxFYqzJ0abr8Sb5LOo80uOhWNqIrYA= -github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= -github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= -github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= -github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/julz/importas v0.1.0 h1:F78HnrsjY3cR7j0etXy5+TU1Zuy7Xt08X/1aJnH5xXY= github.com/julz/importas v0.1.0/go.mod h1:oSFU2R4XK/P7kNBrnL/FEQlDGN1/6WoxXEjSSXO0DV0= github.com/karamaru-alpha/copyloopvar v1.1.0 h1:x7gNyKcC2vRBO1H2Mks5u1VxQtYvFiym7fCjIP8RPos= github.com/karamaru-alpha/copyloopvar v1.1.0/go.mod h1:u7CIfztblY0jZLOQZgH3oYsJzpC2A7S6u/lfgSXHy0k= github.com/kisielk/errcheck v1.8.0 h1:ZX/URYa7ilESY19ik/vBmCn6zdGQLxACwjAcWbHlYlg= github.com/kisielk/errcheck v1.8.0/go.mod h1:1kLL+jV4e+CFfueBmI1dSK2ADDyQnlrnrY/FqKluHJQ= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kkHAIKE/contextcheck v1.1.5 h1:CdnJh63tcDe53vG+RebdpdXJTc9atMgGqdx8LXxiilg= github.com/kkHAIKE/contextcheck v1.1.5/go.mod h1:O930cpht4xb1YQpK+1+AgoM3mFsvxr7uyFptcnWTYUA= -github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= +github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kulti/thelper v0.6.3 h1:ElhKf+AlItIu+xGnI990no4cE2+XaSu1ULymV2Yulxs= @@ -349,16 +216,17 @@ github.com/leonklingele/grouper v1.1.2 h1:o1ARBDLOmmasUaNDesWqWCIFH3u7hoFlM84Yrj github.com/leonklingele/grouper v1.1.2/go.mod h1:6D0M/HVkhs2yRKRFZUoGjeDy7EZTfFBE9gl4kjmIGkA= github.com/macabu/inamedparam v0.1.3 h1:2tk/phHkMlEL/1GNe/Yf6kkR/hkcUdAEY3L0hjYV1Mk= github.com/macabu/inamedparam v0.1.3/go.mod h1:93FLICAIk/quk7eaPPQvbzihUdn/QkGDwIZEoLtpH6I= -github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= -github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= +github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= +github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/maratori/testableexamples v1.0.0 h1:dU5alXRrD8WKSjOUnmJZuzdxWOEQ57+7s93SLMxb2vI= github.com/maratori/testableexamples v1.0.0/go.mod h1:4rhjL1n20TUTT4vdh3RDqSizKLyXp7K2u6HgraZCGzE= github.com/maratori/testpackage v1.1.1 h1:S58XVV5AD7HADMmD0fNnziNHqKvSdDuEKdPD1rNTU04= github.com/maratori/testpackage v1.1.1/go.mod h1:s4gRK/ym6AMrqpOa/kEbQTV4Q4jb7WeLZzVhVVVOQMc= github.com/matoous/godox v0.0.0-20230222163458-006bad1f9d26 h1:gWg6ZQ4JhDfJPqlo2srm/LN17lpybq15AryXIRcWYLE= github.com/matoous/godox v0.0.0-20230222163458-006bad1f9d26/go.mod h1:1BELzlh859Sh1c6+90blK8lbYy0kwQf1bYlBhBysy1s= -github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE= github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= +github.com/matryer/is v1.4.1 h1:55ehd8zaGABKLXQUe2awZ99BD/PTc2ls+KV/dXphgEQ= +github.com/matryer/is v1.4.1/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= @@ -367,23 +235,16 @@ github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= -github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mgechev/revive v1.5.0 h1:oaSmjA7rP8+HyoRuCgC531VHwnLH1AlJdjj+1AnQceQ= github.com/mgechev/revive v1.5.0/go.mod h1:L6T3H8EoerRO86c7WuGpvohIUmiploGiyoYbtIWFmV8= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= -github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c h1:cqn374mizHuIWj+OSJCajGr/phAmuMug9qIX3l9CflE= +github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/moricho/tparallel v0.3.2 h1:odr8aZVFA3NZrNybggMkYO3rgPRcqjeQUlBBFVxKHTI= github.com/moricho/tparallel v0.3.2/go.mod h1:OQ+K3b4Ln3l2TZveGCywybl68glfLEwFGqvnjok8b+U= -github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +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/nakabonne/nestif v0.3.1 h1:wm28nZjhQY5HyYPx+weN3Q65k6ilSBxDb8v5S81B81U= github.com/nakabonne/nestif v0.3.1/go.mod h1:9EtoZochLn5iUprVDmDjqGKPofoUEBL8U4Ngq6aY7OE= github.com/nishanths/exhaustive v0.12.0 h1:vIY9sALmw6T/yxiASewa4TQcFsVYZQQRUQJhKRf3Swg= @@ -394,10 +255,10 @@ github.com/nunnatsa/ginkgolinter v0.18.0 h1:ZXO1wKhPg3A6LpbN5dMuqwhfOjN5c3ous8Yd github.com/nunnatsa/ginkgolinter v0.18.0/go.mod h1:vPrWafSULmjMGCMsfGA908if95VnHQNAahvSBOjTuWs= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= -github.com/onsi/ginkgo/v2 v2.20.2 h1:7NVCeyIWROIAheY21RLS+3j2bb52W0W82tkberYytp4= -github.com/onsi/ginkgo/v2 v2.20.2/go.mod h1:K9gyxPIlb+aIvnZ8bd9Ak+YP18w3APlR+5coaZoE2ag= -github.com/onsi/gomega v1.34.2 h1:pNCwDkzrsv7MS9kpaQvVb1aVLahQXyJ/Tv5oAZMI3i8= -github.com/onsi/gomega v1.34.2/go.mod h1:v1xfxRgk0KIsG+QOdm7p8UosrOzPYRo60fd3B/1Dukc= +github.com/onsi/ginkgo/v2 v2.22.0 h1:Yed107/8DjTr0lKCNt7Dn8yQ6ybuDRQoMGrNFKzMfHg= +github.com/onsi/ginkgo/v2 v2.22.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo= +github.com/onsi/gomega v1.36.1 h1:bJDPBO7ibjxcbHMgSCoo4Yj18UWbKDlLwX1x9sybDcw= +github.com/onsi/gomega v1.36.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog= github.com/otiai10/copy v1.2.0/go.mod h1:rrF5dJ5F0t/EWSYODDu4j9/vEeYHMkc8jt0zJChqQWw= github.com/otiai10/copy v1.14.0 h1:dCI/t1iTdYGtkvCuBG2BgR6KZa83PTclw4U5n2wAllU= github.com/otiai10/copy v1.14.0/go.mod h1:ECfuL02W+/FkTWZWgQqXPWZgW9oeKCSQ5qVfSc4qc4w= @@ -405,42 +266,23 @@ github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJ github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs= github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo= github.com/otiai10/mint v1.3.1/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc= -github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= -github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= -github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/polyfloyd/go-errorlint v1.6.0 h1:tftWV9DE7txiFzPpztTAwyoRLKNj9gpVm2cg8/OwcYY= github.com/polyfloyd/go-errorlint v1.6.0/go.mod h1:HR7u8wuP1kb1NeN1zqTd1ZMlqUKPPHF+Id4vIPvDqVw= github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U= -github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= -github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= -github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= -github.com/prometheus/client_golang v1.12.1 h1:ZiaPsmm9uiBeaSMRznKsCDNtPCS0T3JVDGF+06gjBzk= -github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= -github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= -github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= -github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= -github.com/prometheus/common v0.32.1 h1:hWIdL3N2HoUx3B8j3YN9mWor0qhY/NlEKZEaXxuIRh4= -github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= -github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= -github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU= -github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/client_golang v1.21.0 h1:DIsaGmiaBkSangBgMtWdNfxbMNdku5IK6iNhrEqWvdA= +github.com/prometheus/client_golang v1.21.0/go.mod h1:U9NM32ykUErtVBxdvD3zfi+EuFkkaBvMb09mIfe0Zgg= +github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= +github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= +github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io= +github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I= +github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= +github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= github.com/quasilyte/go-ruleguard v0.4.3-0.20240823090925-0fe6f58b47b1 h1:+Wl/0aFp0hpuHM3H//KMft64WQ1yX9LdJY64Qm/gFCo= github.com/quasilyte/go-ruleguard v0.4.3-0.20240823090925-0fe6f58b47b1/go.mod h1:GJLgqsLeo4qgavUoL8JeGFNS7qcisx3awV/w9eWTmNI= github.com/quasilyte/go-ruleguard/dsl v0.3.22 h1:wd8zkOhSNr+I+8Qeciml08ivDt1pSXe60+5DqOpCjPE= @@ -456,7 +298,6 @@ github.com/raeperd/recvcheck v0.1.2/go.mod h1:n04eYkwIR0JbgD73wT8wL4JjPC3wm0nFtz github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= -github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= @@ -464,6 +305,10 @@ github.com/ryancurrah/gomodguard v1.3.5 h1:cShyguSwUEeC0jS7ylOiG/idnd1TpJ1LfHGpV github.com/ryancurrah/gomodguard v1.3.5/go.mod h1:MXlEPQRxgfPQa62O8wzK3Ozbkv9Rkqr+wKjSxTdsNJE= github.com/ryanrolds/sqlclosecheck v0.5.1 h1:dibWW826u0P8jNLsLN+En7+RqWWTYrjCB9fJfSfdyCU= github.com/ryanrolds/sqlclosecheck v0.5.1/go.mod h1:2g3dUjoS6AL4huFdv6wn55WpLIDjY7ZgUR4J8HOO/XQ= +github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= +github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= +github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= +github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= github.com/sanposhiho/wastedassign/v2 v2.0.7 h1:J+6nrY4VW+gC9xFzUc+XjPD3g3wF3je/NsJFwFK7Uxc= github.com/sanposhiho/wastedassign/v2 v2.0.7/go.mod h1:KyZ0MWTwxxBmfwn33zh3k1dmsbF2ud9pAAGfoLfjhtI= github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 h1:lZUw3E0/J3roVtGQ+SCrUrg3ON6NgVqpn3+iol9aGu4= @@ -478,9 +323,6 @@ github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c h1:W65qqJCIOVP4jpqP github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c/go.mod h1:/PevMnwAxekIXwN8qQyfc5gl2NlkB3CQlkizAbOkeBs= github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ= -github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sivchari/containedctx v1.0.3 h1:x+etemjbsh2fB5ewm5FeLNi5bUjK0V8n0RB+Wwfd0XE= @@ -489,31 +331,30 @@ github.com/sivchari/tenv v1.12.1 h1:+E0QzjktdnExv/wwsnnyk4oqZBUfuh89YMQT1cyuvSY= github.com/sivchari/tenv v1.12.1/go.mod h1:1LjSOUCc25snIr5n3DtGGrENhX3LuWefcplwVGC24mw= github.com/sonatard/noctx v0.1.0 h1:JjqOc2WN16ISWAjAk8M5ej0RfExEXtkEyExl2hLW+OM= github.com/sonatard/noctx v0.1.0/go.mod h1:0RvBxqY8D4j9cTTTWE8ylt2vqj2EPI8fHmrxHdsaZ2c= +github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= +github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= github.com/sourcegraph/go-diff v0.7.0 h1:9uLlrd5T46OXs5qpp8L/MTltk0zikUGi0sNNyCpA8G0= github.com/sourcegraph/go-diff v0.7.0/go.mod h1:iBszgVvyxdc8SFZ7gm69go2KDdt3ag071iBaWPF6cjs= github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= -github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= -github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= -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/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= -github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w= +github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= +github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.12.0 h1:CZ7eSOd3kZoaYDLbXnmzgQI5RlciuXBMA+18HwHRfZQ= -github.com/spf13/viper v1.12.0/go.mod h1:b6COn30jlNxbm/V2IqWiNWkJ+vZNiMNksliPCiuKtSI= +github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= +github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI= +github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg= github.com/ssgreg/nlreturn/v2 v2.2.1 h1:X4XDI7jstt3ySqGU86YGAURbxw3oTDPK9sPEi6YEwQ0= github.com/ssgreg/nlreturn/v2 v2.2.1/go.mod h1:E/iiPB78hV7Szg2YfRgyIrk1AD6JVMTRkkxBiELzh2I= github.com/stbenjam/no-sprintf-host-port v0.1.1 h1:tYugd/yrm1O0dV+ThCbaKZh195Dfm07ysF0U6JQXczc= github.com/stbenjam/no-sprintf-host-port v0.1.1/go.mod h1:TLhvtIvONRzdmkFiio4O8LHsN9N74I+PhRquPsxpL0I= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= @@ -521,10 +362,10 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ 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.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs= -github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= +github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/tdakkota/asciicheck v0.2.0 h1:o8jvnUANo0qXtnslk2d3nMKTFNlOnJjRrNcj0j9qkHM= github.com/tdakkota/asciicheck v0.2.0/go.mod h1:Qb7Y9EgjCLJGup51gDHFzbI08/gbGhL/UVhYIPWG2rg= github.com/tenntenn/modver v1.0.1 h1:2klLppGhDgzJrScMpkj9Ujy3rXPUspSjAcev9tSEBgA= @@ -558,7 +399,6 @@ github.com/yeya24/promlinter v0.3.0/go.mod h1:cDfJQQYv9uYciW60QT0eeHlFodotkYZlL+ github.com/ykadowak/zerologlint v0.1.5 h1:Gy/fMz1dFQN9JZTPjv1hxEk+sRWm05row04Yoolgdiw= github.com/ykadowak/zerologlint v0.1.5/go.mod h1:KaUskqF3e/v59oPmdq1U1DnKcuHokl2/K1U4pmIELKg= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= @@ -572,63 +412,25 @@ go-simpler.org/musttag v0.13.0 h1:Q/YAW0AHvaoaIbsPj3bvEI5/QFP7w696IMUpnKXQfCE= go-simpler.org/musttag v0.13.0/go.mod h1:FTzIGeK6OkKlUDVpj0iQUXZLUO1Js9+mvykDQy9C5yM= go-simpler.org/sloglint v0.7.2 h1:Wc9Em/Zeuu7JYpl+oKoYOsQSy2X560aVueCW/m6IijY= go-simpler.org/sloglint v0.7.2/go.mod h1:US+9C80ppl7VsThQclkM7BkCHQAzuz8kHLsW3ppuluo= -go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= -go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= -go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= -go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs= go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8= -go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= -go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= -go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= -go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= -go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= -go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= -golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= -golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= -golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= -golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= -golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk= golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY= golang.org/x/exp/typeparams v0.0.0-20220428152302-39d4317da171/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= golang.org/x/exp/typeparams v0.0.0-20230203172020-98cc5a0785f9/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= golang.org/x/exp/typeparams v0.0.0-20240909161429-701f63a606c0 h1:bVwtbF629Xlyxk6xLQq2TDYmqP0uiWaet5LwRebuY0k= golang.org/x/exp/typeparams v0.0.0-20240909161429-701f63a606c0/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= -golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= -golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= -golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= -golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= -golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= -golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= @@ -639,100 +441,36 @@ golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91 golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI= golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= -golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM= +golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo= -golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/net v0.36.0 h1:vWF2fRbw4qslQsQzgFqZff+BItCvGFQqKzKIzx1rmoA= +golang.org/x/net v0.36.0/go.mod h1:bFmbeoIPfrw4sMHNhb4J9f6+tPziuGjq7Jk/38fxi1I= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ= -golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +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.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -740,12 +478,9 @@ golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211105183446-c75c47738b0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -755,17 +490,15 @@ golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= -golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +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/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= @@ -773,57 +506,16 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= -golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= -golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= +golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190321232350-e250d351ecad/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190910044552-dd2b5c81c578/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/tools v0.0.0-20200324003944-a576cf524670/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= golang.org/x/tools v0.0.0-20200329025819-fd4102a86c65/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= -golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= -golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200724022722-7017fd6b1305/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200820010801-b793a1359eac/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20201023174141-c8cfbd0f21e6/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.1-0.20210205202024-ef80cdb6ec6d/go.mod h1:9bzcO0MWcOuT0tm1iBGzDVPshzfwoVvREIui8C+MHqU= @@ -837,121 +529,28 @@ golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA= golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k= golang.org/x/tools v0.5.0/go.mod h1:N+Kgy78s5I24c24dU8OfWNEotWjutIs8SnJvn5IDq+k= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.27.0 h1:qEKojBykQkQ4EynWy4S8Weg69NumxKdn40Fce3uc/8o= -golang.org/x/tools v0.27.0/go.mod h1:sUi0ZgbwW9ZPAq26Ekut+weQPR5eIM6GQLQ1Yjm1H0Q= +golang.org/x/tools v0.30.0 h1:BgcpHewrV5AUp2G9MebG4XPFI1E2W41zU1SaqVA9vJY= +golang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= -google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= -google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= -google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= -google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= -google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= -google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= -google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= -google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= -google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= -google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= -google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= -google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= -google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= -google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= -google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= -google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= -google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= -google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= -gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= +google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.5.1 h1:4bH5o3b5ZULQ4UrBmP+63W9r7qIkqJClEA9ko5YKx+I= honnef.co/go/tools v0.5.1/go.mod h1:e9irvo83WDG9/irijV44wr3tbhcFeRnfpVlRqVwpzMs= mvdan.cc/gofumpt v0.7.0 h1:bg91ttqXmi9y2xawvkuMXyvAA/1ZGJqYAEGjXuP0JXU= mvdan.cc/gofumpt v0.7.0/go.mod h1:txVFJy/Sc/mvaycET54pV8SW8gWxTlUuGHVEcncmNUo= mvdan.cc/unparam v0.0.0-20240528143540-8a5130ca722f h1:lMpcwN6GxNbWtbpI1+xzFLSW8XzX0u72NttUGVFjO3U= mvdan.cc/unparam v0.0.0-20240528143540-8a5130ca722f/go.mod h1:RSLa7mKKCNeTTMHBw5Hsy2rfJmd6O2ivt9Dw9ZqCQpQ= -rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= -rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= -rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/.citools/jb/go.mod b/.citools/jb/go.mod index a8df6197a17..ab4dca4ed3d 100644 --- a/.citools/jb/go.mod +++ b/.citools/jb/go.mod @@ -8,7 +8,7 @@ require ( github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - github.com/fatih/color v1.17.0 // indirect + github.com/fatih/color v1.18.0 // indirect github.com/jsonnet-bundler/jsonnet-bundler v0.5.1 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect diff --git a/.citools/jb/go.sum b/.citools/jb/go.sum index fa93a05ce6a..e8928e48705 100644 --- a/.citools/jb/go.sum +++ b/.citools/jb/go.sum @@ -10,8 +10,8 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= -github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4= -github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI= +github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= +github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= github.com/jsonnet-bundler/jsonnet-bundler v0.5.1 h1:eUd6EA1Qzz73Q4NLNLOrNkMb96+6NTTERbX9lqaxVwk= github.com/jsonnet-bundler/jsonnet-bundler v0.5.1/go.mod h1:Qrdw/7mOFS2SKCOALKFfEH8gdvXJi8XZjw9g5ilpf4I= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= diff --git a/.citools/lefthook/go.mod b/.citools/lefthook/go.mod index 2fbfd58e665..2a4ace29be4 100644 --- a/.citools/lefthook/go.mod +++ b/.citools/lefthook/go.mod @@ -11,7 +11,7 @@ require ( github.com/charmbracelet/lipgloss v0.6.0 // indirect github.com/creack/pty v1.1.18 // indirect github.com/evilmartians/lefthook v1.4.8 // indirect - github.com/fatih/color v1.17.0 // indirect + github.com/fatih/color v1.18.0 // indirect github.com/fsnotify/fsnotify v1.8.0 // indirect github.com/gobwas/glob v0.2.3 // indirect github.com/google/go-cmp v0.7.0 // indirect diff --git a/.citools/lefthook/go.sum b/.citools/lefthook/go.sum index 1b0fbdb81ce..f93f56568c1 100644 --- a/.citools/lefthook/go.sum +++ b/.citools/lefthook/go.sum @@ -16,8 +16,8 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/evilmartians/lefthook v1.4.8 h1:8FmXWtfFiEZw3w18JbhVrp3g+Iy/j2XEo6gcC25+4KA= github.com/evilmartians/lefthook v1.4.8/go.mod h1:anwwu2QiCEnsOCBHfRgGOB3/sd9FMVNhmY8l9DDQAG8= -github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4= -github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI= +github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= +github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= diff --git a/.citools/swagger/go.mod b/.citools/swagger/go.mod index 80a7feab758..3e4a0cc8963 100644 --- a/.citools/swagger/go.mod +++ b/.citools/swagger/go.mod @@ -5,12 +5,13 @@ go 1.24.1 tool github.com/go-swagger/go-swagger/cmd/swagger require ( + dario.cat/mergo v1.0.1 // indirect github.com/Masterminds/goutils v1.1.1 // indirect - github.com/Masterminds/semver/v3 v3.2.1 // indirect - github.com/Masterminds/sprig/v3 v3.2.3 // indirect + github.com/Masterminds/semver/v3 v3.3.0 // indirect + github.com/Masterminds/sprig/v3 v3.3.0 // indirect github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect - github.com/fsnotify/fsnotify v1.7.0 // indirect + github.com/fsnotify/fsnotify v1.8.0 // indirect github.com/go-openapi/analysis v0.23.0 // indirect github.com/go-openapi/errors v0.22.0 // indirect github.com/go-openapi/inflect v0.21.0 // indirect @@ -23,11 +24,11 @@ require ( github.com/go-openapi/swag v0.23.0 // indirect github.com/go-openapi/validate v0.24.0 // indirect github.com/go-swagger/go-swagger v0.30.6-0.20240310114303-db51e79a0e37 // indirect + github.com/google/go-cmp v0.7.0 // indirect github.com/google/uuid v1.6.0 // indirect github.com/gorilla/handlers v1.5.2 // indirect github.com/hashicorp/hcl v1.0.0 // indirect - github.com/huandu/xstrings v1.4.0 // indirect - github.com/imdario/mergo v0.3.16 // indirect + github.com/huandu/xstrings v1.5.0 // indirect github.com/jessevdk/go-flags v1.5.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/kr/pretty v0.3.1 // indirect @@ -35,30 +36,31 @@ require ( github.com/magiconair/properties v1.8.7 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect - github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/oklog/ulid v1.3.1 // indirect - github.com/pelletier/go-toml/v2 v2.1.1 // indirect - github.com/rogpeppe/go-internal v1.12.0 // indirect + github.com/pelletier/go-toml/v2 v2.2.3 // indirect + github.com/rogpeppe/go-internal v1.13.1 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect - github.com/shopspring/decimal v1.3.1 // indirect + github.com/shopspring/decimal v1.4.0 // indirect github.com/sourcegraph/conc v0.3.0 // indirect github.com/spf13/afero v1.11.0 // indirect - github.com/spf13/cast v1.6.0 // indirect - github.com/spf13/pflag v1.0.5 // indirect - github.com/spf13/viper v1.18.2 // indirect + github.com/spf13/cast v1.7.0 // indirect + github.com/spf13/pflag v1.0.6 // indirect + github.com/spf13/viper v1.19.0 // indirect + github.com/stretchr/testify v1.10.0 // indirect github.com/subosito/gotenv v1.6.0 // indirect github.com/toqueteos/webbrowser v1.2.0 // indirect - go.mongodb.org/mongo-driver v1.14.0 // indirect + go.mongodb.org/mongo-driver v1.16.1 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/crypto v0.31.0 // indirect - golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 // indirect - golang.org/x/mod v0.17.0 // indirect - golang.org/x/sync v0.10.0 // indirect - golang.org/x/sys v0.28.0 // indirect - golang.org/x/text v0.21.0 // indirect - golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect + golang.org/x/crypto v0.35.0 // indirect + golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 // indirect + golang.org/x/mod v0.23.0 // 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 + golang.org/x/tools v0.30.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/.citools/swagger/go.sum b/.citools/swagger/go.sum index 53aa93f8a54..8c8679c2371 100644 --- a/.citools/swagger/go.sum +++ b/.citools/swagger/go.sum @@ -1,14 +1,14 @@ +dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= +dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= -github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= -github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0= -github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= -github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA= -github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM= +github.com/Masterminds/semver/v3 v3.3.0 h1:B8LGeaivUe71a5qox1ICM/JLl0NqZSW5CHyL+hmvYS0= +github.com/Masterminds/semver/v3 v3.3.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= +github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs= +github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -16,8 +16,8 @@ github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2 github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= -github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= -github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= +github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= +github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/go-openapi/analysis v0.23.0 h1:aGday7OWupfMs+LbmLZG4k0MYXIANxcuBTYUC03zFCU= github.com/go-openapi/analysis v0.23.0/go.mod h1:9mz9ZWaSlV8TvjQHLl2mUW2PbZtemkE8yA5v22ohupo= github.com/go-openapi/errors v0.22.0 h1:c4xY/OLxUBSTiepAg3j/MHuAv5mJhnf53LLMWFB+u/w= @@ -42,21 +42,16 @@ github.com/go-openapi/validate v0.24.0 h1:LdfDKwNbpB6Vn40xhTdNZAnfLECL81w+VX3Bum github.com/go-openapi/validate v0.24.0/go.mod h1:iyeX1sEufmv3nPbBdX3ieNviWnOZaJ1+zquzJEf2BAQ= github.com/go-swagger/go-swagger v0.30.6-0.20240310114303-db51e79a0e37 h1:KFcZmKdZmapAog2+eL1buervAYrYolBZk7fMecPPDmo= github.com/go-swagger/go-swagger v0.30.6-0.20240310114303-db51e79a0e37/go.mod h1:i1/E+d8iPNReSE7y04FaVu5OPKB3il5cn+T1Egogg3I= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +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/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyEE= github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= -github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU= -github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= -github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= -github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= -github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= +github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI= +github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/jessevdk/go-flags v1.5.0 h1:1jKYvbxEjfUl0fmqTCOfonvskHHXMjBySTLW4y9LFvc= github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= @@ -69,113 +64,69 @@ github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0V github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= -github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= -github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= -github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c h1:cqn374mizHuIWj+OSJCajGr/phAmuMug9qIX3l9CflE= +github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= -github.com/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI= -github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= +github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= +github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= -github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= -github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= -github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= -github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= -github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= +github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= -github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= -github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= -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/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ= -github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk= -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.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -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.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w= +github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= +github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI= +github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/toqueteos/webbrowser v1.2.0 h1:tVP/gpK69Fx+qMJKsLE7TD8LuGWPnEV71wBN9rrstGQ= github.com/toqueteos/webbrowser v1.2.0/go.mod h1:XWoZq4cyp9WeUeak7w7LXRUQf1F1ATJMir8RTqb4ayM= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -go.mongodb.org/mongo-driver v1.14.0 h1:P98w8egYRjYe3XDjxhYJagTokP/H6HzlsnojRgZRd80= -go.mongodb.org/mongo-driver v1.14.0/go.mod h1:Vzb0Mk/pa7e6cWw85R4F/endUC3u0U9jGcNU603k65c= +go.mongodb.org/mongo-driver v1.16.1 h1:rIVLL3q0IHM39dvE+z2ulZLp9ENZKThVfuvN/IiN4l8= +go.mongodb.org/mongo-driver v1.16.1/go.mod h1:oB6AhJQvFQL4LEHyXi6aJzQJtBiTQHiAd83l0GdFaiw= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= -golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= -golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= -golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 h1:LfspQV/FYTatPTr/3HzIcmiUFH7PGP+OQ6mgDYo3yuQ= -golang.org/x/exp v0.0.0-20240222234643-814bf88cf225/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= -golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= -golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs= +golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ= +golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk= +golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY= +golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM= +golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= +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.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= -golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= -golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= -golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +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= +golang.org/x/tools v0.30.0 h1:BgcpHewrV5AUp2G9MebG4XPFI1E2W41zU1SaqVA9vJY= +golang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index bdf666d3fc6..247bc556cdb 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -768,6 +768,7 @@ embed.go @grafana/grafana-as-code /.github/workflows/alerting-swagger-gen.yml @grafana/alerting-backend /.github/workflows/alerting-update-module.yml @grafana/alerting-backend /.github/workflows/auto-milestone.yml @grafana/grafana-developer-enablement-squad +/.github/workflows/backend-unit-tests.yml @grafana/grafana-backend-group /.github/workflows/backport.yml @grafana/grafana-developer-enablement-squad /.github/workflows/bump-version.yml @grafana/grafana-developer-enablement-squad /.github/workflows/close-milestone.yml @grafana/grafana-developer-enablement-squad @@ -798,7 +799,6 @@ embed.go @grafana/grafana-as-code /.github/workflows/pr-lint-build-docs.yml @grafana/docs-tooling /.github/workflows/pr-patch-check.yml @grafana/grafana-developer-enablement-squad /.github/workflows/pr-test-integration.yml @grafana/grafana-backend-group -/.github/workflows/pr-backend-unit-tests.yml @grafana/grafana-backend-group /.github/workflows/pr-backend-coverage.yml @grafana/grafana-backend-group /.github/workflows/sync-mirror.yml @grafana/grafana-developer-enablement-squad /.github/workflows/publish-technical-documentation-next.yml @grafana/docs-tooling diff --git a/.github/workflows/pr-backend-unit-tests.yml b/.github/workflows/backend-unit-tests.yml similarity index 96% rename from .github/workflows/pr-backend-unit-tests.yml rename to .github/workflows/backend-unit-tests.yml index f81b9c8f6ac..e9412ca9a67 100644 --- a/.github/workflows/pr-backend-unit-tests.yml +++ b/.github/workflows/backend-unit-tests.yml @@ -5,6 +5,13 @@ on: paths-ignore: - 'docs/**' - '**/*.md' + push: + branches: + - main + - release-*.*.* + paths-ignore: + - 'docs/**' + - '**/*.md' concurrency: group: ${{ github.workflow }}-${{ github.ref }} diff --git a/.github/workflows/pr-k8s-codegen-check.yml b/.github/workflows/pr-k8s-codegen-check.yml index 71dd29b6354..ae5b6d3893a 100644 --- a/.github/workflows/pr-k8s-codegen-check.yml +++ b/.github/workflows/pr-k8s-codegen-check.yml @@ -9,6 +9,7 @@ on: - "pkg/aggregator/apis/**" - "pkg/apimachinery/apis/**" - "hack/**" + - "apps/**" - "*.sum" jobs: diff --git a/.prettierignore b/.prettierignore index 23d9231bc49..b9f59a37afb 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,6 +1,7 @@ .git .github .yarn +.bingo build compiled data diff --git a/Dockerfile b/Dockerfile index 76d1101e0e3..bb334ff8dfb 100644 --- a/Dockerfile +++ b/Dockerfile @@ -65,7 +65,6 @@ COPY .citools/cue .citools/cue COPY .citools/cog .citools/cog COPY .citools/lefthook .citools/lefthook COPY .citools/jb .citools/jb -COPY .citools/drone .citools/drone COPY .citools/golangci-lint .citools/golangci-lint COPY .citools/swagger ./citools/swagger diff --git a/apps/advisor/pkg/app/checkscheduler/checkscheduler.go b/apps/advisor/pkg/app/checkscheduler/checkscheduler.go index 2bcb8d43f19..cdca40cbd77 100644 --- a/apps/advisor/pkg/app/checkscheduler/checkscheduler.go +++ b/apps/advisor/pkg/app/checkscheduler/checkscheduler.go @@ -14,6 +14,7 @@ import ( advisorv0alpha1 "github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1" "github.com/grafana/grafana/apps/advisor/pkg/app/checkregistry" "github.com/grafana/grafana/apps/advisor/pkg/app/checks" + "github.com/grafana/grafana/pkg/infra/log" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/klog/v2" ) @@ -30,6 +31,7 @@ type Runner struct { evaluationInterval time.Duration maxHistory int namespace string + log log.Logger } // NewRunner creates a new Runner. @@ -66,22 +68,25 @@ func New(cfg app.Config) (app.Runnable, error) { evaluationInterval: evalInterval, maxHistory: maxHistory, namespace: namespace, + log: log.New("advisor.checkscheduler"), }, nil } func (r *Runner) Run(ctx context.Context) error { lastCreated, err := r.checkLastCreated(ctx) if err != nil { - return err - } - - // do an initial creation if necessary - if lastCreated.IsZero() { - err = r.createChecks(ctx) - if err != nil { - klog.Error("Error creating new check reports", "error", err) - } else { - lastCreated = time.Now() + r.log.Error("Error getting last check creation time", "error", err) + // Wait for interval to create the next scheduled check + lastCreated = time.Now() + } else { + // do an initial creation if necessary + if lastCreated.IsZero() { + err = r.createChecks(ctx) + if err != nil { + klog.Error("Error creating new check reports", "error", err) + } else { + lastCreated = time.Now() + } } } diff --git a/apps/advisor/pkg/app/checkscheduler/checkscheduler_test.go b/apps/advisor/pkg/app/checkscheduler/checkscheduler_test.go index 4fd5876b9ff..23ce113df4a 100644 --- a/apps/advisor/pkg/app/checkscheduler/checkscheduler_test.go +++ b/apps/advisor/pkg/app/checkscheduler/checkscheduler_test.go @@ -11,28 +11,35 @@ import ( "github.com/grafana/grafana-app-sdk/resource" advisorv0alpha1 "github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1" "github.com/grafana/grafana/apps/advisor/pkg/app/checks" + "github.com/grafana/grafana/pkg/infra/log" "github.com/stretchr/testify/assert" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -func TestRunner_Run_ErrorOnList(t *testing.T) { - mockCheckService := &MockCheckService{} - mockClient := &MockClient{ - listFunc: func(ctx context.Context, namespace string, options resource.ListOptions) (resource.ListObject, error) { - return nil, errors.New("list error") - }, - createFunc: func(ctx context.Context, id resource.Identifier, obj resource.Object, opts resource.CreateOptions) (resource.Object, error) { - return &advisorv0alpha1.Check{}, nil - }, - } +func TestRunner_Run(t *testing.T) { + t.Run("does not crash when error on list", func(t *testing.T) { + mockCheckService := &MockCheckService{} + mockClient := &MockClient{ + listFunc: func(ctx context.Context, namespace string, options resource.ListOptions) (resource.ListObject, error) { + return nil, errors.New("list error") + }, + createFunc: func(ctx context.Context, id resource.Identifier, obj resource.Object, opts resource.CreateOptions) (resource.Object, error) { + return &advisorv0alpha1.Check{}, nil + }, + } - runner := &Runner{ - checkRegistry: mockCheckService, - client: mockClient, - } + runner := &Runner{ + checkRegistry: mockCheckService, + client: mockClient, + log: log.NewNopLogger(), + evaluationInterval: 1 * time.Hour, + } - err := runner.Run(context.Background()) - assert.Error(t, err) + ctx, cancel := context.WithCancel(context.Background()) + cancel() + err := runner.Run(ctx) + assert.ErrorAs(t, err, &context.Canceled) + }) } func TestRunner_checkLastCreated_ErrorOnList(t *testing.T) { @@ -44,6 +51,7 @@ func TestRunner_checkLastCreated_ErrorOnList(t *testing.T) { runner := &Runner{ client: mockClient, + log: log.NewNopLogger(), } lastCreated, err := runner.checkLastCreated(context.Background()) @@ -68,6 +76,7 @@ func TestRunner_createChecks_ErrorOnCreate(t *testing.T) { runner := &Runner{ checkRegistry: mockCheckService, client: mockClient, + log: log.NewNopLogger(), } err := runner.createChecks(context.Background()) @@ -91,6 +100,7 @@ func TestRunner_createChecks_Success(t *testing.T) { runner := &Runner{ checkRegistry: mockCheckService, client: mockClient, + log: log.NewNopLogger(), } err := runner.createChecks(context.Background()) @@ -106,6 +116,7 @@ func TestRunner_cleanupChecks_ErrorOnList(t *testing.T) { runner := &Runner{ client: mockClient, + log: log.NewNopLogger(), } err := runner.cleanupChecks(context.Background()) @@ -126,6 +137,7 @@ func TestRunner_cleanupChecks_WithinMax(t *testing.T) { runner := &Runner{ client: mockClient, + log: log.NewNopLogger(), } err := runner.cleanupChecks(context.Background()) @@ -155,6 +167,7 @@ func TestRunner_cleanupChecks_ErrorOnDelete(t *testing.T) { runner := &Runner{ client: mockClient, maxHistory: defaultMaxHistory, + log: log.NewNopLogger(), } err := runner.cleanupChecks(context.Background()) assert.ErrorContains(t, err, "delete error") @@ -190,6 +203,7 @@ func TestRunner_cleanupChecks_Success(t *testing.T) { runner := &Runner{ client: mockClient, maxHistory: defaultMaxHistory, + log: log.NewNopLogger(), } err := runner.cleanupChecks(context.Background()) assert.NoError(t, err) diff --git a/apps/advisor/pkg/app/checktyperegisterer/checktyperegisterer.go b/apps/advisor/pkg/app/checktyperegisterer/checktyperegisterer.go index 2ed38e51ecb..3e563b3f88f 100644 --- a/apps/advisor/pkg/app/checktyperegisterer/checktyperegisterer.go +++ b/apps/advisor/pkg/app/checktyperegisterer/checktyperegisterer.go @@ -3,6 +3,7 @@ package checktyperegisterer import ( "context" "fmt" + "time" "github.com/grafana/grafana-app-sdk/app" "github.com/grafana/grafana-app-sdk/k8s" @@ -10,6 +11,7 @@ import ( advisorv0alpha1 "github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1" "github.com/grafana/grafana/apps/advisor/pkg/app/checkregistry" "github.com/grafana/grafana/apps/advisor/pkg/app/checks" + "github.com/grafana/grafana/pkg/infra/log" "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -21,6 +23,9 @@ type Runner struct { checkRegistry checkregistry.CheckService client resource.Client namespace string + log log.Logger + retryAttempts int + retryDelay time.Duration } // NewRunner creates a new Runner. @@ -47,9 +52,32 @@ func New(cfg app.Config) (app.Runnable, error) { checkRegistry: checkRegistry, client: client, namespace: namespace, + log: log.New("advisor.checktyperegisterer"), + retryAttempts: 3, + retryDelay: time.Second * 5, }, nil } +func (r *Runner) createOrUpdate(ctx context.Context, obj resource.Object) error { + id := obj.GetStaticMetadata().Identifier() + _, err := r.client.Create(ctx, id, obj, resource.CreateOptions{}) + if err != nil { + if errors.IsAlreadyExists(err) { + // Already exists, update + r.log.Debug("Check type already exists, updating", "identifier", id) + _, err = r.client.Update(ctx, id, obj, resource.UpdateOptions{}) + if err != nil { + // Ignore the error, it's probably due to a race condition + r.log.Error("Error updating check type", "error", err) + } + return nil + } + return err + } + r.log.Debug("Check type registered successfully", "identifier", id) + return nil +} + func (r *Runner) Run(ctx context.Context) error { for _, t := range r.checkRegistry.Checks() { steps := t.Steps() @@ -72,19 +100,19 @@ func (r *Runner) Run(ctx context.Context) error { Steps: stepTypes, }, } - id := obj.GetStaticMetadata().Identifier() - _, err := r.client.Create(ctx, id, obj, resource.CreateOptions{}) - if err != nil { - if errors.IsAlreadyExists(err) { - // Already exists, update - _, err = r.client.Update(ctx, id, obj, resource.UpdateOptions{}) - if err != nil { - return err + for i := 0; i < r.retryAttempts; i++ { + err := r.createOrUpdate(ctx, obj) + if err != nil { + r.log.Error("Error creating check type, retrying", "error", err, "attempt", i+1) + if i == r.retryAttempts-1 { + r.log.Error("Unable to register check type") } else { - continue + time.Sleep(r.retryDelay) } + continue } - return err + r.log.Debug("Check type registered successfully", "check_type", t.ID()) + break } } return nil diff --git a/apps/advisor/pkg/app/checktyperegisterer/checktyperegisterer_test.go b/apps/advisor/pkg/app/checktyperegisterer/checktyperegisterer_test.go index 97fc7742504..4d4bab9adf3 100644 --- a/apps/advisor/pkg/app/checktyperegisterer/checktyperegisterer_test.go +++ b/apps/advisor/pkg/app/checktyperegisterer/checktyperegisterer_test.go @@ -9,6 +9,7 @@ import ( "github.com/grafana/grafana-app-sdk/resource" advisorv0alpha1 "github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1" "github.com/grafana/grafana/apps/advisor/pkg/app/checks" + "github.com/grafana/grafana/pkg/infra/log" k8sErrs "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime/schema" ) @@ -117,7 +118,10 @@ func TestCheckTypesRegisterer_Run(t *testing.T) { createFunc: tt.createFunc, updateFunc: tt.updateFunc, }, - namespace: "custom-namespace", + namespace: "custom-namespace", + log: log.New("test"), + retryAttempts: 1, + retryDelay: 0, } err := r.Run(context.Background()) if err != nil { diff --git a/apps/alerting/notifications/go.mod b/apps/alerting/notifications/go.mod index 6f0c3f390d0..17415e7048e 100644 --- a/apps/alerting/notifications/go.mod +++ b/apps/alerting/notifications/go.mod @@ -49,6 +49,8 @@ require ( github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/onsi/ginkgo/v2 v2.22.0 // indirect + github.com/onsi/gomega v1.36.1 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/prometheus/client_golang v1.21.0 // indirect github.com/prometheus/client_model v0.6.1 // indirect diff --git a/apps/alerting/notifications/go.sum b/apps/alerting/notifications/go.sum index 8df3afad699..f613d6fe414 100644 --- a/apps/alerting/notifications/go.sum +++ b/apps/alerting/notifications/go.sum @@ -109,10 +109,10 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/onsi/ginkgo/v2 v2.21.0 h1:7rg/4f3rB88pb5obDgNZrNHrQ4e6WpjonchcpuBRnZM= -github.com/onsi/ginkgo/v2 v2.21.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo= -github.com/onsi/gomega v1.35.1 h1:Cwbd75ZBPxFSuZ6T+rN/WCb/gOc6YgFBXLlZLhC7Ds4= -github.com/onsi/gomega v1.35.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog= +github.com/onsi/ginkgo/v2 v2.22.0 h1:Yed107/8DjTr0lKCNt7Dn8yQ6ybuDRQoMGrNFKzMfHg= +github.com/onsi/ginkgo/v2 v2.22.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo= +github.com/onsi/gomega v1.36.1 h1:bJDPBO7ibjxcbHMgSCoo4Yj18UWbKDlLwX1x9sybDcw= +github.com/onsi/gomega v1.36.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog= 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= diff --git a/apps/dashboard/go.mod b/apps/dashboard/go.mod index 8c2285debc3..a851c6e064b 100644 --- a/apps/dashboard/go.mod +++ b/apps/dashboard/go.mod @@ -12,7 +12,7 @@ require ( ) require ( - github.com/BurntSushi/toml v1.4.0 // indirect + github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c // indirect github.com/apache/arrow-go/v18 v18.0.1-0.20241212180703-82be143d7c30 // indirect github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect github.com/beorn7/perks v1.0.1 // indirect @@ -24,7 +24,7 @@ require ( github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/elazarl/goproxy v1.7.2 // indirect github.com/emicklei/go-restful/v3 v3.11.0 // indirect - github.com/fatih/color v1.17.0 // indirect + github.com/fatih/color v1.18.0 // indirect github.com/fsnotify/fsnotify v1.8.0 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect github.com/getkin/kin-openapi v0.129.0 // indirect diff --git a/apps/dashboard/go.sum b/apps/dashboard/go.sum index 33cfb1d9e90..dfcbb8409bc 100644 --- a/apps/dashboard/go.sum +++ b/apps/dashboard/go.sum @@ -1,6 +1,7 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0= github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= +github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c h1:pxW6RcqyfI9/kWtOwnv/G+AzdKuy2ZrqINhenH4HyNs= +github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA= github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA= github.com/apache/arrow-go/v18 v18.0.1-0.20241212180703-82be143d7c30 h1:hXVi7QKuCQ0E8Yujfu9b0f0RnzZ72efpWvPnZgnJPrE= @@ -34,8 +35,8 @@ github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVo github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= -github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4= -github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI= +github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= +github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= diff --git a/apps/dashboard/kinds/v2alpha1/dashboard_spec.cue b/apps/dashboard/kinds/v2alpha1/dashboard_spec.cue index 26ca2d35eec..2396c0cc1aa 100644 --- a/apps/dashboard/kinds/v2alpha1/dashboard_spec.cue +++ b/apps/dashboard/kinds/v2alpha1/dashboard_spec.cue @@ -554,7 +554,7 @@ RowsLayoutRowSpec: { collapsed: bool conditionalRendering?: ConditionalRenderingGroupKind repeat?: RowRepeatOptions - layout: GridLayoutKind | ResponsiveGridLayoutKind | TabsLayoutKind + layout: GridLayoutKind | ResponsiveGridLayoutKind | TabsLayoutKind | RowsLayoutKind } ResponsiveGridLayoutKind: { @@ -595,7 +595,7 @@ TabsLayoutTabKind: { TabsLayoutTabSpec: { title?: string - layout: GridLayoutKind | RowsLayoutKind | ResponsiveGridLayoutKind + layout: GridLayoutKind | RowsLayoutKind | ResponsiveGridLayoutKind | TabsLayoutKind } PanelSpec: { diff --git a/apps/dashboard/pkg/apis/dashboard/v0alpha1/dashboard_object_gen.go b/apps/dashboard/pkg/apis/dashboard/v0alpha1/dashboard_object_gen.go index 5e5643192a6..f37ced365cf 100644 --- a/apps/dashboard/pkg/apis/dashboard/v0alpha1/dashboard_object_gen.go +++ b/apps/dashboard/pkg/apis/dashboard/v0alpha1/dashboard_object_gen.go @@ -219,6 +219,7 @@ func (o *Dashboard) Copy() resource.Object { } func (o *Dashboard) DeepCopyObject() runtime.Object { + // This really should be deep copy. If the generator tries to change it to o.Copy() it needs to be manually changed back to o.DeepCopy() and this comment added back. return o.DeepCopy() } diff --git a/apps/dashboard/pkg/apis/dashboard/v1alpha1/dashboard_object_gen.go b/apps/dashboard/pkg/apis/dashboard/v1alpha1/dashboard_object_gen.go index 8d45c64fc1a..137d7149477 100644 --- a/apps/dashboard/pkg/apis/dashboard/v1alpha1/dashboard_object_gen.go +++ b/apps/dashboard/pkg/apis/dashboard/v1alpha1/dashboard_object_gen.go @@ -219,6 +219,7 @@ func (o *Dashboard) Copy() resource.Object { } func (o *Dashboard) DeepCopyObject() runtime.Object { + // This really should be deep copy. If the generator tries to change it to o.Copy() it needs to be manually changed back to o.DeepCopy() and this comment added back. return o.DeepCopy() } diff --git a/apps/dashboard/pkg/apis/dashboard/v2alpha1/dashboard_object_gen.go b/apps/dashboard/pkg/apis/dashboard/v2alpha1/dashboard_object_gen.go index 5b59fbd93aa..4437f066574 100644 --- a/apps/dashboard/pkg/apis/dashboard/v2alpha1/dashboard_object_gen.go +++ b/apps/dashboard/pkg/apis/dashboard/v2alpha1/dashboard_object_gen.go @@ -219,6 +219,7 @@ func (o *Dashboard) Copy() resource.Object { } func (o *Dashboard) DeepCopyObject() runtime.Object { + // This really should be deep copy. If the generator tries to change it to o.Copy() it needs to be manually changed back to o.DeepCopy() and this comment added back. return o.DeepCopy() } diff --git a/apps/dashboard/pkg/apis/dashboard/v2alpha1/dashboard_spec_gen.go b/apps/dashboard/pkg/apis/dashboard/v2alpha1/dashboard_spec_gen.go index b491bd8510d..801cd807bd3 100644 --- a/apps/dashboard/pkg/apis/dashboard/v2alpha1/dashboard_spec_gen.go +++ b/apps/dashboard/pkg/apis/dashboard/v2alpha1/dashboard_spec_gen.go @@ -836,17 +836,17 @@ func NewDashboardRowsLayoutRowKind() *DashboardRowsLayoutRowKind { // +k8s:openapi-gen=true type DashboardRowsLayoutRowSpec struct { - Title *string `json:"title,omitempty"` - Collapsed bool `json:"collapsed"` - ConditionalRendering *DashboardConditionalRenderingGroupKind `json:"conditionalRendering,omitempty"` - Repeat *DashboardRowRepeatOptions `json:"repeat,omitempty"` - Layout DashboardGridLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKind `json:"layout"` + Title *string `json:"title,omitempty"` + Collapsed bool `json:"collapsed"` + ConditionalRendering *DashboardConditionalRenderingGroupKind `json:"conditionalRendering,omitempty"` + Repeat *DashboardRowRepeatOptions `json:"repeat,omitempty"` + Layout DashboardGridLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKindOrRowsLayoutKind `json:"layout"` } // NewDashboardRowsLayoutRowSpec creates a new DashboardRowsLayoutRowSpec object. func NewDashboardRowsLayoutRowSpec() *DashboardRowsLayoutRowSpec { return &DashboardRowsLayoutRowSpec{ - Layout: *NewDashboardGridLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKind(), + Layout: *NewDashboardGridLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKindOrRowsLayoutKind(), } } @@ -1054,14 +1054,14 @@ func NewDashboardTabsLayoutTabKind() *DashboardTabsLayoutTabKind { // +k8s:openapi-gen=true type DashboardTabsLayoutTabSpec struct { - Title *string `json:"title,omitempty"` - Layout DashboardGridLayoutKindOrRowsLayoutKindOrResponsiveGridLayoutKind `json:"layout"` + Title *string `json:"title,omitempty"` + Layout DashboardGridLayoutKindOrRowsLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKind `json:"layout"` } // NewDashboardTabsLayoutTabSpec creates a new DashboardTabsLayoutTabSpec object. func NewDashboardTabsLayoutTabSpec() *DashboardTabsLayoutTabSpec { return &DashboardTabsLayoutTabSpec{ - Layout: *NewDashboardGridLayoutKindOrRowsLayoutKindOrResponsiveGridLayoutKind(), + Layout: *NewDashboardGridLayoutKindOrRowsLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKind(), } } @@ -2012,19 +2012,20 @@ func (resource *DashboardGridLayoutItemKindOrGridLayoutRowKind) UnmarshalJSON(ra } // +k8s:openapi-gen=true -type DashboardGridLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKind struct { +type DashboardGridLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKindOrRowsLayoutKind struct { GridLayoutKind *DashboardGridLayoutKind `json:"GridLayoutKind,omitempty"` ResponsiveGridLayoutKind *DashboardResponsiveGridLayoutKind `json:"ResponsiveGridLayoutKind,omitempty"` TabsLayoutKind *DashboardTabsLayoutKind `json:"TabsLayoutKind,omitempty"` + RowsLayoutKind *DashboardRowsLayoutKind `json:"RowsLayoutKind,omitempty"` } -// NewDashboardGridLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKind creates a new DashboardGridLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKind object. -func NewDashboardGridLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKind() *DashboardGridLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKind { - return &DashboardGridLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKind{} +// NewDashboardGridLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKindOrRowsLayoutKind creates a new DashboardGridLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKindOrRowsLayoutKind object. +func NewDashboardGridLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKindOrRowsLayoutKind() *DashboardGridLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKindOrRowsLayoutKind { + return &DashboardGridLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKindOrRowsLayoutKind{} } -// MarshalJSON implements a custom JSON marshalling logic to encode `DashboardGridLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKind` as JSON. -func (resource DashboardGridLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKind) MarshalJSON() ([]byte, error) { +// MarshalJSON implements a custom JSON marshalling logic to encode `DashboardGridLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKindOrRowsLayoutKind` as JSON. +func (resource DashboardGridLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKindOrRowsLayoutKind) MarshalJSON() ([]byte, error) { if resource.GridLayoutKind != nil { return json.Marshal(resource.GridLayoutKind) } @@ -2034,11 +2035,14 @@ func (resource DashboardGridLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKind if resource.TabsLayoutKind != nil { return json.Marshal(resource.TabsLayoutKind) } + if resource.RowsLayoutKind != nil { + return json.Marshal(resource.RowsLayoutKind) + } return []byte("null"), nil } -// UnmarshalJSON implements a custom JSON unmarshalling logic to decode `DashboardGridLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKind` from JSON. -func (resource *DashboardGridLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKind) UnmarshalJSON(raw []byte) error { +// UnmarshalJSON implements a custom JSON unmarshalling logic to decode `DashboardGridLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKindOrRowsLayoutKind` from JSON. +func (resource *DashboardGridLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKindOrRowsLayoutKind) UnmarshalJSON(raw []byte) error { if raw == nil { return nil } @@ -2071,6 +2075,14 @@ func (resource *DashboardGridLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKin resource.ResponsiveGridLayoutKind = &dashboardResponsiveGridLayoutKind return nil + case "RowsLayout": + var dashboardRowsLayoutKind DashboardRowsLayoutKind + if err := json.Unmarshal(raw, &dashboardRowsLayoutKind); err != nil { + return err + } + + resource.RowsLayoutKind = &dashboardRowsLayoutKind + return nil case "TabsLayout": var dashboardTabsLayoutKind DashboardTabsLayoutKind if err := json.Unmarshal(raw, &dashboardTabsLayoutKind); err != nil { @@ -2158,19 +2170,20 @@ func (resource *DashboardConditionalRenderingVariableKindOrConditionalRenderingD } // +k8s:openapi-gen=true -type DashboardGridLayoutKindOrRowsLayoutKindOrResponsiveGridLayoutKind struct { +type DashboardGridLayoutKindOrRowsLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKind struct { GridLayoutKind *DashboardGridLayoutKind `json:"GridLayoutKind,omitempty"` RowsLayoutKind *DashboardRowsLayoutKind `json:"RowsLayoutKind,omitempty"` ResponsiveGridLayoutKind *DashboardResponsiveGridLayoutKind `json:"ResponsiveGridLayoutKind,omitempty"` + TabsLayoutKind *DashboardTabsLayoutKind `json:"TabsLayoutKind,omitempty"` } -// NewDashboardGridLayoutKindOrRowsLayoutKindOrResponsiveGridLayoutKind creates a new DashboardGridLayoutKindOrRowsLayoutKindOrResponsiveGridLayoutKind object. -func NewDashboardGridLayoutKindOrRowsLayoutKindOrResponsiveGridLayoutKind() *DashboardGridLayoutKindOrRowsLayoutKindOrResponsiveGridLayoutKind { - return &DashboardGridLayoutKindOrRowsLayoutKindOrResponsiveGridLayoutKind{} +// NewDashboardGridLayoutKindOrRowsLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKind creates a new DashboardGridLayoutKindOrRowsLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKind object. +func NewDashboardGridLayoutKindOrRowsLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKind() *DashboardGridLayoutKindOrRowsLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKind { + return &DashboardGridLayoutKindOrRowsLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKind{} } -// MarshalJSON implements a custom JSON marshalling logic to encode `DashboardGridLayoutKindOrRowsLayoutKindOrResponsiveGridLayoutKind` as JSON. -func (resource DashboardGridLayoutKindOrRowsLayoutKindOrResponsiveGridLayoutKind) MarshalJSON() ([]byte, error) { +// MarshalJSON implements a custom JSON marshalling logic to encode `DashboardGridLayoutKindOrRowsLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKind` as JSON. +func (resource DashboardGridLayoutKindOrRowsLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKind) MarshalJSON() ([]byte, error) { if resource.GridLayoutKind != nil { return json.Marshal(resource.GridLayoutKind) } @@ -2180,11 +2193,14 @@ func (resource DashboardGridLayoutKindOrRowsLayoutKindOrResponsiveGridLayoutKind if resource.ResponsiveGridLayoutKind != nil { return json.Marshal(resource.ResponsiveGridLayoutKind) } + if resource.TabsLayoutKind != nil { + return json.Marshal(resource.TabsLayoutKind) + } return []byte("null"), nil } -// UnmarshalJSON implements a custom JSON unmarshalling logic to decode `DashboardGridLayoutKindOrRowsLayoutKindOrResponsiveGridLayoutKind` from JSON. -func (resource *DashboardGridLayoutKindOrRowsLayoutKindOrResponsiveGridLayoutKind) UnmarshalJSON(raw []byte) error { +// UnmarshalJSON implements a custom JSON unmarshalling logic to decode `DashboardGridLayoutKindOrRowsLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKind` from JSON. +func (resource *DashboardGridLayoutKindOrRowsLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKind) UnmarshalJSON(raw []byte) error { if raw == nil { return nil } @@ -2225,6 +2241,14 @@ func (resource *DashboardGridLayoutKindOrRowsLayoutKindOrResponsiveGridLayoutKin resource.RowsLayoutKind = &dashboardRowsLayoutKind return nil + case "TabsLayout": + var dashboardTabsLayoutKind DashboardTabsLayoutKind + if err := json.Unmarshal(raw, &dashboardTabsLayoutKind); err != nil { + return err + } + + resource.TabsLayoutKind = &dashboardTabsLayoutKind + return nil } return fmt.Errorf("could not unmarshal resource with `kind = %v`", discriminator) @@ -2472,88 +2496,3 @@ func (resource *DashboardStringOrFloat64) UnmarshalJSON(raw []byte) error { return errors.Join(errList...) } - -// +k8s:openapi-gen=true -type DashboardGridLayoutKindOrRowsLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKind struct { - GridLayoutKind *DashboardGridLayoutKind `json:"GridLayoutKind,omitempty"` - RowsLayoutKind *DashboardRowsLayoutKind `json:"RowsLayoutKind,omitempty"` - ResponsiveGridLayoutKind *DashboardResponsiveGridLayoutKind `json:"ResponsiveGridLayoutKind,omitempty"` - TabsLayoutKind *DashboardTabsLayoutKind `json:"TabsLayoutKind,omitempty"` -} - -// NewDashboardGridLayoutKindOrRowsLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKind creates a new DashboardGridLayoutKindOrRowsLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKind object. -func NewDashboardGridLayoutKindOrRowsLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKind() *DashboardGridLayoutKindOrRowsLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKind { - return &DashboardGridLayoutKindOrRowsLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKind{} -} - -// MarshalJSON implements a custom JSON marshalling logic to encode `DashboardGridLayoutKindOrRowsLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKind` as JSON. -func (resource DashboardGridLayoutKindOrRowsLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKind) MarshalJSON() ([]byte, error) { - if resource.GridLayoutKind != nil { - return json.Marshal(resource.GridLayoutKind) - } - if resource.RowsLayoutKind != nil { - return json.Marshal(resource.RowsLayoutKind) - } - if resource.ResponsiveGridLayoutKind != nil { - return json.Marshal(resource.ResponsiveGridLayoutKind) - } - if resource.TabsLayoutKind != nil { - return json.Marshal(resource.TabsLayoutKind) - } - return []byte("null"), nil -} - -// UnmarshalJSON implements a custom JSON unmarshalling logic to decode `DashboardGridLayoutKindOrRowsLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKind` from JSON. -func (resource *DashboardGridLayoutKindOrRowsLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKind) UnmarshalJSON(raw []byte) error { - if raw == nil { - return nil - } - - // FIXME: this is wasteful, we need to find a more efficient way to unmarshal this. - parsedAsMap := make(map[string]interface{}) - if err := json.Unmarshal(raw, &parsedAsMap); err != nil { - return err - } - - discriminator, found := parsedAsMap["kind"] - if !found { - return errors.New("discriminator field 'kind' not found in payload") - } - - switch discriminator { - case "GridLayout": - var dashboardGridLayoutKind DashboardGridLayoutKind - if err := json.Unmarshal(raw, &dashboardGridLayoutKind); err != nil { - return err - } - - resource.GridLayoutKind = &dashboardGridLayoutKind - return nil - case "ResponsiveGridLayout": - var dashboardResponsiveGridLayoutKind DashboardResponsiveGridLayoutKind - if err := json.Unmarshal(raw, &dashboardResponsiveGridLayoutKind); err != nil { - return err - } - - resource.ResponsiveGridLayoutKind = &dashboardResponsiveGridLayoutKind - return nil - case "RowsLayout": - var dashboardRowsLayoutKind DashboardRowsLayoutKind - if err := json.Unmarshal(raw, &dashboardRowsLayoutKind); err != nil { - return err - } - - resource.RowsLayoutKind = &dashboardRowsLayoutKind - return nil - case "TabsLayout": - var dashboardTabsLayoutKind DashboardTabsLayoutKind - if err := json.Unmarshal(raw, &dashboardTabsLayoutKind); err != nil { - return err - } - - resource.TabsLayoutKind = &dashboardTabsLayoutKind - return nil - } - - return fmt.Errorf("could not unmarshal resource with `kind = %v`", discriminator) -} diff --git a/apps/dashboard/pkg/apis/dashboard/v2alpha1/zz_generated.openapi.go b/apps/dashboard/pkg/apis/dashboard/v2alpha1/zz_generated.openapi.go index 818002bf235..580015fc2ed 100644 --- a/apps/dashboard/pkg/apis/dashboard/v2alpha1/zz_generated.openapi.go +++ b/apps/dashboard/pkg/apis/dashboard/v2alpha1/zz_generated.openapi.go @@ -54,8 +54,7 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardGridLayoutItemKindOrGridLayoutRowKind": schema_pkg_apis_dashboard_v2alpha1_DashboardGridLayoutItemKindOrGridLayoutRowKind(ref), "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardGridLayoutItemSpec": schema_pkg_apis_dashboard_v2alpha1_DashboardGridLayoutItemSpec(ref), "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardGridLayoutKind": schema_pkg_apis_dashboard_v2alpha1_DashboardGridLayoutKind(ref), - "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardGridLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKind": schema_pkg_apis_dashboard_v2alpha1_DashboardGridLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKind(ref), - "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardGridLayoutKindOrRowsLayoutKindOrResponsiveGridLayoutKind": schema_pkg_apis_dashboard_v2alpha1_DashboardGridLayoutKindOrRowsLayoutKindOrResponsiveGridLayoutKind(ref), + "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardGridLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKindOrRowsLayoutKind": schema_pkg_apis_dashboard_v2alpha1_DashboardGridLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKindOrRowsLayoutKind(ref), "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardGridLayoutKindOrRowsLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKind": schema_pkg_apis_dashboard_v2alpha1_DashboardGridLayoutKindOrRowsLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKind(ref), "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardGridLayoutRowKind": schema_pkg_apis_dashboard_v2alpha1_DashboardGridLayoutRowKind(ref), "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardGridLayoutRowSpec": schema_pkg_apis_dashboard_v2alpha1_DashboardGridLayoutRowSpec(ref), @@ -1935,7 +1934,7 @@ func schema_pkg_apis_dashboard_v2alpha1_DashboardGridLayoutKind(ref common.Refer } } -func schema_pkg_apis_dashboard_v2alpha1_DashboardGridLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKind(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_pkg_apis_dashboard_v2alpha1_DashboardGridLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKindOrRowsLayoutKind(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ @@ -1956,40 +1955,16 @@ func schema_pkg_apis_dashboard_v2alpha1_DashboardGridLayoutKindOrResponsiveGridL Ref: ref("github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardTabsLayoutKind"), }, }, - }, - }, - }, - Dependencies: []string{ - "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardGridLayoutKind", "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardResponsiveGridLayoutKind", "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardTabsLayoutKind"}, - } -} - -func schema_pkg_apis_dashboard_v2alpha1_DashboardGridLayoutKindOrRowsLayoutKindOrResponsiveGridLayoutKind(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "GridLayoutKind": { - SchemaProps: spec.SchemaProps{ - Ref: ref("github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardGridLayoutKind"), - }, - }, "RowsLayoutKind": { SchemaProps: spec.SchemaProps{ Ref: ref("github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardRowsLayoutKind"), }, }, - "ResponsiveGridLayoutKind": { - SchemaProps: spec.SchemaProps{ - Ref: ref("github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardResponsiveGridLayoutKind"), - }, - }, }, }, }, Dependencies: []string{ - "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardGridLayoutKind", "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardResponsiveGridLayoutKind", "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardRowsLayoutKind"}, + "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardGridLayoutKind", "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardResponsiveGridLayoutKind", "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardRowsLayoutKind", "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardTabsLayoutKind"}, } } @@ -3597,7 +3572,7 @@ func schema_pkg_apis_dashboard_v2alpha1_DashboardRowsLayoutRowSpec(ref common.Re }, "layout": { SchemaProps: spec.SchemaProps{ - Ref: ref("github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardGridLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKind"), + Ref: ref("github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardGridLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKindOrRowsLayoutKind"), }, }, }, @@ -3605,7 +3580,7 @@ func schema_pkg_apis_dashboard_v2alpha1_DashboardRowsLayoutRowSpec(ref common.Re }, }, Dependencies: []string{ - "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardConditionalRenderingGroupKind", "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardGridLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKind", "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardRowRepeatOptions"}, + "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardConditionalRenderingGroupKind", "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardGridLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKindOrRowsLayoutKind", "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardRowRepeatOptions"}, } } @@ -3987,7 +3962,7 @@ func schema_pkg_apis_dashboard_v2alpha1_DashboardTabsLayoutTabSpec(ref common.Re }, "layout": { SchemaProps: spec.SchemaProps{ - Ref: ref("github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardGridLayoutKindOrRowsLayoutKindOrResponsiveGridLayoutKind"), + Ref: ref("github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardGridLayoutKindOrRowsLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKind"), }, }, }, @@ -3995,7 +3970,7 @@ func schema_pkg_apis_dashboard_v2alpha1_DashboardTabsLayoutTabSpec(ref common.Re }, }, Dependencies: []string{ - "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardGridLayoutKindOrRowsLayoutKindOrResponsiveGridLayoutKind"}, + "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardGridLayoutKindOrRowsLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKind"}, } } diff --git a/apps/dashboard/pkg/apis/dashboard/v2alpha1/zz_generated.openapi_violation_exceptions.list b/apps/dashboard/pkg/apis/dashboard/v2alpha1/zz_generated.openapi_violation_exceptions.list index 31084da6e9f..7b4e371ea2b 100644 --- a/apps/dashboard/pkg/apis/dashboard/v2alpha1/zz_generated.openapi_violation_exceptions.list +++ b/apps/dashboard/pkg/apis/dashboard/v2alpha1/zz_generated.openapi_violation_exceptions.list @@ -37,12 +37,10 @@ API rule violation: names_match,github.com/grafana/grafana/apps/dashboard/pkg/ap API rule violation: names_match,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1,DashboardConditionalRenderingVariableKindOrConditionalRenderingDataKindOrConditionalRenderingTimeIntervalKind,ConditionalRenderingVariableKind API rule violation: names_match,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1,DashboardGridLayoutItemKindOrGridLayoutRowKind,GridLayoutItemKind API rule violation: names_match,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1,DashboardGridLayoutItemKindOrGridLayoutRowKind,GridLayoutRowKind -API rule violation: names_match,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1,DashboardGridLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKind,GridLayoutKind -API rule violation: names_match,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1,DashboardGridLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKind,ResponsiveGridLayoutKind -API rule violation: names_match,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1,DashboardGridLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKind,TabsLayoutKind -API rule violation: names_match,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1,DashboardGridLayoutKindOrRowsLayoutKindOrResponsiveGridLayoutKind,GridLayoutKind -API rule violation: names_match,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1,DashboardGridLayoutKindOrRowsLayoutKindOrResponsiveGridLayoutKind,ResponsiveGridLayoutKind -API rule violation: names_match,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1,DashboardGridLayoutKindOrRowsLayoutKindOrResponsiveGridLayoutKind,RowsLayoutKind +API rule violation: names_match,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1,DashboardGridLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKindOrRowsLayoutKind,GridLayoutKind +API rule violation: names_match,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1,DashboardGridLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKindOrRowsLayoutKind,ResponsiveGridLayoutKind +API rule violation: names_match,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1,DashboardGridLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKindOrRowsLayoutKind,RowsLayoutKind +API rule violation: names_match,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1,DashboardGridLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKindOrRowsLayoutKind,TabsLayoutKind API rule violation: names_match,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1,DashboardGridLayoutKindOrRowsLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKind,GridLayoutKind API rule violation: names_match,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1,DashboardGridLayoutKindOrRowsLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKind,ResponsiveGridLayoutKind API rule violation: names_match,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1,DashboardGridLayoutKindOrRowsLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKind,RowsLayoutKind diff --git a/conf/defaults.ini b/conf/defaults.ini index 1061677bee4..a4679a13a6f 100644 --- a/conf/defaults.ini +++ b/conf/defaults.ini @@ -932,6 +932,8 @@ key_file = key_id = role_attribute_path = role_attribute_strict = false +org_attribute_path = +org_mapping = groups_attribute_path = auto_sign_up = false url_login = false @@ -1515,6 +1517,16 @@ max_age = # Configures max number of alert annotations that Grafana stores. Default value is 0, which keeps all alert annotations. max_annotations_to_keep = +[unified_alerting.prometheus_conversion] +# Configuration options for converting Prometheus alerting and recording rules to Grafana rules. +# These settings affect rules created via the Prometheus conversion API. + +# Offset the rule evaluation time for imported rules by a specified duration in the past. +# This offset is applied and saved to the rule query during the conversion process from Prometheus to Grafana format. +# The setting only affects rules imported after the configuration change is made and does not modify existing rules. +# Accepts duration formats like: 30s, 1m, 1h. +rule_query_offset = 1m + [recording_rules] # Enable recording rules. You must provide write credentials below. enabled = false @@ -1533,7 +1545,7 @@ timeout = 10s # Default data source UID to write to if not specified in the rule definition. # Only has effect if the grafanaManagedRecordRulesDatasources feature toggle is enabled. -default_datasource_uid = +default_datasource_uid = # Optional custom headers to include in recording rule write requests. [recording_rules.custom_headers] diff --git a/conf/sample.ini b/conf/sample.ini index 4cecf657e42..f06e41efed9 100644 --- a/conf/sample.ini +++ b/conf/sample.ini @@ -132,7 +132,7 @@ # Set to true or false to enable or disable high availability mode. # When it's set to false some functions will be simplified and only run in-process # instead of relying on the database. -# +# # Only set it to false if you run only a single instance of Grafana. ;high_availability = true @@ -901,6 +901,8 @@ ;key_id = some-key-id ;role_attribute_path = ;role_attribute_strict = false +;org_attribute_path = +;org_mapping = ;groups_attribute_path = ;auto_sign_up = false ;url_login = false @@ -1496,6 +1498,16 @@ max_age = # Configures max number of alert annotations that Grafana stores. Default value is 0, which keeps all alert annotations. max_annotations_to_keep = +[unified_alerting.prometheus_conversion] +# Configuration options for converting Prometheus alerting and recording rules to Grafana rules. +# These settings affect rules created via the Prometheus conversion API. + +# Offset the rule evaluation time for imported rules by a specified duration in the past. +# This offset is applied and saved to the rule query during the conversion process from Prometheus to Grafana format. +# The setting only affects rules imported after the configuration change is made and does not modify existing rules. +# Accepts duration formats like: 30s, 1m, 1h. +rule_query_offset = 1m + #################################### Recording Rules ##################### [recording_rules] # Enable recording rules. You must provide write credentials below. @@ -1515,7 +1527,7 @@ timeout = 30s # Default data source UID to write to if not specified in the rule definition. # Only has effect if the grafanaManagedRecordRulesDatasources feature toggle is enabled. -default_datasource_uid = +default_datasource_uid = # Optional custom headers to include in recording rule write requests. [recording_rules.custom_headers] diff --git a/contribute/developer-guide.md b/contribute/developer-guide.md index da3669615ab..6084df4ce66 100644 --- a/contribute/developer-guide.md +++ b/contribute/developer-guide.md @@ -19,8 +19,13 @@ We recommend using [Homebrew](https://brew.sh/) for installing any missing depen brew install git brew install go brew install node@22 -brew install corepack +``` + +In the repository enable and install yarn via corepack + +``` corepack enable +corepack install ``` ### Windows diff --git a/docs/sources/administration/roles-and-permissions/access-control/_index.md b/docs/sources/administration/roles-and-permissions/access-control/_index.md index 888006f4ac7..a6815849f49 100644 --- a/docs/sources/administration/roles-and-permissions/access-control/_index.md +++ b/docs/sources/administration/roles-and-permissions/access-control/_index.md @@ -169,6 +169,8 @@ To interact with the API and view or modify basic roles permissions, refer to [t You cannot use a service account to modify basic roles via the RBAC API. To update basic roles, you must be a Grafana administrator and use basic authentication with the request. {{% /admonition %}} +For Cloud customers, contact Support to reset roles. + ### Fixed roles Grafana Enterprise includes the ability for you to assign discrete fixed roles to users, teams, and service accounts. This gives you fine-grained control over user permissions than you would have with basic roles alone. These roles are called "fixed" because you cannot change or delete fixed roles. You can also create _custom_ roles of your own; see more information in the [custom roles section](#custom-roles) below. diff --git a/docs/sources/administration/roles-and-permissions/access-control/custom-role-actions-scopes/index.md b/docs/sources/administration/roles-and-permissions/access-control/custom-role-actions-scopes/index.md index baab890cbef..beb5fef013c 100644 --- a/docs/sources/administration/roles-and-permissions/access-control/custom-role-actions-scopes/index.md +++ b/docs/sources/administration/roles-and-permissions/access-control/custom-role-actions-scopes/index.md @@ -100,8 +100,6 @@ The following list contains role-based access control actions. | `folders:delete` |
  • `folders:*`
  • `folders:uid:*`
| Delete one or more folders and their subfolders. | | `folders:read` |
  • `folders:*`
  • `folders:uid:*`
| Read one or more folders and their subfolders. | | `folders:write` |
  • `folders:*`
  • `folders:uid:*`
| Update one or more folders and their subfolders. | -| `groupsync.mappings:read` | None | List group attribute sync mappings. To use this permission, enable the `groupAttributeSync` feature toggle. | -| `groupsync.mappings:write` | None | List, create, update, and delete group attribute sync mappings. To use this permission, enable the `groupAttributeSync` feature toggle. | | `ldap.config:reload` | None | Reload the LDAP configuration. | | `ldap.status:read` | None | Verify the availability of the LDAP server or servers. | | `ldap.user:read` | None | Read users via LDAP. | @@ -205,6 +203,20 @@ The following list contains role-based access control actions used by Grafana Ad | `grafana-adaptive-metrics-app.exemptions:read` | None | Read recommendation exemptions. | | `grafana-adaptive-metrics-app.exemptions:write` | None | Create, update, and delete recommendation exemptions. | +### Cloud Access Policies action definitions + +The following list contains role-based access control actions used by Cloud Access Policies. + +| Action | Applicable scopes | Description | +| ------------------------ | ----------------- | ------------------------------------------------------------------- | +| `grafana-auth-app:write` | None | Create, read, update, and delete access policies for Grafana Cloud. | + +{{< admonition type="warning" >}} +Granting the `grafana-auth-app:write` permission is equivalent to assigning the Admin role to a user in Grafana, as it allows them to manage all stack service accounts. This provides significant privileges and should be assigned with caution. +{{< /admonition >}} + +For more information on Cloud Access Policies and how to use them, see [Access policies](https://grafana.com/docs/grafana-cloud/account-management/authentication-and-permissions/access-policies/). + ### Grafana Alerting Notification action definitions To use these permissions, enable the `alertingApiServer` feature toggle. diff --git a/docs/sources/administration/roles-and-permissions/access-control/rbac-fixed-basic-role-definitions/index.md b/docs/sources/administration/roles-and-permissions/access-control/rbac-fixed-basic-role-definitions/index.md index 8458e4d9355..193c872d58b 100644 --- a/docs/sources/administration/roles-and-permissions/access-control/rbac-fixed-basic-role-definitions/index.md +++ b/docs/sources/administration/roles-and-permissions/access-control/rbac-fixed-basic-role-definitions/index.md @@ -50,17 +50,17 @@ refs: Available in [Grafana Enterprise](/docs/grafana//introduction/grafana-enterprise/) and [Grafana Cloud](/docs/grafana-cloud). {{% /admonition %}} -The following tables list permissions associated with basic and fixed roles. +The following tables list permissions associated with basic and fixed roles. This does not include basic role assignments added by plugins or apps. ## Basic role assignments -| Basic role | UID | Associated fixed roles | Description | -| ------------- | --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Grafana Admin | `basic_grafana_admin` | `fixed:roles:reader`
`fixed:roles:writer`
`fixed:users:reader`
`fixed:users:writer`
`fixed:org.users:reader`
`fixed:org.users:writer`
`fixed:ldap:reader`
`fixed:ldap:writer`
`fixed:stats:reader`
`fixed:settings:reader`
`fixed:settings:writer`
`fixed:provisioning:writer`
`fixed:organization:reader`
`fixed:organization:maintainer`
`fixed:licensing:reader`
`fixed:licensing:writer`
`fixed:datasources.caching:reader`
`fixed:datasources.caching:writer`
`fixed:dashboards.insights:reader`
`fixed:datasources.insights:reader`
`fixed:plugins:maintainer`
`fixed:authentication.config:writer`
`fixed:library.panels:creator`
`fixed:library.panels:reader`
`fixed:library.panels:general.reader`
`fixed:library.panels:writer`
`fixed:library.panels:general.writer`
`fixed:groupsync:writer`
`fixed:migrationassistant:migrator` | Default [Grafana server administrator](/docs/grafana//administration/roles-and-permissions/#grafana-server-administrators) assignments. | -| Admin | `basic_admin` | `fixed:reports:reader`
`fixed:reports:writer`
`fixed:datasources:reader`
`fixed:datasources:writer`
`fixed:organization:writer`
`fixed:datasources.permissions:reader`
`fixed:datasources.permissions:writer`
`fixed:teams:writer`
`fixed:dashboards:reader`
`fixed:dashboards:writer`
`fixed:dashboards.permissions:reader`
`fixed:dashboards.permissions:writer`
`fixed:dashboards.public:writer`
`fixed:folders:reader`
`fixed:folders:writer`
`fixed:folders.permissions:reader`
`fixed:folders.permissions:writer`
`fixed:alerting:writer`
`fixed:apikeys:reader`
`fixed:apikeys:writer`
`fixed:alerting.provisioning.secrets:reader`
`fixed:alerting.provisioning:writer`
`fixed:datasources.caching:reader`
`fixed:datasources.caching:writer`
`fixed:dashboards.insights:reader`
`fixed:datasources.insights:reader`
`fixed:plugins:writer`
`fixed:library.panels:creator`
`fixed:library.panels:reader`
`fixed:library.panels:general.reader`
`fixed:library.panels:writer`
`fixed:library.panels:general.writer`
`fixed:alerting.provisioning.status:writer`
`fixed:groupsync:writer` | Default [Grafana organization administrator](ref:rbac-basic-roles) assignments. | -| Editor | `basic_editor` | `fixed:datasources:explorer`
`fixed:dashboards:creator`
`fixed:folders:creator`
`fixed:annotations:writer`
`fixed:alerting:writer`
`fixed:dashboards.insights:reader`
`fixed:datasources.insights:reader`
`fixed:library.panels:creator`
`fixed:library.panels:general.reader`
`fixed:library.panels:general.writer`
`fixed:alerting.provisioning.status:writer` | Default [Editor](ref:rbac-basic-roles) assignments. | -| Viewer | `basic_viewer` | `fixed:datasources.id:reader`
`fixed:organization:reader`
`fixed:annotations:reader`
`fixed:annotations.dashboard:writer`
`fixed:alerting:reader`
`fixed:plugins.app:reader`
`fixed:dashboards.insights:reader`
`fixed:datasources.insights:reader`
`fixed:library.panels:general.reader` | Default [Viewer](ref:rbac-basic-roles) assignments. | -| No Basic Role | n/a | | Default [No Basic Role](ref:rbac-basic-roles) | +| Basic role | UID | Associated fixed roles | Description | +| ------------- | --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Grafana Admin | `basic_grafana_admin` | `fixed:roles:reader`
`fixed:roles:writer`
`fixed:users:reader`
`fixed:users:writer`
`fixed:org.users:reader`
`fixed:org.users:writer`
`fixed:ldap:reader`
`fixed:ldap:writer`
`fixed:stats:reader`
`fixed:settings:reader`
`fixed:settings:writer`
`fixed:provisioning:writer`
`fixed:organization:reader`
`fixed:organization:maintainer`
`fixed:licensing:reader`
`fixed:licensing:writer`
`fixed:datasources.caching:reader`
`fixed:datasources.caching:writer`
`fixed:dashboards.insights:reader`
`fixed:datasources.insights:reader`
`fixed:plugins:maintainer`
`fixed:authentication.config:writer`
`fixed:library.panels:creator`
`fixed:library.panels:reader`
`fixed:library.panels:general.reader`
`fixed:library.panels:writer`
`fixed:library.panels:general.writer`
`fixed:migrationassistant:migrator` | Default [Grafana server administrator](/docs/grafana//administration/roles-and-permissions/#grafana-server-administrators) assignments. | +| Admin | `basic_admin` | `fixed:reports:reader`
`fixed:reports:writer`
`fixed:datasources:reader`
`fixed:datasources:writer`
`fixed:organization:writer`
`fixed:datasources.permissions:reader`
`fixed:datasources.permissions:writer`
`fixed:teams:writer`
`fixed:dashboards:reader`
`fixed:dashboards:writer`
`fixed:dashboards.permissions:reader`
`fixed:dashboards.permissions:writer`
`fixed:dashboards.public:writer`
`fixed:folders:reader`
`fixed:folders:writer`
`fixed:folders.permissions:reader`
`fixed:folders.permissions:writer`
`fixed:alerting:writer`
`fixed:apikeys:reader`
`fixed:apikeys:writer`
`fixed:alerting.provisioning.secrets:reader`
`fixed:alerting.provisioning:writer`
`fixed:datasources.caching:reader`
`fixed:datasources.caching:writer`
`fixed:dashboards.insights:reader`
`fixed:datasources.insights:reader`
`fixed:plugins:writer`
`fixed:library.panels:creator`
`fixed:library.panels:reader`
`fixed:library.panels:general.reader`
`fixed:library.panels:writer`
`fixed:library.panels:general.writer`
`fixed:alerting.provisioning.status:writer` | Default [Grafana organization administrator](ref:rbac-basic-roles) assignments. | +| Editor | `basic_editor` | `fixed:datasources:explorer`
`fixed:dashboards:creator`
`fixed:folders:creator`
`fixed:annotations:writer`
`fixed:alerting:writer`
`fixed:dashboards.insights:reader`
`fixed:datasources.insights:reader`
`fixed:library.panels:creator`
`fixed:library.panels:general.reader`
`fixed:library.panels:general.writer`
`fixed:alerting.provisioning.status:writer` | Default [Editor](ref:rbac-basic-roles) assignments. | +| Viewer | `basic_viewer` | `fixed:datasources.id:reader`
`fixed:organization:reader`
`fixed:annotations:reader`
`fixed:annotations.dashboard:writer`
`fixed:alerting:reader`
`fixed:plugins.app:reader`
`fixed:dashboards.insights:reader`
`fixed:datasources.insights:reader`
`fixed:library.panels:general.reader` | Default [Viewer](ref:rbac-basic-roles) assignments. | +| No Basic Role | n/a | | Default [No Basic Role](ref:rbac-basic-roles) | ## Fixed role definitions @@ -115,8 +115,6 @@ To learn how to use the roles API to determine the role UUIDs, refer to [Manage | `fixed:folders.permissions:reader` | `fixed_E06l4cx0JFm47EeLBE4nmv3pnSo` | `folders.permissions:read` | Read all folder permissions. | | `fixed:folders.permissions:writer` | `fixed_3GAgpQ_hWG8o7-lwNb86_VB37eI` | All permissions from `fixed:folders.permissions:reader` and
`folders.permissions:write` | Read and update all folder permissions. | | `fixed:ldap:reader` | `fixed_lMcOPwSkxKY-qCK8NMJc5k6izLE` | `ldap.user:read`
`ldap.status:read` | Read the LDAP configuration and LDAP status information. | -| `fixed:groupsync:reader` | `fixed_tLIbDrE6kw93sKqooF8GVS9BF4E` | `groupsync.mappings:read` | List all group attribute sync mappings. To use this role, enable the `groupAttributeSync` feature toggle. | -| `fixed:groupsync:writer` | `fixed_q7XUYx_efzxxsVmWhQgpiYClwBs` | `groupsync.mappings:read`
`groupsync.mappings:write` | Create, read, update, and delete all group attribute sync mappings. To use this role, enable the `groupAttributeSync` feature toggle. | | `fixed:ldap:writer` | `fixed_p6AvnU4GCQyIh7-hbwI-bk3GYnU` | All permissions from `fixed:ldap:reader` and
`ldap.user:sync`
`ldap.config:reload` | Read and update the LDAP configuration, and read LDAP status information. | | `fixed:library.panels:creator` | `fixed_6eX6ItfegCIY5zLmPqTDW8ZV7KY` | `library.panels:create`
`folders:read` | Create library panel at the root level. | | `fixed:library.panels:general.reader` | `fixed_ct0DghiBWR_2BiQm3EvNPDVmpio` | `library.panels:read` | Read all library panels at the root level. | @@ -165,16 +163,7 @@ There is only one exclusion at this moment. Role `fixed:alerting.provisioning:wr For more information about the permissions required to access alert rules, refer to [Create a custom role to access alerts in a folder](ref:plan-rbac-rollout-strategy-create-a-custom-role-to-access-alerts-in-a-folder). -### Grafana OnCall roles (beta) - -{{% admonition type="note" %}} -Available from Grafana 9.4 in early access. -{{% /admonition %}} - -{{% admonition type="note" %}} -This feature is behind the `accessControlOnCall` feature toggle. -You can enable feature toggles through configuration file or environment variables. See configuration [docs](/docs/grafana//setup-grafana/configure-grafana/#feature_toggles) for details. -{{% /admonition %}} +### Grafana OnCall roles If you are using [Grafana OnCall](ref:oncall), you can try out the integration between Grafana OnCall and RBAC. For a detailed list of the available OnCall RBAC roles, refer to the table in [Available Grafana OnCall RBAC roles and granted actions](ref:available-grafana-oncall-rbac-roles--granted-actions). diff --git a/docs/sources/administration/roles-and-permissions/access-control/rbac-for-app-plugins/index.md b/docs/sources/administration/roles-and-permissions/access-control/rbac-for-app-plugins/index.md index 020bf9c982c..46304ed2000 100644 --- a/docs/sources/administration/roles-and-permissions/access-control/rbac-for-app-plugins/index.md +++ b/docs/sources/administration/roles-and-permissions/access-control/rbac-for-app-plugins/index.md @@ -24,6 +24,9 @@ refs: destination: /docs/grafana//administration/roles-and-permissions/access-control/custom-role-actions-scopes/#grafana-adaptive-metrics-action-definitions - pattern: /docs/grafana-cloud/ destination: /docs/grafana-cloud/account-management/authentication-and-permissions/access-control/custom-role-actions-scopes/#grafana-adaptive-metrics-action-definitions + cloud-access-policies-action-definitions: + - pattern: /docs/grafana-cloud/ + destination: /docs/grafana//administration/roles-and-permissions/access-control/custom-role-actions-scopes/#cloud-access-policies-action-definitions rbac-role-definitions: - pattern: /docs/grafana/ destination: /docs/grafana//administration/roles-and-permissions/access-control/rbac-fixed-basic-role-definitions/ @@ -62,7 +65,7 @@ The following list contains app plugins that have fine-grained RBAC support. | App plugin | App plugin ID | App plugin permission documentation | | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| [Access policies](https://grafana.com/docs/grafana-cloud/account-management/authentication-and-permissions/access-policies/) | `grafana-auth-app` | n/a | +| [Access policies](https://grafana.com/docs/grafana-cloud/account-management/authentication-and-permissions/access-policies/) | `grafana-auth-app` | [RBAC actions for Access Policies](ref:cloud-access-policies-action-definitions) | | [Adaptive metrics](https://grafana.com/docs/grafana-cloud/cost-management-and-billing/reduce-costs/metrics-costs/control-metrics-usage-via-adaptive-metrics/adaptive-metrics-plugin/) | `grafana-adaptive-metrics-app` | [RBAC actions for Adaptive Metrics](ref:adaptive-metrics-permissions) | | [Incident](https://grafana.com/docs/grafana-cloud/alerting-and-irm/irm/incident/) | `grafana-incident-app` | n/a | | [OnCall](https://grafana.com/docs/grafana-cloud/alerting-and-irm/irm/oncall/) | `grafana-oncall-app` | [Configure RBAC for OnCall](https://grafana.com/docs/grafana-cloud/alerting-and-irm/irm/oncall/manage/user-and-team-management/#manage-users-and-teams-for-grafana-oncall) | diff --git a/docs/sources/alerting/fundamentals/alert-rule-evaluation/state-and-health.md b/docs/sources/alerting/fundamentals/alert-rule-evaluation/state-and-health.md index 5438c8c377b..79b137c1a7d 100644 --- a/docs/sources/alerting/fundamentals/alert-rule-evaluation/state-and-health.md +++ b/docs/sources/alerting/fundamentals/alert-rule-evaluation/state-and-health.md @@ -74,9 +74,14 @@ When an alert rule evaluation results in a `No Data` or `Error` state, Grafana A - `alertname`: Either `DatasourceNoData` or `DatasourceError` depending on the state. - `datasource_uid`: The UID of the data source that caused the state. +- `rulename`: The name of the alert rule that originated the alert. + +Note that `DatasourceNoData` and `DatasourceError` alert instances are independent from the original alert instance. They have different labels, which means existing silences, mute timings, and notification policies applied to the original alert may not apply to them. You can manage these alerts like regular ones by using their labels to apply actions such as adding a silence, routing via notification policies, and more. +If the alert rule is configured to send notifications directly to a selected contact point (instead of using notification policies), the `DatasourceNoData` and `DatasourceError` alerts are also sent to that contact point. Any additional notification settings defined in the alert rule, such as muting or grouping, are preserved. + ### Lifecycle of stale alert instances An alert instance is considered stale if its dimension or series has disappeared from the query results entirely for two evaluation intervals. @@ -85,9 +90,9 @@ Stale alert instances that are in the **Alerting**, **No Data**, or **Error** st ## Modify the `No Data` or `Error` state -In [Configure no data and error handling](ref:no-data-and-error-handling), you can change the default behaviour when the evaluation returns no data or an error. You can set the alert instance state to `Alerting`, `Normal`, or keep the last state. +These states are supported only for Grafana-managed alert rules. -Note that `No Data` and `Error` states are supported only for Grafana-managed alert rules. +In [Configure no data and error handling](ref:no-data-and-error-handling), you can change the default behaviour when the evaluation returns no data or an error. You can set the alert instance state to `Alerting`, `Normal`, `Error`, or `Keep Last State`. {{< figure src="/media/docs/alerting/alert-rule-configure-no-data-and-error-v2.png" alt="A screenshot of the `Configure no data and error handling` option in Grafana Alerting." max-width="500px" >}} diff --git a/docs/sources/alerting/monitor-status/view-alert-rules.md b/docs/sources/alerting/monitor-status/view-alert-rules.md index 2d7a404b90b..f521800f5bb 100644 --- a/docs/sources/alerting/monitor-status/view-alert-rules.md +++ b/docs/sources/alerting/monitor-status/view-alert-rules.md @@ -61,6 +61,8 @@ For details on how rule states and alert instance states are displayed, refer to ## View, compare and restore alert rules versions. +You can view, compare, and restore previous alert rule versions. The number of alert rule versions is limited to a maximum of 10 alert rule versions for free users, and a maximum of 100 stored alert rule versions for paid tier users. + To view or restore previous versions for an alert rule, complete the following steps. 1. Navigate to **Alerts & IRM -> Alerting -> Alert rules**. diff --git a/docs/sources/developers/http_api/_index.md b/docs/sources/developers/http_api/_index.md index 8889f1670c1..86d9ab8d185 100644 --- a/docs/sources/developers/http_api/_index.md +++ b/docs/sources/developers/http_api/_index.md @@ -84,7 +84,6 @@ Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk Grafana Enterprise includes all of the Grafana OSS APIs as well as the following APIs: - [Data source permissions API](datasource_permissions/) -- [Group attribute sync API](group_attribute_sync/) - [License API](licensing/) - [Query and resource caching API](query_and_resource_caching/) - [Reporting API](reporting/) diff --git a/docs/sources/developers/http_api/group_attribute_sync.md b/docs/sources/developers/http_api/group_attribute_sync.md deleted file mode 100644 index f6d5e752297..00000000000 --- a/docs/sources/developers/http_api/group_attribute_sync.md +++ /dev/null @@ -1,208 +0,0 @@ ---- -description: Grafana Group Attribute Sync HTTP API -keywords: - - grafana - - http - - documentation - - api - - group - - member - - enterprise -labels: - products: - - enterprise - - oss -title: Group Attribute Sync HTTP API ---- - -# Group attribute sync API - -The Group Attribute Sync API allows you to configure [group attribute sync feature](https://grafana.com/docs/grafana//setup-grafana/configure-security/configure-group-attribute-sync). This API is useful when you want to manage user roles based on group membership in an external system. - -> **Note:** Available in [Grafana Enterprise](https://grafana.com/docs/grafana//introduction/grafana-enterprise) and [Grafana Cloud](https://grafana.com/docs/grafana-cloud/) - -{{% admonition type="note" %}} -This feature is currently in [private preview](https://grafana.com/docs/release-life-cycle/#private-preview) and behind the `groupAttributeSync` feature toggle. Please contact support to have this feature enabled. -{{% /admonition %}} - -## List group mappings - -`GET /api/groupsync/groups` - -**Required permissions** - -| Action | Scope | -| ----------------------- | ----- | -| groupsync.mappings:read | n/a | - -**Example Request**: - -```http -GET /api/groupsync/groups HTTP/1.1 -Accept: application/json -Content-Type: application/json -Authorization: Bearer glsa_kcVxDhZtu5ISOZIEt -``` - -**Example Response**: - -```http -HTTP/1.1 200 -Content-Type: application/json - -[ - { - "groups": [ - { - "groupID": "group 1", - "mappings": { - "1": { - "roles": [ - "fixed_nzVQoNSDSn0fg1MDgO6XnZX2RZI", - "my_custom_role", - ] - } - } - }, - { - "groupID": "group 2", - "mappings": { - "1": { - "roles": [ - "another_role", - ] - } - } - } - ], - "total": 2 - } -] -``` - -Status Codes: - -- **200** - Ok -- **400** - Bad request -- **401** - Unauthorized -- **403** - Permission denied -- **500** - Internal server error - -## Create group mappings - -`POST /api/groupsync/groups/:groupID` - -**Required permissions** - -| Action | Scope | -| ------------------------ | ----- | -| groupsync.mappings:write | n/a | - -**Example Request**: - -```http -POST /api/groupsync/groups/my_group_id HTTP/1.1 -Accept: application/json -Content-Type: application/json -Authorization: Bearer glsa_kcVxDhZtu5ISOZIEt - -{ - "roles": [ - "fixed_nzVQoNSDSn0fg1MDgO6XnZX2RZI", - "my_custom_role_uid" - ] -} -``` - -**Example Response**: - -```http -HTTP/1.1 200 -Content-Type: application/json - -{ - "message": "Group mappings created." -} -``` - -Status Codes: - -- **201** - Ok -- **400** - Bad request -- **401** - Unauthorized -- **403** - Permission denied -- **500** - Internal server error - -## Update group mappings - -`PUT /api/groupsync/groups/:groupID` - -This endpoint will replace the existing mappings for the group with the new mappings provided in the request. - -**Required permissions** - -| Action | Scope | -| ------------------------ | ----- | -| groupsync.mappings:write | n/a | - -**Example Request**: - -```http -PUT /api/groupsync/groups/my_group_id HTTP/1.1 -Accept: application/json -Content-Type: application/json -Authorization: Bearer glsa_kcVxDhZtu5ISOZIEt - -{ - "roles": [ - "fixed_nzVQoNSDSn0fg1MDgO6XnZX2RZI", - "my_custom_role_uid" - ] -} -``` - -**Example Response**: - -```http -HTTP/1.1 200 -Content-Type: application/json - -{ - "message": "Group mappings set." -} -``` - -Status Codes: - -- **201** - Ok -- **400** - Bad request -- **401** - Unauthorized -- **403** - Permission denied -- **500** - Internal server error - -## Remove group mappings - -`DELETE /api/groupsync/groups/:groupID` - -**Required permissions** - -| Action | Scope | -| ------------------------ | ----- | -| groupsync.mappings:write | n/a | - -**Example Request**: - -```http -DELETE /api/groupsync/groups/my_group_id HTTP/1.1 -Accept: application/json -Content-Type: application/json -Authorization: Bearer glsa_kcVxDhZtu5ISOZIEt -``` - -Status Codes: - -- **204** - Ok -- **400** - Bad request -- **401** - Unauthorized -- **403** - Permission denied -- **500** - Internal server error diff --git a/docs/sources/explore/trace-integration.md b/docs/sources/explore/trace-integration.md index 532bb3ca8f2..ecfa1331199 100644 --- a/docs/sources/explore/trace-integration.md +++ b/docs/sources/explore/trace-integration.md @@ -85,15 +85,29 @@ You can expand any span in a trace and view the details, including the span and For more information about spans and traces, refer to [Introduction to tracing](https://grafana.com/docs/tempo/latest/introduction/) in the Tempo documentation. -Span details include: +Span details include span attributes, resource attributes, events, and links. -- **Span attributes** - Key/value pairs that provides context for spans. For example, if the span deals with calling another service via HTTP, an attribute could include the HTTP URL (maybe as the span attribute key `http.url`) and the HTTP status code returned (as the span attribute `http.status_code`). +#### Span and resource attributes -- **Resource attributes** - Key/value pairs that describe the context of how the span was collected. +**Span attributes** are key-value pairs that provide metadata about a specific span. They give context to the operation being performed, such as information about the request, response, or any relevant operational details. For example, if the span deals with calling another service via HTTP, an attribute could include the HTTP URL (maybe as the span attribute key `http.url`) and the HTTP status code returned (as the span attribute `http.status_code`). -Refer to [Span and resource attributes](/docs/tempo//operations/best-practices/#span-and-resource-attributes) for more detail. +{{< figure src="/media/docs/tempo/screenshot-grafana-trace-view-span-span-attributes.png" class="docs-image--no-shadow" max-width= "900px" caption="Trace view span attributes" >}} -{{< figure src="/media/docs/tempo/screenshot-grafana-trace-view-span-details.png" class="docs-image--no-shadow" max-width= "900px" caption="Trace view span details" >}} +**Resource attributes** are key-value pairs that describe the environment or entity that is producing the trace. They capture static information about the origin of traces, like the application name or the service version. + +{{< figure src="/media/docs/tempo/screenshot-grafana-trace-view-span-resource-attributes.png" class="docs-image--no-shadow" max-width= "900px" caption="Trace view span resource attributes" >}} + +Span attributes are specific to a particular operation, while resource attributes are associated with the whole trace or the entire service emitting the spans. Refer to [Span and resource attributes](/docs/tempo//operations/best-practices/#span-and-resource-attributes) for more detail. + +#### Events + +Events are log-like records attached to a span that represent an occurrence during its execution. They record notable moments or occurrences within the span's lifecycle, such as errors, warnings, or checkpoints. If an error occurs during an operation, an event can be added to the span to indicate what went wrong and when. Events include a timestamp, name, and key-value pairs attributes that provide additional context or details about the event. + +{{< figure src="/media/docs/tempo/screenshot-grafana-trace-view-span-events.png" class="docs-image--no-shadow" max-width= "900px" caption="Trace view span events" >}} + +#### Links + +Links show relationships between spans that are not in a direct parent-child relationship. They represent associations between spans that happen concurrently or across separate trace trees, linking traces that originated from separate sources but are logically connected, such as background job processing initiated from a web request. You might use links when a trace passes through an asynchronous queue or when correlating traces from different services. ### Span filters diff --git a/docs/sources/introduction/grafana-enterprise.md b/docs/sources/introduction/grafana-enterprise.md index 15db695b9bc..0a3189d6bc9 100644 --- a/docs/sources/introduction/grafana-enterprise.md +++ b/docs/sources/introduction/grafana-enterprise.md @@ -34,14 +34,14 @@ Grafana Enterprise includes integrations with more ways to authenticate your use Supported auth providers: - [Auth Proxy](https://grafana.com/docs/grafana//setup-grafana/configure-security/configure-authentication/auth-proxy#team-sync-enterprise-only) -- [Azure AD](https://grafana.com/docs/grafana//setup-grafana/configure-security/configure-authentication/azuread#group-sync-enterprise-only) -- [Generic OAuth integration](https://grafana.com/docs/grafana//setup-grafana/configure-security/configure-authentication/generic-oauth#configure-group-synchronization) -- [GitHub OAuth](https://grafana.com/docs/grafana//setup-grafana/configure-security/configure-authentication/github#configure-group-synchronization) -- [GitLab OAuth](https://grafana.com/docs/grafana//setup-grafana/configure-security/configure-authentication/gitlab#configure-group-synchronization) -- [Google OAuth](https://grafana.com/docs/grafana//setup-grafana/configure-security/configure-authentication/google#configure-group-synchronization) -- [LDAP](https://grafana.com/docs/grafana//setup-grafana/configure-security/configure-authentication/enhanced-ldap#ldap-group-synchronization) -- [Okta](https://grafana.com/docs/grafana//setup-grafana/configure-security/configure-authentication/okta#configure-group-synchronization-enterprise-only) -- [SAML](https://grafana.com/docs/grafana//setup-grafana/configure-security/configure-authentication/saml#configure-group-synchronization) +- [Azure AD OAuth](https://grafana.com/docs/grafana//setup-grafana/configure-security/configure-authentication/azuread/#team-sync-enterprise-only) +- [GitHub OAuth](https://grafana.com/docs/grafana//setup-grafana/configure-security/configure-authentication/github/#configure-team-synchronization) +- [Generic OAuth integration](https://grafana.com/docs/grafana//configure-security/configure-authentication/generic-oauth#configure-team-synchronization) +- [GitLab OAuth](https://grafana.com/docs/grafana//setup-grafana/configure-security/configure-authentication/gitlab/#configure-team-synchronization) +- [Google OAuth](https://grafana.com/docs/grafana//configure-security/configure-authentication/google#configure-team-synchronization) +- [LDAP](https://grafana.com/docs/grafana//setup-grafana/configure-security/configure-authentication/enhanced-ldap/#ldap-group-synchronization-for-teams) +- [Okta](https://grafana.com/docs/grafana//setup-grafana/configure-security/configure-authentication/okta#configure-team-synchronization-enterprise-only) +- [SAML](https://grafana.com/docs/grafana//setup-grafana/configure-security/configure-authentication/saml#configure-team-sync) ### Enhanced LDAP integration diff --git a/docs/sources/panels-visualizations/query-transform-data/transform-data/index.md b/docs/sources/panels-visualizations/query-transform-data/transform-data/index.md index cfc5a5e29e5..c0a4301d6ae 100644 --- a/docs/sources/panels-visualizations/query-transform-data/transform-data/index.md +++ b/docs/sources/panels-visualizations/query-transform-data/transform-data/index.md @@ -1189,29 +1189,11 @@ Use this transformation to address issues when a data source returns time series #### Available options -##### Multi-frame time series - -Use this option to transform the time series data frame from the wide format to the long format. This is particularly helpful when your data source delivers time series information in a format that needs to be reshaped for optimal compatibility with your visualization. - -**Example: Converting from wide to long format** - -| Timestamp | Value1 | Value2 | -| ------------------- | ------ | ------ | -| 2023-01-01 00:00:00 | 10 | 20 | -| 2023-01-01 01:00:00 | 15 | 25 | - -**Transformed to:** - -| Timestamp | Variable | Value | -| ------------------- | -------- | ----- | -| 2023-01-01 00:00:00 | Value1 | 10 | -| 2023-01-01 00:00:00 | Value2 | 20 | -| 2023-01-01 01:00:00 | Value1 | 15 | -| 2023-01-01 01:00:00 | Value2 | 25 | - ##### Wide time series -Select this option to transform the time series data frame from the long format to the wide format. If your data source returns time series data in a long format and your visualization requires a wide format, this transformation simplifies the process. +Select this option to transform the time series data frame from the long format to the wide format. + +A wide time series combines data into a single frame with one shared, ascending time field. Time fields do not repeat and multiple values extend in separate columns. **Example: Converting from long to wide format** @@ -1229,6 +1211,32 @@ Select this option to transform the time series data frame from the long format | 2023-01-01 00:00:00 | 10 | 20 | | 2023-01-01 01:00:00 | 15 | 25 | +##### Multi-frame time series + +Multi-frame time series break data into multiple frames that all contain two fields: a time field and a numeric value field. Time is always ascending. String values are represented as field labels. + +##### Long time series + +A long time series combines data to one frame, with the first field being an ascending time field. The time field might have duplicates. String values are in separate fields, and there might be more than one. + +**Example: Converting to long format** + +| Value1 | Value2 | Timestamp | +| ------ | ------ | ------------------- | +| 10 | 20 | 2023-01-03 00:00:00 | +| 30 | 40 | 2023-01-02 00:00:00 | +| 50 | 60 | 2023-01-01 00:00:00 | +| 70 | 80 | 2023-01-01 00:00:00 | + +**Transformed to:** + +| Timestamp | Value1 | Value2 | +| ------------------- | ------ | ------ | +| 2023-01-01 00:00:00 | 70 | 80 | +| 2023-01-01 01:00:00 | 50 | 60 | +| 2023-01-02 01:00:00 | 30 | 40 | +| 2023-01-03 01:00:00 | 10 | 20 | + ### Reduce Use this transformation to apply a calculation to each field in the data frame and return a single value. This transformation is particularly useful for consolidating multiple time series data into a more compact, summarized format. Time fields are removed when applying this transformation. diff --git a/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md b/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md index 786bdcd6bde..84bc6c9d3b3 100644 --- a/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md +++ b/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md @@ -150,7 +150,6 @@ Experimental features might be changed or removed without prior notice. | `lokiPredefinedOperations` | Adds predefined query operations to Loki query editor | | `awsDatasourcesTempCredentials` | Support temporary security credentials in AWS plugins for Grafana Cloud customers | | `mlExpressions` | Enable support for Machine Learning in server-side expressions | -| `metricsSummary` | Enables metrics summary queries in the Tempo data source | | `datasourceAPIServers` | Expose some datasources as apiservers. | | `provisioning` | Next generation provisioning... and git | | `permissionsFilterRemoveSubquery` | Alternative permission filter implementation that does not use subqueries for fetching the dashboard folder | @@ -164,7 +163,6 @@ Experimental features might be changed or removed without prior notice. | `disableClassicHTTPHistogram` | Disables classic HTTP Histogram (use with enableNativeHTTPHistogram) | | `kubernetesSnapshots` | Routes snapshot requests from /api to the /apis endpoint | | `kubernetesDashboards` | Use the kubernetes API in the frontend for dashboards | -| `kubernetesRestore` | Allow restoring objects in k8s | | `kubernetesClientDashboardsFolders` | Route the folder and dashboard service requests to k8s | | `datasourceQueryTypes` | Show query type endpoints in datasource API servers (currently hardcoded for testdata, expressions, and prometheus) | | `queryService` | Register /apis/query.grafana.app/ -- will eventually replace /api/ds/query | diff --git a/docs/sources/setup-grafana/configure-security/configure-authentication/azuread/index.md b/docs/sources/setup-grafana/configure-security/configure-authentication/azuread/index.md index fd1b2773749..fb0501919f2 100644 --- a/docs/sources/setup-grafana/configure-security/configure-authentication/azuread/index.md +++ b/docs/sources/setup-grafana/configure-security/configure-authentication/azuread/index.md @@ -390,14 +390,14 @@ This setting is ignored if multiple auth providers are configured to use auto lo auto_login = true ``` -### Group sync (Enterprise only) +### Team Sync (Enterprise only) -With group sync you can map your Entra ID groups to teams and roles in Grafana. This allows users to automatically be added to -the correct teams and be granted the correct roles in Grafana. +With Team Sync you can map your Entra ID groups to teams in Grafana so that your users will automatically be added to +the correct teams. You can reference Entra ID groups by group object ID, like `8bab1c86-8fba-33e5-2089-1d1c80ec267d`. -To learn more about group synchronization, refer to [Configure team sync](https://grafana.com/docs/grafana//setup-grafana/configure-security/configure-team-sync) and [Configure group attribute sync](https://grafana.com/docs/grafana//setup-grafana/configure-security/configure-group-attribute-sync). +To learn more, refer to the [Team Sync](https://grafana.com/docs/grafana//setup-grafana/configure-security/configure-team-sync) documentation. ## Common troubleshooting @@ -408,21 +408,24 @@ configuring Azure AD authentication in Grafana. To ensure that the token size doesn't exceed HTTP header size limits, Entra ID limits the number of object IDs that it includes in the groups claim. -If a user is member of more groups than the -overage limit (200), then -Entra ID does not emit the groups claim in the token and emits a group overage claim instead. +If a user is member of more groups than the coverage limit (200), then Entra ID does not emit the groups claim in the token and emits a group overage claim instead. > More information in [Groups overage claim](https://learn.microsoft.com/en-us/entra/identity-platform/id-token-claims-reference#groups-overage-claim) If Grafana receives a token with a group overage claim instead of a groups claim, Grafana attempts to retrieve the user's group membership by calling the included endpoint. -To ensure this functionality works correctly, you must enable [`force_use_graph_api`](./#force-fetching-groups-from-microsoft-graph-api) in your configuration. +The Entra ID `App registration` must include the following API permissions for group overage claim calls to succeed: + +| Permissions name | Type | Admin consent required | Status | +| ---------------------- | --------- | ---------------------- | ------- | +| `GroupMember.Read.All` | Delegated | Yes | Granted | +| `User.Read` | Delegated | No | Granted | + +Admin consent is required for the `GroupMember.Read.All` permission. To grant admin consent, navigate to **API permissions** in the **App registration** and select **Grant admin consent for [your-organization]**. {{% admonition type="note" %}} -The 'App registration' must include the `GroupMember.Read.All` API permission for group overage claim calls to succeed. - -Admin consent might be required for this permission. +You can make Grafana always get group information from the Microsoft Graph API by turning on the [`force_use_graph_api`](./#force-fetching-groups-from-microsoft-graph-api) setting in the configuration. {{% /admonition %}} #### Configure the required Graph API permissions @@ -433,6 +436,10 @@ Admin consent might be required for this permission. 1. Select **Delegated permissions**. 1. Under the **GroupMember** section, select **GroupMember.Read.All**. 1. Click **Add permissions**. +1. Select **Microsoft Graph** from the list of APIs. +1. Select **Delegated permissions**.. +1. In the **Select permissions** pane, under the **User** section, select **User.Read**. +1. Click the **Add permissions** button at the bottom of the page. {{% admonition type="note" %}} Admin consent may be required for this permission. diff --git a/docs/sources/setup-grafana/configure-security/configure-authentication/enhanced-ldap/index.md b/docs/sources/setup-grafana/configure-security/configure-authentication/enhanced-ldap/index.md index ee6f7c6335f..c8ec1ab03b6 100644 --- a/docs/sources/setup-grafana/configure-security/configure-authentication/enhanced-ldap/index.md +++ b/docs/sources/setup-grafana/configure-security/configure-authentication/enhanced-ldap/index.md @@ -28,16 +28,18 @@ The enhanced LDAP integration adds additional functionality on top of the [LDAP > To control user access with role-based permissions, refer to [role-based access control](../../../../administration/roles-and-permissions/access-control/). -## LDAP group synchronization +## LDAP group synchronization for teams -With enhanced LDAP integration, you can set up synchronization between LDAP groups and Grafana teams and roles. This enables users that are members -of certain LDAP groups to automatically be added to teams and gain roles in Grafana. - -The below example shows an LDAP group member mapped to a Grafana team. +With enhanced LDAP integration, you can set up synchronization between LDAP groups and teams. This enables LDAP users that are members +of certain LDAP groups to automatically be added or removed as members to certain teams in Grafana. ![LDAP group synchronization](/static/img/docs/enterprise/team_members_ldap.png) -To learn more about group synchronization, refer to [Configure team sync](https://grafana.com/docs/grafana//setup-grafana/configure-security/configure-team-sync) and [Configure group attribute sync](https://grafana.com/docs/grafana//setup-grafana/configure-security/configure-group-attribute-sync). +Grafana keeps track of all synchronized users in teams, and you can see which users have been synchronized from LDAP in the team members list, see `LDAP` label in screenshot. +This mechanism allows Grafana to remove an existing synchronized user from a team when its LDAP group membership changes. This mechanism also allows you to manually add +a user as member of a team, and it will not be removed when the user signs in. This gives you flexibility to combine LDAP group memberships and Grafana team memberships. + +[Learn more about team sync.](https://grafana.com/docs/grafana//setup-grafana/configure-security/configure-team-sync)
diff --git a/docs/sources/setup-grafana/configure-security/configure-authentication/generic-oauth/index.md b/docs/sources/setup-grafana/configure-security/configure-authentication/generic-oauth/index.md index 99e9abb057a..7a7612a35f6 100644 --- a/docs/sources/setup-grafana/configure-security/configure-authentication/generic-oauth/index.md +++ b/docs/sources/setup-grafana/configure-security/configure-authentication/generic-oauth/index.md @@ -127,7 +127,7 @@ To integrate your OAuth2 provider with Grafana using our Generic OAuth authentic c. Enable the refresh token on the provider if required. 1. [Configure role mapping](#configure-role-mapping). -1. Optional: [Configure group synchronization](#configure-group-synchronization). +1. Optional: [Configure team synchronization](https://grafana.com/docs/grafana/ **Note:** The `accessTokenExpirationCheck` feature toggle has been removed in Grafana v10.3.0 and the `use_refresh_token` configuration value will be used instead for configuring refresh token fetching and access token expiration check. +{{< admonition type="note" >}} +The `accessTokenExpirationCheck` feature toggle has been removed in Grafana v10.3.0 and the `use_refresh_token` configuration value will be used instead for configuring refresh token fetching and access token expiration check. +{{< /admonition >}} ### Configure role mapping @@ -310,21 +312,21 @@ org_attribute_path = roles org_mapping = org_foo:org_foo:Viewer org_bar:org_bar:Editor *:org_baz:Editor ``` -## Configure group synchronization +## Configure team synchronization {{< admonition type="note" >}} -Available in [Grafana Enterprise](https://grafana.com/docs/grafana//introduction/grafana-enterprise) and [Grafana Cloud](/docs/grafana-cloud/). +Available in [Grafana Enterprise](https://grafana.com/docs/grafana//introduction/grafana-enterprise/) and [Grafana Cloud](/docs/grafana-cloud/). {{< /admonition >}} -Grafana supports synchronization of OAuth2 groups with Grafana teams and roles. This allows automatically assigning users to the appropriate teams or automatically granting them the mapped roles. -Teams and roles get synchronized when the user logs in. +By using Team Sync, you can link your OAuth2 groups to teams within Grafana. This will automatically assign users to the appropriate teams. +Teams for each user are synchronized when the user logs in. Generic OAuth groups can be referenced by group ID, such as `8bab1c86-8fba-33e5-2089-1d1c80ec267d` or `myteam`. For information on configuring OAuth2 groups with Grafana using the `groups_attribute_path` configuration option, refer to [configuration options](#configuration-options). -To learn more about group synchronization, refer to [Configure team sync](https://grafana.com/docs/grafana//setup-grafana/configure-security/configure-team-sync) and [Configure group attribute sync](https://grafana.com/docs/grafana//setup-grafana/configure-security/configure-group-attribute-sync). +To learn more about Team Sync, refer to [Configure team sync](https://grafana.com/docs/grafana//setup-grafana/configure-security/configure-team-sync/). -#### Group attribute synchronization example +### Team synchronization example Configuration: diff --git a/docs/sources/setup-grafana/configure-security/configure-authentication/github/index.md b/docs/sources/setup-grafana/configure-security/configure-authentication/github/index.md index ffde83ab6d1..1ec6047e0de 100644 --- a/docs/sources/setup-grafana/configure-security/configure-authentication/github/index.md +++ b/docs/sources/setup-grafana/configure-security/configure-authentication/github/index.md @@ -49,7 +49,7 @@ Available in Public Preview in Grafana 10.4 behind the `ssoSettingsApi` feature As a Grafana Admin, you can configure GitHub OAuth client from within Grafana using the GitHub UI. To do this, navigate to **Administration > Authentication > GitHub** page and fill in the form. If you have a current configuration in the Grafana configuration file, the form will be pre-populated with those values. Otherwise the form will contain default values. -After you have filled in the form, click **Save** . If the save was successful, Grafana will apply the new configurations. +After you have filled in the form, click **Save**. If the save was successful, Grafana will apply the new configurations. If you need to reset changes you made in the UI back to the default values, click **Reset**. After you have reset the changes, Grafana will apply the configuration from the Grafana configuration file (if there is any configuration) or the default values. @@ -110,7 +110,7 @@ To configure GitHub authentication with Grafana, follow these steps: Review the list of other GitHub [configuration options](#configuration-options) and complete them, as necessary. 1. [Configure role mapping](#configure-role-mapping). -1. Optional: [Configure group synchronization](#configure-group-synchronization). +1. Optional: [Configure team synchronization](https://grafana.com/docs/grafana//setup-grafana/configure-security/configure-team-sync/). 1. Restart Grafana. You should now see a GitHub login button on the login page and be able to log in or sign up with your GitHub accounts. @@ -216,14 +216,14 @@ allowed_domains = mycompany.com mycompany.org role_attribute_path = [login=='octocat'][0] && 'GrafanaAdmin' || 'Viewer' ``` -## Configure group synchronization +## Configure team synchronization {{< admonition type="note" >}} -Available in [Grafana Enterprise](https://grafana.com/docs/grafana//introduction/grafana-enterprise) and [Grafana Cloud](/docs/grafana-cloud/). +Available in [Grafana Enterprise](https://grafana.com/docs/grafana//introduction/grafana-enterprise/) and Grafana Cloud. {{< /admonition >}} -Grafana supports synchronization of teams from your GitHub organization with Grafana teams and roles. This allows automatically assigning users to the appropriate teams or granting them the mapped roles. -Teams and roles get synchronized when the user logs in. +By using Team Sync, you can map teams from your GitHub organization to teams within Grafana. This will automatically assign users to the appropriate teams. +Teams for each user are synchronized when the user logs in. GitHub teams can be referenced in two ways: @@ -232,7 +232,7 @@ GitHub teams can be referenced in two ways: Examples: `https://github.com/orgs/grafana/teams/developers` or `@grafana/developers`. -To learn more about group synchronization, refer to [Configure team sync](https://grafana.com/docs/grafana//setup-grafana/configure-security/configure-team-sync) and [Configure group attribute sync](https://grafana.com/docs/grafana//setup-grafana/configure-security/configure-group-attribute-sync). +To learn more about Team Sync, refer to [Configure team sync](https://grafana.com/docs/grafana//setup-grafana/configure-security/configure-team-sync/). ## Configuration options diff --git a/docs/sources/setup-grafana/configure-security/configure-authentication/gitlab/index.md b/docs/sources/setup-grafana/configure-security/configure-authentication/gitlab/index.md index 230eeb1873f..4d350ceabd6 100644 --- a/docs/sources/setup-grafana/configure-security/configure-authentication/gitlab/index.md +++ b/docs/sources/setup-grafana/configure-security/configure-authentication/gitlab/index.md @@ -119,7 +119,7 @@ To configure GitLab authentication with Grafana, follow these steps: a. Set `use_refresh_token` to `true` in `[auth.gitlab]` section in Grafana configuration file. 1. [Configure role mapping](#configure-role-mapping). -1. Optional: [Configure group synchronization](#configure-group-synchronization). +1. Optional: [Configure team synchronization](https://grafana.com/docs/grafana//setup-grafana/configure-security/configure-team-sync/). 1. Restart Grafana. You should now see a GitLab login button on the login page and be able to log in or sign up with your GitLab accounts. @@ -242,20 +242,20 @@ use_pkce = true use_refresh_token = true ``` -## Configure group synchronization +## Configure team synchronization -{{< admonition type="note" >}} -Available in [Grafana Enterprise](https://grafana.com/docs/grafana//introduction/grafana-enterprise) and [Grafana Cloud](/docs/grafana-cloud/). -{{< /admonition >}} +{{% admonition type="note" %}} +Available in [Grafana Enterprise](https://grafana.com/docs/grafana//introduction/grafana-enterprise/) and [Grafana Cloud](/docs/grafana-cloud/). +{{% /admonition %}} -Grafana supports synchronization of GitLab groups with Grafana teams and roles. This allows automatically assigning users to the appropriate teams or granting them the mapped roles. -Teams and roles get synchronized when the user logs in. +By using Team Sync, you can map GitLab groups to teams within Grafana. This will automatically assign users to the appropriate teams. +Teams for each user are synchronized when the user logs in. GitLab groups are referenced by the group name. For example, `developers`. To reference a subgroup `frontend`, use `developers/frontend`. Note that in GitLab, the group or subgroup name does not always match its display name, especially if the display name contains spaces or special characters. Make sure you always use the group or subgroup name as it appears in the URL of the group or subgroup. -To learn more about group synchronization, refer to [Configure team sync](https://grafana.com/docs/grafana//setup-grafana/configure-security/configure-team-sync) and [Configure group attribute sync](https://grafana.com/docs/grafana//setup-grafana/configure-security/configure-group-attribute-sync). +To learn more about Team Sync, refer to [Configure team sync](https://grafana.com/docs/grafana//setup-grafana/configure-security/configure-team-sync/). ## Configuration options diff --git a/docs/sources/setup-grafana/configure-security/configure-authentication/google/index.md b/docs/sources/setup-grafana/configure-security/configure-authentication/google/index.md index ce130da6e0d..9931151c7e5 100644 --- a/docs/sources/setup-grafana/configure-security/configure-authentication/google/index.md +++ b/docs/sources/setup-grafana/configure-security/configure-authentication/google/index.md @@ -165,15 +165,11 @@ This setting is ignored if multiple auth providers are configured to use auto lo auto_login = true ``` -### Configure group synchronization +### Configure team synchronization -{{< admonition type="note" >}} -Available in [Grafana Enterprise](https://grafana.com/docs/grafana//introduction/grafana-enterprise) and [Grafana Cloud](/docs/grafana-cloud/). -{{< /admonition >}} +With team sync, you can easily add users to teams by utilizing their Google groups. To set up team sync for Google OAuth, refer to the following example. -Grafana supports syncing users to teams and roles based on their Google groups. - -To set up group sync for Google OAuth: +To set up team sync for Google OAuth: 1. Enable the Google Cloud Identity API on your [organization's dashboard](https://console.cloud.google.com/apis/api/cloudidentity.googleapis.com/). @@ -187,9 +183,10 @@ To set up group sync for Google OAuth: scopes = openid email profile https://www.googleapis.com/auth/cloud-identity.groups.readonly ``` -The external group ID for a Google group is the group's email address, such as `dev@grafana.com`. +1. Configure team sync in your Grafana team's `External group sync` tab. + The external group ID for a Google group is the group's email address, such as `dev@grafana.com`. -To learn more about how to configure group synchronization, refer to [Configure team sync](../../configure-team-sync/) and [Configure group attribute sync](https://grafana.com/docs/grafana//setup-grafana/configure-security/configure-group-attribute-sync) documentation. +To learn more about Team Sync, refer to [Configure Team Sync](https://grafana.com/docs/grafana//setup-grafana/configure-security/configure-team-sync/). #### Configure allowed groups diff --git a/docs/sources/setup-grafana/configure-security/configure-authentication/jwt/index.md b/docs/sources/setup-grafana/configure-security/configure-authentication/jwt/index.md index 29dfa1f17ae..26ac14c8188 100644 --- a/docs/sources/setup-grafana/configure-security/configure-authentication/jwt/index.md +++ b/docs/sources/setup-grafana/configure-security/configure-authentication/jwt/index.md @@ -202,13 +202,19 @@ Grafana checks for the presence of a role using the [JMESPath](http://jmespath.o To assign the role to a specific organization include the `X-Grafana-Org-Id` header along with your JWT when making API requests to Grafana. To learn more about the header, please refer to the [documentation](../../../../developers/http_api/#x-grafana-org-id-header). -### JMESPath examples +### Configure role mapping -To ease configuration of a proper JMESPath expression, you can test/evaluate expressions with custom payloads at http://jmespath.org/. +Unless `skip_org_role_sync` option is enabled, the user's role will be set to the role retrieved from the JWT. -### Role mapping +The user's role is retrieved using a [JMESPath](http://jmespath.org/examples.html) expression from the `role_attribute_path` configuration option. +To map the server administrator role, use the `allow_assign_grafana_admin` configuration option. -If the `role_attribute_path` property does not return a role, then the user is assigned the `Viewer` role by default. You can disable the role assignment by setting `role_attribute_strict = true`. It denies user access if no role or an invalid role is returned. +If no valid role is found, the user is assigned the role specified by [the `auto_assign_org_role` option](../../../configure-grafana/#auto_assign_org_role). +You can disable this default role assignment by setting `role_attribute_strict = true`. This setting denies user access if no role or an invalid role is returned after evaluating the `role_attribute_path` and the `org_mapping` expressions. + +You can use the `org_attribute_path` and `org_mapping` configuration options to assign the user to organizations and specify their role. For more information, refer to [Org roles mapping example](#org-roles-mapping-example). If both org role mapping (`org_mapping`) and the regular role mapping (`role_attribute_path`) are specified, then the user will get the highest of the two mapped roles. + +To ease configuration of a proper JMESPath expression, go to [JMESPath](http://jmespath.org/) to test and evaluate expressions with custom payloads. **Basic example:** @@ -224,9 +230,9 @@ Payload: } ``` -Config: +Configuration: -```bash +```ini role_attribute_path = role ``` @@ -251,12 +257,40 @@ Payload: } ``` -Config: +Configuration: -```bash +```ini role_attribute_path = contains(info.roles[*], 'admin') && 'Admin' || contains(info.roles[*], 'editor') && 'Editor' || 'Viewer' ``` +**Org roles mapping example** + +In the following example, the , the user has been granted the role of a `Viewer` in the `org_foo` organization, and the role of an `Editor` in the `org_bar` and `org_baz` organizations. + +Payload: + +```json +{ + ... + "info": { + ... + "orgs": [ + "engineer", + "admin", + ], + ... + }, + ... +} +``` + +Configuration: + +```ini +org_attribute_path = info.orgs +org_mapping = engineer:org_foo:Viewer admin:org_bar:Editor *:org_baz:Editor +``` + ### Grafana Admin Role If the `role_attribute_path` property returns a `GrafanaAdmin` role, Grafana Admin is not assigned by default, instead the `Admin` role is assigned. To allow `Grafana Admin` role to be assigned set `allow_assign_grafana_admin = true`. diff --git a/docs/sources/setup-grafana/configure-security/configure-authentication/keycloak/index.md b/docs/sources/setup-grafana/configure-security/configure-authentication/keycloak/index.md index 347d5fbc4dc..c27aa80ae63 100644 --- a/docs/sources/setup-grafana/configure-security/configure-authentication/keycloak/index.md +++ b/docs/sources/setup-grafana/configure-security/configure-authentication/keycloak/index.md @@ -94,7 +94,7 @@ roles ``` {{% admonition type="warning" %}} -These scopes do not add group claims to the id_token. Without group claims, group synchronization will not work. Group synchronization is covered further down in this document. +These scopes do not add group claims to the `id_token`. Without group claims, teamsync will not work. Teamsync is covered further down in this document. {{% /admonition %}} 3. For role mapping to work with the example configuration above, @@ -106,18 +106,16 @@ editor viewer ``` -## Group synchronization +## Teamsync -{{< admonition type="note" >}} -Available in [Grafana Enterprise](https://grafana.com/docs/grafana//introduction/grafana-enterprise) and [Grafana Cloud](/docs/grafana-cloud/). -{{< /admonition >}} +{{% admonition type="note" %}} +Available in [Grafana Enterprise](https://grafana.com/docs/grafana//introduction/grafana-enterprise/) and [Grafana Cloud](/docs/grafana-cloud/). +{{% /admonition %}} -By using group synchronization, you can link your Keycloak groups to teams and roles within Grafana. This allows automatically assigning users to the appropriate teams or granting them the mapped roles. -This is useful if you want to give your users access to specific resources based on their group membership. -Teams and roles get synchronized when the user logs in. +[Teamsync](https://grafana.com/docs/grafana//setup-grafana/configure-security/configure-team-sync/) is a feature that allows you to map groups from your identity provider to Grafana teams. This is useful if you want to give your users access to specific dashboards or folders based on their group membership. -To enable group synchronization, you need to add a `groups` mapper to the client configuration in Keycloak. -This will add the `groups` claim to the id_token. You can then use the `groups` claim to map groups to teams and roles in Grafana. +To enable teamsync, you need to add a `groups` mapper to the client configuration in Keycloak. +This will add the `groups` claim to the id_token. You can then use the `groups` claim to map groups to teams in Grafana. 1. In the client configuration, head to `Mappers` and create a mapper with the following settings: @@ -143,8 +141,6 @@ If you use nested groups containing special characters such as quotes or colons, groups_attribute_path = reverse("Global:department") ``` -To learn more about how to configure group synchronization, refer to [Configure team sync](../../configure-team-sync/) and [Configure group attribute sync](https://grafana.com/docs/grafana//setup-grafana/configure-security/configure-group-attribute-sync) documentation. - ## Enable Single Logout To enable Single Logout, you need to add the following option to the configuration of Grafana: diff --git a/docs/sources/setup-grafana/configure-security/configure-authentication/okta/index.md b/docs/sources/setup-grafana/configure-security/configure-authentication/okta/index.md index 4cc50a36f2a..a159e01bd13 100644 --- a/docs/sources/setup-grafana/configure-security/configure-authentication/okta/index.md +++ b/docs/sources/setup-grafana/configure-security/configure-authentication/okta/index.md @@ -169,7 +169,7 @@ To integrate your Okta OIDC provider with Grafana using our Okta OIDC integratio 1. Optional: [Configure a refresh token](#configure-a-refresh-token). 1. [Configure role mapping](#configure-role-mapping). -1. Optional: [Configure group synchronization](#configure-group-synchronization-enterprise-only). +1. Optional: [Configure team synchronization](#configure-team-synchronization-enterprise-only). 1. Restart Grafana. You should now see a Okta OIDC login button on the login page and be able to log in or sign up with your OIDC provider. @@ -243,18 +243,20 @@ org_attribute_path = groups org_mapping = ["Group 1:org_foo:Viewer", "Group 2:org_bar:Editor", "*:3:Editor"] ``` -### Configure group synchronization (Enterprise only) +### Configure team synchronization (Enterprise only) -{{< admonition type="note" >}} -Available in [Grafana Enterprise](https://grafana.com/docs/grafana//introduction/grafana-enterprise) and [Grafana Cloud](/docs/grafana-cloud/). -{{< /admonition >}} +{{% admonition type="note" %}} +Available in [Grafana Enterprise](https://grafana.com/docs/grafana//introduction/grafana-enterprise/) and [Grafana Cloud](../../../../introduction/grafana-cloud). +{{% /admonition %}} -By using group synchronization, you can link your Okta groups to teams and roles within Grafana. This allows automatically assigning users to the appropriate teams or granting them the mapped roles. -Teams and roles get synchronized when the user logs in. +By using Team Sync, you can link your Okta groups to teams within Grafana. This will automatically assign users to the appropriate teams. + +Map your Okta groups to teams in Grafana so that your users will automatically be added to +the correct teams. Okta groups can be referenced by group names, like `Admins` or `Editors`. -To learn more about how to configure group synchronization, refer to [Configure team sync](../../configure-team-sync/) and [Configure group attribute sync](https://grafana.com/docs/grafana//setup-grafana/configure-security/configure-group-attribute-sync) documentation. +To learn more about Team Sync, refer to [Configure Team Sync](https://grafana.com/docs/grafana//setup-grafana/configure-security/configure-team-sync/). ## Configuration options diff --git a/docs/sources/setup-grafana/configure-security/configure-authentication/saml-ui/index.md b/docs/sources/setup-grafana/configure-security/configure-authentication/saml-ui/index.md index 833759f5a2c..29bc37cfbc1 100644 --- a/docs/sources/setup-grafana/configure-security/configure-authentication/saml-ui/index.md +++ b/docs/sources/setup-grafana/configure-security/configure-authentication/saml-ui/index.md @@ -131,8 +131,8 @@ http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress ![image](https://github.com/user-attachments/assets/23910ab8-20ec-4dfd-8ef6-7dbaec51ac90) -You also need to configure the **Groups attribute** field if you want to use group synchronization. Group sync allows you to automatically map users to Grafana teams or role-based access control roles based on their SAML group membership. -To learn more about how to configure group synchronization, refer to [Configure team sync](../../configure-team-sync/) and [Configure group attribute sync](https://grafana.com/docs/grafana//setup-grafana/configure-security/configure-group-attribute-sync) documentation. +You also need to configure the **Groups attribute** field if you want to use team sync. Team sync automatically maps users to Grafana teams based on their SAML group membership. +Learn more about [team sync](../../configure-team-sync) and [configuring team sync for SAML](../saml#configure-team-sync). 1. If you want to automatically assign users' roles based on their SAML roles, complete the **Role mapping** section. diff --git a/docs/sources/setup-grafana/configure-security/configure-authentication/saml/index.md b/docs/sources/setup-grafana/configure-security/configure-authentication/saml/index.md index a04761c8b13..2c1d895a594 100644 --- a/docs/sources/setup-grafana/configure-security/configure-authentication/saml/index.md +++ b/docs/sources/setup-grafana/configure-security/configure-authentication/saml/index.md @@ -445,15 +445,24 @@ This setting is ignored if multiple auth providers are configured to use auto lo auto_login = true ``` -### Configure group synchronization +### Configure team sync -Group synchronization allows you to map user groups from an identity provider to Grafana teams and roles. +To use SAML Team sync, set [`assertion_attribute_groups`](../../../configure-grafana/enterprise-configuration#assertion_attribute_groups) to the attribute name where you store user groups. Then Grafana will use attribute values extracted from SAML assertion to add user into the groups with the same name configured on the External group sync tab. -To use SAML group synchronization, set [`assertion_attribute_groups`](/docs/grafana//setup-grafana/configure-grafana/enterprise-configuration/#assertion_attribute_groups) to the attribute name where you store user groups. -Then Grafana will use attribute values extracted from SAML assertion to add user to Grafana teams and grant them roles. +{{% admonition type="warning" %}} +Grafana requires the SAML groups attribute to be configured with distinct `AttributeValue` elements for each group. Do not include multiple groups within a single `AttributeValue` delimited by a comma or any other character. Failure to do so will prevent correct group parsing. Example: + +```xml + + admins_group + division_1 + +``` + +{{% /admonition %}} {{% admonition type="note" %}} -Team sync allows you sync users from SAML to Grafana teams, but you must create teams in Grafana before you can use this feature. It does not automatically create teams in Grafana. +Teamsync allows you sync users from SAML to Grafana teams. It does not automatically create teams in Grafana. You need to create teams in Grafana before you can use this feature. {{% /admonition %}} Given the following partial SAML assertion: @@ -483,12 +492,12 @@ The configuration would look like this: assertion_attribute_groups = groups ``` -The following `External Group ID`s would be valid for configuring team sync or role sync in Grafana: +The following `External Group ID`s would be valid for input in the desired team's _External group sync_ tab: - `admins_group` - `division_1` -To learn more about how to configure group synchronization, refer to [Configure team sync](/docs/grafana//setup-grafana/configure-security/configure-group-attribute-sync/) and [Configure group attribute sync](/docs/grafana//setup-grafana/configure-security/configure-group-attribute-sync) documentation. +[Learn more about Team Sync](https://grafana.com/docs/grafana//setup-grafana/configure-security/configure-team-sync/) ### Configure role sync diff --git a/docs/sources/setup-grafana/configure-security/configure-group-attribute-sync.md b/docs/sources/setup-grafana/configure-security/configure-group-attribute-sync.md deleted file mode 100644 index ff963122050..00000000000 --- a/docs/sources/setup-grafana/configure-security/configure-group-attribute-sync.md +++ /dev/null @@ -1,88 +0,0 @@ ---- -description: Learn how to use group attribute sync to synchronize between groups in your authentication provider and Grafana RBAC roles. -labels: - products: - - cloud - - enterprise -title: Configure group attribute sync -weight: 1000 ---- - -# Configure group attribute sync - -Group attribute sync allows you to manage user permissions in Grafana based on group membership sourced from the user's identity provider (IdP). -Groups are mapped to [fixed](https://grafana.com/docs/grafana//administration/roles-and-permissions/access-control#fixed-roles) and [custom](https://grafana.com/docs/grafana//administration/roles-and-permissions/access-control#custom-roles) role-based access control roles in Grafana. - -> **Note:** Available in [Grafana Enterprise](https://grafana.com/docs/grafana//introduction/grafana-enterprise) and [Grafana Cloud](/docs/grafana-cloud/). - -{{% admonition type="note" %}} -This feature is currently in [private preview](https://grafana.com/docs/release-life-cycle/#private-preview) and behind the `groupAttributeSync` feature toggle. Please contact support to have this feature enabled. -{{% /admonition %}} - -When a user logs in, Grafana checks the user's external group memberships and the configured group to role mappings to assign the corresponding roles to the user. -If the user's group memberships change or a new mapping is created, the user's role assignments are updated the next time the user logs in. -If a group mapping is removed, the role assignment to users for the group mapping is revoked immediately. - -Role mappings are tied to organizations, so you can have different mappings for different organizations. - -## Supported providers - -- [Azure AD](https://grafana.com/docs/grafana//setup-grafana/configure-security/configure-authentication/azuread#group-sync-enterprise-only) -- [Generic OAuth integration](https://grafana.com/docs/grafana//setup-grafana/configure-security/configure-authentication/generic-oauth#configure-group-synchronization) -- [GitHub OAuth](https://grafana.com/docs/grafana//setup-grafana/configure-security/configure-authentication/github#configure-group-synchronization) -- [GitLab OAuth](https://grafana.com/docs/grafana//setup-grafana/configure-security/configure-authentication/gitlab#configure-group-synchronization) -- [Google OAuth](https://grafana.com/docs/grafana//setup-grafana/configure-security/configure-authentication/google#configure-group-synchronization) -- [LDAP](https://grafana.com/docs/grafana//setup-grafana/configure-security/configure-authentication/enhanced-ldap#ldap-group-synchronization) -- [Okta](https://grafana.com/docs/grafana//setup-grafana/configure-security/configure-authentication/okta#configure-group-synchronization-enterprise-only) -- [SAML](https://grafana.com/docs/grafana//setup-grafana/configure-security/configure-authentication/saml#configure-group-synchronization) - -## Create role mappings for a new group - -For information about creating group mappings via the API, refer to [create group mappings reference](https://grafana.com/docs/grafana//developers/http_api/group_attribute_sync#create-group-mappings). - -### Before you begin - -Ensure you have permission to create and update group mappings. By default, the organization administrator role is required to create and edit group mappings. For more information about user permissions, refer to [roles and permissions](https://grafana.com/docs/grafana//administration/roles-and-permissions). - -### To create mappings between an external group and RBAC roles - -1. Sign in to Grafana and click **Administration** in the left-side menu. -1. Click **Users and access**. -1. Click **External group sync**. -1. Click **New**. -1. Insert the group identifier for the group that you want to map. -1. Use the role picker to select the roles that you want to map to the group and click **Update**. -1. Click **Save**. - -## Update role mappings for a group - -For information about updating group mappings via the API, refer to [update group mappings reference](https://grafana.com/docs/grafana//developers/http_api/group_attribute_sync#update-group-mappings). - -### Before you begin - -Ensure you have permission to update group mappings. By default, the organization administrator role is required to edit group mappings. For more information about user permissions, refer to [roles and permissions](https://grafana.com/docs/grafana//administration/roles-and-permissions). - -### To update role mappings for an external group - -1. Sign in to Grafana and click **Administration** in the left-side menu. -1. Click **Users and access**. -1. Click **External group sync**. -1. Find the group whose mappings you want to update. -1. Click on the role picker corresponding to the group and select the roles that you want to map. -1. Click **Apply**. - -## Remove role mappings for a group - -For information about deleting group mappings via the API, refer to [delete group mappings reference](https://grafana.com/docs/grafana//developers/http_api/group_attribute_sync#delete-group-mappings). - -### Before you begin - -Ensure you have permission to update group mappings. By default, the organization administrator role is required to edit group mappings. For more information about user permissions, refer to [roles and permissions](https://grafana.com/docs/grafana//administration/roles-and-permissions). - -### To remove role mappings for an external group - -1. Sign in to Grafana and click **Administration** in the left-side menu. -1. Click **Users and access**. -1. Click **External group sync**. -1. Find the group whose mappings you want to remove. -1. Click on the trash bin icon corresponding to the group mappings you want to remove. diff --git a/docs/sources/setup-grafana/configure-security/configure-security-hardening/index.md b/docs/sources/setup-grafana/configure-security/configure-security-hardening/index.md index b06fad81904..133a4626fc3 100644 --- a/docs/sources/setup-grafana/configure-security/configure-security-hardening/index.md +++ b/docs/sources/setup-grafana/configure-security/configure-security-hardening/index.md @@ -14,17 +14,17 @@ title: Configure security hardening Security hardening enables you to apply additional security, which can help stop certain vulnerabilities from being exploited by a malicious attacker. -{{% admonition type="note" %}} +{{< admonition type="note" >}} These settings are available in the [grafana.ini configuration file](../../configure-grafana/#configuration-file-location). To apply changes to the configuration file, restart the Grafana server. -{{% /admonition %}} +{{< /admonition >}} ## Additional security for cookies If Grafana uses HTTPS, you can further secure the cookie that the system uses to authenticate access to the web UI. By applying additional security to the cookie, you might mitigate certain attacks that result from an attacker obtaining the cookie value. -{{% admonition type="note" %}} +{{< admonition type="note" >}} Grafana must use HTTPS for the following configurations to work properly. -{{% /admonition %}} +{{< /admonition >}} ### Add a secure attribute to cookies @@ -33,6 +33,7 @@ To provide mitigation against some MITM attacks, add the `Secure` attribute to t Example: ```toml +[security] # Set to true if you host Grafana behind HTTPS. The default value is false. cookie_secure = true ``` @@ -44,13 +45,14 @@ To mitigate almost all CSRF-attacks, set the _cookie_samesite_ option to `strict Example: ```toml +[security] # set cookie SameSite attribute. defaults to `lax`. can be set to "lax", "strict", "none" and "disabled" cookie_samesite = strict ``` -{{% admonition type="note" %}} +{{< admonition type="note" >}} By setting the SameSite attribute to "strict," only the user clicks within a Grafana instance work. The default option, "lax," does not produce this behavior. -{{% /admonition %}} +{{< /admonition >}} ### Add a prefix to cookie names @@ -60,6 +62,7 @@ Add a prefix to the current cookie name with either `__Secure-` or `__Host-` whe Example: ```toml +[auth] # Login cookie name login_cookie_name = __Host-grafana_session ``` @@ -75,6 +78,7 @@ A content security policy (CSP) is an HTTP response header that controls how the Example: ```toml +[security] # Enable adding the Content-Security-Policy header to your requests. # CSP enables you to control the resources the user agent can load and helps prevent XSS attacks. content_security_policy = true @@ -114,17 +118,19 @@ If set to `true`, the Grafana server hides the running version number for unauth Example: ```toml +[anonymous.auth] # mask the Grafana version number for unauthenticated users hide_version = true ``` ### Enable auth for metrics -By default, metrics from Grafana itself can be accessed without authentication. This can lead to inadvertent information leakage. +By default, metrics from Grafana itself can be accessed without authentication. This can lead to information leakage. -To enable basic authentication for the metrics endpoint: +Example: ```toml +[metrics] # If both are set, basic auth will be required for the metrics endpoints basic_auth_username = basic_auth_password = @@ -137,6 +143,7 @@ If set to `true`, the Grafana server redirects requests that have a Host-header Example: ```toml +[sever] # Redirect to correct domain if host header does not match domain # Prevents DNS rebinding attacks enforce_domain = true diff --git a/docs/sources/setup-grafana/configure-security/configure-team-sync.md b/docs/sources/setup-grafana/configure-security/configure-team-sync.md index dfe6d4f2c99..70544186767 100644 --- a/docs/sources/setup-grafana/configure-security/configure-team-sync.md +++ b/docs/sources/setup-grafana/configure-security/configure-team-sync.md @@ -28,14 +28,14 @@ This mechanism allows Grafana to remove an existing synchronized user from a tea ## Supported providers - [Auth Proxy](https://grafana.com/docs/grafana//setup-grafana/configure-security/configure-authentication/auth-proxy/#team-sync-enterprise-only) -- [Azure AD](https://grafana.com/docs/grafana//setup-grafana/configure-security/configure-authentication/azuread#group-sync-enterprise-only) -- [Generic OAuth integration](https://grafana.com/docs/grafana//setup-grafana/configure-security/configure-authentication/generic-oauth#configure-group-synchronization) -- [GitHub OAuth](https://grafana.com/docs/grafana//setup-grafana/configure-security/configure-authentication/github#configure-group-synchronization) -- [GitLab OAuth](https://grafana.com/docs/grafana//setup-grafana/configure-security/configure-authentication/gitlab#configure-group-synchronization) -- [Google OAuth](https://grafana.com/docs/grafana//setup-grafana/configure-security/configure-authentication/google#configure-group-synchronization) -- [LDAP](https://grafana.com/docs/grafana//setup-grafana/configure-security/configure-authentication/enhanced-ldap#ldap-group-synchronization) -- [Okta](https://grafana.com/docs/grafana//setup-grafana/configure-security/configure-authentication/okta#configure-group-synchronization-enterprise-only) -- [SAML](https://grafana.com/docs/grafana//setup-grafana/configure-security/configure-authentication/saml#configure-group-synchronization) +- [Azure AD](https://grafana.com/docs/grafana//setup-grafana/configure-authentication/azuread#team-sync-enterprise-only) +- [Generic OAuth integration](https://grafana.com/docs/grafana//setup-grafana/configure-authentication/generic-oauth#configure-team-synchronization) +- [GitHub OAuth](https://grafana.com/docs/grafana//setup-grafana/configure-authentication/github#configure-team-synchronization) +- [GitLab OAuth](https://grafana.com/docs/grafana//setup-grafana/configure-authentication/gitlab#configure-team-synchronization) +- [Google OAuth](https://grafana.com/docs/grafana//setup-grafana/configure-authentication/google#configure-team-sync-for-google-oauth) +- [LDAP](https://grafana.com/docs/grafana//setup-grafana/configure-authentication/enhanced-ldap#ldap-group-synchronization-for-teams) +- [Okta](https://grafana.com/docs/grafana//setup-grafana/configure-authentication/okta#configure-team-synchronization-enterprise-only) +- [SAML](https://grafana.com/docs/grafana//setup-grafana/configure-authentication/saml#configure-team-sync) ## Synchronize a Grafana team with an external group diff --git a/e2e/test-plugins/grafana-extensionstest-app/package.json b/e2e/test-plugins/grafana-extensionstest-app/package.json index 0bef81cbf48..2e7c91d08e6 100644 --- a/e2e/test-plugins/grafana-extensionstest-app/package.json +++ b/e2e/test-plugins/grafana-extensionstest-app/package.json @@ -1,6 +1,6 @@ { "name": "@test-plugins/extensions-test-app", - "version": "11.6.0-pre", + "version": "12.0.0-pre", "private": true, "scripts": { "build": "webpack -c ./webpack.config.ts --env production", diff --git a/e2e/test-plugins/grafana-test-datasource/package.json b/e2e/test-plugins/grafana-test-datasource/package.json index e4472e7a923..fce57ed8757 100644 --- a/e2e/test-plugins/grafana-test-datasource/package.json +++ b/e2e/test-plugins/grafana-test-datasource/package.json @@ -1,6 +1,6 @@ { "name": "@test-plugins/grafana-e2etest-datasource", - "version": "11.6.0-pre", + "version": "12.0.0-pre", "private": true, "scripts": { "build": "webpack -c ./webpack.config.ts --env production", diff --git a/go.mod b/go.mod index c58a948ebc8..ae380cc710d 100644 --- a/go.mod +++ b/go.mod @@ -18,7 +18,7 @@ require ( github.com/Azure/azure-storage-blob-go v0.15.0 // @grafana/grafana-backend-group github.com/Azure/go-autorest/autorest v0.11.29 // @grafana/grafana-backend-group github.com/Azure/go-autorest/autorest/adal v0.9.24 // @grafana/grafana-backend-group - github.com/BurntSushi/toml v1.4.0 // @grafana/identity-access-team + github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c // @grafana/identity-access-team github.com/DATA-DOG/go-sqlmock v1.5.2 // @grafana/grafana-search-and-storage github.com/Masterminds/semver v1.5.0 // @grafana/grafana-backend-group github.com/Masterminds/semver/v3 v3.3.0 // @grafana/grafana-developer-enablement-squad @@ -44,7 +44,7 @@ require ( github.com/dlmiddlecote/sqlstats v1.0.2 // @grafana/grafana-backend-group github.com/dolthub/go-mysql-server v0.19.1-0.20250206012855-c216e59c21a7 // @grafana/grafana-datasources-core-services github.com/dolthub/vitess v0.0.0-20250123002143-3b45b8cacbfa // @grafana/grafana-datasources-core-services - github.com/fatih/color v1.17.0 // @grafana/grafana-backend-group + github.com/fatih/color v1.18.0 // @grafana/grafana-backend-group github.com/fullstorydev/grpchan v1.1.1 // @grafana/grafana-backend-group github.com/gchaincl/sqlhooks v1.3.0 // @grafana/grafana-search-and-storage github.com/getkin/kin-openapi v0.129.0 // @grafana/grafana-app-platform-squad @@ -559,6 +559,7 @@ require ( ) require ( + github.com/1NCE-GmbH/grpc-go-pool v0.0.0-20231117122434-2a5bb974daa2 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.49.0 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.49.0 // indirect github.com/RoaringBitmap/roaring/v2 v2.4.5 // indirect diff --git a/go.sum b/go.sum index 4272556ca58..82affcd7be1 100644 --- a/go.sum +++ b/go.sum @@ -643,6 +643,8 @@ filippo.io/age v1.2.1 h1:X0TZjehAZylOIj4DubWYU1vWQxv9bJpo+Uu2/LGhi1o= filippo.io/age v1.2.1/go.mod h1:JL9ew2lTN+Pyft4RiNGguFfOpewKwSHm5ayKD/A4004= gioui.org v0.0.0-20210308172011-57750fc8a0a6/go.mod h1:RSH6KIUZ0p2xy5zHDxgAM4zumjgTw83q2ge/PI+yyw8= git.sr.ht/~sbinet/gg v0.3.1/go.mod h1:KGYtlADtqsqANL9ueOFkWymvzUvLMQllU5Ixo+8v3pc= +github.com/1NCE-GmbH/grpc-go-pool v0.0.0-20231117122434-2a5bb974daa2 h1:qFYgLH2zZe3WHpQgUrzeazC+ebDebwAQqS9yE1cP5Bs= +github.com/1NCE-GmbH/grpc-go-pool v0.0.0-20231117122434-2a5bb974daa2/go.mod h1:09/ALd1AXCTCOfcJYD8+jIYKmFmi6PVCkTsipC18F7E= github.com/Azure/azure-pipeline-go v0.2.3 h1:7U9HBg1JFK3jHl5qmo4CTZKFTVgMwdFHMVtCdfBE21U= github.com/Azure/azure-pipeline-go v0.2.3/go.mod h1:x841ezTBIMG6O3lAcl8ATHnsOPVl2bqk7S3ta6S6u4k= github.com/Azure/azure-sdk-for-go v23.2.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= @@ -704,8 +706,9 @@ github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mo github.com/AzureAD/microsoft-authentication-library-for-go v1.3.2 h1:kYRSnvJju5gYVyhkij+RTJ/VR6QIUaCfWeaFm2ycsjQ= github.com/AzureAD/microsoft-authentication-library-for-go v1.3.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0= github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= +github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c h1:pxW6RcqyfI9/kWtOwnv/G+AzdKuy2ZrqINhenH4HyNs= +github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/Code-Hex/go-generics-cache v1.5.1 h1:6vhZGc5M7Y/YD8cIUcY8kcuQLB4cHR7U+0KMqAA0KcU= github.com/Code-Hex/go-generics-cache v1.5.1/go.mod h1:qxcC9kRVrct9rHeiYpFWSoW1vxyillCVzX13KZG8dl4= @@ -1149,8 +1152,8 @@ github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5Kwzbycv github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= -github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4= -github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI= +github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= +github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= diff --git a/go.work b/go.work index 905df9f4e80..6d6bd805ff0 100644 --- a/go.work +++ b/go.work @@ -8,7 +8,6 @@ use ( ./.citools/bra ./.citools/cog ./.citools/cue - ./.citools/drone ./.citools/golangci-lint ./.citools/jb ./.citools/lefthook diff --git a/go.work.sum b/go.work.sum index fb21639617c..b4a8e63ad7a 100644 --- a/go.work.sum +++ b/go.work.sum @@ -508,6 +508,7 @@ gioui.org v0.0.0-20210308172011-57750fc8a0a6 h1:K72hopUosKG3ntOPNG4OzzbuhxGuVf06 git.sr.ht/~sbinet/gg v0.5.0 h1:6V43j30HM623V329xA9Ntq+WJrMjDxRjuAB1LFWF5m8= git.sr.ht/~sbinet/gg v0.5.0/go.mod h1:G2C0eRESqlKhS7ErsNey6HHrqU1PwsnCQlekFi9Q2Oo= github.com/99designs/basicauth-go v0.0.0-20160802081356-2a93ba0f464d h1:j6oB/WPCigdOkxtuPl1VSIiLpy7Mdsu6phQffbF19Ng= +github.com/99designs/httpsignatures-go v0.0.0-20170731043157-88528bf4ca7e h1:rl2Aq4ZODqTDkeSqQBy+fzpZPamacO1Srp8zq7jf2Sc= 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/Azure/azure-amqp-common-go/v3 v3.2.3 h1:uDF62mbd9bypXWi19V1bN5NZEO84JqgmI5G73ibAmrk= @@ -800,6 +801,8 @@ github.com/dave/patsy v0.0.0-20210517141501-957256f50cba h1:1o36L4EKbZzazMk8iGC4 github.com/dave/patsy v0.0.0-20210517141501-957256f50cba/go.mod h1:qfR88CgEGLoiqDaE+xxDCi5QA5v4vUoW0UCX2Nd5Tlc= github.com/dave/rebecca v0.9.1 h1:jxVfdOxRirbXL28vXMvUvJ1in3djwkVKXCq339qhBL0= github.com/dave/rebecca v0.9.1/go.mod h1:N6XYdMD/OKw3lkF3ywh8Z6wPGuwNFDNtWYEMFWEmXBA= +github.com/dchest/uniuri v1.2.0 h1:koIcOUdrTIivZgSLhHQvKgqdWZq5d7KdMEWF1Ud6+5g= +github.com/dchest/uniuri v1.2.0/go.mod h1:fSzm4SLHzNZvWLvWJew423PhAzkpNQYq+uNLq4kxhkY= github.com/denisenkom/go-mssqldb v0.0.0-20190515213511-eb9f6a1743f3 h1:tkum0XDgfR0jcVVXuTsYv/erY2NnEDqwRojbxR1rBYA= github.com/denverdino/aliyungo v0.0.0-20190125010748-a747050bb1ba h1:p6poVbjHDkKa+wtC8frBMwQtT3BmqGYBjzMwJ63tuR4= github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= @@ -812,6 +815,8 @@ github.com/dlclark/regexp2 v1.4.0 h1:F1rxgk7p4uKjwIQxBs9oAXe5CqrXlCduYEJvrF4u93E github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI= github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= +github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8= +github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c h1:+pKlWGMw7gf6bQ+oDZB4KHQFypsfjYlq/C4rfL7D3g8= github.com/docker/go-metrics v0.0.1 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQV8= github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw= @@ -824,6 +829,9 @@ github.com/dolthub/sqllogictest/go v0.0.0-20201107003712-816f3ae12d81 h1:7/v8q9X github.com/dolthub/sqllogictest/go v0.0.0-20201107003712-816f3ae12d81/go.mod h1:siLfyv2c92W1eN/R4QqG/+RjjX5W2+gCTRjZxBjI3TY= github.com/dolthub/swiss v0.2.1 h1:gs2osYs5SJkAaH5/ggVJqXQxRXtWshF6uE0lgR/Y3Gw= github.com/dolthub/swiss v0.2.1/go.mod h1:8AhKZZ1HK7g18j7v7k6c5cYIGEZJcPn0ARsai8cUrh0= +github.com/drone/funcmap v0.0.0-20220929084810-72602997d16f h1:/jEs7lulqVO2u1+XI5rW4oFwIIusxuDOVKD9PAzlW2E= +github.com/drone/funcmap v0.0.0-20220929084810-72602997d16f/go.mod h1:nDRkX7PHq+p39AD5/usv3KZMerxZTYU/9rfLS5IDspU= +github.com/drone/signal v1.0.0 h1:NrnM2M/4yAuU/tXs6RP1a1ZfxnaHwYkd0kJurA1p6uI= github.com/dvyukov/go-fuzz v0.0.0-20210103155950-6a8e9d1f2415 h1:q1oJaUPdmpDm/VyXosjgPgr6wS7c5iV2p0PwJD73bUI= github.com/dvyukov/go-fuzz v0.0.0-20210103155950-6a8e9d1f2415/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw= github.com/eapache/go-resiliency v1.6.0 h1:CqGDTLtpwuWKn6Nj3uNUdflaq+/kIPsg0gfNzHton30= @@ -859,7 +867,6 @@ github.com/fluent/fluent-bit-go v0.0.0-20230731091245-a7a013e2473c h1:yKN46XJHYC github.com/fluent/fluent-bit-go v0.0.0-20230731091245-a7a013e2473c/go.mod h1:L92h+dgwElEyUuShEwjbiHjseW410WIcNz+Bjutc8YQ= github.com/fogleman/gg v1.3.0 h1:/7zJX8F6AaYQc57WQCyN9cAIz+4bCJGO9B+dyW29am8= github.com/form3tech-oss/jwt-go v3.2.2+incompatible h1:TcekIExNqud5crz4xD2pavyTgWiPvpYe4Xau31I0PRk= -github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/fsouza/fake-gcs-server v1.7.0 h1:Un0BXUXrRWYSmYyC1Rqm2e2WJfTPyDy/HGMz31emTi8= github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa h1:RDBNVkRviHZtvDvId8XSGPu3rmpmSe+wKRcEWNgsfWU= github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= @@ -940,6 +947,8 @@ github.com/google/cel-go v0.22.0/go.mod h1:BuznPXXfQDpXKWQ9sPW3TzlAJN5zzFe+i9tIs github.com/google/generative-ai-go v0.18.0 h1:6ybg9vOCLcI/UpBBYXOTVgvKmcUKFRNj+2Cj3GnebSo= github.com/google/generative-ai-go v0.18.0/go.mod h1:JYolL13VG7j79kM5BtHz4qwONHkeJQzOCkKXnpqtS/E= github.com/google/go-github v17.0.0+incompatible h1:N0LgJ1j65A7kfXrZnUDaYCs/Sf4rEjNlfyDHW9dolSY= +github.com/google/go-jsonnet v0.18.0 h1:/6pTy6g+Jh1a1I2UMoAODkqELFiVIdOxbNwv0DDzoOg= +github.com/google/go-jsonnet v0.18.0/go.mod h1:C3fTzyVJDslXdiTqw/bTFk7vSGyCtH3MGRbDfvEwGd0= github.com/google/go-pkcs11 v0.3.0 h1:PVRnTgtArZ3QQqTGtbtjtnIkzl2iY2kt24yqbrf7td8= github.com/google/go-pkcs11 v0.3.0/go.mod h1:6eQoGcuNJpa7jnd5pMGdkSaQpNDYvPlXWMcjXXThLlY= github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw= @@ -1047,6 +1056,8 @@ github.com/j-keck/arping v0.0.0-20160618110441-2cf9dc699c56 h1:742eGXur0715JMq73 github.com/jackc/fake v0.0.0-20150926172116-812a484cc733 h1:vr3AYkKovP8uR8AvSGGUK1IDqRa5lAAvEkZG1LKaCRc= github.com/jackc/pgx v3.2.0+incompatible h1:0Vihzu20St42/UDsvZGdNE6jak7oi/UOeMzwMPHkgFY= github.com/jackc/pgx/v5 v5.7.1/go.mod h1:e7O26IywZZ+naJtWWos6i6fvWK+29etgITqrqHLfoZA= +github.com/jackspirou/syscerts v0.0.0-20160531025014-b68f5469dff1 h1:9Xm8CKtMZIXgcopfdWk/qZ1rt0HjMgfMR9nxxSeK6vk= +github.com/jackspirou/syscerts v0.0.0-20160531025014-b68f5469dff1/go.mod h1:zuHl3Hh+e9P6gmBPvcqR1HjkaWHC/csgyskg6IaFKFo= github.com/jaegertracing/jaeger v1.57.0 h1:3wDtUUPs6NRYH7+d+y8MilDkLHdpPrVlQ2wbcsA62bs= github.com/jaegertracing/jaeger v1.57.0/go.mod h1:p/1fxIU9hKHl7qEhKC72p2ZYVhvvZvNB73y6V7YyuTs= github.com/jedib0t/go-pretty/v6 v6.2.4 h1:wdaj2KHD2W+mz8JgJ/Q6L/T5dB7kyqEFI16eLq7GEmk= @@ -1058,6 +1069,8 @@ github.com/jhump/gopoet v0.1.0 h1:gYjOPnzHd2nzB37xYQZxj4EIQNpBrBskRqQQ3q4ZgSg= github.com/jhump/goprotoc v0.5.0 h1:Y1UgUX+txUznfqcGdDef8ZOVlyQvnV0pKWZH08RmZuo= github.com/joeshaw/multierror v0.0.0-20140124173710-69b34d4ec901 h1:rp+c0RAYOWj8l6qbCUTSiRLG/iKnW3K3/QfPPuSsBt4= github.com/joeshaw/multierror v0.0.0-20140124173710-69b34d4ec901/go.mod h1:Z86h9688Y0wesXCyonoVr47MasHilkuLMqGhRZ4Hpak= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/jon-whit/go-grpc-prometheus v1.4.0 h1:/wmpGDJcLXuEjXryWhVYEGt9YBRhtLwFEN7T+Flr8sw= github.com/jon-whit/go-grpc-prometheus v1.4.0/go.mod h1:iTPm+Iuhh3IIqR0iGZ91JJEg5ax6YQEe1I0f6vtBuao= github.com/joncrlsn/dque v0.0.0-20211108142734-c2ef48c5192a h1:sfe532Ipn7GX0V6mHdynBk393rDmqgI0QmjLK7ct7TU= @@ -1173,6 +1186,7 @@ github.com/mozilla/tls-observatory v0.0.0-20210609171429-7bc42856d2e5 h1:0KqC6/s github.com/mozilla/tls-observatory v0.0.0-20210609171429-7bc42856d2e5/go.mod h1:FUqVoUPHSEdDR0MnFM3Dh8AU0pZHLXUD127SAJGER/s= github.com/mrunalp/fileutils v0.5.0 h1:NKzVxiH7eSk+OQ4M+ZYW1K6h27RUV3MI6NUTsHhU6Z4= github.com/nakagami/firebirdsql v0.0.0-20190310045651-3c02a58cfed8 h1:P48LjvUQpTReR3TQRbxSeSBsMXzfK0uol7eRcr7VBYQ= +github.com/natessilva/dag v0.0.0-20180124060714-7194b8dcc5c4 h1:dnMxwus89s86tI8rcGVp2HwZzlz7c5o92VOy7dSckBQ= github.com/nats-io/nats.go v1.34.0 h1:fnxnPCNiwIG5w08rlMcEKTUw4AV/nKyGCOJE8TdhSPk= github.com/nats-io/nats.go v1.34.0/go.mod h1:Ubdu4Nh9exXdSz0RVWRFBbRfrbSxOYd26oF0wkWclB8= github.com/nats-io/nkeys v0.4.7 h1:RwNJbbIdYCoClSDNY7QVKZlyb/wfT6ugvFCiKy6vDvI= @@ -1250,6 +1264,7 @@ github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 h1:onHthvaw9LFnH4t2D github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58/go.mod h1:DXv8WO4yhMYhSNPKjeNKa5WY9YCIEBRbNzFFPJbWO6Y= github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30 h1:BHT1/DKsYDGkUgQ2jmMaozVcdk+sVfz0+1ZJq4zkWgw= github.com/pborman/uuid v1.2.0 h1:J7Q5mO4ysT1dv8hyrUGHb9+ooztCXu1D8MY8DZYsu3g= +github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= github.com/petar/GoLLRB v0.0.0-20130427215148-53be0d36a84c h1:AwcgVYzW1T+QuJ2fc55ceOSCiVaOpdYUNpFj9t7+n9U= github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= @@ -1316,9 +1331,9 @@ github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e h1:MZM7FHLqUHYI0Y/mQAt github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041 h1:llrF3Fs4018ePo4+G/HV/uQUqEI1HMDjCeOf2V6puPc= github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= github.com/spf13/afero v1.10.0/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= -github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g= github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= +github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad h1:fiWzISvDn0Csy5H0iwgAuJGQTUpVfEMJJd4nRFXogbc= github.com/stefanberger/go-pkcs11uri v0.0.0-20201008174630-78d3cae3a980 h1:lIOOHPEbXzO3vnmx2gok1Tfs31Q8GQqKLc8vVqyQq/I= github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= @@ -1728,6 +1743,7 @@ google.golang.org/grpc v1.69.2/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7 google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0 h1:M1YKkFIboKNieVO5DLUEVzQfGwJD30Nv2jfUgzb5UcE= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= google.golang.org/protobuf v1.36.0/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= diff --git a/lerna.json b/lerna.json index f39bf8763c4..e0645ae04bc 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { "$schema": "node_modules/lerna/schemas/lerna-schema.json", "npmClient": "yarn", - "version": "11.6.0-pre" + "version": "12.0.0-pre" } diff --git a/package.json b/package.json index 358559cd0e8..2d04f81fdc0 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "license": "AGPL-3.0-only", "private": true, "name": "grafana", - "version": "11.6.0-pre", + "version": "12.0.0-pre", "repository": "github:grafana/grafana", "scripts": { "build": "NODE_ENV=production nx exec --verbose -- webpack --config scripts/webpack/webpack.prod.js --progress", @@ -277,8 +277,8 @@ "@grafana/prometheus": "workspace:*", "@grafana/runtime": "workspace:*", "@grafana/saga-icons": "workspace:*", - "@grafana/scenes": "6.5.2", - "@grafana/scenes-react": "6.5.2", + "@grafana/scenes": "6.5.3", + "@grafana/scenes-react": "6.5.3", "@grafana/schema": "workspace:*", "@grafana/sql": "workspace:*", "@grafana/ui": "workspace:*", diff --git a/packages/grafana-data/package.json b/packages/grafana-data/package.json index a366ceedb0f..82a8a8c46f9 100644 --- a/packages/grafana-data/package.json +++ b/packages/grafana-data/package.json @@ -2,7 +2,7 @@ "author": "Grafana Labs", "license": "Apache-2.0", "name": "@grafana/data", - "version": "11.6.0-pre", + "version": "12.0.0-pre", "description": "Grafana Data Library", "keywords": [ "typescript" @@ -36,9 +36,10 @@ }, "dependencies": { "@braintree/sanitize-url": "7.0.1", - "@grafana/schema": "11.6.0-pre", + "@grafana/schema": "12.0.0-pre", "@types/d3-interpolate": "^3.0.0", "@types/string-hash": "1.1.3", + "@types/systemjs": "6.15.1", "d3-interpolate": "3.0.1", "date-fns": "4.1.0", "dompurify": "3.2.4", diff --git a/packages/grafana-data/src/types/config.ts b/packages/grafana-data/src/types/config.ts index c982e0fe25c..a85914216ec 100644 --- a/packages/grafana-data/src/types/config.ts +++ b/packages/grafana-data/src/types/config.ts @@ -199,6 +199,7 @@ export interface GrafanaConfig { trustedTypesDefaultPolicyEnabled: boolean; cspReportOnlyEnabled: boolean; liveEnabled: boolean; + liveMessageSizeLimit: number; /** @deprecated Use `theme2` instead. */ theme: GrafanaTheme; theme2: GrafanaTheme2; diff --git a/packages/grafana-data/src/types/featureToggles.gen.ts b/packages/grafana-data/src/types/featureToggles.gen.ts index 43e3ccea8ef..46f4da08a67 100644 --- a/packages/grafana-data/src/types/featureToggles.gen.ts +++ b/packages/grafana-data/src/types/featureToggles.gen.ts @@ -271,10 +271,6 @@ export interface FeatureToggles { */ traceQLStreaming?: boolean; /** - * Enables metrics summary queries in the Tempo data source - */ - metricsSummary?: boolean; - /** * Expose some datasources as apiservers. */ datasourceAPIServers?: boolean; @@ -390,10 +386,6 @@ export interface FeatureToggles { */ kubernetesDashboards?: boolean; /** - * Allow restoring objects in k8s - */ - kubernetesRestore?: boolean; - /** * Route the folder and dashboard service requests to k8s */ kubernetesClientDashboardsFolders?: boolean; @@ -818,6 +810,7 @@ export interface FeatureToggles { unifiedStorageSearchSprinkles?: boolean; /** * Enable permission filtering on unified storage search + * @default true */ unifiedStorageSearchPermissionFiltering?: boolean; /** @@ -1064,4 +1057,8 @@ export interface FeatureToggles { * Enables the unified storage history pruner */ unifiedStorageHistoryPruner?: boolean; + /** + * Enables the unified storage grpc connection pool + */ + unifiedStorageGrpcConnectionPool?: boolean; } diff --git a/packages/grafana-data/src/types/live.ts b/packages/grafana-data/src/types/live.ts index 565a6d3fcae..7f4af737f05 100644 --- a/packages/grafana-data/src/types/live.ts +++ b/packages/grafana-data/src/types/live.ts @@ -12,6 +12,7 @@ export enum LiveChannelScope { Plugin = 'plugin', // namespace = plugin name (singleton works for apps too) Grafana = 'grafana', // namespace = feature Stream = 'stream', // namespace = id for the managed data stream + Watch = 'watch', // namespace = k8s group we will watch } /** diff --git a/packages/grafana-e2e-selectors/package.json b/packages/grafana-e2e-selectors/package.json index dc8a0541bcf..7525eb21469 100644 --- a/packages/grafana-e2e-selectors/package.json +++ b/packages/grafana-e2e-selectors/package.json @@ -2,7 +2,7 @@ "author": "Grafana Labs", "license": "Apache-2.0", "name": "@grafana/e2e-selectors", - "version": "11.6.0-pre", + "version": "12.0.0-pre", "description": "Grafana End-to-End Test Selectors Library", "keywords": [ "cli", diff --git a/packages/grafana-eslint-rules/package.json b/packages/grafana-eslint-rules/package.json index fab662fb0ac..449c41eff0f 100644 --- a/packages/grafana-eslint-rules/package.json +++ b/packages/grafana-eslint-rules/package.json @@ -1,7 +1,7 @@ { "name": "@grafana/eslint-plugin", "description": "ESLint rules for use within the Grafana repo. Not suitable (or supported) for external use.", - "version": "11.6.0-pre", + "version": "12.0.0-pre", "main": "./index.cjs", "author": "Grafana Labs", "license": "Apache-2.0", diff --git a/packages/grafana-flamegraph/package.json b/packages/grafana-flamegraph/package.json index 9ce7c7e25c5..7093bbcd4b1 100644 --- a/packages/grafana-flamegraph/package.json +++ b/packages/grafana-flamegraph/package.json @@ -2,7 +2,7 @@ "author": "Grafana Labs", "license": "Apache-2.0", "name": "@grafana/flamegraph", - "version": "11.6.0-pre", + "version": "12.0.0-pre", "description": "Grafana flamegraph visualization component", "keywords": [ "grafana", @@ -44,8 +44,8 @@ ], "dependencies": { "@emotion/css": "11.13.5", - "@grafana/data": "11.6.0-pre", - "@grafana/ui": "11.6.0-pre", + "@grafana/data": "12.0.0-pre", + "@grafana/ui": "12.0.0-pre", "@leeoniya/ufuzzy": "1.0.18", "d3": "^7.8.5", "lodash": "4.17.21", diff --git a/packages/grafana-icons/package.json b/packages/grafana-icons/package.json index 08bb3641efb..a7079f36870 100644 --- a/packages/grafana-icons/package.json +++ b/packages/grafana-icons/package.json @@ -1,6 +1,6 @@ { "name": "@grafana/saga-icons", - "version": "11.6.0-pre", + "version": "12.0.0-pre", "private": true, "description": "Icons for Grafana", "author": "Grafana Labs", diff --git a/packages/grafana-o11y-ds-frontend/package.json b/packages/grafana-o11y-ds-frontend/package.json index 9bf98a363fc..f6c2a2c4977 100644 --- a/packages/grafana-o11y-ds-frontend/package.json +++ b/packages/grafana-o11y-ds-frontend/package.json @@ -3,7 +3,7 @@ "license": "AGPL-3.0-only", "name": "@grafana/o11y-ds-frontend", "private": true, - "version": "11.6.0-pre", + "version": "12.0.0-pre", "description": "Library to manage traces in Grafana.", "sideEffects": false, "repository": { @@ -18,12 +18,12 @@ }, "dependencies": { "@emotion/css": "11.13.5", - "@grafana/data": "11.6.0-pre", - "@grafana/e2e-selectors": "11.6.0-pre", + "@grafana/data": "12.0.0-pre", + "@grafana/e2e-selectors": "12.0.0-pre", "@grafana/plugin-ui": "0.10.1", - "@grafana/runtime": "11.6.0-pre", - "@grafana/schema": "11.6.0-pre", - "@grafana/ui": "11.6.0-pre", + "@grafana/runtime": "12.0.0-pre", + "@grafana/schema": "12.0.0-pre", + "@grafana/ui": "12.0.0-pre", "react-select": "5.10.0", "react-use": "17.6.0", "rxjs": "7.8.1", diff --git a/packages/grafana-plugin-configs/package.json b/packages/grafana-plugin-configs/package.json index 384e4c2d42c..d88cf2ea261 100644 --- a/packages/grafana-plugin-configs/package.json +++ b/packages/grafana-plugin-configs/package.json @@ -2,7 +2,7 @@ "name": "@grafana/plugin-configs", "description": "Shared dependencies and files for core plugins", "private": true, - "version": "11.6.0-pre", + "version": "12.0.0-pre", "dependencies": { "tslib": "2.8.1" }, diff --git a/packages/grafana-prometheus/package.json b/packages/grafana-prometheus/package.json index bd1378e8870..7b76365cdc8 100644 --- a/packages/grafana-prometheus/package.json +++ b/packages/grafana-prometheus/package.json @@ -2,7 +2,7 @@ "author": "Grafana Labs", "license": "AGPL-3.0-only", "name": "@grafana/prometheus", - "version": "11.6.0-pre", + "version": "12.0.0-pre", "description": "Grafana Prometheus Library", "keywords": [ "typescript" @@ -38,12 +38,12 @@ "dependencies": { "@emotion/css": "11.13.5", "@floating-ui/react": "0.27.5", - "@grafana/data": "11.6.0-pre", - "@grafana/e2e-selectors": "11.6.0-pre", + "@grafana/data": "12.0.0-pre", + "@grafana/e2e-selectors": "12.0.0-pre", "@grafana/plugin-ui": "0.10.1", - "@grafana/runtime": "11.6.0-pre", - "@grafana/schema": "11.6.0-pre", - "@grafana/ui": "11.6.0-pre", + "@grafana/runtime": "12.0.0-pre", + "@grafana/schema": "12.0.0-pre", + "@grafana/ui": "12.0.0-pre", "@hello-pangea/dnd": "17.0.0", "@leeoniya/ufuzzy": "1.0.18", "@lezer/common": "1.2.3", diff --git a/packages/grafana-prometheus/src/components/PromQueryField.tsx b/packages/grafana-prometheus/src/components/PromQueryField.tsx index 4c619f88b7a..05cdda9864c 100644 --- a/packages/grafana-prometheus/src/components/PromQueryField.tsx +++ b/packages/grafana-prometheus/src/components/PromQueryField.tsx @@ -3,6 +3,7 @@ import { css, cx } from '@emotion/css'; import { PureComponent, ReactNode } from 'react'; import { + getDefaultTimeRange, isDataFrame, LocalStorageValueProvider, QueryEditorProps, @@ -249,6 +250,7 @@ class PromQueryFieldClass extends PureComponent diff --git a/packages/grafana-prometheus/src/components/PrometheusMetricsBrowser.test.tsx b/packages/grafana-prometheus/src/components/PrometheusMetricsBrowser.test.tsx index df14c9e02c8..602aeca8f6a 100644 --- a/packages/grafana-prometheus/src/components/PrometheusMetricsBrowser.test.tsx +++ b/packages/grafana-prometheus/src/components/PrometheusMetricsBrowser.test.tsx @@ -2,7 +2,7 @@ import { render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import { createTheme } from '@grafana/data'; +import { createTheme, getDefaultTimeRange, TimeRange } from '@grafana/data'; import PromQlLanguageProvider from '../language_provider'; @@ -146,7 +146,7 @@ describe('PrometheusMetricsBrowser', () => { const setupProps = (): BrowserProps => { const mockLanguageProvider = { start: () => Promise.resolve(), - getLabelValues: (name: string) => { + getLabelValues: (timeRange: TimeRange, name: string) => { switch (name) { case 'label1': return ['value1-1', 'value1-2']; @@ -163,7 +163,7 @@ describe('PrometheusMetricsBrowser', () => { // The metrics browser expects both label names and label values. // The labels endpoint with match does not supply label values // and so using it breaks the metrics browser. - fetchSeriesLabels: (selector: string) => { + fetchSeriesLabels: (timeRange: TimeRange, selector: string) => { switch (selector) { case '{label1="value1-1"}': return { label1: ['value1-1'], label2: ['value2-1'], label3: ['value3-1'] }; @@ -187,6 +187,7 @@ describe('PrometheusMetricsBrowser', () => { lastUsedLabels: [], storeLastUsedLabels: () => {}, deleteLastUsedLabels: () => {}, + timeRange: getDefaultTimeRange(), }; return defaults; diff --git a/packages/grafana-prometheus/src/components/PrometheusMetricsBrowser.tsx b/packages/grafana-prometheus/src/components/PrometheusMetricsBrowser.tsx index 1eeb59d35ed..db3ec6c60b4 100644 --- a/packages/grafana-prometheus/src/components/PrometheusMetricsBrowser.tsx +++ b/packages/grafana-prometheus/src/components/PrometheusMetricsBrowser.tsx @@ -4,7 +4,7 @@ import { ChangeEvent } from 'react'; import * as React from 'react'; import { FixedSizeList } from 'react-window'; -import { GrafanaTheme2, TimeRange } from '@grafana/data'; +import { getDefaultTimeRange, GrafanaTheme2, TimeRange } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; import { BrowserLabel as PromLabel, @@ -409,11 +409,15 @@ export class UnthemedPrometheusMetricsBrowser extends React.Component { + return this.props.timeRange ?? getDefaultTimeRange(); + }; + async fetchValues(name: string, selector: string) { const { languageProvider } = this.props; this.updateLabelState(name, { loading: true }, `Fetching values for ${name}`); try { - let rawValues = await languageProvider.getLabelValues(name); + let rawValues = await languageProvider.getLabelValues(this.getTimeRange(), name); // If selector changed, clear loading state and discard result by returning early if (selector !== buildSelector(this.state.labels)) { this.updateLabelState(name, { loading: false }); @@ -444,7 +448,12 @@ export class UnthemedPrometheusMetricsBrowser extends React.Component ({ label: variable, value: variable })); + let timeRange = range; + if (!timeRange) { + timeRange = getDefaultTimeRange(); + } + if (!metric) { // get all the labels - datasource.getTagKeys({ filters: [] }).then((labelNames: Array<{ text: string }>) => { + datasource.getTagKeys({ timeRange, filters: [] }).then((labelNames: Array<{ text: string }>) => { const names = labelNames.map(({ text }) => ({ label: text, value: text })); setLabels(names, variables); }); @@ -122,13 +127,15 @@ export const PromVariableQueryEditor = ({ onChange, query, datasource, range }: const labelToConsider = [{ label: '__name__', op: '=', value: metric }]; const expr = promQueryModeller.renderLabels(labelToConsider); - datasource.languageProvider.fetchLabelsWithMatch(expr).then((labelsIndex: Record) => { - const labelNames = Object.keys(labelsIndex); - const names = labelNames.map((value) => ({ label: value, value: value })); - setLabels(names, variables); - }); + datasource.languageProvider + .fetchLabelsWithMatch(timeRange, expr) + .then((labelsIndex: Record) => { + const labelNames = Object.keys(labelsIndex); + const names = labelNames.map((value) => ({ label: value, value: value })); + setLabels(names, variables); + }); } - }, [datasource, qryType, metric]); + }, [datasource, qryType, metric, range]); const onChangeWithVariableString = ( updateVar: { [key: string]: QueryType | string }, @@ -302,6 +309,7 @@ export const PromVariableQueryEditor = ({ onChange, query, datasource, range }: datasource={datasource} onChange={metricsLabelsChange} variableEditor={true} + timeRange={range ?? getDefaultTimeRange()} /> )} diff --git a/packages/grafana-prometheus/src/components/monaco-query-field/MonacoQueryField.tsx b/packages/grafana-prometheus/src/components/monaco-query-field/MonacoQueryField.tsx index 97e49df416b..d6c86499c25 100644 --- a/packages/grafana-prometheus/src/components/monaco-query-field/MonacoQueryField.tsx +++ b/packages/grafana-prometheus/src/components/monaco-query-field/MonacoQueryField.tsx @@ -105,7 +105,7 @@ const MonacoQueryField = (props: Props) => { // we need only one instance of `overrideServices` during the lifetime of the react component const overrideServicesRef = useRef(getOverrideServices()); const containerRef = useRef(null); - const { languageProvider, history, onBlur, onRunQuery, initialValue, placeholder, datasource } = props; + const { languageProvider, history, onBlur, onRunQuery, initialValue, placeholder, datasource, timeRange } = props; const lpRef = useLatest(languageProvider); const historyRef = useLatest(history); @@ -155,7 +155,7 @@ const MonacoQueryField = (props: Props) => { historyProvider: historyRef.current, languageProvider: lpRef.current, }); - const completionProvider = getCompletionProvider(monaco, dataProvider); + const completionProvider = getCompletionProvider(monaco, dataProvider, timeRange); // completion-providers in monaco are not registered directly to editor-instances, // they are registered to languages. this makes it hard for us to have diff --git a/packages/grafana-prometheus/src/components/monaco-query-field/MonacoQueryFieldProps.ts b/packages/grafana-prometheus/src/components/monaco-query-field/MonacoQueryFieldProps.ts index 4ca3fd894ed..ac2995273e5 100644 --- a/packages/grafana-prometheus/src/components/monaco-query-field/MonacoQueryFieldProps.ts +++ b/packages/grafana-prometheus/src/components/monaco-query-field/MonacoQueryFieldProps.ts @@ -1,5 +1,5 @@ // Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/components/monaco-query-field/MonacoQueryFieldProps.ts -import { HistoryItem } from '@grafana/data'; +import { HistoryItem, TimeRange } from '@grafana/data'; import { PrometheusDatasource } from '../../datasource'; import type PromQlLanguageProvider from '../../language_provider'; @@ -17,4 +17,5 @@ export type Props = { onRunQuery: (value: string) => void; onBlur: (value: string) => void; datasource: PrometheusDatasource; + timeRange: TimeRange; }; diff --git a/packages/grafana-prometheus/src/components/monaco-query-field/monaco-completion-provider/completions.test.ts b/packages/grafana-prometheus/src/components/monaco-query-field/monaco-completion-provider/completions.test.ts index 7a97ba98740..a778cf74300 100644 --- a/packages/grafana-prometheus/src/components/monaco-query-field/monaco-completion-provider/completions.test.ts +++ b/packages/grafana-prometheus/src/components/monaco-query-field/monaco-completion-provider/completions.test.ts @@ -2,6 +2,7 @@ import { config } from '@grafana/runtime'; import { SUGGESTIONS_LIMIT } from '../../../language_provider'; import { FUNCTIONS } from '../../../promql'; +import { getMockTimeRange } from '../../../test/__mocks__/datasource'; import { filterMetricNames, getCompletions } from './completions'; import { DataProvider, type DataProviderParams } from './data_provider'; @@ -183,6 +184,8 @@ function getSuggestionCountForSituation(situationType: MetricNameSituation, metr } describe.each(metricNameCompletionSituations)('metric name completions in situation %s', (situationType) => { + const timeRange = getMockTimeRange(); + it('should return completions for all metric names when the number of metric names is at or below the limit', async () => { jest.spyOn(dataProvider, 'getAllMetricNames').mockReturnValue(metrics.atLimit); const expectedCompletionsCount = getSuggestionCountForSituation(situationType, metrics.atLimit.length); @@ -192,12 +195,12 @@ describe.each(metricNameCompletionSituations)('metric name completions in situat // No text input dataProvider.monacoSettings.setInputInRange(''); - let completions = await getCompletions(situation, dataProvider); + let completions = await getCompletions(situation, dataProvider, timeRange); expect(completions).toHaveLength(expectedCompletionsCount); // With text input (use fuzzy search) dataProvider.monacoSettings.setInputInRange('name_1'); - completions = await getCompletions(situation, dataProvider); + completions = await getCompletions(situation, dataProvider, timeRange); expect(completions?.length).toBeLessThanOrEqual(expectedCompletionsCount); }); @@ -210,12 +213,12 @@ describe.each(metricNameCompletionSituations)('metric name completions in situat // Complex query dataProvider.monacoSettings.setInputInRange('metric name one two three four five'); - let completions = await getCompletions(situation, dataProvider); + let completions = await getCompletions(situation, dataProvider, timeRange); expect(completions.length).toBeLessThanOrEqual(expectedCompletionsCount); // Simple query with fuzzy match dataProvider.monacoSettings.setInputInRange('metric_name_'); - completions = await getCompletions(situation, dataProvider); + completions = await getCompletions(situation, dataProvider, timeRange); expect(completions.length).toBeLessThanOrEqual(expectedCompletionsCount); }); @@ -227,19 +230,19 @@ describe.each(metricNameCompletionSituations)('metric name completions in situat // Do not cross the metrics names threshold jest.spyOn(dataProvider, 'getAllMetricNames').mockReturnValueOnce(metrics.atLimit); dataProvider.monacoSettings.setInputInRange('name_1'); - await getCompletions(situation, dataProvider); + await getCompletions(situation, dataProvider, timeRange); expect(dataProvider.monacoSettings.suggestionsIncomplete).toBe(false); // Cross the metric names threshold, without text input jest.spyOn(dataProvider, 'getAllMetricNames').mockReturnValueOnce(metrics.beyondLimit); dataProvider.monacoSettings.setInputInRange(''); - await getCompletions(situation, dataProvider); + await getCompletions(situation, dataProvider, timeRange); expect(dataProvider.monacoSettings.suggestionsIncomplete).toBe(true); // Cross the metric names threshold, with text input jest.spyOn(dataProvider, 'getAllMetricNames').mockReturnValueOnce(metrics.beyondLimit); dataProvider.monacoSettings.setInputInRange('name_1'); - await getCompletions(situation, dataProvider); + await getCompletions(situation, dataProvider, timeRange); expect(dataProvider.monacoSettings.suggestionsIncomplete).toBe(true); }); @@ -253,7 +256,7 @@ describe.each(metricNameCompletionSituations)('metric name completions in situat // Test with a complex query (> 4 terms) dataProvider.monacoSettings.setInputInRange('metric name 1 with extra terms more'); - const completions = await getCompletions(situation, dataProvider); + const completions = await getCompletions(situation, dataProvider, timeRange); const metricCompletions = completions.filter((c) => c.type === 'METRIC_NAME'); expect(metricCompletions.some((c) => c.label === 'metric_name_1_with_extra_terms')).toBe(true); @@ -268,7 +271,7 @@ describe.each(metricNameCompletionSituations)('metric name completions in situat // Test with multiple terms dataProvider.monacoSettings.setInputInRange('metric name 1 2 3 4 5'); - const completions = await getCompletions(situation, dataProvider); + const completions = await getCompletions(situation, dataProvider, timeRange); const expectedCompletionsCount = getSuggestionCountForSituation(situationType, metrics.beyondLimit.length); expect(completions.length).toBeLessThanOrEqual(expectedCompletionsCount); @@ -308,6 +311,8 @@ describe('Label value completions', () => { }); }); + const timeRange = getMockTimeRange(); + it('should not escape special characters when between quotes', async () => { const situation: Situation = { type: 'IN_LABEL_SELECTOR_WITH_LABEL_NAME', @@ -316,7 +321,7 @@ describe('Label value completions', () => { otherLabels: [], }; - const completions = await getCompletions(situation, dataProvider); + const completions = await getCompletions(situation, dataProvider, timeRange); expect(completions).toHaveLength(4); expect(completions[0].insertText).toBe('value1'); @@ -333,7 +338,7 @@ describe('Label value completions', () => { otherLabels: [], }; - const completions = await getCompletions(situation, dataProvider); + const completions = await getCompletions(situation, dataProvider, timeRange); expect(completions).toHaveLength(4); expect(completions[0].insertText).toBe('"value1"'); @@ -350,6 +355,8 @@ describe('Label value completions', () => { }); }); + const timeRange = getMockTimeRange(); + it('should escape special characters when between quotes', async () => { const situation: Situation = { type: 'IN_LABEL_SELECTOR_WITH_LABEL_NAME', @@ -358,7 +365,7 @@ describe('Label value completions', () => { otherLabels: [], }; - const completions = await getCompletions(situation, dataProvider); + const completions = await getCompletions(situation, dataProvider, timeRange); expect(completions).toHaveLength(4); expect(completions[0].insertText).toBe('value1'); @@ -375,7 +382,7 @@ describe('Label value completions', () => { otherLabels: [], }; - const completions = await getCompletions(situation, dataProvider); + const completions = await getCompletions(situation, dataProvider, timeRange); expect(completions).toHaveLength(4); expect(completions[0].insertText).toBe('"value1"'); @@ -392,6 +399,8 @@ describe('Label value completions', () => { }); }); + const timeRange = getMockTimeRange(); + it('should handle empty values', async () => { jest.spyOn(dataProvider, 'getLabelValues').mockResolvedValue(['']); @@ -402,7 +411,7 @@ describe('Label value completions', () => { otherLabels: [], }; - const completions = await getCompletions(situation, dataProvider); + const completions = await getCompletions(situation, dataProvider, timeRange); expect(completions).toHaveLength(1); expect(completions[0].insertText).toBe('""'); }); @@ -417,7 +426,7 @@ describe('Label value completions', () => { otherLabels: [], }; - const completions = await getCompletions(situation, dataProvider); + const completions = await getCompletions(situation, dataProvider, timeRange); expect(completions).toHaveLength(1); expect(completions[0].insertText).toBe('test\\"\\\\value'); }); @@ -432,7 +441,7 @@ describe('Label value completions', () => { otherLabels: [], }; - const completions = await getCompletions(situation, dataProvider); + const completions = await getCompletions(situation, dataProvider, timeRange); expect(completions).toHaveLength(1); expect(completions[0].insertText).toBe('"123"'); }); diff --git a/packages/grafana-prometheus/src/components/monaco-query-field/monaco-completion-provider/completions.ts b/packages/grafana-prometheus/src/components/monaco-query-field/monaco-completion-provider/completions.ts index ff1f0490560..af0b51bddd4 100644 --- a/packages/grafana-prometheus/src/components/monaco-query-field/monaco-completion-provider/completions.ts +++ b/packages/grafana-prometheus/src/components/monaco-query-field/monaco-completion-provider/completions.ts @@ -2,6 +2,7 @@ import UFuzzy from '@leeoniya/ufuzzy'; import { languages } from 'monaco-editor'; +import { TimeRange } from '@grafana/data'; import { config } from '@grafana/runtime'; import { prometheusRegularEscape } from '../../../datasource'; @@ -160,14 +161,15 @@ function makeSelector(metricName: string | undefined, labels: Label[]): string { async function getLabelNames( metric: string | undefined, otherLabels: Label[], - dataProvider: DataProvider + dataProvider: DataProvider, + timeRange: TimeRange ): Promise { if (metric === undefined && otherLabels.length === 0) { // if there is no filtering, we have to use a special endpoint return Promise.resolve(dataProvider.getAllLabelNames()); } else { const selector = makeSelector(metric, otherLabels); - return await dataProvider.getSeriesLabels(selector, otherLabels); + return await dataProvider.getSeriesLabels(timeRange, selector, otherLabels); } } @@ -176,9 +178,10 @@ async function getLabelNamesForCompletions( suffix: string, triggerOnInsert: boolean, otherLabels: Label[], - dataProvider: DataProvider + dataProvider: DataProvider, + timeRange: TimeRange ): Promise { - const labelNames = await getLabelNames(metric, otherLabels, dataProvider); + const labelNames = await getLabelNames(metric, otherLabels, dataProvider, timeRange); return labelNames.map((text) => { const isUtf8 = !isValidLegacyName(text); return { @@ -200,31 +203,34 @@ async function getLabelNamesForCompletions( async function getLabelNamesForSelectorCompletions( metric: string | undefined, otherLabels: Label[], - dataProvider: DataProvider + dataProvider: DataProvider, + timeRange: TimeRange ): Promise { - return getLabelNamesForCompletions(metric, '=', true, otherLabels, dataProvider); + return getLabelNamesForCompletions(metric, '=', true, otherLabels, dataProvider, timeRange); } async function getLabelNamesForByCompletions( metric: string | undefined, otherLabels: Label[], - dataProvider: DataProvider + dataProvider: DataProvider, + timeRange: TimeRange ): Promise { - return getLabelNamesForCompletions(metric, '', false, otherLabels, dataProvider); + return getLabelNamesForCompletions(metric, '', false, otherLabels, dataProvider, timeRange); } async function getLabelValues( metric: string | undefined, labelName: string, otherLabels: Label[], - dataProvider: DataProvider + dataProvider: DataProvider, + timeRange: TimeRange ): Promise { if (metric === undefined && otherLabels.length === 0) { // if there is no filtering, we have to use a special endpoint - return dataProvider.getLabelValues(labelName); + return dataProvider.getLabelValues(timeRange, labelName); } else { const selector = makeSelector(metric, otherLabels); - return await dataProvider.getSeriesValues(labelName, selector); + return await dataProvider.getSeriesValues(timeRange, labelName, selector); } } @@ -233,9 +239,10 @@ async function getLabelValuesForMetricCompletions( labelName: string, betweenQuotes: boolean, otherLabels: Label[], - dataProvider: DataProvider + dataProvider: DataProvider, + timeRange: TimeRange ): Promise { - const values = await getLabelValues(metric, labelName, otherLabels, dataProvider); + const values = await getLabelValues(metric, labelName, otherLabels, dataProvider, timeRange); return values.map((text) => ({ type: 'LABEL_VALUE', label: text, @@ -248,7 +255,11 @@ function formatLabelValueForCompletion(value: string, betweenQuotes: boolean): s return betweenQuotes ? text : `"${text}"`; } -export function getCompletions(situation: Situation, dataProvider: DataProvider): Promise { +export function getCompletions( + situation: Situation, + dataProvider: DataProvider, + timeRange: TimeRange +): Promise { switch (situation.type) { case 'IN_DURATION': return Promise.resolve(DURATION_COMPLETIONS); @@ -263,16 +274,17 @@ export function getCompletions(situation: Situation, dataProvider: DataProvider) return Promise.resolve([...historyCompletions, ...FUNCTION_COMPLETIONS, ...metricNames]); } case 'IN_LABEL_SELECTOR_NO_LABEL_NAME': - return getLabelNamesForSelectorCompletions(situation.metricName, situation.otherLabels, dataProvider); + return getLabelNamesForSelectorCompletions(situation.metricName, situation.otherLabels, dataProvider, timeRange); case 'IN_GROUPING': - return getLabelNamesForByCompletions(situation.metricName, situation.otherLabels, dataProvider); + return getLabelNamesForByCompletions(situation.metricName, situation.otherLabels, dataProvider, timeRange); case 'IN_LABEL_SELECTOR_WITH_LABEL_NAME': return getLabelValuesForMetricCompletions( situation.metricName, situation.labelName, situation.betweenQuotes, situation.otherLabels, - dataProvider + dataProvider, + timeRange ); default: throw new NeverCaseError(situation); diff --git a/packages/grafana-prometheus/src/components/monaco-query-field/monaco-completion-provider/index.ts b/packages/grafana-prometheus/src/components/monaco-query-field/monaco-completion-provider/index.ts index 5108401b2fd..def57a02621 100644 --- a/packages/grafana-prometheus/src/components/monaco-query-field/monaco-completion-provider/index.ts +++ b/packages/grafana-prometheus/src/components/monaco-query-field/monaco-completion-provider/index.ts @@ -1,4 +1,5 @@ // Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/components/monaco-query-field/monaco-completion-provider/index.ts +import { TimeRange } from '@grafana/data'; import type { Monaco, monacoTypes } from '@grafana/ui'; import { CompletionType, getCompletions } from './completions'; @@ -48,7 +49,8 @@ function getMonacoCompletionItemKind(type: CompletionType, monaco: Monaco): mona export function getCompletionProvider( monaco: Monaco, - dataProvider: DataProvider + dataProvider: DataProvider, + timeRange: TimeRange ): monacoTypes.languages.CompletionItemProvider { const provideCompletionItems = ( model: monacoTypes.editor.ITextModel, @@ -84,7 +86,8 @@ export function getCompletionProvider( const offset = model.getOffsetAt(positionClone); const situation = getSituation(model.getValue(), offset); - const completionsPromise = situation != null ? getCompletions(situation, dataProvider) : Promise.resolve([]); + const completionsPromise = + situation != null ? getCompletions(situation, dataProvider, timeRange) : Promise.resolve([]); return completionsPromise.then((items) => { // monaco by-default alphabetically orders the items. diff --git a/packages/grafana-prometheus/src/datasource.ts b/packages/grafana-prometheus/src/datasource.ts index 44e93ebb7d1..082e391ad9b 100644 --- a/packages/grafana-prometheus/src/datasource.ts +++ b/packages/grafana-prometheus/src/datasource.ts @@ -483,7 +483,7 @@ export class PrometheusDatasource return Promise.resolve([]); } - const timeRange = options?.range ?? this.languageProvider.timeRange ?? getDefaultTimeRange(); + const timeRange = options?.range ?? getDefaultTimeRange(); const scopedVars = { ...this.getIntervalVars(), @@ -654,6 +654,10 @@ export class PrometheusDatasource // it is used in metric_find_query.ts // and in Tempo here grafana/public/app/plugins/datasource/tempo/QueryEditor/ServiceGraphSection.tsx async getTagKeys(options: DataSourceGetTagKeysOptions): Promise { + if (!options.timeRange) { + options.timeRange = getDefaultTimeRange(); + } + if (config.featureToggles.promQLScope && (options?.scopes?.length ?? 0) > 0) { const suggestions = await this.languageProvider.fetchSuggestions( options.timeRange, @@ -680,7 +684,10 @@ export class PrometheusDatasource })); const expr = promQueryModeller.renderLabels(labelFilters); - let labelsIndex: Record = await this.languageProvider.fetchLabelsWithMatch(expr); + let labelsIndex: Record = await this.languageProvider.fetchLabelsWithMatch( + options.timeRange, + expr + ); // filter out already used labels return Object.keys(labelsIndex) @@ -689,7 +696,11 @@ export class PrometheusDatasource } // By implementing getTagKeys and getTagValues we add ad-hoc filters functionality - async getTagValues(options: DataSourceGetTagValuesOptions) { + async getTagValues(options: DataSourceGetTagValuesOptions): Promise { + if (!options.timeRange) { + options.timeRange = getDefaultTimeRange(); + } + const requestId = `[${this.uid}][${options.key}]`; if (config.featureToggles.promQLScope && (options?.scopes?.length ?? 0) > 0) { return ( @@ -715,7 +726,7 @@ export class PrometheusDatasource if (this.hasLabelsMatchAPISupport()) { return ( - await this.languageProvider.fetchSeriesValuesWithMatch(options.key, expr, requestId, options.timeRange) + await this.languageProvider.fetchSeriesValuesWithMatch(options.timeRange, options.key, expr, requestId) ).map((v) => ({ value: v, text: v, diff --git a/packages/grafana-prometheus/src/language_provider.test.ts b/packages/grafana-prometheus/src/language_provider.test.ts index 2b0c7edcaf3..e91845d1f00 100644 --- a/packages/grafana-prometheus/src/language_provider.test.ts +++ b/packages/grafana-prometheus/src/language_provider.test.ts @@ -110,6 +110,8 @@ describe('Language completion provider', () => { }); describe('getSeriesLabels', () => { + const timeRange = getMockTimeRange(); + it('should call labels endpoint', () => { const languageProvider = new LanguageProvider({ ...defaultDatasource, @@ -120,7 +122,7 @@ describe('Language completion provider', () => { const labelName = 'job'; const labelValue = 'grafana'; - getSeriesLabels(`{${labelName}="${labelValue}"}`, [ + getSeriesLabels(timeRange, `{${labelName}="${labelValue}"}`, [ { name: labelName, value: labelValue, @@ -151,7 +153,7 @@ describe('Language completion provider', () => { const labelName = 'job'; const labelValue = 'grafana'; - getSeriesLabels(`{${labelName}="${labelValue}"}`, [ + getSeriesLabels(timeRange, `{${labelName}="${labelValue}"}`, [ { name: labelName, value: labelValue, @@ -186,7 +188,7 @@ describe('Language completion provider', () => { const labelName = 'job'; const labelValue = 'grafana'; - getSeriesLabels(`{${labelName}="${labelValue}"}`, [ + getSeriesLabels(timeRange, `{${labelName}="${labelValue}"}`, [ { name: labelName, value: labelValue, @@ -217,13 +219,15 @@ describe('Language completion provider', () => { }); describe('getSeriesValues', () => { + const timeRange = getMockTimeRange(); + it('should call old series endpoint and should use match[] parameter', () => { const languageProvider = new LanguageProvider({ ...defaultDatasource, } as PrometheusDatasource); const getSeriesValues = languageProvider.getSeriesValues; const requestSpy = jest.spyOn(languageProvider, 'request'); - getSeriesValues('job', '{job="grafana"}'); + getSeriesValues(timeRange, 'job', '{job="grafana"}'); expect(requestSpy).toHaveBeenCalled(); expect(requestSpy).toHaveBeenCalledWith( '/api/v1/series', @@ -246,7 +250,7 @@ describe('Language completion provider', () => { const requestSpy = jest.spyOn(languageProvider, 'request'); const labelName = 'job'; const labelValue = 'grafana'; - getSeriesValues(labelName, `{${labelName}="${labelValue}"}`); + getSeriesValues(timeRange, labelName, `{${labelName}="${labelValue}"}`); expect(requestSpy).toHaveBeenCalled(); expect(requestSpy).toHaveBeenCalledWith( `/api/v1/label/${labelName}/values`, @@ -267,7 +271,7 @@ describe('Language completion provider', () => { } as PrometheusDatasource); const getSeriesValues = languageProvider.getSeriesValues; const requestSpy = jest.spyOn(languageProvider, 'request'); - getSeriesValues('job', '{instance="$instance", job="grafana"}'); + getSeriesValues(timeRange, 'job', '{instance="$instance", job="grafana"}'); expect(requestSpy).toHaveBeenCalled(); expect(requestSpy).toHaveBeenCalledWith( '/api/v1/series', @@ -288,7 +292,7 @@ describe('Language completion provider', () => { const timeRange = getMockTimeRange(); await languageProvider.start(timeRange); const requestSpy = jest.spyOn(languageProvider, 'request'); - await languageProvider.fetchSeries('{job="grafana"}'); + await languageProvider.fetchSeries(timeRange, '{job="grafana"}'); expect(requestSpy).toHaveBeenCalled(); expect(requestSpy).toHaveBeenCalledWith( '/api/v1/series', @@ -311,7 +315,7 @@ describe('Language completion provider', () => { } as PrometheusDatasource); const fetchSeriesLabels = languageProvider.fetchSeriesLabels; const requestSpy = jest.spyOn(languageProvider, 'request'); - fetchSeriesLabels('$metric'); + fetchSeriesLabels(getMockTimeRange(), '$metric'); expect(requestSpy).toHaveBeenCalled(); expect(requestSpy).toHaveBeenCalledWith( '/api/v1/series', @@ -332,7 +336,7 @@ describe('Language completion provider', () => { } as PrometheusDatasource); const fetchSeriesLabels = languageProvider.fetchSeriesLabels; const requestSpy = jest.spyOn(languageProvider, 'request'); - fetchSeriesLabels('metric-with-limit', undefined, 'none'); + fetchSeriesLabels(getMockTimeRange(), 'metric-with-limit', undefined, 'none'); expect(requestSpy).toHaveBeenCalled(); expect(requestSpy).toHaveBeenCalledWith( '/api/v1/series', @@ -353,7 +357,7 @@ describe('Language completion provider', () => { } as PrometheusDatasource); const fetchSeriesLabels = languageProvider.fetchSeriesLabels; const requestSpy = jest.spyOn(languageProvider, 'request'); - fetchSeriesLabels('metric-without-limit', false, 'none'); + fetchSeriesLabels(getMockTimeRange(), 'metric-without-limit', false, 'none'); expect(requestSpy).toHaveBeenCalled(); expect(requestSpy).toHaveBeenCalledWith( '/api/v1/series', @@ -619,7 +623,7 @@ describe('Language completion provider', () => { } as PrometheusDatasource); const fetchLabelValues = languageProvider.fetchLabelValues; const requestSpy = jest.spyOn(languageProvider, 'request'); - fetchLabelValues('$job'); + fetchLabelValues(getMockTimeRange(), '$job'); expect(requestSpy).toHaveBeenCalled(); expect(requestSpy).toHaveBeenCalledWith( '/api/v1/label/interpolated_job/values', @@ -639,7 +643,7 @@ describe('Language completion provider', () => { } as PrometheusDatasource); const fetchLabelValues = languageProvider.fetchLabelValues; const requestSpy = jest.spyOn(languageProvider, 'request'); - fetchLabelValues('"http.status:sum"'); + fetchLabelValues(getMockTimeRange(), '"http.status:sum"'); expect(requestSpy).toHaveBeenCalled(); expect(requestSpy).toHaveBeenCalledWith( '/api/v1/label/U__http_2e_status:sum/values', @@ -661,7 +665,7 @@ describe('Language completion provider', () => { } as PrometheusDatasource); const fetchSeriesValuesWithMatch = languageProvider.fetchSeriesValuesWithMatch; const requestSpy = jest.spyOn(languageProvider, 'request'); - fetchSeriesValuesWithMatch('"http.status:sum"', '{__name__="a_utf8_http_requests_total"}'); + fetchSeriesValuesWithMatch(getMockTimeRange(), '"http.status:sum"', '{__name__="a_utf8_http_requests_total"}'); expect(requestSpy).toHaveBeenCalled(); expect(requestSpy).toHaveBeenCalledWith( '/api/v1/label/U__http_2e_status:sum/values', @@ -681,7 +685,7 @@ describe('Language completion provider', () => { } as PrometheusDatasource); const fetchSeriesValuesWithMatch = languageProvider.fetchSeriesValuesWithMatch; const requestSpy = jest.spyOn(languageProvider, 'request'); - fetchSeriesValuesWithMatch('"http_status_sum"', '{__name__="a_utf8_http_requests_total"}'); + fetchSeriesValuesWithMatch(getMockTimeRange(), '"http_status_sum"', '{__name__="a_utf8_http_requests_total"}'); expect(requestSpy).toHaveBeenCalled(); expect(requestSpy).toHaveBeenCalledWith( '/api/v1/label/http_status_sum/values', diff --git a/packages/grafana-prometheus/src/language_provider.ts b/packages/grafana-prometheus/src/language_provider.ts index 22c427c979b..04a9f7e6e7b 100644 --- a/packages/grafana-prometheus/src/language_provider.ts +++ b/packages/grafana-prometheus/src/language_provider.ts @@ -79,7 +79,6 @@ const PREFIX_DELIMITER_REGEX = const secondsInDay = 86400; export default class PromQlLanguageProvider extends LanguageProvider { histogramMetrics: string[]; - timeRange: TimeRange; metrics: string[]; metricsMetadata?: PromMetricsMetadata; declare startTask: Promise; @@ -92,7 +91,6 @@ export default class PromQlLanguageProvider extends LanguageProvider { this.datasource = datasource; this.histogramMetrics = []; - this.timeRange = getDefaultTimeRange(); this.metrics = []; Object.assign(this, initialValues); @@ -109,7 +107,7 @@ export default class PromQlLanguageProvider extends LanguageProvider { cleanText(s: string) { const parts = s.split(PREFIX_DELIMITER_REGEX); const last = parts.pop()!; - return last.trimLeft().replace(/"$/, '').replace(/^"/, ''); + return last.trimStart().replace(/"$/, '').replace(/^"/, ''); } get syntax() { @@ -129,16 +127,14 @@ export default class PromQlLanguageProvider extends LanguageProvider { return defaultValue; }; - start = async (timeRange?: TimeRange): Promise => { - this.timeRange = timeRange ?? getDefaultTimeRange(); - + start = async (timeRange: TimeRange = getDefaultTimeRange()): Promise => { if (this.datasource.lookupsDisabled) { return []; } - this.metrics = (await this.fetchLabelValues('__name__')) || []; + this.metrics = (await this.fetchLabelValues(timeRange, '__name__')) || []; this.histogramMetrics = processHistogramMetrics(this.metrics).sort(); - return Promise.all([this.loadMetricsMetadata(), this.fetchLabels()]); + return Promise.all([this.loadMetricsMetadata(), this.fetchLabels(timeRange)]); }; async loadMetricsMetadata() { @@ -186,15 +182,15 @@ export default class PromQlLanguageProvider extends LanguageProvider { }; } - async getSeries(selector: string, withName?: boolean): Promise> { + async getSeries(timeRange: TimeRange, selector: string, withName?: boolean): Promise> { if (this.datasource.lookupsDisabled) { return {}; } try { if (selector === EMPTY_SELECTOR) { - return await this.fetchDefaultSeries(); + return await this.fetchDefaultSeries(timeRange); } else { - return await this.fetchSeriesLabels(selector, withName, REMOVE_SERIES_LIMIT); + return await this.fetchSeriesLabels(timeRange, selector, withName, REMOVE_SERIES_LIMIT); } } catch (error) { // TODO: better error handling @@ -203,11 +199,8 @@ export default class PromQlLanguageProvider extends LanguageProvider { } } - /** - * @param key - */ - fetchLabelValues = async (key: string): Promise => { - const params = this.datasource.getAdjustedInterval(this.timeRange); + fetchLabelValues = async (range: TimeRange, key: string): Promise => { + const params = this.datasource.getAdjustedInterval(range); const interpolatedName = this.datasource.interpolateString(key); const interpolatedAndEscapedName = escapeForUtf8Support(removeQuotesIfExist(interpolatedName)); const url = `/api/v1/label/${interpolatedAndEscapedName}/values`; @@ -215,19 +208,16 @@ export default class PromQlLanguageProvider extends LanguageProvider { return value ?? []; }; - async getLabelValues(key: string): Promise { - return await this.fetchLabelValues(key); + async getLabelValues(range: TimeRange, key: string): Promise { + return await this.fetchLabelValues(range, key); } /** * Fetches all label keys */ - fetchLabels = async (timeRange?: TimeRange, queries?: PromQuery[]): Promise => { - if (timeRange) { - this.timeRange = timeRange; - } + fetchLabels = async (timeRange: TimeRange, queries?: PromQuery[]): Promise => { let url = '/api/v1/labels'; - const timeParams = this.datasource.getAdjustedInterval(this.timeRange); + const timeParams = this.datasource.getAdjustedInterval(timeRange); this.labelFetchTs = Date.now().valueOf(); const searchParams = new URLSearchParams({ ...timeParams }); @@ -259,17 +249,15 @@ export default class PromQlLanguageProvider extends LanguageProvider { /** * Gets series values - * Function to replace old getSeries calls in a way that will provide faster endpoints for new prometheus instances, - * while maintaining backward compatability - * @param labelName - * @param selector + * Function to replace old getSeries calls in a way that will provide faster endpoints + * for new prometheus instances, while maintaining backward compatability */ - getSeriesValues = async (labelName: string, selector: string): Promise => { + getSeriesValues = async (timeRange: TimeRange, labelName: string, selector: string): Promise => { if (!this.datasource.hasLabelsMatchAPISupport()) { - const data = await this.getSeries(selector); + const data = await this.getSeries(timeRange, selector); return data[removeQuotesIfExist(labelName)] ?? []; } - return await this.fetchSeriesValuesWithMatch(labelName, selector); + return await this.fetchSeriesValuesWithMatch(timeRange, labelName, selector); }; /** @@ -280,10 +268,10 @@ export default class PromQlLanguageProvider extends LanguageProvider { * @param requestId */ fetchSeriesValuesWithMatch = async ( + timeRange: TimeRange, name: string, match: string, - requestId?: string, - timeRange: TimeRange = this.timeRange + requestId?: string ): Promise => { const interpolatedName = name ? this.datasource.interpolateString(name) : null; const interpolatedMatch = match ? this.datasource.interpolateString(match) : null; @@ -321,16 +309,16 @@ export default class PromQlLanguageProvider extends LanguageProvider { * @param selector * @param otherLabels */ - getSeriesLabels = async (selector: string, otherLabels: Label[]): Promise => { + getSeriesLabels = async (timeRange: TimeRange, selector: string, otherLabels: Label[]): Promise => { let possibleLabelNames, data: Record; if (!this.datasource.hasLabelsMatchAPISupport()) { - data = await this.getSeries(selector); + data = await this.getSeries(timeRange, selector); possibleLabelNames = Object.keys(data); // all names from prometheus } else { // Exclude __name__ from output otherLabels.push({ name: '__name__', value: '', op: '!=' }); - data = await this.fetchSeriesLabelsMatch(selector); + data = await this.fetchSeriesLabelsMatch(timeRange, selector); possibleLabelNames = Object.keys(data); } @@ -341,31 +329,31 @@ export default class PromQlLanguageProvider extends LanguageProvider { /** * Fetch labels using the best endpoint that datasource supports. * This is cached by its args but also by the global timeRange currently selected as they can change over requested time. - * @param name - * @param withName */ - fetchLabelsWithMatch = async (name: string, withName?: boolean): Promise> => { + fetchLabelsWithMatch = async ( + timeRange: TimeRange, + name: string, + withName?: boolean + ): Promise> => { if (this.datasource.hasLabelsMatchAPISupport()) { - return this.fetchSeriesLabelsMatch(name, withName); + return this.fetchSeriesLabelsMatch(timeRange, name, withName); } else { - return this.fetchSeriesLabels(name, withName, REMOVE_SERIES_LIMIT); + return this.fetchSeriesLabels(timeRange, name, withName, REMOVE_SERIES_LIMIT); } }; /** * Fetch labels for a series using /series endpoint. This is cached by its args but also by the global timeRange currently selected as * they can change over requested time. - * @param name - * @param withName - * @param withLimit */ fetchSeriesLabels = async ( + timeRange: TimeRange, name: string, withName?: boolean, withLimit?: string ): Promise> => { const interpolatedName = this.datasource.interpolateString(name); - const range = this.datasource.getAdjustedInterval(this.timeRange); + const range = this.datasource.getAdjustedInterval(timeRange); let urlParams: UrlParamsType = { ...range, 'match[]': interpolatedName, @@ -385,12 +373,14 @@ export default class PromQlLanguageProvider extends LanguageProvider { /** * Fetch labels for a series using /labels endpoint. This is cached by its args but also by the global timeRange currently selected as * they can change over requested time. - * @param name - * @param withName */ - fetchSeriesLabelsMatch = async (name: string, withName?: boolean): Promise> => { + fetchSeriesLabelsMatch = async ( + timeRange: TimeRange, + name: string, + withName?: boolean + ): Promise> => { const interpolatedName = this.datasource.interpolateString(name); - const range = this.datasource.getAdjustedInterval(this.timeRange); + const range = this.datasource.getAdjustedInterval(timeRange); const urlParams = { ...range, 'match[]': interpolatedName, @@ -404,11 +394,10 @@ export default class PromQlLanguageProvider extends LanguageProvider { /** * Fetch series for a selector. Use this for raw results. Use fetchSeriesLabels() to get labels. - * @param match */ - fetchSeries = async (match: string): Promise>> => { + fetchSeries = async (timeRange: TimeRange, match: string): Promise>> => { const url = '/api/v1/series'; - const range = this.datasource.getTimeRangeParams(this.timeRange); + const range = this.datasource.getTimeRangeParams(timeRange); const params = { ...range, 'match[]': match }; return await this.request(url, {}, params, this.getDefaultCacheHeaders()); }; @@ -418,8 +407,8 @@ export default class PromQlLanguageProvider extends LanguageProvider { * because we can cache more aggressively here and also we do not want to invalidate this cache the same way as in * fetchSeriesLabels. */ - fetchDefaultSeries = once(async () => { - const values = await Promise.all(DEFAULT_KEYS.map((key) => this.fetchLabelValues(key))); + fetchDefaultSeries = once(async (timeRange: TimeRange) => { + const values = await Promise.all(DEFAULT_KEYS.map((key) => this.fetchLabelValues(timeRange, key))); return DEFAULT_KEYS.reduce((acc, key, i) => ({ ...acc, [key]: values[i] }), {}); }); @@ -442,12 +431,12 @@ export default class PromQlLanguageProvider extends LanguageProvider { limit?: number, requestId?: string ): Promise => { - if (timeRange) { - this.timeRange = timeRange; + if (!timeRange) { + timeRange = getDefaultTimeRange(); } const url = '/suggestions'; - const timeParams = this.datasource.getAdjustedInterval(this.timeRange); + const timeParams = this.datasource.getAdjustedInterval(timeRange); const value = await this.request( url, [], @@ -456,7 +445,7 @@ export default class PromQlLanguageProvider extends LanguageProvider { queries: queries?.map((q) => this.datasource.interpolateString(q.expr, { ...this.datasource.getIntervalVars(), - ...this.datasource.getRangeScopedVars(this.timeRange), + ...this.datasource.getRangeScopedVars(timeRange), }) ), scopes: scopes?.reduce((acc, scope) => { diff --git a/packages/grafana-prometheus/src/metric_find_query.ts b/packages/grafana-prometheus/src/metric_find_query.ts index 69494f0f7fc..12d17e7126f 100644 --- a/packages/grafana-prometheus/src/metric_find_query.ts +++ b/packages/grafana-prometheus/src/metric_find_query.ts @@ -1,7 +1,7 @@ // Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/metric_find_query.ts import { chain, map as _map, uniq } from 'lodash'; -import { getDefaultTimeRange, MetricFindValue, TimeRange } from '@grafana/data'; +import { MetricFindValue, TimeRange } from '@grafana/data'; import { PrometheusDatasource } from './datasource'; import { getPrometheusTime } from './language_utils'; @@ -15,19 +15,15 @@ import { import { escapeForUtf8Support, isValidLegacyName } from './utf8_support'; export class PrometheusMetricFindQuery { - range: TimeRange; - constructor( private datasource: PrometheusDatasource, private query: string ) { this.datasource = datasource; this.query = query; - this.range = getDefaultTimeRange(); } process(timeRange: TimeRange): Promise { - this.range = timeRange; const labelNamesRegex = PrometheusLabelNamesRegex; const labelNamesRegexWithMatch = PrometheusLabelNamesRegexWithMatch; const labelValuesRegex = PrometheusLabelValuesRegex; @@ -38,7 +34,7 @@ export class PrometheusMetricFindQuery { if (labelNamesMatchQuery) { const selector = `{__name__=~".*${labelNamesMatchQuery[1]}.*"}`; - return this.datasource.languageProvider.getSeriesLabels(selector, []).then((results) => + return this.datasource.languageProvider.getSeriesLabels(timeRange, selector, []).then((results) => results.map((result) => ({ text: result, })) @@ -54,35 +50,35 @@ export class PrometheusMetricFindQuery { const filter = labelValuesQuery[1]; const label = labelValuesQuery[2]; if (isFilterDefined(filter)) { - return this.labelValuesQuery(label, filter); + return this.labelValuesQuery(label, timeRange, filter); } else { // Exclude the filter part of the expression because it is blank or empty - return this.labelValuesQuery(label); + return this.labelValuesQuery(label, timeRange); } } const metricNamesQuery = this.query.match(metricNamesRegex); if (metricNamesQuery) { - return this.metricNameQuery(metricNamesQuery[1]); + return this.metricNameQuery(metricNamesQuery[1], timeRange); } const queryResultQuery = this.query.match(queryResultRegex); if (queryResultQuery) { - return this.queryResultQuery(queryResultQuery[1]); + return this.queryResultQuery(queryResultQuery[1], timeRange); } // if query contains full metric name, return metric name and label list const expressions = ['label_values()', 'metrics()', 'query_result()']; if (!expressions.includes(this.query)) { - return this.metricNameAndLabelsQuery(this.query); + return this.metricNameAndLabelsQuery(this.query, timeRange); } return Promise.resolve([]); } - labelValuesQuery(label: string, metric?: string) { - const start = getPrometheusTime(this.range.from, false); - const end = getPrometheusTime(this.range.to, true); + labelValuesQuery(label: string, range: TimeRange, metric?: string) { + const start = getPrometheusTime(range.from, false); + const end = getPrometheusTime(range.to, true); const params = { ...(metric && { 'match[]': metric }), start: start.toString(), end: end.toString() }; let escapedLabel = label; @@ -118,9 +114,9 @@ export class PrometheusMetricFindQuery { } } - metricNameQuery(metricFilterPattern: string) { - const start = getPrometheusTime(this.range.from, false); - const end = getPrometheusTime(this.range.to, true); + metricNameQuery(metricFilterPattern: string, range: TimeRange) { + const start = getPrometheusTime(range.from, false); + const end = getPrometheusTime(range.to, true); const params = { start: start.toString(), end: end.toString(), @@ -143,11 +139,11 @@ export class PrometheusMetricFindQuery { }); } - queryResultQuery(query: string) { + queryResultQuery(query: string, range: TimeRange) { const url = '/api/v1/query'; const params = { query, - time: getPrometheusTime(this.range.to, true).toString(), + time: getPrometheusTime(range.to, true).toString(), }; return this.datasource.metadataRequest(url, params).then((result) => { switch (result.data.data.resultType) { @@ -182,9 +178,9 @@ export class PrometheusMetricFindQuery { }); } - metricNameAndLabelsQuery(query: string): Promise { - const start = getPrometheusTime(this.range.from, false); - const end = getPrometheusTime(this.range.to, true); + metricNameAndLabelsQuery(query: string, range: TimeRange): Promise { + const start = getPrometheusTime(range.from, false); + const end = getPrometheusTime(range.to, true); const params = { 'match[]': query, start: start.toString(), diff --git a/packages/grafana-prometheus/src/querybuilder/components/MetricsLabelsSection.tsx b/packages/grafana-prometheus/src/querybuilder/components/MetricsLabelsSection.tsx index ae6353e8b1a..f6f62ffb54b 100644 --- a/packages/grafana-prometheus/src/querybuilder/components/MetricsLabelsSection.tsx +++ b/packages/grafana-prometheus/src/querybuilder/components/MetricsLabelsSection.tsx @@ -1,7 +1,7 @@ // Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/querybuilder/components/MetricsLabelsSection.tsx import { useCallback } from 'react'; -import { SelectableValue } from '@grafana/data'; +import { SelectableValue, TimeRange } from '@grafana/data'; import { config } from '@grafana/runtime'; import { PrometheusDatasource } from '../../datasource'; @@ -22,6 +22,7 @@ export interface MetricsLabelsSectionProps { onChange: (update: PromVisualQuery) => void; variableEditor?: boolean; onBlur?: () => void; + timeRange: TimeRange; } export function MetricsLabelsSection({ @@ -30,6 +31,7 @@ export function MetricsLabelsSection({ onChange, onBlur, variableEditor, + timeRange, }: MetricsLabelsSectionProps) { // fixing the use of 'as' from refactoring // @ts-ignore @@ -63,7 +65,7 @@ export function MetricsLabelsSection({ const onGetLabelNames = async (forLabel: Partial): Promise => { // If no metric we need to use a different method if (!query.metric) { - await datasource.languageProvider.fetchLabels(); + await datasource.languageProvider.fetchLabels(timeRange); return datasource.languageProvider.getLabelKeys().map((k) => ({ value: k })); } @@ -71,7 +73,7 @@ export function MetricsLabelsSection({ labelsToConsider.push({ label: '__name__', op: '=', value: query.metric }); const expr = promQueryModeller.renderLabels(labelsToConsider); - let labelsIndex: Record = await datasource.languageProvider.fetchLabelsWithMatch(expr); + let labelsIndex: Record = await datasource.languageProvider.fetchLabelsWithMatch(timeRange, expr); // filter out already used labels return Object.keys(labelsIndex) @@ -124,7 +126,7 @@ export function MetricsLabelsSection({ if (!forLabel.label) { return Promise.resolve([]); } - const result = datasource.languageProvider.fetchSeries(promQLExpression); + const result = datasource.languageProvider.fetchSeries(timeRange, promQLExpression); const forLabelInterpolated = datasource.interpolateString(forLabel.label); return result.then((result) => { // This query returns duplicate values, scrub them out @@ -154,7 +156,7 @@ export function MetricsLabelsSection({ const requestId = `[${datasource.uid}][${query.metric}][${forLabel.label}][${forLabel.op}]`; return datasource.languageProvider - .fetchSeriesValuesWithMatch(forLabel.label, promQLExpression, requestId) + .fetchSeriesValuesWithMatch(timeRange, forLabel.label, promQLExpression, requestId) .then((response) => response.map((v) => ({ value: v, label: v }))); }; @@ -169,7 +171,7 @@ export function MetricsLabelsSection({ } // If no metric is selected, we can get the raw list of labels if (!query.metric) { - return (await datasource.languageProvider.getLabelValues(forLabel.label)).map((v) => ({ value: v })); + return (await datasource.languageProvider.getLabelValues(timeRange, forLabel.label)).map((v) => ({ value: v })); } const labelsToConsider = query.labels.filter((x) => x !== forLabel); @@ -191,8 +193,8 @@ export function MetricsLabelsSection({ }; const onGetMetrics = useCallback(() => { - return withTemplateVariableOptions(getMetrics(datasource, query)); - }, [datasource, query, withTemplateVariableOptions]); + return withTemplateVariableOptions(getMetrics(datasource, query, timeRange)); + }, [datasource, query, timeRange, withTemplateVariableOptions]); const MetricSelectComponent = config.featureToggles.prometheusUsesCombobox ? MetricCombobox : MetricSelect; @@ -229,7 +231,8 @@ export function MetricsLabelsSection({ */ async function getMetrics( datasource: PrometheusDatasource, - query: PromVisualQuery + query: PromVisualQuery, + timeRange: TimeRange ): Promise> { // Makes sure we loaded the metadata for metrics. Usually this is done in the start() method of the provider but we // don't use it with the visual builder and there is no need to run all the start() setup anyway. @@ -245,9 +248,9 @@ async function getMetrics( let metrics: string[]; if (query.labels.length > 0) { const expr = promQueryModeller.renderLabels(query.labels); - metrics = (await datasource.languageProvider.getSeries(expr, true))['__name__'] ?? []; + metrics = (await datasource.languageProvider.getSeries(timeRange, expr, true))['__name__'] ?? []; } else { - metrics = (await datasource.languageProvider.getLabelValues('__name__')) ?? []; + metrics = (await datasource.languageProvider.getLabelValues(timeRange, '__name__')) ?? []; } return metrics.map((m) => ({ diff --git a/packages/grafana-prometheus/src/querybuilder/components/PromQueryBuilder.test.tsx b/packages/grafana-prometheus/src/querybuilder/components/PromQueryBuilder.test.tsx index 8e58f495963..372873eebb1 100644 --- a/packages/grafana-prometheus/src/querybuilder/components/PromQueryBuilder.test.tsx +++ b/packages/grafana-prometheus/src/querybuilder/components/PromQueryBuilder.test.tsx @@ -89,7 +89,7 @@ describe('PromQueryBuilder', () => { it('tries to load metrics without labels', async () => { const { languageProvider, container } = setup(); await openMetricSelect(container); - await waitFor(() => expect(languageProvider.getLabelValues).toHaveBeenCalledWith('__name__')); + await waitFor(() => expect(languageProvider.getLabelValues).toHaveBeenCalledWith(expect.anything(), '__name__')); }); it('tries to load metrics with labels', async () => { @@ -98,7 +98,13 @@ describe('PromQueryBuilder', () => { labels: [{ label: 'label_name', op: '=', value: 'label_value' }], }); await openMetricSelect(container); - await waitFor(() => expect(languageProvider.getSeries).toHaveBeenCalledWith('{label_name="label_value"}', true)); + await waitFor(() => + expect(languageProvider.getSeries).toHaveBeenCalledWith( + expect.anything(), + '{label_name="label_value"}', + expect.anything() + ) + ); }); it('tries to load variables in metric field', async () => { @@ -113,7 +119,10 @@ describe('PromQueryBuilder', () => { const { languageProvider } = setup(); await openLabelNameSelect(); await waitFor(() => - expect(languageProvider.fetchLabelsWithMatch).toHaveBeenCalledWith('{__name__="random_metric"}') + expect(languageProvider.fetchLabelsWithMatch).toHaveBeenCalledWith( + expect.anything(), + '{__name__="random_metric"}' + ) ); }); @@ -135,6 +144,7 @@ describe('PromQueryBuilder', () => { await openLabelNameSelect(1); await waitFor(() => expect(languageProvider.fetchLabelsWithMatch).toHaveBeenCalledWith( + expect.anything(), '{label_name="label_value", __name__="random_metric"}' ) ); @@ -273,7 +283,10 @@ describe('PromQueryBuilder', () => { }); await openLabelNameSelect(); await waitFor(() => - expect(languageProvider.fetchLabelsWithMatch).toHaveBeenCalledWith('{__name__="random_metric"}') + expect(languageProvider.fetchLabelsWithMatch).toHaveBeenCalledWith( + expect.anything(), + '{__name__="random_metric"}' + ) ); }); @@ -301,6 +314,7 @@ describe('PromQueryBuilder', () => { await openLabelNameSelect(1); await waitFor(() => expect(languageProvider.fetchLabelsWithMatch).toHaveBeenCalledWith( + expect.anything(), '{label_name="label_value", __name__="random_metric"}' ) ); diff --git a/packages/grafana-prometheus/src/querybuilder/components/PromQueryBuilder.tsx b/packages/grafana-prometheus/src/querybuilder/components/PromQueryBuilder.tsx index 11c79f5932c..a7e0841f634 100644 --- a/packages/grafana-prometheus/src/querybuilder/components/PromQueryBuilder.tsx +++ b/packages/grafana-prometheus/src/querybuilder/components/PromQueryBuilder.tsx @@ -2,7 +2,7 @@ import { css } from '@emotion/css'; import { memo, useState } from 'react'; -import { DataSourceApi, PanelData } from '@grafana/data'; +import { DataSourceApi, getDefaultTimeRange, PanelData } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; import { EditorRow } from '@grafana/plugin-ui'; @@ -43,7 +43,12 @@ export const PromQueryBuilder = memo((props) => { return ( <> - + {initHints.length ? (
; + publish(address: LiveChannelAddress, data: unknown, options?: LivePublishOptions): Promise; } let singletonInstance: GrafanaLiveSrv; diff --git a/packages/grafana-schema/package.json b/packages/grafana-schema/package.json index 3a97767a218..d6b8759e8b1 100644 --- a/packages/grafana-schema/package.json +++ b/packages/grafana-schema/package.json @@ -2,7 +2,7 @@ "author": "Grafana Labs", "license": "Apache-2.0", "name": "@grafana/schema", - "version": "11.6.0-pre", + "version": "12.0.0-pre", "description": "Grafana Schema Library", "keywords": [ "typescript" diff --git a/packages/grafana-schema/src/raw/composable/annotationslist/panelcfg/x/AnnotationsListPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/annotationslist/panelcfg/x/AnnotationsListPanelCfg_types.gen.ts index fb399d8d9af..b8c475c5cb0 100644 --- a/packages/grafana-schema/src/raw/composable/annotationslist/panelcfg/x/AnnotationsListPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/annotationslist/panelcfg/x/AnnotationsListPanelCfg_types.gen.ts @@ -8,7 +8,7 @@ // // Run 'make gen-cue' from repository root to regenerate. -export const pluginVersion = "11.6.0-pre"; +export const pluginVersion = "12.0.0-pre"; export interface Options { limit: number; diff --git a/packages/grafana-schema/src/raw/composable/barchart/panelcfg/x/BarChartPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/barchart/panelcfg/x/BarChartPanelCfg_types.gen.ts index ef9b15eb259..a613949f576 100644 --- a/packages/grafana-schema/src/raw/composable/barchart/panelcfg/x/BarChartPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/barchart/panelcfg/x/BarChartPanelCfg_types.gen.ts @@ -10,7 +10,7 @@ import * as common from '@grafana/schema'; -export const pluginVersion = "11.6.0-pre"; +export const pluginVersion = "12.0.0-pre"; export interface Options extends common.OptionsWithLegend, common.OptionsWithTooltip, common.OptionsWithTextFormatting { /** diff --git a/packages/grafana-schema/src/raw/composable/bargauge/panelcfg/x/BarGaugePanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/bargauge/panelcfg/x/BarGaugePanelCfg_types.gen.ts index 6eeabf6c4da..8ea837b198b 100644 --- a/packages/grafana-schema/src/raw/composable/bargauge/panelcfg/x/BarGaugePanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/bargauge/panelcfg/x/BarGaugePanelCfg_types.gen.ts @@ -10,7 +10,7 @@ import * as common from '@grafana/schema'; -export const pluginVersion = "11.6.0-pre"; +export const pluginVersion = "12.0.0-pre"; export interface Options extends common.OptionsWithLegend, common.SingleStatBaseOptions { displayMode: common.BarGaugeDisplayMode; diff --git a/packages/grafana-schema/src/raw/composable/candlestick/panelcfg/x/CandlestickPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/candlestick/panelcfg/x/CandlestickPanelCfg_types.gen.ts index 51ffd29995a..1ab1eeb4005 100644 --- a/packages/grafana-schema/src/raw/composable/candlestick/panelcfg/x/CandlestickPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/candlestick/panelcfg/x/CandlestickPanelCfg_types.gen.ts @@ -10,7 +10,7 @@ import * as common from '@grafana/schema'; -export const pluginVersion = "11.6.0-pre"; +export const pluginVersion = "12.0.0-pre"; export enum VizDisplayMode { Candles = 'candles', diff --git a/packages/grafana-schema/src/raw/composable/canvas/panelcfg/x/CanvasPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/canvas/panelcfg/x/CanvasPanelCfg_types.gen.ts index 7172fd336ec..2e70c51ccbb 100644 --- a/packages/grafana-schema/src/raw/composable/canvas/panelcfg/x/CanvasPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/canvas/panelcfg/x/CanvasPanelCfg_types.gen.ts @@ -10,7 +10,7 @@ import * as ui from '@grafana/schema'; -export const pluginVersion = "11.6.0-pre"; +export const pluginVersion = "12.0.0-pre"; export enum HorizontalConstraint { Center = 'center', diff --git a/packages/grafana-schema/src/raw/composable/cloudwatch/dataquery/x/CloudWatchDataQuery_types.gen.ts b/packages/grafana-schema/src/raw/composable/cloudwatch/dataquery/x/CloudWatchDataQuery_types.gen.ts index e6b8fc13cd0..30ac8aa2af5 100644 --- a/packages/grafana-schema/src/raw/composable/cloudwatch/dataquery/x/CloudWatchDataQuery_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/cloudwatch/dataquery/x/CloudWatchDataQuery_types.gen.ts @@ -10,7 +10,7 @@ import * as common from '@grafana/schema'; -export const pluginVersion = "11.6.0-pre"; +export const pluginVersion = "12.0.0-pre"; export interface MetricStat { /** diff --git a/packages/grafana-schema/src/raw/composable/dashboardlist/panelcfg/x/DashboardListPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/dashboardlist/panelcfg/x/DashboardListPanelCfg_types.gen.ts index 376cd0293d6..c345f24064b 100644 --- a/packages/grafana-schema/src/raw/composable/dashboardlist/panelcfg/x/DashboardListPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/dashboardlist/panelcfg/x/DashboardListPanelCfg_types.gen.ts @@ -8,7 +8,7 @@ // // Run 'make gen-cue' from repository root to regenerate. -export const pluginVersion = "11.6.0-pre"; +export const pluginVersion = "12.0.0-pre"; export interface Options { /** diff --git a/packages/grafana-schema/src/raw/composable/datagrid/panelcfg/x/DatagridPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/datagrid/panelcfg/x/DatagridPanelCfg_types.gen.ts index ffc4365a8ae..14c4faac257 100644 --- a/packages/grafana-schema/src/raw/composable/datagrid/panelcfg/x/DatagridPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/datagrid/panelcfg/x/DatagridPanelCfg_types.gen.ts @@ -8,7 +8,7 @@ // // Run 'make gen-cue' from repository root to regenerate. -export const pluginVersion = "11.6.0-pre"; +export const pluginVersion = "12.0.0-pre"; export interface Options { selectedSeries: number; diff --git a/packages/grafana-schema/src/raw/composable/debug/panelcfg/x/DebugPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/debug/panelcfg/x/DebugPanelCfg_types.gen.ts index d2119d55213..7ea1d48bd5d 100644 --- a/packages/grafana-schema/src/raw/composable/debug/panelcfg/x/DebugPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/debug/panelcfg/x/DebugPanelCfg_types.gen.ts @@ -8,7 +8,7 @@ // // Run 'make gen-cue' from repository root to regenerate. -export const pluginVersion = "11.6.0-pre"; +export const pluginVersion = "12.0.0-pre"; export type UpdateConfig = { render: boolean, diff --git a/packages/grafana-schema/src/raw/composable/elasticsearch/dataquery/x/ElasticsearchDataQuery_types.gen.ts b/packages/grafana-schema/src/raw/composable/elasticsearch/dataquery/x/ElasticsearchDataQuery_types.gen.ts index d11da7e9b28..57fa63d3a07 100644 --- a/packages/grafana-schema/src/raw/composable/elasticsearch/dataquery/x/ElasticsearchDataQuery_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/elasticsearch/dataquery/x/ElasticsearchDataQuery_types.gen.ts @@ -10,7 +10,7 @@ import * as common from '@grafana/schema'; -export const pluginVersion = "11.6.0-pre"; +export const pluginVersion = "12.0.0-pre"; export type BucketAggregation = (DateHistogram | Histogram | Terms | Filters | GeoHashGrid | Nested); diff --git a/packages/grafana-schema/src/raw/composable/gauge/panelcfg/x/GaugePanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/gauge/panelcfg/x/GaugePanelCfg_types.gen.ts index f1a444fd18b..0678c2947de 100644 --- a/packages/grafana-schema/src/raw/composable/gauge/panelcfg/x/GaugePanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/gauge/panelcfg/x/GaugePanelCfg_types.gen.ts @@ -10,7 +10,7 @@ import * as common from '@grafana/schema'; -export const pluginVersion = "11.6.0-pre"; +export const pluginVersion = "12.0.0-pre"; export interface Options extends common.SingleStatBaseOptions { minVizHeight: number; diff --git a/packages/grafana-schema/src/raw/composable/geomap/panelcfg/x/GeomapPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/geomap/panelcfg/x/GeomapPanelCfg_types.gen.ts index f1960dd27a4..8258f6d0501 100644 --- a/packages/grafana-schema/src/raw/composable/geomap/panelcfg/x/GeomapPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/geomap/panelcfg/x/GeomapPanelCfg_types.gen.ts @@ -10,7 +10,7 @@ import * as ui from '@grafana/schema'; -export const pluginVersion = "11.6.0-pre"; +export const pluginVersion = "12.0.0-pre"; export interface Options { basemap: ui.MapLayerOptions; diff --git a/packages/grafana-schema/src/raw/composable/heatmap/panelcfg/x/HeatmapPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/heatmap/panelcfg/x/HeatmapPanelCfg_types.gen.ts index 96ef75f8618..b440943dacd 100644 --- a/packages/grafana-schema/src/raw/composable/heatmap/panelcfg/x/HeatmapPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/heatmap/panelcfg/x/HeatmapPanelCfg_types.gen.ts @@ -10,7 +10,7 @@ import * as ui from '@grafana/schema'; -export const pluginVersion = "11.6.0-pre"; +export const pluginVersion = "12.0.0-pre"; /** * Controls the color mode of the heatmap diff --git a/packages/grafana-schema/src/raw/composable/histogram/panelcfg/x/HistogramPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/histogram/panelcfg/x/HistogramPanelCfg_types.gen.ts index 91e402b587f..4871a0e625b 100644 --- a/packages/grafana-schema/src/raw/composable/histogram/panelcfg/x/HistogramPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/histogram/panelcfg/x/HistogramPanelCfg_types.gen.ts @@ -10,7 +10,7 @@ import * as common from '@grafana/schema'; -export const pluginVersion = "11.6.0-pre"; +export const pluginVersion = "12.0.0-pre"; export interface Options extends common.OptionsWithLegend, common.OptionsWithTooltip { /** diff --git a/packages/grafana-schema/src/raw/composable/logs/panelcfg/x/LogsPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/logs/panelcfg/x/LogsPanelCfg_types.gen.ts index a2c30353a8e..39ef7f65f58 100644 --- a/packages/grafana-schema/src/raw/composable/logs/panelcfg/x/LogsPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/logs/panelcfg/x/LogsPanelCfg_types.gen.ts @@ -10,7 +10,7 @@ import * as common from '@grafana/schema'; -export const pluginVersion = "11.6.0-pre"; +export const pluginVersion = "12.0.0-pre"; export interface Options { dedupStrategy: common.LogsDedupStrategy; diff --git a/packages/grafana-schema/src/raw/composable/logsnew/panelcfg/x/LogsNewPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/logsnew/panelcfg/x/LogsNewPanelCfg_types.gen.ts index b758575dba2..5ff339342be 100644 --- a/packages/grafana-schema/src/raw/composable/logsnew/panelcfg/x/LogsNewPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/logsnew/panelcfg/x/LogsNewPanelCfg_types.gen.ts @@ -10,7 +10,7 @@ import * as common from '@grafana/schema'; -export const pluginVersion = "11.6.0-pre"; +export const pluginVersion = "12.0.0-pre"; export interface Options { dedupStrategy: common.LogsDedupStrategy; diff --git a/packages/grafana-schema/src/raw/composable/loki/dataquery/x/LokiDataQuery_types.gen.ts b/packages/grafana-schema/src/raw/composable/loki/dataquery/x/LokiDataQuery_types.gen.ts index e5e064c929a..cc56fb954fa 100644 --- a/packages/grafana-schema/src/raw/composable/loki/dataquery/x/LokiDataQuery_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/loki/dataquery/x/LokiDataQuery_types.gen.ts @@ -10,7 +10,7 @@ import * as common from '@grafana/schema'; -export const pluginVersion = "11.6.0-pre"; +export const pluginVersion = "12.0.0-pre"; export enum QueryEditorMode { Builder = 'builder', diff --git a/packages/grafana-schema/src/raw/composable/news/panelcfg/x/NewsPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/news/panelcfg/x/NewsPanelCfg_types.gen.ts index 36210e7648d..cbd6c366085 100644 --- a/packages/grafana-schema/src/raw/composable/news/panelcfg/x/NewsPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/news/panelcfg/x/NewsPanelCfg_types.gen.ts @@ -8,7 +8,7 @@ // // Run 'make gen-cue' from repository root to regenerate. -export const pluginVersion = "11.6.0-pre"; +export const pluginVersion = "12.0.0-pre"; export interface Options { /** diff --git a/packages/grafana-schema/src/raw/composable/nodegraph/panelcfg/x/NodeGraphPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/nodegraph/panelcfg/x/NodeGraphPanelCfg_types.gen.ts index 34084472b19..b6c0e360636 100644 --- a/packages/grafana-schema/src/raw/composable/nodegraph/panelcfg/x/NodeGraphPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/nodegraph/panelcfg/x/NodeGraphPanelCfg_types.gen.ts @@ -8,7 +8,7 @@ // // Run 'make gen-cue' from repository root to regenerate. -export const pluginVersion = "11.6.0-pre"; +export const pluginVersion = "12.0.0-pre"; export interface ArcOption { /** diff --git a/packages/grafana-schema/src/raw/composable/piechart/panelcfg/x/PieChartPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/piechart/panelcfg/x/PieChartPanelCfg_types.gen.ts index 0f8681b7b78..e9dbbd45d5c 100644 --- a/packages/grafana-schema/src/raw/composable/piechart/panelcfg/x/PieChartPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/piechart/panelcfg/x/PieChartPanelCfg_types.gen.ts @@ -10,7 +10,7 @@ import * as common from '@grafana/schema'; -export const pluginVersion = "11.6.0-pre"; +export const pluginVersion = "12.0.0-pre"; /** * Select the pie chart display style. diff --git a/packages/grafana-schema/src/raw/composable/stat/panelcfg/x/StatPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/stat/panelcfg/x/StatPanelCfg_types.gen.ts index e731d7fa1fe..cac66616404 100644 --- a/packages/grafana-schema/src/raw/composable/stat/panelcfg/x/StatPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/stat/panelcfg/x/StatPanelCfg_types.gen.ts @@ -10,7 +10,7 @@ import * as common from '@grafana/schema'; -export const pluginVersion = "11.6.0-pre"; +export const pluginVersion = "12.0.0-pre"; export interface Options extends common.SingleStatBaseOptions { colorMode: common.BigValueColorMode; diff --git a/packages/grafana-schema/src/raw/composable/statetimeline/panelcfg/x/StateTimelinePanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/statetimeline/panelcfg/x/StateTimelinePanelCfg_types.gen.ts index e589cdde3d8..f3e32f729ee 100644 --- a/packages/grafana-schema/src/raw/composable/statetimeline/panelcfg/x/StateTimelinePanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/statetimeline/panelcfg/x/StateTimelinePanelCfg_types.gen.ts @@ -10,7 +10,7 @@ import * as ui from '@grafana/schema'; -export const pluginVersion = "11.6.0-pre"; +export const pluginVersion = "12.0.0-pre"; export interface Options extends ui.OptionsWithLegend, ui.OptionsWithTooltip, ui.OptionsWithTimezones { /** diff --git a/packages/grafana-schema/src/raw/composable/statushistory/panelcfg/x/StatusHistoryPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/statushistory/panelcfg/x/StatusHistoryPanelCfg_types.gen.ts index beb6db453da..554a2e83491 100644 --- a/packages/grafana-schema/src/raw/composable/statushistory/panelcfg/x/StatusHistoryPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/statushistory/panelcfg/x/StatusHistoryPanelCfg_types.gen.ts @@ -10,7 +10,7 @@ import * as ui from '@grafana/schema'; -export const pluginVersion = "11.6.0-pre"; +export const pluginVersion = "12.0.0-pre"; export interface Options extends ui.OptionsWithLegend, ui.OptionsWithTooltip, ui.OptionsWithTimezones { /** diff --git a/packages/grafana-schema/src/raw/composable/table/panelcfg/x/TablePanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/table/panelcfg/x/TablePanelCfg_types.gen.ts index bc9b29ca205..fb3d375fd29 100644 --- a/packages/grafana-schema/src/raw/composable/table/panelcfg/x/TablePanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/table/panelcfg/x/TablePanelCfg_types.gen.ts @@ -10,7 +10,7 @@ import * as ui from '@grafana/schema'; -export const pluginVersion = "11.6.0-pre"; +export const pluginVersion = "12.0.0-pre"; export interface Options { /** diff --git a/packages/grafana-schema/src/raw/composable/tempo/dataquery/x/TempoDataQuery_types.gen.ts b/packages/grafana-schema/src/raw/composable/tempo/dataquery/x/TempoDataQuery_types.gen.ts index 9a279f1653b..342f399cd17 100644 --- a/packages/grafana-schema/src/raw/composable/tempo/dataquery/x/TempoDataQuery_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/tempo/dataquery/x/TempoDataQuery_types.gen.ts @@ -19,7 +19,7 @@ export interface TempoQuery extends common.DataQuery { exemplars?: number; filters: Array; /** - * Filters that are used to query the metrics summary + * deprecated Filters that are used to query the metrics summary */ groupBy?: Array; /** diff --git a/packages/grafana-schema/src/raw/composable/text/panelcfg/x/TextPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/text/panelcfg/x/TextPanelCfg_types.gen.ts index a3cbd4096a9..cf1dc84ee31 100644 --- a/packages/grafana-schema/src/raw/composable/text/panelcfg/x/TextPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/text/panelcfg/x/TextPanelCfg_types.gen.ts @@ -8,7 +8,7 @@ // // Run 'make gen-cue' from repository root to regenerate. -export const pluginVersion = "11.6.0-pre"; +export const pluginVersion = "12.0.0-pre"; export enum TextMode { Code = 'code', diff --git a/packages/grafana-schema/src/raw/composable/timeseries/panelcfg/x/TimeSeriesPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/timeseries/panelcfg/x/TimeSeriesPanelCfg_types.gen.ts index be8096fcf67..7cb398ca8de 100644 --- a/packages/grafana-schema/src/raw/composable/timeseries/panelcfg/x/TimeSeriesPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/timeseries/panelcfg/x/TimeSeriesPanelCfg_types.gen.ts @@ -10,7 +10,7 @@ import * as common from '@grafana/schema'; -export const pluginVersion = "11.6.0-pre"; +export const pluginVersion = "12.0.0-pre"; export interface Options extends common.OptionsWithTimezones { legend: common.VizLegendOptions; diff --git a/packages/grafana-schema/src/raw/composable/trend/panelcfg/x/TrendPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/trend/panelcfg/x/TrendPanelCfg_types.gen.ts index e9e79e21c63..a0cb5ed211c 100644 --- a/packages/grafana-schema/src/raw/composable/trend/panelcfg/x/TrendPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/trend/panelcfg/x/TrendPanelCfg_types.gen.ts @@ -10,7 +10,7 @@ import * as common from '@grafana/schema'; -export const pluginVersion = "11.6.0-pre"; +export const pluginVersion = "12.0.0-pre"; /** * Identical to timeseries... except it does not have timezone settings diff --git a/packages/grafana-schema/src/raw/composable/xychart/panelcfg/x/XYChartPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/xychart/panelcfg/x/XYChartPanelCfg_types.gen.ts index 208d73a91c2..9e86a2ac7de 100644 --- a/packages/grafana-schema/src/raw/composable/xychart/panelcfg/x/XYChartPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/xychart/panelcfg/x/XYChartPanelCfg_types.gen.ts @@ -10,7 +10,7 @@ import * as common from '@grafana/schema'; -export const pluginVersion = "11.6.0-pre"; +export const pluginVersion = "12.0.0-pre"; export enum PointShape { Circle = 'circle', diff --git a/packages/grafana-schema/src/schema/dashboard/v2alpha0/dashboard.schema.cue b/packages/grafana-schema/src/schema/dashboard/v2alpha0/dashboard.schema.cue index 846d0cabd8f..e0fdc845ec3 100644 --- a/packages/grafana-schema/src/schema/dashboard/v2alpha0/dashboard.schema.cue +++ b/packages/grafana-schema/src/schema/dashboard/v2alpha0/dashboard.schema.cue @@ -554,7 +554,7 @@ RowsLayoutRowSpec: { collapsed: bool repeat?: RowRepeatOptions conditionalRendering?: ConditionalRenderingGroupKind - layout: GridLayoutKind | ResponsiveGridLayoutKind | TabsLayoutKind + layout: GridLayoutKind | ResponsiveGridLayoutKind | TabsLayoutKind | RowsLayoutKind } ResponsiveGridLayoutKind: { @@ -595,7 +595,7 @@ TabsLayoutTabKind: { TabsLayoutTabSpec: { title?: string - layout: GridLayoutKind | RowsLayoutKind | ResponsiveGridLayoutKind + layout: GridLayoutKind | RowsLayoutKind | ResponsiveGridLayoutKind | TabsLayoutKind } PanelSpec: { diff --git a/packages/grafana-schema/src/schema/dashboard/v2alpha0/types.gen.ts b/packages/grafana-schema/src/schema/dashboard/v2alpha0/types.gen.ts index 895014f6b6c..022ee9f3bc8 100644 --- a/packages/grafana-schema/src/schema/dashboard/v2alpha0/types.gen.ts +++ b/packages/grafana-schema/src/schema/dashboard/v2alpha0/types.gen.ts @@ -51,49 +51,54 @@ export const defaultDashboardV2Spec = (): DashboardV2Spec => ({ variables: [], }); -// Supported dashboard elements -// |* more element types in the future -export type Element = PanelKind | LibraryPanelKind; - -export const defaultElement = (): Element => (defaultPanelKind()); - -export interface LibraryPanelKind { - kind: "LibraryPanel"; - spec: LibraryPanelSpec; +export interface AnnotationQueryKind { + kind: "AnnotationQuery"; + spec: AnnotationQuerySpec; } -export const defaultLibraryPanelKind = (): LibraryPanelKind => ({ - kind: "LibraryPanel", - spec: defaultLibraryPanelSpec(), +export const defaultAnnotationQueryKind = (): AnnotationQueryKind => ({ + kind: "AnnotationQuery", + spec: defaultAnnotationQuerySpec(), }); -export interface LibraryPanelSpec { - // Panel ID for the library panel in the dashboard - id: number; - // Title for the library panel in the dashboard - title: string; - libraryPanel: LibraryPanelRef; -} - -export const defaultLibraryPanelSpec = (): LibraryPanelSpec => ({ - id: 0, - title: "", - libraryPanel: defaultLibraryPanelRef(), -}); - -// A library panel is a reusable panel that you can use in any dashboard. -// When you make a change to a library panel, that change propagates to all instances of where the panel is used. -// Library panels streamline reuse of panels across multiple dashboards. -export interface LibraryPanelRef { - // Library panel name +export interface AnnotationQuerySpec { + datasource?: DataSourceRef; + query?: DataQueryKind; + enable: boolean; + hide: boolean; + iconColor: string; name: string; - // Library panel uid - uid: string; + builtIn?: boolean; + filter?: AnnotationPanelFilter; } -export const defaultLibraryPanelRef = (): LibraryPanelRef => ({ +export const defaultAnnotationQuerySpec = (): AnnotationQuerySpec => ({ + enable: false, + hide: false, + iconColor: "", name: "", - uid: "", + builtIn: false, +}); + +export interface DataSourceRef { + // The plugin type-id + type?: string; + // Specific datasource instance + uid?: string; +} + +export const defaultDataSourceRef = (): DataSourceRef => ({ +}); + +export interface DataQueryKind { + // The kind of a DataQueryKind is the datasource type + kind: string; + spec: Record; +} + +export const defaultDataQueryKind = (): DataQueryKind => ({ + kind: "", + spec: {}, }); export interface AnnotationPanelFilter { @@ -115,51 +120,106 @@ export type DashboardCursorSync = "Off" | "Crosshair" | "Tooltip"; export const defaultDashboardCursorSync = (): DashboardCursorSync => ("Off"); -// Links with references to other dashboards or external resources -export interface DashboardLink { - // Title to display with the link - title: string; - // Link type. Accepted values are dashboards (to refer to another dashboard) and link (to refer to an external resource) - // FIXME: The type is generated as `type: DashboardLinkType | dashboardLinkType.Link;` but it should be `type: DashboardLinkType` - type: DashboardLinkType; - // Icon name to be displayed with the link - icon: string; - // Tooltip to display when the user hovers their mouse over it - tooltip: string; - // Link URL. Only required/valid if the type is link - url?: string; - // List of tags to limit the linked dashboards. If empty, all dashboards will be displayed. Only valid if the type is dashboards - tags: string[]; - // If true, all dashboards links will be displayed in a dropdown. If false, all dashboards links will be displayed side by side. Only valid if the type is dashboards - asDropdown: boolean; - // If true, the link will be opened in a new tab - targetBlank: boolean; - // If true, includes current template variables values in the link as query params - includeVars: boolean; - // If true, includes current time range in the link as query params - keepTime: boolean; +// Supported dashboard elements +// |* more element types in the future +export type Element = PanelKind | LibraryPanelKind; + +export const defaultElement = (): Element => (defaultPanelKind()); + +export interface PanelKind { + kind: "Panel"; + spec: PanelSpec; } -export const defaultDashboardLink = (): DashboardLink => ({ - title: "", - type: "link", - icon: "", - tooltip: "", - tags: [], - asDropdown: false, - targetBlank: false, - includeVars: false, - keepTime: false, +export const defaultPanelKind = (): PanelKind => ({ + kind: "Panel", + spec: defaultPanelSpec(), }); -export interface DataSourceRef { - // The plugin type-id - type?: string; - // Specific datasource instance - uid?: string; +export interface PanelSpec { + id: number; + title: string; + description: string; + links: DataLink[]; + data: QueryGroupKind; + vizConfig: VizConfigKind; + transparent?: boolean; } -export const defaultDataSourceRef = (): DataSourceRef => ({ +export const defaultPanelSpec = (): PanelSpec => ({ + id: 0, + title: "", + description: "", + links: [], + data: defaultQueryGroupKind(), + vizConfig: defaultVizConfigKind(), +}); + +export interface DataLink { + title: string; + url: string; + targetBlank?: boolean; +} + +export const defaultDataLink = (): DataLink => ({ + title: "", + url: "", +}); + +export interface QueryGroupKind { + kind: "QueryGroup"; + spec: QueryGroupSpec; +} + +export const defaultQueryGroupKind = (): QueryGroupKind => ({ + kind: "QueryGroup", + spec: defaultQueryGroupSpec(), +}); + +export interface QueryGroupSpec { + queries: PanelQueryKind[]; + transformations: TransformationKind[]; + queryOptions: QueryOptionsSpec; +} + +export const defaultQueryGroupSpec = (): QueryGroupSpec => ({ + queries: [], + transformations: [], + queryOptions: defaultQueryOptionsSpec(), +}); + +export interface PanelQueryKind { + kind: "PanelQuery"; + spec: PanelQuerySpec; +} + +export const defaultPanelQueryKind = (): PanelQueryKind => ({ + kind: "PanelQuery", + spec: defaultPanelQuerySpec(), +}); + +export interface PanelQuerySpec { + query: DataQueryKind; + datasource?: DataSourceRef; + refId: string; + hidden: boolean; +} + +export const defaultPanelQuerySpec = (): PanelQuerySpec => ({ + query: defaultDataQueryKind(), + refId: "", + hidden: false, +}); + +export interface TransformationKind { + // The kind of a TransformationKind is the transformation ID + kind: string; + spec: DataTransformerConfig; +} + +export const defaultTransformationKind = (): TransformationKind => ({ + kind: "", + spec: defaultDataTransformerConfig(), }); // Transformations allow to manipulate data returned by a query before the system applies a visualization. @@ -184,15 +244,54 @@ export const defaultDataTransformerConfig = (): DataTransformerConfig => ({ options: {}, }); -export interface DataLink { - title: string; - url: string; - targetBlank?: boolean; +// Matcher is a predicate configuration. Based on the config a set of field(s) or values is filtered in order to apply override / transformation. +// It comes with in id ( to resolve implementation from registry) and a configuration that’s specific to a particular matcher type. +export interface MatcherConfig { + // The matcher id. This is used to find the matcher implementation from registry. + id: string; + // The matcher options. This is specific to the matcher implementation. + options?: any; } -export const defaultDataLink = (): DataLink => ({ - title: "", - url: "", +export const defaultMatcherConfig = (): MatcherConfig => ({ + id: "", +}); + +export interface QueryOptionsSpec { + timeFrom?: string; + maxDataPoints?: number; + timeShift?: string; + queryCachingTTL?: number; + interval?: string; + cacheTimeout?: string; + hideTimeOverride?: boolean; +} + +export const defaultQueryOptionsSpec = (): QueryOptionsSpec => ({ +}); + +export interface VizConfigKind { + // The kind of a VizConfigKind is the plugin ID + kind: string; + spec: VizConfigSpec; +} + +export const defaultVizConfigKind = (): VizConfigKind => ({ + kind: "", + spec: defaultVizConfigSpec(), +}); + +// --- Kinds --- +export interface VizConfigSpec { + pluginVersion: string; + options: Record; + fieldConfig: FieldConfigSource; +} + +export const defaultVizConfigSpec = (): VizConfigSpec => ({ + pluginVersion: "", + options: {}, + fieldConfig: defaultFieldConfigSource(), }); // The data model used in Grafana, namely the data frame, is a columnar-oriented table structure that unifies both time series and table query results. @@ -272,65 +371,10 @@ export interface FieldConfig { export const defaultFieldConfig = (): FieldConfig => ({ }); -export interface DynamicConfigValue { - id: string; - value?: any; -} - -export const defaultDynamicConfigValue = (): DynamicConfigValue => ({ - id: "", -}); - -// Matcher is a predicate configuration. Based on the config a set of field(s) or values is filtered in order to apply override / transformation. -// It comes with in id ( to resolve implementation from registry) and a configuration that’s specific to a particular matcher type. -export interface MatcherConfig { - // The matcher id. This is used to find the matcher implementation from registry. - id: string; - // The matcher options. This is specific to the matcher implementation. - options?: any; -} - -export const defaultMatcherConfig = (): MatcherConfig => ({ - id: "", -}); - -export interface Threshold { - value: number; - color: string; -} - -export const defaultThreshold = (): Threshold => ({ - value: 0, - color: "", -}); - -export type ThresholdsMode = "absolute" | "percentage"; - -export const defaultThresholdsMode = (): ThresholdsMode => ("absolute"); - -export interface ThresholdsConfig { - mode: ThresholdsMode; - steps: Threshold[]; -} - -export const defaultThresholdsConfig = (): ThresholdsConfig => ({ - mode: "absolute", - steps: [], -}); - export type ValueMapping = ValueMap | RangeMap | RegexMap | SpecialValueMap; export const defaultValueMapping = (): ValueMapping => (defaultValueMap()); -// Supported value mapping types -// `value`: Maps text values to a color or different display text and color. For example, you can configure a value mapping so that all instances of the value 10 appear as Perfection! rather than the number. -// `range`: Maps numerical ranges to a display text and color. For example, if a value is within a certain range, you can configure a range value mapping to display Low or High rather than the number. -// `regex`: Maps regular expressions to replacement text and a color. For example, if a value is www.example.com, you can configure a regex value mapping so that Grafana displays www and truncates the domain. -// `special`: Maps special values like Null, NaN (not a number), and boolean values like true and false to a display text and color. See SpecialValueMatch to see the list of special values. For example, you can configure a special value mapping so that null values appear as N/A. -export type MappingType = "value" | "range" | "regex" | "special"; - -export const defaultMappingType = (): MappingType => ("value"); - // Maps text values to a color or different display text and color. // For example, you can configure a value mapping so that all instances of the value 10 appear as Perfection! rather than the number. export interface ValueMap { @@ -344,6 +388,21 @@ export const defaultValueMap = (): ValueMap => ({ options: {}, }); +// Result used as replacement with text and color when the value matches +export interface ValueMappingResult { + // Text to display when the value matches + text?: string; + // Text to use when the value matches + color?: string; + // Icon to display when the value matches. Only specific visualizations. + icon?: string; + // Position in the mapping array. Only used internally. + index?: number; +} + +export const defaultValueMappingResult = (): ValueMappingResult => ({ +}); + // Maps numerical ranges to a display text and color. // For example, if a value is within a certain range, you can configure a range value mapping to display Low or High rather than the number. export interface RangeMap { @@ -415,19 +474,42 @@ export type SpecialValueMatch = "true" | "false" | "null" | "nan" | "null+nan" | export const defaultSpecialValueMatch = (): SpecialValueMatch => ("true"); -// Result used as replacement with text and color when the value matches -export interface ValueMappingResult { - // Text to display when the value matches - text?: string; - // Text to use when the value matches - color?: string; - // Icon to display when the value matches. Only specific visualizations. - icon?: string; - // Position in the mapping array. Only used internally. - index?: number; +export interface ThresholdsConfig { + mode: ThresholdsMode; + steps: Threshold[]; } -export const defaultValueMappingResult = (): ValueMappingResult => ({ +export const defaultThresholdsConfig = (): ThresholdsConfig => ({ + mode: "absolute", + steps: [], +}); + +export type ThresholdsMode = "absolute" | "percentage"; + +export const defaultThresholdsMode = (): ThresholdsMode => ("absolute"); + +export interface Threshold { + value: number; + color: string; +} + +export const defaultThreshold = (): Threshold => ({ + value: 0, + color: "", +}); + +// Map a field to a color. +export interface FieldColor { + // The main color scheme mode. + mode: FieldColorModeId; + // The fixed color value for fixed or shades color modes. + fixedColor?: string; + // Some visualizations need to know how to assign a series color from by value color schemes. + seriesBy?: FieldColorSeriesByMode; +} + +export const defaultFieldColor = (): FieldColor => ({ + mode: "thresholds", }); // Color mode for a field. You can specify a single color, or select a continuous (gradient) color schemes, based on a value. @@ -457,268 +539,80 @@ export type FieldColorSeriesByMode = "min" | "max" | "last"; export const defaultFieldColorSeriesByMode = (): FieldColorSeriesByMode => ("min"); -// Map a field to a color. -export interface FieldColor { - // The main color scheme mode. - mode: FieldColorModeId; - // The fixed color value for fixed or shades color modes. - fixedColor?: string; - // Some visualizations need to know how to assign a series color from by value color schemes. - seriesBy?: FieldColorSeriesByMode; +export interface DynamicConfigValue { + id: string; + value?: any; } -export const defaultFieldColor = (): FieldColor => ({ - mode: "thresholds", +export const defaultDynamicConfigValue = (): DynamicConfigValue => ({ + id: "", }); -// Dashboard Link type. Accepted values are dashboards (to refer to another dashboard) and link (to refer to an external resource) -export type DashboardLinkType = "link" | "dashboards"; - -export const defaultDashboardLinkType = (): DashboardLinkType => ("link"); - -// --- Common types --- -export interface Kind { - kind: string; - spec: any; - metadata?: any; +export interface LibraryPanelKind { + kind: "LibraryPanel"; + spec: LibraryPanelSpec; } -export const defaultKind = (): Kind => ({ - kind: "", - spec: {}, +export const defaultLibraryPanelKind = (): LibraryPanelKind => ({ + kind: "LibraryPanel", + spec: defaultLibraryPanelSpec(), }); -// --- Kinds --- -export interface VizConfigSpec { - pluginVersion: string; - options: Record; - fieldConfig: FieldConfigSource; +export interface LibraryPanelSpec { + // Panel ID for the library panel in the dashboard + id: number; + // Title for the library panel in the dashboard + title: string; + libraryPanel: LibraryPanelRef; } -export const defaultVizConfigSpec = (): VizConfigSpec => ({ - pluginVersion: "", - options: {}, - fieldConfig: defaultFieldConfigSource(), +export const defaultLibraryPanelSpec = (): LibraryPanelSpec => ({ + id: 0, + title: "", + libraryPanel: defaultLibraryPanelRef(), }); -export interface VizConfigKind { - // The kind of a VizConfigKind is the plugin ID - kind: string; - spec: VizConfigSpec; -} - -export const defaultVizConfigKind = (): VizConfigKind => ({ - kind: "", - spec: defaultVizConfigSpec(), -}); - -export interface AnnotationQuerySpec { - datasource?: DataSourceRef; - query?: DataQueryKind; - enable: boolean; - hide: boolean; - iconColor: string; +// A library panel is a reusable panel that you can use in any dashboard. +// When you make a change to a library panel, that change propagates to all instances of where the panel is used. +// Library panels streamline reuse of panels across multiple dashboards. +export interface LibraryPanelRef { + // Library panel name name: string; - builtIn?: boolean; - filter?: AnnotationPanelFilter; + // Library panel uid + uid: string; } -export const defaultAnnotationQuerySpec = (): AnnotationQuerySpec => ({ - enable: false, - hide: false, - iconColor: "", +export const defaultLibraryPanelRef = (): LibraryPanelRef => ({ name: "", - builtIn: false, + uid: "", }); -export interface AnnotationQueryKind { - kind: "AnnotationQuery"; - spec: AnnotationQuerySpec; +export interface GridLayoutKind { + kind: "GridLayout"; + spec: GridLayoutSpec; } -export const defaultAnnotationQueryKind = (): AnnotationQueryKind => ({ - kind: "AnnotationQuery", - spec: defaultAnnotationQuerySpec(), +export const defaultGridLayoutKind = (): GridLayoutKind => ({ + kind: "GridLayout", + spec: defaultGridLayoutSpec(), }); -export interface QueryOptionsSpec { - timeFrom?: string; - maxDataPoints?: number; - timeShift?: string; - queryCachingTTL?: number; - interval?: string; - cacheTimeout?: string; - hideTimeOverride?: boolean; +export interface GridLayoutSpec { + items: (GridLayoutItemKind | GridLayoutRowKind)[]; } -export const defaultQueryOptionsSpec = (): QueryOptionsSpec => ({ +export const defaultGridLayoutSpec = (): GridLayoutSpec => ({ + items: [], }); -export interface DataQueryKind { - // The kind of a DataQueryKind is the datasource type - kind: string; - spec: Record; +export interface GridLayoutItemKind { + kind: "GridLayoutItem"; + spec: GridLayoutItemSpec; } -export const defaultDataQueryKind = (): DataQueryKind => ({ - kind: "", - spec: {}, -}); - -export interface PanelQuerySpec { - query: DataQueryKind; - datasource?: DataSourceRef; - refId: string; - hidden: boolean; -} - -export const defaultPanelQuerySpec = (): PanelQuerySpec => ({ - query: defaultDataQueryKind(), - refId: "", - hidden: false, -}); - -export interface PanelQueryKind { - kind: "PanelQuery"; - spec: PanelQuerySpec; -} - -export const defaultPanelQueryKind = (): PanelQueryKind => ({ - kind: "PanelQuery", - spec: defaultPanelQuerySpec(), -}); - -export interface TransformationKind { - // The kind of a TransformationKind is the transformation ID - kind: string; - spec: DataTransformerConfig; -} - -export const defaultTransformationKind = (): TransformationKind => ({ - kind: "", - spec: defaultDataTransformerConfig(), -}); - -export interface QueryGroupSpec { - queries: PanelQueryKind[]; - transformations: TransformationKind[]; - queryOptions: QueryOptionsSpec; -} - -export const defaultQueryGroupSpec = (): QueryGroupSpec => ({ - queries: [], - transformations: [], - queryOptions: defaultQueryOptionsSpec(), -}); - -export interface QueryGroupKind { - kind: "QueryGroup"; - spec: QueryGroupSpec; -} - -export const defaultQueryGroupKind = (): QueryGroupKind => ({ - kind: "QueryGroup", - spec: defaultQueryGroupSpec(), -}); - -export interface TimeRangeOption { - display: string; - from: string; - to: string; -} - -export const defaultTimeRangeOption = (): TimeRangeOption => ({ - display: "Last 6 hours", - from: "now-6h", - to: "now", -}); - -// Time configuration -// It defines the default time config for the time picker, the refresh picker for the specific dashboard. -export interface TimeSettingsSpec { - // Timezone of dashboard. Accepted values are IANA TZDB zone ID or "browser" or "utc". - timezone?: string; - // Start time range for dashboard. - // Accepted values are relative time strings like "now-6h" or absolute time strings like "2020-07-10T08:00:00.000Z". - from: string; - // End time range for dashboard. - // Accepted values are relative time strings like "now-6h" or absolute time strings like "2020-07-10T08:00:00.000Z". - to: string; - // Refresh rate of dashboard. Represented via interval string, e.g. "5s", "1m", "1h", "1d". - // v1: refresh - autoRefresh: string; - // Interval options available in the refresh picker dropdown. - // v1: timepicker.refresh_intervals - autoRefreshIntervals: string[]; - // Selectable options available in the time picker dropdown. Has no effect on provisioned dashboard. - // v1: timepicker.quick_ranges , not exposed in the UI - quickRanges?: TimeRangeOption[]; - // Whether timepicker is visible or not. - // v1: timepicker.hidden - hideTimepicker: boolean; - // Day when the week starts. Expressed by the name of the day in lowercase, e.g. "monday". - weekStart?: "saturday" | "monday" | "sunday"; - // The month that the fiscal year starts on. 0 = January, 11 = December - fiscalYearStartMonth: number; - // Override the now time by entering a time delay. Use this option to accommodate known delays in data aggregation to avoid null values. - // v1: timepicker.nowDelay - nowDelay?: string; -} - -export const defaultTimeSettingsSpec = (): TimeSettingsSpec => ({ - timezone: "browser", - from: "now-6h", - to: "now", - autoRefresh: "", - autoRefreshIntervals: [ -"5s", -"10s", -"30s", -"1m", -"5m", -"15m", -"30m", -"1h", -"2h", -"1d", -], - hideTimepicker: false, - fiscalYearStartMonth: 0, -}); - -// other repeat modes will be added in the future: label, frame -export const RepeatMode = "variable"; - -export interface RepeatOptions { - mode: "variable"; - value: string; - direction?: "h" | "v"; - maxPerRow?: number; -} - -export const defaultRepeatOptions = (): RepeatOptions => ({ - mode: RepeatMode, - value: "", -}); - -export interface RowRepeatOptions { - mode: "variable"; - value: string; -} - -export const defaultRowRepeatOptions = (): RowRepeatOptions => ({ - mode: RepeatMode, - value: "", -}); - -export interface ResponsiveGridRepeatOptions { - mode: "variable"; - value: string; -} - -export const defaultResponsiveGridRepeatOptions = (): ResponsiveGridRepeatOptions => ({ - mode: RepeatMode, - value: "", +export const defaultGridLayoutItemKind = (): GridLayoutItemKind => ({ + kind: "GridLayoutItem", + spec: defaultGridLayoutItemSpec(), }); export interface GridLayoutItemSpec { @@ -739,16 +633,31 @@ export const defaultGridLayoutItemSpec = (): GridLayoutItemSpec => ({ element: defaultElementReference(), }); -export interface GridLayoutItemKind { - kind: "GridLayoutItem"; - spec: GridLayoutItemSpec; +export interface ElementReference { + kind: "ElementReference"; + name: string; } -export const defaultGridLayoutItemKind = (): GridLayoutItemKind => ({ - kind: "GridLayoutItem", - spec: defaultGridLayoutItemSpec(), +export const defaultElementReference = (): ElementReference => ({ + kind: "ElementReference", + name: "", }); +export interface RepeatOptions { + mode: "variable"; + value: string; + direction?: "h" | "v"; + maxPerRow?: number; +} + +export const defaultRepeatOptions = (): RepeatOptions => ({ + mode: RepeatMode, + value: "", +}); + +// other repeat modes will be added in the future: label, frame +export const RepeatMode = "variable"; + export interface GridLayoutRowKind { kind: "GridLayoutRow"; spec: GridLayoutRowSpec; @@ -775,22 +684,14 @@ export const defaultGridLayoutRowSpec = (): GridLayoutRowSpec => ({ elements: [], }); -export interface GridLayoutSpec { - items: (GridLayoutItemKind | GridLayoutRowKind)[]; +export interface RowRepeatOptions { + mode: "variable"; + value: string; } -export const defaultGridLayoutSpec = (): GridLayoutSpec => ({ - items: [], -}); - -export interface GridLayoutKind { - kind: "GridLayout"; - spec: GridLayoutSpec; -} - -export const defaultGridLayoutKind = (): GridLayoutKind => ({ - kind: "GridLayout", - spec: defaultGridLayoutSpec(), +export const defaultRowRepeatOptions = (): RowRepeatOptions => ({ + mode: RepeatMode, + value: "", }); export interface RowsLayoutKind { @@ -826,7 +727,7 @@ export interface RowsLayoutRowSpec { collapsed: boolean; repeat?: RowRepeatOptions; conditionalRendering?: ConditionalRenderingGroupKind; - layout: GridLayoutKind | ResponsiveGridLayoutKind | TabsLayoutKind; + layout: GridLayoutKind | ResponsiveGridLayoutKind | TabsLayoutKind | RowsLayoutKind; } export const defaultRowsLayoutRowSpec = (): RowsLayoutRowSpec => ({ @@ -834,575 +735,6 @@ export const defaultRowsLayoutRowSpec = (): RowsLayoutRowSpec => ({ layout: defaultGridLayoutKind(), }); -export interface ResponsiveGridLayoutKind { - kind: "ResponsiveGridLayout"; - spec: ResponsiveGridLayoutSpec; -} - -export const defaultResponsiveGridLayoutKind = (): ResponsiveGridLayoutKind => ({ - kind: "ResponsiveGridLayout", - spec: defaultResponsiveGridLayoutSpec(), -}); - -export interface ResponsiveGridLayoutSpec { - row: string; - col: string; - items: ResponsiveGridLayoutItemKind[]; -} - -export const defaultResponsiveGridLayoutSpec = (): ResponsiveGridLayoutSpec => ({ - row: "", - col: "", - items: [], -}); - -export interface ResponsiveGridLayoutItemKind { - kind: "ResponsiveGridLayoutItem"; - spec: ResponsiveGridLayoutItemSpec; -} - -export const defaultResponsiveGridLayoutItemKind = (): ResponsiveGridLayoutItemKind => ({ - kind: "ResponsiveGridLayoutItem", - spec: defaultResponsiveGridLayoutItemSpec(), -}); - -export interface ResponsiveGridLayoutItemSpec { - element: ElementReference; - repeat?: ResponsiveGridRepeatOptions; - conditionalRendering?: ConditionalRenderingGroupKind; -} - -export const defaultResponsiveGridLayoutItemSpec = (): ResponsiveGridLayoutItemSpec => ({ - element: defaultElementReference(), -}); - -export interface TabsLayoutKind { - kind: "TabsLayout"; - spec: TabsLayoutSpec; -} - -export const defaultTabsLayoutKind = (): TabsLayoutKind => ({ - kind: "TabsLayout", - spec: defaultTabsLayoutSpec(), -}); - -export interface TabsLayoutSpec { - tabs: TabsLayoutTabKind[]; -} - -export const defaultTabsLayoutSpec = (): TabsLayoutSpec => ({ - tabs: [], -}); - -export interface TabsLayoutTabKind { - kind: "TabsLayoutTab"; - spec: TabsLayoutTabSpec; -} - -export const defaultTabsLayoutTabKind = (): TabsLayoutTabKind => ({ - kind: "TabsLayoutTab", - spec: defaultTabsLayoutTabSpec(), -}); - -export interface TabsLayoutTabSpec { - title?: string; - layout: GridLayoutKind | RowsLayoutKind | ResponsiveGridLayoutKind; -} - -export const defaultTabsLayoutTabSpec = (): TabsLayoutTabSpec => ({ - layout: defaultGridLayoutKind(), -}); - -export interface PanelSpec { - id: number; - title: string; - description: string; - links: DataLink[]; - data: QueryGroupKind; - vizConfig: VizConfigKind; - transparent?: boolean; -} - -export const defaultPanelSpec = (): PanelSpec => ({ - id: 0, - title: "", - description: "", - links: [], - data: defaultQueryGroupKind(), - vizConfig: defaultVizConfigKind(), -}); - -export interface PanelKind { - kind: "Panel"; - spec: PanelSpec; -} - -export const defaultPanelKind = (): PanelKind => ({ - kind: "Panel", - spec: defaultPanelSpec(), -}); - -export interface ElementReference { - kind: "ElementReference"; - name: string; -} - -export const defaultElementReference = (): ElementReference => ({ - kind: "ElementReference", - name: "", -}); - -// Variable types -export type VariableValue = VariableValueSingle | VariableValueSingle[]; - -export const defaultVariableValue = (): VariableValue => (defaultVariableValueSingle()); - -export type VariableValueSingle = string | boolean | number | CustomVariableValue; - -export const defaultVariableValueSingle = (): VariableValueSingle => (""); - -// Custom formatter variable -export interface CustomFormatterVariable { - name: string; - type: VariableType; - multi: boolean; - includeAll: boolean; -} - -export const defaultCustomFormatterVariable = (): CustomFormatterVariable => ({ - name: "", - type: "query", - multi: false, - includeAll: false, -}); - -// Custom variable value -export interface CustomVariableValue { - // The format name or function used in the expression - formatter: string | VariableCustomFormatterFn; -} - -export const defaultCustomVariableValue = (): CustomVariableValue => ({ - formatter: "", -}); - -// Custom formatter function -export interface VariableCustomFormatterFn { - value: any; - legacyVariableModel: { - name: string; - type: VariableType; - multi: boolean; - includeAll: boolean; - }; - legacyDefaultFormatter?: VariableCustomFormatterFn; -} - -export const defaultVariableCustomFormatterFn = (): VariableCustomFormatterFn => ({ - value: {}, - legacyVariableModel: { - name: "", - type: "query", - multi: false, - includeAll: false, -}, -}); - -// Dashboard variable type -// `query`: Query-generated list of values such as metric names, server names, sensor IDs, data centers, and so on. -// `adhoc`: Key/value filters that are automatically added to all metric queries for a data source (Prometheus, Loki, InfluxDB, and Elasticsearch only). -// `constant`: Define a hidden constant. -// `datasource`: Quickly change the data source for an entire dashboard. -// `interval`: Interval variables represent time spans. -// `textbox`: Display a free text input field with an optional default value. -// `custom`: Define the variable options manually using a comma-separated list. -// `system`: Variables defined by Grafana. See: https://grafana.com/docs/grafana/latest/dashboards/variables/add-template-variables/#global-variables -export type VariableType = "query" | "adhoc" | "groupby" | "constant" | "datasource" | "interval" | "textbox" | "custom" | "system" | "snapshot"; - -export const defaultVariableType = (): VariableType => ("query"); - -export type VariableKind = QueryVariableKind | TextVariableKind | ConstantVariableKind | DatasourceVariableKind | IntervalVariableKind | CustomVariableKind | GroupByVariableKind | AdhocVariableKind; - -export const defaultVariableKind = (): VariableKind => (defaultQueryVariableKind()); - -// Sort variable options -// Accepted values are: -// `disabled`: No sorting -// `alphabeticalAsc`: Alphabetical ASC -// `alphabeticalDesc`: Alphabetical DESC -// `numericalAsc`: Numerical ASC -// `numericalDesc`: Numerical DESC -// `alphabeticalCaseInsensitiveAsc`: Alphabetical Case Insensitive ASC -// `alphabeticalCaseInsensitiveDesc`: Alphabetical Case Insensitive DESC -// `naturalAsc`: Natural ASC -// `naturalDesc`: Natural DESC -// VariableSort enum with default value -export type VariableSort = "disabled" | "alphabeticalAsc" | "alphabeticalDesc" | "numericalAsc" | "numericalDesc" | "alphabeticalCaseInsensitiveAsc" | "alphabeticalCaseInsensitiveDesc" | "naturalAsc" | "naturalDesc"; - -export const defaultVariableSort = (): VariableSort => ("disabled"); - -// Options to config when to refresh a variable -// `never`: Never refresh the variable -// `onDashboardLoad`: Queries the data source every time the dashboard loads. -// `onTimeRangeChanged`: Queries the data source when the dashboard time range changes. -export type VariableRefresh = "never" | "onDashboardLoad" | "onTimeRangeChanged"; - -export const defaultVariableRefresh = (): VariableRefresh => ("never"); - -// Determine if the variable shows on dashboard -// Accepted values are `dontHide` (show label and value), `hideLabel` (show value only), `hideVariable` (show nothing). -export type VariableHide = "dontHide" | "hideLabel" | "hideVariable"; - -export const defaultVariableHide = (): VariableHide => ("dontHide"); - -// FIXME: should we introduce this? --- Variable value option -export interface VariableValueOption { - label: string; - value: VariableValueSingle; - group?: string; -} - -export const defaultVariableValueOption = (): VariableValueOption => ({ - label: "", - value: defaultVariableValueSingle(), -}); - -// Variable option specification -export interface VariableOption { - // Whether the option is selected or not - selected?: boolean; - // Text to be displayed for the option - text: string | string[]; - // Value of the option - value: string | string[]; -} - -export const defaultVariableOption = (): VariableOption => ({ - text: "", - value: "", -}); - -// Query variable specification -export interface QueryVariableSpec { - name: string; - current: VariableOption; - label?: string; - hide: VariableHide; - refresh: VariableRefresh; - skipUrlSync: boolean; - description?: string; - datasource?: DataSourceRef; - query: DataQueryKind; - regex: string; - sort: VariableSort; - definition?: string; - options: VariableOption[]; - multi: boolean; - includeAll: boolean; - allValue?: string; - placeholder?: string; -} - -export const defaultQueryVariableSpec = (): QueryVariableSpec => ({ - name: "", - current: { text: "", value: "", }, - hide: "dontHide", - refresh: "never", - skipUrlSync: false, - query: defaultDataQueryKind(), - regex: "", - sort: "disabled", - options: [], - multi: false, - includeAll: false, -}); - -// Query variable kind -export interface QueryVariableKind { - kind: "QueryVariable"; - spec: QueryVariableSpec; -} - -export const defaultQueryVariableKind = (): QueryVariableKind => ({ - kind: "QueryVariable", - spec: defaultQueryVariableSpec(), -}); - -// Text variable specification -export interface TextVariableSpec { - name: string; - current: VariableOption; - query: string; - label?: string; - hide: VariableHide; - skipUrlSync: boolean; - description?: string; -} - -export const defaultTextVariableSpec = (): TextVariableSpec => ({ - name: "", - current: { text: "", value: "", }, - query: "", - hide: "dontHide", - skipUrlSync: false, -}); - -// Text variable kind -export interface TextVariableKind { - kind: "TextVariable"; - spec: TextVariableSpec; -} - -export const defaultTextVariableKind = (): TextVariableKind => ({ - kind: "TextVariable", - spec: defaultTextVariableSpec(), -}); - -// Constant variable specification -export interface ConstantVariableSpec { - name: string; - query: string; - current: VariableOption; - label?: string; - hide: VariableHide; - skipUrlSync: boolean; - description?: string; -} - -export const defaultConstantVariableSpec = (): ConstantVariableSpec => ({ - name: "", - query: "", - current: { text: "", value: "", }, - hide: "dontHide", - skipUrlSync: false, -}); - -// Constant variable kind -export interface ConstantVariableKind { - kind: "ConstantVariable"; - spec: ConstantVariableSpec; -} - -export const defaultConstantVariableKind = (): ConstantVariableKind => ({ - kind: "ConstantVariable", - spec: defaultConstantVariableSpec(), -}); - -// Datasource variable specification -export interface DatasourceVariableSpec { - name: string; - pluginId: string; - refresh: VariableRefresh; - regex: string; - current: VariableOption; - options: VariableOption[]; - multi: boolean; - includeAll: boolean; - allValue?: string; - label?: string; - hide: VariableHide; - skipUrlSync: boolean; - description?: string; -} - -export const defaultDatasourceVariableSpec = (): DatasourceVariableSpec => ({ - name: "", - pluginId: "", - refresh: "never", - regex: "", - current: { text: "", value: "", }, - options: [], - multi: false, - includeAll: false, - hide: "dontHide", - skipUrlSync: false, -}); - -// Datasource variable kind -export interface DatasourceVariableKind { - kind: "DatasourceVariable"; - spec: DatasourceVariableSpec; -} - -export const defaultDatasourceVariableKind = (): DatasourceVariableKind => ({ - kind: "DatasourceVariable", - spec: defaultDatasourceVariableSpec(), -}); - -// Interval variable specification -export interface IntervalVariableSpec { - name: string; - query: string; - current: VariableOption; - options: VariableOption[]; - auto: boolean; - auto_min: string; - auto_count: number; - refresh: VariableRefresh; - label?: string; - hide: VariableHide; - skipUrlSync: boolean; - description?: string; -} - -export const defaultIntervalVariableSpec = (): IntervalVariableSpec => ({ - name: "", - query: "", - current: { text: "", value: "", }, - options: [], - auto: false, - auto_min: "", - auto_count: 0, - refresh: "never", - hide: "dontHide", - skipUrlSync: false, -}); - -// Interval variable kind -export interface IntervalVariableKind { - kind: "IntervalVariable"; - spec: IntervalVariableSpec; -} - -export const defaultIntervalVariableKind = (): IntervalVariableKind => ({ - kind: "IntervalVariable", - spec: defaultIntervalVariableSpec(), -}); - -// Custom variable specification -export interface CustomVariableSpec { - name: string; - query: string; - current: VariableOption; - options: VariableOption[]; - multi: boolean; - includeAll: boolean; - allValue?: string; - label?: string; - hide: VariableHide; - skipUrlSync: boolean; - description?: string; -} - -export const defaultCustomVariableSpec = (): CustomVariableSpec => ({ - name: "", - query: "", - current: defaultVariableOption(), - options: [], - multi: false, - includeAll: false, - hide: "dontHide", - skipUrlSync: false, -}); - -// Custom variable kind -export interface CustomVariableKind { - kind: "CustomVariable"; - spec: CustomVariableSpec; -} - -export const defaultCustomVariableKind = (): CustomVariableKind => ({ - kind: "CustomVariable", - spec: defaultCustomVariableSpec(), -}); - -// GroupBy variable specification -export interface GroupByVariableSpec { - name: string; - datasource?: DataSourceRef; - current: VariableOption; - options: VariableOption[]; - multi: boolean; - label?: string; - hide: VariableHide; - skipUrlSync: boolean; - description?: string; -} - -export const defaultGroupByVariableSpec = (): GroupByVariableSpec => ({ - name: "", - current: { text: "", value: "", }, - options: [], - multi: false, - hide: "dontHide", - skipUrlSync: false, -}); - -// Group variable kind -export interface GroupByVariableKind { - kind: "GroupByVariable"; - spec: GroupByVariableSpec; -} - -export const defaultGroupByVariableKind = (): GroupByVariableKind => ({ - kind: "GroupByVariable", - spec: defaultGroupByVariableSpec(), -}); - -// Adhoc variable specification -export interface AdhocVariableSpec { - name: string; - datasource?: DataSourceRef; - baseFilters: AdHocFilterWithLabels[]; - filters: AdHocFilterWithLabels[]; - defaultKeys: MetricFindValue[]; - label?: string; - hide: VariableHide; - skipUrlSync: boolean; - description?: string; -} - -export const defaultAdhocVariableSpec = (): AdhocVariableSpec => ({ - name: "", - baseFilters: [], - filters: [], - defaultKeys: [], - hide: "dontHide", - skipUrlSync: false, -}); - -// Define the MetricFindValue type -export interface MetricFindValue { - text: string; - value?: string | number; - group?: string; - expandable?: boolean; -} - -export const defaultMetricFindValue = (): MetricFindValue => ({ - text: "", -}); - -// Define the AdHocFilterWithLabels type -export interface AdHocFilterWithLabels { - key: string; - operator: string; - value: string; - values?: string[]; - keyLabel?: string; - valueLabels?: string[]; - forceEdit?: boolean; - // @deprecated - condition?: string; -} - -export const defaultAdHocFilterWithLabels = (): AdHocFilterWithLabels => ({ - key: "", - operator: "", - value: "", -}); - -// Adhoc variable kind -export interface AdhocVariableKind { - kind: "AdhocVariable"; - spec: AdhocVariableSpec; -} - -export const defaultAdhocVariableKind = (): AdhocVariableKind => ({ - kind: "AdhocVariable", - spec: defaultAdhocVariableSpec(), -}); - export interface ConditionalRenderingGroupKind { kind: "ConditionalRenderingGroup"; spec: ConditionalRenderingGroupSpec; @@ -1481,3 +813,671 @@ export const defaultConditionalRenderingTimeIntervalSpec = (): ConditionalRender value: "", }); +export interface ResponsiveGridLayoutKind { + kind: "ResponsiveGridLayout"; + spec: ResponsiveGridLayoutSpec; +} + +export const defaultResponsiveGridLayoutKind = (): ResponsiveGridLayoutKind => ({ + kind: "ResponsiveGridLayout", + spec: defaultResponsiveGridLayoutSpec(), +}); + +export interface ResponsiveGridLayoutSpec { + row: string; + col: string; + items: ResponsiveGridLayoutItemKind[]; +} + +export const defaultResponsiveGridLayoutSpec = (): ResponsiveGridLayoutSpec => ({ + row: "", + col: "", + items: [], +}); + +export interface ResponsiveGridLayoutItemKind { + kind: "ResponsiveGridLayoutItem"; + spec: ResponsiveGridLayoutItemSpec; +} + +export const defaultResponsiveGridLayoutItemKind = (): ResponsiveGridLayoutItemKind => ({ + kind: "ResponsiveGridLayoutItem", + spec: defaultResponsiveGridLayoutItemSpec(), +}); + +export interface ResponsiveGridLayoutItemSpec { + element: ElementReference; + repeat?: ResponsiveGridRepeatOptions; + conditionalRendering?: ConditionalRenderingGroupKind; +} + +export const defaultResponsiveGridLayoutItemSpec = (): ResponsiveGridLayoutItemSpec => ({ + element: defaultElementReference(), +}); + +export interface ResponsiveGridRepeatOptions { + mode: "variable"; + value: string; +} + +export const defaultResponsiveGridRepeatOptions = (): ResponsiveGridRepeatOptions => ({ + mode: RepeatMode, + value: "", +}); + +export interface TabsLayoutKind { + kind: "TabsLayout"; + spec: TabsLayoutSpec; +} + +export const defaultTabsLayoutKind = (): TabsLayoutKind => ({ + kind: "TabsLayout", + spec: defaultTabsLayoutSpec(), +}); + +export interface TabsLayoutSpec { + tabs: TabsLayoutTabKind[]; +} + +export const defaultTabsLayoutSpec = (): TabsLayoutSpec => ({ + tabs: [], +}); + +export interface TabsLayoutTabKind { + kind: "TabsLayoutTab"; + spec: TabsLayoutTabSpec; +} + +export const defaultTabsLayoutTabKind = (): TabsLayoutTabKind => ({ + kind: "TabsLayoutTab", + spec: defaultTabsLayoutTabSpec(), +}); + +export interface TabsLayoutTabSpec { + title?: string; + layout: GridLayoutKind | RowsLayoutKind | ResponsiveGridLayoutKind | TabsLayoutKind; +} + +export const defaultTabsLayoutTabSpec = (): TabsLayoutTabSpec => ({ + layout: defaultGridLayoutKind(), +}); + +// Links with references to other dashboards or external resources +export interface DashboardLink { + // Title to display with the link + title: string; + // Link type. Accepted values are dashboards (to refer to another dashboard) and link (to refer to an external resource) + // FIXME: The type is generated as `type: DashboardLinkType | dashboardLinkType.Link;` but it should be `type: DashboardLinkType` + type: DashboardLinkType; + // Icon name to be displayed with the link + icon: string; + // Tooltip to display when the user hovers their mouse over it + tooltip: string; + // Link URL. Only required/valid if the type is link + url?: string; + // List of tags to limit the linked dashboards. If empty, all dashboards will be displayed. Only valid if the type is dashboards + tags: string[]; + // If true, all dashboards links will be displayed in a dropdown. If false, all dashboards links will be displayed side by side. Only valid if the type is dashboards + asDropdown: boolean; + // If true, the link will be opened in a new tab + targetBlank: boolean; + // If true, includes current template variables values in the link as query params + includeVars: boolean; + // If true, includes current time range in the link as query params + keepTime: boolean; +} + +export const defaultDashboardLink = (): DashboardLink => ({ + title: "", + type: "link", + icon: "", + tooltip: "", + tags: [], + asDropdown: false, + targetBlank: false, + includeVars: false, + keepTime: false, +}); + +// Dashboard Link type. Accepted values are dashboards (to refer to another dashboard) and link (to refer to an external resource) +export type DashboardLinkType = "link" | "dashboards"; + +export const defaultDashboardLinkType = (): DashboardLinkType => ("link"); + +// Time configuration +// It defines the default time config for the time picker, the refresh picker for the specific dashboard. +export interface TimeSettingsSpec { + // Timezone of dashboard. Accepted values are IANA TZDB zone ID or "browser" or "utc". + timezone?: string; + // Start time range for dashboard. + // Accepted values are relative time strings like "now-6h" or absolute time strings like "2020-07-10T08:00:00.000Z". + from: string; + // End time range for dashboard. + // Accepted values are relative time strings like "now-6h" or absolute time strings like "2020-07-10T08:00:00.000Z". + to: string; + // Refresh rate of dashboard. Represented via interval string, e.g. "5s", "1m", "1h", "1d". + // v1: refresh + autoRefresh: string; + // Interval options available in the refresh picker dropdown. + // v1: timepicker.refresh_intervals + autoRefreshIntervals: string[]; + // Selectable options available in the time picker dropdown. Has no effect on provisioned dashboard. + // v1: timepicker.quick_ranges , not exposed in the UI + quickRanges?: TimeRangeOption[]; + // Whether timepicker is visible or not. + // v1: timepicker.hidden + hideTimepicker: boolean; + // Day when the week starts. Expressed by the name of the day in lowercase, e.g. "monday". + weekStart?: "saturday" | "monday" | "sunday"; + // The month that the fiscal year starts on. 0 = January, 11 = December + fiscalYearStartMonth: number; + // Override the now time by entering a time delay. Use this option to accommodate known delays in data aggregation to avoid null values. + // v1: timepicker.nowDelay + nowDelay?: string; +} + +export const defaultTimeSettingsSpec = (): TimeSettingsSpec => ({ + timezone: "browser", + from: "now-6h", + to: "now", + autoRefresh: "", + autoRefreshIntervals: [ +"5s", +"10s", +"30s", +"1m", +"5m", +"15m", +"30m", +"1h", +"2h", +"1d", +], + hideTimepicker: false, + fiscalYearStartMonth: 0, +}); + +export interface TimeRangeOption { + display: string; + from: string; + to: string; +} + +export const defaultTimeRangeOption = (): TimeRangeOption => ({ + display: "Last 6 hours", + from: "now-6h", + to: "now", +}); + +export type VariableKind = QueryVariableKind | TextVariableKind | ConstantVariableKind | DatasourceVariableKind | IntervalVariableKind | CustomVariableKind | GroupByVariableKind | AdhocVariableKind; + +export const defaultVariableKind = (): VariableKind => (defaultQueryVariableKind()); + +// Query variable kind +export interface QueryVariableKind { + kind: "QueryVariable"; + spec: QueryVariableSpec; +} + +export const defaultQueryVariableKind = (): QueryVariableKind => ({ + kind: "QueryVariable", + spec: defaultQueryVariableSpec(), +}); + +// Query variable specification +export interface QueryVariableSpec { + name: string; + current: VariableOption; + label?: string; + hide: VariableHide; + refresh: VariableRefresh; + skipUrlSync: boolean; + description?: string; + datasource?: DataSourceRef; + query: DataQueryKind; + regex: string; + sort: VariableSort; + definition?: string; + options: VariableOption[]; + multi: boolean; + includeAll: boolean; + allValue?: string; + placeholder?: string; +} + +export const defaultQueryVariableSpec = (): QueryVariableSpec => ({ + name: "", + current: { text: "", value: "", }, + hide: "dontHide", + refresh: "never", + skipUrlSync: false, + query: defaultDataQueryKind(), + regex: "", + sort: "disabled", + options: [], + multi: false, + includeAll: false, +}); + +// Variable option specification +export interface VariableOption { + // Whether the option is selected or not + selected?: boolean; + // Text to be displayed for the option + text: string | string[]; + // Value of the option + value: string | string[]; +} + +export const defaultVariableOption = (): VariableOption => ({ + text: "", + value: "", +}); + +// Determine if the variable shows on dashboard +// Accepted values are `dontHide` (show label and value), `hideLabel` (show value only), `hideVariable` (show nothing). +export type VariableHide = "dontHide" | "hideLabel" | "hideVariable"; + +export const defaultVariableHide = (): VariableHide => ("dontHide"); + +// Options to config when to refresh a variable +// `never`: Never refresh the variable +// `onDashboardLoad`: Queries the data source every time the dashboard loads. +// `onTimeRangeChanged`: Queries the data source when the dashboard time range changes. +export type VariableRefresh = "never" | "onDashboardLoad" | "onTimeRangeChanged"; + +export const defaultVariableRefresh = (): VariableRefresh => ("never"); + +// Sort variable options +// Accepted values are: +// `disabled`: No sorting +// `alphabeticalAsc`: Alphabetical ASC +// `alphabeticalDesc`: Alphabetical DESC +// `numericalAsc`: Numerical ASC +// `numericalDesc`: Numerical DESC +// `alphabeticalCaseInsensitiveAsc`: Alphabetical Case Insensitive ASC +// `alphabeticalCaseInsensitiveDesc`: Alphabetical Case Insensitive DESC +// `naturalAsc`: Natural ASC +// `naturalDesc`: Natural DESC +// VariableSort enum with default value +export type VariableSort = "disabled" | "alphabeticalAsc" | "alphabeticalDesc" | "numericalAsc" | "numericalDesc" | "alphabeticalCaseInsensitiveAsc" | "alphabeticalCaseInsensitiveDesc" | "naturalAsc" | "naturalDesc"; + +export const defaultVariableSort = (): VariableSort => ("disabled"); + +// Text variable kind +export interface TextVariableKind { + kind: "TextVariable"; + spec: TextVariableSpec; +} + +export const defaultTextVariableKind = (): TextVariableKind => ({ + kind: "TextVariable", + spec: defaultTextVariableSpec(), +}); + +// Text variable specification +export interface TextVariableSpec { + name: string; + current: VariableOption; + query: string; + label?: string; + hide: VariableHide; + skipUrlSync: boolean; + description?: string; +} + +export const defaultTextVariableSpec = (): TextVariableSpec => ({ + name: "", + current: { text: "", value: "", }, + query: "", + hide: "dontHide", + skipUrlSync: false, +}); + +// Constant variable kind +export interface ConstantVariableKind { + kind: "ConstantVariable"; + spec: ConstantVariableSpec; +} + +export const defaultConstantVariableKind = (): ConstantVariableKind => ({ + kind: "ConstantVariable", + spec: defaultConstantVariableSpec(), +}); + +// Constant variable specification +export interface ConstantVariableSpec { + name: string; + query: string; + current: VariableOption; + label?: string; + hide: VariableHide; + skipUrlSync: boolean; + description?: string; +} + +export const defaultConstantVariableSpec = (): ConstantVariableSpec => ({ + name: "", + query: "", + current: { text: "", value: "", }, + hide: "dontHide", + skipUrlSync: false, +}); + +// Datasource variable kind +export interface DatasourceVariableKind { + kind: "DatasourceVariable"; + spec: DatasourceVariableSpec; +} + +export const defaultDatasourceVariableKind = (): DatasourceVariableKind => ({ + kind: "DatasourceVariable", + spec: defaultDatasourceVariableSpec(), +}); + +// Datasource variable specification +export interface DatasourceVariableSpec { + name: string; + pluginId: string; + refresh: VariableRefresh; + regex: string; + current: VariableOption; + options: VariableOption[]; + multi: boolean; + includeAll: boolean; + allValue?: string; + label?: string; + hide: VariableHide; + skipUrlSync: boolean; + description?: string; +} + +export const defaultDatasourceVariableSpec = (): DatasourceVariableSpec => ({ + name: "", + pluginId: "", + refresh: "never", + regex: "", + current: { text: "", value: "", }, + options: [], + multi: false, + includeAll: false, + hide: "dontHide", + skipUrlSync: false, +}); + +// Interval variable kind +export interface IntervalVariableKind { + kind: "IntervalVariable"; + spec: IntervalVariableSpec; +} + +export const defaultIntervalVariableKind = (): IntervalVariableKind => ({ + kind: "IntervalVariable", + spec: defaultIntervalVariableSpec(), +}); + +// Interval variable specification +export interface IntervalVariableSpec { + name: string; + query: string; + current: VariableOption; + options: VariableOption[]; + auto: boolean; + auto_min: string; + auto_count: number; + refresh: VariableRefresh; + label?: string; + hide: VariableHide; + skipUrlSync: boolean; + description?: string; +} + +export const defaultIntervalVariableSpec = (): IntervalVariableSpec => ({ + name: "", + query: "", + current: { text: "", value: "", }, + options: [], + auto: false, + auto_min: "", + auto_count: 0, + refresh: "never", + hide: "dontHide", + skipUrlSync: false, +}); + +// Custom variable kind +export interface CustomVariableKind { + kind: "CustomVariable"; + spec: CustomVariableSpec; +} + +export const defaultCustomVariableKind = (): CustomVariableKind => ({ + kind: "CustomVariable", + spec: defaultCustomVariableSpec(), +}); + +// Custom variable specification +export interface CustomVariableSpec { + name: string; + query: string; + current: VariableOption; + options: VariableOption[]; + multi: boolean; + includeAll: boolean; + allValue?: string; + label?: string; + hide: VariableHide; + skipUrlSync: boolean; + description?: string; +} + +export const defaultCustomVariableSpec = (): CustomVariableSpec => ({ + name: "", + query: "", + current: defaultVariableOption(), + options: [], + multi: false, + includeAll: false, + hide: "dontHide", + skipUrlSync: false, +}); + +// Group variable kind +export interface GroupByVariableKind { + kind: "GroupByVariable"; + spec: GroupByVariableSpec; +} + +export const defaultGroupByVariableKind = (): GroupByVariableKind => ({ + kind: "GroupByVariable", + spec: defaultGroupByVariableSpec(), +}); + +// GroupBy variable specification +export interface GroupByVariableSpec { + name: string; + datasource?: DataSourceRef; + current: VariableOption; + options: VariableOption[]; + multi: boolean; + label?: string; + hide: VariableHide; + skipUrlSync: boolean; + description?: string; +} + +export const defaultGroupByVariableSpec = (): GroupByVariableSpec => ({ + name: "", + current: { text: "", value: "", }, + options: [], + multi: false, + hide: "dontHide", + skipUrlSync: false, +}); + +// Adhoc variable kind +export interface AdhocVariableKind { + kind: "AdhocVariable"; + spec: AdhocVariableSpec; +} + +export const defaultAdhocVariableKind = (): AdhocVariableKind => ({ + kind: "AdhocVariable", + spec: defaultAdhocVariableSpec(), +}); + +// Adhoc variable specification +export interface AdhocVariableSpec { + name: string; + datasource?: DataSourceRef; + baseFilters: AdHocFilterWithLabels[]; + filters: AdHocFilterWithLabels[]; + defaultKeys: MetricFindValue[]; + label?: string; + hide: VariableHide; + skipUrlSync: boolean; + description?: string; +} + +export const defaultAdhocVariableSpec = (): AdhocVariableSpec => ({ + name: "", + baseFilters: [], + filters: [], + defaultKeys: [], + hide: "dontHide", + skipUrlSync: false, +}); + +// Define the AdHocFilterWithLabels type +export interface AdHocFilterWithLabels { + key: string; + operator: string; + value: string; + values?: string[]; + keyLabel?: string; + valueLabels?: string[]; + forceEdit?: boolean; + // @deprecated + condition?: string; +} + +export const defaultAdHocFilterWithLabels = (): AdHocFilterWithLabels => ({ + key: "", + operator: "", + value: "", +}); + +// Define the MetricFindValue type +export interface MetricFindValue { + text: string; + value?: string | number; + group?: string; + expandable?: boolean; +} + +export const defaultMetricFindValue = (): MetricFindValue => ({ + text: "", +}); + +// Supported value mapping types +// `value`: Maps text values to a color or different display text and color. For example, you can configure a value mapping so that all instances of the value 10 appear as Perfection! rather than the number. +// `range`: Maps numerical ranges to a display text and color. For example, if a value is within a certain range, you can configure a range value mapping to display Low or High rather than the number. +// `regex`: Maps regular expressions to replacement text and a color. For example, if a value is www.example.com, you can configure a regex value mapping so that Grafana displays www and truncates the domain. +// `special`: Maps special values like Null, NaN (not a number), and boolean values like true and false to a display text and color. See SpecialValueMatch to see the list of special values. For example, you can configure a special value mapping so that null values appear as N/A. +export type MappingType = "value" | "range" | "regex" | "special"; + +export const defaultMappingType = (): MappingType => ("value"); + +// --- Common types --- +export interface Kind { + kind: string; + spec: any; + metadata?: any; +} + +export const defaultKind = (): Kind => ({ + kind: "", + spec: {}, +}); + +// Variable types +export type VariableValue = VariableValueSingle | VariableValueSingle[]; + +export const defaultVariableValue = (): VariableValue => (defaultVariableValueSingle()); + +export type VariableValueSingle = string | boolean | number | CustomVariableValue; + +export const defaultVariableValueSingle = (): VariableValueSingle => (""); + +// Custom variable value +export interface CustomVariableValue { + // The format name or function used in the expression + formatter: string | VariableCustomFormatterFn; +} + +export const defaultCustomVariableValue = (): CustomVariableValue => ({ + formatter: "", +}); + +// Custom formatter function +export interface VariableCustomFormatterFn { + value: any; + legacyVariableModel: { + name: string; + type: VariableType; + multi: boolean; + includeAll: boolean; + }; + legacyDefaultFormatter?: VariableCustomFormatterFn; +} + +export const defaultVariableCustomFormatterFn = (): VariableCustomFormatterFn => ({ + value: {}, + legacyVariableModel: { + name: "", + type: "query", + multi: false, + includeAll: false, +}, +}); + +// Dashboard variable type +// `query`: Query-generated list of values such as metric names, server names, sensor IDs, data centers, and so on. +// `adhoc`: Key/value filters that are automatically added to all metric queries for a data source (Prometheus, Loki, InfluxDB, and Elasticsearch only). +// `constant`: Define a hidden constant. +// `datasource`: Quickly change the data source for an entire dashboard. +// `interval`: Interval variables represent time spans. +// `textbox`: Display a free text input field with an optional default value. +// `custom`: Define the variable options manually using a comma-separated list. +// `system`: Variables defined by Grafana. See: https://grafana.com/docs/grafana/latest/dashboards/variables/add-template-variables/#global-variables +export type VariableType = "query" | "adhoc" | "groupby" | "constant" | "datasource" | "interval" | "textbox" | "custom" | "system" | "snapshot"; + +export const defaultVariableType = (): VariableType => ("query"); + +// Custom formatter variable +export interface CustomFormatterVariable { + name: string; + type: VariableType; + multi: boolean; + includeAll: boolean; +} + +export const defaultCustomFormatterVariable = (): CustomFormatterVariable => ({ + name: "", + type: "query", + multi: false, + includeAll: false, +}); + +// FIXME: should we introduce this? --- Variable value option +export interface VariableValueOption { + label: string; + value: VariableValueSingle; + group?: string; +} + +export const defaultVariableValueOption = (): VariableValueOption => ({ + label: "", + value: defaultVariableValueSingle(), +}); + diff --git a/packages/grafana-sql/package.json b/packages/grafana-sql/package.json index ef556ab0d49..3c217f75312 100644 --- a/packages/grafana-sql/package.json +++ b/packages/grafana-sql/package.json @@ -3,7 +3,7 @@ "license": "AGPL-3.0-only", "private": true, "name": "@grafana/sql", - "version": "11.6.0-pre", + "version": "12.0.0-pre", "repository": { "type": "git", "url": "http://github.com/grafana/grafana.git", @@ -15,11 +15,11 @@ }, "dependencies": { "@emotion/css": "11.13.5", - "@grafana/data": "11.6.0-pre", - "@grafana/e2e-selectors": "11.6.0-pre", + "@grafana/data": "12.0.0-pre", + "@grafana/e2e-selectors": "12.0.0-pre", "@grafana/plugin-ui": "0.10.1", - "@grafana/runtime": "11.6.0-pre", - "@grafana/ui": "11.6.0-pre", + "@grafana/runtime": "12.0.0-pre", + "@grafana/ui": "12.0.0-pre", "@react-awesome-query-builder/ui": "6.6.4", "immutable": "5.0.3", "lodash": "4.17.21", diff --git a/packages/grafana-ui/package.json b/packages/grafana-ui/package.json index e72cfe337bd..55785d7de92 100644 --- a/packages/grafana-ui/package.json +++ b/packages/grafana-ui/package.json @@ -2,7 +2,7 @@ "author": "Grafana Labs", "license": "Apache-2.0", "name": "@grafana/ui", - "version": "11.6.0-pre", + "version": "12.0.0-pre", "description": "Grafana Components Library", "keywords": [ "grafana", @@ -66,10 +66,10 @@ "@emotion/react": "11.14.0", "@emotion/serialize": "1.3.3", "@floating-ui/react": "0.27.5", - "@grafana/data": "11.6.0-pre", - "@grafana/e2e-selectors": "11.6.0-pre", + "@grafana/data": "12.0.0-pre", + "@grafana/e2e-selectors": "12.0.0-pre", "@grafana/faro-web-sdk": "^1.13.2", - "@grafana/schema": "11.6.0-pre", + "@grafana/schema": "12.0.0-pre", "@hello-pangea/dnd": "17.0.0", "@leeoniya/ufuzzy": "1.0.18", "@monaco-editor/react": "4.6.0", diff --git a/packages/grafana-ui/src/components/Combobox/useComboboxFloat.ts b/packages/grafana-ui/src/components/Combobox/useComboboxFloat.ts index f5803afdf2e..f2d5ca7de20 100644 --- a/packages/grafana-ui/src/components/Combobox/useComboboxFloat.ts +++ b/packages/grafana-ui/src/components/Combobox/useComboboxFloat.ts @@ -1,4 +1,4 @@ -import { autoUpdate, flip, size, useFloating } from '@floating-ui/react'; +import { autoUpdate, autoPlacement, size, useFloating } from '@floating-ui/react'; import { useMemo, useRef, useState } from 'react'; import { measureText } from '../../utils'; @@ -31,10 +31,11 @@ export const useComboboxFloat = (items: Array>, // the order of middleware is important! const middleware = [ - flip({ - // see https://floating-ui.com/docs/flip#combining-with-shift - crossAxis: true, + autoPlacement({ + // see https://floating-ui.com/docs/autoplacement + allowedPlacements: ['bottom-start', 'bottom-end', 'top-start', 'top-end'], boundary: document.body, + crossAxis: true, }), size({ apply({ availableWidth, availableHeight }) { diff --git a/packages/grafana-ui/src/components/DateTimePickers/TimeRangeInput.tsx b/packages/grafana-ui/src/components/DateTimePickers/TimeRangeInput.tsx index d07788bf97e..7ef005c42c6 100644 --- a/packages/grafana-ui/src/components/DateTimePickers/TimeRangeInput.tsx +++ b/packages/grafana-ui/src/components/DateTimePickers/TimeRangeInput.tsx @@ -1,8 +1,7 @@ import { css, cx } from '@emotion/css'; -import { useDialog } from '@react-aria/dialog'; +import { useDismiss, useFloating, useInteractions } from '@floating-ui/react'; import { FocusScope } from '@react-aria/focus'; -import { useOverlay } from '@react-aria/overlays'; -import { createRef, FormEvent, MouseEvent, useState } from 'react'; +import { FormEvent, MouseEvent, useState } from 'react'; import { dateTime, getDefaultTimeRange, GrafanaTheme2, TimeRange, TimeZone } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; @@ -79,22 +78,21 @@ export const TimeRangeInput = ({ onChange({ from, to, raw: { from, to } }); }; - const overlayRef = createRef(); - const buttonRef = createRef(); + const { refs, floatingStyles, context } = useFloating({ + open: isOpen, + onOpenChange: setIsOpen, + placement: 'bottom-start', + strategy: 'fixed', + }); - const { dialogProps } = useDialog({}, overlayRef); - - const { overlayProps } = useOverlay( - { - onClose, - isDismissable: true, - isOpen, - shouldCloseOnInteractOutside: (element) => { - return !buttonRef.current?.contains(element); - }, + const dismiss = useDismiss(context, { + bubbles: { + outsidePress: false, }, - overlayRef - ); + }); + + const { getReferenceProps, getFloatingProps } = useInteractions([dismiss]); + return (
{isOpen && ( -
+
{ marginLeft: 0, position: 'absolute', top: '116%', - zIndex: theme.zIndex.dropdown, + zIndex: theme.zIndex.modal, }), pickerInput: cx( inputStyles.input, diff --git a/packages/grafana-ui/src/components/PanelChrome/PanelChrome.tsx b/packages/grafana-ui/src/components/PanelChrome/PanelChrome.tsx index 3346c110c51..7b534d88b09 100644 --- a/packages/grafana-ui/src/components/PanelChrome/PanelChrome.tsx +++ b/packages/grafana-ui/src/components/PanelChrome/PanelChrome.tsx @@ -151,7 +151,7 @@ export function PanelChrome({ const panelContentId = useId(); const panelTitleId = useId().replace(/:/g, '_'); const { isSelected, onSelect, isSelectable } = useElementSelection(selectionId); - const pointerDownEvt = useRef(); + const pointerDownPos = useRef<{ screenX: number; screenY: number }>({ screenX: 0, screenY: 0 }); const hasHeader = !hoverHeader; @@ -203,11 +203,11 @@ export function PanelChrome({ evt.stopPropagation(); const distance = Math.hypot( - pointerDownEvt.current?.screenX ?? 0 - evt.screenX, - pointerDownEvt.current?.screenY ?? 0 - evt.screenY + pointerDownPos.current.screenX - evt.screenX, + pointerDownPos.current.screenY - evt.screenY ); - pointerDownEvt.current = undefined; + pointerDownPos.current = { screenX: 0, screenY: 0 }; // If we are dragging some distance or clicking on elements that should cancel dragging (panel menu, etc) if ( @@ -222,7 +222,10 @@ export function PanelChrome({ const onPointerDown = (evt: React.PointerEvent) => { evt.stopPropagation(); - pointerDownEvt.current = evt; + + pointerDownPos.current = { screenX: evt.screenX, screenY: evt.screenY }; + + onDragStart?.(evt); }; const headerContent = ( @@ -350,11 +353,6 @@ export function PanelChrome({ className={cx(styles.headerContainer, dragClass)} style={headerStyles} data-testid="header-container" - onPointerMove={() => { - if (pointerDownEvt.current) { - onDragStart?.(pointerDownEvt.current); - } - }} onPointerDown={onPointerDown} onMouseEnter={isSelectable ? onHeaderEnter : undefined} onMouseLeave={isSelectable ? onHeaderLeave : undefined} diff --git a/pkg/aggregator/go.mod b/pkg/aggregator/go.mod index e6ca98240ed..eb227a8af37 100644 --- a/pkg/aggregator/go.mod +++ b/pkg/aggregator/go.mod @@ -22,7 +22,7 @@ require ( require ( cel.dev/expr v0.19.1 // indirect - github.com/BurntSushi/toml v1.4.0 // indirect + github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c // indirect github.com/NYTimes/gziphandler v1.1.1 // indirect github.com/antlr4-go/antlr/v4 v4.13.1 // indirect github.com/apache/arrow-go/v18 v18.0.1-0.20241212180703-82be143d7c30 // indirect @@ -39,7 +39,7 @@ require ( github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/elazarl/goproxy v1.7.2 // indirect github.com/evanphx/json-patch v5.6.0+incompatible // indirect - github.com/fatih/color v1.17.0 // indirect + github.com/fatih/color v1.18.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsnotify v1.8.0 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect diff --git a/pkg/aggregator/go.sum b/pkg/aggregator/go.sum index 0198e0efd67..e947b008d44 100644 --- a/pkg/aggregator/go.sum +++ b/pkg/aggregator/go.sum @@ -2,8 +2,9 @@ cel.dev/expr v0.19.1 h1:NciYrtDRIR0lNCnH1LFJegdjspNx9fI59O7TWcua/W4= cel.dev/expr v0.19.1/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0= github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= +github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c h1:pxW6RcqyfI9/kWtOwnv/G+AzdKuy2ZrqINhenH4HyNs= +github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I= github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA= @@ -60,8 +61,8 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7 github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U= github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= -github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4= -github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI= +github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= +github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= diff --git a/pkg/api/annotations.go b/pkg/api/annotations.go index 4720e25e91f..5873504d7c9 100644 --- a/pkg/api/annotations.go +++ b/pkg/api/annotations.go @@ -16,7 +16,6 @@ import ( "github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/folder" - "github.com/grafana/grafana/pkg/services/guardian" "github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/util" "github.com/grafana/grafana/pkg/web" @@ -278,7 +277,7 @@ func (hs *HTTPServer) UpdateAnnotation(c *contextmodel.ReqContext) response.Resp } if !hs.Features.IsEnabled(c.Req.Context(), featuremgmt.FlagAnnotationPermissionUpdate) { - if canSave, err := hs.canSaveAnnotation(c, annotation); err != nil || !canSave { + if canSave, err := hs.canSaveAnnotation(c, hs.AccessControl, annotation); err != nil || !canSave { return dashboardGuardianResponse(err) } } @@ -336,7 +335,7 @@ func (hs *HTTPServer) PatchAnnotation(c *contextmodel.ReqContext) response.Respo } if !hs.Features.IsEnabled(c.Req.Context(), featuremgmt.FlagAnnotationPermissionUpdate) { - if canSave, err := hs.canSaveAnnotation(c, annotation); err != nil || !canSave { + if canSave, err := hs.canSaveAnnotation(c, hs.AccessControl, annotation); err != nil || !canSave { return dashboardGuardianResponse(err) } } @@ -502,7 +501,7 @@ func (hs *HTTPServer) DeleteAnnotationByID(c *contextmodel.ReqContext) response. return resp } - if canSave, err := hs.canSaveAnnotation(c, annotation); err != nil || !canSave { + if canSave, err := hs.canSaveAnnotation(c, hs.AccessControl, annotation); err != nil || !canSave { return dashboardGuardianResponse(err) } } @@ -518,25 +517,17 @@ func (hs *HTTPServer) DeleteAnnotationByID(c *contextmodel.ReqContext) response. return response.Success("Annotation deleted") } -func (hs *HTTPServer) canSaveAnnotation(c *contextmodel.ReqContext, annotation *annotations.ItemDTO) (bool, error) { +func (hs *HTTPServer) canSaveAnnotation(c *contextmodel.ReqContext, ac accesscontrol.AccessControl, annotation *annotations.ItemDTO) (bool, error) { if annotation.GetType() == annotations.Dashboard { - return canEditDashboard(c, annotation.DashboardID) + return canEditDashboard(c, ac, annotation.DashboardID) } else { return true, nil } } -func canEditDashboard(c *contextmodel.ReqContext, dashboardID int64) (bool, error) { - guard, err := guardian.New(c.Req.Context(), dashboardID, c.SignedInUser.GetOrgID(), c.SignedInUser) - if err != nil { - return false, err - } - - if canEdit, err := guard.CanEdit(); err != nil || !canEdit { - return false, err - } - - return true, nil +func canEditDashboard(c *contextmodel.ReqContext, ac accesscontrol.AccessControl, dashboardID int64) (bool, error) { + evaluator := accesscontrol.EvalPermission(dashboards.ActionDashboardsWrite, dashboards.ScopeDashboardsProvider.GetResourceScope(strconv.FormatInt(dashboardID, 10))) + return ac.Evaluate(c.Req.Context(), c.SignedInUser, evaluator) } func findAnnotationByID(ctx context.Context, repo annotations.Repository, annotationID int64, user *user.SignedInUser) (*annotations.ItemDTO, response.Response) { @@ -680,7 +671,7 @@ func (hs *HTTPServer) canCreateAnnotation(c *contextmodel.ReqContext, dashboardI return canSave, err } - return canEditDashboard(c, dashboardId) + return canEditDashboard(c, hs.AccessControl, dashboardId) } else { // organization annotations evaluator := accesscontrol.EvalPermission(accesscontrol.ActionAnnotationsCreate, accesscontrol.ScopeAnnotationsTypeOrganization) return hs.AccessControl.Evaluate(c.Req.Context(), c.SignedInUser, evaluator) @@ -708,7 +699,7 @@ func (hs *HTTPServer) canMassDeleteAnnotations(c *contextmodel.ReqContext, dashb return false, err } - canSave, err = canEditDashboard(c, dashboardID) + canSave, err = canEditDashboard(c, hs.AccessControl, dashboardID) if err != nil || !canSave { return false, err } diff --git a/pkg/api/annotations_test.go b/pkg/api/annotations_test.go index 83e11eb91cf..fa4f119f13b 100644 --- a/pkg/api/annotations_test.go +++ b/pkg/api/annotations_test.go @@ -19,7 +19,6 @@ import ( "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/folder" "github.com/grafana/grafana/pkg/services/folder/foldertest" - "github.com/grafana/grafana/pkg/services/guardian" "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/web/webtest" ) @@ -110,7 +109,10 @@ func TestAPI_Annotations(t *testing.T) { path: "/api/annotations/2", method: http.MethodPut, expectedCode: http.StatusOK, - permissions: []accesscontrol.Permission{{Action: accesscontrol.ActionAnnotationsWrite, Scope: accesscontrol.ScopeAnnotationsTypeDashboard}}, + permissions: []accesscontrol.Permission{ + {Action: accesscontrol.ActionAnnotationsWrite, Scope: accesscontrol.ScopeAnnotationsTypeDashboard}, + {Action: dashboards.ActionDashboardsWrite, Scope: dashboards.ScopeDashboardsAll}, + }, }, { desc: "should not be able to update dashboard annotation without correct permission", @@ -162,7 +164,10 @@ func TestAPI_Annotations(t *testing.T) { path: "/api/annotations/2", method: http.MethodPatch, expectedCode: http.StatusOK, - permissions: []accesscontrol.Permission{{Action: accesscontrol.ActionAnnotationsWrite, Scope: accesscontrol.ScopeAnnotationsTypeDashboard}}, + permissions: []accesscontrol.Permission{ + {Action: accesscontrol.ActionAnnotationsWrite, Scope: accesscontrol.ScopeAnnotationsTypeDashboard}, + {Action: dashboards.ActionDashboardsWrite, Scope: dashboards.ScopeDashboardsAll}, + }, }, { desc: "should not be able to patch dashboard annotation without correct permission", @@ -215,7 +220,10 @@ func TestAPI_Annotations(t *testing.T) { method: http.MethodPost, body: "{\"dashboardId\": 2,\"text\": \"test\"}", expectedCode: http.StatusOK, - permissions: []accesscontrol.Permission{{Action: accesscontrol.ActionAnnotationsCreate, Scope: accesscontrol.ScopeAnnotationsTypeDashboard}}, + permissions: []accesscontrol.Permission{ + {Action: accesscontrol.ActionAnnotationsCreate, Scope: accesscontrol.ScopeAnnotationsTypeDashboard}, + {Action: dashboards.ActionDashboardsWrite, Scope: dashboards.ScopeDashboardsAll}, + }, }, { desc: "should not be able to create dashboard annotation without correct permission", @@ -273,7 +281,10 @@ func TestAPI_Annotations(t *testing.T) { path: "/api/annotations/2", method: http.MethodDelete, expectedCode: http.StatusOK, - permissions: []accesscontrol.Permission{{Action: accesscontrol.ActionAnnotationsDelete, Scope: accesscontrol.ScopeAnnotationsTypeDashboard}}, + permissions: []accesscontrol.Permission{ + {Action: accesscontrol.ActionAnnotationsDelete, Scope: accesscontrol.ScopeAnnotationsTypeDashboard}, + {Action: dashboards.ActionDashboardsWrite, Scope: dashboards.ScopeDashboardsAll}, + }, }, { desc: "should not be able to delete dashboard annotation without correct permission", @@ -341,7 +352,10 @@ func TestAPI_Annotations(t *testing.T) { body: "{\"dashboardId\": 2, \"panelId\": 1}", method: http.MethodPost, expectedCode: http.StatusOK, - permissions: []accesscontrol.Permission{{Action: accesscontrol.ActionAnnotationsDelete, Scope: accesscontrol.ScopeAnnotationsTypeDashboard}}, + permissions: []accesscontrol.Permission{ + {Action: accesscontrol.ActionAnnotationsDelete, Scope: accesscontrol.ScopeAnnotationsTypeDashboard}, + {Action: dashboards.ActionDashboardsWrite, Scope: dashboards.ScopeDashboardsAll}, + }, }, { desc: "should not be able to mass delete dashboard annotations without correct permission", @@ -382,10 +396,6 @@ func TestAPI_Annotations(t *testing.T) { for _, tt := range tests { t.Run(tt.desc, func(t *testing.T) { - // Don't need access to dashboards if annotationPermissionUpdate is enabled - if len(tt.featureFlags) == 0 { - setUpRBACGuardian(t) - } server := SetupAPITestServer(t, func(hs *HTTPServer) { hs.Cfg = setting.NewCfg() repo := annotationstest.NewFakeAnnotationsRepo() @@ -518,12 +528,3 @@ func TestService_AnnotationTypeScopeResolver(t *testing.T) { }) } } - -func setUpRBACGuardian(t *testing.T) { - origNewGuardian := guardian.New - t.Cleanup(func() { - guardian.New = origNewGuardian - }) - - guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{CanEditValue: true, CanViewValue: true}) -} diff --git a/pkg/api/api.go b/pkg/api/api.go index 77efde07038..0026529cbd8 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -117,6 +117,7 @@ func (hs *HTTPServer) registerRoutes() { r.Get("/admin/orgs/edit/:id", authorizeInOrg(ac.UseGlobalOrg, ac.OrgsAccessEvaluator), hs.Index) r.Get("/admin/stats", authorize(ac.EvalPermission(ac.ActionServerStatsRead)), hs.Index) r.Get("/admin/provisioning", reqOrgAdmin, hs.Index) + r.Get("/admin/provisioning/*", reqOrgAdmin, hs.Index) if hs.Features.IsEnabledGlobally(featuremgmt.FlagOnPremToCloudMigrations) { r.Get("/admin/migrate-to-cloud", authorize(cloudmigration.MigrationAssistantAccess), hs.Index) @@ -462,22 +463,24 @@ func (hs *HTTPServer) registerRoutes() { // Dashboard apiRoute.Group("/dashboards", func(dashboardRoute routing.RouteRegister) { - dashboardRoute.Get("/uid/:uid", authorize(ac.EvalPermission(dashboards.ActionDashboardsRead)), routing.Wrap(hs.GetDashboard)) + dashUIDScope := dashboards.ScopeDashboardsProvider.GetResourceScopeUID(ac.Parameter(":uid")) + + dashboardRoute.Get("/uid/:uid", authorize(ac.EvalPermission(dashboards.ActionDashboardsRead, dashUIDScope)), routing.Wrap(hs.GetDashboard)) if hs.Features.IsEnabledGlobally(featuremgmt.FlagDashboardRestore) { - dashboardRoute.Delete("/uid/:uid", authorize(ac.EvalPermission(dashboards.ActionDashboardsDelete)), routing.Wrap(hs.SoftDeleteDashboard)) + dashboardRoute.Delete("/uid/:uid", authorize(ac.EvalPermission(dashboards.ActionDashboardsDelete, dashUIDScope)), routing.Wrap(hs.SoftDeleteDashboard)) } else { - dashboardRoute.Delete("/uid/:uid", authorize(ac.EvalPermission(dashboards.ActionDashboardsDelete)), routing.Wrap(hs.DeleteDashboardByUID)) + dashboardRoute.Delete("/uid/:uid", authorize(ac.EvalPermission(dashboards.ActionDashboardsDelete, dashUIDScope)), routing.Wrap(hs.DeleteDashboardByUID)) } dashboardRoute.Group("/uid/:uid", func(dashUidRoute routing.RouteRegister) { - dashUidRoute.Get("/versions", authorize(ac.EvalPermission(dashboards.ActionDashboardsWrite)), routing.Wrap(hs.GetDashboardVersions)) - dashUidRoute.Post("/restore", authorize(ac.EvalPermission(dashboards.ActionDashboardsWrite)), routing.Wrap(hs.RestoreDashboardVersion)) - dashUidRoute.Get("/versions/:id", authorize(ac.EvalPermission(dashboards.ActionDashboardsWrite)), routing.Wrap(hs.GetDashboardVersion)) + dashUidRoute.Get("/versions", authorize(ac.EvalPermission(dashboards.ActionDashboardsWrite, dashUIDScope)), routing.Wrap(hs.GetDashboardVersions)) + dashUidRoute.Post("/restore", authorize(ac.EvalPermission(dashboards.ActionDashboardsWrite, dashUIDScope)), routing.Wrap(hs.RestoreDashboardVersion)) + dashUidRoute.Get("/versions/:id", authorize(ac.EvalPermission(dashboards.ActionDashboardsWrite, dashUIDScope)), routing.Wrap(hs.GetDashboardVersion)) if hs.Features.IsEnabledGlobally(featuremgmt.FlagDashboardRestore) { - dashUidRoute.Patch("/trash", reqOrgAdmin, routing.Wrap(hs.RestoreDeletedDashboard)) - dashUidRoute.Delete("/trash", reqOrgAdmin, routing.Wrap(hs.HardDeleteDashboardByUID)) + dashUidRoute.Patch("/trash", reqOrgAdmin, authorize(ac.EvalPermission(dashboards.ActionDashboardsWrite, dashUIDScope)), routing.Wrap(hs.RestoreDeletedDashboard)) + dashUidRoute.Delete("/trash", reqOrgAdmin, authorize(ac.EvalPermission(dashboards.ActionDashboardsDelete, dashUIDScope)), routing.Wrap(hs.HardDeleteDashboardByUID)) } dashUidRoute.Group("/permissions", func(dashboardPermissionRoute routing.RouteRegister) { @@ -497,9 +500,10 @@ func (hs *HTTPServer) registerRoutes() { // Deprecated: use /uid/:uid API instead. dashboardRoute.Group("/id/:dashboardId", func(dashIdRoute routing.RouteRegister) { - dashIdRoute.Get("/versions", authorize(ac.EvalPermission(dashboards.ActionDashboardsWrite)), routing.Wrap(hs.GetDashboardVersions)) - dashIdRoute.Get("/versions/:id", authorize(ac.EvalPermission(dashboards.ActionDashboardsWrite)), routing.Wrap(hs.GetDashboardVersion)) - dashIdRoute.Post("/restore", authorize(ac.EvalPermission(dashboards.ActionDashboardsWrite)), routing.Wrap(hs.RestoreDashboardVersion)) + dashIDScope := dashboards.ScopeDashboardsProvider.GetResourceScope(ac.Parameter(":dashboardId")) + dashIdRoute.Get("/versions", authorize(ac.EvalPermission(dashboards.ActionDashboardsWrite, dashIDScope)), routing.Wrap(hs.GetDashboardVersions)) + dashIdRoute.Get("/versions/:id", authorize(ac.EvalPermission(dashboards.ActionDashboardsWrite, dashIDScope)), routing.Wrap(hs.GetDashboardVersion)) + dashIdRoute.Post("/restore", authorize(ac.EvalPermission(dashboards.ActionDashboardsWrite, dashIDScope)), routing.Wrap(hs.RestoreDashboardVersion)) dashIdRoute.Group("/permissions", func(dashboardPermissionRoute routing.RouteRegister) { dashboardPermissionRoute.Get("/", authorize(ac.EvalPermission(dashboards.ActionDashboardsPermissionsRead)), routing.Wrap(hs.GetDashboardPermissionList)) diff --git a/pkg/api/dashboard.go b/pkg/api/dashboard.go index c1c61753676..17d37830645 100644 --- a/pkg/api/dashboard.go +++ b/pkg/api/dashboard.go @@ -27,7 +27,6 @@ import ( "github.com/grafana/grafana/pkg/services/dashboardversion/dashverimpl" "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/folder" - "github.com/grafana/grafana/pkg/services/guardian" "github.com/grafana/grafana/pkg/services/org" pref "github.com/grafana/grafana/pkg/services/preference" publicdashboardModels "github.com/grafana/grafana/pkg/services/publicdashboards/models" @@ -141,18 +140,19 @@ func (hs *HTTPServer) GetDashboard(c *contextmodel.ReqContext) response.Response dash.Data.Set("id", dash.ID) } } - guardian, err := guardian.NewByDashboard(ctx, dash, c.SignedInUser.GetOrgID(), c.SignedInUser) - if err != nil { - return response.Err(err) - } - if canView, err := guardian.CanView(); err != nil || !canView { - return dashboardGuardianResponse(err) + dashScope := dashboards.ScopeDashboardsProvider.GetResourceScopeUID(dash.UID) + writeEvaluator := accesscontrol.EvalPermission(dashboards.ActionDashboardsWrite, dashScope) + canSave, _ := hs.AccessControl.Evaluate(ctx, c.SignedInUser, writeEvaluator) + canEdit := canSave + //nolint:staticcheck // ViewersCanEdit is deprecated but still used for backward compatibility + if hs.Cfg.ViewersCanEdit { + canEdit = true } - canEdit, _ := guardian.CanEdit() - canSave, _ := guardian.CanSave() - canAdmin, _ := guardian.CanAdmin() - canDelete, _ := guardian.CanDelete() + deleteEvaluator := accesscontrol.EvalPermission(dashboards.ActionDashboardsDelete, dashScope) + canDelete, _ := hs.AccessControl.Evaluate(ctx, c.SignedInUser, deleteEvaluator) + adminEvaluator := accesscontrol.EvalPermission(dashboards.ActionDashboardsPermissionsWrite, dashScope) + canAdmin, _ := hs.AccessControl.Evaluate(ctx, c.SignedInUser, adminEvaluator) isStarred, err := hs.isDashboardStarredByUser(c, dash.ID) if err != nil { @@ -369,15 +369,6 @@ func (hs *HTTPServer) RestoreDeletedDashboard(c *contextmodel.ReqContext) respon return response.Error(http.StatusNotFound, "Dashboard not found", err) } - guardian, err := guardian.NewByDashboard(c.Req.Context(), dash, c.SignedInUser.GetOrgID(), c.SignedInUser) - if err != nil { - return response.Err(err) - } - - if canRestore, err := guardian.CanSave(); err != nil || !canRestore { - return dashboardGuardianResponse(err) - } - err = hs.DashboardService.RestoreDashboard(c.Req.Context(), dash, c.SignedInUser, cmd.FolderUID) if err != nil { var dashboardErr dashboards.DashboardErr @@ -417,16 +408,7 @@ func (hs *HTTPServer) SoftDeleteDashboard(c *contextmodel.ReqContext) response.R return rsp } - guardian, err := guardian.NewByDashboard(c.Req.Context(), dash, c.SignedInUser.GetOrgID(), c.SignedInUser) - if err != nil { - return response.Err(err) - } - - if canDelete, err := guardian.CanDelete(); err != nil || !canDelete { - return dashboardGuardianResponse(err) - } - - err = hs.DashboardService.SoftDeleteDashboard(c.Req.Context(), c.SignedInUser.GetOrgID(), uid) + err := hs.DashboardService.SoftDeleteDashboard(c.Req.Context(), c.SignedInUser.GetOrgID(), uid) if err != nil { var dashboardErr dashboards.DashboardErr if ok := errors.As(err, &dashboardErr); ok { @@ -498,21 +480,12 @@ func (hs *HTTPServer) deleteDashboard(c *contextmodel.ReqContext) response.Respo } } - guardian, err := guardian.NewByDashboard(c.Req.Context(), dash, c.SignedInUser.GetOrgID(), c.SignedInUser) - if err != nil { - return response.Err(err) - } - - if canDelete, err := guardian.CanDelete(); err != nil || !canDelete { - return dashboardGuardianResponse(err) - } - if dash.IsFolder { return response.Error(http.StatusBadRequest, "Use folders endpoint for deleting folders.", nil) } // disconnect all library elements for this dashboard - err = hs.LibraryElementService.DisconnectElementsFromDashboard(c.Req.Context(), dash.ID) + err := hs.LibraryElementService.DisconnectElementsFromDashboard(c.Req.Context(), dash.ID) if err != nil { hs.log.Error( "Failed to disconnect library elements", @@ -840,14 +813,6 @@ func (hs *HTTPServer) GetDashboardVersions(c *contextmodel.ReqContext) response. return rsp } - guardian, err := guardian.NewByDashboard(c.Req.Context(), dash, c.SignedInUser.GetOrgID(), c.SignedInUser) - if err != nil { - return response.Err(err) - } - if canSave, err := guardian.CanSave(); err != nil || !canSave { - return dashboardGuardianResponse(err) - } - query := dashver.ListDashboardVersionsQuery{ OrgID: c.SignedInUser.GetOrgID(), DashboardID: dash.ID, @@ -959,15 +924,6 @@ func (hs *HTTPServer) GetDashboardVersion(c *contextmodel.ReqContext) response.R return rsp } - guardian, err := guardian.NewByDashboard(c.Req.Context(), dash, c.SignedInUser.GetOrgID(), c.SignedInUser) - if err != nil { - return response.Err(err) - } - - if canSave, err := guardian.CanSave(); err != nil || !canSave { - return dashboardGuardianResponse(err) - } - version, err := strconv.ParseInt(web.Params(c.Req)[":id"], 10, 64) if err != nil { return response.Err(err) @@ -1027,22 +983,15 @@ func (hs *HTTPServer) CalculateDashboardDiff(c *contextmodel.ReqContext) respons if err := web.Bind(c.Req, &apiOptions); err != nil { return response.Error(http.StatusBadRequest, "bad request data", err) } - guardianBase, err := guardian.New(c.Req.Context(), apiOptions.Base.DashboardId, c.SignedInUser.GetOrgID(), c.SignedInUser) - if err != nil { - return response.Err(err) - } - if canSave, err := guardianBase.CanSave(); err != nil || !canSave { + evaluator := accesscontrol.EvalPermission(dashboards.ActionDashboardsWrite, dashboards.ScopeDashboardsProvider.GetResourceScope(strconv.FormatInt(apiOptions.Base.DashboardId, 10))) + if canWrite, err := hs.AccessControl.Evaluate(c.Req.Context(), c.SignedInUser, evaluator); err != nil || !canWrite { return dashboardGuardianResponse(err) } if apiOptions.Base.DashboardId != apiOptions.New.DashboardId { - guardianNew, err := guardian.New(c.Req.Context(), apiOptions.New.DashboardId, c.SignedInUser.GetOrgID(), c.SignedInUser) - if err != nil { - return response.Err(err) - } - - if canSave, err := guardianNew.CanSave(); err != nil || !canSave { + evaluator = accesscontrol.EvalPermission(dashboards.ActionDashboardsWrite, dashboards.ScopeDashboardsProvider.GetResourceScope(strconv.FormatInt(apiOptions.New.DashboardId, 10))) + if canWrite, err := hs.AccessControl.Evaluate(c.Req.Context(), c.SignedInUser, evaluator); err != nil || !canWrite { return dashboardGuardianResponse(err) } } @@ -1159,15 +1108,6 @@ func (hs *HTTPServer) RestoreDashboardVersion(c *contextmodel.ReqContext) respon return rsp } - guardian, err := guardian.NewByDashboard(c.Req.Context(), dash, c.SignedInUser.GetOrgID(), c.SignedInUser) - if err != nil { - return response.Err(err) - } - - if canSave, err := guardian.CanSave(); err != nil || !canSave { - return dashboardGuardianResponse(err) - } - versionQuery := dashver.GetDashboardVersionQuery{DashboardID: dashID, DashboardUID: dash.UID, Version: apiCmd.Version, OrgID: c.SignedInUser.GetOrgID()} version, err := hs.dashboardVersionService.Get(c.Req.Context(), &versionQuery) if err != nil { diff --git a/pkg/api/dashboard_snapshot.go b/pkg/api/dashboard_snapshot.go index b583c550da7..4ebe1e8708f 100644 --- a/pkg/api/dashboard_snapshot.go +++ b/pkg/api/dashboard_snapshot.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "net/http" + "strconv" "time" "github.com/grafana/grafana/pkg/api/dtos" @@ -17,7 +18,6 @@ import ( "github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/dashboardsnapshots" "github.com/grafana/grafana/pkg/services/featuremgmt" - "github.com/grafana/grafana/pkg/services/guardian" "github.com/grafana/grafana/pkg/util" "github.com/grafana/grafana/pkg/util/errhttp" "github.com/grafana/grafana/pkg/web" @@ -226,21 +226,15 @@ func (hs *HTTPServer) DeleteDashboardSnapshot(c *contextmodel.ReqContext) respon dashboardID := queryResult.Dashboard.Get("id").MustInt64() if dashboardID != 0 { - g, err := guardian.New(c.Req.Context(), dashboardID, c.SignedInUser.GetOrgID(), c.SignedInUser) - if err != nil { - if !errors.Is(err, dashboards.ErrDashboardNotFound) { - return response.Err(err) - } - } else { - canEdit, err := g.CanEdit() - // check for permissions only if the dashboard is found - if err != nil && !errors.Is(err, dashboards.ErrDashboardNotFound) { - return response.Error(http.StatusInternalServerError, "Error while checking permissions for snapshot", err) - } + evaluator := ac.EvalPermission(dashboards.ActionDashboardsWrite, dashboards.ScopeDashboardsProvider.GetResourceScope(strconv.FormatInt(dashboardID, 10))) + canEdit, err := hs.AccessControl.Evaluate(c.Req.Context(), c.SignedInUser, evaluator) + // check for permissions only if the dashboard is found + if err != nil && !errors.Is(err, dashboards.ErrDashboardNotFound) { + return response.Error(http.StatusInternalServerError, "Error while checking permissions for snapshot", err) + } - if !canEdit && queryResult.UserID != c.SignedInUser.UserID && !errors.Is(err, dashboards.ErrDashboardNotFound) { - return response.Error(http.StatusForbidden, "Access denied to this snapshot", nil) - } + if !canEdit && queryResult.UserID != c.SignedInUser.UserID && !errors.Is(err, dashboards.ErrDashboardNotFound) { + return response.Error(http.StatusForbidden, "Access denied to this snapshot", nil) } } diff --git a/pkg/api/dashboard_snapshot_test.go b/pkg/api/dashboard_snapshot_test.go index 24dcd3ef4ec..01d9e6ce4d0 100644 --- a/pkg/api/dashboard_snapshot_test.go +++ b/pkg/api/dashboard_snapshot_test.go @@ -15,13 +15,12 @@ import ( "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/infra/db/dbtest" - "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/accesscontrol/acimpl" + "github.com/grafana/grafana/pkg/services/accesscontrol/actest" "github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/dashboardsnapshots" "github.com/grafana/grafana/pkg/services/featuremgmt" - "github.com/grafana/grafana/pkg/services/guardian" "github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/setting" @@ -41,7 +40,7 @@ func TestHTTPServer_DeleteDashboardSnapshot(t *testing.T) { hs.DashboardService = svc hs.AccessControl = acimpl.ProvideAccessControl(featuremgmt.WithFeatures()) - guardian.InitAccessControlGuardian(hs.Cfg, hs.AccessControl, hs.DashboardService, hs.folderService, log.NewNopLogger()) + hs.AccessControl.RegisterScopeAttributeResolver(dashboards.NewDashboardIDScopeResolver(svc, nil)) }) } @@ -378,6 +377,7 @@ func buildHttpServer(d dashboardsnapshots.Service, snapshotEnabled bool) *HTTPSe Cfg: &setting.Cfg{ SnapshotEnabled: snapshotEnabled, }, + AccessControl: actest.FakeAccessControl{ExpectedEvaluate: true}, } return hs } diff --git a/pkg/api/dashboard_test.go b/pkg/api/dashboard_test.go index 3ed18a073e8..0dfdcd43934 100644 --- a/pkg/api/dashboard_test.go +++ b/pkg/api/dashboard_test.go @@ -1,6 +1,7 @@ package api import ( + "bytes" "context" "encoding/json" "fmt" @@ -22,8 +23,10 @@ import ( "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/infra/db" "github.com/grafana/grafana/pkg/infra/db/dbtest" + "github.com/grafana/grafana/pkg/infra/kvstore" "github.com/grafana/grafana/pkg/infra/localcache" "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/infra/serverlock" "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/infra/usagestats" "github.com/grafana/grafana/pkg/services/accesscontrol" @@ -134,7 +137,10 @@ func newTestLive(t *testing.T, store db.DB) *live.GrafanaLive { nil, &usagestats.UsageStatsMock{T: t}, nil, - features, acimpl.ProvideAccessControl(features), &dashboards.FakeDashboardService{}, annotationstest.NewFakeAnnotationsRepo(), nil) + features, acimpl.ProvideAccessControl(features), + &dashboards.FakeDashboardService{}, + annotationstest.NewFakeAnnotationsRepo(), + nil, nil) require.NoError(t, err) return gLive } @@ -327,12 +333,16 @@ func TestHTTPServer_GetDashboardVersions_AccessControl(t *testing.T) { hs.AccessControl = acimpl.ProvideAccessControl(featuremgmt.WithFeatures()) hs.starService = startest.NewStarServiceFake() - hs.dashboardVersionService = &dashvertest.FakeDashboardVersionService{ - ExpectedListDashboarVersions: []*dashver.DashboardVersionDTO{}, - ExpectedDashboardVersion: &dashver.DashboardVersionDTO{}, + expectedDashVersions := []*dashver.DashboardVersionDTO{ + {Data: simplejson.NewFromAny(map[string]any{"title": "Dash"})}, + {Data: simplejson.NewFromAny(map[string]any{"title": "Dash updated"})}, } - guardian.InitAccessControlGuardian(hs.Cfg, hs.AccessControl, hs.DashboardService, hs.folderService, log.NewNopLogger()) + hs.dashboardVersionService = &dashvertest.FakeDashboardVersionService{ + ExpectedListDashboarVersions: []*dashver.DashboardVersionDTO{}, + ExpectedDashboardVersions: expectedDashVersions, + ExpectedDashboardVersion: &dashver.DashboardVersionDTO{}, + } }) } @@ -344,6 +354,17 @@ func TestHTTPServer_GetDashboardVersions_AccessControl(t *testing.T) { return server.Send(webtest.RequestWithSignedInUser(server.NewGetRequest("/api/dashboards/uid/1/versions"), userWithPermissions(1, permissions))) } + calculateDiff := func(server *webtest.Server, permissions []accesscontrol.Permission) (*http.Response, error) { + cmd := &dtos.CalculateDiffOptions{ + Base: dtos.CalculateDiffTarget{DashboardId: 1, Version: 1}, + New: dtos.CalculateDiffTarget{DashboardId: 1, Version: 2}, + DiffType: "json", + } + jsonBytes, err := json.Marshal(cmd) + require.NoError(t, err) + return server.SendJSON(webtest.RequestWithSignedInUser(server.NewPostRequest("/api/dashboards/calculate-diff", bytes.NewReader(jsonBytes)), userWithPermissions(1, permissions))) + } + t.Run("Should not be able to list dashboard versions without correct permission", func(t *testing.T) { server := setup() @@ -363,7 +384,6 @@ func TestHTTPServer_GetDashboardVersions_AccessControl(t *testing.T) { server := setup() permissions := []accesscontrol.Permission{ - {Action: dashboards.ActionDashboardsRead, Scope: "dashboards:uid:1"}, {Action: dashboards.ActionDashboardsWrite, Scope: "dashboards:uid:1"}, } @@ -378,6 +398,28 @@ func TestHTTPServer_GetDashboardVersions_AccessControl(t *testing.T) { require.NoError(t, res.Body.Close()) }) + + t.Run("Should be able to diff dashboards with correct permissions", func(t *testing.T) { + server := setup() + + permissions := []accesscontrol.Permission{ + {Action: dashboards.ActionDashboardsWrite, Scope: dashboards.ScopeDashboardsAll}, + } + + res, err := calculateDiff(server, permissions) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, res.StatusCode) + require.NoError(t, res.Body.Close()) + }) + + t.Run("Should not be able to diff dashboards without permissions", func(t *testing.T) { + server := setup() + + res, err := calculateDiff(server, []accesscontrol.Permission{}) + require.NoError(t, err) + assert.Equal(t, http.StatusForbidden, res.StatusCode) + require.NoError(t, res.Body.Close()) + }) } func TestDashboardAPIEndpoint(t *testing.T) { @@ -527,39 +569,6 @@ func TestDashboardAPIEndpoint(t *testing.T) { }), }, } - sqlmock := dbtest.NewFakeDB() - cmd := dtos.CalculateDiffOptions{ - Base: dtos.CalculateDiffTarget{ - DashboardId: 1, - Version: 1, - }, - New: dtos.CalculateDiffTarget{ - DashboardId: 2, - Version: 2, - }, - DiffType: "basic", - } - - t.Run("when user does not have permission", func(t *testing.T) { - role := org.RoleViewer - postDiffScenario(t, "When calling POST on", "/api/dashboards/calculate-diff", "/api/dashboards/calculate-diff", cmd, role, func(sc *scenarioContext) { - guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{CanSaveValue: false}) - - callPostDashboard(sc) - assert.Equal(t, http.StatusForbidden, sc.resp.Code) - }, sqlmock, fakeDashboardVersionService) - }) - - t.Run("when user does have permission", func(t *testing.T) { - role := org.RoleAdmin - postDiffScenario(t, "When calling POST on", "/api/dashboards/calculate-diff", "/api/dashboards/calculate-diff", cmd, role, func(sc *scenarioContext) { - guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{CanSaveValue: true}) - // This test shouldn't hit GetDashboardACLInfoList, so no setup needed - sc.dashboardVersionService = fakeDashboardVersionService - callPostDashboard(sc) - assert.Equal(t, http.StatusOK, sc.resp.Code) - }, sqlmock, fakeDashboardVersionService) - }) }) t.Run("Given dashboard in folder being restored should restore to folder", func(t *testing.T) { @@ -588,11 +597,6 @@ func TestDashboardAPIEndpoint(t *testing.T) { }, } mockSQLStore := dbtest.NewFakeDB() - origNewGuardian := guardian.New - guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{CanSaveValue: true}) - t.Cleanup(func() { - guardian.New = origNewGuardian - }) restoreDashboardVersionScenario(t, "When calling POST on", "/api/dashboards/id/1/restore", "/api/dashboards/id/:dashboardId/restore", dashboardService, fakeDashboardVersionService, cmd, func(sc *scenarioContext) { @@ -648,7 +652,6 @@ func TestDashboardAPIEndpoint(t *testing.T) { require.NoError(t, err) qResult := &dashboards.Dashboard{ID: 1, Data: dataValue} dashboardService.On("GetDashboard", mock.Anything, mock.AnythingOfType("*dashboards.GetDashboardQuery")).Return(qResult, nil) - guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{CanViewValue: true}) loggedInUserScenarioWithRole(t, "When calling GET on", "GET", "/api/dashboards/uid/dash", "/api/dashboards/uid/:uid", org.RoleEditor, func(sc *scenarioContext) { fakeProvisioningService := provisioning.NewProvisioningServiceMock(context.Background()) @@ -678,7 +681,7 @@ func TestDashboardAPIEndpoint(t *testing.T) { LibraryElementService: &libraryelementsfake.LibraryElementService{}, dashboardProvisioningService: mockDashboardProvisioningService{}, SQLStore: mockSQLStore, - AccessControl: accesscontrolmock.New(), + AccessControl: actest.FakeAccessControl{ExpectedEvaluate: true}, DashboardService: dashboardService, Features: featuremgmt.WithFeatures(), starService: startest.NewStarServiceFake(), @@ -710,7 +713,6 @@ func TestDashboardAPIEndpoint(t *testing.T) { Data: dataValue, } dashboardService.On("GetDashboard", mock.Anything, mock.AnythingOfType("*dashboards.GetDashboardQuery")).Return(qResult, nil) - guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{CanViewValue: true}) loggedInUserScenarioWithRole(t, "When calling GET on", "GET", "/api/dashboards/uid/dash", "/api/dashboards/uid/:uid", org.RoleEditor, func(sc *scenarioContext) { hs := &HTTPServer{ @@ -718,7 +720,7 @@ func TestDashboardAPIEndpoint(t *testing.T) { LibraryPanelService: &mockLibraryPanelService{}, LibraryElementService: &libraryelementsfake.LibraryElementService{}, SQLStore: mockSQLStore, - AccessControl: accesscontrolmock.New(), + AccessControl: actest.FakeAccessControl{ExpectedEvaluate: true}, DashboardService: dashboardService, Features: featuremgmt.WithFeatures(), starService: startest.NewStarServiceFake(), @@ -753,7 +755,7 @@ func TestDashboardVersionsAPIEndpoint(t *testing.T) { Cfg: cfg, pluginStore: &pluginstore.FakePluginStore{}, SQLStore: mockSQLStore, - AccessControl: accesscontrolmock.New(), + AccessControl: actest.FakeAccessControl{ExpectedEvaluate: true}, Features: featuremgmt.WithFeatures(), DashboardService: dashboardService, dashboardVersionService: fakeDashboardVersionService, @@ -765,13 +767,8 @@ func TestDashboardVersionsAPIEndpoint(t *testing.T) { } } - setUp := func() { - guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{CanSaveValue: true}) - } - loggedInUserScenarioWithRole(t, "When user exists and calling GET on", "GET", "/api/dashboards/id/2/versions", "/api/dashboards/id/:dashboardId/versions", org.RoleEditor, func(sc *scenarioContext) { - setUp() fakeDashboardVersionService.ExpectedListDashboarVersions = []*dashver.DashboardVersionDTO{ { Version: 1, @@ -797,7 +794,6 @@ func TestDashboardVersionsAPIEndpoint(t *testing.T) { loggedInUserScenarioWithRole(t, "When user does not exist and calling GET on", "GET", "/api/dashboards/id/2/versions", "/api/dashboards/id/:dashboardId/versions", org.RoleEditor, func(sc *scenarioContext) { - setUp() fakeDashboardVersionService.ExpectedListDashboarVersions = []*dashver.DashboardVersionDTO{ { Version: 1, @@ -823,7 +819,6 @@ func TestDashboardVersionsAPIEndpoint(t *testing.T) { loggedInUserScenarioWithRole(t, "When failing to get user and calling GET on", "GET", "/api/dashboards/id/2/versions", "/api/dashboards/id/:dashboardId/versions", org.RoleEditor, func(sc *scenarioContext) { - setUp() fakeDashboardVersionService.ExpectedListDashboarVersions = []*dashver.DashboardVersionDTO{ { Version: 1, @@ -882,6 +877,7 @@ func getDashboardShouldReturn200WithConfig(t *testing.T, sc *scenarioContext, pr cfg, dashboardStore, folderStore, features, folderPermissions, ac, folderSvc, fStore, nil, client.MockTestRestConfig{}, nil, quotaService, nil, nil, nil, dualwrite.ProvideTestService(), sort.ProvideService(), + serverlock.ProvideService(db, tracing.InitializeTracerForTest()), kvstore.NewFakeKVStore(), ) require.NoError(t, err) dashboardService.(dashboards.PermissionsRegistrationService).RegisterDashboardPermissions(dashboardPermissions) @@ -891,6 +887,7 @@ func getDashboardShouldReturn200WithConfig(t *testing.T, sc *scenarioContext, pr cfg, dashboardStore, folderStore, features, folderPermissions, ac, folderSvc, fStore, nil, client.MockTestRestConfig{}, nil, quotaService, nil, nil, nil, dualwrite.ProvideTestService(), sort.ProvideService(), + serverlock.ProvideService(db, tracing.InitializeTracerForTest()), kvstore.NewFakeKVStore(), ) require.NoError(t, err) @@ -978,47 +975,6 @@ func postDashboardScenario(t *testing.T, desc string, url string, routePattern s }) } -func postDiffScenario(t *testing.T, desc string, url string, routePattern string, cmd dtos.CalculateDiffOptions, - role org.RoleType, fn scenarioFunc, sqlmock db.DB, fakeDashboardVersionService *dashvertest.FakeDashboardVersionService, -) { - t.Run(fmt.Sprintf("%s %s", desc, url), func(t *testing.T) { - cfg := setting.NewCfg() - - dashSvc := dashboards.NewFakeDashboardService(t) - hs := HTTPServer{ - Cfg: cfg, - ProvisioningService: provisioning.NewProvisioningServiceMock(context.Background()), - Live: newTestLive(t, db.InitTestDB(t)), - QuotaService: quotatest.New(false, nil), - LibraryPanelService: &mockLibraryPanelService{}, - LibraryElementService: &libraryelementsfake.LibraryElementService{}, - SQLStore: sqlmock, - dashboardVersionService: fakeDashboardVersionService, - Features: featuremgmt.WithFeatures(), - DashboardService: dashSvc, - tracer: tracing.InitializeTracerForTest(), - } - - sc := setupScenarioContext(t, url) - sc.defaultHandler = routing.Wrap(func(c *contextmodel.ReqContext) response.Response { - c.Req.Body = mockRequestBody(cmd) - c.Req.Header.Add("Content-Type", "application/json") - sc.context = c - sc.context.SignedInUser = &user.SignedInUser{ - OrgID: testOrgID, - UserID: testUserID, - } - sc.context.OrgRole = role - - return hs.CalculateDashboardDiff(c) - }) - - sc.m.Post(routePattern, sc.defaultHandler) - - fn(sc) - }) -} - func restoreDashboardVersionScenario(t *testing.T, desc string, url string, routePattern string, mock *dashboards.FakeDashboardService, fakeDashboardVersionService *dashvertest.FakeDashboardVersionService, cmd dtos.RestoreDashboardVersionCommand, fn scenarioFunc, sqlStore db.DB, diff --git a/pkg/api/dtos/frontend_settings.go b/pkg/api/dtos/frontend_settings.go index 60fe760ce06..9193f3d6354 100644 --- a/pkg/api/dtos/frontend_settings.go +++ b/pkg/api/dtos/frontend_settings.go @@ -154,20 +154,21 @@ type FrontendSettingsSqlConnectionLimitsDTO struct { } type FrontendSettingsDTO struct { - DefaultDatasource string `json:"defaultDatasource"` - Datasources map[string]plugins.DataSourceDTO `json:"datasources"` - MinRefreshInterval string `json:"minRefreshInterval"` - Panels map[string]plugins.PanelDTO `json:"panels"` - Apps map[string]*plugins.AppDTO `json:"apps"` - AppUrl string `json:"appUrl"` - AppSubUrl string `json:"appSubUrl"` - AllowOrgCreate bool `json:"allowOrgCreate"` - AuthProxyEnabled bool `json:"authProxyEnabled"` - LdapEnabled bool `json:"ldapEnabled"` - JwtHeaderName string `json:"jwtHeaderName"` - JwtUrlLogin bool `json:"jwtUrlLogin"` - LiveEnabled bool `json:"liveEnabled"` - AutoAssignOrg bool `json:"autoAssignOrg"` + DefaultDatasource string `json:"defaultDatasource"` + Datasources map[string]plugins.DataSourceDTO `json:"datasources"` + MinRefreshInterval string `json:"minRefreshInterval"` + Panels map[string]plugins.PanelDTO `json:"panels"` + Apps map[string]*plugins.AppDTO `json:"apps"` + AppUrl string `json:"appUrl"` + AppSubUrl string `json:"appSubUrl"` + AllowOrgCreate bool `json:"allowOrgCreate"` + AuthProxyEnabled bool `json:"authProxyEnabled"` + LdapEnabled bool `json:"ldapEnabled"` + JwtHeaderName string `json:"jwtHeaderName"` + JwtUrlLogin bool `json:"jwtUrlLogin"` + LiveEnabled bool `json:"liveEnabled"` + LiveMessageSizeLimit int `json:"liveMessageSizeLimit"` + AutoAssignOrg bool `json:"autoAssignOrg"` VerifyEmailEnabled bool `json:"verifyEmailEnabled"` SigV4AuthEnabled bool `json:"sigV4AuthEnabled"` diff --git a/pkg/api/folder_bench_test.go b/pkg/api/folder_bench_test.go index 5847529c941..03a9ab1cf63 100644 --- a/pkg/api/folder_bench_test.go +++ b/pkg/api/folder_bench_test.go @@ -18,8 +18,10 @@ import ( "github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/infra/db" + "github.com/grafana/grafana/pkg/infra/kvstore" "github.com/grafana/grafana/pkg/infra/localcache" "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/infra/serverlock" "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/accesscontrol/acimpl" @@ -476,6 +478,8 @@ func setupServer(b testing.TB, sc benchScenario, features featuremgmt.FeatureTog sc.cfg, dashStore, folderStore, features, folderPermissions, ac, folderServiceWithFlagOn, fStore, nil, client.MockTestRestConfig{}, nil, quotaSrv, nil, nil, nil, dualwrite.ProvideTestService(), sort.ProvideService(), + serverlock.ProvideService(sc.db, tracing.InitializeTracerForTest()), + kvstore.NewFakeKVStore(), ) require.NoError(b, err) diff --git a/pkg/api/folder_test.go b/pkg/api/folder_test.go index 840ae125594..96e8b75a0ec 100644 --- a/pkg/api/folder_test.go +++ b/pkg/api/folder_test.go @@ -768,3 +768,12 @@ func TestSetDefaultPermissionsWhenCreatingFolder(t *testing.T) { }) } } + +func setUpRBACGuardian(t *testing.T) { + origNewGuardian := guardian.New + t.Cleanup(func() { + guardian.New = origNewGuardian + }) + + guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{CanEditValue: true, CanViewValue: true}) +} diff --git a/pkg/api/frontendsettings.go b/pkg/api/frontendsettings.go index 6a9ee667d25..9028bd4faea 100644 --- a/pkg/api/frontendsettings.go +++ b/pkg/api/frontendsettings.go @@ -194,6 +194,7 @@ func (hs *HTTPServer) getFrontendSettings(c *contextmodel.ReqContext) (*dtos.Fro JwtHeaderName: hs.Cfg.JWTAuth.HeaderName, JwtUrlLogin: hs.Cfg.JWTAuth.URLLogin, LiveEnabled: hs.Cfg.LiveMaxConnections != 0, + LiveMessageSizeLimit: hs.Cfg.LiveMessageSizeLimit, AutoAssignOrg: hs.Cfg.AutoAssignOrg, VerifyEmailEnabled: hs.Cfg.VerifyEmailEnabled, SigV4AuthEnabled: hs.Cfg.SigV4AuthEnabled, diff --git a/pkg/apimachinery/utils/meta.go b/pkg/apimachinery/utils/meta.go index c285c202bc7..67076f2e96a 100644 --- a/pkg/apimachinery/utils/meta.go +++ b/pkg/apimachinery/utils/meta.go @@ -86,6 +86,7 @@ type GrafanaMetaAccessor interface { GetMessage() string SetMessage(msg string) SetAnnotation(key string, val string) + GetAnnotation(key string) string SetBlob(v *BlobInfo) GetBlob() *BlobInfo @@ -143,9 +144,13 @@ type grafanaMetaAccessor struct { // required fields are missing. Fields that are not required return the default // value and are a no-op if set. func MetaAccessor(raw interface{}) (GrafanaMetaAccessor, error) { + if raw == nil { + return nil, fmt.Errorf("unable to read metadata from nil object") + } + obj, err := meta.Accessor(raw) if err != nil { - return nil, err + return nil, fmt.Errorf("unable to read metadata from: %T, %s", raw, err) } // reflection to find title and other non object properties @@ -188,6 +193,14 @@ func (m *grafanaMetaAccessor) SetAnnotation(key string, val string) { m.obj.SetAnnotations(anno) } +func (m *grafanaMetaAccessor) GetAnnotation(key string) string { + anno := m.obj.GetAnnotations() + if anno != nil { + return anno[key] + } + return "" +} + func (m *grafanaMetaAccessor) get(key string) string { return m.obj.GetAnnotations()[key] } diff --git a/pkg/apis/provisioning/v0alpha1/jobs.go b/pkg/apis/provisioning/v0alpha1/jobs.go index d621bf3411f..1e075fd1c26 100644 --- a/pkg/apis/provisioning/v0alpha1/jobs.go +++ b/pkg/apis/provisioning/v0alpha1/jobs.go @@ -104,16 +104,13 @@ type ExportJobOptions struct { Branch string `json:"branch,omitempty"` // Prefix in target file system - Prefix string `json:"prefix,omitempty"` + Path string `json:"path,omitempty"` // Include the identifier in the exported metadata Identifier bool `json:"identifier"` } type MigrateJobOptions struct { - // Target file prefix - Prefix string `json:"prefix,omitempty"` - // Preserve history (if possible) History bool `json:"history,omitempty"` diff --git a/pkg/apis/provisioning/v0alpha1/zz_generated.openapi.go b/pkg/apis/provisioning/v0alpha1/zz_generated.openapi.go index 571f26b2b27..6ddc883ebda 100644 --- a/pkg/apis/provisioning/v0alpha1/zz_generated.openapi.go +++ b/pkg/apis/provisioning/v0alpha1/zz_generated.openapi.go @@ -108,7 +108,7 @@ func schema_pkg_apis_provisioning_v0alpha1_ExportJobOptions(ref common.Reference Format: "", }, }, - "prefix": { + "path": { SchemaProps: spec.SchemaProps{ Description: "Prefix in target file system", Type: []string{"string"}, @@ -818,13 +818,6 @@ func schema_pkg_apis_provisioning_v0alpha1_MigrateJobOptions(ref common.Referenc SchemaProps: spec.SchemaProps{ Type: []string{"object"}, Properties: map[string]spec.Schema{ - "prefix": { - SchemaProps: spec.SchemaProps{ - Description: "Target file prefix", - Type: []string{"string"}, - Format: "", - }, - }, "history": { SchemaProps: spec.SchemaProps{ Description: "Preserve history (if possible)", diff --git a/pkg/apis/secret/v0alpha1/decrypt/decrypt.pb.go b/pkg/apis/secret/v0alpha1/decrypt/decrypt.pb.go index c1b285caede..cffa2065ef0 100644 --- a/pkg/apis/secret/v0alpha1/decrypt/decrypt.pb.go +++ b/pkg/apis/secret/v0alpha1/decrypt/decrypt.pb.go @@ -78,9 +78,9 @@ func (x *SecureValueDecryptRequest) GetNames() []string { type SecureValueDecryptResponseCollection struct { state protoimpl.MessageState `protogen:"open.v1"` // A map of secure value names and their decrypted values. - // The value may be empty if the requestor does not have permissions to read it, or if the value does not exist. + // The value will be an error message if the requestor does not have permissions to read it, or if the value does not exist. // It will never return a 404 Not Found to avoid scanning of valid secure values. - DecryptedValues map[string]string `protobuf:"bytes,1,rep,name=decrypted_values,json=decryptedValues,proto3" json:"decrypted_values,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + DecryptedValues map[string]*Result `protobuf:"bytes,1,rep,name=decrypted_values,json=decryptedValues,proto3" json:"decrypted_values,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -115,13 +115,95 @@ func (*SecureValueDecryptResponseCollection) Descriptor() ([]byte, []int) { return file_decrypt_proto_rawDescGZIP(), []int{1} } -func (x *SecureValueDecryptResponseCollection) GetDecryptedValues() map[string]string { +func (x *SecureValueDecryptResponseCollection) GetDecryptedValues() map[string]*Result { if x != nil { return x.DecryptedValues } return nil } +type Result struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Types that are valid to be assigned to Result: + // + // *Result_Value + // *Result_ErrorMessage + Result isResult_Result `protobuf_oneof:"result"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Result) Reset() { + *x = Result{} + mi := &file_decrypt_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Result) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Result) ProtoMessage() {} + +func (x *Result) ProtoReflect() protoreflect.Message { + mi := &file_decrypt_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Result.ProtoReflect.Descriptor instead. +func (*Result) Descriptor() ([]byte, []int) { + return file_decrypt_proto_rawDescGZIP(), []int{2} +} + +func (x *Result) GetResult() isResult_Result { + if x != nil { + return x.Result + } + return nil +} + +func (x *Result) GetValue() string { + if x != nil { + if x, ok := x.Result.(*Result_Value); ok { + return x.Value + } + } + return "" +} + +func (x *Result) GetErrorMessage() string { + if x != nil { + if x, ok := x.Result.(*Result_ErrorMessage); ok { + return x.ErrorMessage + } + } + return "" +} + +type isResult_Result interface { + isResult_Result() +} + +type Result_Value struct { + Value string `protobuf:"bytes,1,opt,name=value,proto3,oneof"` +} + +type Result_ErrorMessage struct { + ErrorMessage string `protobuf:"bytes,2,opt,name=error_message,json=errorMessage,proto3,oneof"` +} + +func (*Result_Value) isResult_Result() {} + +func (*Result_ErrorMessage) isResult_Result() {} + var File_decrypt_proto protoreflect.FileDescriptor var file_decrypt_proto_rawDesc = string([]byte{ @@ -131,7 +213,7 @@ var file_decrypt_proto_rawDesc = string([]byte{ 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, - 0x28, 0x09, 0x52, 0x05, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x22, 0xd9, 0x01, 0x0a, 0x24, 0x53, 0x65, + 0x28, 0x09, 0x52, 0x05, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x22, 0xea, 0x01, 0x0a, 0x24, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x44, 0x65, 0x63, 0x72, 0x79, 0x70, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x6d, 0x0a, 0x10, 0x64, 0x65, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x5f, @@ -141,23 +223,30 @@ var file_decrypt_proto_rawDesc = string([]byte{ 0x65, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x44, 0x65, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0f, 0x64, 0x65, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x56, 0x61, 0x6c, 0x75, 0x65, - 0x73, 0x1a, 0x42, 0x0a, 0x14, 0x44, 0x65, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x56, 0x61, + 0x73, 0x1a, 0x53, 0x0a, 0x14, 0x44, 0x65, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, - 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, - 0x65, 0x3a, 0x02, 0x38, 0x01, 0x32, 0x80, 0x01, 0x0a, 0x14, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, - 0x56, 0x61, 0x6c, 0x75, 0x65, 0x44, 0x65, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x72, 0x12, 0x68, - 0x0a, 0x13, 0x44, 0x65, 0x63, 0x72, 0x79, 0x70, 0x74, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x56, - 0x61, 0x6c, 0x75, 0x65, 0x73, 0x12, 0x22, 0x2e, 0x64, 0x65, 0x63, 0x72, 0x79, 0x70, 0x74, 0x2e, - 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x44, 0x65, 0x63, 0x72, 0x79, - 0x70, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2d, 0x2e, 0x64, 0x65, 0x63, 0x72, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x25, 0x0a, 0x05, 0x76, + 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x64, 0x65, 0x63, + 0x72, 0x79, 0x70, 0x74, 0x2e, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x05, 0x76, 0x61, 0x6c, + 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x51, 0x0a, 0x06, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, + 0x12, 0x16, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x48, + 0x00, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x25, 0x0a, 0x0d, 0x65, 0x72, 0x72, 0x6f, + 0x72, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, + 0x00, 0x52, 0x0c, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x42, + 0x08, 0x0a, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x32, 0x80, 0x01, 0x0a, 0x14, 0x53, 0x65, + 0x63, 0x75, 0x72, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x44, 0x65, 0x63, 0x72, 0x79, 0x70, 0x74, + 0x65, 0x72, 0x12, 0x68, 0x0a, 0x13, 0x44, 0x65, 0x63, 0x72, 0x79, 0x70, 0x74, 0x53, 0x65, 0x63, + 0x75, 0x72, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x12, 0x22, 0x2e, 0x64, 0x65, 0x63, 0x72, 0x79, 0x70, 0x74, 0x2e, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x44, - 0x65, 0x63, 0x72, 0x79, 0x70, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x43, 0x6f, - 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x3d, 0x5a, 0x3b, 0x67, 0x69, 0x74, 0x68, - 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x72, 0x61, 0x66, 0x61, 0x6e, 0x61, 0x2f, 0x67, - 0x72, 0x61, 0x66, 0x61, 0x6e, 0x61, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x61, 0x70, 0x69, 0x73, 0x2f, - 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x2f, 0x76, 0x30, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2f, - 0x64, 0x65, 0x63, 0x72, 0x79, 0x70, 0x74, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x65, 0x63, 0x72, 0x79, 0x70, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2d, 0x2e, + 0x64, 0x65, 0x63, 0x72, 0x79, 0x70, 0x74, 0x2e, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x56, 0x61, + 0x6c, 0x75, 0x65, 0x44, 0x65, 0x63, 0x72, 0x79, 0x70, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x3d, 0x5a, 0x3b, + 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x72, 0x61, 0x66, 0x61, + 0x6e, 0x61, 0x2f, 0x67, 0x72, 0x61, 0x66, 0x61, 0x6e, 0x61, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x61, + 0x70, 0x69, 0x73, 0x2f, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x2f, 0x76, 0x30, 0x61, 0x6c, 0x70, + 0x68, 0x61, 0x31, 0x2f, 0x64, 0x65, 0x63, 0x72, 0x79, 0x70, 0x74, 0x62, 0x06, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x33, }) var ( @@ -172,21 +261,23 @@ func file_decrypt_proto_rawDescGZIP() []byte { return file_decrypt_proto_rawDescData } -var file_decrypt_proto_msgTypes = make([]protoimpl.MessageInfo, 3) +var file_decrypt_proto_msgTypes = make([]protoimpl.MessageInfo, 4) var file_decrypt_proto_goTypes = []any{ (*SecureValueDecryptRequest)(nil), // 0: decrypt.SecureValueDecryptRequest (*SecureValueDecryptResponseCollection)(nil), // 1: decrypt.SecureValueDecryptResponseCollection - nil, // 2: decrypt.SecureValueDecryptResponseCollection.DecryptedValuesEntry + (*Result)(nil), // 2: decrypt.Result + nil, // 3: decrypt.SecureValueDecryptResponseCollection.DecryptedValuesEntry } var file_decrypt_proto_depIdxs = []int32{ - 2, // 0: decrypt.SecureValueDecryptResponseCollection.decrypted_values:type_name -> decrypt.SecureValueDecryptResponseCollection.DecryptedValuesEntry - 0, // 1: decrypt.SecureValueDecrypter.DecryptSecureValues:input_type -> decrypt.SecureValueDecryptRequest - 1, // 2: decrypt.SecureValueDecrypter.DecryptSecureValues:output_type -> decrypt.SecureValueDecryptResponseCollection - 2, // [2:3] is the sub-list for method output_type - 1, // [1:2] is the sub-list for method input_type - 1, // [1:1] is the sub-list for extension type_name - 1, // [1:1] is the sub-list for extension extendee - 0, // [0:1] is the sub-list for field type_name + 3, // 0: decrypt.SecureValueDecryptResponseCollection.decrypted_values:type_name -> decrypt.SecureValueDecryptResponseCollection.DecryptedValuesEntry + 2, // 1: decrypt.SecureValueDecryptResponseCollection.DecryptedValuesEntry.value:type_name -> decrypt.Result + 0, // 2: decrypt.SecureValueDecrypter.DecryptSecureValues:input_type -> decrypt.SecureValueDecryptRequest + 1, // 3: decrypt.SecureValueDecrypter.DecryptSecureValues:output_type -> decrypt.SecureValueDecryptResponseCollection + 3, // [3:4] is the sub-list for method output_type + 2, // [2:3] is the sub-list for method input_type + 2, // [2:2] is the sub-list for extension type_name + 2, // [2:2] is the sub-list for extension extendee + 0, // [0:2] is the sub-list for field type_name } func init() { file_decrypt_proto_init() } @@ -194,13 +285,17 @@ func file_decrypt_proto_init() { if File_decrypt_proto != nil { return } + file_decrypt_proto_msgTypes[2].OneofWrappers = []any{ + (*Result_Value)(nil), + (*Result_ErrorMessage)(nil), + } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_decrypt_proto_rawDesc), len(file_decrypt_proto_rawDesc)), NumEnums: 0, - NumMessages: 3, + NumMessages: 4, NumExtensions: 0, NumServices: 1, }, diff --git a/pkg/apis/secret/v0alpha1/decrypt/decrypt.proto b/pkg/apis/secret/v0alpha1/decrypt/decrypt.proto index 402ef684efd..bf328ff5217 100644 --- a/pkg/apis/secret/v0alpha1/decrypt/decrypt.proto +++ b/pkg/apis/secret/v0alpha1/decrypt/decrypt.proto @@ -14,9 +14,16 @@ message SecureValueDecryptRequest { message SecureValueDecryptResponseCollection { // A map of secure value names and their decrypted values. - // The value may be empty if the requestor does not have permissions to read it, or if the value does not exist. + // The value will be an error message if the requestor does not have permissions to read it, or if the value does not exist. // It will never return a 404 Not Found to avoid scanning of valid secure values. - map decrypted_values = 1; + map decrypted_values = 1; +} + +message Result { + oneof result { + string value = 1; + string error_message = 2; + } } service SecureValueDecrypter { diff --git a/pkg/apiserver/registry/generic/strategy.go b/pkg/apiserver/registry/generic/strategy.go index 6cb5dd918fd..5293335f1f3 100644 --- a/pkg/apiserver/registry/generic/strategy.go +++ b/pkg/apiserver/registry/generic/strategy.go @@ -3,8 +3,6 @@ package generic import ( "context" - "github.com/grafana/grafana/pkg/apimachinery/utils" - apiequality "k8s.io/apimachinery/pkg/api/equality" "k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/labels" @@ -14,6 +12,8 @@ import ( "k8s.io/apiserver/pkg/storage" "k8s.io/apiserver/pkg/storage/names" "sigs.k8s.io/structured-merge-diff/v4/fieldpath" + + "github.com/grafana/grafana/pkg/apimachinery/utils" ) type genericStrategy struct { @@ -81,20 +81,6 @@ func (g *genericStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime } else { _ = newMeta.SetStatus(status) } - - spec, err := newMeta.GetSpec() - if err != nil { - return - } - - oldSpec, err := oldMeta.GetSpec() - if err != nil { - return - } - - if !apiequality.Semantic.DeepEqual(spec, oldSpec) { - newMeta.SetGeneration(oldMeta.GetGeneration() + 1) - } } func (g *genericStrategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList { diff --git a/pkg/apiserver/registry/generic/strategy_test.go b/pkg/apiserver/registry/generic/strategy_test.go index 969332305a2..906713d18fe 100644 --- a/pkg/apiserver/registry/generic/strategy_test.go +++ b/pkg/apiserver/registry/generic/strategy_test.go @@ -4,12 +4,13 @@ import ( "context" "testing" - "github.com/grafana/grafana/pkg/apiserver/registry/generic" "github.com/stretchr/testify/require" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apiserver/pkg/apis/example" + + "github.com/grafana/grafana/pkg/apiserver/registry/generic" ) func TestPrepareForUpdate(t *testing.T) { @@ -69,37 +70,6 @@ func TestPrepareForUpdate(t *testing.T) { }, }, }, - { - name: "increment generation if spec changes", - newObj: &example.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test", - Namespace: "default", - Generation: 1, - }, - Spec: example.PodSpec{ - NodeSelector: map[string]string{"foo": "baz"}, - }, - Status: example.PodStatus{ - Phase: example.PodPhase("Running"), - }, - }, - oldObj: oldObj.DeepCopy(), - expectedGen: 2, - expectedObj: &example.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test", - Namespace: "default", - Generation: 2, - }, - Spec: example.PodSpec{ - NodeSelector: map[string]string{"foo": "baz"}, - }, - Status: example.PodStatus{ - Phase: example.PodPhase("Running"), - }, - }, - }, } for _, tc := range testCases { diff --git a/pkg/expr/sql/parser.go b/pkg/expr/sql/parser.go index 12269b4a68f..c65e4b89131 100644 --- a/pkg/expr/sql/parser.go +++ b/pkg/expr/sql/parser.go @@ -14,7 +14,7 @@ var logger = log.New("sql_expr") func TablesList(rawSQL string) ([]string, error) { stmt, err := sqlparser.Parse(rawSQL) if err != nil { - logger.Error("error parsing sql: %s", err.Error(), "sql", rawSQL) + logger.Error("error parsing sql", "error", err.Error(), "sql", rawSQL) return nil, fmt.Errorf("error parsing sql: %s", err.Error()) } diff --git a/pkg/expr/sql_command.go b/pkg/expr/sql_command.go index 0b4d7ab698e..8bcde6abb9b 100644 --- a/pkg/expr/sql_command.go +++ b/pkg/expr/sql_command.go @@ -14,6 +14,16 @@ import ( "github.com/grafana/grafana/pkg/infra/tracing" ) +var ( + ErrMissingSQLQuery = errutil.BadRequest("sql-missing-query").Errorf("missing SQL query") + ErrInvalidSQLQuery = errutil.BadRequest("sql-invalid-sql").MustTemplate( + "invalid SQL query: {{ .Private.query }} err: {{ .Error }}", + errutil.WithPublic( + "Invalid SQL query: {{ .Public.error }}", + ), + ) +) + // SQLCommand is an expression to run SQL over results type SQLCommand struct { query string @@ -25,15 +35,20 @@ type SQLCommand struct { // NewSQLCommand creates a new SQLCommand. func NewSQLCommand(refID, rawSQL string, limit int64) (*SQLCommand, error) { if rawSQL == "" { - return nil, errutil.BadRequest("sql-missing-query", - errutil.WithPublicMessage("missing SQL query")) + return nil, ErrMissingSQLQuery } tables, err := sql.TablesList(rawSQL) if err != nil { logger.Warn("invalid sql query", "sql", rawSQL, "error", err) - return nil, errutil.BadRequest("sql-invalid-sql", - errutil.WithPublicMessage(fmt.Sprintf("invalid SQL query: %s", err)), - ) + return nil, ErrInvalidSQLQuery.Build(errutil.TemplateData{ + Error: err, + Public: map[string]any{ + "error": err.Error(), + }, + Private: map[string]any{ + "query": rawSQL, + }, + }) } if len(tables) == 0 { logger.Warn("no tables found in SQL query", "sql", rawSQL) diff --git a/pkg/plugins/backendplugin/grpcplugin/client_v2.go b/pkg/plugins/backendplugin/grpcplugin/client_v2.go index 7babf11c2df..78d7b0d6dcc 100644 --- a/pkg/plugins/backendplugin/grpcplugin/client_v2.go +++ b/pkg/plugins/backendplugin/grpcplugin/client_v2.go @@ -172,6 +172,14 @@ func (c *ClientV2) QueryData(ctx context.Context, req *backend.QueryDataRequest) return nil, plugins.ErrMethodNotImplemented } + if status.Code(err) == codes.Unavailable { + return nil, plugins.ErrPluginGrpcConnectionUnavailableBase.Errorf("%v", err) + } + + if status.Code(err) == codes.ResourceExhausted { + return nil, plugins.ErrPluginGrpcResourceExhaustedBase.Errorf("%v", err) + } + if errorSource, ok := backend.ErrorSourceFromGrpcStatusError(ctx, err); ok { return nil, handleGrpcStatusError(ctx, errorSource, err) } diff --git a/pkg/plugins/errors.go b/pkg/plugins/errors.go index 1ab785ff7a3..77e35735ee0 100644 --- a/pkg/plugins/errors.go +++ b/pkg/plugins/errors.go @@ -35,4 +35,16 @@ var ( // Exposed as a base error to wrap it with plugin cancelled errors. ErrPluginRequestCanceledErrorBase = errutil.ClientClosedRequest("plugin.requestCanceled", errutil.WithPublicMessage("Plugin request canceled")) + + // ErrPluginGrpcResourceExhaustedBase error returned when a plugin response is larger than the grpc limit. + // Exposed as a base error to wrap it with plugin resource exhausted errors. + ErrPluginGrpcResourceExhaustedBase = errutil.Internal("plugin.resourceExhausted", + errutil.WithPublicMessage("The response is too large. Please try to reduce the time range or narrow down your query to return fewer data points."), + errutil.WithDownstream()) + + // ErrPluginGrpcConnectionUnavailableBase error returned when a plugin connection issue occurs. + // Exposed as a base error to wrap it with plugin connection issue errors. + ErrPluginGrpcConnectionUnavailableBase = errutil.Internal("plugin.connectionUnavailable", + errutil.WithPublicMessage("Data source became unavailable during request. Please try again."), + errutil.WithDownstream()) ) diff --git a/pkg/plugins/manager/client/client.go b/pkg/plugins/manager/client/client.go index 7ab5d6a60ff..4f4d19aa6b0 100644 --- a/pkg/plugins/manager/client/client.go +++ b/pkg/plugins/manager/client/client.go @@ -30,6 +30,14 @@ var ( errNilSender = errors.New("sender cannot be nil") ) +// passthroughErrors contains a list of errors that should be returned directly to the caller without wrapping +var passthroughErrors = []error{ + plugins.ErrPluginUnavailable, + plugins.ErrMethodNotImplemented, + plugins.ErrPluginGrpcResourceExhaustedBase, + plugins.ErrPluginGrpcConnectionUnavailableBase, +} + type Service struct { pluginRegistry registry.Service } @@ -52,12 +60,10 @@ func (s *Service) QueryData(ctx context.Context, req *backend.QueryDataRequest) resp, err := p.QueryData(ctx, req) if err != nil { - if errors.Is(err, plugins.ErrMethodNotImplemented) { - return nil, err - } - - if errors.Is(err, plugins.ErrPluginUnavailable) { - return nil, err + for _, e := range passthroughErrors { + if errors.Is(err, e) { + return nil, err + } } if errors.Is(err, context.Canceled) { diff --git a/pkg/plugins/manager/client/client_test.go b/pkg/plugins/manager/client/client_test.go index f45761c3649..4964a3b1631 100644 --- a/pkg/plugins/manager/client/client_test.go +++ b/pkg/plugins/manager/client/client_test.go @@ -25,24 +25,39 @@ func TestQueryData(t *testing.T) { t.Run("Non-empty registry", func(t *testing.T) { tcs := []struct { - err error - expectedError error + err error + expectedError error + shouldPassThrough bool }{ { - err: plugins.ErrPluginUnavailable, - expectedError: plugins.ErrPluginUnavailable, + err: plugins.ErrPluginUnavailable, + expectedError: plugins.ErrPluginUnavailable, + shouldPassThrough: true, }, { - err: plugins.ErrMethodNotImplemented, - expectedError: plugins.ErrMethodNotImplemented, + err: plugins.ErrMethodNotImplemented, + expectedError: plugins.ErrMethodNotImplemented, + shouldPassThrough: true, }, { - err: errors.New("surprise surprise"), - expectedError: plugins.ErrPluginRequestFailureErrorBase, + err: errors.New("surprise surprise"), + expectedError: plugins.ErrPluginRequestFailureErrorBase, + shouldPassThrough: false, }, { - err: context.Canceled, - expectedError: plugins.ErrPluginRequestCanceledErrorBase, + err: context.Canceled, + expectedError: plugins.ErrPluginRequestCanceledErrorBase, + shouldPassThrough: false, + }, + { + err: plugins.ErrPluginGrpcConnectionUnavailableBase.Errorf("unavailable"), + expectedError: plugins.ErrPluginGrpcConnectionUnavailableBase.Errorf("unavailable"), + shouldPassThrough: true, + }, + { + err: plugins.ErrPluginGrpcResourceExhaustedBase.Errorf("exhausted"), + expectedError: plugins.ErrPluginGrpcResourceExhaustedBase.Errorf("exhausted"), + shouldPassThrough: true, }, } @@ -69,7 +84,11 @@ func TestQueryData(t *testing.T) { }, }) require.Error(t, err) - require.ErrorIs(t, err, tc.expectedError) + if tc.shouldPassThrough { + require.Equal(t, tc.err, err) + } else { + require.ErrorIs(t, err, tc.expectedError) + } }) } }) diff --git a/pkg/promlib/go.mod b/pkg/promlib/go.mod index 4b4393c9c70..020113b5982 100644 --- a/pkg/promlib/go.mod +++ b/pkg/promlib/go.mod @@ -18,7 +18,7 @@ require ( require ( github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.1 // indirect - github.com/BurntSushi/toml v1.4.0 // indirect + github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c // indirect github.com/apache/arrow-go/v18 v18.0.1-0.20241212180703-82be143d7c30 // indirect github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect github.com/bahlo/generic-list-go v0.2.0 // indirect @@ -33,7 +33,7 @@ require ( github.com/dennwc/varint v1.0.0 // indirect github.com/elazarl/goproxy v1.7.2 // indirect github.com/emicklei/go-restful/v3 v3.11.0 // indirect - github.com/fatih/color v1.17.0 // indirect + github.com/fatih/color v1.18.0 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect github.com/getkin/kin-openapi v0.129.0 // indirect github.com/go-logr/logr v1.4.2 // indirect diff --git a/pkg/promlib/go.sum b/pkg/promlib/go.sum index 899f8550654..cbc5e25726a 100644 --- a/pkg/promlib/go.sum +++ b/pkg/promlib/go.sum @@ -13,8 +13,9 @@ github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0/go.mod h1:iZDifYGJTIgIIkY github.com/AzureAD/microsoft-authentication-library-for-go v1.3.2 h1:kYRSnvJju5gYVyhkij+RTJ/VR6QIUaCfWeaFm2ycsjQ= github.com/AzureAD/microsoft-authentication-library-for-go v1.3.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0= github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= +github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c h1:pxW6RcqyfI9/kWtOwnv/G+AzdKuy2ZrqINhenH4HyNs= +github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b h1:mimo19zliBX/vSQ6PWWSL9lK8qwHozUj03+zLoEB8O0= github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b/go.mod h1:fvzegU4vN3H1qMT+8wDmzjAcDONcgo2/SZ/TyfdUOFs= github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA= @@ -60,8 +61,8 @@ github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVo github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= -github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4= -github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI= +github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= +github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= diff --git a/pkg/registry/apis/dashboard/authorizer.go b/pkg/registry/apis/dashboard/authorizer.go deleted file mode 100644 index 0b51cd32c68..00000000000 --- a/pkg/registry/apis/dashboard/authorizer.go +++ /dev/null @@ -1,93 +0,0 @@ -package dashboard - -import ( - "context" - - "k8s.io/apiserver/pkg/authorization/authorizer" - - claims "github.com/grafana/authlib/types" - "github.com/grafana/grafana/pkg/apimachinery/identity" - "github.com/grafana/grafana/pkg/infra/log" - "github.com/grafana/grafana/pkg/services/dashboards" - "github.com/grafana/grafana/pkg/services/guardian" -) - -func GetAuthorizer(dashboardService dashboards.DashboardService, l log.Logger) authorizer.Authorizer { - return authorizer.AuthorizerFunc( - func(ctx context.Context, attr authorizer.Attributes) (authorized authorizer.Decision, reason string, err error) { - // Use the standard authorizer - if !attr.IsResourceRequest() { - return authorizer.DecisionNoOpinion, "", nil - } - - user, err := identity.GetRequester(ctx) - if err != nil { - return authorizer.DecisionDeny, "", err - } - - // Allow search and list requests - if attr.GetResource() == "search" || attr.GetName() == "" { - return authorizer.DecisionNoOpinion, "", nil - } - - ns := attr.GetNamespace() - if ns == "" { - return authorizer.DecisionDeny, "expected namespace", nil - } - - info, err := claims.ParseNamespace(attr.GetNamespace()) - if err != nil { - return authorizer.DecisionDeny, "error reading org from namespace", err - } - - // expensive path to lookup permissions for a single dashboard - // must include deleted to allow for restores - dto, err := dashboardService.GetDashboard(ctx, &dashboards.GetDashboardQuery{ - UID: attr.GetName(), - OrgID: info.OrgID, - IncludeDeleted: true, - }) - if err != nil { - return authorizer.DecisionDeny, "error loading dashboard", err - } - - ok := false - guardian, err := guardian.NewByDashboard(ctx, dto, info.OrgID, user) - if err != nil { - return authorizer.DecisionDeny, "", err - } - - switch attr.GetVerb() { - case "get": - ok, err = guardian.CanView() - if !ok || err != nil { - return authorizer.DecisionDeny, "can not view dashboard", err - } - case "create": - fallthrough - case "post": - ok, err = guardian.CanSave() // vs Edit? - if !ok || err != nil { - return authorizer.DecisionDeny, "can not save dashboard", err - } - case "update": - fallthrough - case "patch": - fallthrough - case "put": - ok, err = guardian.CanEdit() // vs Save - if !ok || err != nil { - return authorizer.DecisionDeny, "can not edit dashboard", err - } - case "delete": - ok, err = guardian.CanDelete() - if !ok || err != nil { - return authorizer.DecisionDeny, "can not delete dashboard", err - } - default: - l.Info("unknown verb", "verb", attr.GetVerb()) - return authorizer.DecisionNoOpinion, "unsupported verb", nil // Unknown verb - } - return authorizer.DecisionAllow, "", nil - }) -} diff --git a/pkg/registry/apis/dashboard/latest.go b/pkg/registry/apis/dashboard/latest.go deleted file mode 100644 index 0944bcdf537..00000000000 --- a/pkg/registry/apis/dashboard/latest.go +++ /dev/null @@ -1,103 +0,0 @@ -package dashboard - -import ( - "context" - "fmt" - "net/http" - "strconv" - - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apiserver/pkg/registry/rest" - "k8s.io/apiserver/pkg/storage" - - "github.com/grafana/grafana/pkg/services/apiserver/endpoints/request" - "github.com/grafana/grafana/pkg/storage/unified/resource" -) - -// LatestConnector will return the latest version of the resource - even if it is deleted -type LatestConnector interface { - rest.Storage - rest.Connecter - rest.StorageMetadata -} - -func NewLatestConnector(unified resource.ResourceClient, gr schema.GroupResource) LatestConnector { - return &latestREST{ - unified: unified, - gr: gr, - } -} - -type latestREST struct { - unified resource.ResourceClient - gr schema.GroupResource -} - -func (l *latestREST) New() runtime.Object { - return &metav1.PartialObjectMetadataList{} -} - -func (l *latestREST) Destroy() { -} - -func (l *latestREST) ConnectMethods() []string { - return []string{"GET"} -} - -func (l *latestREST) ProducesMIMETypes(verb string) []string { - return nil -} - -func (l *latestREST) ProducesObject(verb string) interface{} { - return &metav1.PartialObjectMetadataList{} -} - -func (l *latestREST) NewConnectOptions() (runtime.Object, bool, string) { - return nil, false, "" -} - -func (l *latestREST) Connect(ctx context.Context, uid string, opts runtime.Object, responder rest.Responder) (http.Handler, error) { - info, err := request.NamespaceInfoFrom(ctx, true) - if err != nil { - return nil, err - } - - key := &resource.ResourceKey{ - Namespace: info.Value, - Group: l.gr.Group, - Resource: l.gr.Resource, - Name: uid, - } - - return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - rsp, err := l.unified.Read(ctx, &resource.ReadRequest{ - Key: key, - ResourceVersion: 0, // 0 will return the latest version that was not a delete event - IncludeDeleted: true, - }) - if err != nil { - responder.Error(err) - return - } else if rsp == nil || (rsp.Error != nil && rsp.Error.Code == http.StatusNotFound) { - responder.Error(storage.NewKeyNotFoundError(uid, 0)) - return - } else if rsp.Error != nil { - responder.Error(fmt.Errorf("could not retrieve object: %s", rsp.Error.Message)) - return - } - - uncastObj, err := runtime.Decode(unstructured.UnstructuredJSONScheme, rsp.Value) - if err != nil { - responder.Error(fmt.Errorf("could not convert object: %s", err.Error())) - return - } - - finalObj := uncastObj.(*unstructured.Unstructured) - finalObj.SetResourceVersion(strconv.FormatInt(rsp.ResourceVersion, 10)) - - responder.Object(http.StatusOK, finalObj) - }), nil -} diff --git a/pkg/registry/apis/dashboard/latest_test.go b/pkg/registry/apis/dashboard/latest_test.go deleted file mode 100644 index cdf54985a70..00000000000 --- a/pkg/registry/apis/dashboard/latest_test.go +++ /dev/null @@ -1,99 +0,0 @@ -package dashboard - -import ( - "context" - "encoding/json" - "net/http" - "net/http/httptest" - "reflect" - "strconv" - "testing" - - "github.com/grafana/grafana/pkg/storage/unified/resource" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" - "github.com/stretchr/testify/require" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apiserver/pkg/endpoints/request" -) - -func TestLatest(t *testing.T) { - gr := schema.GroupResource{ - Group: "group", - Resource: "resource", - } - ctx := context.Background() - mockResponder := &mockResponder{} - mockClient := &mockResourceClient{} - r := &latestREST{ - unified: mockClient, - gr: gr, - } - - t.Run("no namespace in context", func(t *testing.T) { - _, err := r.Connect(ctx, "test-uid", nil, mockResponder) - require.Error(t, err) - }) - - ctx = request.WithNamespace(context.Background(), "default") - - t.Run("happy path", func(t *testing.T) { - req := httptest.NewRequest("GET", "/latest", nil) - w := httptest.NewRecorder() - - readReq := &resource.ReadRequest{ - Key: &resource.ResourceKey{ - Namespace: "default", - Group: "group", - Resource: "resource", - Name: "uid", - }, - ResourceVersion: 0, - IncludeDeleted: true, - } - - expectedObject := &metav1.PartialObjectMetadata{ - TypeMeta: metav1.TypeMeta{ - Kind: "resource", - APIVersion: "v0alpha1", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "uid", - Namespace: "default", - ResourceVersion: strconv.FormatInt(123, 10), - }, - } - - val, err := json.Marshal(expectedObject) - require.NoError(t, err) - mockClient.On("Read", ctx, readReq).Return(&resource.ReadResponse{ - ResourceVersion: 123, - Value: val, - }, nil).Once() - - mockResponder.On("Object", http.StatusOK, mock.MatchedBy(func(obj interface{}) bool { - unstructuredObj, ok := obj.(*unstructured.Unstructured) - expectedMap := map[string]interface{}{ - "apiVersion": expectedObject.APIVersion, - "kind": expectedObject.Kind, - "metadata": map[string]interface{}{ - "name": expectedObject.Name, - "namespace": expectedObject.Namespace, - "resourceVersion": expectedObject.ResourceVersion, - "creationTimestamp": nil, - }, - } - return ok && reflect.DeepEqual(unstructuredObj.Object, expectedMap) - })) - - handler, err := r.Connect(ctx, "uid", nil, mockResponder) - require.NoError(t, err) - handler.ServeHTTP(w, req) - assert.Equal(t, http.StatusOK, w.Code) - - mockClient.AssertExpectations(t) - mockResponder.AssertExpectations(t) - }) -} diff --git a/pkg/registry/apis/dashboard/legacy/client.go b/pkg/registry/apis/dashboard/legacy/client.go index afcd1e0fddf..d85c66047c9 100644 --- a/pkg/registry/apis/dashboard/legacy/client.go +++ b/pkg/registry/apis/dashboard/legacy/client.go @@ -70,11 +70,6 @@ func (d *directResourceClient) Read(ctx context.Context, in *resource.ReadReques return d.server.Read(ctx, in) } -// Restore implements ResourceClient. -func (d *directResourceClient) Restore(ctx context.Context, in *resource.RestoreRequest, opts ...grpc.CallOption) (*resource.RestoreResponse, error) { - return d.server.Restore(ctx, in) -} - // Search implements ResourceClient. func (d *directResourceClient) Search(ctx context.Context, in *resource.ResourceSearchRequest, opts ...grpc.CallOption) (*resource.ResourceSearchResponse, error) { return d.server.Search(ctx, in) diff --git a/pkg/registry/apis/dashboard/legacy/sql_dashboards.go b/pkg/registry/apis/dashboard/legacy/sql_dashboards.go index 8f07f37658b..bc9b5a2c4e0 100644 --- a/pkg/registry/apis/dashboard/legacy/sql_dashboards.go +++ b/pkg/registry/apis/dashboard/legacy/sql_dashboards.go @@ -404,6 +404,7 @@ func (a *dashboardSqlAccess) buildSaveDashboardCommand(ctx context.Context, orgI }) if old != nil { dash.Spec.Set("id", old.ID) + dash.Spec.Set("version", float64(old.Version)) } else { dash.Spec.Remove("id") // existing of "id" makes it an update created = true diff --git a/pkg/registry/apis/dashboard/legacy/sql_dashboards_test.go b/pkg/registry/apis/dashboard/legacy/sql_dashboards_test.go index 2e8d5485e9e..efa4ffde96f 100644 --- a/pkg/registry/apis/dashboard/legacy/sql_dashboards_test.go +++ b/pkg/registry/apis/dashboard/legacy/sql_dashboards_test.go @@ -169,6 +169,7 @@ func TestBuildSaveDashboardCommand(t *testing.T) { mockStore.On("GetDashboard", mock.Anything, mock.Anything).Return( &dashboards.Dashboard{ ID: 1234, + Version: 2, APIVersion: "dashboard.grafana.app/v0alpha1", }, nil).Once() cmd, created, err = access.buildSaveDashboardCommand(ctx, 1, dash) @@ -176,8 +177,9 @@ func TestBuildSaveDashboardCommand(t *testing.T) { require.Equal(t, false, created) require.NotNil(t, cmd) require.Equal(t, "test-dash", cmd.Dashboard.Get("uid").MustString()) - require.Equal(t, cmd.Dashboard.Get("id").MustInt64(), int64(1234)) // should set to existing ID - require.Equal(t, cmd.APIVersion, "v0alpha1") // should trim prefix + require.Equal(t, cmd.Dashboard.Get("id").MustInt64(), int64(1234)) // should set to existing ID + require.Equal(t, cmd.Dashboard.Get("version").MustFloat64(), float64(2)) // version must be set - otherwise seen as a new dashboard in NewDashboardFromJson + require.Equal(t, cmd.APIVersion, "v0alpha1") // should trim prefix require.Equal(t, cmd.OrgID, int64(1)) require.True(t, cmd.Overwrite) } diff --git a/pkg/registry/apis/dashboard/legacy_storage.go b/pkg/registry/apis/dashboard/legacy_storage.go index 20445e5aa6c..2037376723f 100644 --- a/pkg/registry/apis/dashboard/legacy_storage.go +++ b/pkg/registry/apis/dashboard/legacy_storage.go @@ -39,6 +39,10 @@ func (s *DashboardStorage) NewStore(dash utils.ResourceInfo, scheme *runtime.Sch optsGetter := apistore.NewRESTOptionsGetterForClient(client, defaultOpts.StorageConfig.Config, ) + optsGetter.RegisterOptions(dash.GroupResource(), apistore.StorageOptions{ + EnableFolderSupport: true, + RequireDeprecatedInternalID: true, + }) store, err := grafanaregistry.NewRegistryStore(scheme, dash, optsGetter) return &storeWrapper{ diff --git a/pkg/registry/apis/dashboard/register.go b/pkg/registry/apis/dashboard/register.go index d09af5df91c..c8a7b6f9f55 100644 --- a/pkg/registry/apis/dashboard/register.go +++ b/pkg/registry/apis/dashboard/register.go @@ -184,6 +184,7 @@ func (b *DashboardsAPIBuilder) Validate(ctx context.Context, a admission.Attribu func (b *DashboardsAPIBuilder) UpdateAPIGroupInfo(apiGroupInfo *genericapiserver.APIGroupInfo, opts builder.APIGroupOptions) error { storageOpts := apistore.StorageOptions{ + EnableFolderSupport: true, RequireDeprecatedInternalID: true, } @@ -280,11 +281,6 @@ func (b *DashboardsAPIBuilder) storageForVersion( return err } - if b.features.IsEnabledGlobally(featuremgmt.FlagKubernetesRestore) { - storage[dashboards.StoragePath("restore")] = NewRestoreConnector(b.unified, gr) - storage[dashboards.StoragePath("latest")] = NewLatestConnector(b.unified, gr) - } - // Register the DTO endpoint that will consolidate all dashboard bits storage[dashboards.StoragePath("dto")], err = NewDTOConnector( storage[dashboards.StoragePath()].(rest.Getter), diff --git a/pkg/registry/apis/dashboard/restore.go b/pkg/registry/apis/dashboard/restore.go deleted file mode 100644 index 839516cbb31..00000000000 --- a/pkg/registry/apis/dashboard/restore.go +++ /dev/null @@ -1,122 +0,0 @@ -package dashboard - -import ( - "context" - "encoding/json" - "fmt" - "io" - "net/http" - "strconv" - - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apiserver/pkg/registry/rest" - "k8s.io/apiserver/pkg/storage" - - "github.com/grafana/grafana/pkg/services/apiserver/endpoints/request" - "github.com/grafana/grafana/pkg/storage/unified/resource" -) - -type RestoreConnector interface { - rest.Storage - rest.Connecter - rest.StorageMetadata -} - -func NewRestoreConnector(unified resource.ResourceClient, gr schema.GroupResource) RestoreConnector { - return &restoreREST{ - unified: unified, - gr: gr, - } -} - -type restoreREST struct { - unified resource.ResourceClient - gr schema.GroupResource -} - -func (r *restoreREST) New() runtime.Object { - return &metav1.PartialObjectMetadataList{} -} - -func (r *restoreREST) Destroy() { -} - -func (r *restoreREST) ConnectMethods() []string { - return []string{"POST"} -} - -func (r *restoreREST) ProducesMIMETypes(verb string) []string { - return nil -} - -func (r *restoreREST) ProducesObject(verb string) interface{} { - return &metav1.PartialObjectMetadataList{} -} - -func (r *restoreREST) NewConnectOptions() (runtime.Object, bool, string) { - return nil, false, "" -} - -type RestoreOptions struct { - ResourceVersion int64 `json:"resourceVersion"` -} - -func (r *restoreREST) Connect(ctx context.Context, uid string, opts runtime.Object, responder rest.Responder) (http.Handler, error) { - info, err := request.NamespaceInfoFrom(ctx, true) - if err != nil { - return nil, err - } - - key := &resource.ResourceKey{ - Namespace: info.Value, - Group: r.gr.Group, - Resource: r.gr.Resource, - Name: uid, - } - - return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - body, err := io.ReadAll(req.Body) - if err != nil { - responder.Error(fmt.Errorf("unable to read request body: %s", err.Error())) - return - } - reqBody := &RestoreOptions{} - err = json.Unmarshal(body, &reqBody) - if err != nil { - responder.Error(fmt.Errorf("unable to unmarshal request body: %s", err.Error())) - return - } - - if reqBody.ResourceVersion == 0 { - responder.Error(fmt.Errorf("resource version required")) - return - } - - rsp, err := r.unified.Restore(ctx, &resource.RestoreRequest{ - ResourceVersion: reqBody.ResourceVersion, - Key: key, - }) - if err != nil { - responder.Error(err) - return - } else if rsp == nil || (rsp.Error != nil && rsp.Error.Code == http.StatusNotFound) { - responder.Error(storage.NewKeyNotFoundError(uid, reqBody.ResourceVersion)) - return - } else if rsp.Error != nil { - responder.Error(fmt.Errorf("could not re-create object: %s", rsp.Error.Message)) - return - } - - obj := metav1.PartialObjectMetadata{ - ObjectMeta: metav1.ObjectMeta{ - Name: key.Name, - Namespace: key.Namespace, - ResourceVersion: strconv.FormatInt(rsp.ResourceVersion, 10), - }, - } - - responder.Object(http.StatusOK, &obj) - }), nil -} diff --git a/pkg/registry/apis/dashboard/restore_test.go b/pkg/registry/apis/dashboard/restore_test.go deleted file mode 100644 index 26edde534ef..00000000000 --- a/pkg/registry/apis/dashboard/restore_test.go +++ /dev/null @@ -1,126 +0,0 @@ -package dashboard - -import ( - "bytes" - "context" - "fmt" - "net/http" - "net/http/httptest" - "strconv" - "testing" - - "github.com/grafana/grafana/pkg/storage/unified/resource" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" - "google.golang.org/grpc" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apiserver/pkg/endpoints/request" -) - -type mockResourceClient struct { - mock.Mock - resource.ResourceClient -} - -func (m *mockResourceClient) Restore(ctx context.Context, req *resource.RestoreRequest, opts ...grpc.CallOption) (*resource.RestoreResponse, error) { - args := m.Called(ctx, req) - return args.Get(0).(*resource.RestoreResponse), args.Error(1) -} - -func (m *mockResourceClient) Read(ctx context.Context, req *resource.ReadRequest, opts ...grpc.CallOption) (*resource.ReadResponse, error) { - args := m.Called(ctx, req) - return args.Get(0).(*resource.ReadResponse), args.Error(1) -} - -type mockResponder struct { - mock.Mock -} - -func (m *mockResponder) Object(statusCode int, obj runtime.Object) { - m.Called(statusCode, obj) -} - -func (m *mockResponder) Error(err error) { - m.Called(err) -} - -func TestRestore(t *testing.T) { - gr := schema.GroupResource{ - Group: "group", - Resource: "resource", - } - ctx := context.Background() - mockResponder := &mockResponder{} - mockClient := &mockResourceClient{} - r := &restoreREST{ - unified: mockClient, - gr: gr, - } - - t.Run("no namespace in context", func(t *testing.T) { - _, err := r.Connect(ctx, "test-uid", nil, mockResponder) - assert.Error(t, err) - }) - - ctx = request.WithNamespace(context.Background(), "default") - - t.Run("invalid resourceVersion", func(t *testing.T) { - req := httptest.NewRequest("POST", "/restore", bytes.NewReader([]byte(`{"resourceVersion":0}`))) - w := httptest.NewRecorder() - - expectedError := fmt.Errorf("resource version required") - mockResponder.On("Error", mock.MatchedBy(func(err error) bool { - return err.Error() == expectedError.Error() - })) - - handler, err := r.Connect(ctx, "test-uid", nil, mockResponder) - assert.NoError(t, err) - - handler.ServeHTTP(w, req) - mockResponder.AssertExpectations(t) - }) - - t.Run("happy path", func(t *testing.T) { - req := httptest.NewRequest("POST", "/restore", bytes.NewReader([]byte(`{"resourceVersion":123}`))) - w := httptest.NewRecorder() - restoreReq := &resource.RestoreRequest{ - ResourceVersion: 123, - Key: &resource.ResourceKey{ - Namespace: "default", - Group: "group", - Resource: "resource", - Name: "uid", - }, - } - - expectedObject := &metav1.PartialObjectMetadata{ - ObjectMeta: metav1.ObjectMeta{ - Name: "uid", - Namespace: "default", - ResourceVersion: strconv.FormatInt(123, 10), - }, - } - - mockClient.On("Restore", ctx, restoreReq).Return(&resource.RestoreResponse{ - ResourceVersion: 123, - }, nil).Once() - - mockResponder.On("Object", http.StatusOK, mock.MatchedBy(func(obj interface{}) bool { - metadata, ok := obj.(*metav1.PartialObjectMetadata) - return ok && - metadata.ObjectMeta.Name == "uid" && - metadata.ObjectMeta.Namespace == "default" && - metadata.ObjectMeta.ResourceVersion == "123" - })).Return(expectedObject) - - handler, err := r.Connect(ctx, "uid", nil, mockResponder) - assert.NoError(t, err) - handler.ServeHTTP(w, req) - assert.Equal(t, http.StatusOK, w.Code) - - mockClient.AssertExpectations(t) - mockResponder.AssertExpectations(t) - }) -} diff --git a/pkg/registry/apis/dashboard/search.go b/pkg/registry/apis/dashboard/search.go index 33162ccabb6..86967a9f5d3 100644 --- a/pkg/registry/apis/dashboard/search.go +++ b/pkg/registry/apis/dashboard/search.go @@ -407,6 +407,7 @@ func asResourceKey(ns string, k string) (*resource.ResourceKey, error) { func (s *SearchHandler) getDashboardsUIDsSharedWithUser(ctx context.Context, user identity.Requester) ([]string, error) { if !s.features.IsEnabledGlobally(featuremgmt.FlagUnifiedStorageSearchPermissionFiltering) { + s.log.Warn("Tried to search for 'sharedwithme' dashboards with ", featuremgmt.FlagUnifiedStorageSearchPermissionFiltering, " disabled") return []string{}, nil } @@ -459,7 +460,7 @@ func (s *SearchHandler) getDashboardsUIDsSharedWithUser(ctx context.Context, use } if folderUidIdx == -1 { - return sharedDashboards, fmt.Errorf("Error retrieving folder information") + return sharedDashboards, fmt.Errorf("error retrieving folder information") } // populate list of unique folder UIDs in the list of dashboards user has read permissions diff --git a/pkg/registry/apis/dashboard/search_test.go b/pkg/registry/apis/dashboard/search_test.go index e393aabea62..90411b3cbbf 100644 --- a/pkg/registry/apis/dashboard/search_test.go +++ b/pkg/registry/apis/dashboard/search_test.go @@ -692,9 +692,6 @@ func (m *MockClient) Update(ctx context.Context, in *resource.UpdateRequest, opt func (m *MockClient) Read(ctx context.Context, in *resource.ReadRequest, opts ...grpc.CallOption) (*resource.ReadResponse, error) { return nil, nil } -func (m *MockClient) Restore(ctx context.Context, in *resource.RestoreRequest, opts ...grpc.CallOption) (*resource.RestoreResponse, error) { - return nil, nil -} func (m *MockClient) GetBlob(ctx context.Context, in *resource.GetBlobRequest, opts ...grpc.CallOption) (*resource.GetBlobResponse, error) { return nil, nil } diff --git a/pkg/registry/apis/dashboard/sub_dto.go b/pkg/registry/apis/dashboard/sub_dto.go index 5768ce906d5..32f0b787ee8 100644 --- a/pkg/registry/apis/dashboard/sub_dto.go +++ b/pkg/registry/apis/dashboard/sub_dto.go @@ -19,7 +19,6 @@ import ( "github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/apiserver/endpoints/request" "github.com/grafana/grafana/pkg/services/dashboards" - "github.com/grafana/grafana/pkg/services/guardian" "github.com/grafana/grafana/pkg/storage/unified/apistore" "github.com/grafana/grafana/pkg/storage/unified/resource" ) @@ -87,7 +86,7 @@ func (r *DTOConnector) ProducesObject(verb string) interface{} { } func (r *DTOConnector) Connect(ctx context.Context, name string, opts runtime.Object, responder rest.Responder) (http.Handler, error) { - info, err := request.NamespaceInfoFrom(ctx, true) + _, err := request.NamespaceInfoFrom(ctx, true) if err != nil { return nil, err } @@ -128,33 +127,22 @@ func (r *DTOConnector) Connect(ctx context.Context, name string, opts runtime.Ob return } - // Calculate access information -- needed to help smooth transition from /api/dashboard format - dto := &dashboards.Dashboard{ - UID: name, - OrgID: info.OrgID, - ID: obj.GetDeprecatedInternalID(), // nolint:staticcheck - } - manager, ok := obj.GetManagerProperties() - if ok && manager.Kind == utils.ManagerKindPlugin { - dto.PluginID = manager.Identity - } - - guardian, err := guardian.NewByDashboard(ctx, dto, info.OrgID, user) - if err != nil { - responder.Error(err) - return - } - canView, err := guardian.CanView() + dashScope := dashboards.ScopeDashboardsProvider.GetResourceScopeUID(name) + evaluator := accesscontrol.EvalPermission(dashboards.ActionDashboardsRead, dashScope) + canView, err := r.accessControl.Evaluate(ctx, user, evaluator) if err != nil || !canView { responder.Error(fmt.Errorf("not allowed to view")) return } access := &dashboard.DashboardAccess{} - access.CanEdit, _ = guardian.CanEdit() - access.CanSave, _ = guardian.CanSave() - access.CanAdmin, _ = guardian.CanAdmin() - access.CanDelete, _ = guardian.CanDelete() + writeEvaluator := accesscontrol.EvalPermission(dashboards.ActionDashboardsWrite, dashScope) + access.CanSave, _ = r.accessControl.Evaluate(ctx, user, writeEvaluator) + access.CanEdit = access.CanSave + adminEvaluator := accesscontrol.EvalPermission(dashboards.ActionDashboardsPermissionsWrite, dashScope) + access.CanAdmin, _ = r.accessControl.Evaluate(ctx, user, adminEvaluator) + deleteEvaluator := accesscontrol.EvalPermission(dashboards.ActionDashboardsDelete, dashScope) + access.CanDelete, _ = r.accessControl.Evaluate(ctx, user, deleteEvaluator) access.CanStar = user.IsIdentityType(claims.TypeUser) access.AnnotationsPermissions = &dashboard.AnnotationPermission{} diff --git a/pkg/registry/apis/folders/register.go b/pkg/registry/apis/folders/register.go index ca3d492815d..df8b2431f2f 100644 --- a/pkg/registry/apis/folders/register.go +++ b/pkg/registry/apis/folders/register.go @@ -6,7 +6,6 @@ import ( "fmt" "strings" - authtypes "github.com/grafana/authlib/types" "github.com/prometheus/client_golang/prometheus" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" @@ -18,6 +17,7 @@ import ( common "k8s.io/kube-openapi/pkg/common" "k8s.io/kube-openapi/pkg/spec3" + authtypes "github.com/grafana/authlib/types" "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/apimachinery/utils" "github.com/grafana/grafana/pkg/apis/folder/v0alpha1" @@ -156,6 +156,7 @@ func (b *FolderAPIBuilder) UpdateAPIGroupInfo(apiGroupInfo *genericapiserver.API } opts.StorageOptions(resourceInfo.GroupResource(), apistore.StorageOptions{ + EnableFolderSupport: true, RequireDeprecatedInternalID: true}) folderStore := &folderStorage{ diff --git a/pkg/registry/apis/secret/contracts/encryption.go b/pkg/registry/apis/secret/contracts/encryption.go new file mode 100644 index 00000000000..164143ce423 --- /dev/null +++ b/pkg/registry/apis/secret/contracts/encryption.go @@ -0,0 +1,49 @@ +package contracts + +import "context" + +// EncryptionManager is an envelope encryption service in charge of encrypting/decrypting secrets. +type EncryptionManager interface { + // Encrypt MUST NOT be used within database transactions, it may cause database locks. + // For those specific use cases where the encryption operation cannot be moved outside + // the database transaction, look at database-specific methods present at the specific + // implementation present at manager.EncryptionService. + Encrypt(ctx context.Context, namespace string, payload []byte, opt EncryptionOptions) ([]byte, error) + Decrypt(ctx context.Context, namespace string, payload []byte) ([]byte, error) + + RotateDataKeys(ctx context.Context, namespace string) error + ReEncryptDataKeys(ctx context.Context, namespace string) error +} + +type EncryptionOptions func() string + +// EncryptWithoutScope uses a root level data key for encryption (DEK), +// in other words this DEK is not bound to any specific scope (not attached to any user, org, etc.). +func EncryptWithoutScope() EncryptionOptions { + return func() string { + return "root" + } +} + +// EncryptWithScope uses a data key for encryption bound to some specific scope (i.e., user, org, etc.). +// Scope should look like "user:10", "org:1". +func EncryptWithScope(scope string) EncryptionOptions { + return func() string { + return scope + } +} + +type EncryptedValue struct { + UID string + Namespace string + EncryptedData []byte + Created int64 + Updated int64 +} + +type EncryptedValueStorage interface { + Create(ctx context.Context, namespace string, encryptedData []byte) (*EncryptedValue, error) + Update(ctx context.Context, namespace string, uid string, encryptedData []byte) error + Get(ctx context.Context, namespace string, uid string) (*EncryptedValue, error) + Delete(ctx context.Context, namespace string, uid string) error +} diff --git a/pkg/registry/apis/secret/contracts/keeper.go b/pkg/registry/apis/secret/contracts/keeper.go index edd996ed4e9..2ba2cfb05dd 100644 --- a/pkg/registry/apis/secret/contracts/keeper.go +++ b/pkg/registry/apis/secret/contracts/keeper.go @@ -21,3 +21,35 @@ type KeeperMetadataStorage interface { Delete(ctx context.Context, namespace xkube.Namespace, name string) error List(ctx context.Context, namespace xkube.Namespace, options *internalversion.ListOptions) (*secretv0alpha1.KeeperList, error) } + +// KeeperType represents the type of a Keeper. +type KeeperType string + +const ( + SQLKeeperType KeeperType = "sql" + AWSKeeperType KeeperType = "aws" + AzureKeeperType KeeperType = "azure" + GCPKeeperType KeeperType = "gcp" + HashiCorpKeeperType KeeperType = "hashicorp" +) + +// ExternalID represents either the secure value's GUID or ref (in case of external secret references). +// This is saved in the secure_value metadata storage as `external_id`. +// TODO: this does not belong in the k8s spec, but it is used by us internally. Place it somewhere appropriate. +type ExternalID string + +func (s ExternalID) String() string { + return string(s) +} + +// Keeper is the interface for secret keepers. +type Keeper interface { + Store(ctx context.Context, cfg secretv0alpha1.KeeperConfig, namespace string, exposedValueOrRef string) (ExternalID, error) + Update(ctx context.Context, cfg secretv0alpha1.KeeperConfig, namespace string, externalID ExternalID, exposedValueOrRef string) error + Expose(ctx context.Context, cfg secretv0alpha1.KeeperConfig, namespace string, externalID ExternalID) (secretv0alpha1.ExposedSecureValue, error) + Delete(ctx context.Context, cfg secretv0alpha1.KeeperConfig, namespace string, externalID ExternalID) error +} + +const ( + DefaultSQLKeeper = "kp-default-sql" +) diff --git a/pkg/registry/apis/secret/secretkeeper/secretkeeper.go b/pkg/registry/apis/secret/secretkeeper/secretkeeper.go new file mode 100644 index 00000000000..daf150d1543 --- /dev/null +++ b/pkg/registry/apis/secret/secretkeeper/secretkeeper.go @@ -0,0 +1,45 @@ +package secretkeeper + +import ( + "fmt" + + "github.com/grafana/grafana/pkg/infra/tracing" + "github.com/grafana/grafana/pkg/registry/apis/secret/contracts" + "github.com/grafana/grafana/pkg/registry/apis/secret/secretkeeper/sqlkeeper" +) + +// Service is the interface for secret keeper services. +// This exists because OSS and Enterprise have different amounts of keepers available. +type Service interface { + GetKeepers() (map[contracts.KeeperType]contracts.Keeper, error) +} + +// OSSKeeperService is the OSS implementation of the Service interface. +type OSSKeeperService struct { + tracer tracing.Tracer + encryptionManager contracts.EncryptionManager + store contracts.EncryptedValueStorage +} + +func ProvideService( + tracer tracing.Tracer, + store contracts.EncryptedValueStorage, + encryptionManager contracts.EncryptionManager, +) (OSSKeeperService, error) { + return OSSKeeperService{ + tracer: tracer, + encryptionManager: encryptionManager, + store: store, + }, nil +} + +func (ks OSSKeeperService) GetKeepers() (map[contracts.KeeperType]contracts.Keeper, error) { + sqlKeeper, err := sqlkeeper.NewSQLKeeper(ks.tracer, ks.encryptionManager, ks.store) + if err != nil { + return nil, fmt.Errorf("failed to create sql keeper: %w", err) + } + + return map[contracts.KeeperType]contracts.Keeper{ + contracts.SQLKeeperType: sqlKeeper, + }, nil +} diff --git a/pkg/registry/apis/secret/secretkeeper/secretkeeper_test.go b/pkg/registry/apis/secret/secretkeeper/secretkeeper_test.go new file mode 100644 index 00000000000..8a89eefc226 --- /dev/null +++ b/pkg/registry/apis/secret/secretkeeper/secretkeeper_test.go @@ -0,0 +1,35 @@ +package secretkeeper + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/grafana/grafana/pkg/infra/tracing" + "github.com/grafana/grafana/pkg/registry/apis/secret/contracts" + "github.com/grafana/grafana/pkg/registry/apis/secret/secretkeeper/sqlkeeper" + "github.com/grafana/grafana/pkg/setting" +) + +func Test_OSSKeeperService_GetKeepers(t *testing.T) { + cfg := setting.NewCfg() + keeperService, err := setupTestService(t, cfg) + require.NoError(t, err) + + t.Run("GetKeepers should return a map with a sql keeper", func(t *testing.T) { + keeperMap, err := keeperService.GetKeepers() + require.NoError(t, err) + + assert.NotNil(t, keeperMap) + assert.Equal(t, 1, len(keeperMap)) + assert.IsType(t, &sqlkeeper.SQLKeeper{}, keeperMap[contracts.SQLKeeperType]) + }) +} + +func setupTestService(t *testing.T, cfg *setting.Cfg) (OSSKeeperService, error) { + // Initialize the keeper service + keeperService, err := ProvideService(tracing.InitializeTracerForTest(), nil, nil) + + return keeperService, err +} diff --git a/pkg/registry/apis/secret/secretkeeper/sqlkeeper/keeper.go b/pkg/registry/apis/secret/secretkeeper/sqlkeeper/keeper.go new file mode 100644 index 00000000000..7e35a2f6aa0 --- /dev/null +++ b/pkg/registry/apis/secret/secretkeeper/sqlkeeper/keeper.go @@ -0,0 +1,92 @@ +package sqlkeeper + +import ( + "context" + "fmt" + + secretv0alpha1 "github.com/grafana/grafana/pkg/apis/secret/v0alpha1" + "github.com/grafana/grafana/pkg/infra/tracing" + "github.com/grafana/grafana/pkg/registry/apis/secret/contracts" +) + +type SQLKeeper struct { + tracer tracing.Tracer + encryptionManager contracts.EncryptionManager + store contracts.EncryptedValueStorage +} + +var _ contracts.Keeper = (*SQLKeeper)(nil) + +func NewSQLKeeper( + tracer tracing.Tracer, + encryptionManager contracts.EncryptionManager, + store contracts.EncryptedValueStorage, +) (*SQLKeeper, error) { + return &SQLKeeper{ + tracer: tracer, + encryptionManager: encryptionManager, + store: store, + }, nil +} + +func (s *SQLKeeper) Store(ctx context.Context, cfg secretv0alpha1.KeeperConfig, namespace string, exposedValueOrRef string) (contracts.ExternalID, error) { + ctx, span := s.tracer.Start(ctx, "sqlKeeper.Store") + defer span.End() + + encryptedData, err := s.encryptionManager.Encrypt(ctx, namespace, []byte(exposedValueOrRef), contracts.EncryptWithoutScope()) + if err != nil { + return "", fmt.Errorf("unable to encrypt value: %w", err) + } + + encryptedVal, err := s.store.Create(ctx, namespace, encryptedData) + if err != nil { + return "", fmt.Errorf("unable to store encrypted value: %w", err) + } + + return contracts.ExternalID(encryptedVal.UID), nil +} + +func (s *SQLKeeper) Expose(ctx context.Context, cfg secretv0alpha1.KeeperConfig, namespace string, externalID contracts.ExternalID) (secretv0alpha1.ExposedSecureValue, error) { + ctx, span := s.tracer.Start(ctx, "sqlKeeper.Expose") + defer span.End() + + encryptedValue, err := s.store.Get(ctx, namespace, externalID.String()) + if err != nil { + return "", fmt.Errorf("unable to get encrypted value: %w", err) + } + + exposedBytes, err := s.encryptionManager.Decrypt(ctx, namespace, encryptedValue.EncryptedData) + if err != nil { + return "", fmt.Errorf("unable to decrypt value: %w", err) + } + + exposedValue := secretv0alpha1.NewExposedSecureValue(string(exposedBytes)) + return exposedValue, nil +} + +func (s *SQLKeeper) Delete(ctx context.Context, cfg secretv0alpha1.KeeperConfig, namespace string, externalID contracts.ExternalID) error { + ctx, span := s.tracer.Start(ctx, "sqlKeeper.Delete") + defer span.End() + + err := s.store.Delete(ctx, namespace, externalID.String()) + if err != nil { + return fmt.Errorf("failed to delete encrypted value: %w", err) + } + return nil +} + +func (s *SQLKeeper) Update(ctx context.Context, cfg secretv0alpha1.KeeperConfig, namespace string, externalID contracts.ExternalID, exposedValueOrRef string) error { + ctx, span := s.tracer.Start(ctx, "sqlKeeper.Update") + defer span.End() + + encryptedData, err := s.encryptionManager.Encrypt(ctx, namespace, []byte(exposedValueOrRef), contracts.EncryptWithoutScope()) + if err != nil { + return fmt.Errorf("unable to encrypt value: %w", err) + } + + err = s.store.Update(ctx, namespace, externalID.String(), encryptedData) + if err != nil { + return fmt.Errorf("failed to update encrypted value: %w", err) + } + return nil +} diff --git a/pkg/registry/apis/secret/secretkeeper/sqlkeeper/keeper_test.go b/pkg/registry/apis/secret/secretkeeper/sqlkeeper/keeper_test.go new file mode 100644 index 00000000000..a304c6cdc4c --- /dev/null +++ b/pkg/registry/apis/secret/secretkeeper/sqlkeeper/keeper_test.go @@ -0,0 +1,237 @@ +package sqlkeeper + +import ( + "context" + "encoding/base64" + "fmt" + "sync" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/grafana/grafana/pkg/infra/tracing" + "github.com/grafana/grafana/pkg/registry/apis/secret/contracts" + "github.com/grafana/grafana/pkg/setting" +) + +// Make this a `TestIntegration` once we have the real storage implementation +func Test_SQLKeeperSetup(t *testing.T) { + ctx := context.Background() + namespace1 := "namespace1" + namespace2 := "namespace2" + plaintext1 := "very secret string in namespace 1" + plaintext2 := "very secret string in namespace 2" + nonExistentID := contracts.ExternalID("non existent") + + cfg := setting.NewCfg() + + sqlKeeper, err := setupTestService(t, cfg) + require.NoError(t, err) + require.NotNil(t, sqlKeeper) + + t.Run("storing an encrypted value returns no error", func(t *testing.T) { + externalId1, err := sqlKeeper.Store(ctx, nil, namespace1, plaintext1) + require.NoError(t, err) + require.NotEmpty(t, externalId1) + + externalId2, err := sqlKeeper.Store(ctx, nil, namespace2, plaintext2) + require.NoError(t, err) + require.NotEmpty(t, externalId2) + + t.Run("expose the encrypted value from existing namespace", func(t *testing.T) { + exposedVal1, err := sqlKeeper.Expose(ctx, nil, namespace1, externalId1) + require.NoError(t, err) + require.NotNil(t, exposedVal1) + assert.Equal(t, plaintext1, exposedVal1.DangerouslyExposeAndConsumeValue()) + + exposedVal2, err := sqlKeeper.Expose(ctx, nil, namespace2, externalId2) + require.NoError(t, err) + require.NotNil(t, exposedVal2) + assert.Equal(t, plaintext2, exposedVal2.DangerouslyExposeAndConsumeValue()) + }) + + t.Run("expose encrypted value from different namespace returns error", func(t *testing.T) { + exposedVal, err := sqlKeeper.Expose(ctx, nil, namespace2, externalId1) + require.Error(t, err) + assert.Empty(t, exposedVal) + + exposedVal, err = sqlKeeper.Expose(ctx, nil, namespace1, externalId2) + require.Error(t, err) + assert.Empty(t, exposedVal) + }) + }) + + t.Run("storing same value in same namespace returns no error", func(t *testing.T) { + externalId1, err := sqlKeeper.Store(ctx, nil, namespace1, plaintext1) + require.NoError(t, err) + require.NotEmpty(t, externalId1) + + externalId2, err := sqlKeeper.Store(ctx, nil, namespace1, plaintext1) + require.NoError(t, err) + require.NotEmpty(t, externalId2) + + assert.NotEqual(t, externalId1, externalId2) + }) + + t.Run("storing same value in different namespace returns no error", func(t *testing.T) { + externalId1, err := sqlKeeper.Store(ctx, nil, namespace1, plaintext1) + require.NoError(t, err) + require.NotEmpty(t, externalId1) + + externalId2, err := sqlKeeper.Store(ctx, nil, namespace2, plaintext1) + require.NoError(t, err) + require.NotEmpty(t, externalId2) + + assert.NotEqual(t, externalId1, externalId2) + }) + + t.Run("exposing non existing values returns error", func(t *testing.T) { + exposedVal, err := sqlKeeper.Expose(ctx, nil, namespace1, nonExistentID) + require.Error(t, err) + assert.Empty(t, exposedVal) + }) + + t.Run("deleting an existing encrypted value does not return error", func(t *testing.T) { + externalID, err := sqlKeeper.Store(ctx, nil, namespace1, plaintext1) + require.NoError(t, err) + require.NotEmpty(t, externalID) + + exposedVal, err := sqlKeeper.Expose(ctx, nil, namespace1, externalID) + require.NoError(t, err) + assert.NotNil(t, exposedVal) + assert.Equal(t, plaintext1, exposedVal.DangerouslyExposeAndConsumeValue()) + + err = sqlKeeper.Delete(ctx, nil, namespace1, externalID) + require.NoError(t, err) + }) + + t.Run("deleting an non existing encrypted value does not return error", func(t *testing.T) { + err = sqlKeeper.Delete(ctx, nil, namespace1, nonExistentID) + require.NoError(t, err) + }) + + t.Run("updating an existent encrypted value returns no error", func(t *testing.T) { + externalId1, err := sqlKeeper.Store(ctx, nil, namespace1, plaintext1) + require.NoError(t, err) + require.NotEmpty(t, externalId1) + + err = sqlKeeper.Update(ctx, nil, namespace1, externalId1, plaintext2) + require.NoError(t, err) + + exposedVal, err := sqlKeeper.Expose(ctx, nil, namespace1, externalId1) + require.NoError(t, err) + assert.NotNil(t, exposedVal) + assert.Equal(t, plaintext2, exposedVal.DangerouslyExposeAndConsumeValue()) + }) + + t.Run("updating a non existent encrypted value returns error", func(t *testing.T) { + externalId1, err := sqlKeeper.Store(ctx, nil, namespace1, plaintext1) + require.NoError(t, err) + require.NotEmpty(t, externalId1) + + err = sqlKeeper.Update(ctx, nil, namespace1, nonExistentID, plaintext2) + require.Error(t, err) + }) +} + +func setupTestService(t *testing.T, cfg *setting.Cfg) (*SQLKeeper, error) { + // Initialize the encryption manager with in-memory implementation + encMgr := &inMemoryEncryptionManager{} + + // Initialize encrypted value storage with in-memory implementation + encValueStore := newInMemoryEncryptedValueStorage() + + // Initialize the SQLKeeper + sqlKeeper, err := NewSQLKeeper(tracing.InitializeTracerForTest(), encMgr, encValueStore) + + return sqlKeeper, err +} + +// While we don't have the real implementation, use an in-memory one +type inMemoryEncryptionManager struct{} + +func (m *inMemoryEncryptionManager) Encrypt(_ context.Context, _ string, value []byte, _ contracts.EncryptionOptions) ([]byte, error) { + return []byte(base64.StdEncoding.EncodeToString(value)), nil +} + +func (m *inMemoryEncryptionManager) Decrypt(_ context.Context, _ string, value []byte) ([]byte, error) { + return base64.StdEncoding.DecodeString(string(value)) +} + +func (m *inMemoryEncryptionManager) ReEncryptDataKeys(_ context.Context, _ string) error { + return nil +} + +func (m *inMemoryEncryptionManager) RotateDataKeys(_ context.Context, _ string) error { + return nil +} + +// While we don't have the real implementation, use an in-memory one +type inMemoryEncryptedValueStorage struct { + mu sync.RWMutex + store map[string]*contracts.EncryptedValue +} + +func newInMemoryEncryptedValueStorage() *inMemoryEncryptedValueStorage { + return &inMemoryEncryptedValueStorage{ + store: make(map[string]*contracts.EncryptedValue), + } +} + +func (m *inMemoryEncryptedValueStorage) Create(_ context.Context, namespace string, encryptedData []byte) (*contracts.EncryptedValue, error) { + m.mu.Lock() + defer m.mu.Unlock() + + uid := fmt.Sprintf("%d", len(m.store)+1) // Generate simple incremental IDs + encValue := &contracts.EncryptedValue{ + UID: uid, + Namespace: namespace, + EncryptedData: encryptedData, + Created: 1, // Dummy timestamp + Updated: 1, // Dummy timestamp + } + + compositeKey := namespace + ":" + uid + m.store[compositeKey] = encValue + + return encValue, nil +} + +func (m *inMemoryEncryptedValueStorage) Get(_ context.Context, namespace string, uid string) (*contracts.EncryptedValue, error) { + m.mu.RLock() + defer m.mu.RUnlock() + + compositeKey := namespace + ":" + uid + encValue, exists := m.store[compositeKey] + if !exists { + return nil, fmt.Errorf("value not found for namespace %s and uid %s", namespace, uid) + } + + return encValue, nil +} + +func (m *inMemoryEncryptedValueStorage) Delete(_ context.Context, namespace string, uid string) error { + m.mu.Lock() + defer m.mu.Unlock() + + compositeKey := namespace + ":" + uid + delete(m.store, compositeKey) + + return nil +} + +func (m *inMemoryEncryptedValueStorage) Update(_ context.Context, namespace string, uid string, encryptedData []byte) error { + m.mu.Lock() + defer m.mu.Unlock() + + compositeKey := namespace + ":" + uid + encValue, exists := m.store[compositeKey] + if !exists { + return fmt.Errorf("value not found for namespace %s and uid %s", namespace, uid) + } + + encValue.EncryptedData = encryptedData + encValue.Updated = 2 // Update timestamp + return nil +} diff --git a/pkg/registry/backgroundsvcs/background_services.go b/pkg/registry/backgroundsvcs/background_services.go index b0f339ed29d..621c2eee751 100644 --- a/pkg/registry/backgroundsvcs/background_services.go +++ b/pkg/registry/backgroundsvcs/background_services.go @@ -17,6 +17,7 @@ import ( "github.com/grafana/grafana/pkg/services/authn/authnimpl" "github.com/grafana/grafana/pkg/services/cleanup" "github.com/grafana/grafana/pkg/services/cloudmigration" + "github.com/grafana/grafana/pkg/services/dashboards/service" "github.com/grafana/grafana/pkg/services/dashboardsnapshots" "github.com/grafana/grafana/pkg/services/grpcserver" "github.com/grafana/grafana/pkg/services/guardian" @@ -69,6 +70,7 @@ func ProvideBackgroundServiceRegistry( zanzanaReconciler *dualwrite.ZanzanaReconciler, appRegistry *appregistry.Service, pluginDashboardUpdater *plugindashboardsservice.DashboardUpdater, + dashboardServiceImpl *service.DashboardServiceImpl, // Need to make sure these are initialized, is there a better place to put them? _ dashboardsnapshots.Service, _ serviceaccounts.Service, _ *guardian.Provider, @@ -115,6 +117,7 @@ func ProvideBackgroundServiceRegistry( zanzanaReconciler, appRegistry, pluginDashboardUpdater, + dashboardServiceImpl, ) } diff --git a/pkg/services/annotations/accesscontrol/accesscontrol_test.go b/pkg/services/annotations/accesscontrol/accesscontrol_test.go index 3dc79fe876f..c92e1b9546b 100644 --- a/pkg/services/annotations/accesscontrol/accesscontrol_test.go +++ b/pkg/services/annotations/accesscontrol/accesscontrol_test.go @@ -10,9 +10,11 @@ import ( "github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/infra/db" + "github.com/grafana/grafana/pkg/infra/kvstore" + "github.com/grafana/grafana/pkg/infra/serverlock" "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/services/accesscontrol" - "github.com/grafana/grafana/pkg/services/accesscontrol/acimpl" + "github.com/grafana/grafana/pkg/services/accesscontrol/actest" accesscontrolmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock" "github.com/grafana/grafana/pkg/services/annotations" "github.com/grafana/grafana/pkg/services/annotations/testutil" @@ -22,7 +24,6 @@ import ( dashboardsservice "github.com/grafana/grafana/pkg/services/dashboards/service" "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/folder/folderimpl" - "github.com/grafana/grafana/pkg/services/guardian" "github.com/grafana/grafana/pkg/services/quota/quotatest" "github.com/grafana/grafana/pkg/services/search/sort" "github.com/grafana/grafana/pkg/services/supportbundles/supportbundlestest" @@ -42,19 +43,18 @@ func TestIntegrationAuthorize(t *testing.T) { } sql, cfg := db.InitTestDBWithCfg(t) - origNewDashboardGuardian := guardian.New - defer func() { guardian.New = origNewDashboardGuardian }() - guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{CanSaveValue: true}) folderStore := folderimpl.ProvideDashboardFolderStore(sql) fStore := folderimpl.ProvideStore(sql) dashStore, err := database.ProvideDashboardStore(sql, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sql)) require.NoError(t, err) - ac := acimpl.ProvideAccessControl(featuremgmt.WithFeatures()) + ac := actest.FakeAccessControl{ExpectedEvaluate: true} folderSvc := folderimpl.ProvideService( - fStore, accesscontrolmock.New(), bus.ProvideBus(tracing.InitializeTracerForTest()), dashStore, folderStore, + fStore, ac, bus.ProvideBus(tracing.InitializeTracerForTest()), dashStore, folderStore, nil, sql, featuremgmt.WithFeatures(), supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil, dualwrite.ProvideTestService(), sort.ProvideService()) dashSvc, err := dashboardsservice.ProvideDashboardServiceImpl(cfg, dashStore, folderStore, featuremgmt.WithFeatures(), accesscontrolmock.NewMockedPermissionsService(), - ac, folderSvc, fStore, nil, client.MockTestRestConfig{}, nil, quotatest.New(false, nil), nil, nil, nil, dualwrite.ProvideTestService(), sort.ProvideService()) + ac, folderSvc, fStore, nil, client.MockTestRestConfig{}, nil, quotatest.New(false, nil), nil, nil, nil, dualwrite.ProvideTestService(), sort.ProvideService(), + serverlock.ProvideService(sql, tracing.InitializeTracerForTest()), + kvstore.NewFakeKVStore()) require.NoError(t, err) dashSvc.RegisterDashboardPermissions(accesscontrolmock.NewMockedPermissionsService()) diff --git a/pkg/services/annotations/annotationsimpl/annotations_test.go b/pkg/services/annotations/annotationsimpl/annotations_test.go index 0615023b430..8e169e88401 100644 --- a/pkg/services/annotations/annotationsimpl/annotations_test.go +++ b/pkg/services/annotations/annotationsimpl/annotations_test.go @@ -12,10 +12,12 @@ import ( "github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/infra/db" + "github.com/grafana/grafana/pkg/infra/kvstore" "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/infra/serverlock" "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/services/accesscontrol" - "github.com/grafana/grafana/pkg/services/accesscontrol/acimpl" + "github.com/grafana/grafana/pkg/services/accesscontrol/actest" accesscontrolmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock" "github.com/grafana/grafana/pkg/services/annotations" "github.com/grafana/grafana/pkg/services/annotations/testutil" @@ -54,19 +56,18 @@ func TestIntegrationAnnotationListingWithRBAC(t *testing.T) { features := featuremgmt.WithFeatures() tagService := tagimpl.ProvideService(sql) ruleStore := alertingStore.SetupStoreForTesting(t, sql) - origNewDashboardGuardian := guardian.New - defer func() { guardian.New = origNewDashboardGuardian }() - guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{}) folderStore := folderimpl.ProvideDashboardFolderStore(sql) fStore := folderimpl.ProvideStore(sql) dashStore, err := database.ProvideDashboardStore(sql, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sql)) require.NoError(t, err) - ac := acimpl.ProvideAccessControl(featuremgmt.WithFeatures()) + ac := actest.FakeAccessControl{ExpectedEvaluate: true} folderSvc := folderimpl.ProvideService( - fStore, accesscontrolmock.New(), bus.ProvideBus(tracing.InitializeTracerForTest()), dashStore, folderStore, + fStore, ac, bus.ProvideBus(tracing.InitializeTracerForTest()), dashStore, folderStore, nil, sql, featuremgmt.WithFeatures(), supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil, dualwrite.ProvideTestService(), sort.ProvideService()) dashSvc, err := dashboardsservice.ProvideDashboardServiceImpl(cfg, dashStore, folderStore, featuremgmt.WithFeatures(), accesscontrolmock.NewMockedPermissionsService(), - ac, folderSvc, fStore, nil, client.MockTestRestConfig{}, nil, quotatest.New(false, nil), nil, nil, nil, dualwrite.ProvideTestService(), sort.ProvideService()) + ac, folderSvc, fStore, nil, client.MockTestRestConfig{}, nil, quotatest.New(false, nil), nil, nil, nil, dualwrite.ProvideTestService(), sort.ProvideService(), + serverlock.ProvideService(sql, tracing.InitializeTracerForTest()), + kvstore.NewFakeKVStore()) require.NoError(t, err) dashSvc.RegisterDashboardPermissions(accesscontrolmock.NewMockedPermissionsService()) repo := ProvideService(sql, cfg, features, tagService, tracing.InitializeTracerForTest(), ruleStore, dashSvc) @@ -242,14 +243,17 @@ func TestIntegrationAnnotationListingWithInheritedRBAC(t *testing.T) { guardian.New = origNewGuardian }) - ac := acimpl.ProvideAccessControl(features) + ac := actest.FakeAccessControl{ExpectedEvaluate: true} fStore := folderimpl.ProvideStore(sql) folderStore := folderimpl.ProvideDashboardFolderStore(sql) folderSvc := folderimpl.ProvideService( fStore, ac, bus.ProvideBus(tracing.InitializeTracerForTest()), dashStore, folderStore, nil, sql, features, supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil, dualwrite.ProvideTestService(), sort.ProvideService()) dashSvc, err := dashboardsservice.ProvideDashboardServiceImpl(cfg, dashStore, folderStore, features, accesscontrolmock.NewMockedPermissionsService(), - ac, folderSvc, fStore, nil, client.MockTestRestConfig{}, nil, quotatest.New(false, nil), nil, nil, nil, dualwrite.ProvideTestService(), sort.ProvideService()) + ac, folderSvc, fStore, nil, client.MockTestRestConfig{}, nil, quotatest.New(false, nil), nil, nil, nil, dualwrite.ProvideTestService(), sort.ProvideService(), + serverlock.ProvideService(sql, tracing.InitializeTracerForTest()), + kvstore.NewFakeKVStore(), + ) require.NoError(t, err) dashSvc.RegisterDashboardPermissions(accesscontrolmock.NewMockedPermissionsService()) cfg.AnnotationMaximumTagsLength = 60 diff --git a/pkg/services/annotations/annotationsimpl/cleanup_test.go b/pkg/services/annotations/annotationsimpl/cleanup_test.go index 278bb48e365..324699736b1 100644 --- a/pkg/services/annotations/annotationsimpl/cleanup_test.go +++ b/pkg/services/annotations/annotationsimpl/cleanup_test.go @@ -119,8 +119,8 @@ func TestIntegrationAnnotationCleanUp(t *testing.T) { t.Cleanup(func() { err := fakeSQL.WithDbSession(context.Background(), func(session *db.Session) error { - _, deleteAnnotationErr := session.Exec("DELETE FROM annotation") - _, deleteAnnotationTagErr := session.Exec("DELETE FROM annotation_tag") + _, deleteAnnotationErr := session.Exec("DELETE FROM annotation WHERE true") + _, deleteAnnotationTagErr := session.Exec("DELETE FROM annotation_tag WHERE true") return errors.Join(deleteAnnotationErr, deleteAnnotationTagErr) }) assert.NoError(t, err) @@ -157,7 +157,7 @@ func TestIntegrationOldAnnotationsAreDeletedFirst(t *testing.T) { t.Cleanup(func() { err := fakeSQL.WithDbSession(context.Background(), func(session *db.Session) error { - _, err := session.Exec("DELETE FROM annotation") + _, err := session.Exec("DELETE FROM annotation WHERE true") return err }) assert.NoError(t, err) diff --git a/pkg/services/annotations/annotationsimpl/xorm_store.go b/pkg/services/annotations/annotationsimpl/xorm_store.go index 7b957189312..8bfa69e80cd 100644 --- a/pkg/services/annotations/annotationsimpl/xorm_store.go +++ b/pkg/services/annotations/annotationsimpl/xorm_store.go @@ -333,10 +333,11 @@ func (r *xormRepositoryImpl) Get(ctx context.Context, query annotations.ItemQuer } if len(tags) > 0 { + // "at" is a keyword in Spanner and needs to be quoted. tagsSubQuery := fmt.Sprintf(` - SELECT SUM(1) FROM annotation_tag at - INNER JOIN tag on tag.id = at.tag_id - WHERE at.annotation_id = a.id + SELECT SUM(1) FROM annotation_tag `+r.db.Quote("at")+` + INNER JOIN tag on tag.id = `+r.db.Quote("at")+`.tag_id + WHERE `+r.db.Quote("at")+`.annotation_id = a.id AND ( %s ) diff --git a/pkg/services/authn/authnimpl/registration.go b/pkg/services/authn/authnimpl/registration.go index bad45e88ef5..444ae241c61 100644 --- a/pkg/services/authn/authnimpl/registration.go +++ b/pkg/services/authn/authnimpl/registration.go @@ -7,6 +7,7 @@ import ( "github.com/grafana/grafana/pkg/infra/remotecache" "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/login/social" + "github.com/grafana/grafana/pkg/login/social/connectors" "github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/accesscontrol/permreg" "github.com/grafana/grafana/pkg/services/apikey" @@ -112,7 +113,8 @@ func ProvideRegistration( } if cfg.JWTAuth.Enabled { - authnSvc.RegisterClient(clients.ProvideJWT(jwtService, cfg)) + orgRoleMapper := connectors.ProvideOrgRoleMapper(cfg, orgService) + authnSvc.RegisterClient(clients.ProvideJWT(jwtService, orgRoleMapper, cfg)) } if cfg.ExtJWTAuth.Enabled { diff --git a/pkg/services/authn/authnimpl/sync/user_sync.go b/pkg/services/authn/authnimpl/sync/user_sync.go index f1ccefaae48..2f55c3a9cac 100644 --- a/pkg/services/authn/authnimpl/sync/user_sync.go +++ b/pkg/services/authn/authnimpl/sync/user_sync.go @@ -7,6 +7,7 @@ import ( "strconv" claims "github.com/grafana/authlib/types" + "go.opentelemetry.io/otel/attribute" "golang.org/x/sync/singleflight" "github.com/grafana/grafana/pkg/apimachinery/errutil" @@ -45,6 +46,14 @@ var ( "user.sync.fetch-not-found", errutil.WithPublicMessage("User not found"), ) + errMismatchedExternalUID = errutil.Unauthorized( + "user.sync.mismatched-externalUID", + errutil.WithPublicMessage("Mismatched externalUID"), + ) + errEmptyExternalUID = errutil.Unauthorized( + "user.sync.empty-externalUID", + errutil.WithPublicMessage("Empty externalUID"), + ) ) var ( @@ -231,7 +240,7 @@ func (s *UserSync) upsertAuthConnection(ctx context.Context, userID int64, ident return nil } - // If a user does not a connection to a specific auth module, create it. + // If a user does not have a connection to a specific auth module, create it. // This can happen when: using multiple auth client where the same user exists in several or // changing to new auth client if createConnection { @@ -265,6 +274,8 @@ func (s *UserSync) updateUserAttributes(ctx context.Context, usr *user.User, id ctx, span := s.tracer.Start(ctx, "user.sync.updateUserAttributes") defer span.End() + needsConnectionCreation := userAuth == nil + if errProtection := s.userProtectionService.AllowUserMapping(usr, id.AuthenticatedBy); errProtection != nil { return errUserProtection.Errorf("user mapping not allowed: %w", errProtection) } @@ -305,14 +316,38 @@ func (s *UserSync) updateUserAttributes(ctx context.Context, usr *user.User, id needsUpdate = true } - if needsUpdate { + span.SetAttributes( + attribute.String("identity.ID", id.ID), + attribute.String("identity.ExternalUID", id.ExternalUID), + ) + if usr.IsProvisioned { + s.log.Debug("User is provisioned", "id,UID", id.UID) + needsConnectionCreation = false + authInfo, err := s.authInfoService.GetAuthInfo(ctx, &login.GetAuthInfoQuery{UserId: usr.ID, AuthModule: id.AuthenticatedBy}) + if err != nil { + s.log.Error("Error getting auth info", "error", err) + return err + } + + if id.ExternalUID == "" { + s.log.Error("externalUID is empty", "id", id.UID) + return errEmptyExternalUID.Errorf("externalUID is empty") + } + + if id.ExternalUID != authInfo.ExternalUID { + s.log.Error("mismatched externalUID", "provisioned_externalUID", authInfo.ExternalUID, "identity_externalUID", id.ExternalUID) + return errMismatchedExternalUID.Errorf("externalUID mistmatch") + } + } + + if needsUpdate && !usr.IsProvisioned { s.log.FromContext(ctx).Debug("Syncing user info", "id", id.ID, "update", fmt.Sprintf("%v", updateCmd)) if err := s.userService.Update(ctx, updateCmd); err != nil { return err } } - return s.upsertAuthConnection(ctx, usr.ID, id, userAuth == nil) + return s.upsertAuthConnection(ctx, usr.ID, id, needsConnectionCreation) } func (s *UserSync) createUser(ctx context.Context, id *authn.Identity) (*user.User, error) { diff --git a/pkg/services/authn/clients/jwt.go b/pkg/services/authn/clients/jwt.go index edc1f43dbc9..82323e21bfe 100644 --- a/pkg/services/authn/clients/jwt.go +++ b/pkg/services/authn/clients/jwt.go @@ -7,6 +7,7 @@ import ( "github.com/grafana/grafana/pkg/apimachinery/errutil" "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/login/social/connectors" "github.com/grafana/grafana/pkg/services/auth" authJWT "github.com/grafana/grafana/pkg/services/auth/jwt" "github.com/grafana/grafana/pkg/services/authn" @@ -29,18 +30,22 @@ var ( "jwt.invalid_role", errutil.WithPublicMessage("Invalid Role in claim")) ) -func ProvideJWT(jwtService auth.JWTVerifierService, cfg *setting.Cfg) *JWT { +func ProvideJWT(jwtService auth.JWTVerifierService, orgRoleMapper *connectors.OrgRoleMapper, cfg *setting.Cfg) *JWT { return &JWT{ - cfg: cfg, - log: log.New(authn.ClientJWT), - jwtService: jwtService, + cfg: cfg, + log: log.New(authn.ClientJWT), + jwtService: jwtService, + orgRoleMapper: orgRoleMapper, + orgMappingCfg: orgRoleMapper.ParseOrgMappingSettings(context.Background(), cfg.JWTAuth.OrgMapping, cfg.JWTAuth.RoleAttributeStrict), } } type JWT struct { - cfg *setting.Cfg - log log.Logger - jwtService auth.JWTVerifierService + cfg *setting.Cfg + orgRoleMapper *connectors.OrgRoleMapper + orgMappingCfg connectors.MappingConfiguration + log log.Logger + jwtService auth.JWTVerifierService } func (s *JWT) Name() string { @@ -102,32 +107,31 @@ func (s *JWT) Authenticate(ctx context.Context, r *authn.Request) (*authn.Identi id.Name = name } - orgRoles, isGrafanaAdmin, err := getRoles(s.cfg, func() (org.RoleType, *bool, error) { - if s.cfg.JWTAuth.SkipOrgRoleSync { - return "", nil, nil - } - - role, grafanaAdmin := s.extractRoleAndAdmin(claims) - if s.cfg.JWTAuth.RoleAttributeStrict && !role.IsValid() { - return "", nil, errJWTInvalidRole.Errorf("invalid role claim in JWT: %s", role) - } - - if !s.cfg.JWTAuth.AllowAssignGrafanaAdmin { - return role, nil, nil - } - - return role, &grafanaAdmin, nil - }) + id.Groups, err = s.extractGroups(claims) if err != nil { return nil, err } - id.OrgRoles = orgRoles - id.IsGrafanaAdmin = isGrafanaAdmin + if !s.cfg.JWTAuth.SkipOrgRoleSync { + role, grafanaAdmin := s.extractRoleAndAdmin(claims) + if err != nil { + s.log.Warn("Failed to extract role", "err", err) + } - id.Groups, err = s.extractGroups(claims) - if err != nil { - return nil, err + if s.cfg.JWTAuth.AllowAssignGrafanaAdmin { + id.IsGrafanaAdmin = &grafanaAdmin + } + + externalOrgs, err := s.extractOrgs(claims) + if err != nil { + s.log.Warn("Failed to extract orgs", "err", err) + return nil, err + } + + id.OrgRoles = s.orgRoleMapper.MapOrgRoles(s.orgMappingCfg, externalOrgs, role) + if s.cfg.JWTAuth.RoleAttributeStrict && len(id.OrgRoles) == 0 { + return nil, errJWTInvalidRole.Errorf("could not evaluate any valid roles using IdP provided data") + } } if id.Login == "" && id.Email == "" { @@ -213,3 +217,12 @@ func (s *JWT) extractGroups(claims map[string]any) ([]string, error) { return util.SearchJSONForStringSliceAttr(s.cfg.JWTAuth.GroupsAttributePath, claims) } + +// This code was copied from the social_base.go file and was adapted to match with the JWT structure +func (s *JWT) extractOrgs(claims map[string]any) ([]string, error) { + if s.cfg.JWTAuth.OrgAttributePath == "" { + return []string{}, nil + } + + return util.SearchJSONForStringSliceAttr(s.cfg.JWTAuth.OrgAttributePath, claims) +} diff --git a/pkg/services/authn/clients/jwt_test.go b/pkg/services/authn/clients/jwt_test.go index c4381f3945e..7fccffc6b17 100644 --- a/pkg/services/authn/clients/jwt_test.go +++ b/pkg/services/authn/clients/jwt_test.go @@ -11,9 +11,12 @@ import ( "github.com/stretchr/testify/require" "github.com/grafana/grafana/pkg/apimachinery/identity" + "github.com/grafana/grafana/pkg/login/social/connectors" "github.com/grafana/grafana/pkg/services/auth/jwt" "github.com/grafana/grafana/pkg/services/authn" "github.com/grafana/grafana/pkg/services/login" + "github.com/grafana/grafana/pkg/services/org" + "github.com/grafana/grafana/pkg/services/org/orgtest" "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/util" ) @@ -136,6 +139,116 @@ func TestAuthenticateJWT(t *testing.T) { }, }, }, + { + name: "Valid Use case with org_mapping", + wantID: &authn.Identity{ + OrgID: 0, + OrgName: "", + OrgRoles: map[int64]identity.RoleType{4: identity.RoleEditor, 5: identity.RoleViewer}, + Login: "eai-doe", + Groups: []string{"foo", "bar"}, + Name: "Eai Doe", + Email: "eai.doe@cor.po", + IsGrafanaAdmin: boolPtr(false), + AuthenticatedBy: login.JWTModule, + AuthID: "1234567890", + IsDisabled: false, + HelpFlags1: 0, + ClientParams: authn.ClientParams{ + SyncUser: true, + AllowSignUp: true, + FetchSyncedUser: true, + SyncOrgRoles: true, + SyncPermissions: true, + SyncTeams: true, + LookUpParams: login.UserLookupParams{ + Email: stringPtr("eai.doe@cor.po"), + Login: stringPtr("eai-doe"), + }, + }, + }, + verifyProvider: func(context.Context, string) (map[string]any, error) { + return map[string]any{ + "sub": "1234567890", + "email": "eai.doe@cor.po", + "preferred_username": "eai-doe", + "name": "Eai Doe", + "roles": "None", + "groups": []string{"foo", "bar"}, + "orgs": []string{"org1", "org2"}, + }, nil + }, + cfg: &setting.Cfg{ + JWTAuth: setting.AuthJWTSettings{ + Enabled: true, + HeaderName: jwtHeaderName, + EmailClaim: "email", + UsernameClaim: "preferred_username", + AutoSignUp: true, + AllowAssignGrafanaAdmin: true, + RoleAttributeStrict: true, + RoleAttributePath: "roles", + GroupsAttributePath: "groups[]", + OrgAttributePath: "orgs[]", + OrgMapping: []string{"org1:Org4:Editor", "org2:Org5:Viewer"}, + }, + }, + }, + { + name: "Invalid Use case with org_mapping and invalid roles", + wantID: &authn.Identity{ + OrgID: 0, + OrgName: "", + OrgRoles: map[int64]identity.RoleType{4: identity.RoleEditor, 5: identity.RoleViewer}, + Login: "eai-doe", + Groups: []string{"foo", "bar"}, + Name: "Eai Doe", + Email: "eai.doe@cor.po", + IsGrafanaAdmin: boolPtr(false), + AuthenticatedBy: login.JWTModule, + AuthID: "1234567890", + IsDisabled: false, + HelpFlags1: 0, + ClientParams: authn.ClientParams{ + SyncUser: true, + AllowSignUp: true, + FetchSyncedUser: true, + SyncOrgRoles: true, + SyncPermissions: true, + SyncTeams: true, + LookUpParams: login.UserLookupParams{ + Email: stringPtr("eai.doe@cor.po"), + Login: stringPtr("eai-doe"), + }, + }, + }, + verifyProvider: func(context.Context, string) (map[string]any, error) { + return map[string]any{ + "sub": "1234567890", + "email": "eai.doe@cor.po", + "preferred_username": "eai-doe", + "name": "Eai Doe", + "roles": []string{"Invalid"}, + "groups": []string{"foo", "bar"}, + "orgs": []string{"org1", "org2"}, + }, nil + }, + cfg: &setting.Cfg{ + JWTAuth: setting.AuthJWTSettings{ + Enabled: true, + HeaderName: jwtHeaderName, + EmailClaim: "email", + UsernameClaim: "preferred_username", + AutoSignUp: true, + AllowAssignGrafanaAdmin: true, + RoleAttributeStrict: true, + RoleAttributePath: "roles", + GroupsAttributePath: "groups[]", + OrgAttributePath: "orgs[]", + OrgMapping: []string{"org1:Org4:Editor", "org2:Org5:Viewer"}, + }, + }, + }, } for _, tc := range testCases { @@ -146,7 +259,10 @@ func TestAuthenticateJWT(t *testing.T) { VerifyProvider: tc.verifyProvider, } - jwtClient := ProvideJWT(jwtService, tc.cfg) + jwtClient := ProvideJWT(jwtService, + connectors.ProvideOrgRoleMapper(tc.cfg, + &orgtest.FakeOrgService{ExpectedOrgs: []*org.OrgDTO{{ID: 4, Name: "Org4"}, {ID: 5, Name: "Org5"}}}), + tc.cfg) validHTTPReq := &http.Request{ Header: map[string][]string{ jwtHeaderName: {"sample-token"}}, @@ -262,7 +378,9 @@ func TestJWTClaimConfig(t *testing.T) { Header: map[string][]string{ jwtHeaderName: {token}}, } - jwtClient := ProvideJWT(jwtService, cfg) + jwtClient := ProvideJWT(jwtService, connectors.ProvideOrgRoleMapper(cfg, + &orgtest.FakeOrgService{ExpectedOrgs: []*org.OrgDTO{{ID: 4, Name: "Org4"}, {ID: 5, Name: "Org5"}}}), + cfg) _, err := jwtClient.Authenticate(context.Background(), &authn.Request{ OrgID: 1, HTTPRequest: httpReq, @@ -372,7 +490,10 @@ func TestJWTTest(t *testing.T) { RoleAttributeStrict: true, }, } - jwtClient := ProvideJWT(jwtService, cfg) + jwtClient := ProvideJWT(jwtService, + connectors.ProvideOrgRoleMapper(cfg, + &orgtest.FakeOrgService{ExpectedOrgs: []*org.OrgDTO{{ID: 4, Name: "Org4"}, {ID: 5, Name: "Org5"}}}), + cfg) httpReq := &http.Request{ URL: &url.URL{RawQuery: "auth_token=" + tc.token}, Header: map[string][]string{ @@ -425,7 +546,10 @@ func TestJWTStripParam(t *testing.T) { httpReq := &http.Request{ URL: &url.URL{RawQuery: "auth_token=" + token + "&other_param=other_value"}, } - jwtClient := ProvideJWT(jwtService, cfg) + jwtClient := ProvideJWT(jwtService, + connectors.ProvideOrgRoleMapper(cfg, + &orgtest.FakeOrgService{ExpectedOrgs: []*org.OrgDTO{{ID: 4, Name: "Org4"}, {ID: 5, Name: "Org5"}}}), + cfg) _, err := jwtClient.Authenticate(context.Background(), &authn.Request{ OrgID: 1, HTTPRequest: httpReq, @@ -481,7 +605,10 @@ func TestJWTSubClaimsConfig(t *testing.T) { }, } - jwtClient := ProvideJWT(jwtService, cfg) + jwtClient := ProvideJWT(jwtService, + connectors.ProvideOrgRoleMapper(cfg, + &orgtest.FakeOrgService{ExpectedOrgs: []*org.OrgDTO{{ID: 4, Name: "Org4"}, {ID: 5, Name: "Org5"}}}), + cfg) identity, err := jwtClient.Authenticate(context.Background(), &authn.Request{ OrgID: 1, HTTPRequest: httpReq, diff --git a/pkg/services/authn/identity.go b/pkg/services/authn/identity.go index fd971e7070f..2b6e7bfe533 100644 --- a/pkg/services/authn/identity.go +++ b/pkg/services/authn/identity.go @@ -76,6 +76,8 @@ type Identity struct { Permissions map[int64]map[string][]string // IDToken is a signed token representing the identity that can be forwarded to plugins and external services. IDToken string + // ExternalUID is the unique identifier for the entity in the external system. + ExternalUID string IDTokenClaims *authn.Claims[authn.IDTokenClaims] AccessTokenClaims *authn.Claims[authn.AccessTokenClaims] diff --git a/pkg/services/cloudmigration/cloudmigrationimpl/cloudmigration_test.go b/pkg/services/cloudmigration/cloudmigrationimpl/cloudmigration_test.go index 246e74c6da7..b8f30c1cab4 100644 --- a/pkg/services/cloudmigration/cloudmigrationimpl/cloudmigration_test.go +++ b/pkg/services/cloudmigration/cloudmigrationimpl/cloudmigration_test.go @@ -628,62 +628,92 @@ func TestGetFolderNamesForFolderUIDs(t *testing.T) { func TestGetParentNames(t *testing.T) { t.Parallel() - s := setUpServiceTest(t, false).(*Service) ctx, cancel := context.WithCancel(context.Background()) t.Cleanup(cancel) + s := setUpServiceTest(t, false).(*Service) + user := &user.SignedInUser{OrgID: 1} - libraryElementFolderUID := "folderUID-A" + testcases := []struct { + name string fakeFolders []*folder.Folder - folders []folder.CreateFolderCommand - dashboards []dashboards.Dashboard - libraryElements []libraryElement - alertRules []alertRule - expectedParentNames map[cloudmigration.MigrateDataType][]string + folderHierarchy map[cloudmigration.MigrateDataType]map[string]string + expectedParentNames map[cloudmigration.MigrateDataType]map[string]string }{ { + name: "multiple data types", fakeFolders: []*folder.Folder{ - {UID: "folderUID-A", Title: "Folder A", OrgID: 1, ParentUID: ""}, - {UID: "folderUID-B", Title: "Folder B", OrgID: 1, ParentUID: "folderUID-A"}, - {UID: "folderUID-X", Title: "Folder X", OrgID: 1, ParentUID: ""}, + {UID: "folderUID-A", Title: "Folder A", OrgID: 1}, + {UID: "folderUID-B", Title: "Folder B", OrgID: 1}, + {UID: "folderUID-C", Title: "Folder C", OrgID: 1}, }, - folders: []folder.CreateFolderCommand{ - {UID: "folderUID-C", Title: "Folder A", OrgID: 1, ParentUID: "folderUID-A"}, + folderHierarchy: map[cloudmigration.MigrateDataType]map[string]string{ + cloudmigration.DashboardDataType: {"dashboard-1": "folderUID-A", "dashboard-2": "folderUID-B", "dashboard-3": ""}, + cloudmigration.LibraryElementDataType: {"libElement-1": "folderUID-A", "libElement-2": "folderUID-C"}, + cloudmigration.AlertRuleType: {"alertRule-1": "folderUID-B"}, }, - dashboards: []dashboards.Dashboard{ - {UID: "dashboardUID-0", OrgID: 1, FolderUID: ""}, - {UID: "dashboardUID-1", OrgID: 1, FolderUID: "folderUID-A"}, - {UID: "dashboardUID-2", OrgID: 1, FolderUID: "folderUID-B"}, + expectedParentNames: map[cloudmigration.MigrateDataType]map[string]string{ + cloudmigration.DashboardDataType: {"dashboard-1": "Folder A", "dashboard-2": "Folder B", "dashboard-3": ""}, + cloudmigration.LibraryElementDataType: {"libElement-1": "Folder A", "libElement-2": "Folder C"}, + cloudmigration.AlertRuleType: {"alertRule-1": "Folder B"}, }, - libraryElements: []libraryElement{ - {UID: "libraryElementUID-0", FolderUID: &libraryElementFolderUID}, - {UID: "libraryElementUID-1"}, + }, + { + name: "empty folder hierarchy", + fakeFolders: []*folder.Folder{ + {UID: "folderUID-A", Title: "Folder A", OrgID: 1}, }, - alertRules: []alertRule{ - {UID: "alertRuleUID-0", FolderUID: ""}, - {UID: "alertRuleUID-1", FolderUID: "folderUID-B"}, + folderHierarchy: map[cloudmigration.MigrateDataType]map[string]string{}, + expectedParentNames: map[cloudmigration.MigrateDataType]map[string]string{}, + }, + { + name: "all root folders (no parents)", + fakeFolders: []*folder.Folder{ + {UID: "folderUID-A", Title: "Folder A", OrgID: 1}, }, - expectedParentNames: map[cloudmigration.MigrateDataType][]string{ - cloudmigration.DashboardDataType: {"", "Folder A", "Folder B"}, - cloudmigration.FolderDataType: {"Folder A"}, - cloudmigration.LibraryElementDataType: {"Folder A"}, - cloudmigration.AlertRuleType: {"Folder B"}, + folderHierarchy: map[cloudmigration.MigrateDataType]map[string]string{ + cloudmigration.DashboardDataType: {"dashboard-1": "", "dashboard-2": ""}, + cloudmigration.LibraryElementDataType: {"libElement-1": ""}, + }, + expectedParentNames: map[cloudmigration.MigrateDataType]map[string]string{ + cloudmigration.DashboardDataType: {"dashboard-1": "", "dashboard-2": ""}, + cloudmigration.LibraryElementDataType: {"libElement-1": ""}, + }, + }, + { + name: "non-existent folder UIDs", + fakeFolders: []*folder.Folder{ + {UID: "folderUID-A", Title: "Folder A", OrgID: 1}, + }, + folderHierarchy: map[cloudmigration.MigrateDataType]map[string]string{ + cloudmigration.DashboardDataType: {"dashboard-1": "folderUID-A", "dashboard-2": "non-existent-uid"}, + }, + expectedParentNames: map[cloudmigration.MigrateDataType]map[string]string{ + cloudmigration.DashboardDataType: {"dashboard-1": "Folder A", "dashboard-2": ""}, }, }, } for _, tc := range testcases { - s.folderService = &foldertest.FakeService{ExpectedFolders: tc.fakeFolders} + t.Run(tc.name, func(t *testing.T) { + s.folderService = &foldertest.FakeService{ExpectedFolders: tc.fakeFolders} - dataUIDsToParentNamesByType, err := s.getParentNames(ctx, user, tc.dashboards, tc.folders, tc.libraryElements, tc.alertRules) - require.NoError(t, err) + dataUIDsToParentNamesByType, err := s.getParentNames(ctx, user, tc.folderHierarchy) + require.NoError(t, err) - for dataType, expectedParentNames := range tc.expectedParentNames { - actualParentNames := slices.Collect(maps.Values(dataUIDsToParentNamesByType[dataType])) - require.Len(t, actualParentNames, len(expectedParentNames)) - require.ElementsMatch(t, expectedParentNames, actualParentNames) - } + for dataType, expectedParentNames := range tc.expectedParentNames { + actualParentNames := dataUIDsToParentNamesByType[dataType] + + require.Equal(t, len(expectedParentNames), len(actualParentNames)) + + for uid, expectedName := range expectedParentNames { + actualName, exists := actualParentNames[uid] + require.True(t, exists) + require.Equal(t, expectedName, actualName) + } + } + }) } } diff --git a/pkg/services/cloudmigration/cloudmigrationimpl/snapshot_mgmt.go b/pkg/services/cloudmigration/cloudmigrationimpl/snapshot_mgmt.go index 034c601f510..e74c9217fbc 100644 --- a/pkg/services/cloudmigration/cloudmigrationimpl/snapshot_mgmt.go +++ b/pkg/services/cloudmigration/cloudmigrationimpl/snapshot_mgmt.go @@ -12,6 +12,9 @@ import ( "sort" "time" + "go.opentelemetry.io/otel/codes" + "golang.org/x/crypto/nacl/box" + snapshot "github.com/grafana/grafana-cloud-migration-snapshot/src" "github.com/grafana/grafana-cloud-migration-snapshot/src/contracts" "github.com/grafana/grafana-cloud-migration-snapshot/src/infra/crypto" @@ -29,9 +32,6 @@ import ( "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginsettings" "github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/util/retryer" - "golang.org/x/crypto/nacl/box" - - "go.opentelemetry.io/otel/codes" ) var currentMigrationTypes = []cloudmigration.MigrateDataType{ @@ -52,6 +52,10 @@ func (s *Service) getMigrationDataJSON(ctx context.Context, signedInUser *user.S ctx, span := s.tracer.Start(ctx, "CloudMigrationService.getMigrationDataJSON") defer span.End() + migrationDataSlice := make([]cloudmigration.MigrateDataRequestItem, 0) + + folderHierarchy := make(map[cloudmigration.MigrateDataType]map[string]string, 0) + // Plugins plugins, err := s.getPlugins(ctx, signedInUser) if err != nil { @@ -59,74 +63,6 @@ func (s *Service) getMigrationDataJSON(ctx context.Context, signedInUser *user.S return nil, err } - // Data sources - dataSources, err := s.getDataSourceCommands(ctx, signedInUser) - if err != nil { - s.log.Error("Failed to get datasources", "err", err) - return nil, err - } - - // Dashboards and folders are linked via the schema, so we need to get both - dashs, folders, err := s.getDashboardAndFolderCommands(ctx, signedInUser) - if err != nil { - s.log.Error("Failed to get dashboards and folders", "err", err) - return nil, err - } - - libraryElements, err := s.getLibraryElementsCommands(ctx, signedInUser) - if err != nil { - s.log.Error("Failed to get library elements", "err", err) - return nil, err - } - - // Alerts: Mute Timings - muteTimings, err := s.getAlertMuteTimings(ctx, signedInUser) - if err != nil { - s.log.Error("Failed to get alert mute timings", "err", err) - return nil, err - } - - // Alerts: Notification Templates - notificationTemplates, err := s.getNotificationTemplates(ctx, signedInUser) - if err != nil { - s.log.Error("Failed to get alert notification templates", "err", err) - return nil, err - } - - // Alerts: Contact Points - contactPoints, err := s.getContactPoints(ctx, signedInUser) - if err != nil { - s.log.Error("Failed to get alert contact points", "err", err) - return nil, err - } - - // Alerts: Notification Policies - notificationPolicies, err := s.getNotificationPolicies(ctx, signedInUser) - if err != nil { - s.log.Error("Failed to get alert notification policies", "err", err) - return nil, err - } - - // Alerts: Alert Rule Groups - alertRuleGroups, err := s.getAlertRuleGroups(ctx, signedInUser) - if err != nil { - s.log.Error("Failed to get alert rule groups", "err", err) - return nil, err - } - - // Alerts: Alert Rules - alertRules, err := s.getAlertRules(ctx, signedInUser) - if err != nil { - s.log.Error("Failed to get alert rules", "err", err) - return nil, err - } - - migrationDataSlice := make( - []cloudmigration.MigrateDataRequestItem, 0, - len(plugins)+len(dataSources)+len(dashs)+len(folders)+len(libraryElements)+ - len(muteTimings)+len(notificationTemplates)+len(contactPoints)+len(alertRules), - ) - for _, plugin := range plugins { migrationDataSlice = append(migrationDataSlice, cloudmigration.MigrateDataRequestItem{ Type: cloudmigration.PluginDataType, @@ -136,6 +72,13 @@ func (s *Service) getMigrationDataJSON(ctx context.Context, signedInUser *user.S }) } + // Data sources + dataSources, err := s.getDataSourceCommands(ctx, signedInUser) + if err != nil { + s.log.Error("Failed to get datasources", "err", err) + return nil, err + } + for _, ds := range dataSources { migrationDataSlice = append(migrationDataSlice, cloudmigration.MigrateDataRequestItem{ Type: cloudmigration.DatasourceDataType, @@ -145,6 +88,15 @@ func (s *Service) getMigrationDataJSON(ctx context.Context, signedInUser *user.S }) } + // Dashboards & Folders: linked via the schema, so we need to get both + dashs, folders, err := s.getDashboardAndFolderCommands(ctx, signedInUser) + if err != nil { + s.log.Error("Failed to get dashboards and folders", "err", err) + return nil, err + } + + folderHierarchy[cloudmigration.DashboardDataType] = make(map[string]string, 0) + for _, dashboard := range dashs { dashboard.Data.Del("id") migrationDataSlice = append(migrationDataSlice, cloudmigration.MigrateDataRequestItem{ @@ -159,8 +111,12 @@ func (s *Service) getMigrationDataJSON(ctx context.Context, signedInUser *user.S FolderUID: dashboard.FolderUID, }, }) + + folderHierarchy[cloudmigration.DashboardDataType][dashboard.UID] = dashboard.FolderUID } + folderHierarchy[cloudmigration.FolderDataType] = make(map[string]string, 0) + folders = sortFolders(folders) for _, f := range folders { migrationDataSlice = append(migrationDataSlice, cloudmigration.MigrateDataRequestItem{ @@ -169,8 +125,19 @@ func (s *Service) getMigrationDataJSON(ctx context.Context, signedInUser *user.S Name: f.Title, Data: f, }) + + folderHierarchy[cloudmigration.FolderDataType][f.UID] = f.ParentUID } + // Library Elements + libraryElements, err := s.getLibraryElementsCommands(ctx, signedInUser) + if err != nil { + s.log.Error("Failed to get library elements", "err", err) + return nil, err + } + + folderHierarchy[cloudmigration.LibraryElementDataType] = make(map[string]string, 0) + for _, libraryElement := range libraryElements { migrationDataSlice = append(migrationDataSlice, cloudmigration.MigrateDataRequestItem{ Type: cloudmigration.LibraryElementDataType, @@ -178,6 +145,17 @@ func (s *Service) getMigrationDataJSON(ctx context.Context, signedInUser *user.S Name: libraryElement.Name, Data: libraryElement, }) + + if libraryElement.FolderUID != nil { + folderHierarchy[cloudmigration.LibraryElementDataType][libraryElement.UID] = *libraryElement.FolderUID + } + } + + // Alerts: Mute Timings + muteTimings, err := s.getAlertMuteTimings(ctx, signedInUser) + if err != nil { + s.log.Error("Failed to get alert mute timings", "err", err) + return nil, err } for _, muteTiming := range muteTimings { @@ -189,6 +167,13 @@ func (s *Service) getMigrationDataJSON(ctx context.Context, signedInUser *user.S }) } + // Alerts: Notification Templates + notificationTemplates, err := s.getNotificationTemplates(ctx, signedInUser) + if err != nil { + s.log.Error("Failed to get alert notification templates", "err", err) + return nil, err + } + for _, notificationTemplate := range notificationTemplates { migrationDataSlice = append(migrationDataSlice, cloudmigration.MigrateDataRequestItem{ Type: cloudmigration.NotificationTemplateType, @@ -198,6 +183,13 @@ func (s *Service) getMigrationDataJSON(ctx context.Context, signedInUser *user.S }) } + // Alerts: Contact Points + contactPoints, err := s.getContactPoints(ctx, signedInUser) + if err != nil { + s.log.Error("Failed to get alert contact points", "err", err) + return nil, err + } + for _, contactPoint := range contactPoints { migrationDataSlice = append(migrationDataSlice, cloudmigration.MigrateDataRequestItem{ Type: cloudmigration.ContactPointType, @@ -207,6 +199,13 @@ func (s *Service) getMigrationDataJSON(ctx context.Context, signedInUser *user.S }) } + // Alerts: Notification Policies + notificationPolicies, err := s.getNotificationPolicies(ctx, signedInUser) + if err != nil { + s.log.Error("Failed to get alert notification policies", "err", err) + return nil, err + } + if len(notificationPolicies.Name) > 0 { // Notification Policy can only be managed by updating its entire tree, so we send the whole thing as one item. migrationDataSlice = append(migrationDataSlice, cloudmigration.MigrateDataRequestItem{ @@ -217,6 +216,13 @@ func (s *Service) getMigrationDataJSON(ctx context.Context, signedInUser *user.S }) } + // Alerts: Alert Rule Groups + alertRuleGroups, err := s.getAlertRuleGroups(ctx, signedInUser) + if err != nil { + s.log.Error("Failed to get alert rule groups", "err", err) + return nil, err + } + for _, alertRuleGroup := range alertRuleGroups { migrationDataSlice = append(migrationDataSlice, cloudmigration.MigrateDataRequestItem{ Type: cloudmigration.AlertRuleGroupType, @@ -226,6 +232,15 @@ func (s *Service) getMigrationDataJSON(ctx context.Context, signedInUser *user.S }) } + // Alerts: Alert Rules + alertRules, err := s.getAlertRules(ctx, signedInUser) + if err != nil { + s.log.Error("Failed to get alert rules", "err", err) + return nil, err + } + + folderHierarchy[cloudmigration.AlertRuleType] = make(map[string]string, 0) + for _, alertRule := range alertRules { migrationDataSlice = append(migrationDataSlice, cloudmigration.MigrateDataRequestItem{ Type: cloudmigration.AlertRuleType, @@ -233,10 +248,12 @@ func (s *Service) getMigrationDataJSON(ctx context.Context, signedInUser *user.S Name: alertRule.Title, Data: alertRule, }) + + folderHierarchy[cloudmigration.AlertRuleType][alertRule.UID] = alertRule.FolderUID } - // Obtain the names of parent elements for Dashboard and Folders data types - parentNamesByType, err := s.getParentNames(ctx, signedInUser, dashs, folders, libraryElements, alertRules) + // Obtain the names of parent elements for data types that have folders. + parentNamesByType, err := s.getParentNames(ctx, signedInUser, folderHierarchy) if err != nil { s.log.Error("Failed to get parent folder names", "err", err) } @@ -805,10 +822,7 @@ func (s *Service) getFolderNamesForFolderUIDs(ctx context.Context, signedInUser func (s *Service) getParentNames( ctx context.Context, signedInUser *user.SignedInUser, - dashboards []dashboards.Dashboard, - folders []folder.CreateFolderCommand, - libraryElements []libraryElement, - alertRules []alertRule, + folderHierarchy map[cloudmigration.MigrateDataType]map[string]string, ) (map[cloudmigration.MigrateDataType]map[string](string), error) { parentNamesByType := make(map[cloudmigration.MigrateDataType]map[string]string) for _, dataType := range currentMigrationTypes { @@ -817,25 +831,18 @@ func (s *Service) getParentNames( // Obtain list of unique folderUIDs parentFolderUIDsSet := make(map[string]struct{}) - for _, dashboard := range dashboards { - // we dont need the root folder - if dashboard.FolderUID != "" { - parentFolderUIDsSet[dashboard.FolderUID] = struct{}{} - } - } - for _, f := range folders { - parentFolderUIDsSet[f.ParentUID] = struct{}{} - } - for _, libraryElement := range libraryElements { - if libraryElement.FolderUID != nil { - parentFolderUIDsSet[*libraryElement.FolderUID] = struct{}{} - } - } - for _, alertRule := range alertRules { - if alertRule.FolderUID != "" { - parentFolderUIDsSet[alertRule.FolderUID] = struct{}{} + + for _, folderUIDs := range folderHierarchy { + for _, folderUID := range folderUIDs { + // Skip the root folder + if folderUID == "" { + continue + } + + parentFolderUIDsSet[folderUID] = struct{}{} } } + parentFolderUIDsSlice := make([]string, 0, len(parentFolderUIDsSet)) for parentFolderUID := range parentFolderUIDsSet { parentFolderUIDsSlice = append(parentFolderUIDsSlice, parentFolderUID) @@ -849,20 +856,9 @@ func (s *Service) getParentNames( } // Prepare map of {data type: {data UID : parentName}} - for _, dashboard := range dashboards { - parentNamesByType[cloudmigration.DashboardDataType][dashboard.UID] = foldersUIDsToFolderName[dashboard.FolderUID] - } - for _, f := range folders { - parentNamesByType[cloudmigration.FolderDataType][f.UID] = foldersUIDsToFolderName[f.ParentUID] - } - for _, libraryElement := range libraryElements { - if libraryElement.FolderUID != nil { - parentNamesByType[cloudmigration.LibraryElementDataType][libraryElement.UID] = foldersUIDsToFolderName[*libraryElement.FolderUID] - } - } - for _, alertRule := range alertRules { - if alertRule.FolderUID != "" { - parentNamesByType[cloudmigration.AlertRuleType][alertRule.UID] = foldersUIDsToFolderName[alertRule.FolderUID] + for dataType, uidFolderMap := range folderHierarchy { + for uid, folderUID := range uidFolderMap { + parentNamesByType[dataType][uid] = foldersUIDsToFolderName[folderUID] } } diff --git a/pkg/services/dashboards/dashboard.go b/pkg/services/dashboards/dashboard.go index 92232c95bd8..b904e3c2e07 100644 --- a/pkg/services/dashboards/dashboard.go +++ b/pkg/services/dashboards/dashboard.go @@ -35,6 +35,7 @@ type DashboardService interface { GetAllDashboardsByOrgId(ctx context.Context, orgID int64) ([]*Dashboard, error) SoftDeleteDashboard(ctx context.Context, orgID int64, dashboardUid string) error RestoreDashboard(ctx context.Context, dashboard *Dashboard, user identity.Requester, optionalFolderUID string) error + CleanUpDashboard(ctx context.Context, dashboardUID string, orgId int64) error CleanUpDeletedDashboards(ctx context.Context) (int64, error) GetSoftDeletedDashboard(ctx context.Context, orgID int64, uid string) (*Dashboard, error) CountDashboardsInOrg(ctx context.Context, orgID int64) (int64, error) diff --git a/pkg/services/dashboards/dashboard_service_mock.go b/pkg/services/dashboards/dashboard_service_mock.go index 2e37ea6f3b8..ad526998488 100644 --- a/pkg/services/dashboards/dashboard_service_mock.go +++ b/pkg/services/dashboards/dashboard_service_mock.go @@ -562,6 +562,24 @@ func (_m *FakeDashboardService) SoftDeleteDashboard(ctx context.Context, orgID i return r0 } +// CleanUpDashboard provides a mock function with given fields: ctx, dashboardUID, orgId +func (_m *FakeDashboardService) CleanUpDashboard(ctx context.Context, dashboardUID string, orgId int64) error { + ret := _m.Called(ctx, dashboardUID, orgId) + + if len(ret) == 0 { + panic("no return value specified for CleanUpDashboard") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string, int64) error); ok { + r0 = rf(ctx, dashboardUID, orgId) + } else { + r0 = ret.Error(0) + } + + return r0 +} + // NewFakeDashboardService creates a new instance of FakeDashboardService. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. // The first argument is typically a *testing.T value. func NewFakeDashboardService(t interface { diff --git a/pkg/services/dashboards/database/database.go b/pkg/services/dashboards/database/database.go index d5972f16bc3..032d574b07f 100644 --- a/pkg/services/dashboards/database/database.go +++ b/pkg/services/dashboards/database/database.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "strconv" "strings" "time" @@ -673,7 +674,7 @@ func (d *dashboardStore) deleteDashboard(cmd *dashboards.DeleteDashboardCommand, {SQL: "DELETE FROM dashboard_tag WHERE dashboard_uid = ? AND org_id = ?", args: []any{dashboard.UID, dashboard.OrgID}}, {SQL: "DELETE FROM star WHERE dashboard_id = ? ", args: []any{dashboard.ID}}, {SQL: "DELETE FROM dashboard WHERE id = ?", args: []any{dashboard.ID}}, - {SQL: "DELETE FROM playlist_item WHERE type = 'dashboard_by_id' AND value = ?", args: []any{dashboard.ID}}, + {SQL: "DELETE FROM playlist_item WHERE type = 'dashboard_by_id' AND value = ?", args: []any{strconv.FormatInt(dashboard.ID, 10)}}, // Column has TEXT type. {SQL: "DELETE FROM dashboard_version WHERE dashboard_id = ?", args: []any{dashboard.ID}}, {SQL: "DELETE FROM dashboard_provisioning WHERE dashboard_id = ?", args: []any{dashboard.ID}}, {SQL: "DELETE FROM dashboard_acl WHERE dashboard_id = ?", args: []any{dashboard.ID}}, @@ -737,7 +738,7 @@ func (d *dashboardStore) CleanupAfterDelete(ctx context.Context, cmd *dashboards sqlStatements := []statement{ {SQL: "DELETE FROM dashboard_tag WHERE dashboard_uid = ? AND org_id = ?", args: []any{cmd.UID, cmd.OrgID}}, {SQL: "DELETE FROM star WHERE dashboard_uid = ? AND org_id = ?", args: []any{cmd.UID, cmd.OrgID}}, - {SQL: "DELETE FROM playlist_item WHERE type = 'dashboard_by_id' AND value = ?", args: []any{cmd.ID}}, + {SQL: "DELETE FROM playlist_item WHERE type = 'dashboard_by_id' AND value = ?", args: []any{strconv.FormatInt(cmd.ID, 10)}}, // Column has TEXT type. {SQL: "DELETE FROM dashboard_version WHERE dashboard_id = ?", args: []any{cmd.ID}}, {SQL: "DELETE FROM dashboard_provisioning WHERE dashboard_id = ?", args: []any{cmd.ID}}, {SQL: "DELETE FROM dashboard_acl WHERE dashboard_id = ?", args: []any{cmd.ID}}, diff --git a/pkg/services/dashboards/models.go b/pkg/services/dashboards/models.go index 0128d7d2e17..fd074aba343 100644 --- a/pkg/services/dashboards/models.go +++ b/pkg/services/dashboards/models.go @@ -265,8 +265,6 @@ type GetDashboardQuery struct { FolderID *int64 FolderUID *string OrgID int64 - - IncludeDeleted bool // only supported when using unified storage } type DashboardTagCloudItem struct { diff --git a/pkg/services/dashboards/service/dashboard_service.go b/pkg/services/dashboards/service/dashboard_service.go index 53164cda375..d85dd62405b 100644 --- a/pkg/services/dashboards/service/dashboard_service.go +++ b/pkg/services/dashboards/service/dashboard_service.go @@ -11,8 +11,6 @@ import ( "time" "github.com/google/uuid" - "github.com/grafana/grafana/pkg/registry/apis/dashboard/legacysearcher" - "github.com/grafana/grafana/pkg/util/retryer" "github.com/prometheus/client_golang/prometheus" "go.opentelemetry.io/otel" "golang.org/x/exp/maps" @@ -31,9 +29,13 @@ import ( "github.com/grafana/grafana/pkg/apimachinery/utils" folderv0alpha1 "github.com/grafana/grafana/pkg/apis/folder/v0alpha1" "github.com/grafana/grafana/pkg/components/simplejson" + "github.com/grafana/grafana/pkg/infra/kvstore" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/metrics" + "github.com/grafana/grafana/pkg/infra/serverlock" "github.com/grafana/grafana/pkg/infra/slugify" + "github.com/grafana/grafana/pkg/registry" + "github.com/grafana/grafana/pkg/registry/apis/dashboard/legacysearcher" "github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/apiserver" "github.com/grafana/grafana/pkg/services/apiserver/client" @@ -43,7 +45,6 @@ import ( dashboardsearch "github.com/grafana/grafana/pkg/services/dashboards/service/search" "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/folder" - "github.com/grafana/grafana/pkg/services/guardian" "github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/services/publicdashboards" "github.com/grafana/grafana/pkg/services/quota" @@ -57,6 +58,8 @@ import ( "github.com/grafana/grafana/pkg/storage/unified/resource" "github.com/grafana/grafana/pkg/storage/unified/search" "github.com/grafana/grafana/pkg/util" + "github.com/grafana/grafana/pkg/util/retryer" + "go.opentelemetry.io/otel/attribute" ) var ( @@ -69,6 +72,11 @@ var ( tracer = otel.Tracer("github.com/grafana/grafana/pkg/services/dashboards/service") ) +const ( + k8sDashboardKvNamespace = "dashboard-cleanup" + k8sDashboardKvLastResourceVersionKey = "last-resource-version" +) + type DashboardServiceImpl struct { cfg *setting.Cfg log log.Logger @@ -83,11 +91,271 @@ type DashboardServiceImpl struct { k8sclient client.K8sHandler metrics *dashboardsMetrics publicDashboardService publicdashboards.ServiceWrapper + serverLockService *serverlock.ServerLockService + kvstore kvstore.KVStore dashboardPermissionsReady chan struct{} } +func (dr *DashboardServiceImpl) startK8sDeletedDashboardsCleanupJob(ctx context.Context) chan struct{} { + done := make(chan struct{}) + go func() { + defer close(done) + + ticker := time.NewTicker(dr.cfg.K8sDashboardCleanup.Interval) + defer ticker.Stop() + + for { + select { + case <-ctx.Done(): + return + case <-ticker.C: + if err := dr.executeCleanupWithLock(ctx); err != nil { + dr.log.Error("Failed to execute k8s dashboard cleanup", "error", err) + } + } + } + }() + return done +} + +func (dr *DashboardServiceImpl) executeCleanupWithLock(ctx context.Context) error { + // We're taking a leader-like locking approach here. By locking and executing, but never releasing the lock, + // we ensure that other instances of this service can't run in parallel and hence the cleanup will only happen once + // per cleanup interval by setting the maxInterval and having the time between executions be the cleanup interval as well. + return dr.serverLockService.LockAndExecute( + ctx, + k8sDashboardKvNamespace, + dr.cfg.K8sDashboardCleanup.Interval, + func(ctx context.Context) { + if err := dr.cleanupK8sDashboardResources(ctx, dr.cfg.K8sDashboardCleanup.BatchSize, dr.cfg.K8sDashboardCleanup.Timeout); err != nil { + dr.log.Error("Failed to cleanup k8s dashboard resources", "error", err) + } + }, + ) +} + +// cleanupK8sDashboardResources cleans up resources marked for deletion in the k8s API. +// It processes all organizations, finds dashboards with the trash label, and cleans them up. +// batchSize specifies how many dashboards to process in a single batch. +// timeout specifies the timeout duration for the cleanup operation. +func (dr *DashboardServiceImpl) cleanupK8sDashboardResources(ctx context.Context, batchSize int64, timeout time.Duration) error { + ctx, span := tracer.Start(ctx, "dashboards.service.cleanupK8sDashboardResources") + defer span.End() + + if !dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) { + return nil + } + + // Create a timeout context to ensure we complete before the lock expires + ctx, cancel := context.WithTimeout(ctx, timeout) + defer cancel() + + orgs, err := dr.orgService.Search(ctx, &org.SearchOrgsQuery{}) + if err != nil { + return err + } + dr.log.Debug("Running k8s dashboard resource cleanup for all orgs", "numOrgs", len(orgs)) + + var errs []error + for _, org := range orgs { + // Check if we're approaching the timeout + if ctx.Err() != nil { + dr.log.Info("Timeout reached during cleanup, stopping processing", "timeout", timeout) + break + } + + orgErr := dr.cleanupOrganizationK8sDashboards(ctx, org.ID, batchSize) + if orgErr != nil { + errs = append(errs, fmt.Errorf("org %d: %w", org.ID, orgErr)) + } + } + + if len(errs) > 0 { + return errors.Join(errs...) + } + + return nil +} + +// cleanupOrganizationK8sDashboards handles cleanup for a single organization's Kubernetes dashboards +func (dr *DashboardServiceImpl) cleanupOrganizationK8sDashboards(ctx context.Context, orgID int64, batchSize int64) error { + dr.log.Debug("Running k8s dashboard resource cleanup for org", "orgID", orgID) + + ctx, span := tracer.Start(ctx, "dashboards.service.cleanupK8sDashboardResources.org") + defer span.End() + span.SetAttributes(attribute.Int64("org_id", orgID)) + + ctx, _ = identity.WithServiceIdentity(ctx, orgID) + + // Get the last processed resource version + lastResourceVersion, err := dr.getLastResourceVersion(ctx, orgID) + if err != nil { + return err + } + + var errs []error + continueToken := "" + itemsProcessed := 0 + + for { + // Check if we're approaching the timeout + if ctx.Err() != nil { + dr.log.Info("Timeout reached during org cleanup, stopping processing", "orgID", orgID) + break + } + + // List resources to be cleaned up + data, listErr, shouldContinue := dr.listResourcesToCleanup(ctx, orgID, lastResourceVersion, continueToken, batchSize) + if listErr != nil { + errs = append(errs, fmt.Errorf("failed to list resources: %w", listErr)) + break + } + if shouldContinue { + // Reset and try again with updated resource version + lastResourceVersion = "0" + continueToken = "" + continue + } + + // Skip the first item if it matches our last resource version (due to NotOlderThan behavior) + if len(data.Items) > 0 && data.Items[0].GetResourceVersion() == lastResourceVersion { + data.Items = data.Items[1:] + } + + if len(data.Items) == 0 { + dr.log.Debug("No items to clean up in this batch", "orgID", orgID) + break + } + + dr.log.Info("Processing dashboard cleanup batch", "orgID", orgID, "count", len(data.Items)) + + // Process the batch + processedItems, processingErrs := dr.processDashboardBatch(ctx, orgID, data.Items) + if len(processingErrs) > 0 { + errs = append(errs, processingErrs...) + } + itemsProcessed += processedItems + + // Update resource version after the batch + if len(data.Items) > 0 { + maxBatchResourceVersion := data.Items[len(data.Items)-1].GetResourceVersion() + if lastResourceVersion != maxBatchResourceVersion { + dr.log.Info("Updating resource version after batch", "orgID", orgID, + "newResourceVersion", maxBatchResourceVersion, "oldResourceVersion", lastResourceVersion) + + if updateErr := dr.kvstore.Set(ctx, orgID, k8sDashboardKvNamespace, + k8sDashboardKvLastResourceVersionKey, maxBatchResourceVersion); updateErr != nil { + errs = append(errs, fmt.Errorf("failed to update resource version: %w", updateErr)) + } + } + } + + meta, _ := data.Object["metadata"].(map[string]interface{}) + continueToken, _ = meta["continue"].(string) + if continueToken == "" { + break + } + } + + if itemsProcessed > 0 { + dr.log.Info("Finished k8s dashboard resources cleanup", "orgID", orgID, "itemsProcessed", itemsProcessed) + } + + if len(errs) > 0 { + return errors.Join(errs...) + } + return nil +} + +// getLastResourceVersion retrieves the last processed resource version from kvstore +func (dr *DashboardServiceImpl) getLastResourceVersion(ctx context.Context, orgID int64) (string, error) { + lastResourceVersion, ok, err := dr.kvstore.Get(ctx, orgID, k8sDashboardKvNamespace, k8sDashboardKvLastResourceVersionKey) + if err != nil { + return "", fmt.Errorf("failed to get last resource version: %w", err) + } + + if !ok { + dr.log.Info("No last resource version found, starting from scratch", "orgID", orgID) + return "0", nil + } + + return lastResourceVersion, nil +} + +// listResourcesToCleanup lists resources that need to be cleaned up +func (dr *DashboardServiceImpl) listResourcesToCleanup(ctx context.Context, orgID int64, resourceVersion, continueToken string, batchSize int64) (*unstructured.UnstructuredList, error, bool) { + var listOptions v1.ListOptions + if continueToken != "" { + listOptions = v1.ListOptions{ + LabelSelector: utils.LabelKeyGetTrash + "=true", + Continue: continueToken, + Limit: batchSize, + } + } else { + listOptions = v1.ListOptions{ + LabelSelector: utils.LabelKeyGetTrash + "=true", + ResourceVersionMatch: v1.ResourceVersionMatchNotOlderThan, + ResourceVersion: resourceVersion, + Limit: batchSize, + } + } + + data, err := dr.k8sclient.List(ctx, orgID, listOptions) + if err != nil { + if strings.Contains(err.Error(), "too old resource version") { + // If the resource version is too old, start from the current version + dr.log.Info("Resource version too old, starting from current version", "orgID", orgID) + return nil, nil, true // Signal to continue with reset version + } + return nil, err, false + } + + return data, nil, false +} + +// processDashboardBatch processes a batch of dashboards for cleanup +func (dr *DashboardServiceImpl) processDashboardBatch(ctx context.Context, orgID int64, items []unstructured.Unstructured) (int, []error) { + var errs []error + itemsProcessed := 0 + + for _, item := range items { + dash, err := dr.UnstructuredToLegacyDashboard(ctx, &item, orgID) + if err != nil { + errs = append(errs, fmt.Errorf("failed to convert dashboard: %w", err)) + continue + } + + meta, _ := item.Object["metadata"].(map[string]interface{}) + deletionTimestamp, _ := meta["deletionTimestamp"].(string) + resourceVersion, _ := meta["resourceVersion"].(string) + + dr.log.Info("K8s dashboard resource previously got deleted, cleaning up", + "UID", dash.UID, + "orgID", orgID, + "deletionTimestamp", deletionTimestamp, + "resourceVersion", resourceVersion) + + if err = dr.CleanUpDashboard(ctx, dash.UID, orgID); err != nil { + errs = append(errs, fmt.Errorf("failed to clean up dashboard %s: %w", dash.UID, err)) + } + itemsProcessed++ + } + + return itemsProcessed, errs +} + +// This gets auto-invoked when grafana starts, part of the BackgroundService interface +func (dr *DashboardServiceImpl) Run(ctx context.Context) error { + cleanupBackgroundJobStopped := dr.startK8sDeletedDashboardsCleanupJob(ctx) + <-ctx.Done() + // Wait for cleanup job to finish + <-cleanupBackgroundJobStopped + return ctx.Err() +} + var _ dashboards.PermissionsRegistrationService = (*DashboardServiceImpl)(nil) +var _ registry.BackgroundService = (*DashboardServiceImpl)(nil) // This is the uber service that implements a three smaller services func ProvideDashboardServiceImpl( @@ -97,6 +365,8 @@ func ProvideDashboardServiceImpl( restConfigProvider apiserver.RestConfigProvider, userService user.Service, quotaService quota.Service, orgService org.Service, publicDashboardService publicdashboards.ServiceWrapper, resourceClient resource.ResourceClient, dual dualwrite.Service, sorter sort.Service, + serverLockService *serverlock.ServerLockService, + kvstore kvstore.KVStore, ) (*DashboardServiceImpl, error) { k8sHandler := client.NewK8sHandler(dual, request.GetNamespaceMapper(cfg), dashboardv0alpha1.DashboardResourceInfo.GroupVersionResource(), restConfigProvider.GetRestConfig, dashboardStore, userService, resourceClient, sorter) @@ -114,6 +384,8 @@ func ProvideDashboardServiceImpl( metrics: newDashboardsMetrics(r), dashboardPermissionsReady: make(chan struct{}), publicDashboardService: publicDashboardService, + serverLockService: serverLockService, + kvstore: kvstore, } defaultLimits, err := readQuotaConfig(cfg) @@ -413,14 +685,24 @@ func (dr *DashboardServiceImpl) BuildSaveDashboardCommand(ctx context.Context, d } if isParentFolderChanged { - // Check that the user is allowed to add a dashboard to the folder - guardian, err := guardian.NewByDashboard(ctx, dash, dto.OrgID, dto.User) - if err != nil { - return nil, err + if canCreate, err := dr.canCreateDashboard(ctx, dto.User, dash); err != nil || !canCreate { + if err != nil { + return nil, err + } + return nil, dashboards.ErrDashboardUpdateAccessDenied } + } + + if dash.ID == 0 { metrics.MFolderIDsServiceCount.WithLabelValues(metrics.Dashboard).Inc() - // nolint:staticcheck - if canSave, err := guardian.CanCreate(dash.FolderID, dash.IsFolder); err != nil || !canSave { + if canCreate, err := dr.canCreateDashboard(ctx, dto.User, dash); err != nil || !canCreate { + if err != nil { + return nil, err + } + return nil, dashboards.ErrDashboardUpdateAccessDenied + } + } else { + if canSave, err := dr.canSaveDashboard(ctx, dto.User, dash); err != nil || !canSave { if err != nil { return nil, err } @@ -439,29 +721,6 @@ func (dr *DashboardServiceImpl) BuildSaveDashboardCommand(ctx context.Context, d } } - guard, err := getGuardianForSavePermissionCheck(ctx, dash, dto.User) - if err != nil { - return nil, err - } - - if dash.ID == 0 { - metrics.MFolderIDsServiceCount.WithLabelValues(metrics.Dashboard).Inc() - // nolint:staticcheck - if canCreate, err := guard.CanCreate(dash.FolderID, dash.IsFolder); err != nil || !canCreate { - if err != nil { - return nil, err - } - return nil, dashboards.ErrDashboardUpdateAccessDenied - } - } else { - if canSave, err := guard.CanSave(); err != nil || !canSave { - if err != nil { - return nil, err - } - return nil, dashboards.ErrDashboardUpdateAccessDenied - } - } - var userID int64 if id, err := identity.UserIdentifier(dto.User.GetID()); err == nil { userID = id @@ -561,6 +820,30 @@ func (dr *DashboardServiceImpl) ValidateDashboardBeforeSave(ctx context.Context, return isParentFolderChanged, nil } +func (dr *DashboardServiceImpl) canSaveDashboard(ctx context.Context, user identity.Requester, dash *dashboards.Dashboard) (bool, error) { + action := dashboards.ActionDashboardsWrite + if dash.IsFolder { + action = dashboards.ActionFoldersWrite + } + scope := dashboards.ScopeDashboardsProvider.GetResourceScopeUID(dash.UID) + if dash.IsFolder { + scope = dashboards.ScopeFoldersProvider.GetResourceScopeUID(dash.UID) + } + return dr.ac.Evaluate(ctx, user, accesscontrol.EvalPermission(action, scope)) +} + +func (dr *DashboardServiceImpl) canCreateDashboard(ctx context.Context, user identity.Requester, dash *dashboards.Dashboard) (bool, error) { + action := dashboards.ActionDashboardsCreate + if dash.IsFolder { + action = dashboards.ActionFoldersCreate + } + scope := dashboards.ScopeFoldersProvider.GetResourceScopeUID(dash.FolderUID) + if dash.FolderUID == "" { + scope = dashboards.ScopeFoldersProvider.GetResourceScopeUID(accesscontrol.GeneralFolderUID) + } + return dr.ac.Evaluate(ctx, user, accesscontrol.EvalPermission(action, scope)) +} + // waitForSearchQuery waits for the search query to return the expected number of hits. // Since US doesn't offer search-after-write guarantees, we can use this to wait after writes until the indexer is up to date. func (dr *DashboardServiceImpl) waitForSearchQuery(ctx context.Context, query *dashboards.FindPersistedDashboardsQuery, maxRetries int, expectedHits int64) error { @@ -620,46 +903,6 @@ func (dr *DashboardServiceImpl) DeleteOrphanedProvisionedDashboards(ctx context. return dr.dashboardStore.DeleteOrphanedProvisionedDashboards(ctx, cmd) } -// getGuardianForSavePermissionCheck returns the guardian to be used for checking permission of dashboard -// It replaces deleted Dashboard.GetDashboardIdForSavePermissionCheck() -func getGuardianForSavePermissionCheck(ctx context.Context, d *dashboards.Dashboard, user identity.Requester) (guardian.DashboardGuardian, error) { - ctx, span := tracer.Start(ctx, "dashboards.service.getGuardianForSavePermissionCheck") - defer span.End() - - newDashboard := d.ID == 0 - - if newDashboard { - // if it's a new dashboard/folder check the parent folder permissions - metrics.MFolderIDsServiceCount.WithLabelValues(metrics.Dashboard).Inc() - guard, err := guardian.NewByFolder(ctx, &folder.Folder{ - ID: d.FolderID, // nolint:staticcheck - OrgID: d.OrgID, - }, d.OrgID, user) - if err != nil { - return nil, err - } - return guard, nil - } - - if d.IsFolder { - guard, err := guardian.NewByFolder(ctx, &folder.Folder{ - ID: d.ID, // nolint:staticcheck - UID: d.UID, - OrgID: d.OrgID, - }, d.OrgID, user) - if err != nil { - return nil, err - } - return guard, nil - } - - guard, err := guardian.NewByDashboard(ctx, d, d.OrgID, user) - if err != nil { - return nil, err - } - return guard, nil -} - func validateDashboardRefreshInterval(minRefreshInterval string, dash *dashboards.Dashboard) error { if minRefreshInterval == "" { return nil @@ -787,7 +1030,7 @@ func (dr *DashboardServiceImpl) saveDashboard(ctx context.Context, cmd *dashboar func (dr *DashboardServiceImpl) GetSoftDeletedDashboard(ctx context.Context, orgID int64, uid string) (*dashboards.Dashboard, error) { if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) { - return dr.getDashboardThroughK8s(ctx, &dashboards.GetDashboardQuery{OrgID: orgID, UID: uid, IncludeDeleted: true}) + return dr.getDashboardThroughK8s(ctx, &dashboards.GetDashboardQuery{OrgID: orgID, UID: uid}) } return dr.dashboardStore.GetSoftDeletedDashboard(ctx, orgID, uid) @@ -890,18 +1133,7 @@ func (dr *DashboardServiceImpl) deleteDashboard(ctx context.Context, dashboardId cmd := &dashboards.DeleteDashboardCommand{OrgID: orgId, ID: dashboardId, UID: dashboardUID} if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) { - err := dr.deleteDashboardThroughK8s(ctx, cmd, validateProvisionedDashboard) - if err != nil { - return err - } - - // cleanup things related to dashboards that are not stored in unistore yet - err = dr.publicDashboardService.DeleteByDashboardUIDs(ctx, orgId, []string{dashboardUID}) - if err != nil { - return err - } - - return dr.dashboardStore.CleanupAfterDelete(ctx, cmd) + return dr.deleteDashboardThroughK8s(ctx, cmd, validateProvisionedDashboard) } if validateProvisionedDashboard { @@ -1524,6 +1756,19 @@ func (dr *DashboardServiceImpl) DeleteInFolders(ctx context.Context, orgID int64 func (dr *DashboardServiceImpl) Kind() string { return entity.StandardKindDashboard } +func (dr *DashboardServiceImpl) CleanUpDashboard(ctx context.Context, dashboardUID string, orgId int64) error { + ctx, span := tracer.Start(ctx, "dashboards.service.CleanUpDashboard") + defer span.End() + + // cleanup things related to dashboards that are not stored in unistore yet + var err = dr.publicDashboardService.DeleteByDashboardUIDs(ctx, orgId, []string{dashboardUID}) + if err != nil { + return err + } + + return dr.dashboardStore.CleanupAfterDelete(ctx, &dashboards.DeleteDashboardCommand{OrgID: orgId, UID: dashboardUID}) +} + func (dr *DashboardServiceImpl) CleanUpDeletedDashboards(ctx context.Context) (int64, error) { ctx, span := tracer.Start(ctx, "dashboards.service.CleanUpDeletedDashboards") defer span.End() @@ -1550,12 +1795,6 @@ func (dr *DashboardServiceImpl) CleanUpDeletedDashboards(ctx context.Context) (i // ----------------------------------------------------------------------------------------- func (dr *DashboardServiceImpl) getDashboardThroughK8s(ctx context.Context, query *dashboards.GetDashboardQuery) (*dashboards.Dashboard, error) { - // if including deleted dashboards for restore, use the /latest subresource - subresource := "" - if query.IncludeDeleted && dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesRestore) { - subresource = "latest" - } - // get uid if not passed in if query.UID == "" { result, err := dr.GetDashboardUIDByID(ctx, &dashboards.GetDashboardRefByIDQuery{ @@ -1568,7 +1807,7 @@ func (dr *DashboardServiceImpl) getDashboardThroughK8s(ctx context.Context, quer query.UID = result.UID } - out, err := dr.k8sclient.Get(ctx, query.UID, query.OrgID, v1.GetOptions{}, subresource) + out, err := dr.k8sclient.Get(ctx, query.UID, query.OrgID, v1.GetOptions{}, "") if err != nil && !apierrors.IsNotFound(err) { return nil, err } else if err != nil || out == nil { @@ -1606,12 +1845,13 @@ func (dr *DashboardServiceImpl) saveProvisionedDashboardThroughK8s(ctx context.C meta.SetManagerProperties(m) meta.SetSourceProperties(s) - out, err := dr.createOrUpdateDash(ctx, obj, cmd.OrgID) + // Update will create if not exists (upsert!) + out, err := dr.k8sclient.Update(ctx, obj, cmd.OrgID) if err != nil { return nil, err } - return out, nil + return dr.UnstructuredToLegacyDashboard(ctx, out, cmd.OrgID) } func (dr *DashboardServiceImpl) saveDashboardThroughK8s(ctx context.Context, cmd *dashboards.SaveDashboardCommand, orgID int64) (*dashboards.Dashboard, error) { @@ -1622,35 +1862,13 @@ func (dr *DashboardServiceImpl) saveDashboardThroughK8s(ctx context.Context, cmd dashboard.SetPluginIDMeta(obj, cmd.PluginID) - out, err := dr.createOrUpdateDash(ctx, obj, orgID) + // Update will create if not exists (upsert!) + out, err := dr.k8sclient.Update(ctx, obj, orgID) if err != nil { return nil, err } - return out, nil -} - -func (dr *DashboardServiceImpl) createOrUpdateDash(ctx context.Context, obj *unstructured.Unstructured, orgID int64) (*dashboards.Dashboard, error) { - var out *unstructured.Unstructured - current, err := dr.k8sclient.Get(ctx, obj.GetName(), orgID, v1.GetOptions{}) - if current == nil || err != nil { - out, err = dr.k8sclient.Create(ctx, obj, orgID) - if err != nil { - return nil, err - } - } else { - out, err = dr.k8sclient.Update(ctx, obj, orgID) - if err != nil { - return nil, err - } - } - - finalDash, err := dr.UnstructuredToLegacyDashboard(ctx, out, orgID) - if err != nil { - return nil, err - } - - return finalDash, nil + return dr.UnstructuredToLegacyDashboard(ctx, out, orgID) } func (dr *DashboardServiceImpl) deleteAllDashboardThroughK8s(ctx context.Context, orgID int64) error { diff --git a/pkg/services/dashboards/service/dashboard_service_integration_test.go b/pkg/services/dashboards/service/dashboard_service_integration_test.go index 221ade24f72..7887e628110 100644 --- a/pkg/services/dashboards/service/dashboard_service_integration_test.go +++ b/pkg/services/dashboards/service/dashboard_service_integration_test.go @@ -8,13 +8,14 @@ import ( "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" - "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/infra/db" - "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/infra/kvstore" + "github.com/grafana/grafana/pkg/infra/serverlock" "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/services/accesscontrol" + "github.com/grafana/grafana/pkg/services/accesscontrol/acimpl" "github.com/grafana/grafana/pkg/services/accesscontrol/actest" accesscontrolmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock" "github.com/grafana/grafana/pkg/services/apiserver/client" @@ -23,7 +24,6 @@ import ( "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/folder" "github.com/grafana/grafana/pkg/services/folder/folderimpl" - "github.com/grafana/grafana/pkg/services/guardian" "github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/services/publicdashboards" "github.com/grafana/grafana/pkg/services/quota/quotatest" @@ -49,809 +49,710 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) { t.Run("Given saved folders and dashboards in organization A", func(t *testing.T) { // Basic validation tests - permissionScenario(t, "When saving a dashboard with non-existing id", true, - func(t *testing.T, sc *permissionScenarioContext) { - cmd := dashboards.SaveDashboardCommand{ - OrgID: testOrgID, - Dashboard: simplejson.NewFromAny(map[string]any{ - "id": float64(123412321), - "title": "Expect error", - }), - } + permissionScenario(t, "When saving a dashboard with non-existing id", func(t *testing.T, sc *permissionScenarioContext) { + cmd := dashboards.SaveDashboardCommand{ + OrgID: testOrgID, + Dashboard: simplejson.NewFromAny(map[string]any{ + "id": float64(123412321), + "title": "Expect error", + }), + } - err := callSaveWithError(t, cmd, sc.sqlStore) - assert.Equal(t, dashboards.ErrDashboardNotFound, err) - }) + _, err := callSaveWithResult(t, cmd, sc.sqlStore, nil) + assert.Equal(t, dashboards.ErrDashboardNotFound, err) + }) // Given other organization t.Run("Given organization B", func(t *testing.T) { const otherOrgId int64 = 2 - permissionScenario(t, "When creating a dashboard with same id as dashboard in organization A", - true, func(t *testing.T, sc *permissionScenarioContext) { - cmd := dashboards.SaveDashboardCommand{ - OrgID: otherOrgId, - Dashboard: simplejson.NewFromAny(map[string]any{ - "id": sc.savedDashInFolder.ID, - "title": "Expect error", - }), - Overwrite: false, - } + permissionScenario(t, "When creating a dashboard with same id as dashboard in organization A", func(t *testing.T, sc *permissionScenarioContext) { + cmd := dashboards.SaveDashboardCommand{ + OrgID: otherOrgId, + Dashboard: simplejson.NewFromAny(map[string]any{ + "id": sc.savedDashInFolder.ID, + "title": "Expect error", + }), + Overwrite: false, + } - err := callSaveWithError(t, cmd, sc.sqlStore) - assert.Equal(t, dashboards.ErrDashboardNotFound, err) - }) - - permissionScenario(t, "When creating a dashboard with same uid as dashboard in organization A, it should create a new dashboard in org B", - true, func(t *testing.T, sc *permissionScenarioContext) { - const otherOrgId int64 = 2 - cmd := dashboards.SaveDashboardCommand{ - OrgID: otherOrgId, - Dashboard: simplejson.NewFromAny(map[string]any{ - "uid": sc.savedDashInFolder.UID, - "title": "Dash with existing uid in other org", - }), - Overwrite: false, - } - - res := callSaveWithResult(t, cmd, sc.sqlStore) - require.NotNil(t, res) - - _, err := sc.dashboardStore.GetDashboard(context.Background(), &dashboards.GetDashboardQuery{ - OrgID: otherOrgId, - UID: sc.savedDashInFolder.UID, - }) - require.NoError(t, err) - }) - }) - - t.Run("Given user has no permission to save", func(t *testing.T) { - const canSave = false - - permissionScenario(t, "When creating a new dashboard in the General folder", canSave, - func(t *testing.T, sc *permissionScenarioContext) { - sqlStore := db.InitTestDB(t) - cmd := dashboards.SaveDashboardCommand{ - OrgID: testOrgID, - Dashboard: simplejson.NewFromAny(map[string]any{ - "title": "Dash", - }), - UserID: 10000, - Overwrite: true, - } - - err := callSaveWithError(t, cmd, sqlStore) - assert.Equal(t, dashboards.ErrDashboardUpdateAccessDenied, err) - - userID, err := identity.IntIdentifier(sc.dashboardGuardianMock.User.GetID()) - require.NoError(t, err) - - assert.Equal(t, "", sc.dashboardGuardianMock.DashUID) - assert.Equal(t, cmd.OrgID, sc.dashboardGuardianMock.OrgID) - assert.Equal(t, cmd.UserID, userID) - }) - - permissionScenario(t, "When creating a new dashboard in other folder, it should create dashboard guardian for other folder with correct arguments and rsult in access denied error", - canSave, func(t *testing.T, sc *permissionScenarioContext) { - cmd := dashboards.SaveDashboardCommand{ - OrgID: testOrgID, - Dashboard: simplejson.NewFromAny(map[string]any{ - "title": "Dash", - }), - FolderUID: sc.otherSavedFolder.UID, - UserID: 10000, - Overwrite: true, - } - - err := callSaveWithError(t, cmd, sc.sqlStore) - require.Equal(t, dashboards.ErrDashboardUpdateAccessDenied, err) - - userID, err := identity.IntIdentifier(sc.dashboardGuardianMock.User.GetID()) - require.NoError(t, err) - - assert.Equal(t, sc.otherSavedFolder.ID, sc.dashboardGuardianMock.DashID) - assert.Equal(t, cmd.OrgID, sc.dashboardGuardianMock.OrgID) - assert.Equal(t, cmd.UserID, userID) - }) - - permissionScenario(t, "When creating a new dashboard by existing title in folder, it should create dashboard guardian for dashboard with correct arguments and result in access denied error", - canSave, func(t *testing.T, sc *permissionScenarioContext) { - t.Skip() - - cmd := dashboards.SaveDashboardCommand{ - OrgID: testOrgID, - Dashboard: simplejson.NewFromAny(map[string]any{ - "title": sc.savedDashInFolder.Title, - }), - FolderUID: sc.savedFolder.UID, - UserID: 10000, - Overwrite: true, - } - - err := callSaveWithError(t, cmd, sc.sqlStore) - require.Equal(t, dashboards.ErrDashboardUpdateAccessDenied, err) - - userID, err := identity.IntIdentifier(sc.dashboardGuardianMock.User.GetID()) - require.NoError(t, err) - - assert.Equal(t, sc.savedDashInFolder.UID, sc.dashboardGuardianMock.DashUID) - assert.Equal(t, cmd.OrgID, sc.dashboardGuardianMock.OrgID) - assert.Equal(t, cmd.UserID, userID) - }) - - permissionScenario(t, "When creating a new dashboard by existing UID in folder, it should create dashboard guardian for dashboard with correct arguments and result in access denied error", - canSave, func(t *testing.T, sc *permissionScenarioContext) { - cmd := dashboards.SaveDashboardCommand{ - OrgID: testOrgID, - Dashboard: simplejson.NewFromAny(map[string]any{ - "uid": sc.savedDashInFolder.UID, - "title": "New dash", - }), - FolderUID: sc.savedFolder.UID, - UserID: 10000, - Overwrite: true, - } - - err := callSaveWithError(t, cmd, sc.sqlStore) - require.Equal(t, dashboards.ErrDashboardUpdateAccessDenied, err) - - userID, err := identity.IntIdentifier(sc.dashboardGuardianMock.User.GetID()) - require.NoError(t, err) - - assert.Equal(t, sc.savedDashInFolder.UID, sc.dashboardGuardianMock.DashUID) - assert.Equal(t, cmd.OrgID, sc.dashboardGuardianMock.OrgID) - assert.Equal(t, cmd.UserID, userID) - }) - - permissionScenario(t, "When updating a dashboard by existing id in the General folder, it should create dashboard guardian for dashboard with correct arguments and result in access denied error", - canSave, func(t *testing.T, sc *permissionScenarioContext) { - cmd := dashboards.SaveDashboardCommand{ - OrgID: testOrgID, - Dashboard: simplejson.NewFromAny(map[string]any{ - "id": sc.savedDashInGeneralFolder.ID, - "title": "Dash", - }), - FolderUID: sc.savedDashInGeneralFolder.FolderUID, - UserID: 10000, - Overwrite: true, - } - - err := callSaveWithError(t, cmd, sc.sqlStore) - assert.Equal(t, dashboards.ErrDashboardUpdateAccessDenied, err) - - userID, err := identity.IntIdentifier(sc.dashboardGuardianMock.User.GetID()) - require.NoError(t, err) - - assert.Equal(t, sc.savedDashInGeneralFolder.UID, sc.dashboardGuardianMock.DashUID) - assert.Equal(t, cmd.OrgID, sc.dashboardGuardianMock.OrgID) - assert.Equal(t, cmd.UserID, userID) - }) - - permissionScenario(t, "When updating a dashboard by existing id in other folder, it should create dashboard guardian for dashboard with correct arguments and result in access denied error", - canSave, func(t *testing.T, sc *permissionScenarioContext) { - cmd := dashboards.SaveDashboardCommand{ - OrgID: testOrgID, - Dashboard: simplejson.NewFromAny(map[string]any{ - "id": sc.savedDashInFolder.ID, - "title": "Dash", - }), - FolderUID: sc.savedDashInFolder.FolderUID, - UserID: 10000, - Overwrite: true, - } - - err := callSaveWithError(t, cmd, sc.sqlStore) - require.Equal(t, dashboards.ErrDashboardUpdateAccessDenied, err) - - userID, err := identity.IntIdentifier(sc.dashboardGuardianMock.User.GetID()) - require.NoError(t, err) - - assert.Equal(t, sc.savedDashInFolder.UID, sc.dashboardGuardianMock.DashUID) - assert.Equal(t, cmd.OrgID, sc.dashboardGuardianMock.OrgID) - assert.Equal(t, cmd.UserID, userID) - }) - - permissionScenario(t, "When moving a dashboard by existing ID to other folder from General folder, it should create dashboard guardian for dashboard with correct arguments and result in access denied error", - canSave, func(t *testing.T, sc *permissionScenarioContext) { - cmd := dashboards.SaveDashboardCommand{ - OrgID: testOrgID, - Dashboard: simplejson.NewFromAny(map[string]any{ - "id": sc.savedDashInGeneralFolder.ID, - "title": "Dash", - }), - FolderUID: sc.otherSavedFolder.UID, - UserID: 10000, - Overwrite: true, - } - - err := callSaveWithError(t, cmd, sc.sqlStore) - require.Equal(t, dashboards.ErrDashboardUpdateAccessDenied, err) - - userID, err := identity.IntIdentifier(sc.dashboardGuardianMock.User.GetID()) - require.NoError(t, err) - - assert.Equal(t, sc.savedDashInGeneralFolder.UID, sc.dashboardGuardianMock.DashUID) - assert.Equal(t, cmd.OrgID, sc.dashboardGuardianMock.OrgID) - assert.Equal(t, cmd.UserID, userID) - }) - - permissionScenario(t, "When moving a dashboard by existing id to the General folder from other folder, it should create dashboard guardian for dashboard with correct arguments and result in access denied error", - canSave, func(t *testing.T, sc *permissionScenarioContext) { - cmd := dashboards.SaveDashboardCommand{ - OrgID: testOrgID, - Dashboard: simplejson.NewFromAny(map[string]any{ - "id": sc.savedDashInFolder.ID, - "title": "Dash", - }), - FolderUID: "", - UserID: 10000, - Overwrite: true, - } - - err := callSaveWithError(t, cmd, sc.sqlStore) - assert.Equal(t, dashboards.ErrDashboardUpdateAccessDenied, err) - - userID, err := identity.IntIdentifier(sc.dashboardGuardianMock.User.GetID()) - require.NoError(t, err) - - assert.Equal(t, sc.savedDashInFolder.UID, sc.dashboardGuardianMock.DashUID) - assert.Equal(t, cmd.OrgID, sc.dashboardGuardianMock.OrgID) - assert.Equal(t, cmd.UserID, userID) - }) - - permissionScenario(t, "When moving a dashboard by existing uid to other folder from General folder, it should create dashboard guardian for dashboard with correct arguments and result in access denied error", - canSave, func(t *testing.T, sc *permissionScenarioContext) { - cmd := dashboards.SaveDashboardCommand{ - OrgID: testOrgID, - Dashboard: simplejson.NewFromAny(map[string]any{ - "uid": sc.savedDashInGeneralFolder.UID, - "title": "Dash", - }), - FolderUID: sc.otherSavedFolder.UID, - UserID: 10000, - Overwrite: true, - } - - err := callSaveWithError(t, cmd, sc.sqlStore) - require.Equal(t, dashboards.ErrDashboardUpdateAccessDenied, err) - - userID, err := identity.IntIdentifier(sc.dashboardGuardianMock.User.GetID()) - require.NoError(t, err) - - assert.Equal(t, sc.savedDashInGeneralFolder.UID, sc.dashboardGuardianMock.DashUID) - assert.Equal(t, cmd.OrgID, sc.dashboardGuardianMock.OrgID) - assert.Equal(t, cmd.UserID, userID) - }) - - permissionScenario(t, "When moving a dashboard by existing UID to the General folder from other folder, it should create dashboard guardian for dashboard with correct arguments and result in access denied error", - canSave, func(t *testing.T, sc *permissionScenarioContext) { - cmd := dashboards.SaveDashboardCommand{ - OrgID: testOrgID, - Dashboard: simplejson.NewFromAny(map[string]any{ - "uid": sc.savedDashInFolder.UID, - "title": "Dash", - }), - FolderUID: "", - UserID: 10000, - Overwrite: true, - } - - err := callSaveWithError(t, cmd, sc.sqlStore) - require.Equal(t, dashboards.ErrDashboardUpdateAccessDenied, err) - - userID, err := identity.IntIdentifier(sc.dashboardGuardianMock.User.GetID()) - require.NoError(t, err) - - assert.Equal(t, sc.savedDashInFolder.UID, sc.dashboardGuardianMock.DashUID) - assert.Equal(t, cmd.OrgID, sc.dashboardGuardianMock.OrgID) - assert.Equal(t, cmd.UserID, userID) + _, err := callSaveWithResult(t, cmd, sc.sqlStore, nil) + assert.Equal(t, dashboards.ErrDashboardNotFound, err) + }) + + permissionScenario(t, "When creating a dashboard with same uid as dashboard in organization A, it should create a new dashboard in org B", func(t *testing.T, sc *permissionScenarioContext) { + const otherOrgId int64 = 2 + cmd := dashboards.SaveDashboardCommand{ + OrgID: otherOrgId, + Dashboard: simplejson.NewFromAny(map[string]any{ + "uid": sc.savedDashInFolder.UID, + "title": "Dash with existing uid in other org", + }), + Overwrite: false, + } + + res, _ := callSaveWithResult(t, cmd, sc.sqlStore, nil) + require.NotNil(t, res) + + _, err := sc.dashboardStore.GetDashboard(context.Background(), &dashboards.GetDashboardQuery{ + OrgID: otherOrgId, + UID: sc.savedDashInFolder.UID, }) + require.NoError(t, err) + }) }) t.Run("Given user has permission to save", func(t *testing.T) { - const canSave = true - t.Run("and overwrite flag is set to false", func(t *testing.T) { const shouldOverwrite = false - permissionScenario(t, "When creating a dashboard in General folder with same name as dashboard in other folder", - canSave, func(t *testing.T, sc *permissionScenarioContext) { - cmd := dashboards.SaveDashboardCommand{ - OrgID: testOrgID, - Dashboard: simplejson.NewFromAny(map[string]any{ - "id": nil, - "title": sc.savedDashInFolder.Title, - }), - FolderUID: "", - Overwrite: shouldOverwrite, - } + permissionScenario(t, "When creating a dashboard in General folder with same name as dashboard in other folder", func(t *testing.T, sc *permissionScenarioContext) { + cmd := dashboards.SaveDashboardCommand{ + OrgID: testOrgID, + Dashboard: simplejson.NewFromAny(map[string]any{ + "id": nil, + "title": sc.savedDashInFolder.Title, + }), + FolderUID: "", + Overwrite: shouldOverwrite, + } - res := callSaveWithResult(t, cmd, sc.sqlStore) - require.NotNil(t, res) + res, _ := callSaveWithResult(t, cmd, sc.sqlStore, nil) + require.NotNil(t, res) - _, err := sc.dashboardStore.GetDashboard(context.Background(), &dashboards.GetDashboardQuery{ - ID: res.ID, - OrgID: cmd.OrgID, - }) - - require.NoError(t, err) + _, err := sc.dashboardStore.GetDashboard(context.Background(), &dashboards.GetDashboardQuery{ + ID: res.ID, + OrgID: cmd.OrgID, }) - permissionScenario(t, "When creating a dashboard in other folder with same name as dashboard in General folder", - canSave, func(t *testing.T, sc *permissionScenarioContext) { - cmd := dashboards.SaveDashboardCommand{ - OrgID: testOrgID, - Dashboard: simplejson.NewFromAny(map[string]any{ - "id": nil, - "title": sc.savedDashInGeneralFolder.Title, - }), - FolderUID: sc.savedFolder.UID, - Overwrite: shouldOverwrite, - } + require.NoError(t, err) + }) - res := callSaveWithResult(t, cmd, sc.sqlStore) - require.NotNil(t, res) + permissionScenario(t, "When creating a dashboard in other folder with same name as dashboard in General folder", func(t *testing.T, sc *permissionScenarioContext) { + cmd := dashboards.SaveDashboardCommand{ + OrgID: testOrgID, + Dashboard: simplejson.NewFromAny(map[string]any{ + "id": nil, + "title": sc.savedDashInGeneralFolder.Title, + }), + FolderUID: sc.savedFolder.UID, + Overwrite: shouldOverwrite, + } - assert.NotEqual(t, sc.savedDashInGeneralFolder.ID, res.ID) + res, _ := callSaveWithResult(t, cmd, sc.sqlStore, nil) + require.NotNil(t, res) - _, err := sc.dashboardStore.GetDashboard(context.Background(), &dashboards.GetDashboardQuery{ - ID: res.ID, - OrgID: cmd.OrgID, - }) - require.NoError(t, err) + assert.NotEqual(t, sc.savedDashInGeneralFolder.ID, res.ID) + + _, err := sc.dashboardStore.GetDashboard(context.Background(), &dashboards.GetDashboardQuery{ + ID: res.ID, + OrgID: cmd.OrgID, + }) + require.NoError(t, err) + }) + + permissionScenario(t, "When creating a folder with same name as dashboard in other folder", func(t *testing.T, sc *permissionScenarioContext) { + cmd := dashboards.SaveDashboardCommand{ + OrgID: testOrgID, + Dashboard: simplejson.NewFromAny(map[string]any{ + "id": nil, + "title": sc.savedDashInFolder.Title, + }), + IsFolder: true, + Overwrite: shouldOverwrite, + } + + res, _ := callSaveWithResult(t, cmd, sc.sqlStore, nil) + require.NotNil(t, res) + + assert.NotEqual(t, sc.savedDashInGeneralFolder.ID, res.ID) + assert.True(t, res.IsFolder) + + _, err := sc.dashboardStore.GetDashboard(context.Background(), &dashboards.GetDashboardQuery{ + ID: res.ID, + OrgID: cmd.OrgID, + }) + require.NoError(t, err) + }) + + permissionScenario(t, "When saving a dashboard without id and uid and unique title in folder", func(t *testing.T, sc *permissionScenarioContext) { + cmd := dashboards.SaveDashboardCommand{ + OrgID: testOrgID, + Dashboard: simplejson.NewFromAny(map[string]any{ + "title": "Dash without id and uid", + }), + Overwrite: shouldOverwrite, + } + + res, _ := callSaveWithResult(t, cmd, sc.sqlStore, nil) + require.NotNil(t, res) + + assert.Greater(t, res.ID, int64(0)) + assert.NotEmpty(t, res.UID) + _, err := sc.dashboardStore.GetDashboard(context.Background(), &dashboards.GetDashboardQuery{ + ID: res.ID, + OrgID: cmd.OrgID, + }) + require.NoError(t, err) + }) + + permissionScenario(t, "When saving a dashboard when dashboard id is zero ", func(t *testing.T, sc *permissionScenarioContext) { + cmd := dashboards.SaveDashboardCommand{ + OrgID: testOrgID, + Dashboard: simplejson.NewFromAny(map[string]any{ + "id": 0, + "title": "Dash with zero id", + }), + Overwrite: shouldOverwrite, + } + + res, _ := callSaveWithResult(t, cmd, sc.sqlStore, nil) + require.NotNil(t, res) + + _, err := sc.dashboardStore.GetDashboard(context.Background(), &dashboards.GetDashboardQuery{ + ID: res.ID, + OrgID: cmd.OrgID, + }) + require.NoError(t, err) + }) + + permissionScenario(t, "When saving a dashboard in non-existing folder", func(t *testing.T, sc *permissionScenarioContext) { + cmd := dashboards.SaveDashboardCommand{ + OrgID: testOrgID, + Dashboard: simplejson.NewFromAny(map[string]any{ + "title": "Expect error", + }), + FolderUID: "123412321", + Overwrite: shouldOverwrite, + } + + _, err := callSaveWithResult(t, cmd, sc.sqlStore, nil) + assert.Equal(t, dashboards.ErrFolderNotFound, err) + }) + + permissionScenario(t, "When updating an existing dashboard by id without current version", func(t *testing.T, sc *permissionScenarioContext) { + cmd := dashboards.SaveDashboardCommand{ + OrgID: 1, + Dashboard: simplejson.NewFromAny(map[string]any{ + "id": sc.savedDashInGeneralFolder.ID, + "title": "test dash 23", + }), + FolderUID: sc.savedFolder.UID, + Overwrite: shouldOverwrite, + } + + _, err := callSaveWithResult(t, cmd, sc.sqlStore, nil) + assert.Equal(t, dashboards.ErrDashboardVersionMismatch, err) + }) + + permissionScenario(t, "When updating an existing dashboard by id with current version", func(t *testing.T, sc *permissionScenarioContext) { + cmd := dashboards.SaveDashboardCommand{ + OrgID: 1, + Dashboard: simplejson.NewFromAny(map[string]any{ + "id": sc.savedDashInGeneralFolder.ID, + "title": "Updated title", + "version": sc.savedDashInGeneralFolder.Version, + }), + FolderUID: sc.savedFolder.UID, + Overwrite: shouldOverwrite, + } + + res, _ := callSaveWithResult(t, cmd, sc.sqlStore, nil) + require.NotNil(t, res) + + _, err := sc.dashboardStore.GetDashboard(context.Background(), &dashboards.GetDashboardQuery{ + ID: sc.savedDashInGeneralFolder.ID, + OrgID: cmd.OrgID, }) - permissionScenario(t, "When creating a folder with same name as dashboard in other folder", - canSave, func(t *testing.T, sc *permissionScenarioContext) { - cmd := dashboards.SaveDashboardCommand{ - OrgID: testOrgID, - Dashboard: simplejson.NewFromAny(map[string]any{ - "id": nil, - "title": sc.savedDashInFolder.Title, - }), - IsFolder: true, - Overwrite: shouldOverwrite, - } + require.NoError(t, err) + }) - res := callSaveWithResult(t, cmd, sc.sqlStore) - require.NotNil(t, res) + permissionScenario(t, "When updating an existing dashboard by uid without current version", func(t *testing.T, sc *permissionScenarioContext) { + cmd := dashboards.SaveDashboardCommand{ + OrgID: 1, + Dashboard: simplejson.NewFromAny(map[string]any{ + "uid": sc.savedDashInFolder.UID, + "title": "test dash 23", + }), + FolderUID: "", + Overwrite: shouldOverwrite, + } - assert.NotEqual(t, sc.savedDashInGeneralFolder.ID, res.ID) - assert.True(t, res.IsFolder) + _, err := callSaveWithResult(t, cmd, sc.sqlStore, nil) + assert.Equal(t, dashboards.ErrDashboardVersionMismatch, err) + }) - _, err := sc.dashboardStore.GetDashboard(context.Background(), &dashboards.GetDashboardQuery{ - ID: res.ID, - OrgID: cmd.OrgID, - }) - require.NoError(t, err) + permissionScenario(t, "When updating an existing dashboard by uid with current version", func(t *testing.T, sc *permissionScenarioContext) { + cmd := dashboards.SaveDashboardCommand{ + OrgID: 1, + Dashboard: simplejson.NewFromAny(map[string]any{ + "uid": sc.savedDashInFolder.UID, + "title": "Updated title", + "version": sc.savedDashInFolder.Version, + }), + FolderUID: "", + Overwrite: shouldOverwrite, + } + + res, _ := callSaveWithResult(t, cmd, sc.sqlStore, nil) + require.NotNil(t, res) + + _, err := sc.dashboardStore.GetDashboard(context.Background(), &dashboards.GetDashboardQuery{ + ID: sc.savedDashInFolder.ID, + OrgID: cmd.OrgID, }) + require.NoError(t, err) + }) - permissionScenario(t, "When saving a dashboard without id and uid and unique title in folder", - canSave, func(t *testing.T, sc *permissionScenarioContext) { - cmd := dashboards.SaveDashboardCommand{ - OrgID: testOrgID, - Dashboard: simplejson.NewFromAny(map[string]any{ - "title": "Dash without id and uid", - }), - Overwrite: shouldOverwrite, - } + permissionScenario(t, "When creating a dashboard with same name as dashboard in other folder", func(t *testing.T, sc *permissionScenarioContext) { + cmd := dashboards.SaveDashboardCommand{ + OrgID: testOrgID, + Dashboard: simplejson.NewFromAny(map[string]any{ + "id": nil, + "title": sc.savedDashInFolder.Title, + }), + FolderUID: sc.savedDashInFolder.FolderUID, + Overwrite: shouldOverwrite, + } - res := callSaveWithResult(t, cmd, sc.sqlStore) - require.NotNil(t, res) + _, err := callSaveWithResult(t, cmd, sc.sqlStore, nil) + require.NoError(t, err) + }) - assert.Greater(t, res.ID, int64(0)) - assert.NotEmpty(t, res.UID) - _, err := sc.dashboardStore.GetDashboard(context.Background(), &dashboards.GetDashboardQuery{ - ID: res.ID, - OrgID: cmd.OrgID, - }) - require.NoError(t, err) - }) + permissionScenario(t, "When creating a dashboard with same name as dashboard in General folder", func(t *testing.T, sc *permissionScenarioContext) { + cmd := dashboards.SaveDashboardCommand{ + OrgID: testOrgID, + Dashboard: simplejson.NewFromAny(map[string]any{ + "id": nil, + "title": sc.savedDashInGeneralFolder.Title, + }), + FolderUID: sc.savedDashInGeneralFolder.FolderUID, + Overwrite: shouldOverwrite, + } - permissionScenario(t, "When saving a dashboard when dashboard id is zero ", canSave, - func(t *testing.T, sc *permissionScenarioContext) { - cmd := dashboards.SaveDashboardCommand{ - OrgID: testOrgID, - Dashboard: simplejson.NewFromAny(map[string]any{ - "id": 0, - "title": "Dash with zero id", - }), - Overwrite: shouldOverwrite, - } + _, err := callSaveWithResult(t, cmd, sc.sqlStore, nil) + require.NoError(t, err) + }) - res := callSaveWithResult(t, cmd, sc.sqlStore) - require.NotNil(t, res) + permissionScenario(t, "When creating a folder with same name as existing folder", func(t *testing.T, sc *permissionScenarioContext) { + cmd := dashboards.SaveDashboardCommand{ + OrgID: testOrgID, + Dashboard: simplejson.NewFromAny(map[string]any{ + "id": nil, + "title": sc.savedFolder.Title, + }), + IsFolder: true, + Overwrite: shouldOverwrite, + } - _, err := sc.dashboardStore.GetDashboard(context.Background(), &dashboards.GetDashboardQuery{ - ID: res.ID, - OrgID: cmd.OrgID, - }) - require.NoError(t, err) - }) - - permissionScenario(t, "When saving a dashboard in non-existing folder", canSave, - func(t *testing.T, sc *permissionScenarioContext) { - cmd := dashboards.SaveDashboardCommand{ - OrgID: testOrgID, - Dashboard: simplejson.NewFromAny(map[string]any{ - "title": "Expect error", - }), - FolderUID: "123412321", - Overwrite: shouldOverwrite, - } - - err := callSaveWithError(t, cmd, sc.sqlStore) - assert.Equal(t, dashboards.ErrFolderNotFound, err) - }) - - permissionScenario(t, "When updating an existing dashboard by id without current version", canSave, - func(t *testing.T, sc *permissionScenarioContext) { - cmd := dashboards.SaveDashboardCommand{ - OrgID: 1, - Dashboard: simplejson.NewFromAny(map[string]any{ - "id": sc.savedDashInGeneralFolder.ID, - "title": "test dash 23", - }), - FolderUID: sc.savedFolder.UID, - Overwrite: shouldOverwrite, - } - - err := callSaveWithError(t, cmd, sc.sqlStore) - assert.Equal(t, dashboards.ErrDashboardVersionMismatch, err) - }) - - permissionScenario(t, "When updating an existing dashboard by id with current version", canSave, - func(t *testing.T, sc *permissionScenarioContext) { - cmd := dashboards.SaveDashboardCommand{ - OrgID: 1, - Dashboard: simplejson.NewFromAny(map[string]any{ - "id": sc.savedDashInGeneralFolder.ID, - "title": "Updated title", - "version": sc.savedDashInGeneralFolder.Version, - }), - FolderUID: sc.savedFolder.UID, - Overwrite: shouldOverwrite, - } - - res := callSaveWithResult(t, cmd, sc.sqlStore) - require.NotNil(t, res) - - _, err := sc.dashboardStore.GetDashboard(context.Background(), &dashboards.GetDashboardQuery{ - ID: sc.savedDashInGeneralFolder.ID, - OrgID: cmd.OrgID, - }) - - require.NoError(t, err) - }) - - permissionScenario(t, "When updating an existing dashboard by uid without current version", canSave, - func(t *testing.T, sc *permissionScenarioContext) { - cmd := dashboards.SaveDashboardCommand{ - OrgID: 1, - Dashboard: simplejson.NewFromAny(map[string]any{ - "uid": sc.savedDashInFolder.UID, - "title": "test dash 23", - }), - FolderUID: "", - Overwrite: shouldOverwrite, - } - - err := callSaveWithError(t, cmd, sc.sqlStore) - assert.Equal(t, dashboards.ErrDashboardVersionMismatch, err) - }) - - permissionScenario(t, "When updating an existing dashboard by uid with current version", canSave, - func(t *testing.T, sc *permissionScenarioContext) { - cmd := dashboards.SaveDashboardCommand{ - OrgID: 1, - Dashboard: simplejson.NewFromAny(map[string]any{ - "uid": sc.savedDashInFolder.UID, - "title": "Updated title", - "version": sc.savedDashInFolder.Version, - }), - FolderUID: "", - Overwrite: shouldOverwrite, - } - - res := callSaveWithResult(t, cmd, sc.sqlStore) - require.NotNil(t, res) - - _, err := sc.dashboardStore.GetDashboard(context.Background(), &dashboards.GetDashboardQuery{ - ID: sc.savedDashInFolder.ID, - OrgID: cmd.OrgID, - }) - require.NoError(t, err) - }) - - permissionScenario(t, "When creating a dashboard with same name as dashboard in other folder", - canSave, func(t *testing.T, sc *permissionScenarioContext) { - cmd := dashboards.SaveDashboardCommand{ - OrgID: testOrgID, - Dashboard: simplejson.NewFromAny(map[string]any{ - "id": nil, - "title": sc.savedDashInFolder.Title, - }), - FolderUID: sc.savedDashInFolder.FolderUID, - Overwrite: shouldOverwrite, - } - - err := callSaveWithError(t, cmd, sc.sqlStore) - require.NoError(t, err) - }) - - permissionScenario(t, "When creating a dashboard with same name as dashboard in General folder", - canSave, func(t *testing.T, sc *permissionScenarioContext) { - cmd := dashboards.SaveDashboardCommand{ - OrgID: testOrgID, - Dashboard: simplejson.NewFromAny(map[string]any{ - "id": nil, - "title": sc.savedDashInGeneralFolder.Title, - }), - FolderUID: sc.savedDashInGeneralFolder.FolderUID, - Overwrite: shouldOverwrite, - } - - err := callSaveWithError(t, cmd, sc.sqlStore) - require.NoError(t, err) - }) - - permissionScenario(t, "When creating a folder with same name as existing folder", canSave, - func(t *testing.T, sc *permissionScenarioContext) { - cmd := dashboards.SaveDashboardCommand{ - OrgID: testOrgID, - Dashboard: simplejson.NewFromAny(map[string]any{ - "id": nil, - "title": sc.savedFolder.Title, - }), - IsFolder: true, - Overwrite: shouldOverwrite, - } - - err := callSaveWithError(t, cmd, sc.sqlStore) - require.NoError(t, err) - }) + _, err := callSaveWithResult(t, cmd, sc.sqlStore, nil) + require.NoError(t, err) + }) }) t.Run("and overwrite flag is set to true", func(t *testing.T) { const shouldOverwrite = true - permissionScenario(t, "When updating an existing dashboard by id without current version", canSave, - func(t *testing.T, sc *permissionScenarioContext) { - cmd := dashboards.SaveDashboardCommand{ - OrgID: 1, - Dashboard: simplejson.NewFromAny(map[string]any{ - "id": sc.savedDashInGeneralFolder.ID, - "title": "Updated title", - }), - FolderUID: sc.savedFolder.UID, - Overwrite: shouldOverwrite, - } + permissionScenario(t, "When updating an existing dashboard by id without current version", func(t *testing.T, sc *permissionScenarioContext) { + cmd := dashboards.SaveDashboardCommand{ + OrgID: 1, + Dashboard: simplejson.NewFromAny(map[string]any{ + "id": sc.savedDashInGeneralFolder.ID, + "title": "Updated title", + }), + FolderUID: sc.savedFolder.UID, + Overwrite: shouldOverwrite, + } - res := callSaveWithResult(t, cmd, sc.sqlStore) - require.NotNil(t, res) + res, _ := callSaveWithResult(t, cmd, sc.sqlStore, nil) + require.NotNil(t, res) - _, err := sc.dashboardStore.GetDashboard(context.Background(), &dashboards.GetDashboardQuery{ - ID: sc.savedDashInGeneralFolder.ID, - OrgID: cmd.OrgID, - }) - require.NoError(t, err) + _, err := sc.dashboardStore.GetDashboard(context.Background(), &dashboards.GetDashboardQuery{ + ID: sc.savedDashInGeneralFolder.ID, + OrgID: cmd.OrgID, }) + require.NoError(t, err) + }) - permissionScenario(t, "When updating an existing dashboard by uid without current version", canSave, - func(t *testing.T, sc *permissionScenarioContext) { - cmd := dashboards.SaveDashboardCommand{ - OrgID: 1, - Dashboard: simplejson.NewFromAny(map[string]any{ - "uid": sc.savedDashInFolder.UID, - "title": "Updated title", - }), - FolderUID: "", - Overwrite: shouldOverwrite, - } + permissionScenario(t, "When updating an existing dashboard by uid without current version", func(t *testing.T, sc *permissionScenarioContext) { + cmd := dashboards.SaveDashboardCommand{ + OrgID: 1, + Dashboard: simplejson.NewFromAny(map[string]any{ + "uid": sc.savedDashInFolder.UID, + "title": "Updated title", + }), + FolderUID: "", + Overwrite: shouldOverwrite, + } - res := callSaveWithResult(t, cmd, sc.sqlStore) - require.NotNil(t, res) + res, _ := callSaveWithResult(t, cmd, sc.sqlStore, nil) + require.NotNil(t, res) - _, err := sc.dashboardStore.GetDashboard(context.Background(), &dashboards.GetDashboardQuery{ - ID: sc.savedDashInFolder.ID, - OrgID: cmd.OrgID, - }) - require.NoError(t, err) + _, err := sc.dashboardStore.GetDashboard(context.Background(), &dashboards.GetDashboardQuery{ + ID: sc.savedDashInFolder.ID, + OrgID: cmd.OrgID, }) + require.NoError(t, err) + }) - permissionScenario(t, "When updating uid for existing dashboard using id", canSave, - func(t *testing.T, sc *permissionScenarioContext) { - cmd := dashboards.SaveDashboardCommand{ - OrgID: 1, - Dashboard: simplejson.NewFromAny(map[string]any{ - "id": sc.savedDashInFolder.ID, - "uid": "new-uid", - "title": sc.savedDashInFolder.Title, - }), - Overwrite: shouldOverwrite, - } + permissionScenario(t, "When updating uid for existing dashboard using id", func(t *testing.T, sc *permissionScenarioContext) { + cmd := dashboards.SaveDashboardCommand{ + OrgID: 1, + Dashboard: simplejson.NewFromAny(map[string]any{ + "id": sc.savedDashInFolder.ID, + "uid": "new-uid", + "title": sc.savedDashInFolder.Title, + }), + Overwrite: shouldOverwrite, + } - res := callSaveWithResult(t, cmd, sc.sqlStore) - require.NotNil(t, res) - assert.Equal(t, sc.savedDashInFolder.ID, res.ID) - assert.Equal(t, "new-uid", res.UID) + res, _ := callSaveWithResult(t, cmd, sc.sqlStore, nil) + require.NotNil(t, res) + assert.Equal(t, sc.savedDashInFolder.ID, res.ID) + assert.Equal(t, "new-uid", res.UID) - _, err := sc.dashboardStore.GetDashboard(context.Background(), &dashboards.GetDashboardQuery{ - ID: sc.savedDashInFolder.ID, - OrgID: cmd.OrgID, - }) - require.NoError(t, err) + _, err := sc.dashboardStore.GetDashboard(context.Background(), &dashboards.GetDashboardQuery{ + ID: sc.savedDashInFolder.ID, + OrgID: cmd.OrgID, }) + require.NoError(t, err) + }) - permissionScenario(t, "When updating uid to an existing uid for existing dashboard using id", canSave, - func(t *testing.T, sc *permissionScenarioContext) { - cmd := dashboards.SaveDashboardCommand{ - OrgID: 1, - Dashboard: simplejson.NewFromAny(map[string]any{ - "id": sc.savedDashInFolder.ID, - "uid": sc.savedDashInGeneralFolder.UID, - "title": sc.savedDashInFolder.Title, - }), - Overwrite: shouldOverwrite, - } + permissionScenario(t, "When updating uid to an existing uid for existing dashboard using id", func(t *testing.T, sc *permissionScenarioContext) { + cmd := dashboards.SaveDashboardCommand{ + OrgID: 1, + Dashboard: simplejson.NewFromAny(map[string]any{ + "id": sc.savedDashInFolder.ID, + "uid": sc.savedDashInGeneralFolder.UID, + "title": sc.savedDashInFolder.Title, + }), + Overwrite: shouldOverwrite, + } - err := callSaveWithError(t, cmd, sc.sqlStore) - assert.Equal(t, dashboards.ErrDashboardWithSameUIDExists, err) + _, err := callSaveWithResult(t, cmd, sc.sqlStore, nil) + assert.Equal(t, dashboards.ErrDashboardWithSameUIDExists, err) + }) + + permissionScenario(t, "When creating a dashboard with same name as dashboard in other folder", func(t *testing.T, sc *permissionScenarioContext) { + t.Skip() + + cmd := dashboards.SaveDashboardCommand{ + OrgID: testOrgID, + Dashboard: simplejson.NewFromAny(map[string]any{ + "id": nil, + "title": sc.savedDashInFolder.Title, + }), + FolderUID: sc.savedDashInFolder.FolderUID, + Overwrite: shouldOverwrite, + } + + res, _ := callSaveWithResult(t, cmd, sc.sqlStore, nil) + require.NotNil(t, res) + assert.Equal(t, sc.savedDashInFolder.ID, res.ID) + assert.Equal(t, sc.savedDashInFolder.UID, res.UID) + + _, err := sc.dashboardStore.GetDashboard(context.Background(), &dashboards.GetDashboardQuery{ + ID: res.ID, + OrgID: cmd.OrgID, }) + require.NoError(t, err) + }) - permissionScenario(t, "When creating a dashboard with same name as dashboard in other folder", canSave, - func(t *testing.T, sc *permissionScenarioContext) { - t.Skip() + permissionScenario(t, "When creating a dashboard with same name as dashboard in General folder", func(t *testing.T, sc *permissionScenarioContext) { + t.Skip() - cmd := dashboards.SaveDashboardCommand{ - OrgID: testOrgID, - Dashboard: simplejson.NewFromAny(map[string]any{ - "id": nil, - "title": sc.savedDashInFolder.Title, - }), - FolderUID: sc.savedDashInFolder.FolderUID, - Overwrite: shouldOverwrite, - } + cmd := dashboards.SaveDashboardCommand{ + OrgID: testOrgID, + Dashboard: simplejson.NewFromAny(map[string]any{ + "id": nil, + "title": sc.savedDashInGeneralFolder.Title, + }), + FolderUID: sc.savedDashInGeneralFolder.FolderUID, + Overwrite: shouldOverwrite, + } - res := callSaveWithResult(t, cmd, sc.sqlStore) - require.NotNil(t, res) - assert.Equal(t, sc.savedDashInFolder.ID, res.ID) - assert.Equal(t, sc.savedDashInFolder.UID, res.UID) + res, _ := callSaveWithResult(t, cmd, sc.sqlStore, nil) + require.NotNil(t, res) + assert.Equal(t, sc.savedDashInGeneralFolder.ID, res.ID) + assert.Equal(t, sc.savedDashInGeneralFolder.UID, res.UID) - _, err := sc.dashboardStore.GetDashboard(context.Background(), &dashboards.GetDashboardQuery{ - ID: res.ID, - OrgID: cmd.OrgID, - }) - require.NoError(t, err) + _, err := sc.dashboardStore.GetDashboard(context.Background(), &dashboards.GetDashboardQuery{ + ID: res.ID, + OrgID: cmd.OrgID, }) + require.NoError(t, err) + }) - permissionScenario(t, "When creating a dashboard with same name as dashboard in General folder", canSave, - func(t *testing.T, sc *permissionScenarioContext) { - t.Skip() + permissionScenario(t, "When updating existing folder to a dashboard using id", func(t *testing.T, sc *permissionScenarioContext) { + cmd := dashboards.SaveDashboardCommand{ + OrgID: 1, + Dashboard: simplejson.NewFromAny(map[string]any{ + "id": sc.savedFolder.ID, + "title": "new title", + }), + IsFolder: false, + Overwrite: shouldOverwrite, + } - cmd := dashboards.SaveDashboardCommand{ - OrgID: testOrgID, - Dashboard: simplejson.NewFromAny(map[string]any{ - "id": nil, - "title": sc.savedDashInGeneralFolder.Title, - }), - FolderUID: sc.savedDashInGeneralFolder.FolderUID, - Overwrite: shouldOverwrite, - } + _, err := callSaveWithResult(t, cmd, sc.sqlStore, nil) + assert.Equal(t, dashboards.ErrDashboardTypeMismatch, err) + }) - res := callSaveWithResult(t, cmd, sc.sqlStore) - require.NotNil(t, res) - assert.Equal(t, sc.savedDashInGeneralFolder.ID, res.ID) - assert.Equal(t, sc.savedDashInGeneralFolder.UID, res.UID) + permissionScenario(t, "When updating existing dashboard to a folder using id", func(t *testing.T, sc *permissionScenarioContext) { + cmd := dashboards.SaveDashboardCommand{ + OrgID: 1, + Dashboard: simplejson.NewFromAny(map[string]any{ + "id": sc.savedDashInFolder.ID, + "title": "new folder title", + }), + IsFolder: true, + Overwrite: shouldOverwrite, + } - _, err := sc.dashboardStore.GetDashboard(context.Background(), &dashboards.GetDashboardQuery{ - ID: res.ID, - OrgID: cmd.OrgID, - }) - require.NoError(t, err) - }) + _, err := callSaveWithResult(t, cmd, sc.sqlStore, nil) + assert.Equal(t, dashboards.ErrDashboardTypeMismatch, err) + }) - permissionScenario(t, "When updating existing folder to a dashboard using id", canSave, - func(t *testing.T, sc *permissionScenarioContext) { - cmd := dashboards.SaveDashboardCommand{ - OrgID: 1, - Dashboard: simplejson.NewFromAny(map[string]any{ - "id": sc.savedFolder.ID, - "title": "new title", - }), - IsFolder: false, - Overwrite: shouldOverwrite, - } + permissionScenario(t, "When updating existing folder to a dashboard using uid", func(t *testing.T, sc *permissionScenarioContext) { + cmd := dashboards.SaveDashboardCommand{ + OrgID: 1, + Dashboard: simplejson.NewFromAny(map[string]any{ + "uid": sc.savedFolder.UID, + "title": "new title", + }), + IsFolder: false, + Overwrite: shouldOverwrite, + } - err := callSaveWithError(t, cmd, sc.sqlStore) - assert.Equal(t, dashboards.ErrDashboardTypeMismatch, err) - }) + _, err := callSaveWithResult(t, cmd, sc.sqlStore, nil) + assert.Equal(t, dashboards.ErrDashboardTypeMismatch, err) + }) - permissionScenario(t, "When updating existing dashboard to a folder using id", canSave, - func(t *testing.T, sc *permissionScenarioContext) { - cmd := dashboards.SaveDashboardCommand{ - OrgID: 1, - Dashboard: simplejson.NewFromAny(map[string]any{ - "id": sc.savedDashInFolder.ID, - "title": "new folder title", - }), - IsFolder: true, - Overwrite: shouldOverwrite, - } + permissionScenario(t, "When updating existing dashboard to a folder using uid", func(t *testing.T, sc *permissionScenarioContext) { + cmd := dashboards.SaveDashboardCommand{ + OrgID: 1, + Dashboard: simplejson.NewFromAny(map[string]any{ + "uid": sc.savedDashInFolder.UID, + "title": "new folder title", + }), + IsFolder: true, + Overwrite: shouldOverwrite, + } - err := callSaveWithError(t, cmd, sc.sqlStore) - assert.Equal(t, dashboards.ErrDashboardTypeMismatch, err) - }) + _, err := callSaveWithResult(t, cmd, sc.sqlStore, nil) + assert.Equal(t, dashboards.ErrDashboardTypeMismatch, err) + }) - permissionScenario(t, "When updating existing folder to a dashboard using uid", canSave, - func(t *testing.T, sc *permissionScenarioContext) { - cmd := dashboards.SaveDashboardCommand{ - OrgID: 1, - Dashboard: simplejson.NewFromAny(map[string]any{ - "uid": sc.savedFolder.UID, - "title": "new title", - }), - IsFolder: false, - Overwrite: shouldOverwrite, - } + permissionScenario(t, "When updating existing folder to a dashboard using title", func(t *testing.T, sc *permissionScenarioContext) { + cmd := dashboards.SaveDashboardCommand{ + OrgID: 1, + Dashboard: simplejson.NewFromAny(map[string]any{ + "title": sc.savedFolder.Title, + }), + IsFolder: false, + Overwrite: shouldOverwrite, + } - err := callSaveWithError(t, cmd, sc.sqlStore) - assert.Equal(t, dashboards.ErrDashboardTypeMismatch, err) - }) + _, err := callSaveWithResult(t, cmd, sc.sqlStore, nil) + require.NoError(t, err) + }) - permissionScenario(t, "When updating existing dashboard to a folder using uid", canSave, - func(t *testing.T, sc *permissionScenarioContext) { - cmd := dashboards.SaveDashboardCommand{ - OrgID: 1, - Dashboard: simplejson.NewFromAny(map[string]any{ - "uid": sc.savedDashInFolder.UID, - "title": "new folder title", - }), - IsFolder: true, - Overwrite: shouldOverwrite, - } + permissionScenario(t, "When updating existing dashboard to a folder using title", func(t *testing.T, sc *permissionScenarioContext) { + cmd := dashboards.SaveDashboardCommand{ + OrgID: 1, + Dashboard: simplejson.NewFromAny(map[string]any{ + "title": sc.savedDashInGeneralFolder.Title, + }), + IsFolder: true, + Overwrite: shouldOverwrite, + } - err := callSaveWithError(t, cmd, sc.sqlStore) - assert.Equal(t, dashboards.ErrDashboardTypeMismatch, err) - }) - - permissionScenario(t, "When updating existing folder to a dashboard using title", canSave, - func(t *testing.T, sc *permissionScenarioContext) { - cmd := dashboards.SaveDashboardCommand{ - OrgID: 1, - Dashboard: simplejson.NewFromAny(map[string]any{ - "title": sc.savedFolder.Title, - }), - IsFolder: false, - Overwrite: shouldOverwrite, - } - - err := callSaveWithError(t, cmd, sc.sqlStore) - require.NoError(t, err) - }) - - permissionScenario(t, "When updating existing dashboard to a folder using title", canSave, - func(t *testing.T, sc *permissionScenarioContext) { - cmd := dashboards.SaveDashboardCommand{ - OrgID: 1, - Dashboard: simplejson.NewFromAny(map[string]any{ - "title": sc.savedDashInGeneralFolder.Title, - }), - IsFolder: true, - Overwrite: shouldOverwrite, - } - - err := callSaveWithError(t, cmd, sc.sqlStore) - require.NoError(t, err) - }) + _, err := callSaveWithResult(t, cmd, sc.sqlStore, nil) + require.NoError(t, err) + }) }) }) }) } +func TestIntegrationDashboardServicePermissions(t *testing.T) { + if testing.Short() { + t.Skip("skipping integration test") + } + t.Run("Given saved folders and dashboards in organization A", func(t *testing.T) { + permissionScenario(t, "When creating a new dashboard in the General folder, requires create permissions scoped to the general folder", + func(t *testing.T, sc *permissionScenarioContext) { + sqlStore := db.InitTestDB(t) + cmd := dashboards.SaveDashboardCommand{ + OrgID: testOrgID, + Dashboard: simplejson.NewFromAny(map[string]any{ + "title": "Dash", + }), + UserID: 10000, + Overwrite: true, + } + + permissions := map[int64]map[string][]string{ + testOrgID: { + dashboards.ActionDashboardsWrite: {dashboards.ScopeDashboardsAll}, + }, + } + _, err := callSaveWithResult(t, cmd, sqlStore, permissions) + assert.Equal(t, dashboards.ErrDashboardUpdateAccessDenied, err) + + permissions = map[int64]map[string][]string{ + testOrgID: { + dashboards.ActionDashboardsCreate: {dashboards.ScopeFoldersProvider.GetResourceScopeUID(accesscontrol.GeneralFolderUID)}, + }, + } + _, err = callSaveWithResult(t, cmd, sqlStore, permissions) + assert.Nil(t, err) + }) + + permissionScenario(t, "When creating a new dashboard in other folder, requires create permissions scoped to the other folder", func(t *testing.T, sc *permissionScenarioContext) { + cmd := dashboards.SaveDashboardCommand{ + OrgID: testOrgID, + Dashboard: simplejson.NewFromAny(map[string]any{ + "title": "Dash", + }), + FolderUID: sc.otherSavedFolder.UID, + UserID: 10000, + Overwrite: true, + } + + permissions := map[int64]map[string][]string{ + testOrgID: { + dashboards.ActionDashboardsCreate: {dashboards.ScopeFoldersProvider.GetResourceScopeUID("different_folder_uid")}, + }, + } + _, err := callSaveWithResult(t, cmd, sc.sqlStore, permissions) + assert.Equal(t, dashboards.ErrDashboardUpdateAccessDenied, err) + + permissions = map[int64]map[string][]string{ + testOrgID: { + dashboards.ActionDashboardsCreate: {dashboards.ScopeFoldersProvider.GetResourceScopeUID(sc.otherSavedFolder.UID)}, + }, + } + _, err = callSaveWithResult(t, cmd, sc.sqlStore, permissions) + assert.Nil(t, err) + }) + + permissionScenario(t, "When creating a new dashboard by existing UID in folder, requires write permissions on the existing dashboard", func(t *testing.T, sc *permissionScenarioContext) { + cmd := dashboards.SaveDashboardCommand{ + OrgID: testOrgID, + Dashboard: simplejson.NewFromAny(map[string]any{ + "uid": sc.savedDashInFolder.UID, + "title": "New dash", + }), + FolderUID: sc.savedFolder.UID, + UserID: 10000, + Overwrite: true, + } + + permissions := map[int64]map[string][]string{ + testOrgID: { + dashboards.ActionDashboardsWrite: {dashboards.ScopeDashboardsProvider.GetResourceScopeUID("different_dash_uid")}, + }, + } + _, err := callSaveWithResult(t, cmd, sc.sqlStore, permissions) + assert.Equal(t, dashboards.ErrDashboardUpdateAccessDenied, err) + + permissions = map[int64]map[string][]string{ + testOrgID: { + dashboards.ActionDashboardsWrite: {dashboards.ScopeDashboardsProvider.GetResourceScopeUID(sc.savedDashInFolder.UID)}, + }, + } + _, err = callSaveWithResult(t, cmd, sc.sqlStore, permissions) + assert.Nil(t, err) + }) + + permissionScenario(t, "When moving a dashboard by existing uid to other folder from General folder, requires dashboard creation permissions on the destination folder and write access to the dashboard", func(t *testing.T, sc *permissionScenarioContext) { + cmd := dashboards.SaveDashboardCommand{ + OrgID: testOrgID, + Dashboard: simplejson.NewFromAny(map[string]any{ + "uid": sc.savedDashInGeneralFolder.UID, + "title": "Dash", + }), + FolderUID: sc.otherSavedFolder.UID, + UserID: 10000, + Overwrite: true, + } + + // Perms to write dashboard but not create dashboards in the destination folder + permissions := map[int64]map[string][]string{ + testOrgID: { + dashboards.ActionDashboardsWrite: {dashboards.ScopeDashboardsProvider.GetResourceScopeUID(sc.savedDashInGeneralFolder.UID)}, + }, + } + _, err := callSaveWithResult(t, cmd, sc.sqlStore, permissions) + assert.Equal(t, dashboards.ErrDashboardUpdateAccessDenied, err) + + // Perms to create dashboards in the destination folder but not write the dashboard + permissions = map[int64]map[string][]string{ + testOrgID: { + dashboards.ActionDashboardsCreate: {dashboards.ScopeFoldersProvider.GetResourceScopeUID(sc.otherSavedFolder.UID)}, + }, + } + _, err = callSaveWithResult(t, cmd, sc.sqlStore, permissions) + assert.Equal(t, dashboards.ErrDashboardUpdateAccessDenied, err) + + // Perms to write dashboard and create dashboards in the destination folder + permissions = map[int64]map[string][]string{ + testOrgID: { + dashboards.ActionDashboardsWrite: {dashboards.ScopeDashboardsProvider.GetResourceScopeUID(sc.savedDashInGeneralFolder.UID)}, + dashboards.ActionDashboardsCreate: {dashboards.ScopeFoldersProvider.GetResourceScopeUID(sc.otherSavedFolder.UID)}, + }, + } + _, err = callSaveWithResult(t, cmd, sc.sqlStore, permissions) + assert.Nil(t, err) + }) + + permissionScenario(t, "When moving a dashboard by existing uid to the General folder from other folder, requires dashboard creation permissions on the general folder and write access to the dashboard", func(t *testing.T, sc *permissionScenarioContext) { + cmd := dashboards.SaveDashboardCommand{ + OrgID: testOrgID, + Dashboard: simplejson.NewFromAny(map[string]any{ + "uid": sc.savedDashInFolder.UID, + "title": "Dash", + }), + FolderUID: "", + UserID: 10000, + Overwrite: true, + } + + // Perms to write dashboard but not create dashboards in the destination folder + permissions := map[int64]map[string][]string{ + testOrgID: { + dashboards.ActionDashboardsWrite: {dashboards.ScopeDashboardsProvider.GetResourceScopeUID(sc.savedDashInFolder.UID)}, + }, + } + _, err := callSaveWithResult(t, cmd, sc.sqlStore, permissions) + assert.Equal(t, dashboards.ErrDashboardUpdateAccessDenied, err) + + // Perms to create dashboards in the destination folder but not write the dashboard + permissions = map[int64]map[string][]string{ + testOrgID: { + dashboards.ActionDashboardsCreate: {dashboards.ScopeFoldersProvider.GetResourceScopeUID(accesscontrol.GeneralFolderUID)}, + }, + } + _, err = callSaveWithResult(t, cmd, sc.sqlStore, permissions) + assert.Equal(t, dashboards.ErrDashboardUpdateAccessDenied, err) + + // Perms to write dashboard and create dashboards in the destination folder + permissions = map[int64]map[string][]string{ + testOrgID: { + dashboards.ActionDashboardsWrite: {dashboards.ScopeDashboardsProvider.GetResourceScopeUID(sc.savedDashInFolder.UID)}, + dashboards.ActionDashboardsCreate: {dashboards.ScopeFoldersProvider.GetResourceScopeUID(accesscontrol.GeneralFolderUID)}, + }, + } + _, err = callSaveWithResult(t, cmd, sc.sqlStore, permissions) + assert.NoError(t, err) + }) + }) +} + type permissionScenarioContext struct { - dashboardGuardianMock *guardian.FakeDashboardGuardian sqlStore db.DB dashboardStore dashboards.Store savedFolder *dashboards.Dashboard @@ -862,14 +763,9 @@ type permissionScenarioContext struct { type permissionScenarioFunc func(t *testing.T, sc *permissionScenarioContext) -func permissionScenario(t *testing.T, desc string, canSave bool, fn permissionScenarioFunc) { +func permissionScenario(t *testing.T, desc string, fn permissionScenarioFunc) { t.Helper() - guardianMock := &guardian.FakeDashboardGuardian{ - CanSaveValue: canSave, - CanViewValue: true, - } - t.Run(desc, func(t *testing.T) { features := featuremgmt.WithFeatures() cfg := setting.NewCfg() @@ -919,10 +815,11 @@ func permissionScenario(t *testing.T, desc string, canSave bool, fn permissionSc nil, dualwrite.ProvideTestService(), sort.ProvideService(), + serverlock.ProvideService(sqlStore, tracing.InitializeTracerForTest()), + kvstore.NewFakeKVStore(), ) dashboardService.RegisterDashboardPermissions(dashboardPermissions) require.NoError(t, err) - guardian.InitAccessControlGuardian(cfg, ac, dashboardService, folderService, log.NewNopLogger()) savedFolder := saveTestFolder(t, "Saved folder", testOrgID, sqlStore) savedDashInFolder := saveTestDashboard(t, "Saved dash in folder", testOrgID, savedFolder.UID, sqlStore) @@ -942,14 +839,7 @@ func permissionScenario(t *testing.T, desc string, canSave bool, fn permissionSc require.False(t, savedDashInFolder.IsFolder) require.NotEmpty(t, savedDashInFolder.UID) - origNewDashboardGuardian := guardian.New - t.Cleanup(func() { - guardian.New = origNewDashboardGuardian - }) - guardian.MockDashboardGuardian(guardianMock) - sc := &permissionScenarioContext{ - dashboardGuardianMock: guardianMock, sqlStore: sqlStore, savedDashInFolder: savedDashInFolder, otherSavedFolder: otherSavedFolder, @@ -962,11 +852,17 @@ func permissionScenario(t *testing.T, desc string, canSave bool, fn permissionSc }) } -func callSaveWithResult(t *testing.T, cmd dashboards.SaveDashboardCommand, sqlStore db.DB) *dashboards.Dashboard { +func callSaveWithResult(t *testing.T, cmd dashboards.SaveDashboardCommand, sqlStore db.DB, permissions map[int64]map[string][]string) (*dashboards.Dashboard, error) { t.Helper() features := featuremgmt.WithFeatures() dto := toSaveDashboardDto(cmd) + var ac accesscontrol.AccessControl + ac = actest.FakeAccessControl{ExpectedEvaluate: true} + if permissions != nil { + dto.User = &user.SignedInUser{UserID: cmd.UserID, OrgID: testOrgID, Permissions: permissions} + ac = acimpl.ProvideAccessControl(features) + } cfg := setting.NewCfg() quotaService := quotatest.New(false, nil) dashboardStore, err := database.ProvideDashboardStore(sqlStore, cfg, features, tagimpl.ProvideService(sqlStore)) @@ -1002,7 +898,7 @@ func callSaveWithResult(t *testing.T, cmd dashboards.SaveDashboardCommand, sqlSt cfg, dashboardStore, folderStore, featuremgmt.WithFeatures(), folderPermissions, - actest.FakeAccessControl{}, + ac, folderService, folder.NewFakeStore(), nil, @@ -1014,64 +910,12 @@ func callSaveWithResult(t *testing.T, cmd dashboards.SaveDashboardCommand, sqlSt nil, dualwrite.ProvideTestService(), sort.ProvideService(), + serverlock.ProvideService(sqlStore, tracing.InitializeTracerForTest()), + kvstore.NewFakeKVStore(), ) require.NoError(t, err) service.RegisterDashboardPermissions(dashboardPermissions) - res, err := service.SaveDashboard(context.Background(), &dto, false) - require.NoError(t, err) - - return res -} - -func callSaveWithError(t *testing.T, cmd dashboards.SaveDashboardCommand, sqlStore db.DB) error { - features := featuremgmt.WithFeatures() - dto := toSaveDashboardDto(cmd) - cfg := setting.NewCfg() - quotaService := quotatest.New(false, nil) - dashboardStore, err := database.ProvideDashboardStore(sqlStore, cfg, features, tagimpl.ProvideService(sqlStore)) - require.NoError(t, err) - folderStore := folderimpl.ProvideDashboardFolderStore(sqlStore) - tracer := tracing.InitializeTracerForTest() - publicDashboardFakeService := publicdashboards.NewFakePublicDashboardServiceWrapper(t) - folderStore2 := folderimpl.ProvideStore(sqlStore) - folderService := folderimpl.ProvideService(folderStore2, - actest.FakeAccessControl{ExpectedEvaluate: true}, - bus.ProvideBus(tracer), - dashboardStore, - folderStore, - nil, - sqlStore, - features, - supportbundlestest.NewFakeBundleService(), - publicDashboardFakeService, - cfg, - nil, - tracer, - nil, - dualwrite.ProvideTestService(), - sort.ProvideService(), - ) - service, err := ProvideDashboardServiceImpl( - cfg, dashboardStore, folderStore, - featuremgmt.WithFeatures(), - accesscontrolmock.NewMockedPermissionsService(), - actest.FakeAccessControl{}, - folderService, - folder.NewFakeStore(), - nil, - client.MockTestRestConfig{}, - nil, - quotaService, - nil, - nil, - nil, - dualwrite.ProvideTestService(), - sort.ProvideService(), - ) - require.NoError(t, err) - service.RegisterDashboardPermissions(accesscontrolmock.NewMockedPermissionsService()) - _, err = service.SaveDashboard(context.Background(), &dto, false) - return err + return service.SaveDashboard(context.Background(), &dto, false) } func saveTestDashboard(t *testing.T, title string, orgID int64, folderUID string, sqlStore db.DB) *dashboards.Dashboard { @@ -1127,7 +971,7 @@ func saveTestDashboard(t *testing.T, title string, orgID int64, folderUID string cfg, dashboardStore, folderStore, features, accesscontrolmock.NewMockedPermissionsService(), - actest.FakeAccessControl{}, + actest.FakeAccessControl{ExpectedEvaluate: true}, folderService, folder.NewFakeStore(), nil, @@ -1139,6 +983,8 @@ func saveTestDashboard(t *testing.T, title string, orgID int64, folderUID string nil, dualwrite.ProvideTestService(), sort.ProvideService(), + serverlock.ProvideService(sqlStore, tracing.InitializeTracerForTest()), + kvstore.NewFakeKVStore(), ) require.NoError(t, err) service.RegisterDashboardPermissions(dashboardPermissions) @@ -1206,7 +1052,7 @@ func saveTestFolder(t *testing.T, title string, orgID int64, sqlStore db.DB) *da cfg, dashboardStore, folderStore, featuremgmt.WithFeatures(), folderPermissions, - actest.FakeAccessControl{}, + actest.FakeAccessControl{ExpectedEvaluate: true}, folderService, folder.NewFakeStore(), nil, @@ -1218,6 +1064,8 @@ func saveTestFolder(t *testing.T, title string, orgID int64, sqlStore db.DB) *da nil, dualwrite.ProvideTestService(), sort.ProvideService(), + serverlock.ProvideService(sqlStore, tracing.InitializeTracerForTest()), + kvstore.NewFakeKVStore(), ) require.NoError(t, err) service.RegisterDashboardPermissions(accesscontrolmock.NewMockedPermissionsService()) diff --git a/pkg/services/dashboards/service/dashboard_service_test.go b/pkg/services/dashboards/service/dashboard_service_test.go index 2223eabb9d7..82600d9f2cd 100644 --- a/pkg/services/dashboards/service/dashboard_service_test.go +++ b/pkg/services/dashboards/service/dashboard_service_test.go @@ -18,21 +18,25 @@ import ( "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/apimachinery/utils" "github.com/grafana/grafana/pkg/components/simplejson" + "github.com/grafana/grafana/pkg/infra/kvstore" "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/infra/serverlock" + "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/services/accesscontrol" + "github.com/grafana/grafana/pkg/services/accesscontrol/actest" acmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock" "github.com/grafana/grafana/pkg/services/apiserver/client" "github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/folder" "github.com/grafana/grafana/pkg/services/folder/foldertest" - "github.com/grafana/grafana/pkg/services/guardian" "github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/services/org/orgtest" "github.com/grafana/grafana/pkg/services/publicdashboards" "github.com/grafana/grafana/pkg/services/quota" "github.com/grafana/grafana/pkg/services/search/model" "github.com/grafana/grafana/pkg/services/search/sort" + "github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/storage/unified/resource" @@ -50,6 +54,7 @@ func TestDashboardService(t *testing.T) { log: log.New("test.logger"), dashboardStore: &fakeStore, folderService: folderSvc, + ac: actest.FakeAccessControl{ExpectedEvaluate: true}, features: featuremgmt.WithFeatures(), publicDashboardService: fakePublicDashboardService, } @@ -57,10 +62,6 @@ func TestDashboardService(t *testing.T) { folderStore.On("GetFolderByUID", mock.Anything, mock.AnythingOfType("int64"), mock.AnythingOfType("string")).Return(nil, dashboards.ErrFolderNotFound).Once() service.folderStore = &folderStore - origNewDashboardGuardian := guardian.New - defer func() { guardian.New = origNewDashboardGuardian }() - guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{CanSaveValue: true}) - t.Run("Save dashboard validation", func(t *testing.T) { dto := &dashboards.SaveDashboardDTO{} @@ -836,9 +837,6 @@ func TestDeleteOrphanedProvisionedDashboards(t *testing.T) { _, k8sCliMock := setupK8sDashboardTests(service) k8sCliMock.On("GetNamespace", mock.Anything, mock.Anything).Return("default") k8sCliMock.On("Delete", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil) - fakeStore.On("CleanupAfterDelete", mock.Anything, &dashboards.DeleteDashboardCommand{UID: "uid", OrgID: 1}).Return(nil).Once() - fakeStore.On("CleanupAfterDelete", mock.Anything, &dashboards.DeleteDashboardCommand{UID: "uid3", OrgID: 2}).Return(nil).Once() - fakePublicDashboardService.On("DeleteByDashboardUIDs", mock.Anything, mock.Anything, mock.Anything).Return(nil) k8sCliMock.On("Get", mock.Anything, "uid", mock.Anything, mock.Anything, mock.Anything).Return(&unstructured.Unstructured{Object: map[string]any{ "metadata": map[string]any{ "name": "uid", @@ -1036,8 +1034,6 @@ func TestDeleteOrphanedProvisionedDashboards(t *testing.T) { // Mock deleteDashboard() k8sCliMock.On("Delete", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil).Once() - fakePublicDashboardService.On("DeleteByDashboardUIDs", mock.Anything, mock.Anything, mock.Anything).Return(nil) - fakeStore.On("CleanupAfterDelete", mock.Anything, mock.Anything).Return(nil).Once() // Mock WaitForSearchQuery() // First call returns 1 hit @@ -1292,13 +1288,10 @@ func TestSetDefaultPermissionsWhenSavingFolderForProvisionedDashboards(t *testin UID: "general", }, }, + ac: actest.FakeAccessControl{ExpectedEvaluate: true}, log: log.NewNopLogger(), } - origNewDashboardGuardian := guardian.New - defer func() { guardian.New = origNewDashboardGuardian }() - guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{CanSaveValue: true}) - cmd := &folder.CreateFolderCommand{ Title: "foo", OrgID: 1, @@ -1326,13 +1319,10 @@ func TestSaveProvisionedDashboard(t *testing.T) { UID: "general", }, }, + ac: actest.FakeAccessControl{ExpectedEvaluate: true}, log: log.NewNopLogger(), } - origNewDashboardGuardian := guardian.New - defer func() { guardian.New = origNewDashboardGuardian }() - guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{CanSaveValue: true}) - query := &dashboards.SaveDashboardDTO{ OrgID: 1, User: &user.SignedInUser{UserID: 1}, @@ -1369,9 +1359,8 @@ func TestSaveProvisionedDashboard(t *testing.T) { t.Run("Should use Kubernetes create if feature flags are enabled", func(t *testing.T) { ctx, k8sCliMock := setupK8sDashboardTests(service) fakeStore.On("SaveProvisionedDashboard", mock.Anything, mock.Anything, mock.Anything).Return(&dashboards.Dashboard{}, nil) - k8sCliMock.On("Get", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil, nil) k8sCliMock.On("GetUserFromMeta", mock.Anything, mock.Anything).Return(&user.User{}, nil) - k8sCliMock.On("Create", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&dashboardUnstructured, nil) + k8sCliMock.On("Update", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&dashboardUnstructured, nil) k8sCliMock.On("GetNamespace", mock.Anything).Return("default") dashboard, err := service.SaveProvisionedDashboard(ctx, query, &dashboards.DashboardProvisioning{}) @@ -1392,12 +1381,9 @@ func TestSaveDashboard(t *testing.T) { folderService: &foldertest.FakeService{ ExpectedFolder: &folder.Folder{}, }, + ac: actest.FakeAccessControl{ExpectedEvaluate: true}, } - origNewDashboardGuardian := guardian.New - defer func() { guardian.New = origNewDashboardGuardian }() - guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{CanSaveValue: true}) - query := &dashboards.SaveDashboardDTO{ OrgID: 1, User: &user.SignedInUser{UserID: 1}, @@ -1434,10 +1420,9 @@ func TestSaveDashboard(t *testing.T) { t.Run("Should use Kubernetes create if feature flags are enabled and dashboard doesn't exist", func(t *testing.T) { ctx, k8sCliMock := setupK8sDashboardTests(service) - k8sCliMock.On("Get", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil, nil) k8sCliMock.On("GetUserFromMeta", mock.Anything, mock.Anything).Return(&user.User{}, nil) k8sCliMock.On("GetNamespace", mock.Anything).Return("default") - k8sCliMock.On("Create", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&dashboardUnstructured, nil) + k8sCliMock.On("Update", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&dashboardUnstructured, nil) dashboard, err := service.SaveDashboard(ctx, query, false) require.NoError(t, err) @@ -1446,7 +1431,6 @@ func TestSaveDashboard(t *testing.T) { t.Run("Should use Kubernetes update if feature flags are enabled and dashboard exists", func(t *testing.T) { ctx, k8sCliMock := setupK8sDashboardTests(service) - k8sCliMock.On("Get", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&dashboardUnstructured, nil) k8sCliMock.On("GetUserFromMeta", mock.Anything, mock.Anything).Return(&user.User{}, nil) k8sCliMock.On("GetNamespace", mock.Anything).Return("default") k8sCliMock.On("Update", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&dashboardUnstructured, nil) @@ -1458,9 +1442,8 @@ func TestSaveDashboard(t *testing.T) { t.Run("Should return an error if uid is invalid", func(t *testing.T) { ctx, k8sCliMock := setupK8sDashboardTests(service) - k8sCliMock.On("Get", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil, nil) k8sCliMock.On("GetNamespace", mock.Anything).Return("default") - k8sCliMock.On("Create", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&dashboardUnstructured, nil) + k8sCliMock.On("Update", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&dashboardUnstructured, nil) query.Dashboard.UID = "invalid/uid" _, err := service.SaveDashboard(ctx, query, false) @@ -1492,8 +1475,6 @@ func TestDeleteDashboard(t *testing.T) { t.Run("Should use Kubernetes client if feature flags are enabled", func(t *testing.T) { ctx, k8sCliMock := setupK8sDashboardTests(service) k8sCliMock.On("Delete", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil).Once() - fakeStore.On("CleanupAfterDelete", mock.Anything, mock.Anything).Return(nil).Once() - fakePublicDashboardService.On("DeleteByDashboardUIDs", mock.Anything, mock.Anything, mock.Anything).Return(nil).Once() err := service.DeleteDashboard(ctx, 1, "uid", 1) require.NoError(t, err) @@ -1504,8 +1485,6 @@ func TestDeleteDashboard(t *testing.T) { ctx, k8sCliMock := setupK8sDashboardTests(service) k8sCliMock.On("GetNamespace", mock.Anything, mock.Anything).Return("default") k8sCliMock.On("Delete", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil).Once() - fakeStore.On("CleanupAfterDelete", mock.Anything, mock.Anything).Return(nil).Once() - fakePublicDashboardService.On("DeleteByDashboardUIDs", mock.Anything, mock.Anything, mock.Anything).Return(nil).Once() k8sCliMock.On("Search", mock.Anything, mock.Anything, mock.Anything).Return(&resource.ResourceSearchResponse{ Results: &resource.ResourceTable{ Columns: []*resource.ResourceTableColumnDefinition{ @@ -2330,3 +2309,335 @@ func TestLegacySaveCommandToUnstructured(t *testing.T) { assert.Equal(t, result.GetAnnotations(), map[string]string(nil)) }) } + +func TestCleanUpDashboard(t *testing.T) { + tests := []struct { + name string + deleteError error + cleanupError error + expectCleanup bool + expectedError error + }{ + { + name: "Should delete public dashboards and clean up after delete", + expectCleanup: true, + }, + { + name: "Should return error if DeleteByDashboardUIDs fails", + deleteError: fmt.Errorf("deletion error"), + expectCleanup: false, + expectedError: fmt.Errorf("deletion error"), + }, + { + name: "Should return error if CleanupAfterDelete fails", + cleanupError: fmt.Errorf("cleanup error"), + expectCleanup: true, + expectedError: fmt.Errorf("cleanup error"), + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + fakeStore := dashboards.FakeDashboardStore{} + fakePublicDashboardService := publicdashboards.NewFakePublicDashboardServiceWrapper(t) + service := &DashboardServiceImpl{ + cfg: setting.NewCfg(), + dashboardStore: &fakeStore, + publicDashboardService: fakePublicDashboardService, + } + + ctx := context.Background() + dashboardUID := "dash-uid" + orgID := int64(1) + + // Setup mocks + fakePublicDashboardService.On("DeleteByDashboardUIDs", mock.Anything, orgID, []string{dashboardUID}).Return(tc.deleteError).Maybe() + + if tc.expectCleanup { + fakeStore.On("CleanupAfterDelete", mock.Anything, &dashboards.DeleteDashboardCommand{ + OrgID: orgID, + UID: dashboardUID, + }).Return(tc.cleanupError).Maybe() + } + + // Execute + err := service.CleanUpDashboard(ctx, dashboardUID, orgID) + + // Assert + if tc.expectedError != nil { + require.Error(t, err) + require.Equal(t, tc.expectedError.Error(), err.Error()) + } else { + require.NoError(t, err) + } + + fakePublicDashboardService.AssertExpectations(t) + fakeStore.AssertExpectations(t) + }) + } +} + +func TestK8sDashboardCleanupJob(t *testing.T) { + tests := []struct { + name string + featureEnabled bool + batchSize int + setupFunc func(*DashboardServiceImpl, context.Context, *client.MockK8sHandler) + verifyFunc func(*testing.T, *DashboardServiceImpl, context.Context, *client.MockK8sHandler, *kvstore.FakeKVStore) + }{ + { + name: "Should not run cleanup when feature flag is disabled", + featureEnabled: false, + batchSize: 10, + }, + { + name: "Should process dashboard cleanup for all orgs", + featureEnabled: true, + batchSize: 10, + setupFunc: func(service *DashboardServiceImpl, ctx context.Context, k8sCliMock *client.MockK8sHandler) { + // Test organizations + fakeOrgService := service.orgService.(*orgtest.FakeOrgService) + fakeOrgService.ExpectedOrgs = []*org.OrgDTO{ + {ID: 1, Name: "org1"}, + {ID: 2, Name: "org2"}, + } + + kv := service.kvstore.(*kvstore.FakeKVStore) + fakeStore := service.dashboardStore.(*dashboards.FakeDashboardStore) + fakePublicDashboardService := service.publicDashboardService.(*publicdashboards.FakePublicDashboardServiceWrapper) + + // Create dashboard unstructured items for response + dashboard1 := createTestUnstructuredDashboard("dash1", "org1-dashboard", "101") + dashboard2 := createTestUnstructuredDashboard("dash2", "org2-dashboard", "201") + + // Setup test data in KV store. Only populate org 1. + _ = kv.Set(ctx, int64(1), k8sDashboardKvNamespace, k8sDashboardKvLastResourceVersionKey, "100") + + // Mock K8s responses for org 1 + k8sCliMock.On("List", mock.AnythingOfType("*context.valueCtx"), int64(1), mock.MatchedBy(func(opts metav1.ListOptions) bool { + return opts.LabelSelector == utils.LabelKeyGetTrash+"=true" && + opts.Continue == "" + })).Return(&unstructured.UnstructuredList{ + Object: map[string]interface{}{ + "metadata": map[string]interface{}{ + "resourceVersion": "101", + }, + }, + Items: []unstructured.Unstructured{dashboard1}, + }, nil).Once() + + // Mock K8s responses for org 2 + k8sCliMock.On("List", mock.AnythingOfType("*context.valueCtx"), int64(2), mock.MatchedBy(func(opts metav1.ListOptions) bool { + return opts.LabelSelector == utils.LabelKeyGetTrash+"=true" && + opts.Continue == "" + })).Return(&unstructured.UnstructuredList{ + Object: map[string]interface{}{ + "metadata": map[string]interface{}{ + "resourceVersion": "201", + }, + }, + Items: []unstructured.Unstructured{dashboard2}, + }, nil).Once() + + // Mock GetUserFromMeta calls + k8sCliMock.On("GetUserFromMeta", mock.AnythingOfType("*context.valueCtx"), mock.Anything).Return(&user.User{}, nil).Times(4) + + // Mock cleanup + fakePublicDashboardService.On("DeleteByDashboardUIDs", mock.Anything, int64(1), []string{"dash1"}).Return(nil).Once() + fakePublicDashboardService.On("DeleteByDashboardUIDs", mock.Anything, int64(2), []string{"dash2"}).Return(nil).Once() + fakeStore.On("CleanupAfterDelete", mock.Anything, mock.Anything).Return(nil).Times(2) + }, + verifyFunc: func(t *testing.T, service *DashboardServiceImpl, ctx context.Context, k8sCliMock *client.MockK8sHandler, kv *kvstore.FakeKVStore) { + k8sCliMock.AssertExpectations(t) + + // Verify KV store was updated with new resource versions + val1, found1, _ := kv.Get(ctx, int64(1), k8sDashboardKvNamespace, k8sDashboardKvLastResourceVersionKey) + require.True(t, found1) + require.Equal(t, "101", val1) + + val2, found2, _ := kv.Get(ctx, int64(2), k8sDashboardKvNamespace, k8sDashboardKvLastResourceVersionKey) + require.True(t, found2) + require.Equal(t, "201", val2) + }, + }, + { + name: "Should handle pagination and batching when processing large sets of dashboards", + featureEnabled: true, + batchSize: 3, + setupFunc: func(service *DashboardServiceImpl, ctx context.Context, k8sCliMock *client.MockK8sHandler) { + // Test organization + fakeOrgService := service.orgService.(*orgtest.FakeOrgService) + fakeOrgService.ExpectedOrgs = []*org.OrgDTO{ + {ID: 1, Name: "org1"}, + } + + kv := service.kvstore.(*kvstore.FakeKVStore) + fakeStore := service.dashboardStore.(*dashboards.FakeDashboardStore) + fakePublicDashboardService := service.publicDashboardService.(*publicdashboards.FakePublicDashboardServiceWrapper) + + // Setup initial resource version + initialVersion := "100" + _ = kv.Set(ctx, int64(1), k8sDashboardKvNamespace, k8sDashboardKvLastResourceVersionKey, initialVersion) + + // Create dashboard batches (5 dashboards total, to be processed in 2 batches) + firstBatch := []unstructured.Unstructured{ + createTestUnstructuredDashboard("dash1", "dashboard1", "101"), + createTestUnstructuredDashboard("dash2", "dashboard2", "102"), + createTestUnstructuredDashboard("dash3", "dashboard3", "150"), + } + secondBatch := []unstructured.Unstructured{ + createTestUnstructuredDashboard("dash4", "dashboard4", "180"), + createTestUnstructuredDashboard("dash5", "dashboard5", "200"), + } + + // First batch response with continue token + k8sCliMock.On("List", mock.AnythingOfType("*context.valueCtx"), int64(1), mock.MatchedBy(func(opts metav1.ListOptions) bool { + return opts.LabelSelector == utils.LabelKeyGetTrash+"=true" && + opts.Continue == "" + })).Return(&unstructured.UnstructuredList{ + Object: map[string]interface{}{ + "metadata": map[string]interface{}{ + "resourceVersion": "200", + "continue": "next-token", + }, + }, + Items: firstBatch, + }, nil).Once() + + // Second batch response with updated resource version + k8sCliMock.On("List", mock.AnythingOfType("*context.valueCtx"), int64(1), mock.MatchedBy(func(opts metav1.ListOptions) bool { + return opts.LabelSelector == utils.LabelKeyGetTrash+"=true" && + opts.Continue == "next-token" + })).Return(&unstructured.UnstructuredList{ + Object: map[string]interface{}{ + "metadata": map[string]interface{}{ + "resourceVersion": "200", + }, + }, + Items: secondBatch, + }, nil).Once() + + // Mock GetUserFromMeta calls for each dashboard + k8sCliMock.On("GetUserFromMeta", mock.AnythingOfType("*context.valueCtx"), mock.Anything).Return(&user.User{}, nil).Times(10) + + // Mock public dashboard deletion for each dashboard + fakePublicDashboardService.On("DeleteByDashboardUIDs", mock.Anything, int64(1), []string{"dash1"}).Return(nil).Once() + fakePublicDashboardService.On("DeleteByDashboardUIDs", mock.Anything, int64(1), []string{"dash2"}).Return(nil).Once() + fakePublicDashboardService.On("DeleteByDashboardUIDs", mock.Anything, int64(1), []string{"dash3"}).Return(nil).Once() + fakePublicDashboardService.On("DeleteByDashboardUIDs", mock.Anything, int64(1), []string{"dash4"}).Return(nil).Once() + fakePublicDashboardService.On("DeleteByDashboardUIDs", mock.Anything, int64(1), []string{"dash5"}).Return(nil).Once() + + // Mock cleanup after delete for each dashboard + fakeStore.On("CleanupAfterDelete", mock.Anything, mock.Anything).Return(nil).Times(5) + }, + verifyFunc: func(t *testing.T, service *DashboardServiceImpl, ctx context.Context, k8sCliMock *client.MockK8sHandler, kv *kvstore.FakeKVStore) { + k8sCliMock.AssertExpectations(t) + + // Verify KV store was updated with latest resource version + val, found, _ := kv.Get(ctx, int64(1), k8sDashboardKvNamespace, k8sDashboardKvLastResourceVersionKey) + require.True(t, found) + require.Equal(t, "200", val) + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + // Setup test database and utilities + sqlStore, _ := sqlstore.InitTestDB(t) + lockService := serverlock.ProvideService(sqlStore, tracing.InitializeTracerForTest()) + kv := kvstore.NewFakeKVStore() + + fakeStore := dashboards.FakeDashboardStore{} + fakePublicDashboardService := publicdashboards.NewFakePublicDashboardServiceWrapper(t) + fakeOrgService := orgtest.NewOrgServiceFake() + + features := featuremgmt.WithFeatures() + if tc.featureEnabled { + features = featuremgmt.WithFeatures(featuremgmt.FlagKubernetesClientDashboardsFolders) + } + + service := &DashboardServiceImpl{ + cfg: setting.NewCfg(), + log: log.New("test.logger"), + dashboardStore: &fakeStore, + publicDashboardService: fakePublicDashboardService, + orgService: fakeOrgService, + serverLockService: lockService, + kvstore: kv, + features: features, + } + + ctx, k8sCliMock := setupK8sDashboardTests(service) + + if tc.setupFunc != nil { + tc.setupFunc(service, ctx, k8sCliMock) + } + + // Execute + err := service.cleanupK8sDashboardResources(ctx, int64(tc.batchSize), 20*time.Second) + require.NoError(t, err) + + if tc.verifyFunc != nil { + tc.verifyFunc(t, service, ctx, k8sCliMock, kv) + } + }) + } + + t.Run("Should start and stop background job correctly", func(t *testing.T) { + // Setup test database and utilities + sqlStore, _ := sqlstore.InitTestDB(t) + lockService := serverlock.ProvideService(sqlStore, tracing.InitializeTracerForTest()) + + cfg := setting.NewCfg() + cfg.K8sDashboardCleanup = setting.K8sDashboardCleanupSettings{ + Interval: 30 * time.Second, + Timeout: 25 * time.Second, + BatchSize: 10, + } + + service := &DashboardServiceImpl{ + cfg: cfg, + log: log.New("test.logger"), + features: featuremgmt.WithFeatures(featuremgmt.FlagKubernetesClientDashboardsFolders), + serverLockService: lockService, + } + + // Create a test context that can be canceled + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + // Start job with the context + done := service.startK8sDeletedDashboardsCleanupJob(ctx) + + // Cancel context to verify graceful shutdown + cancel() + + // Wait for goroutine to exit instead of using sleep + select { + case <-done: + // Job exited successfully + case <-time.After(time.Second): + t.Fatal("Cleanup job didn't exit within timeout") + } + }) +} + +// Helper functions for testing + +func createTestUnstructuredDashboard(uid, title string, resourceVersion string) unstructured.Unstructured { + return unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": dashboardv0alpha1.DashboardResourceInfo.GroupVersion().String(), + "kind": dashboardv0alpha1.DashboardResourceInfo.GroupVersionKind().Kind, + "metadata": map[string]interface{}{ + "name": uid, + "deletionTimestamp": "2023-01-01T00:00:00Z", + "resourceVersion": resourceVersion, + }, + "spec": map[string]interface{}{ + "title": title, + }, + }, + } +} diff --git a/pkg/services/dashboardsnapshots/service/service_test.go b/pkg/services/dashboardsnapshots/service/service_test.go index 9c4fedb47e4..c647528785b 100644 --- a/pkg/services/dashboardsnapshots/service/service_test.go +++ b/pkg/services/dashboardsnapshots/service/service_test.go @@ -11,6 +11,9 @@ import ( common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1" dashboardsnapshot "github.com/grafana/grafana/pkg/apis/dashboardsnapshot/v0alpha1" "github.com/grafana/grafana/pkg/infra/db" + "github.com/grafana/grafana/pkg/infra/kvstore" + "github.com/grafana/grafana/pkg/infra/serverlock" + "github.com/grafana/grafana/pkg/infra/tracing" acmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock" "github.com/grafana/grafana/pkg/services/apiserver/client" "github.com/grafana/grafana/pkg/services/dashboards" @@ -121,6 +124,8 @@ func TestValidateDashboardExists(t *testing.T) { nil, dualwrite.ProvideTestService(), sort.ProvideService(), + serverlock.ProvideService(sqlStore, tracing.InitializeTracerForTest()), + kvstore.NewFakeKVStore(), ) require.NoError(t, err) s := ProvideService(dsStore, secretsService, dashSvc) diff --git a/pkg/services/dashboardversion/dashverimpl/dashver.go b/pkg/services/dashboardversion/dashverimpl/dashver.go index 31103e1821a..c054154a72d 100644 --- a/pkg/services/dashboardversion/dashverimpl/dashver.go +++ b/pkg/services/dashboardversion/dashverimpl/dashver.go @@ -221,25 +221,42 @@ func (s *Service) getDashIDMaybeEmpty(ctx context.Context, uid string, orgID int return result.ID, nil } -func (s *Service) getHistoryThroughK8s(ctx context.Context, orgID int64, dashboardUID string, rv int64) (*dashver.DashboardVersionDTO, error) { - out, err := s.k8sclient.Get(ctx, dashboardUID, orgID, v1.GetOptions{ResourceVersion: strconv.FormatInt(rv, 10)}) - if err != nil { - if apierrors.IsNotFound(err) { +func (s *Service) getHistoryThroughK8s(ctx context.Context, orgID int64, dashboardUID string, version int64) (*dashver.DashboardVersionDTO, error) { + // this is an unideal implementation - we have to list all versions and filter here, since there currently is no way to query for the + // generation id in unified storage, so we cannot query for the dashboard version directly, and we cannot use search as history is not indexed. + // use batches to make sure we don't load too much data at once. + const batchSize = 50 + labelSelector := utils.LabelKeyGetHistory + "=" + dashboardUID + var continueToken string + for { + out, err := s.k8sclient.List(ctx, orgID, v1.ListOptions{ + LabelSelector: labelSelector, + Limit: int64(batchSize), + Continue: continueToken, + }) + if err != nil { + if apierrors.IsNotFound(err) { + return nil, dashboards.ErrDashboardNotFound + } + return nil, err + } + if out == nil { return nil, dashboards.ErrDashboardNotFound } - return nil, err - } - if out == nil { - return nil, dashboards.ErrDashboardNotFound + for _, item := range out.Items { + if item.GetGeneration() == version { + return s.UnstructuredToLegacyDashboardVersion(ctx, &item, orgID) + } + } + + continueToken = out.GetContinue() + if continueToken == "" || len(out.Items) == 0 { + break + } } - dash, err := s.UnstructuredToLegacyDashboardVersion(ctx, out, orgID) - if err != nil { - return nil, err - } - - return dash, nil + return nil, dashboards.ErrDashboardNotFound } func (s *Service) listHistoryThroughK8s(ctx context.Context, orgID int64, dashboardUID string, limit int64, continueToken string) (*dashver.DashboardVersionResponse, error) { @@ -308,10 +325,9 @@ func (s *Service) UnstructuredToLegacyDashboardVersion(ctx context.Context, item createdBy = updatedBy } } - - id, err := obj.GetResourceVersionInt64() - if err != nil { - return nil, err + created := obj.GetCreationTimestamp().Time + if updated, err := obj.GetUpdatedTimestamp(); err == nil && updated != nil { + created = *updated } restoreVer, err := getRestoreVersion(obj.GetMessage()) @@ -320,10 +336,10 @@ func (s *Service) UnstructuredToLegacyDashboardVersion(ctx context.Context, item } out := dashver.DashboardVersionDTO{ - ID: id, + ID: dashVersion, DashboardID: obj.GetDeprecatedInternalID(), // nolint:staticcheck DashboardUID: uid, - Created: obj.GetCreationTimestamp().Time, + Created: created, CreatedBy: createdBy.ID, Message: obj.GetMessage(), RestoredFrom: restoreVer, diff --git a/pkg/services/dashboardversion/dashverimpl/dashver_test.go b/pkg/services/dashboardversion/dashverimpl/dashver_test.go index bd022a00640..4f210ea906f 100644 --- a/pkg/services/dashboardversion/dashverimpl/dashver_test.go +++ b/pkg/services/dashboardversion/dashverimpl/dashver_test.go @@ -4,6 +4,7 @@ import ( "context" "errors" "testing" + "time" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" @@ -49,8 +50,9 @@ func TestDashboardVersionService(t *testing.T) { dashboardVersionService.features = featuremgmt.WithFeatures(featuremgmt.FlagKubernetesClientDashboardsFolders) dashboardService.On("GetDashboardUIDByID", mock.Anything, mock.AnythingOfType("*dashboards.GetDashboardRefByIDQuery")).Return(&dashboards.DashboardRef{UID: "uid"}, nil) - mockCli.On("GetUserFromMeta", mock.Anything, "user:1").Return(&user.User{ID: 1}, nil) - mockCli.On("Get", mock.Anything, "uid", int64(1), v1.GetOptions{ResourceVersion: "10"}, mock.Anything).Return(&unstructured.Unstructured{ + creationTimestamp := time.Now().Add(time.Hour * -24).UTC() + updatedTimestamp := time.Now().UTC().Truncate(time.Second) + dash := &unstructured.Unstructured{ Object: map[string]any{ "metadata": map[string]any{ "name": "uid", @@ -66,7 +68,14 @@ func TestDashboardVersionService(t *testing.T) { "spec": map[string]any{ "hello": "world", }, - }}, nil).Once() + }} + dash.SetCreationTimestamp(v1.NewTime(creationTimestamp)) + obj, err := utils.MetaAccessor(dash) + require.NoError(t, err) + obj.SetUpdatedTimestamp(&updatedTimestamp) + mockCli.On("GetUserFromMeta", mock.Anything, "user:1").Return(&user.User{ID: 1}, nil) + mockCli.On("List", mock.Anything, int64(1), mock.Anything).Return(&unstructured.UnstructuredList{ + Items: []unstructured.Unstructured{*dash}}, nil).Once() res, err := dashboardVersionService.Get(context.Background(), &dashver.GetDashboardVersionQuery{ DashboardID: 42, OrgID: 1, @@ -74,32 +83,34 @@ func TestDashboardVersionService(t *testing.T) { }) require.Nil(t, err) require.Equal(t, res, &dashver.DashboardVersionDTO{ - ID: 12, // RV should be used + ID: 10, Version: 10, ParentVersion: 9, DashboardID: 42, DashboardUID: "uid", CreatedBy: 1, + Created: updatedTimestamp, Data: simplejson.NewFromAny(map[string]any{"uid": "uid", "version": int64(10), "hello": "world"}), }) mockCli.On("GetUserFromMeta", mock.Anything, "user:2").Return(&user.User{ID: 2}, nil) - mockCli.On("Get", mock.Anything, "uid", int64(1), v1.GetOptions{ResourceVersion: "11"}, mock.Anything).Return(&unstructured.Unstructured{ - Object: map[string]any{ - "metadata": map[string]any{ - "name": "uid", - "resourceVersion": "11", - "generation": int64(11), - "labels": map[string]any{ - utils.LabelKeyDeprecatedInternalID: "42", // nolint:staticcheck + mockCli.On("List", mock.Anything, int64(1), mock.Anything).Return(&unstructured.UnstructuredList{ + Items: []unstructured.Unstructured{{ + Object: map[string]any{ + "metadata": map[string]any{ + "name": "uid", + "resourceVersion": "11", + "generation": int64(11), + "labels": map[string]any{ + utils.LabelKeyDeprecatedInternalID: "42", // nolint:staticcheck + }, + "annotations": map[string]any{ + utils.AnnoKeyCreatedBy: "user:1", + utils.AnnoKeyUpdatedBy: "user:2", // if updated by is set, that is the version creator + }, }, - "annotations": map[string]any{ - utils.AnnoKeyCreatedBy: "user:1", - utils.AnnoKeyUpdatedBy: "user:2", // if updated by is set, that is the version creator - }, - }, - "spec": map[string]any{}, - }}, nil).Once() + "spec": map[string]any{}, + }}}}, nil).Once() res, err = dashboardVersionService.Get(context.Background(), &dashver.GetDashboardVersionQuery{ DashboardID: 42, OrgID: 1, @@ -107,7 +118,7 @@ func TestDashboardVersionService(t *testing.T) { }) require.Nil(t, err) require.Equal(t, res, &dashver.DashboardVersionDTO{ - ID: 11, // RV should be used + ID: 11, Version: 11, ParentVersion: 10, DashboardID: 42, @@ -124,7 +135,7 @@ func TestDashboardVersionService(t *testing.T) { dashboardVersionService.k8sclient = mockCli dashboardVersionService.features = featuremgmt.WithFeatures(featuremgmt.FlagKubernetesClientDashboardsFolders) dashboardService.On("GetDashboardUIDByID", mock.Anything, mock.AnythingOfType("*dashboards.GetDashboardRefByIDQuery")).Return(&dashboards.DashboardRef{UID: "uid"}, nil) - mockCli.On("Get", mock.Anything, "uid", int64(1), v1.GetOptions{ResourceVersion: "10"}, mock.Anything).Return(nil, apierrors.NewNotFound(schema.GroupResource{Group: "dashboards.dashboard.grafana.app", Resource: "dashboard"}, "uid")) + mockCli.On("List", mock.Anything, int64(1), mock.Anything).Return(nil, apierrors.NewNotFound(schema.GroupResource{Group: "dashboards.dashboard.grafana.app", Resource: "dashboard"}, "uid")) _, err := dashboardVersionService.Get(context.Background(), &dashver.GetDashboardVersionQuery{ DashboardID: 42, @@ -276,7 +287,7 @@ func TestListDashboardVersions(t *testing.T) { require.Equal(t, 1, len(res.Versions)) require.EqualValues(t, &dashver.DashboardVersionResponse{ Versions: []*dashver.DashboardVersionDTO{{ - ID: 12, // should take rv + ID: 5, DashboardID: 42, ParentVersion: 4, Version: 5, // should take from spec diff --git a/pkg/services/featuremgmt/registry.go b/pkg/services/featuremgmt/registry.go index da680c84add..89f2d6f8220 100644 --- a/pkg/services/featuremgmt/registry.go +++ b/pkg/services/featuremgmt/registry.go @@ -441,13 +441,6 @@ var ( Owner: grafanaObservabilityTracesAndProfilingSquad, Expression: "false", }, - { - Name: "metricsSummary", - Description: "Enables metrics summary queries in the Tempo data source", - Stage: FeatureStageExperimental, - FrontendOnly: true, - Owner: grafanaObservabilityTracesAndProfilingSquad, - }, { Name: "datasourceAPIServers", Description: "Expose some datasources as apiservers.", @@ -656,12 +649,6 @@ var ( Owner: grafanaAppPlatformSquad, FrontendOnly: true, }, - { - Name: "kubernetesRestore", - Description: "Allow restoring objects in k8s", - Stage: FeatureStageExperimental, - Owner: grafanaAppPlatformSquad, - }, { Name: "kubernetesClientDashboardsFolders", Description: "Route the folder and dashboard service requests to k8s", @@ -1405,8 +1392,9 @@ var ( { Name: "unifiedStorageSearchPermissionFiltering", Description: "Enable permission filtering on unified storage search", - Stage: FeatureStageExperimental, + Stage: FeatureStageGeneralAvailability, Owner: grafanaSearchAndStorageSquad, + Expression: "true", HideFromDocs: true, HideFromAdminPage: true, }, @@ -1836,6 +1824,14 @@ var ( HideFromAdminPage: true, HideFromDocs: true, }, + { + Name: "unifiedStorageGrpcConnectionPool", + Description: "Enables the unified storage grpc connection pool", + Stage: FeatureStageExperimental, + Owner: grafanaSearchAndStorageSquad, + HideFromAdminPage: true, + HideFromDocs: true, + }, } ) diff --git a/pkg/services/featuremgmt/toggles-gitlog.csv b/pkg/services/featuremgmt/toggles-gitlog.csv index 0cdaace5e3e..cb90956b908 100644 --- a/pkg/services/featuremgmt/toggles-gitlog.csv +++ b/pkg/services/featuremgmt/toggles-gitlog.csv @@ -413,7 +413,6 @@ unifiedStorageSearchUI,2024-12-19T18:21:48Z,,a8f347144ddc16f2033fdeb4f3474e49239 playlistsReconciler,2024-12-20T03:09:31Z,,24bf337c562dc9b9d8684cc9acb7ea171ea83414,Charandas k8SFolderCounts,2024-12-27T17:10:44Z,,df36e77cd31d2ad77e3d708748d040367a0c8c9c,Leonor Oliveira k8SFolderMove,2024-12-27T17:10:44Z,,df36e77cd31d2ad77e3d708748d040367a0c8c9c,Leonor Oliveira -kubernetesRestore,2025-01-03T14:48:47Z,,5429512779bd5f25b88ff728ea91efdef7dfafa0,Stephanie Hingtgen improvedExternalSessionHandlingSAML,2025-01-09T17:02:49Z,,c52ec21c75ab72c2f7d28259bac0364edae560d0,Misi teamHttpHeadersMimir,2025-01-13T10:42:47Z,,04acbcdef23f673bd6bbfdbbece29c9769ce155a,Eric Leijonmarck ABTestFeatureToggleA,2025-01-13T21:13:13Z,,009d7f42b3d09b3a6be1f00f07314e2b25af7ebc,Nathan Marrs diff --git a/pkg/services/featuremgmt/toggles_gen.csv b/pkg/services/featuremgmt/toggles_gen.csv index 9d6dc6fd264..62bd4844236 100644 --- a/pkg/services/featuremgmt/toggles_gen.csv +++ b/pkg/services/featuremgmt/toggles_gen.csv @@ -58,7 +58,6 @@ awsDatasourcesTempCredentials,experimental,@grafana/aws-datasources,false,false, transformationsRedesign,GA,@grafana/observability-metrics,false,false,true mlExpressions,experimental,@grafana/alerting-squad,false,false,false traceQLStreaming,GA,@grafana/observability-traces-and-profiling,false,false,true -metricsSummary,experimental,@grafana/observability-traces-and-profiling,false,false,true datasourceAPIServers,experimental,@grafana/grafana-app-platform-squad,false,true,false grafanaAPIServerWithExperimentalAPIs,experimental,@grafana/grafana-app-platform-squad,true,true,false provisioning,experimental,@grafana/grafana-app-platform-squad,false,true,false @@ -86,7 +85,6 @@ formatString,GA,@grafana/dataviz-squad,false,false,true kubernetesPlaylists,GA,@grafana/grafana-app-platform-squad,false,true,false kubernetesSnapshots,experimental,@grafana/grafana-app-platform-squad,false,true,false kubernetesDashboards,experimental,@grafana/grafana-app-platform-squad,false,false,true -kubernetesRestore,experimental,@grafana/grafana-app-platform-squad,false,false,false kubernetesClientDashboardsFolders,experimental,@grafana/grafana-app-platform-squad,false,false,false datasourceQueryTypes,experimental,@grafana/grafana-app-platform-squad,false,true,false queryService,experimental,@grafana/grafana-app-platform-squad,false,true,false @@ -184,7 +182,7 @@ useSessionStorageForRedirection,GA,@grafana/identity-access-team,false,false,fal rolePickerDrawer,experimental,@grafana/identity-access-team,false,false,false unifiedStorageSearch,experimental,@grafana/search-and-storage,false,false,false unifiedStorageSearchSprinkles,experimental,@grafana/search-and-storage,false,false,false -unifiedStorageSearchPermissionFiltering,experimental,@grafana/search-and-storage,false,false,false +unifiedStorageSearchPermissionFiltering,GA,@grafana/search-and-storage,false,false,false managedDualWriter,experimental,@grafana/search-and-storage,false,false,false pluginsSriChecks,GA,@grafana/plugins-platform-backend,false,false,false unifiedStorageBigObjectsSupport,experimental,@grafana/search-and-storage,false,false,false @@ -242,3 +240,4 @@ extraLanguages,experimental,@grafana/grafana-frontend-platform,false,false,true noBackdropBlur,experimental,@grafana/grafana-frontend-platform,false,false,true alertingMigrationUI,experimental,@grafana/alerting-squad,false,false,true unifiedStorageHistoryPruner,experimental,@grafana/search-and-storage,false,false,false +unifiedStorageGrpcConnectionPool,experimental,@grafana/search-and-storage,false,false,false diff --git a/pkg/services/featuremgmt/toggles_gen.go b/pkg/services/featuremgmt/toggles_gen.go index 943895b2c9e..b1d5e756e5b 100644 --- a/pkg/services/featuremgmt/toggles_gen.go +++ b/pkg/services/featuremgmt/toggles_gen.go @@ -243,10 +243,6 @@ const ( // Enables response streaming of TraceQL queries of the Tempo data source FlagTraceQLStreaming = "traceQLStreaming" - // FlagMetricsSummary - // Enables metrics summary queries in the Tempo data source - FlagMetricsSummary = "metricsSummary" - // FlagDatasourceAPIServers // Expose some datasources as apiservers. FlagDatasourceAPIServers = "datasourceAPIServers" @@ -355,10 +351,6 @@ const ( // Use the kubernetes API in the frontend for dashboards FlagKubernetesDashboards = "kubernetesDashboards" - // FlagKubernetesRestore - // Allow restoring objects in k8s - FlagKubernetesRestore = "kubernetesRestore" - // FlagKubernetesClientDashboardsFolders // Route the folder and dashboard service requests to k8s FlagKubernetesClientDashboardsFolders = "kubernetesClientDashboardsFolders" @@ -978,4 +970,8 @@ const ( // FlagUnifiedStorageHistoryPruner // Enables the unified storage history pruner FlagUnifiedStorageHistoryPruner = "unifiedStorageHistoryPruner" + + // FlagUnifiedStorageGrpcConnectionPool + // Enables the unified storage grpc connection pool + FlagUnifiedStorageGrpcConnectionPool = "unifiedStorageGrpcConnectionPool" ) diff --git a/pkg/services/featuremgmt/toggles_gen.json b/pkg/services/featuremgmt/toggles_gen.json index 0d0fe4b0a57..1d0e357c378 100644 --- a/pkg/services/featuremgmt/toggles_gen.json +++ b/pkg/services/featuremgmt/toggles_gen.json @@ -2404,18 +2404,6 @@ "expression": "true" } }, - { - "metadata": { - "name": "kubernetesRestore", - "resourceVersion": "1735880498698", - "creationTimestamp": "2025-01-03T14:48:47Z" - }, - "spec": { - "description": "Allow restoring objects in k8s", - "stage": "experimental", - "codeowner": "@grafana/grafana-app-platform-squad" - } - }, { "metadata": { "name": "kubernetesSnapshots", @@ -2780,7 +2768,8 @@ "metadata": { "name": "metricsSummary", "resourceVersion": "1718727528075", - "creationTimestamp": "2023-08-28T14:02:12Z" + "creationTimestamp": "2023-08-28T14:02:12Z", + "deletionTimestamp": "2024-11-25T10:47:18Z" }, "spec": { "description": "Enables metrics summary queries in the Tempo data source", @@ -4215,6 +4204,20 @@ "codeowner": "@grafana/search-and-storage" } }, + { + "metadata": { + "name": "unifiedStorageGrpcConnectionPool", + "resourceVersion": "1742549790491", + "creationTimestamp": "2025-03-21T09:36:30Z" + }, + "spec": { + "description": "Enables the unified storage grpc connection pool", + "stage": "experimental", + "codeowner": "@grafana/search-and-storage", + "hideFromAdminPage": true, + "hideFromDocs": true + } + }, { "metadata": { "name": "unifiedStorageHistoryPruner", @@ -4261,15 +4264,19 @@ { "metadata": { "name": "unifiedStorageSearchPermissionFiltering", - "resourceVersion": "1737489629408", - "creationTimestamp": "2025-01-22T11:38:37Z" + "resourceVersion": "1742564039800", + "creationTimestamp": "2025-01-22T11:38:37Z", + "annotations": { + "grafana.app/updatedTimestamp": "2025-03-21 13:33:59.800619 +0000 UTC" + } }, "spec": { "description": "Enable permission filtering on unified storage search", - "stage": "experimental", + "stage": "GA", "codeowner": "@grafana/search-and-storage", "hideFromAdminPage": true, - "hideFromDocs": true + "hideFromDocs": true, + "expression": "true" } }, { diff --git a/pkg/services/folder/folderimpl/folder_test.go b/pkg/services/folder/folderimpl/folder_test.go index b6361936a5d..fcb88a922cf 100644 --- a/pkg/services/folder/folderimpl/folder_test.go +++ b/pkg/services/folder/folderimpl/folder_test.go @@ -20,8 +20,10 @@ import ( "github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/infra/db" "github.com/grafana/grafana/pkg/infra/db/dbtest" + "github.com/grafana/grafana/pkg/infra/kvstore" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log/logtest" + "github.com/grafana/grafana/pkg/infra/serverlock" "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/accesscontrol/acimpl" @@ -498,7 +500,10 @@ func TestIntegrationNestedFolderService(t *testing.T) { publicDashboardFakeService.On("DeleteByDashboardUIDs", mock.Anything, mock.Anything, mock.Anything).Return(nil) dashSrv, err := dashboardservice.ProvideDashboardServiceImpl(cfg, dashStore, folderStore, featuresFlagOn, folderPermissions, ac, serviceWithFlagOn, nestedFolderStore, nil, - client.MockTestRestConfig{}, nil, quotaService, nil, publicDashboardFakeService, nil, dualwrite.ProvideTestService(), sort.ProvideService()) + client.MockTestRestConfig{}, nil, quotaService, nil, publicDashboardFakeService, nil, dualwrite.ProvideTestService(), sort.ProvideService(), + serverlock.ProvideService(db, tracing.InitializeTracerForTest()), + kvstore.NewFakeKVStore(), + ) require.NoError(t, err) dashSrv.RegisterDashboardPermissions(dashboardPermissions) @@ -584,7 +589,10 @@ func TestIntegrationNestedFolderService(t *testing.T) { publicDashboardFakeService.On("DeleteByDashboardUIDs", mock.Anything, mock.Anything, mock.Anything).Return(nil) dashSrv, err := dashboardservice.ProvideDashboardServiceImpl(cfg, dashStore, folderStore, featuresFlagOff, - folderPermissions, ac, serviceWithFlagOff, nestedFolderStore, nil, client.MockTestRestConfig{}, nil, quotaService, nil, publicDashboardFakeService, nil, dualwrite.ProvideTestService(), sort.ProvideService()) + folderPermissions, ac, serviceWithFlagOff, nestedFolderStore, nil, client.MockTestRestConfig{}, nil, quotaService, nil, publicDashboardFakeService, nil, dualwrite.ProvideTestService(), sort.ProvideService(), + serverlock.ProvideService(db, tracing.InitializeTracerForTest()), + kvstore.NewFakeKVStore(), + ) require.NoError(t, err) dashSrv.RegisterDashboardPermissions(dashboardPermissions) alertStore, err := ngstore.ProvideDBStore(cfg, featuresFlagOff, db, serviceWithFlagOff, dashSrv, ac, b) @@ -729,7 +737,10 @@ func TestIntegrationNestedFolderService(t *testing.T) { dashSrv, err := dashboardservice.ProvideDashboardServiceImpl(cfg, dashStore, folderStore, tc.featuresFlag, folderPermissions, ac, tc.service, tc.service.store, nil, client.MockTestRestConfig{}, nil, quotaService, nil, publicDashboardFakeService, nil, - dualwrite.ProvideTestService(), sort.ProvideService()) + dualwrite.ProvideTestService(), sort.ProvideService(), + serverlock.ProvideService(db, tracing.InitializeTracerForTest()), + kvstore.NewFakeKVStore(), + ) require.NoError(t, err) dashSrv.RegisterDashboardPermissions(dashboardPermissions) @@ -1524,6 +1535,8 @@ func TestIntegrationNestedFolderSharedWithMe(t *testing.T) { nil, dualwrite.ProvideTestService(), sort.ProvideService(), + serverlock.ProvideService(db, tracing.InitializeTracerForTest()), + kvstore.NewFakeKVStore(), ) require.NoError(t, err) dashboardService.RegisterDashboardPermissions(dashboardPermissions) diff --git a/pkg/services/folder/folderimpl/sqlstore.go b/pkg/services/folder/folderimpl/sqlstore.go index 14601344a82..20c17c324b5 100644 --- a/pkg/services/folder/folderimpl/sqlstore.go +++ b/pkg/services/folder/folderimpl/sqlstore.go @@ -607,9 +607,9 @@ func (ss *FolderStoreImpl) GetDescendants(ctx context.Context, orgID int64, ance } func getFullpathSQL(dialect migrator.Dialect) string { - escaped := "\\/" - if dialect.DriverName() == migrator.MySQL { - escaped = "\\\\/" + escaped := `\/` + if dialect.DriverName() == migrator.MySQL || dialect.DriverName() == migrator.Spanner { + escaped = `\\/` } concatCols := make([]string, 0, folder.MaxNestedFolderDepth) concatCols = append(concatCols, fmt.Sprintf("COALESCE(REPLACE(f0.title, '/', '%s'), '')", escaped)) diff --git a/pkg/services/guardian/guardian.go b/pkg/services/guardian/guardian.go index 7100b7ffacc..58ebe2aa878 100644 --- a/pkg/services/guardian/guardian.go +++ b/pkg/services/guardian/guardian.go @@ -7,7 +7,6 @@ import ( "github.com/grafana/grafana/pkg/apimachinery/errutil" "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/infra/metrics" - "github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/folder" ) @@ -34,12 +33,6 @@ var New = func(ctx context.Context, dashId int64, orgId int64, user identity.Req panic("no guardian factory implementation provided") } -// NewByDashboard factory for creating a new dashboard guardian instance -// When using access control this function is replaced on startup and the AccessControlDashboardGuardian is returned -var NewByDashboard = func(ctx context.Context, dash *dashboards.Dashboard, orgId int64, user identity.Requester) (DashboardGuardian, error) { - panic("no guardian factory implementation provided") -} - // NewByFolderUID factory for creating a new folder guardian instance // When using access control this function is replaced on startup and the AccessControlDashboardGuardian is returned var NewByFolderUID = func(ctx context.Context, folderUID string, orgId int64, user identity.Requester) (DashboardGuardian, error) { @@ -108,13 +101,6 @@ func MockDashboardGuardian(mock *FakeDashboardGuardian) { mock.User = user return mock, nil } - NewByDashboard = func(_ context.Context, dash *dashboards.Dashboard, orgId int64, user identity.Requester) (DashboardGuardian, error) { - mock.OrgID = orgId - mock.DashUID = dash.UID - mock.DashID = dash.ID - mock.User = user - return mock, nil - } NewByFolderUID = func(_ context.Context, folderUID string, orgId int64, user identity.Requester) (DashboardGuardian, error) { mock.OrgID = orgId diff --git a/pkg/services/guardian/provider.go b/pkg/services/guardian/provider.go index 7e3902f1e5a..7be96481b51 100644 --- a/pkg/services/guardian/provider.go +++ b/pkg/services/guardian/provider.go @@ -31,10 +31,6 @@ func InitAccessControlGuardian( return NewAccessControlDashboardGuardian(ctx, cfg, dashId, user, ac, dashboardService, folderService, logger) } - NewByDashboard = func(ctx context.Context, dash *dashboards.Dashboard, orgId int64, user identity.Requester) (DashboardGuardian, error) { - return NewAccessControlDashboardGuardianByDashboard(ctx, cfg, dash, user, ac, dashboardService, folderService, logger) - } - NewByFolderUID = func(ctx context.Context, folderUID string, orgId int64, user identity.Requester) (DashboardGuardian, error) { return NewAccessControlFolderGuardianByUID(ctx, cfg, folderUID, user, ac, dashboardService, folderService) } diff --git a/pkg/services/libraryelements/libraryelements_test.go b/pkg/services/libraryelements/libraryelements_test.go index ee1d1fe7ec6..8eec5685b14 100644 --- a/pkg/services/libraryelements/libraryelements_test.go +++ b/pkg/services/libraryelements/libraryelements_test.go @@ -18,7 +18,9 @@ import ( "github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/infra/db" + "github.com/grafana/grafana/pkg/infra/kvstore" "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/infra/serverlock" "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/kinds/librarypanel" "github.com/grafana/grafana/pkg/services/accesscontrol" @@ -366,6 +368,8 @@ func createDashboard(t *testing.T, sqlStore db.DB, user user.SignedInUser, dash nil, dualwrite.ProvideTestService(), sort.ProvideService(), + serverlock.ProvideService(sqlStore, tracing.InitializeTracerForTest()), + kvstore.NewFakeKVStore(), ) require.NoError(t, err) service.RegisterDashboardPermissions(dashboardPermissions) @@ -459,6 +463,8 @@ func scenarioWithPanel(t *testing.T, desc string, fn func(t *testing.T, sc scena features, folderPermissions, ac, folderSvc, fStore, nil, client.MockTestRestConfig{}, nil, quotaService, nil, nil, nil, dualwrite.ProvideTestService(), sort.ProvideService(), + serverlock.ProvideService(sqlStore, tracing.InitializeTracerForTest()), + kvstore.NewFakeKVStore(), ) require.NoError(t, svcErr) dashboardService.RegisterDashboardPermissions(dashboardPermissions) @@ -532,6 +538,8 @@ func testScenario(t *testing.T, desc string, fn func(t *testing.T, sc scenarioCo features, folderPermissions, ac, folderSvc, fStore, nil, client.MockTestRestConfig{}, nil, quotaService, nil, nil, nil, dualwrite.ProvideTestService(), sort.ProvideService(), + serverlock.ProvideService(sqlStore, tracing.InitializeTracerForTest()), + kvstore.NewFakeKVStore(), ) require.NoError(t, dashSvcErr) dashService.RegisterDashboardPermissions(dashboardPermissions) diff --git a/pkg/services/libraryelements/model/model.go b/pkg/services/libraryelements/model/model.go index 0828e9578f6..0189d785fd5 100644 --- a/pkg/services/libraryelements/model/model.go +++ b/pkg/services/libraryelements/model/model.go @@ -26,7 +26,7 @@ type LibraryElement struct { Kind int64 Type string Description string - Model json.RawMessage + Model json.RawMessage `xorm:"TEXT"` // Column is defined as TEXT in `library_element`. Version int64 Created time.Time diff --git a/pkg/services/librarypanels/librarypanels_test.go b/pkg/services/librarypanels/librarypanels_test.go index 531f3714eca..930ec705e64 100644 --- a/pkg/services/librarypanels/librarypanels_test.go +++ b/pkg/services/librarypanels/librarypanels_test.go @@ -15,7 +15,9 @@ import ( "github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/infra/db" + "github.com/grafana/grafana/pkg/infra/kvstore" "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/infra/serverlock" "github.com/grafana/grafana/pkg/infra/slugify" "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/kinds/librarypanel" @@ -739,7 +741,8 @@ func createDashboard(t *testing.T, sqlStore db.DB, user *user.SignedInUser, dash features, acmock.NewMockedPermissionsService(), ac, foldertest.NewFakeService(), folder.NewFakeStore(), nil, client.MockTestRestConfig{}, nil, quotaService, nil, nil, nil, dualwrite.ProvideTestService(), sort.ProvideService(), - ) + serverlock.ProvideService(sqlStore, tracing.InitializeTracerForTest()), + kvstore.NewFakeKVStore()) require.NoError(t, err) service.RegisterDashboardPermissions(dashPermissionService) dashboard, err := service.SaveDashboard(context.Background(), dashItem, true) @@ -837,7 +840,8 @@ func testScenario(t *testing.T, desc string, fn func(t *testing.T, sc scenarioCo features, acmock.NewMockedPermissionsService(), ac, folderSvc, folder.NewFakeStore(), nil, client.MockTestRestConfig{}, nil, quotaService, nil, nil, nil, dualwrite.ProvideTestService(), sort.ProvideService(), - ) + serverlock.ProvideService(sqlStore, tracing.InitializeTracerForTest()), + kvstore.NewFakeKVStore()) require.NoError(t, err) dashService.RegisterDashboardPermissions(dashPermissionService) guardian.InitAccessControlGuardian(cfg, ac, dashService, folderSvc, log.NewNopLogger()) diff --git a/pkg/services/live/features/dashboard.go b/pkg/services/live/features/dashboard.go index 2390f409fb4..286dad7c49c 100644 --- a/pkg/services/live/features/dashboard.go +++ b/pkg/services/live/features/dashboard.go @@ -10,8 +10,8 @@ import ( "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/infra/db" + "github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/dashboards" - "github.com/grafana/grafana/pkg/services/guardian" "github.com/grafana/grafana/pkg/services/live/model" ) @@ -63,6 +63,7 @@ type DashboardHandler struct { ClientCount model.ChannelClientCount Store db.DB DashboardService dashboards.DashboardService + AccessControl accesscontrol.AccessControl } // GetHandlerForPath called on init @@ -77,20 +78,17 @@ func (h *DashboardHandler) OnSubscribe(ctx context.Context, user identity.Reques // make sure can view this dashboard if len(parts) == 2 && parts[0] == "uid" { query := dashboards.GetDashboardQuery{UID: parts[1], OrgID: user.GetOrgID()} - queryResult, err := h.DashboardService.GetDashboard(ctx, &query) + _, err := h.DashboardService.GetDashboard(ctx, &query) if err != nil { logger.Error("Error getting dashboard", "query", query, "error", err) return model.SubscribeReply{}, backend.SubscribeStreamStatusNotFound, nil } - dash := queryResult - guard, err := guardian.NewByDashboard(ctx, dash, user.GetOrgID(), user) - if err != nil { + evaluator := accesscontrol.EvalPermission(dashboards.ActionDashboardsRead, dashboards.ScopeDashboardsProvider.GetResourceScopeUID(parts[1])) + canView, err := h.AccessControl.Evaluate(ctx, user, evaluator) + if err != nil || !canView { return model.SubscribeReply{}, backend.SubscribeStreamStatusPermissionDenied, err } - if canView, err := guard.CanView(); err != nil || !canView { - return model.SubscribeReply{}, backend.SubscribeStreamStatusPermissionDenied, nil - } return model.SubscribeReply{ Presence: true, @@ -119,19 +117,14 @@ func (h *DashboardHandler) OnPublish(ctx context.Context, requester identity.Req return model.PublishReply{}, backend.PublishStreamStatusNotFound, fmt.Errorf("ignore???") } query := dashboards.GetDashboardQuery{UID: parts[1], OrgID: requester.GetOrgID()} - queryResult, err := h.DashboardService.GetDashboard(ctx, &query) + _, err = h.DashboardService.GetDashboard(ctx, &query) if err != nil { logger.Error("Unknown dashboard", "query", query) return model.PublishReply{}, backend.PublishStreamStatusNotFound, nil } - guard, err := guardian.NewByDashboard(ctx, queryResult, requester.GetOrgID(), requester) - if err != nil { - logger.Error("Failed to create guardian", "err", err) - return model.PublishReply{}, backend.PublishStreamStatusNotFound, fmt.Errorf("internal error") - } - - canEdit, err := guard.CanEdit() + evaluator := accesscontrol.EvalPermission(dashboards.ActionDashboardsWrite, dashboards.ScopeDashboardsProvider.GetResourceScopeUID(parts[1])) + canEdit, err := h.AccessControl.Evaluate(ctx, requester, evaluator) if err != nil { return model.PublishReply{}, backend.PublishStreamStatusNotFound, fmt.Errorf("internal error") } diff --git a/pkg/services/live/features/watch.go b/pkg/services/live/features/watch.go new file mode 100644 index 00000000000..05a7f875518 --- /dev/null +++ b/pkg/services/live/features/watch.go @@ -0,0 +1,214 @@ +package features + +import ( + "context" + "fmt" + "strings" + "sync" + + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/watch" + "k8s.io/client-go/dynamic" + + provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1" + + "github.com/grafana/authlib/types" + "github.com/grafana/grafana-app-sdk/logging" + "github.com/grafana/grafana-plugin-sdk-go/backend" + "github.com/grafana/grafana-plugin-sdk-go/data/utils/jsoniter" + "github.com/grafana/grafana-plugin-sdk-go/live" + + "github.com/grafana/grafana/pkg/apimachinery/identity" + "github.com/grafana/grafana/pkg/services/apiserver" + "github.com/grafana/grafana/pkg/services/live/model" +) + +// WatchRunner will start a watch task and broadcast results +type WatchRunner struct { + publisher model.ChannelPublisher + configProvider apiserver.RestConfigProvider + + watchingMu sync.Mutex + watching map[string]*watcher +} + +func NewWatchRunner(publisher model.ChannelPublisher, configProvider apiserver.RestConfigProvider) *WatchRunner { + return &WatchRunner{ + publisher: publisher, + configProvider: configProvider, + watching: make(map[string]*watcher), + } +} + +func (b *WatchRunner) GetHandlerForPath(_ string) (model.ChannelHandler, error) { + return b, nil // all dashboards share the same handler +} + +// Valid paths look like: {version}/{resource}[={name}]/{user.uid} +// * v0alpha1/dashboards/u12345 +// * v0alpha1/dashboards=ABCD/u12345 +func (b *WatchRunner) OnSubscribe(ctx context.Context, u identity.Requester, e model.SubscribeEvent) (model.SubscribeReply, backend.SubscribeStreamStatus, error) { + // To make sure we do not share resources across users, in clude the UID in the path + userID := u.GetIdentifier() + if userID == "" { + return model.SubscribeReply{}, backend.SubscribeStreamStatusPermissionDenied, fmt.Errorf("missing user identity") + } + if !strings.HasSuffix(e.Path, userID) { + return model.SubscribeReply{}, backend.SubscribeStreamStatusPermissionDenied, fmt.Errorf("path must end with user uid (%s)", userID) + } + + // While testing with provisioning repositories, we will limit this to admin only + if !u.HasRole(identity.RoleAdmin) { + return model.SubscribeReply{}, backend.SubscribeStreamStatusPermissionDenied, fmt.Errorf("only admin users for now") + } + + b.watchingMu.Lock() + defer b.watchingMu.Unlock() + + current, ok := b.watching[e.Channel] + if ok && !current.done { + return model.SubscribeReply{ + JoinLeave: false, + Presence: false, + Recover: false, + }, backend.SubscribeStreamStatusOK, nil + } + + // Try to start a watcher for this request + gvr, name, err := parseWatchRequest(e.Channel, userID) + if err != nil { + return model.SubscribeReply{}, backend.SubscribeStreamStatusNotFound, err + } + + // Test this with only provisiong support -- then we can evaluate a broader rollout + if gvr.Group != provisioning.GROUP { + return model.SubscribeReply{}, backend.SubscribeStreamStatusPermissionDenied, + fmt.Errorf("watching provisioned resources is OK allowed (for now)") + } + + requester := types.WithAuthInfo(context.Background(), u) + cfg, err := b.configProvider.GetRestConfig(requester) + if err != nil { + return model.SubscribeReply{}, backend.SubscribeStreamStatusNotFound, err + } + uclient, err := dynamic.NewForConfig(cfg) + if err != nil { + return model.SubscribeReply{}, backend.SubscribeStreamStatusNotFound, err + } + client := uclient.Resource(gvr).Namespace(u.GetNamespace()) + + opts := v1.ListOptions{} + if len(name) > 1 { + opts.FieldSelector = "metadata.name=" + name + } + watch, err := client.Watch(requester, opts) + if err != nil { + return model.SubscribeReply{}, backend.SubscribeStreamStatusNotFound, err + } + + current = &watcher{ + orgId: u.GetOrgID(), + channel: e.Channel, + publisher: b.publisher, + watch: watch, + } + + b.watching[e.Channel] = current + go current.run(ctx) + + return model.SubscribeReply{ + JoinLeave: false, // need unsubscribe envents + Presence: false, + Recover: false, + }, backend.SubscribeStreamStatusOK, nil +} + +func parseWatchRequest(channel string, user string) (gvr schema.GroupVersionResource, name string, err error) { + addr, err := live.ParseChannel(channel) + if err != nil { + return gvr, "", err + } + + parts := strings.Split(addr.Path, "/") + if len(parts) != 3 { + return gvr, "", fmt.Errorf("expecting path: {version}/{resource}={name}/{user}") + } + if parts[2] != user { + return gvr, "", fmt.Errorf("expecting user suffix: %s", user) + } + + resource := strings.Split(parts[1], "=") + gvr = schema.GroupVersionResource{ + Group: addr.Namespace, + Version: parts[0], + Resource: resource[0], + } + if len(resource) > 1 { + name = resource[1] + } + return gvr, name, nil +} + +// OnPublish is called when a client wants to broadcast on the websocket +func (b *WatchRunner) OnPublish(_ context.Context, u identity.Requester, e model.PublishEvent) (model.PublishReply, backend.PublishStreamStatus, error) { + return model.PublishReply{}, backend.PublishStreamStatusNotFound, fmt.Errorf("watch does not support publish") +} + +type watcher struct { + orgId int64 + channel string + publisher model.ChannelPublisher + done bool + watch watch.Interface +} + +func (b *watcher) run(ctx context.Context) { + logger := logging.FromContext(ctx).With("channel", b.channel) + + ch := b.watch.ResultChan() + for { + select { + // This is sent when there are no longer any subscriptions + case <-ctx.Done(): + logger.Info("context done", "channel", b.channel) + b.watch.Stop() + b.done = true + return + + // Each watch event + case event, ok := <-ch: + if !ok { + logger.Info("watch stream broken", "channel", b.channel) + b.watch.Stop() + b.done = true // will force reconnect from the frontend + return + } + + cfg := jsoniter.ConfigCompatibleWithStandardLibrary + stream := cfg.BorrowStream(nil) + defer cfg.ReturnStream(stream) + + // regular json.Marshal() uses upper case + stream.WriteObjectStart() + stream.WriteObjectField("type") + stream.WriteString(string(event.Type)) + stream.WriteMore() + stream.WriteObjectField("object") + stream.WriteVal(event.Object) + stream.WriteObjectEnd() + + buf := stream.Buffer() + data := make([]byte, len(buf)) + copy(data, buf) + + err := b.publisher(b.orgId, b.channel, data) + if err != nil { + logger.Error("publish error", "channel", b.channel, "err", err) + b.watch.Stop() + b.done = true // will force reconnect from the frontend + continue + } + } + } +} diff --git a/pkg/services/live/live.go b/pkg/services/live/live.go index 35c2be26d69..eb854f99d9c 100644 --- a/pkg/services/live/live.go +++ b/pkg/services/live/live.go @@ -36,6 +36,7 @@ import ( "github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/annotations" + "github.com/grafana/grafana/pkg/services/apiserver" contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model" "github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/datasources" @@ -79,7 +80,7 @@ func ProvideService(plugCtxProvider *plugincontext.Provider, cfg *setting.Cfg, r dataSourceCache datasources.CacheService, sqlStore db.DB, secretsService secrets.Service, usageStatsService usagestats.Service, queryDataService query.Service, toggles featuremgmt.FeatureToggles, accessControl accesscontrol.AccessControl, dashboardService dashboards.DashboardService, annotationsRepo annotations.Repository, - orgService org.Service) (*GrafanaLive, error) { + orgService org.Service, configProvider apiserver.RestConfigProvider) (*GrafanaLive, error) { g := &GrafanaLive{ Cfg: cfg, Features: toggles, @@ -184,12 +185,18 @@ func ProvideService(plugCtxProvider *plugincontext.Provider, cfg *setting.Cfg, r ClientCount: g.ClientCount, Store: sqlStore, DashboardService: dashboardService, + AccessControl: accessControl, } g.storage = database.NewStorage(g.SQLStore, g.CacheService) g.GrafanaScope.Dashboards = dash g.GrafanaScope.Features["dashboard"] = dash g.GrafanaScope.Features["broadcast"] = features.NewBroadcastRunner(g.storage) + // Testing watch with just the provisioning support -- this will be removed when it is well validated + if toggles.IsEnabledGlobally(featuremgmt.FlagProvisioning) { + g.GrafanaScope.Features["watch"] = features.NewWatchRunner(g.Publish, configProvider) + } + g.surveyCaller = survey.NewCaller(managedStreamRunner, node) err = g.surveyCaller.SetupHandlers() if err != nil { @@ -888,6 +895,8 @@ func (g *GrafanaLive) GetChannelHandlerFactory(ctx context.Context, user identit switch scope { case live.ScopeGrafana: return g.handleGrafanaScope(user, namespace) + case "watch": // TODO: live.ScopeWatch: update 275 https://github.com/grafana/grafana-plugin-sdk-go/releases + return g.handleWatchScope() case live.ScopePlugin: return g.handlePluginScope(ctx, user, namespace) case live.ScopeDatasource: @@ -906,6 +915,13 @@ func (g *GrafanaLive) handleGrafanaScope(_ identity.Requester, namespace string) return nil, fmt.Errorf("unknown feature: %q", namespace) } +func (g *GrafanaLive) handleWatchScope() (model.ChannelHandlerFactory, error) { + if p, ok := g.GrafanaScope.Features["watch"]; ok { + return p, nil + } + return nil, fmt.Errorf("watch not registered") +} + func (g *GrafanaLive) handlePluginScope(ctx context.Context, _ identity.Requester, namespace string) (model.ChannelHandlerFactory, error) { streamHandler, err := g.getStreamPlugin(ctx, namespace) if err != nil { diff --git a/pkg/services/live/live_test.go b/pkg/services/live/live_test.go index 447910e1b84..c002ec7c705 100644 --- a/pkg/services/live/live_test.go +++ b/pkg/services/live/live_test.go @@ -36,7 +36,11 @@ func Test_provideLiveService_RedisUnavailable(t *testing.T) { nil, &usagestats.UsageStatsMock{T: t}, nil, - featuremgmt.WithFeatures(), acimpl.ProvideAccessControl(featuremgmt.WithFeatures()), &dashboards.FakeDashboardService{}, annotationstest.NewFakeAnnotationsRepo(), nil) + featuremgmt.WithFeatures(), + acimpl.ProvideAccessControl(featuremgmt.WithFeatures()), + &dashboards.FakeDashboardService{}, + annotationstest.NewFakeAnnotationsRepo(), + nil, nil) // Proceeds without live HA if redis is unavaialble require.NoError(t, err) diff --git a/pkg/services/login/authinfoimpl/store.go b/pkg/services/login/authinfoimpl/store.go index b5a90fc2a8a..84c405bf276 100644 --- a/pkg/services/login/authinfoimpl/store.go +++ b/pkg/services/login/authinfoimpl/store.go @@ -107,10 +107,11 @@ func (s *Store) GetUserLabels(ctx context.Context, query login.GetUserLabelsQuer func (s *Store) SetAuthInfo(ctx context.Context, cmd *login.SetAuthInfoCommand) error { authUser := &login.UserAuth{ - UserId: cmd.UserId, - AuthModule: cmd.AuthModule, - AuthId: cmd.AuthId, - Created: GetTime(), + UserId: cmd.UserId, + AuthModule: cmd.AuthModule, + AuthId: cmd.AuthId, + ExternalUID: cmd.ExternalUID, + Created: GetTime(), } if cmd.OAuthToken != nil { diff --git a/pkg/services/login/model.go b/pkg/services/login/model.go index 3d755a80301..979a89d6fb2 100644 --- a/pkg/services/login/model.go +++ b/pkg/services/login/model.go @@ -23,6 +23,7 @@ type UserAuth struct { OAuthIdToken string OAuthTokenType string OAuthExpiry time.Time + ExternalUID string `xorm:"external_uid"` } type ExternalUserInfo struct { @@ -72,10 +73,11 @@ type RequestURIKey struct{} // COMMANDS type SetAuthInfoCommand struct { - AuthModule string - AuthId string - UserId int64 - OAuthToken *oauth2.Token + AuthModule string + AuthId string + UserId int64 + OAuthToken *oauth2.Token + ExternalUID string } type UpdateAuthInfoCommand struct { diff --git a/pkg/services/ngalert/api/api_convert_prometheus.go b/pkg/services/ngalert/api/api_convert_prometheus.go index d9f28509622..5cbe398b459 100644 --- a/pkg/services/ngalert/api/api_convert_prometheus.go +++ b/pkg/services/ngalert/api/api_convert_prometheus.go @@ -463,6 +463,7 @@ func (srv *ConvertPrometheusSrv) convertToGrafanaRuleGroup( IsPaused: pauseAlertRules, }, KeepOriginalRuleDefinition: util.Pointer(keepOriginalRuleDefinition), + EvaluationOffset: &srv.cfg.PrometheusConversion.RuleQueryOffset, }, ) if err != nil { diff --git a/pkg/services/ngalert/api/tooling/api.json b/pkg/services/ngalert/api/tooling/api.json index 49ab21484b0..12bee7c6447 100644 --- a/pkg/services/ngalert/api/tooling/api.json +++ b/pkg/services/ngalert/api/tooling/api.json @@ -208,6 +208,9 @@ "isPaused": { "type": "boolean" }, + "keepFiringFor": { + "$ref": "#/definitions/Duration" + }, "labels": { "additionalProperties": { "type": "string" @@ -468,6 +471,10 @@ "health": { "type": "string" }, + "keepFiringFor": { + "format": "double", + "type": "number" + }, "labels": { "$ref": "#/definitions/Labels" }, @@ -3012,9 +3019,22 @@ "Interval": { "$ref": "#/definitions/Duration" }, + "Labels": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "Limit": { + "format": "int64", + "type": "integer" + }, "Name": { "type": "string" }, + "QueryOffset": { + "type": "string" + }, "Rules": { "items": { "$ref": "#/definitions/PrometheusRule" @@ -3117,6 +3137,10 @@ "example": false, "type": "boolean" }, + "keep_firing_for": { + "format": "duration", + "type": "string" + }, "labels": { "additionalProperties": { "type": "string" @@ -4952,7 +4976,6 @@ "type": "object" }, "gettableAlerts": { - "description": "GettableAlerts gettable alerts", "items": { "$ref": "#/definitions/gettableAlert", "type": "object" diff --git a/pkg/services/ngalert/api/tooling/definitions/convert_prometheus_api.go b/pkg/services/ngalert/api/tooling/definitions/convert_prometheus_api.go index f0202486e46..6e9f0f2bd4c 100644 --- a/pkg/services/ngalert/api/tooling/definitions/convert_prometheus_api.go +++ b/pkg/services/ngalert/api/tooling/definitions/convert_prometheus_api.go @@ -192,9 +192,12 @@ type PrometheusNamespace struct { // swagger:model type PrometheusRuleGroup struct { - Name string `yaml:"name"` - Interval model.Duration `yaml:"interval"` - Rules []PrometheusRule `yaml:"rules"` + Name string `yaml:"name"` + Interval model.Duration `yaml:"interval"` + QueryOffset *model.Duration `yaml:"query_offset,omitempty"` + Limit int `yaml:"limit,omitempty"` + Rules []PrometheusRule `yaml:"rules"` + Labels map[string]string `yaml:"labels,omitempty"` } // swagger:model diff --git a/pkg/services/ngalert/api/tooling/post.json b/pkg/services/ngalert/api/tooling/post.json index 3c9cc03dbec..5968f49fdbc 100644 --- a/pkg/services/ngalert/api/tooling/post.json +++ b/pkg/services/ngalert/api/tooling/post.json @@ -208,6 +208,9 @@ "isPaused": { "type": "boolean" }, + "keepFiringFor": { + "$ref": "#/definitions/Duration" + }, "labels": { "additionalProperties": { "type": "string" @@ -468,6 +471,10 @@ "health": { "type": "string" }, + "keepFiringFor": { + "format": "double", + "type": "number" + }, "labels": { "$ref": "#/definitions/Labels" }, @@ -3012,9 +3019,22 @@ "Interval": { "$ref": "#/definitions/Duration" }, + "Labels": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "Limit": { + "format": "int64", + "type": "integer" + }, "Name": { "type": "string" }, + "QueryOffset": { + "type": "string" + }, "Rules": { "items": { "$ref": "#/definitions/PrometheusRule" @@ -3117,6 +3137,10 @@ "example": false, "type": "boolean" }, + "keep_firing_for": { + "format": "duration", + "type": "string" + }, "labels": { "additionalProperties": { "type": "string" diff --git a/pkg/services/ngalert/api/tooling/spec.json b/pkg/services/ngalert/api/tooling/spec.json index 15d8a288303..706a40d67fd 100644 --- a/pkg/services/ngalert/api/tooling/spec.json +++ b/pkg/services/ngalert/api/tooling/spec.json @@ -4433,6 +4433,9 @@ "isPaused": { "type": "boolean" }, + "keepFiringFor": { + "$ref": "#/definitions/Duration" + }, "labels": { "type": "object", "additionalProperties": { @@ -4703,6 +4706,10 @@ "health": { "type": "string" }, + "keepFiringFor": { + "type": "number", + "format": "double" + }, "labels": { "$ref": "#/definitions/Labels" }, @@ -7238,9 +7245,22 @@ "Interval": { "$ref": "#/definitions/Duration" }, + "Labels": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "Limit": { + "type": "integer", + "format": "int64" + }, "Name": { "type": "string" }, + "QueryOffset": { + "type": "string" + }, "Rules": { "type": "array", "items": { @@ -7354,6 +7374,10 @@ "type": "boolean", "example": false }, + "keep_firing_for": { + "type": "string", + "format": "duration" + }, "labels": { "type": "object", "additionalProperties": { diff --git a/pkg/services/ngalert/prom/convert.go b/pkg/services/ngalert/prom/convert.go index c86126bfb90..b433a2ec76b 100644 --- a/pkg/services/ngalert/prom/convert.go +++ b/pkg/services/ngalert/prom/convert.go @@ -199,7 +199,7 @@ func (p *Converter) convertRule(orgID int64, namespaceUID string, promGroup Prom var err error isRecordingRule := rule.Record != "" - query, err = p.createQuery(rule.Expr, isRecordingRule) + query, err = p.createQuery(rule.Expr, isRecordingRule, promGroup) if err != nil { return models.AlertRule{}, err } @@ -241,6 +241,12 @@ func (p *Converter) convertRule(orgID int64, namespaceUID string, promGroup Prom RuleGroup: promGroup.Name, IsPaused: isPaused, Record: record, + + // MissingSeriesEvalsToResolve is set to 1 to match the Prometheus behaviour. + // Prometheus resolves alerts as soon as the series disappears. + // By setting this value to 1 we ensure that the alert is resolved on the first evaluation + // that doesn't have the series. + MissingSeriesEvalsToResolve: util.Pointer(1), } if p.cfg.KeepOriginalRuleDefinition != nil && *p.cfg.KeepOriginalRuleDefinition { @@ -265,8 +271,16 @@ func (p *Converter) convertRule(orgID int64, namespaceUID string, promGroup Prom // // This is needed to ensure that we keep the Prometheus behaviour, where any returned result // is considered alerting, and only when the query returns no data is the alert treated as normal. -func (p *Converter) createQuery(expr string, isRecordingRule bool) ([]models.AlertQuery, error) { - queryNode, err := createQueryNode(p.cfg.DatasourceUID, p.cfg.DatasourceType, expr, *p.cfg.FromTimeRange, *p.cfg.EvaluationOffset) +func (p *Converter) createQuery(expr string, isRecordingRule bool, promGroup PrometheusRuleGroup) ([]models.AlertQuery, error) { + // If evaluation offset is set on the group level, use that, otherwise use the global evaluation offset. + var evaluationOffset time.Duration + if promGroup.QueryOffset != nil { + evaluationOffset = time.Duration(*promGroup.QueryOffset) + } else { + evaluationOffset = *p.cfg.EvaluationOffset + } + + queryNode, err := createQueryNode(p.cfg.DatasourceUID, p.cfg.DatasourceType, expr, *p.cfg.FromTimeRange, evaluationOffset) if err != nil { return nil, err } diff --git a/pkg/services/ngalert/prom/convert_test.go b/pkg/services/ngalert/prom/convert_test.go index dc4017f477e..479417bc6b8 100644 --- a/pkg/services/ngalert/prom/convert_test.go +++ b/pkg/services/ngalert/prom/convert_test.go @@ -36,8 +36,9 @@ func TestPrometheusRulesToGrafana(t *testing.T) { orgID: 1, namespace: "some-namespace-uid", promGroup: PrometheusRuleGroup{ - Name: "test-group-1", - Interval: prommodel.Duration(10 * time.Second), + Name: "test-group-1", + Interval: prommodel.Duration(10 * time.Second), + QueryOffset: util.Pointer(prommodel.Duration(1 * time.Minute)), Rules: []PrometheusRule{ { Alert: "alert-1", @@ -124,16 +125,13 @@ func TestPrometheusRulesToGrafana(t *testing.T) { expectError: false, }, { - name: "rule group with query_offset is not supported", + name: "query_offset must be >= 0", orgID: 1, namespace: "namespaceUID", promGroup: PrometheusRuleGroup{ - Name: "test-group-1", - Interval: prommodel.Duration(10 * time.Second), - QueryOffset: func() *prommodel.Duration { - d := prommodel.Duration(30 * time.Second) - return &d - }(), + Name: "test-group-1", + Interval: prommodel.Duration(10 * time.Second), + QueryOffset: util.Pointer(prommodel.Duration(-1)), Rules: []PrometheusRule{ { Alert: "alert-1", @@ -142,7 +140,7 @@ func TestPrometheusRulesToGrafana(t *testing.T) { }, }, expectError: true, - errorMsg: "query_offset is not supported", + errorMsg: "query_offset must be >= 0", }, { name: "rule group with limit is not supported", @@ -179,6 +177,32 @@ func TestPrometheusRulesToGrafana(t *testing.T) { }, expectError: false, }, + { + name: "when global query offset is set, it should be used", + orgID: 1, + namespace: "some-namespace-uid", + promGroup: PrometheusRuleGroup{ + Name: "test-group-1", + Interval: prommodel.Duration(10 * time.Second), + Rules: []PrometheusRule{ + { + Alert: "alert-1", + Expr: "cpu_usage > 80", + For: util.Pointer(prommodel.Duration(5 * time.Minute)), + Labels: map[string]string{ + "severity": "critical", + }, + Annotations: map[string]string{ + "summary": "CPU usage is critical", + }, + }, + }, + }, + config: Config{ + EvaluationOffset: util.Pointer(5 * time.Minute), + }, + expectError: false, + }, } for _, tc := range testCases { @@ -244,8 +268,19 @@ func TestPrometheusRulesToGrafana(t *testing.T) { require.Equal(t, expectedLabels, grafanaRule.Labels, tc.name) require.Equal(t, promRule.Annotations, grafanaRule.Annotations, tc.name) - require.Equal(t, models.Duration(0*time.Minute), grafanaRule.Data[0].RelativeTimeRange.To) - require.Equal(t, models.Duration(10*time.Minute), grafanaRule.Data[0].RelativeTimeRange.From) + + evalOffset := time.Duration(0) + if tc.config.EvaluationOffset != nil { + evalOffset = *tc.config.EvaluationOffset + } + if tc.promGroup.QueryOffset != nil { + // group-level offset takes precedence + evalOffset = time.Duration(*tc.promGroup.QueryOffset) + } + + require.Equal(t, models.Duration(evalOffset), grafanaRule.Data[0].RelativeTimeRange.To) + require.Equal(t, models.Duration(10*time.Minute+evalOffset), grafanaRule.Data[0].RelativeTimeRange.From) + require.Equal(t, util.Pointer(1), grafanaRule.MissingSeriesEvalsToResolve) originalRuleDefinition, err := yaml.Marshal(promRule) require.NoError(t, err) diff --git a/pkg/services/ngalert/prom/models.go b/pkg/services/ngalert/prom/models.go index 21ea45061dc..0ef0a301885 100644 --- a/pkg/services/ngalert/prom/models.go +++ b/pkg/services/ngalert/prom/models.go @@ -25,14 +25,14 @@ type PrometheusRuleGroup struct { } func (g *PrometheusRuleGroup) Validate() error { - if g.QueryOffset != nil { - return ErrPrometheusRuleGroupValidationFailed.Errorf("query_offset is not supported") - } - if g.Limit != 0 { return ErrPrometheusRuleGroupValidationFailed.Errorf("limit is not supported") } + if g.QueryOffset != nil && *g.QueryOffset < prommodel.Duration(0) { + return ErrPrometheusRuleGroupValidationFailed.Errorf("query_offset must be >= 0") + } + for _, rule := range g.Rules { if err := rule.Validate(); err != nil { return err diff --git a/pkg/services/ngalert/prom/models_test.go b/pkg/services/ngalert/prom/models_test.go index b2bdb734b44..90be51c140c 100644 --- a/pkg/services/ngalert/prom/models_test.go +++ b/pkg/services/ngalert/prom/models_test.go @@ -26,6 +26,7 @@ func TestPrometheusRuleGroup_Validate(t *testing.T) { Labels: map[string]string{ "label-1": "value-1", }, + QueryOffset: util.Pointer(prommodel.Duration(time.Duration(1) * time.Second)), Rules: []PrometheusRule{ { Alert: "test_alert", @@ -36,14 +37,14 @@ func TestPrometheusRuleGroup_Validate(t *testing.T) { expectError: false, }, { - name: "invalid group with query_offset", + name: "invalid group with negative query_offset", group: PrometheusRuleGroup{ Name: "test_group", Interval: prommodel.Duration(60), - QueryOffset: util.Pointer(prommodel.Duration(10)), + QueryOffset: util.Pointer(prommodel.Duration(-1)), }, expectError: true, - errorMsg: "query_offset is not supported", + errorMsg: "query_offset must be >= 0", }, { name: "invalid group with limit", diff --git a/pkg/services/ngalert/prom/query.go b/pkg/services/ngalert/prom/query.go index 74aed34f83f..8e28b0ac3e7 100644 --- a/pkg/services/ngalert/prom/query.go +++ b/pkg/services/ngalert/prom/query.go @@ -43,7 +43,7 @@ func createQueryNode(datasourceUID, datasourceType, expr string, fromTimeRange, RefID: queryRefID, RelativeTimeRange: models.RelativeTimeRange{ From: models.Duration(fromTimeRange + evaluationOffset), - To: models.Duration(0 + evaluationOffset), + To: models.Duration(evaluationOffset), }, }, nil } diff --git a/pkg/services/ngalert/testutil/testutil.go b/pkg/services/ngalert/testutil/testutil.go index 0e13a46834d..daa61da93d3 100644 --- a/pkg/services/ngalert/testutil/testutil.go +++ b/pkg/services/ngalert/testutil/testutil.go @@ -8,6 +8,8 @@ import ( "github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/infra/db" + "github.com/grafana/grafana/pkg/infra/kvstore" + "github.com/grafana/grafana/pkg/infra/serverlock" "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/services/accesscontrol" acmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock" @@ -67,6 +69,8 @@ func SetupDashboardService(tb testing.TB, sqlStore db.DB, fs *folderimpl.Dashboa foldertest.NewFakeService(), folder.NewFakeStore(), nil, client.MockTestRestConfig{}, nil, quotaService, nil, nil, nil, dualwrite.ProvideTestService(), sort.ProvideService(), + serverlock.ProvideService(sqlStore, tracing.InitializeTracerForTest()), + kvstore.NewFakeKVStore(), ) require.NoError(tb, err) dashboardService.RegisterDashboardPermissions(dashboardPermissions) diff --git a/pkg/services/publicdashboards/api/query_test.go b/pkg/services/publicdashboards/api/query_test.go index 8c27d18b0ea..ddbf2925d4b 100644 --- a/pkg/services/publicdashboards/api/query_test.go +++ b/pkg/services/publicdashboards/api/query_test.go @@ -21,8 +21,11 @@ import ( "github.com/grafana/grafana/pkg/apimachinery/errutil" "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/infra/db" + "github.com/grafana/grafana/pkg/infra/kvstore" "github.com/grafana/grafana/pkg/infra/localcache" "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/infra/serverlock" + "github.com/grafana/grafana/pkg/infra/tracing" acmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock" "github.com/grafana/grafana/pkg/services/annotations/annotationstest" "github.com/grafana/grafana/pkg/services/apiserver/client" @@ -330,6 +333,8 @@ func TestIntegrationUnauthenticatedUserCanGetPubdashPanelQueryData(t *testing.T) featuremgmt.WithFeatures(), acmock.NewMockedPermissionsService(), ac, foldertest.NewFakeService(), folder.NewFakeStore(), nil, client.MockTestRestConfig{}, nil, quotatest.New(false, nil), nil, nil, nil, dualwrite.ProvideTestService(), sort.ProvideService(), + serverlock.ProvideService(db, tracing.InitializeTracerForTest()), + kvstore.NewFakeKVStore(), ) require.NoError(t, err) dashService.RegisterDashboardPermissions(dashPermissionService) diff --git a/pkg/services/publicdashboards/service/service_test.go b/pkg/services/publicdashboards/service/service_test.go index 1ae4797a45f..e8ea78749a6 100644 --- a/pkg/services/publicdashboards/service/service_test.go +++ b/pkg/services/publicdashboards/service/service_test.go @@ -19,6 +19,8 @@ import ( "github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/infra/db" + "github.com/grafana/grafana/pkg/infra/kvstore" + "github.com/grafana/grafana/pkg/infra/serverlock" "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/accesscontrol/actest" @@ -29,7 +31,6 @@ import ( dashsvc "github.com/grafana/grafana/pkg/services/dashboards/service" "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/folder/folderimpl" - "github.com/grafana/grafana/pkg/services/guardian" "github.com/grafana/grafana/pkg/services/org" . "github.com/grafana/grafana/pkg/services/publicdashboards" . "github.com/grafana/grafana/pkg/services/publicdashboards/models" @@ -1392,7 +1393,7 @@ func TestPublicDashboardServiceImpl_ListPublicDashboards(t *testing.T) { testDB, cfg := db.InitTestDBWithCfg(t) dashStore, err := dashboardsDB.ProvideDashboardStore(testDB, cfg, features, tagimpl.ProvideService(testDB)) require.NoError(t, err) - ac := acmock.New() + ac := actest.FakeAccessControl{ExpectedEvaluate: true} fStore := folderimpl.ProvideStore(testDB) folderPermissions := acmock.NewMockedPermissionsService() @@ -1401,15 +1402,11 @@ func TestPublicDashboardServiceImpl_ListPublicDashboards(t *testing.T) { fStore, ac, bus.ProvideBus(tracing.InitializeTracerForTest()), dashStore, folderStore, nil, testDB, features, supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil, dualwrite.ProvideTestService(), sort.ProvideService()) - dashboardService, err := dashsvc.ProvideDashboardServiceImpl(cfg, dashStore, folderStore, featuremgmt.WithFeatures(), folderPermissions, ac, folderSvc, fStore, nil, client.MockTestRestConfig{}, nil, quotatest.New(false, nil), nil, nil, nil, dualwrite.ProvideTestService(), sort.ProvideService()) + dashboardService, err := dashsvc.ProvideDashboardServiceImpl(cfg, dashStore, folderStore, featuremgmt.WithFeatures(), folderPermissions, ac, folderSvc, fStore, nil, client.MockTestRestConfig{}, nil, quotatest.New(false, nil), nil, nil, nil, dualwrite.ProvideTestService(), sort.ProvideService(), + serverlock.ProvideService(testDB, tracing.InitializeTracerForTest()), + kvstore.NewFakeKVStore()) require.NoError(t, err) dashboardService.RegisterDashboardPermissions(&actest.FakePermissionsService{}) - fakeGuardian := &guardian.FakeDashboardGuardian{ - CanSaveValue: true, - CanEditUIDs: []string{}, - CanViewUIDs: []string{}, - } - guardian.MockDashboardGuardian(fakeGuardian) // insert in test data so we can check that permissions are working properly through the dashboard service // this will create 4 dashboards and 3 users diff --git a/pkg/services/quota/quotaimpl/quota_test.go b/pkg/services/quota/quotaimpl/quota_test.go index 6100f1f0e6a..c8cc6d5dfde 100644 --- a/pkg/services/quota/quotaimpl/quota_test.go +++ b/pkg/services/quota/quotaimpl/quota_test.go @@ -12,7 +12,9 @@ import ( "github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/infra/db" "github.com/grafana/grafana/pkg/infra/httpclient" + "github.com/grafana/grafana/pkg/infra/kvstore" "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/infra/serverlock" "github.com/grafana/grafana/pkg/infra/tracing" pluginfakes "github.com/grafana/grafana/pkg/plugins/manager/fakes" "github.com/grafana/grafana/pkg/services/accesscontrol/acimpl" @@ -499,7 +501,9 @@ func setupEnv(t *testing.T, sqlStore db.DB, cfg *setting.Cfg, b bus.Bus, quotaSe fStore, acmock.New(), bus.ProvideBus(tracing.InitializeTracerForTest()), dashStore, folderStore, nil, sqlStore, featuremgmt.WithFeatures(), supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil, dualwrite.ProvideTestService(), sort.ProvideService()) dashService, err := dashService.ProvideDashboardServiceImpl(cfg, dashStore, folderStore, featuremgmt.WithFeatures(), acmock.NewMockedPermissionsService(), - ac, folderSvc, fStore, nil, client.MockTestRestConfig{}, nil, quotaService, nil, nil, nil, dualwrite.ProvideTestService(), sort.ProvideService()) + ac, folderSvc, fStore, nil, client.MockTestRestConfig{}, nil, quotaService, nil, nil, nil, dualwrite.ProvideTestService(), sort.ProvideService(), + serverlock.ProvideService(sqlStore, tracing.InitializeTracerForTest()), + kvstore.NewFakeKVStore()) require.NoError(t, err) dashService.RegisterDashboardPermissions(acmock.NewMockedPermissionsService()) secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore()) diff --git a/pkg/services/sqlstore/migrations/dashboard_mig.go b/pkg/services/sqlstore/migrations/dashboard_mig.go index 002b1e2e8a7..c6943ffb07d 100644 --- a/pkg/services/sqlstore/migrations/dashboard_mig.go +++ b/pkg/services/sqlstore/migrations/dashboard_mig.go @@ -261,6 +261,11 @@ func addDashboardMigration(mg *Migrator) { mg.AddMigration("Add apiVersion for dashboard", NewAddColumnMigration(dashboardV2, &Column{ Name: "api_version", Type: DB_Varchar, Length: 16, Nullable: true, })) + + mg.AddMigration("Add index for dashboard_uid on dashboard_tag table", NewAddIndexMigration(dashboardTagV1, &Index{ + Cols: []string{"dashboard_uid"}, + Type: IndexType, + })) } type FillDashbordUIDAndOrgIDMigration struct { @@ -278,23 +283,23 @@ func (m *FillDashbordUIDAndOrgIDMigration) Exec(sess *xorm.Session, mg *Migrator func RunDashboardTagMigrations(sess *xorm.Session, driverName string) error { // sqlite sql := `UPDATE dashboard_tag - SET + SET dashboard_uid = (SELECT uid FROM dashboard WHERE dashboard.id = dashboard_tag.dashboard_id), org_id = (SELECT org_id FROM dashboard WHERE dashboard.id = dashboard_tag.dashboard_id) - WHERE + WHERE (dashboard_uid IS NULL OR org_id IS NULL) AND EXISTS (SELECT 1 FROM dashboard WHERE dashboard.id = dashboard_tag.dashboard_id);` if driverName == Postgres { - sql = `UPDATE dashboard_tag - SET dashboard_uid = dashboard.uid, + sql = `UPDATE dashboard_tag + SET dashboard_uid = dashboard.uid, org_id = dashboard.org_id - FROM dashboard + FROM dashboard WHERE dashboard_tag.dashboard_id = dashboard.id AND (dashboard_tag.dashboard_uid IS NULL OR dashboard_tag.org_id IS NULL);` } else if driverName == MySQL { - sql = `UPDATE dashboard_tag - LEFT JOIN dashboard ON dashboard_tag.dashboard_id = dashboard.id - SET dashboard_tag.dashboard_uid = dashboard.uid, + sql = `UPDATE dashboard_tag + LEFT JOIN dashboard ON dashboard_tag.dashboard_id = dashboard.id + SET dashboard_tag.dashboard_uid = dashboard.uid, dashboard_tag.org_id = dashboard.org_id WHERE dashboard_tag.dashboard_uid IS NULL OR dashboard_tag.org_id IS NULL;` } diff --git a/pkg/services/sqlstore/migrations/ualert/alert_rule_version_guid_mig.go b/pkg/services/sqlstore/migrations/ualert/alert_rule_version_guid_mig.go index 3713e4eaa0a..ae1c1945e36 100644 --- a/pkg/services/sqlstore/migrations/ualert/alert_rule_version_guid_mig.go +++ b/pkg/services/sqlstore/migrations/ualert/alert_rule_version_guid_mig.go @@ -2,6 +2,8 @@ package ualert import ( "fmt" + "os" + "strconv" "strings" "github.com/google/uuid" @@ -29,10 +31,13 @@ func AddAlertRuleGuidMigration(mg *migrator.Migrator) { Nullable: false, Default: "''", })) - mg.AddMigration("drop index in alert_rule_version table on rule_org_id, rule_uid and version columns", migrator.NewDropIndexMigration(alertRuleVersion, alertRuleVersionUDX_OrgIdRuleUIDVersion)) + + mg.AddMigration("cleanup alert_rule_version table", &cleanUpRuleVersionsMigration{}) mg.AddMigration("populate rule guid in alert rule table", &setRuleGuidMigration{}) + mg.AddMigration("drop index in alert_rule_version table on rule_org_id, rule_uid and version columns", migrator.NewDropIndexMigration(alertRuleVersion, alertRuleVersionUDX_OrgIdRuleUIDVersion)) + mg.AddMigration("add index in alert_rule_version table on rule_org_id, rule_uid, rule_guid and version columns", migrator.NewAddIndexMigration(alertRuleVersion, &migrator.Index{Cols: []string{"rule_org_id", "rule_uid", "rule_guid", "version"}, Type: migrator.UniqueIndex}, @@ -118,3 +123,80 @@ func (c setRuleGuidMigration) Exec(sess *xorm.Session, mg *migrator.Migrator) er } return nil } + +type cleanUpRuleVersionsMigration struct { + migrator.MigrationBase +} + +var _ migrator.CodeMigration = (*cleanUpRuleVersionsMigration)(nil) + +func (c cleanUpRuleVersionsMigration) SQL(migrator.Dialect) string { + return codeMigration +} + +func getBatchSize() int { + const defaultBatchSize = 50 + envvar := os.Getenv("ALERT_RULE_VERSION_CLEANUP_MIGRATION_BATCH_SIZE") + if envvar == "" { + return defaultBatchSize + } + batchSize, err := strconv.Atoi(envvar) + if err != nil { + return defaultBatchSize + } + return batchSize +} + +func (c cleanUpRuleVersionsMigration) Exec(sess *xorm.Session, mg *migrator.Migrator) error { + var batchSize = getBatchSize() + + const maxRetention = 100 + toKeep := mg.Cfg.UnifiedAlerting.RuleVersionRecordLimit + if toKeep <= 0 { + mg.Logger.Info("Rule version record limit is not set, fallback to 100", "limit", toKeep) + toKeep = maxRetention + } + + var rules []alertRule + err := sess.Table(alertRule{}).Select("uid, version").Where("version > ?", toKeep).Find(&rules) + if err != nil { + return err + } + mg.Logger.Debug("Got alert rule UIDs with versions greater than retention", "count", len(rules)) + batches := len(rules) / batchSize + if len(rules)%batchSize != 0 { + batches++ + } + + mg.Logger.Info("Cleaning up table `alert_rule_version`", "batchSize", batchSize, "batches", batches, "keepVersions", toKeep) + + for i := 0; i < batches; i++ { + end := i*batchSize + batchSize + if end > len(rules) { + end = len(rules) + } + bd := strings.Builder{} + for idx, r := range rules[i*batchSize : end] { + if idx == 0 { + bd.WriteString(fmt.Sprintf("SELECT '%s' as uid, %d as version", r.UID, r.Version)) + continue + } + bd.WriteString(fmt.Sprintf(" UNION ALL SELECT '%s', %d ", r.UID, r.Version)) + } + _, err = sess.Exec(fmt.Sprintf(` + DELETE FROM alert_rule_version + WHERE EXISTS ( + SELECT 1 + FROM (%s) AR + WHERE AR.uid = alert_rule_version.rule_uid + AND alert_rule_version.version < AR.version - %d + )`, bd.String(), toKeep), + ) + if err != nil { + return err + } + + mg.Logger.Debug(fmt.Sprintf("Batch %d of %d processed", i+1, batches)) + } + return nil +} diff --git a/pkg/services/sqlstore/migrations/user_auth_mig.go b/pkg/services/sqlstore/migrations/user_auth_mig.go index bf3b98955b7..9546d84a766 100644 --- a/pkg/services/sqlstore/migrations/user_auth_mig.go +++ b/pkg/services/sqlstore/migrations/user_auth_mig.go @@ -46,4 +46,8 @@ func addUserAuthMigrations(mg *Migrator) { mg.AddMigration("Add OAuth ID token to user_auth", NewAddColumnMigration(userAuthV1, &Column{ Name: "o_auth_id_token", Type: DB_Text, Nullable: true, })) + + mg.AddMigration("Add user_unique_id to user_auth", NewAddColumnMigration(userAuthV1, &Column{ + Name: "external_uid", Type: DB_Text, Nullable: true, + })) } diff --git a/pkg/services/sqlstore/migrator/spanner_dialect.go b/pkg/services/sqlstore/migrator/spanner_dialect.go index c66032367ff..950aabf2f2a 100644 --- a/pkg/services/sqlstore/migrator/spanner_dialect.go +++ b/pkg/services/sqlstore/migrator/spanner_dialect.go @@ -7,7 +7,6 @@ import ( "encoding/json" "errors" "fmt" - "strconv" "time" "cloud.google.com/go/spanner" @@ -15,12 +14,10 @@ import ( "github.com/googleapis/gax-go/v2" spannerdriver "github.com/googleapis/go-sql-spanner" "github.com/grafana/dskit/concurrency" - "google.golang.org/api/option" - "google.golang.org/grpc" "google.golang.org/grpc/codes" - "google.golang.org/grpc/credentials/insecure" "xorm.io/core" + spannerext "github.com/grafana/grafana/pkg/extensions/spanner" "xorm.io/xorm" _ "embed" @@ -294,7 +291,7 @@ func (s *SpannerDialect) executeDDLStatements(ctx context.Context, engine *xorm. return err } - opts := SpannerConnectorConfigToClientOptions(cfg) + opts := spannerext.SpannerConnectorConfigToClientOptions(cfg) databaseAdminClient, err := database.NewDatabaseAdminClient(ctx, opts...) if err != nil { @@ -319,28 +316,6 @@ func (s *SpannerDialect) executeDDLStatements(ctx context.Context, engine *xorm. return nil } -// SpannerConnectorConfigToClientOptions is adapted from https://github.com/googleapis/go-sql-spanner/blob/main/driver.go#L341-L477, from version 1.11.1. -func SpannerConnectorConfigToClientOptions(connectorConfig spannerdriver.ConnectorConfig) []option.ClientOption { - var opts []option.ClientOption - if connectorConfig.Host != "" { - opts = append(opts, option.WithEndpoint(connectorConfig.Host)) - } - if strval, ok := connectorConfig.Params["credentials"]; ok { - opts = append(opts, option.WithCredentialsFile(strval)) - } - if strval, ok := connectorConfig.Params["credentialsjson"]; ok { - opts = append(opts, option.WithCredentialsJSON([]byte(strval))) - } - if strval, ok := connectorConfig.Params["useplaintext"]; ok { - if val, err := strconv.ParseBool(strval); err == nil && val { - opts = append(opts, - option.WithGRPCDialOption(grpc.WithTransportCredentials(insecure.NewCredentials())), - option.WithoutAuthentication()) - } - } - return opts -} - func (s *SpannerDialect) UnionDistinct() string { return "UNION DISTINCT" } diff --git a/pkg/services/sqlstore/permissions/dashboard_test.go b/pkg/services/sqlstore/permissions/dashboard_test.go index 2f625298937..f26e923228f 100644 --- a/pkg/services/sqlstore/permissions/dashboard_test.go +++ b/pkg/services/sqlstore/permissions/dashboard_test.go @@ -760,9 +760,21 @@ func setupTest(t *testing.T, numFolders, numDashboards int, permissions []access }) } - _, err := sess.InsertMulti(&dashes) - if err != nil { - return err + // Insert dashboards in batches + batchSize := 500 + if db.IsTestDBSpanner() { + batchSize = 30 // spanner has a limit of 950 parameters per query + } + for i := 0; i < len(dashes); i += batchSize { + end := i + batchSize + if end > len(dashes) { + end = len(dashes) + } + + _, err := sess.InsertMulti(dashes[i:end]) + if err != nil { + return err + } } role := &accesscontrol.Role{ @@ -772,7 +784,7 @@ func setupTest(t *testing.T, numFolders, numDashboards int, permissions []access Updated: time.Now(), Created: time.Now(), } - _, err = sess.Insert(role) + _, err := sess.Insert(role) if err != nil { return err } @@ -794,10 +806,18 @@ func setupTest(t *testing.T, numFolders, numDashboards int, permissions []access permissions[i].Updated = time.Now() permissions[i].Kind, permissions[i].Attribute, permissions[i].Identifier = permissions[i].SplitScope() } + if len(permissions) > 0 { - _, err = sess.InsertMulti(&permissions) - if err != nil { - return err + for i := 0; i < len(permissions); i += batchSize { + end := i + batchSize + if end > len(permissions) { + end = len(permissions) + } + + _, err = sess.InsertMulti(permissions[i:end]) + if err != nil { + return err + } } } diff --git a/pkg/services/sqlstore/sqlstore.go b/pkg/services/sqlstore/sqlstore.go index a3029fffe77..10dcf1d0c14 100644 --- a/pkg/services/sqlstore/sqlstore.go +++ b/pkg/services/sqlstore/sqlstore.go @@ -628,6 +628,8 @@ func TestMain(m *testing.M) { if err := testSQLStore.dialect.TruncateDBTables(testSQLStore.GetEngine()); err != nil { return nil, err } + testSQLStore.engine.ResetSequenceGenerator() + if err := testSQLStore.Reset(); err != nil { return nil, err } diff --git a/pkg/services/sqlstore/sqlstore_testinfra.go b/pkg/services/sqlstore/sqlstore_testinfra.go index fa7e1c47b3d..0a5c4b5db4f 100644 --- a/pkg/services/sqlstore/sqlstore_testinfra.go +++ b/pkg/services/sqlstore/sqlstore_testinfra.go @@ -183,6 +183,7 @@ func NewTestStore(tb TestingTB, opts ...TestOption) *SQLStore { tb.Fatalf("failed to truncate DB tables after migrations: %v", err) panic("unreachable") } + testSQLStore.engine.ResetSequenceGenerator() } return store diff --git a/pkg/services/ssosettings/strategies/saml_strategy.go b/pkg/services/ssosettings/strategies/saml_strategy.go index 026fd5cef7f..a863b9f491e 100644 --- a/pkg/services/ssosettings/strategies/saml_strategy.go +++ b/pkg/services/ssosettings/strategies/saml_strategy.go @@ -32,43 +32,44 @@ func (s *SAMLStrategy) GetProviderConfig(_ context.Context, provider string) (ma func (s *SAMLStrategy) loadSAMLSettings() map[string]any { section := s.settingsProvider.Section("auth.saml") result := map[string]any{ - "enabled": section.KeyValue("enabled").MustBool(false), - "entity_id": section.KeyValue("entity_id").MustString(""), - "name": section.KeyValue("name").MustString("SAML"), - "single_logout": section.KeyValue("single_logout").MustBool(false), - "allow_sign_up": section.KeyValue("allow_sign_up").MustBool(false), - "auto_login": section.KeyValue("auto_login").MustBool(false), - "certificate": section.KeyValue("certificate").MustString(""), - "certificate_path": section.KeyValue("certificate_path").MustString(""), - "private_key": section.KeyValue("private_key").MustString(""), - "private_key_path": section.KeyValue("private_key_path").MustString(""), - "signature_algorithm": section.KeyValue("signature_algorithm").MustString(""), - "idp_metadata": section.KeyValue("idp_metadata").MustString(""), - "idp_metadata_path": section.KeyValue("idp_metadata_path").MustString(""), - "idp_metadata_url": section.KeyValue("idp_metadata_url").MustString(""), - "max_issue_delay": section.KeyValue("max_issue_delay").MustDuration(90 * time.Second), - "metadata_valid_duration": section.KeyValue("metadata_valid_duration").MustDuration(48 * time.Hour), - "allow_idp_initiated": section.KeyValue("allow_idp_initiated").MustBool(false), - "relay_state": section.KeyValue("relay_state").MustString(""), - "assertion_attribute_name": section.KeyValue("assertion_attribute_name").MustString(""), - "assertion_attribute_login": section.KeyValue("assertion_attribute_login").MustString(""), - "assertion_attribute_email": section.KeyValue("assertion_attribute_email").MustString(""), - "assertion_attribute_groups": section.KeyValue("assertion_attribute_groups").MustString(""), - "assertion_attribute_role": section.KeyValue("assertion_attribute_role").MustString(""), - "assertion_attribute_org": section.KeyValue("assertion_attribute_org").MustString(""), - "allowed_organizations": section.KeyValue("allowed_organizations").MustString(""), - "org_mapping": section.KeyValue("org_mapping").MustString(""), - "role_values_none": section.KeyValue("role_values_none").MustString(""), - "role_values_viewer": section.KeyValue("role_values_viewer").MustString(""), - "role_values_editor": section.KeyValue("role_values_editor").MustString(""), - "role_values_admin": section.KeyValue("role_values_admin").MustString(""), - "role_values_grafana_admin": section.KeyValue("role_values_grafana_admin").MustString(""), - "name_id_format": section.KeyValue("name_id_format").MustString(""), - "skip_org_role_sync": section.KeyValue("skip_org_role_sync").MustBool(false), - "client_id": section.KeyValue("client_id").MustString(""), - "client_secret": section.KeyValue("client_secret").MustString(""), - "token_url": section.KeyValue("token_url").MustString(""), - "force_use_graph_api": section.KeyValue("force_use_graph_api").MustBool(false), + "allow_idp_initiated": section.KeyValue("allow_idp_initiated").MustBool(false), + "allow_sign_up": section.KeyValue("allow_sign_up").MustBool(false), + "allowed_organizations": section.KeyValue("allowed_organizations").MustString(""), + "assertion_attribute_email": section.KeyValue("assertion_attribute_email").MustString(""), + "assertion_attribute_groups": section.KeyValue("assertion_attribute_groups").MustString(""), + "assertion_attribute_login": section.KeyValue("assertion_attribute_login").MustString(""), + "assertion_attribute_name": section.KeyValue("assertion_attribute_name").MustString(""), + "assertion_attribute_org": section.KeyValue("assertion_attribute_org").MustString(""), + "assertion_attribute_role": section.KeyValue("assertion_attribute_role").MustString(""), + "auto_login": section.KeyValue("auto_login").MustBool(false), + "certificate": section.KeyValue("certificate").MustString(""), + "certificate_path": section.KeyValue("certificate_path").MustString(""), + "client_id": section.KeyValue("client_id").MustString(""), + "client_secret": section.KeyValue("client_secret").MustString(""), + "enabled": section.KeyValue("enabled").MustBool(false), + "entity_id": section.KeyValue("entity_id").MustString(""), + "external_uid_assertion_name": section.KeyValue("external_uid_assertion_name").MustString(""), + "force_use_graph_api": section.KeyValue("force_use_graph_api").MustBool(false), + "idp_metadata": section.KeyValue("idp_metadata").MustString(""), + "idp_metadata_path": section.KeyValue("idp_metadata_path").MustString(""), + "idp_metadata_url": section.KeyValue("idp_metadata_url").MustString(""), + "max_issue_delay": section.KeyValue("max_issue_delay").MustDuration(90 * time.Second), + "metadata_valid_duration": section.KeyValue("metadata_valid_duration").MustDuration(48 * time.Hour), + "name": section.KeyValue("name").MustString("SAML"), + "name_id_format": section.KeyValue("name_id_format").MustString(""), + "org_mapping": section.KeyValue("org_mapping").MustString(""), + "private_key": section.KeyValue("private_key").MustString(""), + "private_key_path": section.KeyValue("private_key_path").MustString(""), + "relay_state": section.KeyValue("relay_state").MustString(""), + "role_values_admin": section.KeyValue("role_values_admin").MustString(""), + "role_values_editor": section.KeyValue("role_values_editor").MustString(""), + "role_values_grafana_admin": section.KeyValue("role_values_grafana_admin").MustString(""), + "role_values_none": section.KeyValue("role_values_none").MustString(""), + "role_values_viewer": section.KeyValue("role_values_viewer").MustString(""), + "signature_algorithm": section.KeyValue("signature_algorithm").MustString(""), + "single_logout": section.KeyValue("single_logout").MustBool(false), + "skip_org_role_sync": section.KeyValue("skip_org_role_sync").MustBool(false), + "token_url": section.KeyValue("token_url").MustString(""), } return result } diff --git a/pkg/services/ssosettings/strategies/saml_strategy_test.go b/pkg/services/ssosettings/strategies/saml_strategy_test.go index c04ef280318..507005a45b2 100644 --- a/pkg/services/ssosettings/strategies/saml_strategy_test.go +++ b/pkg/services/ssosettings/strategies/saml_strategy_test.go @@ -54,43 +54,44 @@ var ( ` expectedSAMLInfo = map[string]any{ - "enabled": true, - "entity_id": "custom-entity-id", - "single_logout": true, - "allow_sign_up": true, - "auto_login": true, - "name": "SAML Test", - "certificate": "devenv/docker/blocks/auth/saml-enterprise/cert.crt", - "certificate_path": "/path/to/cert", - "private_key": "dGhpcyBpcyBteSBwcml2YXRlIGtleSB0aGF0IEkgd2FudCB0byBnZXQgZW5jb2RlZCBpbiBiYXNlIDY0", - "private_key_path": "devenv/docker/blocks/auth/saml-enterprise/key.pem", - "signature_algorithm": "rsa-sha256", - "idp_metadata": "dGhpcyBpcyBteSBwcml2YXRlIGtleSB0aGF0IEkgd2FudCB0byBnZXQgZW5jb2RlZCBpbiBiYXNlIDY0", - "idp_metadata_path": "/path/to/metadata", - "idp_metadata_url": "http://localhost:8086/realms/grafana/protocol/saml/descriptor", - "max_issue_delay": 90 * time.Second, - "metadata_valid_duration": 48 * time.Hour, - "allow_idp_initiated": false, - "relay_state": "relay_state", - "assertion_attribute_name": "name", - "assertion_attribute_login": "login", - "assertion_attribute_email": "email", - "assertion_attribute_groups": "groups", - "assertion_attribute_role": "roles", - "assertion_attribute_org": "orgs", - "allowed_organizations": "org1 org2", - "org_mapping": "org1:1:editor, *:2:viewer", - "role_values_viewer": "viewer", - "role_values_editor": "editor", - "role_values_admin": "admin", - "role_values_grafana_admin": "serveradmin", - "name_id_format": "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress", - "skip_org_role_sync": false, - "role_values_none": "guest disabled", - "token_url": "http://localhost:8086/auth/realms/grafana/protocol/openid-connect/token", - "client_id": "grafana", - "client_secret": "grafana", - "force_use_graph_api": false, + "enabled": true, + "entity_id": "custom-entity-id", + "external_uid_assertion_name": "", + "single_logout": true, + "allow_sign_up": true, + "auto_login": true, + "name": "SAML Test", + "certificate": "devenv/docker/blocks/auth/saml-enterprise/cert.crt", + "certificate_path": "/path/to/cert", + "private_key": "dGhpcyBpcyBteSBwcml2YXRlIGtleSB0aGF0IEkgd2FudCB0byBnZXQgZW5jb2RlZCBpbiBiYXNlIDY0", + "private_key_path": "devenv/docker/blocks/auth/saml-enterprise/key.pem", + "signature_algorithm": "rsa-sha256", + "idp_metadata": "dGhpcyBpcyBteSBwcml2YXRlIGtleSB0aGF0IEkgd2FudCB0byBnZXQgZW5jb2RlZCBpbiBiYXNlIDY0", + "idp_metadata_path": "/path/to/metadata", + "idp_metadata_url": "http://localhost:8086/realms/grafana/protocol/saml/descriptor", + "max_issue_delay": 90 * time.Second, + "metadata_valid_duration": 48 * time.Hour, + "allow_idp_initiated": false, + "relay_state": "relay_state", + "assertion_attribute_name": "name", + "assertion_attribute_login": "login", + "assertion_attribute_email": "email", + "assertion_attribute_groups": "groups", + "assertion_attribute_role": "roles", + "assertion_attribute_org": "orgs", + "allowed_organizations": "org1 org2", + "org_mapping": "org1:1:editor, *:2:viewer", + "role_values_viewer": "viewer", + "role_values_editor": "editor", + "role_values_admin": "admin", + "role_values_grafana_admin": "serveradmin", + "name_id_format": "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress", + "skip_org_role_sync": false, + "role_values_none": "guest disabled", + "token_url": "http://localhost:8086/auth/realms/grafana/protocol/openid-connect/token", + "client_id": "grafana", + "client_secret": "grafana", + "force_use_graph_api": false, } ) diff --git a/pkg/services/user/usertest/fake.go b/pkg/services/user/usertest/fake.go index 09a456d81d5..d0552d16d0f 100644 --- a/pkg/services/user/usertest/fake.go +++ b/pkg/services/user/usertest/fake.go @@ -20,6 +20,7 @@ type FakeUserService struct { UpdateFn func(ctx context.Context, cmd *user.UpdateUserCommand) error GetSignedInUserFn func(ctx context.Context, query *user.GetSignedInUserQuery) (*user.SignedInUser, error) CreateFn func(ctx context.Context, cmd *user.CreateUserCommand) (*user.User, error) + GetByLoginFn func(ctx context.Context, query *user.GetUserByLoginQuery) (*user.User, error) BatchDisableUsersFn func(ctx context.Context, cmd *user.BatchDisableUsersCommand) error GetByEmailFn func(ctx context.Context, query *user.GetUserByEmailQuery) (*user.User, error) @@ -59,6 +60,9 @@ func (f *FakeUserService) GetByUID(ctx context.Context, query *user.GetUserByUID } func (f *FakeUserService) GetByLogin(ctx context.Context, query *user.GetUserByLoginQuery) (*user.User, error) { + if f.GetByLoginFn != nil { + return f.GetByLoginFn(ctx, query) + } return f.ExpectedUser, f.ExpectedError } diff --git a/pkg/setting/setting.go b/pkg/setting/setting.go index 815553d3813..59fe66d9175 100644 --- a/pkg/setting/setting.go +++ b/pkg/setting/setting.go @@ -181,6 +181,9 @@ type Cfg struct { DataProxyWhiteList map[string]bool ActionsAllowPostURL string + // K8s Dashboard Cleanup + K8sDashboardCleanup K8sDashboardCleanupSettings + TempDataLifetime time.Duration // Plugins @@ -1291,6 +1294,7 @@ func (cfg *Cfg) parseINIFile(iniFile *ini.File) error { cfg.readDataSourcesSettings() cfg.readDataSourceSecuritySettings() + cfg.readK8sDashboardCleanupSettings() cfg.readSqlDataSourceSettings() cfg.Storage = readStorageSettings(iniFile) diff --git a/pkg/setting/setting_jwt.go b/pkg/setting/setting_jwt.go index 2a559a145b7..18c7866cccf 100644 --- a/pkg/setting/setting_jwt.go +++ b/pkg/setting/setting_jwt.go @@ -1,6 +1,10 @@ package setting -import "time" +import ( + "time" + + "github.com/grafana/grafana/pkg/util" +) const ( extJWTAccessTokenExpectAudience = "grafana" @@ -22,6 +26,8 @@ type AuthJWTSettings struct { AutoSignUp bool RoleAttributePath string RoleAttributeStrict bool + OrgMapping []string + OrgAttributePath string AllowAssignGrafanaAdmin bool SkipOrgRoleSync bool GroupsAttributePath string @@ -71,6 +77,8 @@ func (cfg *Cfg) readAuthJWTSettings() { jwtSettings.EmailAttributePath = valueAsString(authJWT, "email_attribute_path", "") jwtSettings.UsernameAttributePath = valueAsString(authJWT, "username_attribute_path", "") jwtSettings.TlsSkipVerify = authJWT.Key("tls_skip_verify_insecure").MustBool(false) + jwtSettings.OrgAttributePath = valueAsString(authJWT, "org_attribute_path", "") + jwtSettings.OrgMapping = util.SplitString(valueAsString(authJWT, "org_mapping", "")) cfg.JWTAuth = jwtSettings } diff --git a/pkg/setting/setting_k8s_dashboard_cleanup.go b/pkg/setting/setting_k8s_dashboard_cleanup.go new file mode 100644 index 00000000000..660de43c388 --- /dev/null +++ b/pkg/setting/setting_k8s_dashboard_cleanup.go @@ -0,0 +1,53 @@ +package setting + +import ( + "time" +) + +type K8sDashboardCleanupSettings struct { + Interval time.Duration + Timeout time.Duration + BatchSize int64 +} + +const ( + defaultK8sDashboardCleanupInterval = 30 * time.Second + defaultK8sDashboardCleanupBatchSize = int64(10) + minK8sDashboardCleanupInterval = 10 * time.Second + minK8sDashboardCleanupTimeout = 5 * time.Second + minK8sDashboardCleanupBatchSize = int64(5) + maxK8sDashboardCleanupBatchSize = int64(200) +) + +func (cfg *Cfg) readK8sDashboardCleanupSettings() { + section := cfg.Raw.Section("dashboard_cleanup") + + // Read interval setting with validation + cleanupInterval := section.Key("interval").MustDuration(defaultK8sDashboardCleanupInterval) + if cleanupInterval < minK8sDashboardCleanupInterval { + cfg.Logger.Warn("[dashboard_cleanup.interval] is too low; the minimum allowed (10s) is enforced") + cleanupInterval = minK8sDashboardCleanupInterval + } + + // Calculate timeout as 5 seconds less than interval, with minimum validation + cleanupTimeout := cleanupInterval - (5 * time.Second) + if cleanupTimeout < minK8sDashboardCleanupTimeout { + cleanupTimeout = minK8sDashboardCleanupTimeout + } + + // Read batch size with validation + batchSize := section.Key("batch_size").MustInt64(defaultK8sDashboardCleanupBatchSize) + if batchSize < minK8sDashboardCleanupBatchSize { + cfg.Logger.Warn("[dashboard_cleanup.batch_size] is too low; the minimum allowed (5) is enforced") + batchSize = minK8sDashboardCleanupBatchSize + } else if batchSize > maxK8sDashboardCleanupBatchSize { + cfg.Logger.Warn("[dashboard_cleanup.batch_size] is too high; the maximum allowed (1000) is enforced") + batchSize = maxK8sDashboardCleanupBatchSize + } + + cfg.K8sDashboardCleanup = K8sDashboardCleanupSettings{ + Interval: cleanupInterval, + Timeout: cleanupTimeout, + BatchSize: batchSize, + } +} diff --git a/pkg/setting/setting_unified_alerting.go b/pkg/setting/setting_unified_alerting.go index b14d22b97bc..84461d3c82f 100644 --- a/pkg/setting/setting_unified_alerting.go +++ b/pkg/setting/setting_unified_alerting.go @@ -112,6 +112,7 @@ type UnifiedAlertingSettings struct { StateHistory UnifiedAlertingStateHistorySettings RemoteAlertmanager RemoteAlertmanagerSettings RecordingRules RecordingRuleSettings + PrometheusConversion UnifiedAlertingPrometheusConversionSettings // MaxStateSaveConcurrency controls the number of goroutines (per rule) that can save alert state in parallel. MaxStateSaveConcurrency int @@ -165,6 +166,12 @@ type UnifiedAlertingReservedLabelSettings struct { DisabledLabels map[string]struct{} } +// UnifiedAlertingPrometheusConversionSettings contains configuration for converting Prometheus rules to Grafana format +type UnifiedAlertingPrometheusConversionSettings struct { + // RuleQueryOffset defines a time offset to apply to rule queries during conversion from Prometheus to Grafana format + RuleQueryOffset time.Duration +} + type UnifiedAlertingStateHistorySettings struct { Enabled bool Backend string @@ -437,6 +444,11 @@ func (cfg *Cfg) ReadUnifiedAlertingSettings(iniFile *ini.File) error { } uaCfg.StateHistory = uaCfgStateHistory + prometheusConversion := iniFile.Section("unified_alerting.prometheus_conversion") + uaCfg.PrometheusConversion = UnifiedAlertingPrometheusConversionSettings{ + RuleQueryOffset: prometheusConversion.Key("rule_query_offset").MustDuration(time.Minute), + } + rr := iniFile.Section("recording_rules") uaCfgRecordingRules := RecordingRuleSettings{ Enabled: rr.Key("enabled").MustBool(false), diff --git a/pkg/storage/legacysql/dualwrite/dualwriter.go b/pkg/storage/legacysql/dualwrite/dualwriter.go index c34609cc9f5..7e3c955fa26 100644 --- a/pkg/storage/legacysql/dualwrite/dualwriter.go +++ b/pkg/storage/legacysql/dualwrite/dualwriter.go @@ -45,13 +45,12 @@ func (d *dualWriter) Get(ctx context.Context, name string, options *metav1.GetOp // Once we have successfully read from legacy, we can check if we want to fail on a unified read. // If we allow the unified read to fail, we can do it in the background. if d.errorIsOK { - go func() { - ctxBg, cancel := context.WithTimeout(context.Background(), backgroundReqTimeout) + go func(ctxBg context.Context, cancel context.CancelFunc) { defer cancel() if _, err := d.unified.Get(ctxBg, name, options); err != nil { d.log.Error("failed background GET to unified", "err", err) } - }() + }(context.WithTimeout(context.WithoutCancel(ctx), backgroundReqTimeout)) return legacyGet, nil } // If it's not okay to fail, we have to check it in the foreground. @@ -75,13 +74,12 @@ func (d *dualWriter) List(ctx context.Context, options *metainternalversion.List // Once we have successfully listed from legacy, we can check if we want to fail on a unified list. // If we allow the unified list to fail, we can do it in the background and return. if d.errorIsOK { - go func() { - ctxBg, cancel := context.WithTimeout(context.Background(), backgroundReqTimeout) + go func(ctxBg context.Context, cancel context.CancelFunc) { defer cancel() if _, err := d.unified.List(ctxBg, options); err != nil { d.log.Error("failed background LIST to unified", "err", err) } - }() + }(context.WithTimeout(context.WithoutCancel(ctx), backgroundReqTimeout)) return legacyList, nil } // If it's not okay to fail, we have to check it in the foreground. @@ -139,13 +137,12 @@ func (d *dualWriter) Create(ctx context.Context, in runtime.Object, createValida return storageObj, nil } else if d.errorIsOK { // If we don't use unified as the primary store and errors are okay, let's create it in the background. - go func() { - ctxBg, cancel := context.WithTimeout(context.Background(), backgroundReqTimeout) + go func(ctxBg context.Context, cancel context.CancelFunc) { defer cancel() if _, err := d.unified.Create(ctxBg, createdCopy, createValidation, options); err != nil { log.Error("unable to create object in unified storage", "err", err) } - }() + }(context.WithTimeout(context.WithoutCancel(ctx), backgroundReqTimeout)) } else { // Otherwise let's create it in the foreground and return any error. if _, err := d.unified.Create(ctx, createdCopy, createValidation, options); err != nil { @@ -185,14 +182,13 @@ func (d *dualWriter) Delete(ctx context.Context, name string, deleteValidation r return objFromStorage, asyncStorage, nil } else if d.errorIsOK { // If errors are okay and unified is not primary, we can just run it as background operation. - go func() { - ctxBg, cancel := context.WithTimeout(context.Background(), backgroundReqTimeout) + go func(ctxBg context.Context, cancel context.CancelFunc) { defer cancel() _, _, err := d.unified.Delete(ctxBg, name, deleteValidation, options) if err != nil && !apierrors.IsNotFound(err) && !d.errorIsOK { d.log.Error("failed background DELETE in unified storage", "err", err) } - }() + }(context.WithTimeout(context.WithoutCancel(ctx), backgroundReqTimeout)) } // Otherwise we just run it in the foreground and return an error if any might happen. _, _, err = d.unified.Delete(ctx, name, deleteValidation, options) @@ -226,13 +222,12 @@ func (d *dualWriter) Update(ctx context.Context, name string, objInfo rest.Updat return d.unified.Update(ctx, name, objInfo, createValidation, updateValidation, forceAllowCreate, options) } else if d.errorIsOK { // If unified is not primary, but errors are okay, we can just run in the background. - go func() { - ctxBg, cancel := context.WithTimeout(context.Background(), backgroundReqTimeout) + go func(ctxBg context.Context, cancel context.CancelFunc) { defer cancel() if _, _, err := d.unified.Update(ctxBg, name, objInfo, createValidation, updateValidation, forceAllowCreate, options); err != nil { log.Error("failed background UPDATE to unified storage", "err", err) } - }() + }(context.WithTimeout(context.WithoutCancel(ctx), backgroundReqTimeout)) return objFromLegacy, createdLegacy, nil } // If we want to check unified errors just run it in foreground. @@ -263,13 +258,12 @@ func (d *dualWriter) DeleteCollection(ctx context.Context, deleteValidation rest return d.unified.DeleteCollection(ctx, deleteValidation, options, listOptions) } else if d.errorIsOK { // If unified storage is not the primary store and errors are okay, we can just run it in the background. - go func() { - ctxBg, cancel := context.WithTimeout(context.Background(), backgroundReqTimeout) + go func(ctxBg context.Context, cancel context.CancelFunc) { defer cancel() if _, err := d.unified.DeleteCollection(ctxBg, deleteValidation, options, listOptions); err != nil { log.Error("failed background DELETE collection to unified storage", "err", err) } - }() + }(context.WithTimeout(context.WithoutCancel(ctx), backgroundReqTimeout)) return deletedLegacy, nil } // Otherwise we have to check the error and run it in the foreground. diff --git a/pkg/storage/unified/README.md b/pkg/storage/unified/README.md index f08da73b33a..8efde1c2c91 100644 --- a/pkg/storage/unified/README.md +++ b/pkg/storage/unified/README.md @@ -253,23 +253,24 @@ go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest - make changes in `.proto` file - to compile all protobuf files in the repository run `make protobuf` at its top level -## Setting up search (EXPERIMENTAL) -Unified storage now exposes an **experimental** search API. It can be used to search for specific resources, or to filter/query resources. +## Setting up search To enable it, add the following to your `custom.ini` under the `[feature_toggles]` section: ```ini [feature_toggles] +; Used by the Grafana instance +unifiedStorageSearchUI = true +kubernetesClientDashboardsFolders = true + +; Used by unified storage unifiedStorageSearch = true +; (optional) Allows you to sort dashboards by usage insights fields when using enterprise +; unifiedStorageSearchSprinkles = true +; (optional) Will skip search results filter based on user permissions +; unifiedStorageSearchPermissionFiltering = false ``` -To access the api through Grafana, go to Explore -> Query Type -> Search. +The dashboard search page has been set up to search unified storage. Additionally, all legacy search calls (e.g. `/api/search`) will go to +unified storage when the dual writer mode is set to 3 or greater. When <= 2, the legacy search api calls will go to legacy storage. -The query needs to be a valid [Bleve query string](https://blevesearch.com/docs/Query-String-Query/). - -Some example queries are: -- `*` - returns all objects -- `Kind:Playlist` - returns all playlists -- `Spec.inveral:5m` - returns all objects with the spec.inverval field set to 5m -- `+Kind:Playlist +Spec.title:p4` - returns all playlists with the title matching "p4" -- `*foo*` - returns all objects containing "foo" in any field -- `CreatedAt:>="2024-10-17"` - returns all objects created after 2024-10-17 -- `+CreatedAt:>="2024-10-17" +Kind:Playlist` - returns all playlists created after 2024-10-17 +## Running load tests +Load tests and instructions can be found [here](https://github.com/grafana/grafana-api-tests/tree/main/simulation/src/unified_storage). diff --git a/pkg/storage/unified/apistore/go.mod b/pkg/storage/unified/apistore/go.mod index 14dc81193b1..c6b1c75bfda 100644 --- a/pkg/storage/unified/apistore/go.mod +++ b/pkg/storage/unified/apistore/go.mod @@ -51,7 +51,7 @@ require ( github.com/Azure/go-autorest/autorest/to v0.4.0 // indirect github.com/Azure/go-ntlmssp v0.0.0-20220621081337-cb9428e4ac1e // indirect github.com/AzureAD/microsoft-authentication-library-for-go v1.3.2 // indirect - github.com/BurntSushi/toml v1.4.0 // indirect + github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c // indirect github.com/GoogleCloudPlatform/grpc-gcp-go/grpcgcp v1.5.2 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.49.0 // indirect @@ -149,7 +149,7 @@ require ( github.com/emirpasic/gods v1.18.1 // indirect github.com/envoyproxy/go-control-plane/envoy v1.32.3 // indirect github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect - github.com/fatih/color v1.17.0 // indirect + github.com/fatih/color v1.18.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsnotify v1.8.0 // indirect github.com/fullstorydev/grpchan v1.1.1 // indirect diff --git a/pkg/storage/unified/apistore/go.sum b/pkg/storage/unified/apistore/go.sum index a74e2346a1f..d2ab1f07f14 100644 --- a/pkg/storage/unified/apistore/go.sum +++ b/pkg/storage/unified/apistore/go.sum @@ -649,8 +649,9 @@ github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mo github.com/AzureAD/microsoft-authentication-library-for-go v1.3.2 h1:kYRSnvJju5gYVyhkij+RTJ/VR6QIUaCfWeaFm2ycsjQ= github.com/AzureAD/microsoft-authentication-library-for-go v1.3.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0= github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= +github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c h1:pxW6RcqyfI9/kWtOwnv/G+AzdKuy2ZrqINhenH4HyNs= +github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU= github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU= @@ -975,8 +976,8 @@ github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2T github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U= github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= -github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4= -github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI= +github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= +github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= diff --git a/pkg/storage/unified/apistore/prepare.go b/pkg/storage/unified/apistore/prepare.go index c9677fd0763..e6d9458ebfb 100644 --- a/pkg/storage/unified/apistore/prepare.go +++ b/pkg/storage/unified/apistore/prepare.go @@ -9,13 +9,14 @@ import ( "time" "github.com/google/uuid" + apiequality "k8s.io/apimachinery/pkg/api/equality" + apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" "k8s.io/apiserver/pkg/storage" "k8s.io/klog/v2" authtypes "github.com/grafana/authlib/types" - "github.com/grafana/grafana/pkg/apimachinery/utils" "github.com/grafana/grafana/pkg/storage/unified/resource" ) @@ -57,6 +58,9 @@ func (s *Storage) prepareObjectForStorage(ctx context.Context, newObject runtime if obj.GetUID() == "" { obj.SetUID(types.UID(uuid.NewString())) } + if obj.GetFolder() != "" && !s.opts.EnableFolderSupport { + return nil, apierrors.NewBadRequest(fmt.Sprintf("folders are not supported for: %s", s.gr.String())) + } if s.opts.RequireDeprecatedInternalID { // nolint:staticcheck @@ -77,6 +81,7 @@ func (s *Storage) prepareObjectForStorage(ctx context.Context, newObject runtime obj.SetUpdatedBy("") obj.SetUpdatedTimestamp(nil) obj.SetCreatedBy(info.GetUID()) + obj.SetGeneration(1) // the first time we write var buf bytes.Buffer if err = s.codec.Encode(newObject, &buf); err != nil { @@ -131,8 +136,35 @@ func (s *Storage) prepareObjectForUpdate(ctx context.Context, updateObject runti obj.SetDeprecatedInternalID(previousInternalID) // nolint:staticcheck } - obj.SetUpdatedBy(info.GetUID()) - obj.SetUpdatedTimestampMillis(time.Now().UnixMilli()) + // Check if we should bump the generation + changed := obj.GetFolder() != previous.GetFolder() + if changed { + if !s.opts.EnableFolderSupport { + return nil, apierrors.NewBadRequest(fmt.Sprintf("folders are not supported for: %s", s.gr.String())) + } + // TODO: check that we can move the folder? + } else if obj.GetDeletionTimestamp() != nil && previous.GetDeletionTimestamp() == nil { + changed = true // bump generation when deleted + } else { + spec, e1 := obj.GetSpec() + oldSpec, e2 := previous.GetSpec() + if e1 == nil && e2 == nil { + if !apiequality.Semantic.DeepEqual(spec, oldSpec) { + changed = true + } + } + } + + // Mark the resource as changed + if changed { + obj.SetGeneration(previous.GetGeneration() + 1) + obj.SetUpdatedBy(info.GetUID()) + obj.SetUpdatedTimestampMillis(time.Now().UnixMilli()) + } else { + obj.SetGeneration(previous.GetGeneration()) + obj.SetAnnotation(utils.AnnoKeyUpdatedBy, previous.GetAnnotation(utils.AnnoKeyUpdatedBy)) + obj.SetAnnotation(utils.AnnoKeyUpdatedTimestamp, previous.GetAnnotation(utils.AnnoKeyUpdatedTimestamp)) + } var buf bytes.Buffer if err = s.codec.Encode(updateObject, &buf); err != nil { diff --git a/pkg/storage/unified/apistore/prepare_test.go b/pkg/storage/unified/apistore/prepare_test.go index 1bb9c06cd04..1c17a9806c2 100644 --- a/pkg/storage/unified/apistore/prepare_test.go +++ b/pkg/storage/unified/apistore/prepare_test.go @@ -9,6 +9,8 @@ import ( "github.com/stretchr/testify/require" "golang.org/x/exp/rand" "k8s.io/apimachinery/pkg/api/apitesting" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/serializer" "k8s.io/apiserver/pkg/storage" @@ -30,11 +32,14 @@ func TestPrepareObjectForStorage(t *testing.T) { codec: apitesting.TestCodec(codecs, v0alpha1.DashboardResourceInfo.GroupVersion()), snowflake: node, opts: StorageOptions{ - LargeObjectSupport: nil, + EnableFolderSupport: true, + LargeObjectSupport: nil, }, } - ctx := authtypes.WithAuthInfo(context.Background(), &identity.StaticRequester{UserID: 1, UserUID: "user-uid", Type: authtypes.TypeUser}) + ctx := authtypes.WithAuthInfo(context.Background(), + &identity.StaticRequester{UserID: 1, UserUID: "user-uid", Type: authtypes.TypeUser}, + ) t.Run("Error getting auth info from context", func(t *testing.T) { _, err := s.prepareObjectForStorage(context.Background(), nil) @@ -81,7 +86,7 @@ func TestPrepareObjectForStorage(t *testing.T) { require.Empty(t, updatedTS) }) - t.Run("Should keep repo info", func(t *testing.T) { + t.Run("Should keep manager info", func(t *testing.T) { dashboard := v0alpha1.Dashboard{} dashboard.Name = "test-name" obj := dashboard.DeepCopyObject() @@ -117,6 +122,65 @@ func TestPrepareObjectForStorage(t *testing.T) { require.Equal(t, s.TimestampMillis, now.UnixMilli()) }) + t.Run("Update should manage incrementing generation and metadata", func(t *testing.T) { + dashboard := v0alpha1.Dashboard{} + dashboard.Name = "test-name" + obj := dashboard.DeepCopyObject() + meta, err := utils.MetaAccessor(obj) + meta.SetFolder("aaa") + require.NoError(t, err) + + encodedData, err := s.prepareObjectForStorage(ctx, obj) + require.NoError(t, err) + + insertedObject, _, err := s.codec.Decode(encodedData, nil, &v0alpha1.Dashboard{}) + require.NoError(t, err) + meta, err = utils.MetaAccessor(insertedObject) + require.NoError(t, err) + require.Equal(t, int64(1), meta.GetGeneration()) + require.Equal(t, "user:user-uid", meta.GetCreatedBy()) + require.Equal(t, "", meta.GetUpdatedBy()) // empty + ts, err := meta.GetUpdatedTimestamp() + require.NoError(t, err) + require.Nil(t, ts) + + // Change the user... and only update metadata + ctx = authtypes.WithAuthInfo(context.Background(), + &identity.StaticRequester{UserID: 1, UserUID: "user2", Type: authtypes.TypeUser}, + ) + + // Change the status... but generation is the same + updatedObject := insertedObject.DeepCopyObject() + meta, err = utils.MetaAccessor(updatedObject) + require.NoError(t, err) + err = meta.SetStatus(v0alpha1.DashboardStatus{ + Conversion: &v0alpha1.DashboardConversionStatus{ + Failed: true, + Error: "test", + }, + }) + require.NoError(t, err) + meta.SetGeneration(123) // will be removed + + // Update status without changing generation or update metadata + _, err = s.prepareObjectForUpdate(ctx, updatedObject, insertedObject) + require.NoError(t, err) + require.Equal(t, "", meta.GetUpdatedBy()) + require.Equal(t, int64(1), meta.GetGeneration()) + + // Change the folder -- the generation should increase and the updatedBy metadata + dashboard2 := &v0alpha1.Dashboard{ObjectMeta: v1.ObjectMeta{ + Name: dashboard.Name, + }} // TODO... deep copy, See: https://github.com/grafana/grafana/pull/102258 + meta2, err := utils.MetaAccessor(dashboard2) + require.NoError(t, err) + meta2.SetFolder("xyz") // will bump generation + _, err = s.prepareObjectForUpdate(ctx, dashboard2, updatedObject) + require.NoError(t, err) + require.Equal(t, "user:user2", meta2.GetUpdatedBy()) + require.Equal(t, int64(2), meta2.GetGeneration()) + }) + s.opts.RequireDeprecatedInternalID = true t.Run("Should generate internal id", func(t *testing.T) { dashboard := v0alpha1.Dashboard{} @@ -149,4 +213,87 @@ func TestPrepareObjectForStorage(t *testing.T) { require.NoError(t, err) require.Equal(t, meta.GetDeprecatedInternalID(), int64(1)) // nolint:staticcheck }) + + t.Run("calculate generation", func(t *testing.T) { + dash := &v0alpha1.Dashboard{ + ObjectMeta: v1.ObjectMeta{ + Name: "test", + }, + Spec: v0alpha1.DashboardSpec{ + Object: map[string]interface{}{ + "hello": "world", + }, + }, + } + out := getPreparedObject(t, ctx, s, dash, nil) + require.Equal(t, int64(1), out.GetGeneration()) + require.NotEmpty(t, out.GetAnnotation(utils.AnnoKeyCreatedBy)) + require.Equal(t, "", out.GetAnnotation(utils.AnnoKeyUpdatedBy)) + require.Equal(t, "", out.GetAnnotation(utils.AnnoKeyUpdatedTimestamp)) + + t.Run("increment when the spec changes", func(t *testing.T) { + b := dash.DeepCopy() + b.Spec.Object["x"] = "y" + out = getPreparedObject(t, ctx, s, b, dash) + require.Equal(t, int64(2), out.GetGeneration()) + require.NotEmpty(t, out.GetAnnotation(utils.AnnoKeyUpdatedBy)) + require.NotEmpty(t, out.GetAnnotation(utils.AnnoKeyUpdatedTimestamp)) + }) + + t.Run("increment when the folder changes", func(t *testing.T) { + b := dash.DeepCopy() + b.Annotations = map[string]string{ + utils.AnnoKeyFolder: "abc", + } + out = getPreparedObject(t, ctx, s, b, dash) + require.Equal(t, int64(2), out.GetGeneration()) + }) + + t.Run("increment when deleted", func(t *testing.T) { + now := v1.Now() + b := dash.DeepCopy() + b.DeletionTimestamp = &now + out = getPreparedObject(t, ctx, s, b, dash) + require.Equal(t, int64(2), out.GetGeneration()) + }) + + t.Run("keep when status, labels, or annotations change", func(t *testing.T) { + b := dash.DeepCopy() + b.Annotations = map[string]string{ + "x": "hello", + } + b.Labels = map[string]string{ + "a": "b", + } + b.Status = v0alpha1.DashboardStatus{ + Conversion: &v0alpha1.DashboardConversionStatus{ + Failed: true, + }, + } + out = getPreparedObject(t, ctx, s, b, dash) + require.Equal(t, int64(1), out.GetGeneration()) // still 1 + }) + }) +} + +func getPreparedObject(t *testing.T, ctx context.Context, s *Storage, obj runtime.Object, old runtime.Object) utils.GrafanaMetaAccessor { + t.Helper() + + var raw []byte + var err error + + if old == nil { + raw, err = s.prepareObjectForStorage(ctx, obj) + } else { + raw, err = s.prepareObjectForUpdate(ctx, obj, old) + } + require.NoError(t, err) + + out := &unstructured.Unstructured{} + err = out.UnmarshalJSON(raw) + require.NoError(t, err) + + meta, err := utils.MetaAccessor(out) + require.NoError(t, err) + return meta } diff --git a/pkg/storage/unified/apistore/store.go b/pkg/storage/unified/apistore/store.go index 69fc83f803c..4557c0a5d39 100644 --- a/pkg/storage/unified/apistore/store.go +++ b/pkg/storage/unified/apistore/store.go @@ -47,8 +47,14 @@ var _ storage.Interface = (*Storage)(nil) // Optional settings that apply to a single resource type StorageOptions struct { + // ????: should we constrain this to only dashboards for now? + // Not yet clear if this is a good general solution, or just a stop-gap LargeObjectSupport LargeObjectSupport + // Allow writing objects with metadata.annotations[grafana.app/folder] + EnableFolderSupport bool + + // Add internalID label when missing RequireDeprecatedInternalID bool } @@ -445,11 +451,11 @@ func (s *Storage) GuaranteedUpdate( cachedExistingObject runtime.Object, ) error { var ( - res storage.ResponseMeta - updatedObj runtime.Object - existingObj runtime.Object - created bool - err error + res storage.ResponseMeta + updatedObj runtime.Object + existingObj runtime.Object + existingBytes []byte + err error ) req := &resource.UpdateRequest{} req.Key, err = s.getKey(key) @@ -465,61 +471,71 @@ func (s *Storage) GuaranteedUpdate( for attempt := 1; attempt <= MaxUpdateAttempts; attempt = attempt + 1 { // Read the latest value - rsp, err := s.store.Read(ctx, &resource.ReadRequest{Key: req.Key}) + readResponse, err := s.store.Read(ctx, &resource.ReadRequest{Key: req.Key}) if err != nil { return resource.GetError(resource.AsErrorResult(err)) } - if rsp.Error != nil { - if rsp.Error.Code == http.StatusNotFound { + if readResponse.Error != nil { + if readResponse.Error.Code == http.StatusNotFound { if !ignoreNotFound { return apierrors.NewNotFound(s.gr, req.Key.Name) } } else { - return resource.GetError(rsp.Error) + return resource.GetError(readResponse.Error) } } - created = true - existingObj = s.newFunc() - if len(rsp.Value) > 0 { - created = false - _, err = s.convertToObject(rsp.Value, existingObj) + // Upsert? (create because it does not already exist) + if len(readResponse.Value) == 0 { + if !ignoreNotFound { + return apierrors.NewNotFound(s.gr, req.Key.Name) + } + + updatedObj, _, err = tryUpdate(s.newFunc(), res) if err != nil { - return err - } - - mmm, err := utils.MetaAccessor(existingObj) - if err != nil { - return err - } - mmm.SetResourceVersionInt64(rsp.ResourceVersion) - res.ResourceVersion = uint64(rsp.ResourceVersion) - - if rest.IsDualWriteUpdate(ctx) { - // Ignore the RV when updating legacy values - mmm.SetResourceVersion("") - } else { - if err := preconditions.Check(key, existingObj); err != nil { - if attempt >= MaxUpdateAttempts { - return fmt.Errorf("precondition failed: %w", err) - } - continue - } - } - - // restore the full original object before tryUpdate - if s.opts.LargeObjectSupport != nil && mmm.GetBlob() != nil { - err = s.opts.LargeObjectSupport.Reconstruct(ctx, req.Key, s.store, mmm) - if err != nil { + if attempt >= MaxUpdateAttempts { return err } + continue } - } else if !ignoreNotFound { - return apierrors.NewNotFound(s.gr, req.Key.Name) + return s.Create(ctx, key, updatedObj, destination, 0) } - updatedObj, _, err = tryUpdate(existingObj.DeepCopyObject(), res) + existingBytes = readResponse.Value + existingObj, err = s.convertToObject(readResponse.Value, s.newFunc()) + if err != nil { + return err + } + + existing, err := utils.MetaAccessor(existingObj) + if err != nil { + return err + } + existing.SetResourceVersionInt64(readResponse.ResourceVersion) + res.ResourceVersion = uint64(readResponse.ResourceVersion) + + if rest.IsDualWriteUpdate(ctx) { + // Ignore the RV when updating legacy values + existing.SetResourceVersion("") + } else { + if err := preconditions.Check(key, existingObj); err != nil { + if attempt >= MaxUpdateAttempts { + return fmt.Errorf("precondition failed: %w", err) + } + continue + } + } + + // restore the full original object before tryUpdate + if s.opts.LargeObjectSupport != nil && existing.GetBlob() != nil { + err = s.opts.LargeObjectSupport.Reconstruct(ctx, req.Key, s.store, existing) + if err != nil { + return err + } + } + + updatedObj, _, err = tryUpdate(existingObj, res) if err != nil { if attempt >= MaxUpdateAttempts { return err @@ -529,64 +545,32 @@ func (s *Storage) GuaranteedUpdate( break } - unchanged, err := isUnchanged(s.codec, existingObj, updatedObj) + req.Value, err = s.prepareObjectForUpdate(ctx, updatedObj, existingObj) if err != nil { return err } - if unchanged { - var buf bytes.Buffer - if err = s.codec.Encode(updatedObj, &buf); err != nil { - return err - } - if _, err := s.convertToObject(buf.Bytes(), destination); err != nil { - return err - } - return nil - } - - var ( - value []byte - rv int64 - ) - if created { - value, err = s.prepareObjectForStorage(ctx, updatedObj) - if err != nil { - return err - } - rsp2, err := s.store.Create(ctx, &resource.CreateRequest{ - Key: req.Key, - Value: value, - }) + var rv uint64 + // Only update (for real) if the bytes have changed + if !bytes.Equal(req.Value, existingBytes) { + updateResponse, err := s.store.Update(ctx, req) if err != nil { return resource.GetError(resource.AsErrorResult(err)) } - if rsp2.Error != nil { - return resource.GetError(rsp2.Error) + if updateResponse.Error != nil { + return resource.GetError(updateResponse.Error) } - rv = rsp2.ResourceVersion - } else { - value, err = s.prepareObjectForUpdate(ctx, updatedObj, existingObj) - if err != nil { + rv = uint64(updateResponse.ResourceVersion) + } + + if _, err := s.convertToObject(req.Value, destination); err != nil { + return err + } + + if rv > 0 { + if err := s.versioner.UpdateObject(destination, rv); err != nil { return err } - req.Value = value - rsp2, err := s.store.Update(ctx, req) - if err != nil { - return resource.GetError(resource.AsErrorResult(err)) - } - if rsp2.Error != nil { - return resource.GetError(rsp2.Error) - } - rv = rsp2.ResourceVersion - } - - if _, err := s.convertToObject(value, destination); err != nil { - return err - } - - if err := s.versioner.UpdateObject(destination, uint64(rv)); err != nil { - return err } return nil diff --git a/pkg/storage/unified/apistore/util.go b/pkg/storage/unified/apistore/util.go index a716bd77024..44ed68d0b68 100644 --- a/pkg/storage/unified/apistore/util.go +++ b/pkg/storage/unified/apistore/util.go @@ -6,13 +6,11 @@ package apistore import ( - "bytes" "fmt" "strconv" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/selection" "k8s.io/apiserver/pkg/storage" @@ -40,10 +38,12 @@ func toListRequest(k *resource.ResourceKey, opts storage.ListOptions) (*resource } switch opts.ResourceVersionMatch { - case "", metav1.ResourceVersionMatchNotOlderThan: - req.VersionMatch = resource.ResourceVersionMatch_NotOlderThan + case "": + req.VersionMatchV2 = resource.ResourceVersionMatchV2_Unset + case metav1.ResourceVersionMatchNotOlderThan: + req.VersionMatchV2 = resource.ResourceVersionMatchV2_NotOlderThan case metav1.ResourceVersionMatchExact: - req.VersionMatch = resource.ResourceVersionMatch_Exact + req.VersionMatchV2 = resource.ResourceVersionMatchV2_Exact default: return nil, predicate, apierrors.NewBadRequest( fmt.Sprintf("unsupported version match: %v", opts.ResourceVersionMatch), @@ -112,17 +112,3 @@ func toListRequest(k *resource.ResourceKey, opts storage.ListOptions) (*resource return req, predicate, nil } - -func isUnchanged(codec runtime.Codec, obj runtime.Object, newObj runtime.Object) (bool, error) { - buf := new(bytes.Buffer) - if err := codec.Encode(obj, buf); err != nil { - return false, err - } - - newBuf := new(bytes.Buffer) - if err := codec.Encode(newObj, newBuf); err != nil { - return false, err - } - - return bytes.Equal(buf.Bytes(), newBuf.Bytes()), nil -} diff --git a/pkg/storage/unified/client.go b/pkg/storage/unified/client.go index c16fbc92ad3..9239b260deb 100644 --- a/pkg/storage/unified/client.go +++ b/pkg/storage/unified/client.go @@ -117,10 +117,31 @@ func newClient(opts options.StorageOptions, return nil, fmt.Errorf("expecting address for storage_type: %s", opts.StorageType) } - // Create a connection to the gRPC server. - conn, err := GrpcConn(opts.Address, reg) - if err != nil { - return nil, err + var ( + conn grpc.ClientConnInterface + err error + metrics = newClientMetrics(reg) + ) + // Create either a connection pool or a single connection. + // The connection pool __can__ be useful when connection to + // server side load balancers like kube-proxy. + if features.IsEnabledGlobally(featuremgmt.FlagUnifiedStorageGrpcConnectionPool) { + conn, err = newPooledConn(&poolOpts{ + initialCapacity: 3, + maxCapacity: 6, + idleTimeout: time.Minute, + factory: func() (*grpc.ClientConn, error) { + return grpcConn(opts.Address, metrics) + }, + }) + if err != nil { + return nil, err + } + } else { + conn, err = grpcConn(opts.Address, metrics) + if err != nil { + return nil, err + } } // Create a client instance @@ -144,7 +165,7 @@ func newClient(opts options.StorageOptions, } } -func newResourceClient(conn *grpc.ClientConn, cfg *setting.Cfg, features featuremgmt.FeatureToggles, tracer tracing.Tracer) (resource.ResourceClient, error) { +func newResourceClient(conn grpc.ClientConnInterface, cfg *setting.Cfg, features featuremgmt.FeatureToggles, tracer tracing.Tracer) (resource.ResourceClient, error) { if !features.IsEnabledGlobally(featuremgmt.FlagAppPlatformGrpcClientAuth) { return resource.NewLegacyResourceClient(conn), nil } @@ -160,22 +181,8 @@ func newResourceClient(conn *grpc.ClientConn, cfg *setting.Cfg, features feature }) } -// GrpcConn creates a new gRPC connection to the provided address. -func GrpcConn(address string, reg prometheus.Registerer) (*grpc.ClientConn, error) { - // This works for now as the Provide function is only called once during startup. - // We might eventually want to tight this factory to a struct for more runtime control. - metrics := clientMetrics{ - requestDuration: promauto.With(reg).NewHistogramVec(prometheus.HistogramOpts{ - Name: "resource_server_client_request_duration_seconds", - Help: "Time spent executing requests to the resource server.", - Buckets: prometheus.ExponentialBuckets(0.008, 4, 7), - }, []string{"operation", "status_code"}), - requestRetries: promauto.With(reg).NewCounterVec(prometheus.CounterOpts{ - Name: "resource_server_client_request_retries_total", - Help: "Total number of retries for requests to the resource server.", - }, []string{"operation"}), - } - +// grpcConn creates a new gRPC connection to the provided address. +func grpcConn(address string, metrics *clientMetrics) (*grpc.ClientConn, error) { // Report gRPC status code errors as labels. unary, stream := instrument(metrics.requestDuration, middleware.ReportGRPCStatusOption) @@ -212,6 +219,12 @@ func GrpcConn(address string, reg prometheus.Registerer) (*grpc.ClientConn, erro return grpc.NewClient(address, opts...) } +// GrpcConn is the public constructor that can be used for testing. +func GrpcConn(address string, reg prometheus.Registerer) (*grpc.ClientConn, error) { + metrics := newClientMetrics(reg) + return grpcConn(address, metrics) +} + // instrument is the same as grpcclient.Instrument but without the middleware.ClientUserHeaderInterceptor // and middleware.StreamClientUserHeaderInterceptor as we don't need them. func instrument(requestDuration *prometheus.HistogramVec, instrumentationLabelOptions ...middleware.InstrumentationOption) ([]grpc.UnaryClientInterceptor, []grpc.StreamClientInterceptor) { @@ -223,3 +236,19 @@ func instrument(requestDuration *prometheus.HistogramVec, instrumentationLabelOp middleware.StreamClientInstrumentInterceptor(requestDuration, instrumentationLabelOptions...), } } + +func newClientMetrics(reg prometheus.Registerer) *clientMetrics { + // This works for now as the Provide function is only called once during startup. + // We might eventually want to tight this factory to a struct for more runtime control. + return &clientMetrics{ + requestDuration: promauto.With(reg).NewHistogramVec(prometheus.HistogramOpts{ + Name: "resource_server_client_request_duration_seconds", + Help: "Time spent executing requests to the resource server.", + Buckets: prometheus.ExponentialBuckets(0.008, 4, 7), + }, []string{"operation", "status_code"}), + requestRetries: promauto.With(reg).NewCounterVec(prometheus.CounterOpts{ + Name: "resource_server_client_request_retries_total", + Help: "Total number of retries for requests to the resource server.", + }, []string{"operation"}), + } +} diff --git a/pkg/storage/unified/grpc_pool.go b/pkg/storage/unified/grpc_pool.go new file mode 100644 index 00000000000..cebc8abce86 --- /dev/null +++ b/pkg/storage/unified/grpc_pool.go @@ -0,0 +1,82 @@ +package unified + +import ( + "context" + "errors" + "fmt" + "time" + + grpcpool "github.com/1NCE-GmbH/grpc-go-pool" + "google.golang.org/grpc" +) + +// pooledClientConn implements grpc.ClientConnInterface using a connection from a pool. +type pooledClientConn struct { + pool *grpcpool.Pool + // For streaming we want to keep a single connection, as otherwise we saturate the pool. + // Streaming should only be used for watching. + streamConn grpc.ClientConnInterface +} + +// Invoke implements the grpc.ClientConnInterface.Invoke method. +func (pc *pooledClientConn) Invoke(ctx context.Context, method string, args interface{}, reply interface{}, opts ...grpc.CallOption) error { + conn, err := pc.pool.Get(ctx) + if err != nil { + return fmt.Errorf("failed to create grpc conn in pooled client: %w", err) + } + // Return connection to pool when done. + defer func() { + _ = conn.Close() + }() + return conn.ClientConn.Invoke(ctx, method, args, reply, opts...) +} + +// NewStream implements the grpc.ClientConnInterface.NewStream method. +func (pc *pooledClientConn) NewStream(ctx context.Context, desc *grpc.StreamDesc, method string, opts ...grpc.CallOption) (grpc.ClientStream, error) { + stream, err := pc.streamConn.NewStream(ctx, desc, method, opts...) + if err != nil { + return nil, fmt.Errorf("failed to create grpc stream in pooled client: %w", err) + } + return stream, nil +} + +type poolOpts struct { + initialCapacity int + maxCapacity int + idleTimeout time.Duration + factory func() (*grpc.ClientConn, error) +} + +func (opts *poolOpts) validate() error { + if opts.initialCapacity <= 0 { + return errors.New("initial capacity is required") + } + if opts.maxCapacity < opts.initialCapacity { + return errors.New("max capacity is less than initial capacity") + } + if opts.idleTimeout <= 0 { + return errors.New("idle timeout is required") + } + if opts.factory == nil { + return errors.New("factory is required") + } + return nil +} + +func newPooledConn(opts *poolOpts) (grpc.ClientConnInterface, error) { + if err := opts.validate(); err != nil { + return nil, fmt.Errorf("failed to validate grpc connection pool options: %w", err) + } + pool, err := grpcpool.New(opts.factory, opts.initialCapacity, opts.maxCapacity, opts.idleTimeout) + if err != nil { + return nil, fmt.Errorf("failed to create grpc connection pool: %w", err) + } + streamConn, err := opts.factory() + if err != nil { + return nil, fmt.Errorf("failed to create groc streaming connection: %w", err) + } + return &pooledClientConn{ + pool: pool, + streamConn: streamConn, + }, nil +} diff --git a/pkg/storage/unified/resource/client.go b/pkg/storage/unified/resource/client.go index 649a0b7c9c0..c0eb700835d 100644 --- a/pkg/storage/unified/resource/client.go +++ b/pkg/storage/unified/resource/client.go @@ -14,6 +14,7 @@ import ( authnlib "github.com/grafana/authlib/authn" "github.com/grafana/authlib/types" + "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/infra/tracing" @@ -40,7 +41,7 @@ type resourceClient struct { DiagnosticsClient } -func NewLegacyResourceClient(channel *grpc.ClientConn) ResourceClient { +func NewLegacyResourceClient(channel grpc.ClientConnInterface) ResourceClient { cc := grpchan.InterceptClientConn(channel, grpcUtils.UnaryClientInterceptor, grpcUtils.StreamClientInterceptor) return &resourceClient{ ResourceStoreClient: NewResourceStoreClient(cc), @@ -99,7 +100,7 @@ type RemoteResourceClientConfig struct { AllowInsecure bool } -func NewRemoteResourceClient(tracer tracing.Tracer, conn *grpc.ClientConn, cfg RemoteResourceClientConfig) (ResourceClient, error) { +func NewRemoteResourceClient(tracer tracing.Tracer, conn grpc.ClientConnInterface, cfg RemoteResourceClientConfig) (ResourceClient, error) { exchangeOpts := []authnlib.ExchangeClientOpts{} if cfg.AllowInsecure { diff --git a/pkg/storage/unified/resource/continue.go b/pkg/storage/unified/resource/continue.go index 70ca18a89ba..c8825ccde88 100644 --- a/pkg/storage/unified/resource/continue.go +++ b/pkg/storage/unified/resource/continue.go @@ -9,6 +9,7 @@ import ( type ContinueToken struct { StartOffset int64 `json:"o"` ResourceVersion int64 `json:"v"` + SortAscending bool `json:"s"` } func (c ContinueToken) String() string { diff --git a/pkg/storage/unified/resource/continue_test.go b/pkg/storage/unified/resource/continue_test.go index d82b74a4d85..3f67740efaf 100644 --- a/pkg/storage/unified/resource/continue_test.go +++ b/pkg/storage/unified/resource/continue_test.go @@ -10,6 +10,7 @@ func TestContinueToken(t *testing.T) { token := &ContinueToken{ ResourceVersion: 100, StartOffset: 50, + SortAscending: false, } - assert.Equal(t, "eyJvIjo1MCwidiI6MTAwfQ==", token.String()) + assert.Equal(t, "eyJvIjo1MCwidiI6MTAwLCJzIjpmYWxzZX0=", token.String()) } diff --git a/pkg/storage/unified/resource/go.mod b/pkg/storage/unified/resource/go.mod index bd4cea71221..e3aab925836 100644 --- a/pkg/storage/unified/resource/go.mod +++ b/pkg/storage/unified/resource/go.mod @@ -52,7 +52,7 @@ require ( github.com/Azure/go-autorest v14.2.0+incompatible // indirect github.com/Azure/go-autorest/autorest/to v0.4.0 // indirect github.com/AzureAD/microsoft-authentication-library-for-go v1.3.2 // indirect - github.com/BurntSushi/toml v1.4.0 // indirect + github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c // indirect github.com/GoogleCloudPlatform/grpc-gcp-go/grpcgcp v1.5.2 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.49.0 // indirect @@ -98,7 +98,7 @@ require ( github.com/emicklei/go-restful/v3 v3.11.0 // indirect github.com/envoyproxy/go-control-plane v0.13.1 // indirect github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect - github.com/fatih/color v1.17.0 // indirect + github.com/fatih/color v1.18.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect github.com/getkin/kin-openapi v0.129.0 // indirect diff --git a/pkg/storage/unified/resource/go.sum b/pkg/storage/unified/resource/go.sum index 11f1f5bcb10..2ac29e1c27f 100644 --- a/pkg/storage/unified/resource/go.sum +++ b/pkg/storage/unified/resource/go.sum @@ -649,8 +649,9 @@ github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mo github.com/AzureAD/microsoft-authentication-library-for-go v1.3.2 h1:kYRSnvJju5gYVyhkij+RTJ/VR6QIUaCfWeaFm2ycsjQ= github.com/AzureAD/microsoft-authentication-library-for-go v1.3.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0= github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= +github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c h1:pxW6RcqyfI9/kWtOwnv/G+AzdKuy2ZrqINhenH4HyNs= +github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/GoogleCloudPlatform/grpc-gcp-go/grpcgcp v1.5.2 h1:DBjmt6/otSdULyJdVg2BlG0qGZO5tKL4VzOs0jpvw5Q= @@ -890,8 +891,8 @@ github.com/envoyproxy/protoc-gen-validate v0.10.1/go.mod h1:DRjgyB0I43LtJapqN6Ni 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/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= -github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4= -github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI= +github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= +github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= diff --git a/pkg/storage/unified/resource/resource.pb.go b/pkg/storage/unified/resource/resource.pb.go index 8f8d7925e13..69f2de4f1a9 100644 --- a/pkg/storage/unified/resource/resource.pb.go +++ b/pkg/storage/unified/resource/resource.pb.go @@ -24,19 +24,20 @@ const ( type ResourceVersionMatch int32 const ( - ResourceVersionMatch_NotOlderThan ResourceVersionMatch = 0 - ResourceVersionMatch_Exact ResourceVersionMatch = 1 + // Deprecated, use ResourceVersionMatch V2 + ResourceVersionMatch_DEPRECATED_NotOlderThan ResourceVersionMatch = 0 + ResourceVersionMatch_DEPRECATED_Exact ResourceVersionMatch = 1 ) // Enum value maps for ResourceVersionMatch. var ( ResourceVersionMatch_name = map[int32]string{ - 0: "NotOlderThan", - 1: "Exact", + 0: "DEPRECATED_NotOlderThan", + 1: "DEPRECATED_Exact", } ResourceVersionMatch_value = map[string]int32{ - "NotOlderThan": 0, - "Exact": 1, + "DEPRECATED_NotOlderThan": 0, + "DEPRECATED_Exact": 1, } ) @@ -67,6 +68,58 @@ func (ResourceVersionMatch) EnumDescriptor() ([]byte, []int) { return file_resource_proto_rawDescGZIP(), []int{0} } +type ResourceVersionMatchV2 int32 + +const ( + ResourceVersionMatchV2_UNKNOWN ResourceVersionMatchV2 = 0 + ResourceVersionMatchV2_Unset ResourceVersionMatchV2 = 1 + ResourceVersionMatchV2_Exact ResourceVersionMatchV2 = 2 + ResourceVersionMatchV2_NotOlderThan ResourceVersionMatchV2 = 3 +) + +// Enum value maps for ResourceVersionMatchV2. +var ( + ResourceVersionMatchV2_name = map[int32]string{ + 0: "UNKNOWN", + 1: "Unset", + 2: "Exact", + 3: "NotOlderThan", + } + ResourceVersionMatchV2_value = map[string]int32{ + "UNKNOWN": 0, + "Unset": 1, + "Exact": 2, + "NotOlderThan": 3, + } +) + +func (x ResourceVersionMatchV2) Enum() *ResourceVersionMatchV2 { + p := new(ResourceVersionMatchV2) + *p = x + return p +} + +func (x ResourceVersionMatchV2) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (ResourceVersionMatchV2) Descriptor() protoreflect.EnumDescriptor { + return file_resource_proto_enumTypes[1].Descriptor() +} + +func (ResourceVersionMatchV2) Type() protoreflect.EnumType { + return &file_resource_proto_enumTypes[1] +} + +func (x ResourceVersionMatchV2) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use ResourceVersionMatchV2.Descriptor instead. +func (ResourceVersionMatchV2) EnumDescriptor() ([]byte, []int) { + return file_resource_proto_rawDescGZIP(), []int{1} +} + type ListRequest_Source int32 const ( @@ -100,11 +153,11 @@ func (x ListRequest_Source) String() string { } func (ListRequest_Source) Descriptor() protoreflect.EnumDescriptor { - return file_resource_proto_enumTypes[1].Descriptor() + return file_resource_proto_enumTypes[2].Descriptor() } func (ListRequest_Source) Type() protoreflect.EnumType { - return &file_resource_proto_enumTypes[1] + return &file_resource_proto_enumTypes[2] } func (x ListRequest_Source) Number() protoreflect.EnumNumber { @@ -158,11 +211,11 @@ func (x WatchEvent_Type) String() string { } func (WatchEvent_Type) Descriptor() protoreflect.EnumDescriptor { - return file_resource_proto_enumTypes[2].Descriptor() + return file_resource_proto_enumTypes[3].Descriptor() } func (WatchEvent_Type) Type() protoreflect.EnumType { - return &file_resource_proto_enumTypes[2] + return &file_resource_proto_enumTypes[3] } func (x WatchEvent_Type) Number() protoreflect.EnumNumber { @@ -212,11 +265,11 @@ func (x BulkRequest_Action) String() string { } func (BulkRequest_Action) Descriptor() protoreflect.EnumDescriptor { - return file_resource_proto_enumTypes[3].Descriptor() + return file_resource_proto_enumTypes[4].Descriptor() } func (BulkRequest_Action) Type() protoreflect.EnumType { - return &file_resource_proto_enumTypes[3] + return &file_resource_proto_enumTypes[4] } func (x BulkRequest_Action) Number() protoreflect.EnumNumber { @@ -264,11 +317,11 @@ func (x HealthCheckResponse_ServingStatus) String() string { } func (HealthCheckResponse_ServingStatus) Descriptor() protoreflect.EnumDescriptor { - return file_resource_proto_enumTypes[4].Descriptor() + return file_resource_proto_enumTypes[5].Descriptor() } func (HealthCheckResponse_ServingStatus) Type() protoreflect.EnumType { - return &file_resource_proto_enumTypes[4] + return &file_resource_proto_enumTypes[5] } func (x HealthCheckResponse_ServingStatus) Number() protoreflect.EnumNumber { @@ -339,11 +392,11 @@ func (x ResourceTableColumnDefinition_ColumnType) String() string { } func (ResourceTableColumnDefinition_ColumnType) Descriptor() protoreflect.EnumDescriptor { - return file_resource_proto_enumTypes[5].Descriptor() + return file_resource_proto_enumTypes[6].Descriptor() } func (ResourceTableColumnDefinition_ColumnType) Type() protoreflect.EnumType { - return &file_resource_proto_enumTypes[5] + return &file_resource_proto_enumTypes[6] } func (x ResourceTableColumnDefinition_ColumnType) Number() protoreflect.EnumNumber { @@ -387,11 +440,11 @@ func (x PutBlobRequest_Method) String() string { } func (PutBlobRequest_Method) Descriptor() protoreflect.EnumDescriptor { - return file_resource_proto_enumTypes[6].Descriptor() + return file_resource_proto_enumTypes[7].Descriptor() } func (PutBlobRequest_Method) Type() protoreflect.EnumType { - return &file_resource_proto_enumTypes[6] + return &file_resource_proto_enumTypes[7] } func (x PutBlobRequest_Method) Number() protoreflect.EnumNumber { @@ -400,7 +453,7 @@ func (x PutBlobRequest_Method) Number() protoreflect.EnumNumber { // Deprecated: Use PutBlobRequest_Method.Descriptor instead. func (PutBlobRequest_Method) EnumDescriptor() ([]byte, []int) { - return file_resource_proto_rawDescGZIP(), []int{36, 0} + return file_resource_proto_rawDescGZIP(), []int{34, 0} } type ResourceKey struct { @@ -1152,10 +1205,8 @@ type ReadRequest struct { Key *ResourceKey `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` // Optionally pick an explicit resource version ResourceVersion int64 `protobuf:"varint,2,opt,name=resource_version,json=resourceVersion,proto3" json:"resource_version,omitempty"` - // Optionally decide to return the latest RV if deleted - IncludeDeleted bool `protobuf:"varint,3,opt,name=include_deleted,json=includeDeleted,proto3" json:"include_deleted,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *ReadRequest) Reset() { @@ -1202,13 +1253,6 @@ func (x *ReadRequest) GetResourceVersion() int64 { return 0 } -func (x *ReadRequest) GetIncludeDeleted() bool { - if x != nil { - return x.IncludeDeleted - } - return false -} - type ReadResponse struct { state protoimpl.MessageState `protogen:"open.v1"` // Error details @@ -1408,16 +1452,18 @@ type ListRequest struct { // The resource version ResourceVersion int64 `protobuf:"varint,2,opt,name=resource_version,json=resourceVersion,proto3" json:"resource_version,omitempty"` // List options - VersionMatch ResourceVersionMatch `protobuf:"varint,3,opt,name=version_match,json=versionMatch,proto3,enum=resource.ResourceVersionMatch" json:"version_match,omitempty"` + // DEPRECATED - use version_match_v2 + VersionMatch *ResourceVersionMatch `protobuf:"varint,3,opt,name=version_match,json=versionMatch,proto3,enum=resource.ResourceVersionMatch,oneof" json:"version_match,omitempty"` // Maximum number of items to return // NOTE responses will also be limited by the response payload size Limit int64 `protobuf:"varint,4,opt,name=limit,proto3" json:"limit,omitempty"` // Filtering Options *ListOptions `protobuf:"bytes,5,opt,name=options,proto3" json:"options,omitempty"` // Select values from history or trash - Source ListRequest_Source `protobuf:"varint,6,opt,name=source,proto3,enum=resource.ListRequest_Source" json:"source,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + Source ListRequest_Source `protobuf:"varint,6,opt,name=source,proto3,enum=resource.ListRequest_Source" json:"source,omitempty"` + VersionMatchV2 ResourceVersionMatchV2 `protobuf:"varint,7,opt,name=version_match_v2,json=versionMatchV2,proto3,enum=resource.ResourceVersionMatchV2" json:"version_match_v2,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *ListRequest) Reset() { @@ -1465,10 +1511,10 @@ func (x *ListRequest) GetResourceVersion() int64 { } func (x *ListRequest) GetVersionMatch() ResourceVersionMatch { - if x != nil { - return x.VersionMatch + if x != nil && x.VersionMatch != nil { + return *x.VersionMatch } - return ResourceVersionMatch_NotOlderThan + return ResourceVersionMatch_DEPRECATED_NotOlderThan } func (x *ListRequest) GetLimit() int64 { @@ -1492,6 +1538,13 @@ func (x *ListRequest) GetSource() ListRequest_Source { return ListRequest_STORE } +func (x *ListRequest) GetVersionMatchV2() ResourceVersionMatchV2 { + if x != nil { + return x.VersionMatchV2 + } + return ResourceVersionMatchV2_UNKNOWN +} + type ListResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Items []*ResourceWrapper `protobuf:"bytes,1,rep,name=items,proto3" json:"items,omitempty"` @@ -2844,114 +2897,6 @@ func (x *ResourceTableRow) GetObject() []byte { return nil } -type RestoreRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - // Full key must be set - Key *ResourceKey `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` - // The resource version to restore - ResourceVersion int64 `protobuf:"varint,2,opt,name=resource_version,json=resourceVersion,proto3" json:"resource_version,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *RestoreRequest) Reset() { - *x = RestoreRequest{} - mi := &file_resource_proto_msgTypes[34] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *RestoreRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*RestoreRequest) ProtoMessage() {} - -func (x *RestoreRequest) ProtoReflect() protoreflect.Message { - mi := &file_resource_proto_msgTypes[34] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use RestoreRequest.ProtoReflect.Descriptor instead. -func (*RestoreRequest) Descriptor() ([]byte, []int) { - return file_resource_proto_rawDescGZIP(), []int{34} -} - -func (x *RestoreRequest) GetKey() *ResourceKey { - if x != nil { - return x.Key - } - return nil -} - -func (x *RestoreRequest) GetResourceVersion() int64 { - if x != nil { - return x.ResourceVersion - } - return 0 -} - -type RestoreResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - // Error details - Error *ErrorResult `protobuf:"bytes,1,opt,name=error,proto3" json:"error,omitempty"` - // The updated resource version - ResourceVersion int64 `protobuf:"varint,2,opt,name=resource_version,json=resourceVersion,proto3" json:"resource_version,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *RestoreResponse) Reset() { - *x = RestoreResponse{} - mi := &file_resource_proto_msgTypes[35] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *RestoreResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*RestoreResponse) ProtoMessage() {} - -func (x *RestoreResponse) ProtoReflect() protoreflect.Message { - mi := &file_resource_proto_msgTypes[35] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use RestoreResponse.ProtoReflect.Descriptor instead. -func (*RestoreResponse) Descriptor() ([]byte, []int) { - return file_resource_proto_rawDescGZIP(), []int{35} -} - -func (x *RestoreResponse) GetError() *ErrorResult { - if x != nil { - return x.Error - } - return nil -} - -func (x *RestoreResponse) GetResourceVersion() int64 { - if x != nil { - return x.ResourceVersion - } - return 0 -} - type PutBlobRequest struct { state protoimpl.MessageState `protogen:"open.v1"` // The resource that will use this blob @@ -2970,7 +2915,7 @@ type PutBlobRequest struct { func (x *PutBlobRequest) Reset() { *x = PutBlobRequest{} - mi := &file_resource_proto_msgTypes[36] + mi := &file_resource_proto_msgTypes[34] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2982,7 +2927,7 @@ func (x *PutBlobRequest) String() string { func (*PutBlobRequest) ProtoMessage() {} func (x *PutBlobRequest) ProtoReflect() protoreflect.Message { - mi := &file_resource_proto_msgTypes[36] + mi := &file_resource_proto_msgTypes[34] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2995,7 +2940,7 @@ func (x *PutBlobRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use PutBlobRequest.ProtoReflect.Descriptor instead. func (*PutBlobRequest) Descriptor() ([]byte, []int) { - return file_resource_proto_rawDescGZIP(), []int{36} + return file_resource_proto_rawDescGZIP(), []int{34} } func (x *PutBlobRequest) GetResource() *ResourceKey { @@ -3048,7 +2993,7 @@ type PutBlobResponse struct { func (x *PutBlobResponse) Reset() { *x = PutBlobResponse{} - mi := &file_resource_proto_msgTypes[37] + mi := &file_resource_proto_msgTypes[35] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3060,7 +3005,7 @@ func (x *PutBlobResponse) String() string { func (*PutBlobResponse) ProtoMessage() {} func (x *PutBlobResponse) ProtoReflect() protoreflect.Message { - mi := &file_resource_proto_msgTypes[37] + mi := &file_resource_proto_msgTypes[35] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3073,7 +3018,7 @@ func (x *PutBlobResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use PutBlobResponse.ProtoReflect.Descriptor instead. func (*PutBlobResponse) Descriptor() ([]byte, []int) { - return file_resource_proto_rawDescGZIP(), []int{37} + return file_resource_proto_rawDescGZIP(), []int{35} } func (x *PutBlobResponse) GetError() *ErrorResult { @@ -3140,7 +3085,7 @@ type GetBlobRequest struct { func (x *GetBlobRequest) Reset() { *x = GetBlobRequest{} - mi := &file_resource_proto_msgTypes[38] + mi := &file_resource_proto_msgTypes[36] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3152,7 +3097,7 @@ func (x *GetBlobRequest) String() string { func (*GetBlobRequest) ProtoMessage() {} func (x *GetBlobRequest) ProtoReflect() protoreflect.Message { - mi := &file_resource_proto_msgTypes[38] + mi := &file_resource_proto_msgTypes[36] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3165,7 +3110,7 @@ func (x *GetBlobRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetBlobRequest.ProtoReflect.Descriptor instead. func (*GetBlobRequest) Descriptor() ([]byte, []int) { - return file_resource_proto_rawDescGZIP(), []int{38} + return file_resource_proto_rawDescGZIP(), []int{36} } func (x *GetBlobRequest) GetResource() *ResourceKey { @@ -3214,7 +3159,7 @@ type GetBlobResponse struct { func (x *GetBlobResponse) Reset() { *x = GetBlobResponse{} - mi := &file_resource_proto_msgTypes[39] + mi := &file_resource_proto_msgTypes[37] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3226,7 +3171,7 @@ func (x *GetBlobResponse) String() string { func (*GetBlobResponse) ProtoMessage() {} func (x *GetBlobResponse) ProtoReflect() protoreflect.Message { - mi := &file_resource_proto_msgTypes[39] + mi := &file_resource_proto_msgTypes[37] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3239,7 +3184,7 @@ func (x *GetBlobResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetBlobResponse.ProtoReflect.Descriptor instead. func (*GetBlobResponse) Descriptor() ([]byte, []int) { - return file_resource_proto_rawDescGZIP(), []int{39} + return file_resource_proto_rawDescGZIP(), []int{37} } func (x *GetBlobResponse) GetError() *ErrorResult { @@ -3280,7 +3225,7 @@ type WatchEvent_Resource struct { func (x *WatchEvent_Resource) Reset() { *x = WatchEvent_Resource{} - mi := &file_resource_proto_msgTypes[40] + mi := &file_resource_proto_msgTypes[38] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3292,7 +3237,7 @@ func (x *WatchEvent_Resource) String() string { func (*WatchEvent_Resource) ProtoMessage() {} func (x *WatchEvent_Resource) ProtoReflect() protoreflect.Message { - mi := &file_resource_proto_msgTypes[40] + mi := &file_resource_proto_msgTypes[38] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3339,7 +3284,7 @@ type BulkResponse_Summary struct { func (x *BulkResponse_Summary) Reset() { *x = BulkResponse_Summary{} - mi := &file_resource_proto_msgTypes[41] + mi := &file_resource_proto_msgTypes[39] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3351,7 +3296,7 @@ func (x *BulkResponse_Summary) String() string { func (*BulkResponse_Summary) ProtoMessage() {} func (x *BulkResponse_Summary) ProtoReflect() protoreflect.Message { - mi := &file_resource_proto_msgTypes[41] + mi := &file_resource_proto_msgTypes[39] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3435,7 +3380,7 @@ type BulkResponse_Rejected struct { func (x *BulkResponse_Rejected) Reset() { *x = BulkResponse_Rejected{} - mi := &file_resource_proto_msgTypes[42] + mi := &file_resource_proto_msgTypes[40] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3447,7 +3392,7 @@ func (x *BulkResponse_Rejected) String() string { func (*BulkResponse_Rejected) ProtoMessage() {} func (x *BulkResponse_Rejected) ProtoReflect() protoreflect.Message { - mi := &file_resource_proto_msgTypes[42] + mi := &file_resource_proto_msgTypes[40] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3498,7 +3443,7 @@ type ResourceStatsResponse_Stats struct { func (x *ResourceStatsResponse_Stats) Reset() { *x = ResourceStatsResponse_Stats{} - mi := &file_resource_proto_msgTypes[43] + mi := &file_resource_proto_msgTypes[41] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3510,7 +3455,7 @@ func (x *ResourceStatsResponse_Stats) String() string { func (*ResourceStatsResponse_Stats) ProtoMessage() {} func (x *ResourceStatsResponse_Stats) ProtoReflect() protoreflect.Message { - mi := &file_resource_proto_msgTypes[43] + mi := &file_resource_proto_msgTypes[41] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3557,7 +3502,7 @@ type ResourceSearchRequest_Sort struct { func (x *ResourceSearchRequest_Sort) Reset() { *x = ResourceSearchRequest_Sort{} - mi := &file_resource_proto_msgTypes[44] + mi := &file_resource_proto_msgTypes[42] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3569,7 +3514,7 @@ func (x *ResourceSearchRequest_Sort) String() string { func (*ResourceSearchRequest_Sort) ProtoMessage() {} func (x *ResourceSearchRequest_Sort) ProtoReflect() protoreflect.Message { - mi := &file_resource_proto_msgTypes[44] + mi := &file_resource_proto_msgTypes[42] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3609,7 +3554,7 @@ type ResourceSearchRequest_Facet struct { func (x *ResourceSearchRequest_Facet) Reset() { *x = ResourceSearchRequest_Facet{} - mi := &file_resource_proto_msgTypes[45] + mi := &file_resource_proto_msgTypes[43] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3621,7 +3566,7 @@ func (x *ResourceSearchRequest_Facet) String() string { func (*ResourceSearchRequest_Facet) ProtoMessage() {} func (x *ResourceSearchRequest_Facet) ProtoReflect() protoreflect.Message { - mi := &file_resource_proto_msgTypes[45] + mi := &file_resource_proto_msgTypes[43] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3666,7 +3611,7 @@ type ResourceSearchResponse_Facet struct { func (x *ResourceSearchResponse_Facet) Reset() { *x = ResourceSearchResponse_Facet{} - mi := &file_resource_proto_msgTypes[47] + mi := &file_resource_proto_msgTypes[45] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3678,7 +3623,7 @@ func (x *ResourceSearchResponse_Facet) String() string { func (*ResourceSearchResponse_Facet) ProtoMessage() {} func (x *ResourceSearchResponse_Facet) ProtoReflect() protoreflect.Message { - mi := &file_resource_proto_msgTypes[47] + mi := &file_resource_proto_msgTypes[45] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3732,7 +3677,7 @@ type ResourceSearchResponse_TermFacet struct { func (x *ResourceSearchResponse_TermFacet) Reset() { *x = ResourceSearchResponse_TermFacet{} - mi := &file_resource_proto_msgTypes[48] + mi := &file_resource_proto_msgTypes[46] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3744,7 +3689,7 @@ func (x *ResourceSearchResponse_TermFacet) String() string { func (*ResourceSearchResponse_TermFacet) ProtoMessage() {} func (x *ResourceSearchResponse_TermFacet) ProtoReflect() protoreflect.Message { - mi := &file_resource_proto_msgTypes[48] + mi := &file_resource_proto_msgTypes[46] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3794,7 +3739,7 @@ type ListManagedObjectsResponse_Item struct { func (x *ListManagedObjectsResponse_Item) Reset() { *x = ListManagedObjectsResponse_Item{} - mi := &file_resource_proto_msgTypes[50] + mi := &file_resource_proto_msgTypes[48] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3806,7 +3751,7 @@ func (x *ListManagedObjectsResponse_Item) String() string { func (*ListManagedObjectsResponse_Item) ProtoMessage() {} func (x *ListManagedObjectsResponse_Item) ProtoReflect() protoreflect.Message { - mi := &file_resource_proto_msgTypes[50] + mi := &file_resource_proto_msgTypes[48] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3877,7 +3822,7 @@ type CountManagedObjectsResponse_ResourceCount struct { func (x *CountManagedObjectsResponse_ResourceCount) Reset() { *x = CountManagedObjectsResponse_ResourceCount{} - mi := &file_resource_proto_msgTypes[51] + mi := &file_resource_proto_msgTypes[49] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3889,7 +3834,7 @@ func (x *CountManagedObjectsResponse_ResourceCount) String() string { func (*CountManagedObjectsResponse_ResourceCount) ProtoMessage() {} func (x *CountManagedObjectsResponse_ResourceCount) ProtoReflect() protoreflect.Message { - mi := &file_resource_proto_msgTypes[51] + mi := &file_resource_proto_msgTypes[49] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3962,7 +3907,7 @@ type ResourceTableColumnDefinition_Properties struct { func (x *ResourceTableColumnDefinition_Properties) Reset() { *x = ResourceTableColumnDefinition_Properties{} - mi := &file_resource_proto_msgTypes[52] + mi := &file_resource_proto_msgTypes[50] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3974,7 +3919,7 @@ func (x *ResourceTableColumnDefinition_Properties) String() string { func (*ResourceTableColumnDefinition_Properties) ProtoMessage() {} func (x *ResourceTableColumnDefinition_Properties) ProtoReflect() protoreflect.Message { - mi := &file_resource_proto_msgTypes[52] + mi := &file_resource_proto_msgTypes[50] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4107,529 +4052,522 @@ var file_resource_proto_rawDesc = string([]byte{ 0x72, 0x72, 0x6f, 0x72, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x29, 0x0a, 0x10, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x72, 0x65, 0x73, - 0x6f, 0x75, 0x72, 0x63, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x8a, 0x01, 0x0a, - 0x0b, 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x27, 0x0a, 0x03, - 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72, 0x65, 0x73, 0x6f, - 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4b, 0x65, 0x79, - 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x29, 0x0a, 0x10, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, - 0x65, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x0f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, - 0x12, 0x27, 0x0a, 0x0f, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x64, 0x65, 0x6c, 0x65, - 0x74, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0e, 0x69, 0x6e, 0x63, 0x6c, 0x75, - 0x64, 0x65, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x22, 0x7c, 0x0a, 0x0c, 0x52, 0x65, 0x61, - 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2b, 0x0a, 0x05, 0x65, 0x72, 0x72, - 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x2e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, - 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x29, 0x0a, 0x10, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, - 0x63, 0x65, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x0f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, - 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, - 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x53, 0x0a, 0x0b, 0x52, 0x65, 0x71, 0x75, 0x69, - 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x1a, 0x0a, 0x08, 0x6f, 0x70, 0x65, 0x72, - 0x61, 0x74, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6f, 0x70, 0x65, 0x72, - 0x61, 0x74, 0x6f, 0x72, 0x12, 0x16, 0x0a, 0x06, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x03, - 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x22, 0x94, 0x01, 0x0a, - 0x0b, 0x4c, 0x69, 0x73, 0x74, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x27, 0x0a, 0x03, - 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72, 0x65, 0x73, 0x6f, - 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4b, 0x65, 0x79, - 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x2d, 0x0a, 0x06, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x18, - 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, - 0x2e, 0x52, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x06, 0x6c, 0x61, - 0x62, 0x65, 0x6c, 0x73, 0x12, 0x2d, 0x0a, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x18, 0x03, - 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, - 0x52, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x06, 0x66, 0x69, 0x65, - 0x6c, 0x64, 0x73, 0x22, 0xcf, 0x02, 0x0a, 0x0b, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x12, 0x26, 0x0a, 0x0f, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x70, 0x61, 0x67, 0x65, - 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6e, 0x65, - 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x29, 0x0a, 0x10, 0x72, - 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x56, - 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x43, 0x0a, 0x0d, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, - 0x6e, 0x5f, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1e, 0x2e, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x61, 0x0a, 0x0b, + 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x27, 0x0a, 0x03, 0x6b, + 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4b, 0x65, 0x79, 0x52, + 0x03, 0x6b, 0x65, 0x79, 0x12, 0x29, 0x0a, 0x10, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, + 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, + 0x7c, 0x0a, 0x0c, 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x2b, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, + 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, + 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x29, 0x0a, 0x10, + 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x53, 0x0a, + 0x0b, 0x52, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x10, 0x0a, 0x03, + 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x1a, + 0x0a, 0x08, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x08, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x12, 0x16, 0x0a, 0x06, 0x76, 0x61, + 0x6c, 0x75, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x73, 0x22, 0x94, 0x01, 0x0a, 0x0b, 0x4c, 0x69, 0x73, 0x74, 0x4f, 0x70, 0x74, 0x69, 0x6f, + 0x6e, 0x73, 0x12, 0x27, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x15, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x4b, 0x65, 0x79, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x2d, 0x0a, 0x06, 0x6c, + 0x61, 0x62, 0x65, 0x6c, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72, 0x65, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x6d, 0x65, + 0x6e, 0x74, 0x52, 0x06, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x12, 0x2d, 0x0a, 0x06, 0x66, 0x69, + 0x65, 0x6c, 0x64, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x6d, 0x65, 0x6e, + 0x74, 0x52, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x22, 0xb2, 0x03, 0x0a, 0x0b, 0x4c, 0x69, + 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x26, 0x0a, 0x0f, 0x6e, 0x65, 0x78, + 0x74, 0x5f, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, + 0x6e, 0x12, 0x29, 0x0a, 0x10, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x76, 0x65, + 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x72, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x48, 0x0a, 0x0d, + 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x0e, 0x32, 0x1e, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, + 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x4d, 0x61, + 0x74, 0x63, 0x68, 0x48, 0x00, 0x52, 0x0c, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x4d, 0x61, + 0x74, 0x63, 0x68, 0x88, 0x01, 0x01, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x2f, 0x0a, 0x07, + 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, + 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4f, 0x70, 0x74, + 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x34, 0x0a, + 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1c, 0x2e, + 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x2e, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x06, 0x73, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x12, 0x4a, 0x0a, 0x10, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x6d, + 0x61, 0x74, 0x63, 0x68, 0x5f, 0x76, 0x32, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x20, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, - 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x52, 0x0c, 0x76, - 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x12, 0x14, 0x0a, 0x05, 0x6c, - 0x69, 0x6d, 0x69, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, - 0x74, 0x12, 0x2f, 0x0a, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x05, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x4c, 0x69, - 0x73, 0x74, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, - 0x6e, 0x73, 0x12, 0x34, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x06, 0x20, 0x01, - 0x28, 0x0e, 0x32, 0x1c, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x4c, 0x69, - 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, - 0x52, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x22, 0x2b, 0x0a, 0x06, 0x53, 0x6f, 0x75, 0x72, - 0x63, 0x65, 0x12, 0x09, 0x0a, 0x05, 0x53, 0x54, 0x4f, 0x52, 0x45, 0x10, 0x00, 0x12, 0x0b, 0x0a, - 0x07, 0x48, 0x49, 0x53, 0x54, 0x4f, 0x52, 0x59, 0x10, 0x01, 0x12, 0x09, 0x0a, 0x05, 0x54, 0x52, - 0x41, 0x53, 0x48, 0x10, 0x02, 0x22, 0xf1, 0x01, 0x0a, 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2f, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, - 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, - 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x57, 0x72, 0x61, 0x70, 0x70, 0x65, 0x72, - 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x6e, 0x65, 0x78, 0x74, 0x5f, - 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, - 0x29, 0x0a, 0x10, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x76, 0x65, 0x72, 0x73, - 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x72, 0x65, 0x73, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x30, 0x0a, 0x14, 0x72, 0x65, - 0x6d, 0x61, 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x5f, 0x69, 0x74, 0x65, 0x6d, 0x5f, 0x63, 0x6f, 0x75, - 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x12, 0x72, 0x65, 0x6d, 0x61, 0x69, 0x6e, - 0x69, 0x6e, 0x67, 0x49, 0x74, 0x65, 0x6d, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x2b, 0x0a, 0x05, - 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72, 0x65, - 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x65, 0x73, 0x75, - 0x6c, 0x74, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0xb9, 0x01, 0x0a, 0x0c, 0x57, 0x61, - 0x74, 0x63, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x69, - 0x6e, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x73, 0x69, 0x6e, 0x63, 0x65, - 0x12, 0x2f, 0x0a, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x15, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x4c, 0x69, 0x73, - 0x74, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, - 0x73, 0x12, 0x2e, 0x0a, 0x13, 0x73, 0x65, 0x6e, 0x64, 0x5f, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, - 0x6c, 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x11, - 0x73, 0x65, 0x6e, 0x64, 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, - 0x73, 0x12, 0x32, 0x0a, 0x15, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x5f, 0x77, 0x61, 0x74, 0x63, 0x68, - 0x5f, 0x62, 0x6f, 0x6f, 0x6b, 0x6d, 0x61, 0x72, 0x6b, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, - 0x52, 0x13, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x57, 0x61, 0x74, 0x63, 0x68, 0x42, 0x6f, 0x6f, 0x6b, - 0x6d, 0x61, 0x72, 0x6b, 0x73, 0x22, 0xdf, 0x02, 0x0a, 0x0a, 0x57, 0x61, 0x74, 0x63, 0x68, 0x45, - 0x76, 0x65, 0x6e, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, - 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, - 0x6d, 0x70, 0x12, 0x2d, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, - 0x32, 0x19, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x57, 0x61, 0x74, 0x63, - 0x68, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, - 0x65, 0x12, 0x39, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x57, - 0x61, 0x74, 0x63, 0x68, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, - 0x63, 0x65, 0x52, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x39, 0x0a, 0x08, - 0x70, 0x72, 0x65, 0x76, 0x69, 0x6f, 0x75, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, - 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x57, 0x61, 0x74, 0x63, 0x68, 0x45, - 0x76, 0x65, 0x6e, 0x74, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x08, 0x70, - 0x72, 0x65, 0x76, 0x69, 0x6f, 0x75, 0x73, 0x1a, 0x3a, 0x0a, 0x08, 0x52, 0x65, 0x73, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x14, 0x0a, - 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, - 0x6c, 0x75, 0x65, 0x22, 0x52, 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x55, - 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05, 0x41, 0x44, 0x44, 0x45, - 0x44, 0x10, 0x01, 0x12, 0x0c, 0x0a, 0x08, 0x4d, 0x4f, 0x44, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, - 0x02, 0x12, 0x0b, 0x0a, 0x07, 0x44, 0x45, 0x4c, 0x45, 0x54, 0x45, 0x44, 0x10, 0x03, 0x12, 0x0c, - 0x0a, 0x08, 0x42, 0x4f, 0x4f, 0x4b, 0x4d, 0x41, 0x52, 0x4b, 0x10, 0x04, 0x12, 0x09, 0x0a, 0x05, - 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x05, 0x22, 0xd7, 0x01, 0x0a, 0x0b, 0x42, 0x75, 0x6c, 0x6b, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x27, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, - 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4b, 0x65, 0x79, 0x52, 0x03, 0x6b, 0x65, 0x79, - 0x12, 0x34, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, - 0x32, 0x1c, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x42, 0x75, 0x6c, 0x6b, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x06, - 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x16, 0x0a, 0x06, - 0x66, 0x6f, 0x6c, 0x64, 0x65, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x66, 0x6f, - 0x6c, 0x64, 0x65, 0x72, 0x22, 0x3b, 0x0a, 0x06, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0b, - 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05, 0x41, - 0x44, 0x44, 0x45, 0x44, 0x10, 0x01, 0x12, 0x0c, 0x0a, 0x08, 0x4d, 0x4f, 0x44, 0x49, 0x46, 0x49, - 0x45, 0x44, 0x10, 0x02, 0x12, 0x0b, 0x0a, 0x07, 0x44, 0x45, 0x4c, 0x45, 0x54, 0x45, 0x44, 0x10, - 0x03, 0x22, 0xda, 0x04, 0x0a, 0x0c, 0x42, 0x75, 0x6c, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x2b, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x15, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x45, 0x72, 0x72, - 0x6f, 0x72, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, - 0x1c, 0x0a, 0x09, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x09, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x65, 0x64, 0x12, 0x38, 0x0a, - 0x07, 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1e, - 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x42, 0x75, 0x6c, 0x6b, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x52, 0x07, - 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x12, 0x3b, 0x0a, 0x08, 0x72, 0x65, 0x6a, 0x65, 0x63, - 0x74, 0x65, 0x64, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x72, 0x65, 0x73, 0x6f, - 0x75, 0x72, 0x63, 0x65, 0x2e, 0x42, 0x75, 0x6c, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x2e, 0x52, 0x65, 0x6a, 0x65, 0x63, 0x74, 0x65, 0x64, 0x52, 0x08, 0x72, 0x65, 0x6a, 0x65, - 0x63, 0x74, 0x65, 0x64, 0x1a, 0x86, 0x02, 0x0a, 0x07, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, - 0x12, 0x1c, 0x0a, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x14, - 0x0a, 0x05, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x67, - 0x72, 0x6f, 0x75, 0x70, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, - 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x68, 0x69, 0x73, 0x74, 0x6f, 0x72, - 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x68, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, - 0x12, 0x29, 0x0a, 0x10, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x76, 0x65, 0x72, - 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x72, 0x65, 0x73, 0x6f, - 0x75, 0x72, 0x63, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x25, 0x0a, 0x0e, 0x70, - 0x72, 0x65, 0x76, 0x69, 0x6f, 0x75, 0x73, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x07, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x0d, 0x70, 0x72, 0x65, 0x76, 0x69, 0x6f, 0x75, 0x73, 0x43, 0x6f, 0x75, - 0x6e, 0x74, 0x12, 0x29, 0x0a, 0x10, 0x70, 0x72, 0x65, 0x76, 0x69, 0x6f, 0x75, 0x73, 0x5f, 0x68, - 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x70, 0x72, - 0x65, 0x76, 0x69, 0x6f, 0x75, 0x73, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x1a, 0x7f, 0x0a, - 0x08, 0x52, 0x65, 0x6a, 0x65, 0x63, 0x74, 0x65, 0x64, 0x12, 0x27, 0x0a, 0x03, 0x6b, 0x65, 0x79, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, - 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4b, 0x65, 0x79, 0x52, 0x03, 0x6b, - 0x65, 0x79, 0x12, 0x34, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x0e, 0x32, 0x1c, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x42, 0x75, - 0x6c, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, - 0x52, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, - 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x62, - 0x0a, 0x14, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, - 0x61, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, - 0x70, 0x61, 0x63, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x6b, 0x69, 0x6e, 0x64, 0x73, 0x18, 0x02, 0x20, - 0x03, 0x28, 0x09, 0x52, 0x05, 0x6b, 0x69, 0x6e, 0x64, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x66, 0x6f, - 0x6c, 0x64, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x66, 0x6f, 0x6c, 0x64, - 0x65, 0x72, 0x22, 0xd2, 0x01, 0x0a, 0x15, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, - 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2b, 0x0a, 0x05, - 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72, 0x65, - 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x65, 0x73, 0x75, - 0x6c, 0x74, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x3b, 0x0a, 0x05, 0x73, 0x74, 0x61, - 0x74, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x74, 0x61, 0x74, - 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, - 0x05, 0x73, 0x74, 0x61, 0x74, 0x73, 0x1a, 0x4f, 0x0a, 0x05, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, - 0x14, 0x0a, 0x05, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, - 0x67, 0x72, 0x6f, 0x75, 0x70, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, - 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, - 0x65, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x8e, 0x05, 0x0a, 0x15, 0x52, 0x65, 0x73, 0x6f, - 0x75, 0x72, 0x63, 0x65, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x12, 0x2f, 0x0a, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x4c, 0x69, - 0x73, 0x74, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, - 0x6e, 0x73, 0x12, 0x33, 0x0a, 0x09, 0x66, 0x65, 0x64, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x18, - 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, - 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4b, 0x65, 0x79, 0x52, 0x09, 0x66, 0x65, - 0x64, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x12, 0x14, 0x0a, - 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x6c, 0x69, - 0x6d, 0x69, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x05, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x12, 0x3c, 0x0a, 0x06, 0x73, - 0x6f, 0x72, 0x74, 0x42, 0x79, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x72, 0x65, - 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, - 0x65, 0x61, 0x72, 0x63, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x53, 0x6f, 0x72, - 0x74, 0x52, 0x06, 0x73, 0x6f, 0x72, 0x74, 0x42, 0x79, 0x12, 0x40, 0x0a, 0x05, 0x66, 0x61, 0x63, - 0x65, 0x74, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x65, 0x61, 0x72, - 0x63, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x46, 0x61, 0x63, 0x65, 0x74, 0x45, - 0x6e, 0x74, 0x72, 0x79, 0x52, 0x05, 0x66, 0x61, 0x63, 0x65, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x66, - 0x69, 0x65, 0x6c, 0x64, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x66, 0x69, 0x65, - 0x6c, 0x64, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x78, 0x70, 0x6c, 0x61, 0x69, 0x6e, 0x18, 0x09, - 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x78, 0x70, 0x6c, 0x61, 0x69, 0x6e, 0x12, 0x1d, 0x0a, - 0x0a, 0x69, 0x73, 0x5f, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, - 0x08, 0x52, 0x09, 0x69, 0x73, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x12, 0x12, 0x0a, 0x04, - 0x70, 0x61, 0x67, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x70, 0x61, 0x67, 0x65, - 0x12, 0x1e, 0x0a, 0x0a, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x0c, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, - 0x1a, 0x30, 0x0a, 0x04, 0x53, 0x6f, 0x72, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x66, 0x69, 0x65, 0x6c, - 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x12, 0x12, - 0x0a, 0x04, 0x64, 0x65, 0x73, 0x63, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x04, 0x64, 0x65, - 0x73, 0x63, 0x1a, 0x33, 0x0a, 0x05, 0x46, 0x61, 0x63, 0x65, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x66, - 0x69, 0x65, 0x6c, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x66, 0x69, 0x65, 0x6c, - 0x64, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x1a, 0x5f, 0x0a, 0x0a, 0x46, 0x61, 0x63, 0x65, 0x74, - 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x3b, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, - 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x46, 0x61, 0x63, 0x65, 0x74, 0x52, 0x05, 0x76, - 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xea, 0x04, 0x0a, 0x16, 0x52, 0x65, 0x73, - 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x2b, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x45, 0x72, - 0x72, 0x6f, 0x72, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, - 0x12, 0x27, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, - 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, - 0x65, 0x4b, 0x65, 0x79, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x31, 0x0a, 0x07, 0x72, 0x65, 0x73, - 0x75, 0x6c, 0x74, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x72, 0x65, 0x73, - 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x54, 0x61, - 0x62, 0x6c, 0x65, 0x52, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x12, 0x1d, 0x0a, 0x0a, - 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x68, 0x69, 0x74, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x09, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x48, 0x69, 0x74, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x71, - 0x75, 0x65, 0x72, 0x79, 0x5f, 0x63, 0x6f, 0x73, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x01, 0x52, - 0x09, 0x71, 0x75, 0x65, 0x72, 0x79, 0x43, 0x6f, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x61, - 0x78, 0x5f, 0x73, 0x63, 0x6f, 0x72, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x01, 0x52, 0x08, 0x6d, - 0x61, 0x78, 0x53, 0x63, 0x6f, 0x72, 0x65, 0x12, 0x41, 0x0a, 0x05, 0x66, 0x61, 0x63, 0x65, 0x74, - 0x18, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, - 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x46, 0x61, 0x63, 0x65, 0x74, 0x45, 0x6e, - 0x74, 0x72, 0x79, 0x52, 0x05, 0x66, 0x61, 0x63, 0x65, 0x74, 0x1a, 0x8f, 0x01, 0x0a, 0x05, 0x46, - 0x61, 0x63, 0x65, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x05, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, - 0x74, 0x61, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, - 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6e, 0x67, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x07, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6e, 0x67, 0x12, 0x40, 0x0a, 0x05, 0x74, 0x65, - 0x72, 0x6d, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x72, 0x65, 0x73, 0x6f, - 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x65, 0x61, - 0x72, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x54, 0x65, 0x72, 0x6d, - 0x46, 0x61, 0x63, 0x65, 0x74, 0x52, 0x05, 0x74, 0x65, 0x72, 0x6d, 0x73, 0x1a, 0x35, 0x0a, 0x09, - 0x54, 0x65, 0x72, 0x6d, 0x46, 0x61, 0x63, 0x65, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x65, 0x72, - 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x65, 0x72, 0x6d, 0x12, 0x14, 0x0a, - 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x63, 0x6f, - 0x75, 0x6e, 0x74, 0x1a, 0x60, 0x0a, 0x0a, 0x46, 0x61, 0x63, 0x65, 0x74, 0x45, 0x6e, 0x74, 0x72, - 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, - 0x6b, 0x65, 0x79, 0x12, 0x3c, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, - 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x46, 0x61, 0x63, 0x65, 0x74, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, - 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x85, 0x01, 0x0a, 0x19, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, - 0x6e, 0x61, 0x67, 0x65, 0x64, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x12, 0x26, 0x0a, 0x0f, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x70, 0x61, 0x67, 0x65, - 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6e, 0x65, - 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x1c, 0x0a, 0x09, 0x6e, - 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, - 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6b, 0x69, 0x6e, - 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x12, 0x0e, 0x0a, - 0x02, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0xd4, 0x02, - 0x0a, 0x1a, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x64, 0x4f, 0x62, 0x6a, - 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3f, 0x0a, 0x05, - 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x72, 0x65, - 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, 0x6e, 0x61, 0x67, - 0x65, 0x64, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x2e, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x12, 0x26, 0x0a, - 0x0f, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, - 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x2b, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x03, + 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x56, 0x32, 0x52, + 0x0e, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x56, 0x32, 0x22, + 0x2b, 0x0a, 0x06, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x09, 0x0a, 0x05, 0x53, 0x54, 0x4f, + 0x52, 0x45, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x48, 0x49, 0x53, 0x54, 0x4f, 0x52, 0x59, 0x10, + 0x01, 0x12, 0x09, 0x0a, 0x05, 0x54, 0x52, 0x41, 0x53, 0x48, 0x10, 0x02, 0x42, 0x10, 0x0a, 0x0e, + 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x22, 0xf1, + 0x01, 0x0a, 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x2f, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x19, + 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x57, 0x72, 0x61, 0x70, 0x70, 0x65, 0x72, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, + 0x12, 0x26, 0x0a, 0x0f, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, + 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, + 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x29, 0x0a, 0x10, 0x72, 0x65, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x0f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x56, 0x65, 0x72, 0x73, + 0x69, 0x6f, 0x6e, 0x12, 0x30, 0x0a, 0x14, 0x72, 0x65, 0x6d, 0x61, 0x69, 0x6e, 0x69, 0x6e, 0x67, + 0x5f, 0x69, 0x74, 0x65, 0x6d, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x12, 0x72, 0x65, 0x6d, 0x61, 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x49, 0x74, 0x65, 0x6d, + 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x2b, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x05, 0x65, 0x72, 0x72, - 0x6f, 0x72, 0x1a, 0x9f, 0x01, 0x0a, 0x04, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x2d, 0x0a, 0x06, 0x6f, - 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72, 0x65, - 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4b, - 0x65, 0x79, 0x52, 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, - 0x74, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x12, 0x12, - 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x68, 0x61, - 0x73, 0x68, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x04, 0x74, 0x69, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x18, - 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x12, 0x16, 0x0a, 0x06, - 0x66, 0x6f, 0x6c, 0x64, 0x65, 0x72, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x66, 0x6f, - 0x6c, 0x64, 0x65, 0x72, 0x22, 0x5e, 0x0a, 0x1a, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x4d, 0x61, 0x6e, - 0x61, 0x67, 0x65, 0x64, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, - 0x12, 0x12, 0x0a, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, - 0x6b, 0x69, 0x6e, 0x64, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x02, 0x69, 0x64, 0x22, 0x92, 0x02, 0x0a, 0x1b, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x4d, 0x61, - 0x6e, 0x61, 0x67, 0x65, 0x64, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x49, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x01, 0x20, - 0x03, 0x28, 0x0b, 0x32, 0x33, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x43, - 0x6f, 0x75, 0x6e, 0x74, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x64, 0x4f, 0x62, 0x6a, 0x65, 0x63, - 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x12, - 0x2b, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, - 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, - 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x1a, 0x7b, 0x0a, 0x0d, - 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x12, 0x0a, - 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6b, 0x69, 0x6e, - 0x64, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, - 0x64, 0x12, 0x14, 0x0a, 0x05, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x05, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x2e, 0x0a, 0x12, 0x48, 0x65, 0x61, - 0x6c, 0x74, 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, - 0x18, 0x0a, 0x07, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x07, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x22, 0xab, 0x01, 0x0a, 0x13, 0x48, 0x65, - 0x61, 0x6c, 0x74, 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x43, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0e, 0x32, 0x2b, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x48, 0x65, 0x61, - 0x6c, 0x74, 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, - 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x4f, 0x0a, 0x0d, 0x53, 0x65, 0x72, 0x76, 0x69, 0x6e, - 0x67, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, - 0x57, 0x4e, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x53, 0x45, 0x52, 0x56, 0x49, 0x4e, 0x47, 0x10, - 0x01, 0x12, 0x0f, 0x0a, 0x0b, 0x4e, 0x4f, 0x54, 0x5f, 0x53, 0x45, 0x52, 0x56, 0x49, 0x4e, 0x47, - 0x10, 0x02, 0x12, 0x13, 0x0a, 0x0f, 0x53, 0x45, 0x52, 0x56, 0x49, 0x43, 0x45, 0x5f, 0x55, 0x4e, - 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x03, 0x22, 0x87, 0x02, 0x0a, 0x0d, 0x52, 0x65, 0x73, 0x6f, - 0x75, 0x72, 0x63, 0x65, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x12, 0x41, 0x0a, 0x07, 0x63, 0x6f, 0x6c, - 0x75, 0x6d, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x72, 0x65, 0x73, - 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x54, 0x61, - 0x62, 0x6c, 0x65, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x44, 0x65, 0x66, 0x69, 0x6e, 0x69, 0x74, - 0x69, 0x6f, 0x6e, 0x52, 0x07, 0x63, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x73, 0x12, 0x2e, 0x0a, 0x04, - 0x72, 0x6f, 0x77, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x72, 0x65, 0x73, - 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x54, 0x61, - 0x62, 0x6c, 0x65, 0x52, 0x6f, 0x77, 0x52, 0x04, 0x72, 0x6f, 0x77, 0x73, 0x12, 0x26, 0x0a, 0x0f, - 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, - 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x29, 0x0a, 0x10, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, - 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, - 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, - 0x30, 0x0a, 0x14, 0x72, 0x65, 0x6d, 0x61, 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x5f, 0x69, 0x74, 0x65, - 0x6d, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x12, 0x72, - 0x65, 0x6d, 0x61, 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x49, 0x74, 0x65, 0x6d, 0x43, 0x6f, 0x75, 0x6e, - 0x74, 0x22, 0xf1, 0x04, 0x0a, 0x1d, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x54, 0x61, - 0x62, 0x6c, 0x65, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x44, 0x65, 0x66, 0x69, 0x6e, 0x69, 0x74, - 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x46, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x32, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, - 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x43, 0x6f, - 0x6c, 0x75, 0x6d, 0x6e, 0x44, 0x65, 0x66, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x43, - 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, - 0x19, 0x0a, 0x08, 0x69, 0x73, 0x5f, 0x61, 0x72, 0x72, 0x61, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x08, 0x52, 0x07, 0x69, 0x73, 0x41, 0x72, 0x72, 0x61, 0x79, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, - 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x52, 0x0a, 0x0a, - 0x70, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x69, 0x65, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x32, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, - 0x75, 0x72, 0x63, 0x65, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x44, - 0x65, 0x66, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x50, 0x72, 0x6f, 0x70, 0x65, 0x72, - 0x74, 0x69, 0x65, 0x73, 0x52, 0x0a, 0x70, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x69, 0x65, 0x73, - 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x18, 0x06, 0x20, 0x01, - 0x28, 0x05, 0x52, 0x08, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x1a, 0xae, 0x01, 0x0a, - 0x0a, 0x50, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x69, 0x65, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x75, - 0x6e, 0x69, 0x71, 0x75, 0x65, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x08, 0x52, 0x0c, 0x75, 0x6e, 0x69, 0x71, 0x75, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, - 0x12, 0x1b, 0x0a, 0x09, 0x66, 0x72, 0x65, 0x65, 0x5f, 0x74, 0x65, 0x78, 0x74, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x08, 0x52, 0x08, 0x66, 0x72, 0x65, 0x65, 0x54, 0x65, 0x78, 0x74, 0x12, 0x1e, 0x0a, - 0x0a, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x61, 0x62, 0x6c, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x08, 0x52, 0x0a, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x61, 0x62, 0x6c, 0x65, 0x12, 0x19, 0x0a, - 0x08, 0x6e, 0x6f, 0x74, 0x5f, 0x6e, 0x75, 0x6c, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, - 0x07, 0x6e, 0x6f, 0x74, 0x4e, 0x75, 0x6c, 0x6c, 0x12, 0x23, 0x0a, 0x0d, 0x64, 0x65, 0x66, 0x61, - 0x75, 0x6c, 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, - 0x0c, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x95, 0x01, - 0x0a, 0x0a, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x10, 0x0a, 0x0c, - 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x10, 0x00, 0x12, 0x0a, - 0x0a, 0x06, 0x53, 0x54, 0x52, 0x49, 0x4e, 0x47, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x42, 0x4f, - 0x4f, 0x4c, 0x45, 0x41, 0x4e, 0x10, 0x02, 0x12, 0x09, 0x0a, 0x05, 0x49, 0x4e, 0x54, 0x33, 0x32, - 0x10, 0x03, 0x12, 0x09, 0x0a, 0x05, 0x49, 0x4e, 0x54, 0x36, 0x34, 0x10, 0x04, 0x12, 0x09, 0x0a, - 0x05, 0x46, 0x4c, 0x4f, 0x41, 0x54, 0x10, 0x05, 0x12, 0x0a, 0x0a, 0x06, 0x44, 0x4f, 0x55, 0x42, - 0x4c, 0x45, 0x10, 0x06, 0x12, 0x08, 0x0a, 0x04, 0x44, 0x41, 0x54, 0x45, 0x10, 0x07, 0x12, 0x0d, - 0x0a, 0x09, 0x44, 0x41, 0x54, 0x45, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x10, 0x08, 0x12, 0x0a, 0x0a, - 0x06, 0x42, 0x49, 0x4e, 0x41, 0x52, 0x59, 0x10, 0x09, 0x12, 0x0a, 0x0a, 0x06, 0x4f, 0x42, 0x4a, - 0x45, 0x43, 0x54, 0x10, 0x0a, 0x22, 0x94, 0x01, 0x0a, 0x10, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, - 0x63, 0x65, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x6f, 0x77, 0x12, 0x27, 0x0a, 0x03, 0x6b, 0x65, - 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, - 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4b, 0x65, 0x79, 0x52, 0x03, - 0x6b, 0x65, 0x79, 0x12, 0x29, 0x0a, 0x10, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, - 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x72, - 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x14, - 0x0a, 0x05, 0x63, 0x65, 0x6c, 0x6c, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x05, 0x63, - 0x65, 0x6c, 0x6c, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x04, - 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x22, 0x64, 0x0a, 0x0e, - 0x52, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x27, - 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72, 0x65, - 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4b, - 0x65, 0x79, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x29, 0x0a, 0x10, 0x72, 0x65, 0x73, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x0f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, - 0x6f, 0x6e, 0x22, 0x69, 0x0a, 0x0f, 0x52, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x52, 0x65, 0x73, + 0x6f, 0x72, 0x22, 0xb9, 0x01, 0x0a, 0x0c, 0x57, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x69, 0x6e, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x05, 0x73, 0x69, 0x6e, 0x63, 0x65, 0x12, 0x2f, 0x0a, 0x07, 0x6f, 0x70, 0x74, + 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, + 0x73, 0x52, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x2e, 0x0a, 0x13, 0x73, 0x65, + 0x6e, 0x64, 0x5f, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74, + 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x11, 0x73, 0x65, 0x6e, 0x64, 0x49, 0x6e, 0x69, + 0x74, 0x69, 0x61, 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x32, 0x0a, 0x15, 0x61, 0x6c, + 0x6c, 0x6f, 0x77, 0x5f, 0x77, 0x61, 0x74, 0x63, 0x68, 0x5f, 0x62, 0x6f, 0x6f, 0x6b, 0x6d, 0x61, + 0x72, 0x6b, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x13, 0x61, 0x6c, 0x6c, 0x6f, 0x77, + 0x57, 0x61, 0x74, 0x63, 0x68, 0x42, 0x6f, 0x6f, 0x6b, 0x6d, 0x61, 0x72, 0x6b, 0x73, 0x22, 0xdf, + 0x02, 0x0a, 0x0a, 0x57, 0x61, 0x74, 0x63, 0x68, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x1c, 0x0a, + 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x2d, 0x0a, 0x04, 0x74, + 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x19, 0x2e, 0x72, 0x65, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x2e, 0x57, 0x61, 0x74, 0x63, 0x68, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x2e, + 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x39, 0x0a, 0x08, 0x72, 0x65, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x72, + 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x57, 0x61, 0x74, 0x63, 0x68, 0x45, 0x76, 0x65, + 0x6e, 0x74, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x08, 0x72, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x39, 0x0a, 0x08, 0x70, 0x72, 0x65, 0x76, 0x69, 0x6f, 0x75, + 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x2e, 0x57, 0x61, 0x74, 0x63, 0x68, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x2e, 0x52, 0x65, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x08, 0x70, 0x72, 0x65, 0x76, 0x69, 0x6f, 0x75, 0x73, + 0x1a, 0x3a, 0x0a, 0x08, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x18, 0x0a, 0x07, + 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x76, + 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x52, 0x0a, 0x04, + 0x54, 0x79, 0x70, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, + 0x00, 0x12, 0x09, 0x0a, 0x05, 0x41, 0x44, 0x44, 0x45, 0x44, 0x10, 0x01, 0x12, 0x0c, 0x0a, 0x08, + 0x4d, 0x4f, 0x44, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x02, 0x12, 0x0b, 0x0a, 0x07, 0x44, 0x45, + 0x4c, 0x45, 0x54, 0x45, 0x44, 0x10, 0x03, 0x12, 0x0c, 0x0a, 0x08, 0x42, 0x4f, 0x4f, 0x4b, 0x4d, + 0x41, 0x52, 0x4b, 0x10, 0x04, 0x12, 0x09, 0x0a, 0x05, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x05, + 0x22, 0xd7, 0x01, 0x0a, 0x0b, 0x42, 0x75, 0x6c, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x27, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, + 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x4b, 0x65, 0x79, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x34, 0x0a, 0x06, 0x61, 0x63, 0x74, + 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1c, 0x2e, 0x72, 0x65, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x2e, 0x42, 0x75, 0x6c, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x2e, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, + 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, + 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x66, 0x6f, 0x6c, 0x64, 0x65, 0x72, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x66, 0x6f, 0x6c, 0x64, 0x65, 0x72, 0x22, 0x3b, 0x0a, + 0x06, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, + 0x57, 0x4e, 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05, 0x41, 0x44, 0x44, 0x45, 0x44, 0x10, 0x01, 0x12, + 0x0c, 0x0a, 0x08, 0x4d, 0x4f, 0x44, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x02, 0x12, 0x0b, 0x0a, + 0x07, 0x44, 0x45, 0x4c, 0x45, 0x54, 0x45, 0x44, 0x10, 0x03, 0x22, 0xda, 0x04, 0x0a, 0x0c, 0x42, + 0x75, 0x6c, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2b, 0x0a, 0x05, 0x65, + 0x72, 0x72, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x65, 0x73, 0x75, 0x6c, + 0x74, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x1c, 0x0a, 0x09, 0x70, 0x72, 0x6f, 0x63, + 0x65, 0x73, 0x73, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x70, 0x72, 0x6f, + 0x63, 0x65, 0x73, 0x73, 0x65, 0x64, 0x12, 0x38, 0x0a, 0x07, 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72, + 0x79, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x2e, 0x42, 0x75, 0x6c, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, + 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x52, 0x07, 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, + 0x12, 0x3b, 0x0a, 0x08, 0x72, 0x65, 0x6a, 0x65, 0x63, 0x74, 0x65, 0x64, 0x18, 0x04, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x42, 0x75, + 0x6c, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x52, 0x65, 0x6a, 0x65, 0x63, + 0x74, 0x65, 0x64, 0x52, 0x08, 0x72, 0x65, 0x6a, 0x65, 0x63, 0x74, 0x65, 0x64, 0x1a, 0x86, 0x02, + 0x0a, 0x07, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x12, 0x1c, 0x0a, 0x09, 0x6e, 0x61, 0x6d, + 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6e, 0x61, + 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x67, 0x72, 0x6f, 0x75, 0x70, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x12, 0x1a, 0x0a, + 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6f, 0x75, + 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, + 0x18, 0x0a, 0x07, 0x68, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x07, 0x68, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x12, 0x29, 0x0a, 0x10, 0x72, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x0f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x56, 0x65, 0x72, + 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x25, 0x0a, 0x0e, 0x70, 0x72, 0x65, 0x76, 0x69, 0x6f, 0x75, 0x73, + 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x70, 0x72, + 0x65, 0x76, 0x69, 0x6f, 0x75, 0x73, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x29, 0x0a, 0x10, 0x70, + 0x72, 0x65, 0x76, 0x69, 0x6f, 0x75, 0x73, 0x5f, 0x68, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x18, + 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x70, 0x72, 0x65, 0x76, 0x69, 0x6f, 0x75, 0x73, 0x48, + 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x1a, 0x7f, 0x0a, 0x08, 0x52, 0x65, 0x6a, 0x65, 0x63, 0x74, + 0x65, 0x64, 0x12, 0x27, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x15, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x4b, 0x65, 0x79, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x34, 0x0a, 0x06, 0x61, + 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1c, 0x2e, 0x72, 0x65, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x42, 0x75, 0x6c, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x2e, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x62, 0x0a, 0x14, 0x52, 0x65, 0x73, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x1c, 0x0a, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x14, 0x0a, + 0x05, 0x6b, 0x69, 0x6e, 0x64, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x6b, 0x69, + 0x6e, 0x64, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x66, 0x6f, 0x6c, 0x64, 0x65, 0x72, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x06, 0x66, 0x6f, 0x6c, 0x64, 0x65, 0x72, 0x22, 0xd2, 0x01, 0x0a, 0x15, + 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2b, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x05, 0x65, 0x72, 0x72, - 0x6f, 0x72, 0x12, 0x29, 0x0a, 0x10, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x76, - 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x72, 0x65, - 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0xd3, 0x01, - 0x0a, 0x0e, 0x50, 0x75, 0x74, 0x42, 0x6c, 0x6f, 0x62, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x12, 0x31, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, - 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4b, 0x65, 0x79, 0x52, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x12, 0x37, 0x0a, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x0e, 0x32, 0x1f, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x50, - 0x75, 0x74, 0x42, 0x6c, 0x6f, 0x62, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x4d, 0x65, - 0x74, 0x68, 0x6f, 0x64, 0x52, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x21, 0x0a, 0x0c, - 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, - 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, - 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x1c, 0x0a, 0x06, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, - 0x08, 0x0a, 0x04, 0x47, 0x52, 0x50, 0x43, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x48, 0x54, 0x54, - 0x50, 0x10, 0x01, 0x22, 0xc1, 0x01, 0x0a, 0x0f, 0x50, 0x75, 0x74, 0x42, 0x6c, 0x6f, 0x62, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2b, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, - 0x65, 0x2e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x05, 0x65, - 0x72, 0x72, 0x6f, 0x72, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x03, 0x75, 0x69, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x69, 0x7a, 0x65, - 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x12, 0x12, 0x0a, 0x04, - 0x68, 0x61, 0x73, 0x68, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, - 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x69, 0x6d, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x06, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x08, 0x6d, 0x69, 0x6d, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x18, 0x0a, - 0x07, 0x63, 0x68, 0x61, 0x72, 0x73, 0x65, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, - 0x63, 0x68, 0x61, 0x72, 0x73, 0x65, 0x74, 0x22, 0xaa, 0x01, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x42, - 0x6c, 0x6f, 0x62, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x31, 0x0a, 0x08, 0x72, 0x65, - 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72, - 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, - 0x4b, 0x65, 0x79, 0x52, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x29, 0x0a, - 0x10, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, - 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, - 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x28, 0x0a, 0x10, 0x6d, 0x75, 0x73, 0x74, - 0x5f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x08, 0x52, 0x0e, 0x6d, 0x75, 0x73, 0x74, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x42, 0x79, 0x74, - 0x65, 0x73, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x03, 0x75, 0x69, 0x64, 0x22, 0x89, 0x01, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x62, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2b, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, - 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, - 0x63, 0x65, 0x2e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x05, - 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x65, - 0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, - 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, - 0x6c, 0x75, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, - 0x2a, 0x33, 0x0a, 0x14, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x56, 0x65, 0x72, 0x73, - 0x69, 0x6f, 0x6e, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x12, 0x10, 0x0a, 0x0c, 0x4e, 0x6f, 0x74, 0x4f, - 0x6c, 0x64, 0x65, 0x72, 0x54, 0x68, 0x61, 0x6e, 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05, 0x45, 0x78, - 0x61, 0x63, 0x74, 0x10, 0x01, 0x32, 0xad, 0x03, 0x0a, 0x0d, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, - 0x63, 0x65, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x12, 0x35, 0x0a, 0x04, 0x52, 0x65, 0x61, 0x64, 0x12, - 0x15, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x61, 0x64, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, - 0x65, 0x2e, 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3b, - 0x0a, 0x06, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x12, 0x17, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x18, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x43, 0x72, 0x65, - 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3b, 0x0a, 0x06, 0x55, - 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x17, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, - 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, - 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3b, 0x0a, 0x06, 0x44, 0x65, 0x6c, 0x65, - 0x74, 0x65, 0x12, 0x17, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x44, 0x65, - 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x72, 0x65, - 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3e, 0x0a, 0x07, 0x52, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, - 0x12, 0x18, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x74, - 0x6f, 0x72, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x72, 0x65, 0x73, - 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x35, 0x0a, 0x04, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x15, 0x2e, - 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, - 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x37, 0x0a, 0x05, - 0x57, 0x61, 0x74, 0x63, 0x68, 0x12, 0x16, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, - 0x2e, 0x57, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, - 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x57, 0x61, 0x74, 0x63, 0x68, 0x45, 0x76, - 0x65, 0x6e, 0x74, 0x30, 0x01, 0x32, 0x4b, 0x0a, 0x09, 0x42, 0x75, 0x6c, 0x6b, 0x53, 0x74, 0x6f, - 0x72, 0x65, 0x12, 0x3e, 0x0a, 0x0b, 0x42, 0x75, 0x6c, 0x6b, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, - 0x73, 0x12, 0x15, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x42, 0x75, 0x6c, - 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x2e, 0x42, 0x75, 0x6c, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x28, 0x01, 0x32, 0xa9, 0x01, 0x0a, 0x0d, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, - 0x6e, 0x64, 0x65, 0x78, 0x12, 0x4b, 0x0a, 0x06, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x12, 0x1f, + 0x6f, 0x72, 0x12, 0x3b, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x25, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x73, 0x1a, + 0x4f, 0x0a, 0x05, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x67, 0x72, 0x6f, 0x75, + 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x12, 0x1a, + 0x0a, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6f, + 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, + 0x22, 0x8e, 0x05, 0x0a, 0x15, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x65, 0x61, + 0x72, 0x63, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2f, 0x0a, 0x07, 0x6f, 0x70, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72, 0x65, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4f, 0x70, 0x74, 0x69, 0x6f, + 0x6e, 0x73, 0x52, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x33, 0x0a, 0x09, 0x66, + 0x65, 0x64, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, - 0x63, 0x65, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x20, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, + 0x63, 0x65, 0x4b, 0x65, 0x79, 0x52, 0x09, 0x66, 0x65, 0x64, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, + 0x12, 0x14, 0x0a, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x16, 0x0a, 0x06, + 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x6f, 0x66, + 0x66, 0x73, 0x65, 0x74, 0x12, 0x3c, 0x0a, 0x06, 0x73, 0x6f, 0x72, 0x74, 0x42, 0x79, 0x18, 0x06, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, + 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x53, 0x6f, 0x72, 0x74, 0x52, 0x06, 0x73, 0x6f, 0x72, 0x74, + 0x42, 0x79, 0x12, 0x40, 0x0a, 0x05, 0x66, 0x61, 0x63, 0x65, 0x74, 0x18, 0x07, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x2a, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x2e, 0x46, 0x61, 0x63, 0x65, 0x74, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x05, 0x66, + 0x61, 0x63, 0x65, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x18, 0x08, + 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x12, 0x18, 0x0a, 0x07, + 0x65, 0x78, 0x70, 0x6c, 0x61, 0x69, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, + 0x78, 0x70, 0x6c, 0x61, 0x69, 0x6e, 0x12, 0x1d, 0x0a, 0x0a, 0x69, 0x73, 0x5f, 0x64, 0x65, 0x6c, + 0x65, 0x74, 0x65, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x69, 0x73, 0x44, 0x65, + 0x6c, 0x65, 0x74, 0x65, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x67, 0x65, 0x18, 0x0b, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x04, 0x70, 0x61, 0x67, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x70, 0x65, 0x72, + 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x70, + 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x1a, 0x30, 0x0a, 0x04, 0x53, 0x6f, 0x72, + 0x74, 0x12, 0x14, 0x0a, 0x05, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x05, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x65, 0x73, 0x63, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x04, 0x64, 0x65, 0x73, 0x63, 0x1a, 0x33, 0x0a, 0x05, 0x46, + 0x61, 0x63, 0x65, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x05, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, + 0x6d, 0x69, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, + 0x1a, 0x5f, 0x0a, 0x0a, 0x46, 0x61, 0x63, 0x65, 0x74, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, + 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, + 0x12, 0x3b, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x25, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x2e, 0x46, 0x61, 0x63, 0x65, 0x74, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, + 0x01, 0x22, 0xea, 0x04, 0x0a, 0x16, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x65, + 0x61, 0x72, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2b, 0x0a, 0x05, + 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72, 0x65, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x65, 0x73, 0x75, + 0x6c, 0x74, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x27, 0x0a, 0x03, 0x6b, 0x65, 0x79, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4b, 0x65, 0x79, 0x52, 0x03, 0x6b, + 0x65, 0x79, 0x12, 0x31, 0x0a, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, + 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x07, 0x72, 0x65, + 0x73, 0x75, 0x6c, 0x74, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x68, + 0x69, 0x74, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x74, 0x6f, 0x74, 0x61, 0x6c, + 0x48, 0x69, 0x74, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x71, 0x75, 0x65, 0x72, 0x79, 0x5f, 0x63, 0x6f, + 0x73, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x01, 0x52, 0x09, 0x71, 0x75, 0x65, 0x72, 0x79, 0x43, + 0x6f, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x61, 0x78, 0x5f, 0x73, 0x63, 0x6f, 0x72, 0x65, + 0x18, 0x06, 0x20, 0x01, 0x28, 0x01, 0x52, 0x08, 0x6d, 0x61, 0x78, 0x53, 0x63, 0x6f, 0x72, 0x65, + 0x12, 0x41, 0x0a, 0x05, 0x66, 0x61, 0x63, 0x65, 0x74, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x2b, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x4b, 0x0a, 0x08, 0x47, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x1e, 0x2e, - 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, - 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, - 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, - 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xd9, - 0x01, 0x0a, 0x12, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x64, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, - 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x62, 0x0a, 0x13, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x4d, 0x61, - 0x6e, 0x61, 0x67, 0x65, 0x64, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x12, 0x24, 0x2e, 0x72, - 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x4d, 0x61, 0x6e, - 0x61, 0x67, 0x65, 0x64, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x25, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x43, 0x6f, - 0x75, 0x6e, 0x74, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x64, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, - 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5f, 0x0a, 0x12, 0x4c, 0x69, 0x73, - 0x74, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x64, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x12, - 0x23, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4d, - 0x61, 0x6e, 0x61, 0x67, 0x65, 0x64, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, + 0x65, 0x2e, 0x46, 0x61, 0x63, 0x65, 0x74, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x05, 0x66, 0x61, + 0x63, 0x65, 0x74, 0x1a, 0x8f, 0x01, 0x0a, 0x05, 0x46, 0x61, 0x63, 0x65, 0x74, 0x12, 0x14, 0x0a, + 0x05, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x66, 0x69, + 0x65, 0x6c, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x69, 0x73, + 0x73, 0x69, 0x6e, 0x67, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x6d, 0x69, 0x73, 0x73, + 0x69, 0x6e, 0x67, 0x12, 0x40, 0x0a, 0x05, 0x74, 0x65, 0x72, 0x6d, 0x73, 0x18, 0x04, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x54, 0x65, 0x72, 0x6d, 0x46, 0x61, 0x63, 0x65, 0x74, 0x52, 0x05, + 0x74, 0x65, 0x72, 0x6d, 0x73, 0x1a, 0x35, 0x0a, 0x09, 0x54, 0x65, 0x72, 0x6d, 0x46, 0x61, 0x63, + 0x65, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x65, 0x72, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x04, 0x74, 0x65, 0x72, 0x6d, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x1a, 0x60, 0x0a, 0x0a, + 0x46, 0x61, 0x63, 0x65, 0x74, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, + 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x3c, 0x0a, 0x05, + 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x72, 0x65, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, + 0x65, 0x61, 0x72, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x46, 0x61, + 0x63, 0x65, 0x74, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x85, + 0x01, 0x0a, 0x19, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x64, 0x4f, 0x62, + 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x26, 0x0a, 0x0f, + 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, + 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x1c, 0x0a, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, + 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, + 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0xd4, 0x02, 0x0a, 0x1a, 0x4c, 0x69, 0x73, 0x74, 0x4d, + 0x61, 0x6e, 0x61, 0x67, 0x65, 0x64, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3f, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x01, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x64, 0x4f, 0x62, 0x6a, 0x65, 0x63, - 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0x8b, 0x01, 0x0a, 0x09, 0x42, - 0x6c, 0x6f, 0x62, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x12, 0x3e, 0x0a, 0x07, 0x50, 0x75, 0x74, 0x42, - 0x6c, 0x6f, 0x62, 0x12, 0x18, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x50, - 0x75, 0x74, 0x42, 0x6c, 0x6f, 0x62, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, - 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x50, 0x75, 0x74, 0x42, 0x6c, 0x6f, 0x62, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3e, 0x0a, 0x07, 0x47, 0x65, 0x74, 0x42, - 0x6c, 0x6f, 0x62, 0x12, 0x18, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x47, - 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x62, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, - 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x62, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0x57, 0x0a, 0x0b, 0x44, 0x69, 0x61, 0x67, - 0x6e, 0x6f, 0x73, 0x74, 0x69, 0x63, 0x73, 0x12, 0x48, 0x0a, 0x09, 0x49, 0x73, 0x48, 0x65, 0x61, - 0x6c, 0x74, 0x68, 0x79, 0x12, 0x1c, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, - 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x48, 0x65, - 0x61, 0x6c, 0x74, 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x42, 0x39, 0x5a, 0x37, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, - 0x67, 0x72, 0x61, 0x66, 0x61, 0x6e, 0x61, 0x2f, 0x67, 0x72, 0x61, 0x66, 0x61, 0x6e, 0x61, 0x2f, - 0x70, 0x6b, 0x67, 0x2f, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2f, 0x75, 0x6e, 0x69, 0x66, - 0x69, 0x65, 0x64, 0x2f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x62, 0x06, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x33, + 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x49, 0x74, 0x65, 0x6d, 0x52, + 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x70, + 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x2b, + 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, + 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x65, + 0x73, 0x75, 0x6c, 0x74, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x1a, 0x9f, 0x01, 0x0a, 0x04, + 0x49, 0x74, 0x65, 0x6d, 0x12, 0x2d, 0x0a, 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, + 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4b, 0x65, 0x79, 0x52, 0x06, 0x6f, 0x62, 0x6a, + 0x65, 0x63, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, 0x12, 0x12, 0x0a, 0x04, 0x74, + 0x69, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x74, 0x69, 0x6d, 0x65, 0x12, + 0x14, 0x0a, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, + 0x74, 0x69, 0x74, 0x6c, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x66, 0x6f, 0x6c, 0x64, 0x65, 0x72, 0x18, + 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x66, 0x6f, 0x6c, 0x64, 0x65, 0x72, 0x22, 0x5e, 0x0a, + 0x1a, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x64, 0x4f, 0x62, 0x6a, + 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x6e, + 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, + 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6b, 0x69, 0x6e, + 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x12, 0x0e, 0x0a, + 0x02, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0x92, 0x02, + 0x0a, 0x1b, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x64, 0x4f, 0x62, + 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x49, 0x0a, + 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x33, 0x2e, 0x72, + 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x4d, 0x61, 0x6e, + 0x61, 0x67, 0x65, 0x64, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x43, 0x6f, 0x75, 0x6e, + 0x74, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x12, 0x2b, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, + 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x2e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x05, + 0x65, 0x72, 0x72, 0x6f, 0x72, 0x1a, 0x7b, 0x0a, 0x0d, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x67, 0x72, + 0x6f, 0x75, 0x70, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x67, 0x72, 0x6f, 0x75, 0x70, + 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x14, 0x0a, 0x05, + 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x63, 0x6f, 0x75, + 0x6e, 0x74, 0x22, 0x2e, 0x0a, 0x12, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x43, 0x68, 0x65, 0x63, + 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x65, 0x72, 0x76, + 0x69, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x65, 0x72, 0x76, 0x69, + 0x63, 0x65, 0x22, 0xab, 0x01, 0x0a, 0x13, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x43, 0x68, 0x65, + 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x43, 0x0a, 0x06, 0x73, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2b, 0x2e, 0x72, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x43, 0x68, 0x65, 0x63, + 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x6e, + 0x67, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, + 0x4f, 0x0a, 0x0d, 0x53, 0x65, 0x72, 0x76, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, + 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x0b, 0x0a, + 0x07, 0x53, 0x45, 0x52, 0x56, 0x49, 0x4e, 0x47, 0x10, 0x01, 0x12, 0x0f, 0x0a, 0x0b, 0x4e, 0x4f, + 0x54, 0x5f, 0x53, 0x45, 0x52, 0x56, 0x49, 0x4e, 0x47, 0x10, 0x02, 0x12, 0x13, 0x0a, 0x0f, 0x53, + 0x45, 0x52, 0x56, 0x49, 0x43, 0x45, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x03, + 0x22, 0x87, 0x02, 0x0a, 0x0d, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x54, 0x61, 0x62, + 0x6c, 0x65, 0x12, 0x41, 0x0a, 0x07, 0x63, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x73, 0x18, 0x01, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, + 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x43, 0x6f, 0x6c, 0x75, + 0x6d, 0x6e, 0x44, 0x65, 0x66, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x07, 0x63, 0x6f, + 0x6c, 0x75, 0x6d, 0x6e, 0x73, 0x12, 0x2e, 0x0a, 0x04, 0x72, 0x6f, 0x77, 0x73, 0x18, 0x02, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, + 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x6f, 0x77, 0x52, + 0x04, 0x72, 0x6f, 0x77, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x70, 0x61, + 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, + 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x29, 0x0a, + 0x10, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, + 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x30, 0x0a, 0x14, 0x72, 0x65, 0x6d, 0x61, + 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x5f, 0x69, 0x74, 0x65, 0x6d, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x12, 0x72, 0x65, 0x6d, 0x61, 0x69, 0x6e, 0x69, 0x6e, + 0x67, 0x49, 0x74, 0x65, 0x6d, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0xf1, 0x04, 0x0a, 0x1d, 0x52, + 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x43, 0x6f, 0x6c, 0x75, + 0x6d, 0x6e, 0x44, 0x65, 0x66, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, + 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, + 0x12, 0x46, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x32, + 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x44, 0x65, 0x66, + 0x69, 0x6e, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x54, 0x79, + 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x69, 0x73, 0x5f, 0x61, + 0x72, 0x72, 0x61, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x69, 0x73, 0x41, 0x72, + 0x72, 0x61, 0x79, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, + 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, + 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x52, 0x0a, 0x0a, 0x70, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, + 0x69, 0x65, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x32, 0x2e, 0x72, 0x65, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x54, 0x61, 0x62, + 0x6c, 0x65, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x44, 0x65, 0x66, 0x69, 0x6e, 0x69, 0x74, 0x69, + 0x6f, 0x6e, 0x2e, 0x50, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x69, 0x65, 0x73, 0x52, 0x0a, 0x70, + 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x69, 0x65, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x69, + 0x6f, 0x72, 0x69, 0x74, 0x79, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x72, 0x69, + 0x6f, 0x72, 0x69, 0x74, 0x79, 0x1a, 0xae, 0x01, 0x0a, 0x0a, 0x50, 0x72, 0x6f, 0x70, 0x65, 0x72, + 0x74, 0x69, 0x65, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x75, 0x6e, 0x69, 0x71, 0x75, 0x65, 0x5f, 0x76, + 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x75, 0x6e, 0x69, + 0x71, 0x75, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x66, 0x72, 0x65, + 0x65, 0x5f, 0x74, 0x65, 0x78, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x66, 0x72, + 0x65, 0x65, 0x54, 0x65, 0x78, 0x74, 0x12, 0x1e, 0x0a, 0x0a, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, + 0x61, 0x62, 0x6c, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x66, 0x69, 0x6c, 0x74, + 0x65, 0x72, 0x61, 0x62, 0x6c, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x6e, 0x6f, 0x74, 0x5f, 0x6e, 0x75, + 0x6c, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x6e, 0x6f, 0x74, 0x4e, 0x75, 0x6c, + 0x6c, 0x12, 0x23, 0x0a, 0x0d, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x76, 0x61, 0x6c, + 0x75, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0c, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, + 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x95, 0x01, 0x0a, 0x0a, 0x43, 0x6f, 0x6c, 0x75, 0x6d, + 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x10, 0x0a, 0x0c, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, + 0x5f, 0x54, 0x59, 0x50, 0x45, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x53, 0x54, 0x52, 0x49, 0x4e, + 0x47, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x42, 0x4f, 0x4f, 0x4c, 0x45, 0x41, 0x4e, 0x10, 0x02, + 0x12, 0x09, 0x0a, 0x05, 0x49, 0x4e, 0x54, 0x33, 0x32, 0x10, 0x03, 0x12, 0x09, 0x0a, 0x05, 0x49, + 0x4e, 0x54, 0x36, 0x34, 0x10, 0x04, 0x12, 0x09, 0x0a, 0x05, 0x46, 0x4c, 0x4f, 0x41, 0x54, 0x10, + 0x05, 0x12, 0x0a, 0x0a, 0x06, 0x44, 0x4f, 0x55, 0x42, 0x4c, 0x45, 0x10, 0x06, 0x12, 0x08, 0x0a, + 0x04, 0x44, 0x41, 0x54, 0x45, 0x10, 0x07, 0x12, 0x0d, 0x0a, 0x09, 0x44, 0x41, 0x54, 0x45, 0x5f, + 0x54, 0x49, 0x4d, 0x45, 0x10, 0x08, 0x12, 0x0a, 0x0a, 0x06, 0x42, 0x49, 0x4e, 0x41, 0x52, 0x59, + 0x10, 0x09, 0x12, 0x0a, 0x0a, 0x06, 0x4f, 0x42, 0x4a, 0x45, 0x43, 0x54, 0x10, 0x0a, 0x22, 0x94, + 0x01, 0x0a, 0x10, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x54, 0x61, 0x62, 0x6c, 0x65, + 0x52, 0x6f, 0x77, 0x12, 0x27, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x15, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x4b, 0x65, 0x79, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x29, 0x0a, 0x10, + 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x65, 0x6c, 0x6c, 0x73, + 0x18, 0x03, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x05, 0x63, 0x65, 0x6c, 0x6c, 0x73, 0x12, 0x16, 0x0a, + 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x6f, + 0x62, 0x6a, 0x65, 0x63, 0x74, 0x22, 0xd3, 0x01, 0x0a, 0x0e, 0x50, 0x75, 0x74, 0x42, 0x6c, 0x6f, + 0x62, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x31, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4b, 0x65, + 0x79, 0x52, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x37, 0x0a, 0x06, 0x6d, + 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1f, 0x2e, 0x72, 0x65, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x50, 0x75, 0x74, 0x42, 0x6c, 0x6f, 0x62, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x52, 0x06, 0x6d, 0x65, + 0x74, 0x68, 0x6f, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x5f, + 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x74, + 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x1c, 0x0a, + 0x06, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x08, 0x0a, 0x04, 0x47, 0x52, 0x50, 0x43, 0x10, + 0x00, 0x12, 0x08, 0x0a, 0x04, 0x48, 0x54, 0x54, 0x50, 0x10, 0x01, 0x22, 0xc1, 0x01, 0x0a, 0x0f, + 0x50, 0x75, 0x74, 0x42, 0x6c, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x2b, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, + 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, + 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x10, 0x0a, 0x03, + 0x75, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x69, 0x64, 0x12, 0x10, + 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, + 0x12, 0x12, 0x0a, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, + 0x73, 0x69, 0x7a, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, 0x18, 0x05, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x69, 0x6d, 0x65, + 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6d, 0x69, 0x6d, + 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x68, 0x61, 0x72, 0x73, 0x65, 0x74, + 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x68, 0x61, 0x72, 0x73, 0x65, 0x74, 0x22, + 0xaa, 0x01, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x62, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x31, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, + 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4b, 0x65, 0x79, 0x52, 0x08, 0x72, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x29, 0x0a, 0x10, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x0f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, + 0x12, 0x28, 0x0a, 0x10, 0x6d, 0x75, 0x73, 0x74, 0x5f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x5f, 0x62, + 0x79, 0x74, 0x65, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0e, 0x6d, 0x75, 0x73, 0x74, + 0x50, 0x72, 0x6f, 0x78, 0x79, 0x42, 0x79, 0x74, 0x65, 0x73, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x69, + 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x69, 0x64, 0x22, 0x89, 0x01, 0x0a, + 0x0f, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x2b, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x15, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x45, 0x72, 0x72, 0x6f, 0x72, + 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x10, 0x0a, + 0x03, 0x75, 0x72, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x12, + 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x54, 0x79, + 0x70, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x2a, 0x49, 0x0a, 0x14, 0x52, 0x65, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x4d, 0x61, 0x74, 0x63, 0x68, + 0x12, 0x1b, 0x0a, 0x17, 0x44, 0x45, 0x50, 0x52, 0x45, 0x43, 0x41, 0x54, 0x45, 0x44, 0x5f, 0x4e, + 0x6f, 0x74, 0x4f, 0x6c, 0x64, 0x65, 0x72, 0x54, 0x68, 0x61, 0x6e, 0x10, 0x00, 0x12, 0x14, 0x0a, + 0x10, 0x44, 0x45, 0x50, 0x52, 0x45, 0x43, 0x41, 0x54, 0x45, 0x44, 0x5f, 0x45, 0x78, 0x61, 0x63, + 0x74, 0x10, 0x01, 0x2a, 0x4d, 0x0a, 0x16, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x56, + 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x56, 0x32, 0x12, 0x0b, 0x0a, + 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05, 0x55, 0x6e, + 0x73, 0x65, 0x74, 0x10, 0x01, 0x12, 0x09, 0x0a, 0x05, 0x45, 0x78, 0x61, 0x63, 0x74, 0x10, 0x02, + 0x12, 0x10, 0x0a, 0x0c, 0x4e, 0x6f, 0x74, 0x4f, 0x6c, 0x64, 0x65, 0x72, 0x54, 0x68, 0x61, 0x6e, + 0x10, 0x03, 0x32, 0xed, 0x02, 0x0a, 0x0d, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, + 0x74, 0x6f, 0x72, 0x65, 0x12, 0x35, 0x0a, 0x04, 0x52, 0x65, 0x61, 0x64, 0x12, 0x15, 0x2e, 0x72, + 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, + 0x65, 0x61, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3b, 0x0a, 0x06, 0x43, + 0x72, 0x65, 0x61, 0x74, 0x65, 0x12, 0x17, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, + 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3b, 0x0a, 0x06, 0x55, 0x70, 0x64, 0x61, + 0x74, 0x65, 0x12, 0x17, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x55, 0x70, + 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x72, 0x65, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3b, 0x0a, 0x06, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x12, + 0x17, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, + 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x35, 0x0a, 0x04, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x15, 0x2e, 0x72, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x16, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x4c, 0x69, 0x73, + 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x37, 0x0a, 0x05, 0x57, 0x61, 0x74, + 0x63, 0x68, 0x12, 0x16, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x57, 0x61, + 0x74, 0x63, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x72, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x57, 0x61, 0x74, 0x63, 0x68, 0x45, 0x76, 0x65, 0x6e, 0x74, + 0x30, 0x01, 0x32, 0x4b, 0x0a, 0x09, 0x42, 0x75, 0x6c, 0x6b, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x12, + 0x3e, 0x0a, 0x0b, 0x42, 0x75, 0x6c, 0x6b, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x12, 0x15, + 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x42, 0x75, 0x6c, 0x6b, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x2e, 0x42, 0x75, 0x6c, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x28, 0x01, 0x32, + 0xa9, 0x01, 0x0a, 0x0d, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x6e, 0x64, 0x65, + 0x78, 0x12, 0x4b, 0x0a, 0x06, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x12, 0x1f, 0x2e, 0x72, 0x65, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, + 0x65, 0x61, 0x72, 0x63, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x72, + 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4b, + 0x0a, 0x08, 0x47, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x1e, 0x2e, 0x72, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x74, + 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x72, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x74, + 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xd9, 0x01, 0x0a, 0x12, + 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x64, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x49, 0x6e, 0x64, + 0x65, 0x78, 0x12, 0x62, 0x0a, 0x13, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x4d, 0x61, 0x6e, 0x61, 0x67, + 0x65, 0x64, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x12, 0x24, 0x2e, 0x72, 0x65, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x2e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, + 0x64, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x25, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x43, 0x6f, 0x75, 0x6e, 0x74, + 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x64, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5f, 0x0a, 0x12, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, + 0x6e, 0x61, 0x67, 0x65, 0x64, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x12, 0x23, 0x2e, 0x72, + 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, 0x6e, 0x61, + 0x67, 0x65, 0x64, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x24, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x4c, 0x69, 0x73, + 0x74, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x64, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0x8b, 0x01, 0x0a, 0x09, 0x42, 0x6c, 0x6f, 0x62, + 0x53, 0x74, 0x6f, 0x72, 0x65, 0x12, 0x3e, 0x0a, 0x07, 0x50, 0x75, 0x74, 0x42, 0x6c, 0x6f, 0x62, + 0x12, 0x18, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x50, 0x75, 0x74, 0x42, + 0x6c, 0x6f, 0x62, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x72, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x50, 0x75, 0x74, 0x42, 0x6c, 0x6f, 0x62, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3e, 0x0a, 0x07, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x62, + 0x12, 0x18, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x47, 0x65, 0x74, 0x42, + 0x6c, 0x6f, 0x62, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x72, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x62, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0x57, 0x0a, 0x0b, 0x44, 0x69, 0x61, 0x67, 0x6e, 0x6f, 0x73, + 0x74, 0x69, 0x63, 0x73, 0x12, 0x48, 0x0a, 0x09, 0x49, 0x73, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, + 0x79, 0x12, 0x1c, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x48, 0x65, 0x61, + 0x6c, 0x74, 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x1d, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x48, 0x65, 0x61, 0x6c, 0x74, + 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x39, + 0x5a, 0x37, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x72, 0x61, + 0x66, 0x61, 0x6e, 0x61, 0x2f, 0x67, 0x72, 0x61, 0x66, 0x61, 0x6e, 0x61, 0x2f, 0x70, 0x6b, 0x67, + 0x2f, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2f, 0x75, 0x6e, 0x69, 0x66, 0x69, 0x65, 0x64, + 0x2f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x33, }) var ( @@ -4644,166 +4582,162 @@ func file_resource_proto_rawDescGZIP() []byte { return file_resource_proto_rawDescData } -var file_resource_proto_enumTypes = make([]protoimpl.EnumInfo, 7) -var file_resource_proto_msgTypes = make([]protoimpl.MessageInfo, 53) +var file_resource_proto_enumTypes = make([]protoimpl.EnumInfo, 8) +var file_resource_proto_msgTypes = make([]protoimpl.MessageInfo, 51) var file_resource_proto_goTypes = []any{ (ResourceVersionMatch)(0), // 0: resource.ResourceVersionMatch - (ListRequest_Source)(0), // 1: resource.ListRequest.Source - (WatchEvent_Type)(0), // 2: resource.WatchEvent.Type - (BulkRequest_Action)(0), // 3: resource.BulkRequest.Action - (HealthCheckResponse_ServingStatus)(0), // 4: resource.HealthCheckResponse.ServingStatus - (ResourceTableColumnDefinition_ColumnType)(0), // 5: resource.ResourceTableColumnDefinition.ColumnType - (PutBlobRequest_Method)(0), // 6: resource.PutBlobRequest.Method - (*ResourceKey)(nil), // 7: resource.ResourceKey - (*ResourceWrapper)(nil), // 8: resource.ResourceWrapper - (*ErrorResult)(nil), // 9: resource.ErrorResult - (*ErrorDetails)(nil), // 10: resource.ErrorDetails - (*ErrorCause)(nil), // 11: resource.ErrorCause - (*CreateRequest)(nil), // 12: resource.CreateRequest - (*CreateResponse)(nil), // 13: resource.CreateResponse - (*UpdateRequest)(nil), // 14: resource.UpdateRequest - (*UpdateResponse)(nil), // 15: resource.UpdateResponse - (*DeleteRequest)(nil), // 16: resource.DeleteRequest - (*DeleteResponse)(nil), // 17: resource.DeleteResponse - (*ReadRequest)(nil), // 18: resource.ReadRequest - (*ReadResponse)(nil), // 19: resource.ReadResponse - (*Requirement)(nil), // 20: resource.Requirement - (*ListOptions)(nil), // 21: resource.ListOptions - (*ListRequest)(nil), // 22: resource.ListRequest - (*ListResponse)(nil), // 23: resource.ListResponse - (*WatchRequest)(nil), // 24: resource.WatchRequest - (*WatchEvent)(nil), // 25: resource.WatchEvent - (*BulkRequest)(nil), // 26: resource.BulkRequest - (*BulkResponse)(nil), // 27: resource.BulkResponse - (*ResourceStatsRequest)(nil), // 28: resource.ResourceStatsRequest - (*ResourceStatsResponse)(nil), // 29: resource.ResourceStatsResponse - (*ResourceSearchRequest)(nil), // 30: resource.ResourceSearchRequest - (*ResourceSearchResponse)(nil), // 31: resource.ResourceSearchResponse - (*ListManagedObjectsRequest)(nil), // 32: resource.ListManagedObjectsRequest - (*ListManagedObjectsResponse)(nil), // 33: resource.ListManagedObjectsResponse - (*CountManagedObjectsRequest)(nil), // 34: resource.CountManagedObjectsRequest - (*CountManagedObjectsResponse)(nil), // 35: resource.CountManagedObjectsResponse - (*HealthCheckRequest)(nil), // 36: resource.HealthCheckRequest - (*HealthCheckResponse)(nil), // 37: resource.HealthCheckResponse - (*ResourceTable)(nil), // 38: resource.ResourceTable - (*ResourceTableColumnDefinition)(nil), // 39: resource.ResourceTableColumnDefinition - (*ResourceTableRow)(nil), // 40: resource.ResourceTableRow - (*RestoreRequest)(nil), // 41: resource.RestoreRequest - (*RestoreResponse)(nil), // 42: resource.RestoreResponse - (*PutBlobRequest)(nil), // 43: resource.PutBlobRequest - (*PutBlobResponse)(nil), // 44: resource.PutBlobResponse - (*GetBlobRequest)(nil), // 45: resource.GetBlobRequest - (*GetBlobResponse)(nil), // 46: resource.GetBlobResponse - (*WatchEvent_Resource)(nil), // 47: resource.WatchEvent.Resource - (*BulkResponse_Summary)(nil), // 48: resource.BulkResponse.Summary - (*BulkResponse_Rejected)(nil), // 49: resource.BulkResponse.Rejected - (*ResourceStatsResponse_Stats)(nil), // 50: resource.ResourceStatsResponse.Stats - (*ResourceSearchRequest_Sort)(nil), // 51: resource.ResourceSearchRequest.Sort - (*ResourceSearchRequest_Facet)(nil), // 52: resource.ResourceSearchRequest.Facet - nil, // 53: resource.ResourceSearchRequest.FacetEntry - (*ResourceSearchResponse_Facet)(nil), // 54: resource.ResourceSearchResponse.Facet - (*ResourceSearchResponse_TermFacet)(nil), // 55: resource.ResourceSearchResponse.TermFacet - nil, // 56: resource.ResourceSearchResponse.FacetEntry - (*ListManagedObjectsResponse_Item)(nil), // 57: resource.ListManagedObjectsResponse.Item - (*CountManagedObjectsResponse_ResourceCount)(nil), // 58: resource.CountManagedObjectsResponse.ResourceCount - (*ResourceTableColumnDefinition_Properties)(nil), // 59: resource.ResourceTableColumnDefinition.Properties + (ResourceVersionMatchV2)(0), // 1: resource.ResourceVersionMatchV2 + (ListRequest_Source)(0), // 2: resource.ListRequest.Source + (WatchEvent_Type)(0), // 3: resource.WatchEvent.Type + (BulkRequest_Action)(0), // 4: resource.BulkRequest.Action + (HealthCheckResponse_ServingStatus)(0), // 5: resource.HealthCheckResponse.ServingStatus + (ResourceTableColumnDefinition_ColumnType)(0), // 6: resource.ResourceTableColumnDefinition.ColumnType + (PutBlobRequest_Method)(0), // 7: resource.PutBlobRequest.Method + (*ResourceKey)(nil), // 8: resource.ResourceKey + (*ResourceWrapper)(nil), // 9: resource.ResourceWrapper + (*ErrorResult)(nil), // 10: resource.ErrorResult + (*ErrorDetails)(nil), // 11: resource.ErrorDetails + (*ErrorCause)(nil), // 12: resource.ErrorCause + (*CreateRequest)(nil), // 13: resource.CreateRequest + (*CreateResponse)(nil), // 14: resource.CreateResponse + (*UpdateRequest)(nil), // 15: resource.UpdateRequest + (*UpdateResponse)(nil), // 16: resource.UpdateResponse + (*DeleteRequest)(nil), // 17: resource.DeleteRequest + (*DeleteResponse)(nil), // 18: resource.DeleteResponse + (*ReadRequest)(nil), // 19: resource.ReadRequest + (*ReadResponse)(nil), // 20: resource.ReadResponse + (*Requirement)(nil), // 21: resource.Requirement + (*ListOptions)(nil), // 22: resource.ListOptions + (*ListRequest)(nil), // 23: resource.ListRequest + (*ListResponse)(nil), // 24: resource.ListResponse + (*WatchRequest)(nil), // 25: resource.WatchRequest + (*WatchEvent)(nil), // 26: resource.WatchEvent + (*BulkRequest)(nil), // 27: resource.BulkRequest + (*BulkResponse)(nil), // 28: resource.BulkResponse + (*ResourceStatsRequest)(nil), // 29: resource.ResourceStatsRequest + (*ResourceStatsResponse)(nil), // 30: resource.ResourceStatsResponse + (*ResourceSearchRequest)(nil), // 31: resource.ResourceSearchRequest + (*ResourceSearchResponse)(nil), // 32: resource.ResourceSearchResponse + (*ListManagedObjectsRequest)(nil), // 33: resource.ListManagedObjectsRequest + (*ListManagedObjectsResponse)(nil), // 34: resource.ListManagedObjectsResponse + (*CountManagedObjectsRequest)(nil), // 35: resource.CountManagedObjectsRequest + (*CountManagedObjectsResponse)(nil), // 36: resource.CountManagedObjectsResponse + (*HealthCheckRequest)(nil), // 37: resource.HealthCheckRequest + (*HealthCheckResponse)(nil), // 38: resource.HealthCheckResponse + (*ResourceTable)(nil), // 39: resource.ResourceTable + (*ResourceTableColumnDefinition)(nil), // 40: resource.ResourceTableColumnDefinition + (*ResourceTableRow)(nil), // 41: resource.ResourceTableRow + (*PutBlobRequest)(nil), // 42: resource.PutBlobRequest + (*PutBlobResponse)(nil), // 43: resource.PutBlobResponse + (*GetBlobRequest)(nil), // 44: resource.GetBlobRequest + (*GetBlobResponse)(nil), // 45: resource.GetBlobResponse + (*WatchEvent_Resource)(nil), // 46: resource.WatchEvent.Resource + (*BulkResponse_Summary)(nil), // 47: resource.BulkResponse.Summary + (*BulkResponse_Rejected)(nil), // 48: resource.BulkResponse.Rejected + (*ResourceStatsResponse_Stats)(nil), // 49: resource.ResourceStatsResponse.Stats + (*ResourceSearchRequest_Sort)(nil), // 50: resource.ResourceSearchRequest.Sort + (*ResourceSearchRequest_Facet)(nil), // 51: resource.ResourceSearchRequest.Facet + nil, // 52: resource.ResourceSearchRequest.FacetEntry + (*ResourceSearchResponse_Facet)(nil), // 53: resource.ResourceSearchResponse.Facet + (*ResourceSearchResponse_TermFacet)(nil), // 54: resource.ResourceSearchResponse.TermFacet + nil, // 55: resource.ResourceSearchResponse.FacetEntry + (*ListManagedObjectsResponse_Item)(nil), // 56: resource.ListManagedObjectsResponse.Item + (*CountManagedObjectsResponse_ResourceCount)(nil), // 57: resource.CountManagedObjectsResponse.ResourceCount + (*ResourceTableColumnDefinition_Properties)(nil), // 58: resource.ResourceTableColumnDefinition.Properties } var file_resource_proto_depIdxs = []int32{ - 10, // 0: resource.ErrorResult.details:type_name -> resource.ErrorDetails - 11, // 1: resource.ErrorDetails.causes:type_name -> resource.ErrorCause - 7, // 2: resource.CreateRequest.key:type_name -> resource.ResourceKey - 9, // 3: resource.CreateResponse.error:type_name -> resource.ErrorResult - 7, // 4: resource.UpdateRequest.key:type_name -> resource.ResourceKey - 9, // 5: resource.UpdateResponse.error:type_name -> resource.ErrorResult - 7, // 6: resource.DeleteRequest.key:type_name -> resource.ResourceKey - 9, // 7: resource.DeleteResponse.error:type_name -> resource.ErrorResult - 7, // 8: resource.ReadRequest.key:type_name -> resource.ResourceKey - 9, // 9: resource.ReadResponse.error:type_name -> resource.ErrorResult - 7, // 10: resource.ListOptions.key:type_name -> resource.ResourceKey - 20, // 11: resource.ListOptions.labels:type_name -> resource.Requirement - 20, // 12: resource.ListOptions.fields:type_name -> resource.Requirement + 11, // 0: resource.ErrorResult.details:type_name -> resource.ErrorDetails + 12, // 1: resource.ErrorDetails.causes:type_name -> resource.ErrorCause + 8, // 2: resource.CreateRequest.key:type_name -> resource.ResourceKey + 10, // 3: resource.CreateResponse.error:type_name -> resource.ErrorResult + 8, // 4: resource.UpdateRequest.key:type_name -> resource.ResourceKey + 10, // 5: resource.UpdateResponse.error:type_name -> resource.ErrorResult + 8, // 6: resource.DeleteRequest.key:type_name -> resource.ResourceKey + 10, // 7: resource.DeleteResponse.error:type_name -> resource.ErrorResult + 8, // 8: resource.ReadRequest.key:type_name -> resource.ResourceKey + 10, // 9: resource.ReadResponse.error:type_name -> resource.ErrorResult + 8, // 10: resource.ListOptions.key:type_name -> resource.ResourceKey + 21, // 11: resource.ListOptions.labels:type_name -> resource.Requirement + 21, // 12: resource.ListOptions.fields:type_name -> resource.Requirement 0, // 13: resource.ListRequest.version_match:type_name -> resource.ResourceVersionMatch - 21, // 14: resource.ListRequest.options:type_name -> resource.ListOptions - 1, // 15: resource.ListRequest.source:type_name -> resource.ListRequest.Source - 8, // 16: resource.ListResponse.items:type_name -> resource.ResourceWrapper - 9, // 17: resource.ListResponse.error:type_name -> resource.ErrorResult - 21, // 18: resource.WatchRequest.options:type_name -> resource.ListOptions - 2, // 19: resource.WatchEvent.type:type_name -> resource.WatchEvent.Type - 47, // 20: resource.WatchEvent.resource:type_name -> resource.WatchEvent.Resource - 47, // 21: resource.WatchEvent.previous:type_name -> resource.WatchEvent.Resource - 7, // 22: resource.BulkRequest.key:type_name -> resource.ResourceKey - 3, // 23: resource.BulkRequest.action:type_name -> resource.BulkRequest.Action - 9, // 24: resource.BulkResponse.error:type_name -> resource.ErrorResult - 48, // 25: resource.BulkResponse.summary:type_name -> resource.BulkResponse.Summary - 49, // 26: resource.BulkResponse.rejected:type_name -> resource.BulkResponse.Rejected - 9, // 27: resource.ResourceStatsResponse.error:type_name -> resource.ErrorResult - 50, // 28: resource.ResourceStatsResponse.stats:type_name -> resource.ResourceStatsResponse.Stats - 21, // 29: resource.ResourceSearchRequest.options:type_name -> resource.ListOptions - 7, // 30: resource.ResourceSearchRequest.federated:type_name -> resource.ResourceKey - 51, // 31: resource.ResourceSearchRequest.sortBy:type_name -> resource.ResourceSearchRequest.Sort - 53, // 32: resource.ResourceSearchRequest.facet:type_name -> resource.ResourceSearchRequest.FacetEntry - 9, // 33: resource.ResourceSearchResponse.error:type_name -> resource.ErrorResult - 7, // 34: resource.ResourceSearchResponse.key:type_name -> resource.ResourceKey - 38, // 35: resource.ResourceSearchResponse.results:type_name -> resource.ResourceTable - 56, // 36: resource.ResourceSearchResponse.facet:type_name -> resource.ResourceSearchResponse.FacetEntry - 57, // 37: resource.ListManagedObjectsResponse.items:type_name -> resource.ListManagedObjectsResponse.Item - 9, // 38: resource.ListManagedObjectsResponse.error:type_name -> resource.ErrorResult - 58, // 39: resource.CountManagedObjectsResponse.items:type_name -> resource.CountManagedObjectsResponse.ResourceCount - 9, // 40: resource.CountManagedObjectsResponse.error:type_name -> resource.ErrorResult - 4, // 41: resource.HealthCheckResponse.status:type_name -> resource.HealthCheckResponse.ServingStatus - 39, // 42: resource.ResourceTable.columns:type_name -> resource.ResourceTableColumnDefinition - 40, // 43: resource.ResourceTable.rows:type_name -> resource.ResourceTableRow - 5, // 44: resource.ResourceTableColumnDefinition.type:type_name -> resource.ResourceTableColumnDefinition.ColumnType - 59, // 45: resource.ResourceTableColumnDefinition.properties:type_name -> resource.ResourceTableColumnDefinition.Properties - 7, // 46: resource.ResourceTableRow.key:type_name -> resource.ResourceKey - 7, // 47: resource.RestoreRequest.key:type_name -> resource.ResourceKey - 9, // 48: resource.RestoreResponse.error:type_name -> resource.ErrorResult - 7, // 49: resource.PutBlobRequest.resource:type_name -> resource.ResourceKey - 6, // 50: resource.PutBlobRequest.method:type_name -> resource.PutBlobRequest.Method - 9, // 51: resource.PutBlobResponse.error:type_name -> resource.ErrorResult - 7, // 52: resource.GetBlobRequest.resource:type_name -> resource.ResourceKey - 9, // 53: resource.GetBlobResponse.error:type_name -> resource.ErrorResult - 7, // 54: resource.BulkResponse.Rejected.key:type_name -> resource.ResourceKey - 3, // 55: resource.BulkResponse.Rejected.action:type_name -> resource.BulkRequest.Action - 52, // 56: resource.ResourceSearchRequest.FacetEntry.value:type_name -> resource.ResourceSearchRequest.Facet - 55, // 57: resource.ResourceSearchResponse.Facet.terms:type_name -> resource.ResourceSearchResponse.TermFacet - 54, // 58: resource.ResourceSearchResponse.FacetEntry.value:type_name -> resource.ResourceSearchResponse.Facet - 7, // 59: resource.ListManagedObjectsResponse.Item.object:type_name -> resource.ResourceKey - 18, // 60: resource.ResourceStore.Read:input_type -> resource.ReadRequest - 12, // 61: resource.ResourceStore.Create:input_type -> resource.CreateRequest - 14, // 62: resource.ResourceStore.Update:input_type -> resource.UpdateRequest - 16, // 63: resource.ResourceStore.Delete:input_type -> resource.DeleteRequest - 41, // 64: resource.ResourceStore.Restore:input_type -> resource.RestoreRequest - 22, // 65: resource.ResourceStore.List:input_type -> resource.ListRequest - 24, // 66: resource.ResourceStore.Watch:input_type -> resource.WatchRequest - 26, // 67: resource.BulkStore.BulkProcess:input_type -> resource.BulkRequest - 30, // 68: resource.ResourceIndex.Search:input_type -> resource.ResourceSearchRequest - 28, // 69: resource.ResourceIndex.GetStats:input_type -> resource.ResourceStatsRequest - 34, // 70: resource.ManagedObjectIndex.CountManagedObjects:input_type -> resource.CountManagedObjectsRequest - 32, // 71: resource.ManagedObjectIndex.ListManagedObjects:input_type -> resource.ListManagedObjectsRequest - 43, // 72: resource.BlobStore.PutBlob:input_type -> resource.PutBlobRequest - 45, // 73: resource.BlobStore.GetBlob:input_type -> resource.GetBlobRequest - 36, // 74: resource.Diagnostics.IsHealthy:input_type -> resource.HealthCheckRequest - 19, // 75: resource.ResourceStore.Read:output_type -> resource.ReadResponse - 13, // 76: resource.ResourceStore.Create:output_type -> resource.CreateResponse - 15, // 77: resource.ResourceStore.Update:output_type -> resource.UpdateResponse - 17, // 78: resource.ResourceStore.Delete:output_type -> resource.DeleteResponse - 42, // 79: resource.ResourceStore.Restore:output_type -> resource.RestoreResponse - 23, // 80: resource.ResourceStore.List:output_type -> resource.ListResponse - 25, // 81: resource.ResourceStore.Watch:output_type -> resource.WatchEvent - 27, // 82: resource.BulkStore.BulkProcess:output_type -> resource.BulkResponse - 31, // 83: resource.ResourceIndex.Search:output_type -> resource.ResourceSearchResponse - 29, // 84: resource.ResourceIndex.GetStats:output_type -> resource.ResourceStatsResponse - 35, // 85: resource.ManagedObjectIndex.CountManagedObjects:output_type -> resource.CountManagedObjectsResponse - 33, // 86: resource.ManagedObjectIndex.ListManagedObjects:output_type -> resource.ListManagedObjectsResponse - 44, // 87: resource.BlobStore.PutBlob:output_type -> resource.PutBlobResponse - 46, // 88: resource.BlobStore.GetBlob:output_type -> resource.GetBlobResponse - 37, // 89: resource.Diagnostics.IsHealthy:output_type -> resource.HealthCheckResponse - 75, // [75:90] is the sub-list for method output_type - 60, // [60:75] is the sub-list for method input_type - 60, // [60:60] is the sub-list for extension type_name - 60, // [60:60] is the sub-list for extension extendee - 0, // [0:60] is the sub-list for field type_name + 22, // 14: resource.ListRequest.options:type_name -> resource.ListOptions + 2, // 15: resource.ListRequest.source:type_name -> resource.ListRequest.Source + 1, // 16: resource.ListRequest.version_match_v2:type_name -> resource.ResourceVersionMatchV2 + 9, // 17: resource.ListResponse.items:type_name -> resource.ResourceWrapper + 10, // 18: resource.ListResponse.error:type_name -> resource.ErrorResult + 22, // 19: resource.WatchRequest.options:type_name -> resource.ListOptions + 3, // 20: resource.WatchEvent.type:type_name -> resource.WatchEvent.Type + 46, // 21: resource.WatchEvent.resource:type_name -> resource.WatchEvent.Resource + 46, // 22: resource.WatchEvent.previous:type_name -> resource.WatchEvent.Resource + 8, // 23: resource.BulkRequest.key:type_name -> resource.ResourceKey + 4, // 24: resource.BulkRequest.action:type_name -> resource.BulkRequest.Action + 10, // 25: resource.BulkResponse.error:type_name -> resource.ErrorResult + 47, // 26: resource.BulkResponse.summary:type_name -> resource.BulkResponse.Summary + 48, // 27: resource.BulkResponse.rejected:type_name -> resource.BulkResponse.Rejected + 10, // 28: resource.ResourceStatsResponse.error:type_name -> resource.ErrorResult + 49, // 29: resource.ResourceStatsResponse.stats:type_name -> resource.ResourceStatsResponse.Stats + 22, // 30: resource.ResourceSearchRequest.options:type_name -> resource.ListOptions + 8, // 31: resource.ResourceSearchRequest.federated:type_name -> resource.ResourceKey + 50, // 32: resource.ResourceSearchRequest.sortBy:type_name -> resource.ResourceSearchRequest.Sort + 52, // 33: resource.ResourceSearchRequest.facet:type_name -> resource.ResourceSearchRequest.FacetEntry + 10, // 34: resource.ResourceSearchResponse.error:type_name -> resource.ErrorResult + 8, // 35: resource.ResourceSearchResponse.key:type_name -> resource.ResourceKey + 39, // 36: resource.ResourceSearchResponse.results:type_name -> resource.ResourceTable + 55, // 37: resource.ResourceSearchResponse.facet:type_name -> resource.ResourceSearchResponse.FacetEntry + 56, // 38: resource.ListManagedObjectsResponse.items:type_name -> resource.ListManagedObjectsResponse.Item + 10, // 39: resource.ListManagedObjectsResponse.error:type_name -> resource.ErrorResult + 57, // 40: resource.CountManagedObjectsResponse.items:type_name -> resource.CountManagedObjectsResponse.ResourceCount + 10, // 41: resource.CountManagedObjectsResponse.error:type_name -> resource.ErrorResult + 5, // 42: resource.HealthCheckResponse.status:type_name -> resource.HealthCheckResponse.ServingStatus + 40, // 43: resource.ResourceTable.columns:type_name -> resource.ResourceTableColumnDefinition + 41, // 44: resource.ResourceTable.rows:type_name -> resource.ResourceTableRow + 6, // 45: resource.ResourceTableColumnDefinition.type:type_name -> resource.ResourceTableColumnDefinition.ColumnType + 58, // 46: resource.ResourceTableColumnDefinition.properties:type_name -> resource.ResourceTableColumnDefinition.Properties + 8, // 47: resource.ResourceTableRow.key:type_name -> resource.ResourceKey + 8, // 48: resource.PutBlobRequest.resource:type_name -> resource.ResourceKey + 7, // 49: resource.PutBlobRequest.method:type_name -> resource.PutBlobRequest.Method + 10, // 50: resource.PutBlobResponse.error:type_name -> resource.ErrorResult + 8, // 51: resource.GetBlobRequest.resource:type_name -> resource.ResourceKey + 10, // 52: resource.GetBlobResponse.error:type_name -> resource.ErrorResult + 8, // 53: resource.BulkResponse.Rejected.key:type_name -> resource.ResourceKey + 4, // 54: resource.BulkResponse.Rejected.action:type_name -> resource.BulkRequest.Action + 51, // 55: resource.ResourceSearchRequest.FacetEntry.value:type_name -> resource.ResourceSearchRequest.Facet + 54, // 56: resource.ResourceSearchResponse.Facet.terms:type_name -> resource.ResourceSearchResponse.TermFacet + 53, // 57: resource.ResourceSearchResponse.FacetEntry.value:type_name -> resource.ResourceSearchResponse.Facet + 8, // 58: resource.ListManagedObjectsResponse.Item.object:type_name -> resource.ResourceKey + 19, // 59: resource.ResourceStore.Read:input_type -> resource.ReadRequest + 13, // 60: resource.ResourceStore.Create:input_type -> resource.CreateRequest + 15, // 61: resource.ResourceStore.Update:input_type -> resource.UpdateRequest + 17, // 62: resource.ResourceStore.Delete:input_type -> resource.DeleteRequest + 23, // 63: resource.ResourceStore.List:input_type -> resource.ListRequest + 25, // 64: resource.ResourceStore.Watch:input_type -> resource.WatchRequest + 27, // 65: resource.BulkStore.BulkProcess:input_type -> resource.BulkRequest + 31, // 66: resource.ResourceIndex.Search:input_type -> resource.ResourceSearchRequest + 29, // 67: resource.ResourceIndex.GetStats:input_type -> resource.ResourceStatsRequest + 35, // 68: resource.ManagedObjectIndex.CountManagedObjects:input_type -> resource.CountManagedObjectsRequest + 33, // 69: resource.ManagedObjectIndex.ListManagedObjects:input_type -> resource.ListManagedObjectsRequest + 42, // 70: resource.BlobStore.PutBlob:input_type -> resource.PutBlobRequest + 44, // 71: resource.BlobStore.GetBlob:input_type -> resource.GetBlobRequest + 37, // 72: resource.Diagnostics.IsHealthy:input_type -> resource.HealthCheckRequest + 20, // 73: resource.ResourceStore.Read:output_type -> resource.ReadResponse + 14, // 74: resource.ResourceStore.Create:output_type -> resource.CreateResponse + 16, // 75: resource.ResourceStore.Update:output_type -> resource.UpdateResponse + 18, // 76: resource.ResourceStore.Delete:output_type -> resource.DeleteResponse + 24, // 77: resource.ResourceStore.List:output_type -> resource.ListResponse + 26, // 78: resource.ResourceStore.Watch:output_type -> resource.WatchEvent + 28, // 79: resource.BulkStore.BulkProcess:output_type -> resource.BulkResponse + 32, // 80: resource.ResourceIndex.Search:output_type -> resource.ResourceSearchResponse + 30, // 81: resource.ResourceIndex.GetStats:output_type -> resource.ResourceStatsResponse + 36, // 82: resource.ManagedObjectIndex.CountManagedObjects:output_type -> resource.CountManagedObjectsResponse + 34, // 83: resource.ManagedObjectIndex.ListManagedObjects:output_type -> resource.ListManagedObjectsResponse + 43, // 84: resource.BlobStore.PutBlob:output_type -> resource.PutBlobResponse + 45, // 85: resource.BlobStore.GetBlob:output_type -> resource.GetBlobResponse + 38, // 86: resource.Diagnostics.IsHealthy:output_type -> resource.HealthCheckResponse + 73, // [73:87] is the sub-list for method output_type + 59, // [59:73] is the sub-list for method input_type + 59, // [59:59] is the sub-list for extension type_name + 59, // [59:59] is the sub-list for extension extendee + 0, // [0:59] is the sub-list for field type_name } func init() { file_resource_proto_init() } @@ -4811,13 +4745,14 @@ func file_resource_proto_init() { if File_resource_proto != nil { return } + file_resource_proto_msgTypes[15].OneofWrappers = []any{} type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_resource_proto_rawDesc), len(file_resource_proto_rawDesc)), - NumEnums: 7, - NumMessages: 53, + NumEnums: 8, + NumMessages: 51, NumExtensions: 0, NumServices: 6, }, diff --git a/pkg/storage/unified/resource/resource.proto b/pkg/storage/unified/resource/resource.proto index 830c2a4d890..f5111d90657 100644 --- a/pkg/storage/unified/resource/resource.proto +++ b/pkg/storage/unified/resource/resource.proto @@ -177,8 +177,6 @@ message ReadRequest { // Optionally pick an explicit resource version int64 resource_version = 2; - // Optionally decide to return the latest RV if deleted - bool include_deleted = 3; } message ReadResponse { @@ -220,10 +218,19 @@ message ListOptions { } enum ResourceVersionMatch { - NotOlderThan = 0; - Exact = 1; + // Deprecated, use ResourceVersionMatch V2 + DEPRECATED_NotOlderThan = 0; + DEPRECATED_Exact = 1; } +enum ResourceVersionMatchV2 { + UNKNOWN = 0; + Unset = 1; + Exact = 2; + NotOlderThan = 3; +} + + message ListRequest { enum Source { STORE = 0; // the standard place @@ -238,7 +245,8 @@ message ListRequest { int64 resource_version = 2; // List options - ResourceVersionMatch version_match = 3; + // DEPRECATED - use version_match_v2 + optional ResourceVersionMatch version_match = 3; // Maximum number of items to return // NOTE responses will also be limited by the response payload size @@ -249,6 +257,8 @@ message ListRequest { // Select values from history or trash Source source = 6; + + ResourceVersionMatchV2 version_match_v2 = 7; } message ListResponse { @@ -702,26 +712,6 @@ message ResourceTableRow { bytes object = 4; } -//---------------------------- -// Restore Support -//---------------------------- - -message RestoreRequest { - // Full key must be set - ResourceKey key = 1; - - // The resource version to restore - int64 resource_version = 2; -} - -message RestoreResponse { - // Error details - ErrorResult error = 1; - - // The updated resource version - int64 resource_version = 2; -} - //---------------------------- // Blob Support //---------------------------- @@ -811,7 +801,6 @@ service ResourceStore { rpc Create(CreateRequest) returns (CreateResponse); rpc Update(UpdateRequest) returns (UpdateResponse); rpc Delete(DeleteRequest) returns (DeleteResponse); - rpc Restore(RestoreRequest) returns (RestoreResponse); // The results *may* include values that should not be returned to the user // This will perform best-effort filtering to increase performace. diff --git a/pkg/storage/unified/resource/resource_grpc.pb.go b/pkg/storage/unified/resource/resource_grpc.pb.go index 25c4a937f9c..4186147ef7c 100644 --- a/pkg/storage/unified/resource/resource_grpc.pb.go +++ b/pkg/storage/unified/resource/resource_grpc.pb.go @@ -19,13 +19,12 @@ import ( const _ = grpc.SupportPackageIsVersion8 const ( - ResourceStore_Read_FullMethodName = "/resource.ResourceStore/Read" - ResourceStore_Create_FullMethodName = "/resource.ResourceStore/Create" - ResourceStore_Update_FullMethodName = "/resource.ResourceStore/Update" - ResourceStore_Delete_FullMethodName = "/resource.ResourceStore/Delete" - ResourceStore_Restore_FullMethodName = "/resource.ResourceStore/Restore" - ResourceStore_List_FullMethodName = "/resource.ResourceStore/List" - ResourceStore_Watch_FullMethodName = "/resource.ResourceStore/Watch" + ResourceStore_Read_FullMethodName = "/resource.ResourceStore/Read" + ResourceStore_Create_FullMethodName = "/resource.ResourceStore/Create" + ResourceStore_Update_FullMethodName = "/resource.ResourceStore/Update" + ResourceStore_Delete_FullMethodName = "/resource.ResourceStore/Delete" + ResourceStore_List_FullMethodName = "/resource.ResourceStore/List" + ResourceStore_Watch_FullMethodName = "/resource.ResourceStore/Watch" ) // ResourceStoreClient is the client API for ResourceStore service. @@ -41,7 +40,6 @@ type ResourceStoreClient interface { Create(ctx context.Context, in *CreateRequest, opts ...grpc.CallOption) (*CreateResponse, error) Update(ctx context.Context, in *UpdateRequest, opts ...grpc.CallOption) (*UpdateResponse, error) Delete(ctx context.Context, in *DeleteRequest, opts ...grpc.CallOption) (*DeleteResponse, error) - Restore(ctx context.Context, in *RestoreRequest, opts ...grpc.CallOption) (*RestoreResponse, error) // The results *may* include values that should not be returned to the user // This will perform best-effort filtering to increase performace. // NOTE: storage.Interface is ultimatly responsible for the final filtering @@ -100,16 +98,6 @@ func (c *resourceStoreClient) Delete(ctx context.Context, in *DeleteRequest, opt return out, nil } -func (c *resourceStoreClient) Restore(ctx context.Context, in *RestoreRequest, opts ...grpc.CallOption) (*RestoreResponse, error) { - cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) - out := new(RestoreResponse) - err := c.cc.Invoke(ctx, ResourceStore_Restore_FullMethodName, in, out, cOpts...) - if err != nil { - return nil, err - } - return out, nil -} - func (c *resourceStoreClient) List(ctx context.Context, in *ListRequest, opts ...grpc.CallOption) (*ListResponse, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(ListResponse) @@ -166,7 +154,6 @@ type ResourceStoreServer interface { Create(context.Context, *CreateRequest) (*CreateResponse, error) Update(context.Context, *UpdateRequest) (*UpdateResponse, error) Delete(context.Context, *DeleteRequest) (*DeleteResponse, error) - Restore(context.Context, *RestoreRequest) (*RestoreResponse, error) // The results *may* include values that should not be returned to the user // This will perform best-effort filtering to increase performace. // NOTE: storage.Interface is ultimatly responsible for the final filtering @@ -193,9 +180,6 @@ func (UnimplementedResourceStoreServer) Update(context.Context, *UpdateRequest) func (UnimplementedResourceStoreServer) Delete(context.Context, *DeleteRequest) (*DeleteResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method Delete not implemented") } -func (UnimplementedResourceStoreServer) Restore(context.Context, *RestoreRequest) (*RestoreResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method Restore not implemented") -} func (UnimplementedResourceStoreServer) List(context.Context, *ListRequest) (*ListResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method List not implemented") } @@ -286,24 +270,6 @@ func _ResourceStore_Delete_Handler(srv interface{}, ctx context.Context, dec fun return interceptor(ctx, in, info, handler) } -func _ResourceStore_Restore_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(RestoreRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(ResourceStoreServer).Restore(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: ResourceStore_Restore_FullMethodName, - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(ResourceStoreServer).Restore(ctx, req.(*RestoreRequest)) - } - return interceptor(ctx, in, info, handler) -} - func _ResourceStore_List_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(ListRequest) if err := dec(in); err != nil { @@ -366,10 +332,6 @@ var ResourceStore_ServiceDesc = grpc.ServiceDesc{ MethodName: "Delete", Handler: _ResourceStore_Delete_Handler, }, - { - MethodName: "Restore", - Handler: _ResourceStore_Restore_Handler, - }, { MethodName: "List", Handler: _ResourceStore_List_Handler, diff --git a/pkg/storage/unified/resource/server.go b/pkg/storage/unified/resource/server.go index 0a983de75aa..0509c938382 100644 --- a/pkg/storage/unified/resource/server.go +++ b/pkg/storage/unified/resource/server.go @@ -10,14 +10,12 @@ import ( "sync/atomic" "time" - "github.com/google/uuid" "github.com/prometheus/client_golang/prometheus" "go.opentelemetry.io/otel/trace" "go.opentelemetry.io/otel/trace/noop" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/types" claims "github.com/grafana/authlib/types" "github.com/grafana/grafana/pkg/apimachinery/utils" @@ -393,6 +391,7 @@ func (s *server) newEvent(ctx context.Context, user claims.AuthInfo, key *Resour if oldValue == nil { event.Type = WatchEvent_ADDED } else { + event.Type = WatchEvent_MODIFIED check.Verb = utils.VerbUpdate temp := &unstructured.Unstructured{} @@ -404,13 +403,6 @@ func (s *server) newEvent(ctx context.Context, user claims.AuthInfo, key *Resour if err != nil { return nil, AsErrorResult(err) } - - // restores will restore with a different k8s uid - if event.ObjectOld.GetUID() != obj.GetUID() { - event.Type = WatchEvent_ADDED - } else { - event.Type = WatchEvent_MODIFIED - } } if key.Namespace != obj.GetNamespace() { @@ -761,10 +753,11 @@ func (s *server) List(ctx context.Context, req *ListRequest) (*ListResponse, err rsp.Items = append(rsp.Items, item) if len(rsp.Items) >= int(req.Limit) || pageBytes >= maxPageBytes { t := iter.ContinueToken() - if req.Source == ListRequest_HISTORY { - // history lists in desc order, so the continue token takes the - // final RV in the list, and then will start from there in the next page, - // rather than the lists first RV + if req.Source == ListRequest_HISTORY || req.Source == ListRequest_TRASH { + // For history lists, we need to use the current RV in the continue token + // to ensure consistent pagination. The order depends on VersionMatch: + // - NotOlderThan: ascending order (oldest to newest) + // - Unset: descending order (newest to oldest) t = iter.ContinueTokenWithCurrentRV() } if iter.Next() { @@ -792,126 +785,6 @@ func (s *server) List(ctx context.Context, req *ListRequest) (*ListResponse, err return rsp, err } -func (s *server) Restore(ctx context.Context, req *RestoreRequest) (*RestoreResponse, error) { - ctx, span := s.tracer.Start(ctx, "storage_server.List") - defer span.End() - - // check that the user has access - user, ok := claims.AuthInfoFrom(ctx) - if !ok || user == nil { - return &RestoreResponse{ - Error: &ErrorResult{ - Message: "no user found in context", - Code: http.StatusUnauthorized, - }}, nil - } - - if err := s.Init(ctx); err != nil { - return nil, err - } - - checker, err := s.access.Compile(ctx, user, claims.ListRequest{ - Group: req.Key.Group, - Resource: req.Key.Resource, - Namespace: req.Key.Namespace, - Verb: utils.VerbGet, - }) - if err != nil { - return &RestoreResponse{Error: AsErrorResult(err)}, nil - } - if checker == nil { - return &RestoreResponse{Error: &ErrorResult{ - Code: http.StatusForbidden, - }}, nil - } - - // get the asked for resource version to restore - readRsp, err := s.Read(ctx, &ReadRequest{ - Key: req.Key, - ResourceVersion: req.ResourceVersion, - IncludeDeleted: true, - }) - if err != nil || readRsp == nil || readRsp.Error != nil { - return &RestoreResponse{ - Error: &ErrorResult{ - Code: http.StatusNotFound, - Message: fmt.Sprintf("could not find old resource: %s", readRsp.Error.Message), - }, - }, nil - } - - // generate a new k8s UID when restoring. The name will remain the same - // (for dashboards, this will be the dashboard uid), but since controllers - // will see this as a create event, we do not want the same k8s UID, or - // there may be unintended behavior - newUid := types.UID(uuid.NewString()) - tmp := &unstructured.Unstructured{} - err = tmp.UnmarshalJSON(readRsp.Value) - if err != nil { - return &RestoreResponse{ - Error: &ErrorResult{ - Code: http.StatusNotFound, - Message: fmt.Sprintf("could not unmarhsal: %s", err.Error()), - }, - }, nil - } - obj, err := utils.MetaAccessor(tmp) - if err != nil { - return &RestoreResponse{ - Error: &ErrorResult{ - Code: http.StatusNotFound, - Message: fmt.Sprintf("could not get object: %s", err.Error()), - }, - }, nil - } - obj.SetUID(newUid) - - rtObj, ok := obj.GetRuntimeObject() - if !ok { - return &RestoreResponse{ - Error: &ErrorResult{ - Code: http.StatusNotFound, - Message: "could not get runtime object", - }, - }, nil - } - - newObj, err := json.Marshal(rtObj) - if err != nil { - return &RestoreResponse{ - Error: &ErrorResult{ - Code: http.StatusNotFound, - Message: fmt.Sprintf("could not marshal object: %s", err.Error()), - }, - }, nil - } - - // finally, send to the backend to create & update the history of the restored object - event, errRes := s.newEvent(ctx, user, req.Key, newObj, readRsp.Value) - if errRes != nil { - return &RestoreResponse{ - Error: &ErrorResult{ - Code: http.StatusInternalServerError, - Message: fmt.Sprintf("could not create restore resource event: %s", errRes.Message), - }, - }, nil - } - rv, err := s.backend.WriteEvent(ctx, *event) - if err != nil { - return &RestoreResponse{ - Error: &ErrorResult{ - Code: http.StatusInternalServerError, - Message: fmt.Sprintf("could not restore resource: %s", err.Error()), - }, - }, nil - } - - return &RestoreResponse{ - Error: nil, - ResourceVersion: rv, - }, nil -} - func (s *server) initWatcher() error { var err error s.broadcaster, err = NewBroadcaster(s.ctx, func(out chan<- *WrittenEvent) error { diff --git a/pkg/storage/unified/resource/server_test.go b/pkg/storage/unified/resource/server_test.go index 3e204717040..bb42d73b12b 100644 --- a/pkg/storage/unified/resource/server_test.go +++ b/pkg/storage/unified/resource/server_test.go @@ -233,79 +233,4 @@ func TestSimpleServer(t *testing.T) { ResourceVersion: created.ResourceVersion}) require.ErrorIs(t, err, ErrOptimisticLockingFailed) }) - - t.Run("playlist restore", func(t *testing.T) { - uid := "zzz" - raw := []byte(`{ - "apiVersion": "playlist.grafana.app/v0alpha1", - "kind": "Playlist", - "metadata": { - "name": "fdgsv37qslr0ga", - "namespace": "default", - "uid": "` + uid + `", - "annotations": { - "grafana.app/repoName": "elsewhere", - "grafana.app/repoPath": "path/to/item", - "grafana.app/repoTimestamp": "2024-02-02T00:00:00Z" - } - }, - "spec": { - "title": "hello", - "interval": "5m", - "items": [ - { - "type": "dashboard_by_uid", - "value": "vmie2cmWz" - } - ] - } - }`) - - key := &ResourceKey{ - Group: "playlist.grafana.app", - Resource: "rrrr", - Namespace: "default", - Name: "fdgsv37qslr0ga", - } - - // create - created, err := server.Create(ctx, &CreateRequest{ - Value: raw, - Key: key, - }) - require.NoError(t, err) - - // make sure it exists - found, err := server.Read(ctx, &ReadRequest{Key: key}) - require.NoError(t, err) - require.Nil(t, found.Error) - fmt.Println(found.ResourceVersion) - - // delete it - deleted, err := server.Delete(ctx, &DeleteRequest{Key: key, ResourceVersion: created.ResourceVersion}) - require.NoError(t, err) - require.True(t, deleted.ResourceVersion > created.ResourceVersion) - - // restore it - restored, err := server.Restore(ctx, &RestoreRequest{ - Key: key, - ResourceVersion: found.ResourceVersion, - }) - require.NoError(t, err) - require.Nil(t, restored.Error) - require.True(t, restored.ResourceVersion > deleted.ResourceVersion) - - // ensure it exists now - found, err = server.Read(ctx, &ReadRequest{Key: key}) - require.NoError(t, err) - require.Nil(t, found.Error) - require.Equal(t, restored.ResourceVersion, found.ResourceVersion) - foundUnstructured := &unstructured.Unstructured{} - err = foundUnstructured.UnmarshalJSON(found.Value) - require.NoError(t, err) - foundObj, err := utils.MetaAccessor(foundUnstructured) - require.NoError(t, err) - // the UID should be different now - require.NotEqual(t, uid, string(foundObj.GetUID())) - }) } diff --git a/pkg/storage/unified/search/bleve.go b/pkg/storage/unified/search/bleve.go index 4976449f548..e99df0870e3 100644 --- a/pkg/storage/unified/search/bleve.go +++ b/pkg/storage/unified/search/bleve.go @@ -159,7 +159,7 @@ func (b *bleveBackend) BuildIndex(ctx context.Context, var index bleve.Index build := true - mapper, err := getBleveMappings(fields) + mapper, err := GetBleveMappings(fields) if err != nil { return nil, err } @@ -660,8 +660,14 @@ func (b *bleveIndex) toBleveSearchRequest(ctx context.Context, req *resource.Res } } - // Add a text query - if req.Query != "" && req.Query != "*" { + if len(req.Query) > 1 && strings.Contains(req.Query, "*") { + // wildcard query is expensive - should be used with caution + wildcard := bleve.NewWildcardQuery(req.Query) + queries = append(queries, wildcard) + } + + if req.Query != "" && !strings.Contains(req.Query, "*") { + // Add a text query searchrequest.Fields = append(searchrequest.Fields, resource.SEARCH_FIELD_SCORE) // There are multiple ways to match the query string to documents. The following queries are ordered by priority: @@ -789,6 +795,11 @@ var textSortFields = map[string]string{ const lowerCase = "phrase" +// termField fields to use termQuery for filtering +var termFields = []string{ + resource.SEARCH_FIELD_TITLE, +} + // Convert a "requirement" into a bleve query func requirementQuery(req *resource.Requirement, prefix string) (query.Query, *resource.ErrorResult) { switch selection.Operator(req.Operator) { @@ -797,16 +808,14 @@ func requirementQuery(req *resource.Requirement, prefix string) (query.Query, *r return query.NewMatchAllQuery(), nil } - if len(req.Values[0]) == 1 { - q := query.NewMatchQuery(filterValue(req.Key, req.Values[0])) - q.FieldVal = prefix + req.Key - return q, nil + if len(req.Values) == 1 { + filter := filterValue(req.Key, req.Values[0]) + return newQuery(req.Key, filter, prefix), nil } conjuncts := []query.Query{} for _, v := range req.Values { - q := query.NewMatchQuery(filterValue(req.Key, v)) - q.FieldVal = prefix + req.Key + q := newQuery(req.Key, filterValue(req.Key, v), prefix) conjuncts = append(conjuncts, q) } @@ -822,15 +831,13 @@ func requirementQuery(req *resource.Requirement, prefix string) (query.Query, *r return query.NewMatchAllQuery(), nil } if len(req.Values) == 1 { - q := query.NewMatchQuery(filterValue(req.Key, req.Values[0])) - q.FieldVal = prefix + req.Key + q := newQuery(req.Key, filterValue(req.Key, req.Values[0]), prefix) return q, nil } disjuncts := []query.Query{} for _, v := range req.Values { - q := query.NewMatchQuery(filterValue(req.Key, v)) - q.FieldVal = prefix + req.Key + q := newQuery(req.Key, filterValue(req.Key, v), prefix) disjuncts = append(disjuncts, q) } @@ -841,7 +848,8 @@ func requirementQuery(req *resource.Requirement, prefix string) (query.Query, *r var mustNotQueries []query.Query for _, value := range req.Values { - mustNotQueries = append(mustNotQueries, bleve.NewMatchQuery(filterValue(req.Key, value))) + q := newQuery(req.Key, filterValue(req.Key, value), prefix) + mustNotQueries = append(mustNotQueries, q) } boolQuery.AddMustNot(mustNotQueries...) @@ -856,6 +864,55 @@ func requirementQuery(req *resource.Requirement, prefix string) (query.Query, *r ) } +// newQuery will create a query that will match the value or the tokens of the value +func newQuery(key string, value string, prefix string) query.Query { + if value == "*" { + return bleve.NewMatchAllQuery() + } + if strings.Contains(value, "*") { + // wildcard query is expensive - should be used with caution + return bleve.NewWildcardQuery(value) + } + delimiter, ok := hasTerms(value) + if slices.Contains(termFields, key) && ok { + return newTermsQuery(key, value, delimiter, prefix) + } + q := bleve.NewMatchQuery(value) + q.SetField(prefix + key) + return q +} + +// newTermsQuery will create a query that will match on term or tokens +func newTermsQuery(key string, value string, delimiter string, prefix string) query.Query { + tokens := strings.Split(value, delimiter) + // won't match with ending space + value = strings.TrimSuffix(value, " ") + + q := bleve.NewTermQuery(value) + q.SetField(prefix + key) + + cq := newMatchAllTokensQuery(tokens, key, prefix) + return bleve.NewDisjunctionQuery(q, cq) +} + +// newMatchAllTokensQuery will create a query that will match on all tokens +func newMatchAllTokensQuery(tokens []string, key string, prefix string) query.Query { + cq := bleve.NewConjunctionQuery() + for _, token := range tokens { + _, ok := hasTerms(token) + if ok { + tq := bleve.NewTermQuery(token) + tq.SetField(prefix + key) + cq.AddQuery(tq) + continue + } + mq := bleve.NewMatchQuery(token) + mq.SetField(prefix + key) + cq.AddQuery(mq) + } + return cq +} + // filterValue will convert the value to lower case if the field is a phrase field func filterValue(field string, v string) string { if strings.HasSuffix(field, lowerCase) { @@ -1068,3 +1125,20 @@ func (q *permissionScopedQuery) Searcher(ctx context.Context, i index.IndexReade return filteringSearcher, nil } + +// hasTerms - any value that will be split into multiple tokens +var hasTerms = func(v string) (string, bool) { + for _, c := range TermCharacters { + if strings.Contains(v, c) { + return c, true + } + } + return "", false +} + +// TermCharacters characters that will be used to determine if a value is split into tokens +var TermCharacters = []string{ + " ", "-", "_", ".", ",", ":", ";", "?", "!", "@", "#", "$", "%", "^", "&", "*", "(", ")", "+", + "=", "{", "}", "[", "]", "|", "\\", "/", "<", ">", "~", "`", + "'", "\"", +} diff --git a/pkg/storage/unified/search/bleve_mappings.go b/pkg/storage/unified/search/bleve_mappings.go index c9afce91a09..3a8c4e75420 100644 --- a/pkg/storage/unified/search/bleve_mappings.go +++ b/pkg/storage/unified/search/bleve_mappings.go @@ -9,7 +9,7 @@ import ( "github.com/grafana/grafana/pkg/storage/unified/resource" ) -func getBleveMappings(fields resource.SearchableDocumentFields) (mapping.IndexMapping, error) { +func GetBleveMappings(fields resource.SearchableDocumentFields) (mapping.IndexMapping, error) { mapper := bleve.NewIndexMapping() err := RegisterCustomAnalyzers(mapper) @@ -31,22 +31,22 @@ func getBleveDocMappings(_ resource.SearchableDocumentFields) *mapping.DocumentM } mapper.AddFieldMappingsAt(resource.SEARCH_FIELD_NAME, nameMapping) + // for sorting by title full phrase + titlePhraseMapping := bleve.NewKeywordFieldMapping() + titlePhraseMapping.Store = false // already stored in title + mapper.AddFieldMappingsAt(resource.SEARCH_FIELD_TITLE_PHRASE, titlePhraseMapping) + // for searching by title - uses an edge ngram token filter titleSearchMapping := bleve.NewTextFieldMapping() titleSearchMapping.Analyzer = TITLE_ANALYZER titleSearchMapping.Store = false // already stored in title - mapper.AddFieldMappingsAt(resource.SEARCH_FIELD_TITLE_NGRAM, titleSearchMapping) // mapping for title to search on words/tokens larger than the ngram size titleWordMapping := bleve.NewTextFieldMapping() titleWordMapping.Analyzer = standard.Name titleWordMapping.Store = true - mapper.AddFieldMappingsAt(resource.SEARCH_FIELD_TITLE, titleWordMapping) - - // for filtering/sorting by title full phrase - titlePhraseMapping := bleve.NewKeywordFieldMapping() - titleSearchMapping.Store = false // already stored in title - mapper.AddFieldMappingsAt(resource.SEARCH_FIELD_TITLE_PHRASE, titlePhraseMapping) + // NOTE: this causes 3 title fields in the response + mapper.AddFieldMappingsAt(resource.SEARCH_FIELD_TITLE, titleWordMapping, titleSearchMapping, titlePhraseMapping) descriptionMapping := &mapping.FieldMapping{ Name: resource.SEARCH_FIELD_DESCRIPTION, diff --git a/pkg/storage/unified/search/bleve_mappings_test.go b/pkg/storage/unified/search/bleve_mappings_test.go index 9c1c6c7f8ad..3b8027ee06e 100644 --- a/pkg/storage/unified/search/bleve_mappings_test.go +++ b/pkg/storage/unified/search/bleve_mappings_test.go @@ -1,4 +1,4 @@ -package search +package search_test import ( "fmt" @@ -9,10 +9,11 @@ import ( "github.com/grafana/grafana/pkg/apimachinery/utils" "github.com/grafana/grafana/pkg/storage/unified/resource" + "github.com/grafana/grafana/pkg/storage/unified/search" ) func TestDocumentMapping(t *testing.T) { - mappings, err := getBleveMappings(nil) + mappings, err := search.GetBleveMappings(nil) require.NoError(t, err) data := resource.IndexableDocument{ Title: "title", @@ -48,5 +49,5 @@ func TestDocumentMapping(t *testing.T) { fmt.Printf("DOC: fields %d\n", len(doc.Fields)) fmt.Printf("DOC: size %d\n", doc.Size()) - require.Equal(t, 16, len(doc.Fields)) + require.Equal(t, 17, len(doc.Fields)) } diff --git a/pkg/storage/unified/search/bleve_performance_test.go b/pkg/storage/unified/search/bleve_performance_test.go new file mode 100644 index 00000000000..2d645235259 --- /dev/null +++ b/pkg/storage/unified/search/bleve_performance_test.go @@ -0,0 +1,131 @@ +package search_test + +import ( + "context" + "fmt" + "os" + "runtime" + "testing" + "time" + + "github.com/grafana/grafana/pkg/storage/unified/resource" + "github.com/stretchr/testify/require" +) + +func setupIndex() (resource.ResourceIndex, string) { + // size := 1000000 // TODO: 200k documents standard size? + size := 200000 + // batchSize := 1000 slower 8s (for 200k documents) - 34s (for 1M documents) + // batchSize := 10000 // faster 5s (for 200k documents) - 27s (for 1M documents) + batchSize := 100000 // fasterer 3.5s (for 200k documents) - 27s (for 1M documents) + writer := newTestWriter(size, batchSize) + return newTestDashboardsIndex(nil, 1, int64(size), int64(batchSize), writer) +} + +const maxAllowedTime = 20 * time.Millisecond // Reasonable (can vary per env) performance threshold per query (e.g., 20ms) +const maxAllowedAllocMB = 1 // 1MB memory per operation +const maxAllowedAlloc = maxAllowedAllocMB * 1024 * 1024 +const verbose = false + +// BenchmarkBleveQuery measures the time, mem, cpu to execute a search query +// changes the the indexer settings can cause unforeseen performance issues ( for example: using wildcard queries ) +// this will fail if the stats exceed the "normal" thresholds +func BenchmarkBleveQuery(b *testing.B) { + var memStatsStart runtime.MemStats + var memStatsAfterIndex runtime.MemStats + runtime.ReadMemStats(&memStatsStart) + + testIndex, testIndexDir := setupIndex() + defer func() { + err := os.RemoveAll(testIndexDir) + if err != nil { + fmt.Printf("Error removing index directory: %v\n", err) + } + }() + + runtime.ReadMemStats(&memStatsAfterIndex) + + allocDiff := memStatsAfterIndex.Alloc - memStatsStart.Alloc + + logVerbose(fmt.Sprintf("Memory allocated for index: %d bytes", allocDiff)) + + searchRequest := newQueryByTitle("name99999") + + b.ResetTimer() // Reset timer before benchmarking + b.ReportAllocs() // Track memory allocations + + for i := 0; i < b.N; i++ { + start := time.Now() // Start timer + var memStatsBefore, memStatsAfter runtime.MemStats + runtime.ReadMemStats(&memStatsBefore) + + _, err := testIndex.Search(context.Background(), nil, searchRequest, nil) + + elapsed := time.Since(start) // Calculate elapsed time + runtime.ReadMemStats(&memStatsAfter) + allocDiff := (memStatsAfter.Alloc - memStatsBefore.Alloc) + if memStatsAfter.Alloc < memStatsBefore.Alloc { + // This can happen due to memory being freed after the search operation + allocDiff = 0 // don't care if it goes down + } + + logVerbose(fmt.Sprintf("Memory allocated for query: %d bytes", allocDiff)) + + require.NoError(b, err) + + // Fail if query takes longer than maxAllowedTime + if elapsed > maxAllowedTime { + b.Fatalf("Query too slow: %v (limit: %v)", elapsed, maxAllowedTime) + } + // Check memory allocation limit + if allocDiff > maxAllowedAlloc { + b.Fatalf("Excessive memory usage: %d mb (limit: %d mb)", allocDiff, maxAllowedAllocMB) + } + } +} + +func newTestWriter(size int, batchSize int) IndexWriter { + key := &resource.ResourceKey{ + Namespace: "default", + Group: "dashboard.grafana.app", + Resource: "dashboards", + } + + return func(index resource.ResourceIndex) (int64, error) { + total := time.Now() + start := time.Now() + for i := range size { + name := fmt.Sprintf("name%d", i) + err := index.Write(&resource.IndexableDocument{ + RV: int64(i), + Name: name, + Key: &resource.ResourceKey{ + Name: name, + Namespace: key.Namespace, + Group: key.Group, + Resource: key.Resource, + }, + Title: name + "-title", + }) + if err != nil { + return 0, err + } + // show progress for every batch + if i%batchSize == 0 && verbose { + fmt.Printf("Indexed %d documents\n", i) + end := time.Now() + fmt.Printf("Time taken for indexing batch: %s\n", end.Sub(start)) + start = time.Now() + } + } + end := time.Now() + logVerbose(fmt.Sprintf("Indexed %d documents in %s", size, end.Sub(total))) + return 0, nil + } +} + +func logVerbose(msg string) { + if verbose { + fmt.Println(msg) + } +} diff --git a/pkg/storage/unified/search/bleve_search_test.go b/pkg/storage/unified/search/bleve_search_test.go index 29287de97e4..ae1782c61a2 100644 --- a/pkg/storage/unified/search/bleve_search_test.go +++ b/pkg/storage/unified/search/bleve_search_test.go @@ -1,4 +1,4 @@ -package search +package search_test import ( "context" @@ -16,8 +16,11 @@ import ( "github.com/grafana/grafana/pkg/services/store/kind/dashboard" "github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/storage/unified/resource" + "github.com/grafana/grafana/pkg/storage/unified/search" ) +const threshold = 9999 + func TestCanSearchByTitle(t *testing.T) { key := &resource.ResourceKey{ Namespace: "default", @@ -26,7 +29,7 @@ func TestCanSearchByTitle(t *testing.T) { } t.Run("when query is empty, sort documents by title instead of search score", func(t *testing.T) { - index := newTestDashboardsIndex(t) + index, _ := newTestDashboardsIndex(t, threshold, 2, 2, noop) err := index.Write(&resource.IndexableDocument{ RV: 1, Name: "name1", @@ -53,7 +56,7 @@ func TestCanSearchByTitle(t *testing.T) { require.NoError(t, err) // search for phrase - query := newQuery("") + query := newTestQuery("") res, err := index.Search(context.Background(), nil, query, nil) require.NoError(t, err) require.Equal(t, int64(2), res.TotalHits) @@ -61,7 +64,7 @@ func TestCanSearchByTitle(t *testing.T) { }) t.Run("will boost phrase match query over match query results", func(t *testing.T) { - index := newTestDashboardsIndex(t) + index, _ := newTestDashboardsIndex(t, threshold, 2, 2, noop) err := index.Write(&resource.IndexableDocument{ RV: 1, Name: "name1", @@ -88,7 +91,7 @@ func TestCanSearchByTitle(t *testing.T) { require.NoError(t, err) // search for phrase - query := newQuery("want hello") + query := newTestQuery("want hello") res, err := index.Search(context.Background(), nil, query, nil) require.NoError(t, err) require.Equal(t, int64(2), res.TotalHits) @@ -96,7 +99,7 @@ func TestCanSearchByTitle(t *testing.T) { }) t.Run("will prioritize matches", func(t *testing.T) { - index := newTestDashboardsIndex(t) + index, _ := newTestDashboardsIndex(t, threshold, 2, 2, noop) err := index.Write(&resource.IndexableDocument{ RV: 1, Name: "name1", @@ -122,7 +125,7 @@ func TestCanSearchByTitle(t *testing.T) { }) require.NoError(t, err) - query := newQuery("New dash") + query := newTestQuery("New dash") res, err := index.Search(context.Background(), nil, query, nil) require.NoError(t, err) require.Equal(t, int64(2), res.TotalHits) @@ -130,7 +133,7 @@ func TestCanSearchByTitle(t *testing.T) { }) t.Run("will boost exact match query over match phrase query results", func(t *testing.T) { - index := newTestDashboardsIndex(t) + index, _ := newTestDashboardsIndex(t, threshold, 2, 2, noop) err := index.Write(&resource.IndexableDocument{ RV: 1, Name: "name1", @@ -157,7 +160,7 @@ func TestCanSearchByTitle(t *testing.T) { require.NoError(t, err) // search for exact match - query := newQuery("we want hello") + query := newTestQuery("we want hello") res, err := index.Search(context.Background(), nil, query, nil) require.NoError(t, err) require.Equal(t, int64(2), res.TotalHits) @@ -165,7 +168,7 @@ func TestCanSearchByTitle(t *testing.T) { }) t.Run("title with numbers will match document", func(t *testing.T) { - index := newTestDashboardsIndex(t) + index, _ := newTestDashboardsIndex(t, threshold, 2, 2, noop) err := index.Write(&resource.IndexableDocument{ RV: 1, Name: "name1", @@ -180,20 +183,65 @@ func TestCanSearchByTitle(t *testing.T) { require.NoError(t, err) // search for prefix of title with mix of chars and numbers - query := newQuery("A12") + query := newQueryByTitle("A12") res, err := index.Search(context.Background(), nil, query, nil) require.NoError(t, err) require.Equal(t, int64(1), res.TotalHits) // search for whole title - query = newQuery("A123456") + query = newQueryByTitle("A123456") + res, err = index.Search(context.Background(), nil, query, nil) + require.NoError(t, err) + require.Equal(t, int64(1), res.TotalHits) + + // case insensive search for partial title + query = newQueryByTitle("a1234") res, err = index.Search(context.Background(), nil, query, nil) require.NoError(t, err) require.Equal(t, int64(1), res.TotalHits) }) + t.Run("title will match escaped characters", func(t *testing.T) { + index, _ := newTestDashboardsIndex(t, threshold, 2, 2, noop) + err := index.Write(&resource.IndexableDocument{ + RV: 1, + Name: "name1", + Key: &resource.ResourceKey{ + Name: "aaa", + Namespace: key.Namespace, + Group: key.Group, + Resource: key.Resource, + }, + Title: "what\"s up", + }) + require.NoError(t, err) + + err = index.Write(&resource.IndexableDocument{ + RV: 2, + Name: "name2", + Key: &resource.ResourceKey{ + Name: "name2", + Namespace: key.Namespace, + Group: key.Group, + Resource: key.Resource, + }, + Title: "what\"s that", + }) + require.NoError(t, err) + + query := newQueryByTitle("what\"s up") + res, err := index.Search(context.Background(), nil, query, nil) + require.NoError(t, err) + require.Equal(t, int64(1), res.TotalHits) + + query = newQueryByTitle("what\"s") + res, err = index.Search(context.Background(), nil, query, nil) + require.NoError(t, err) + require.Equal(t, int64(2), res.TotalHits) + }) + t.Run("title search will match document", func(t *testing.T) { - index := newTestDashboardsIndex(t) + index, _ := newTestDashboardsIndex(t, threshold, 2, 2, noop) err := index.Write(&resource.IndexableDocument{ RV: 1, Name: "name1", @@ -208,50 +256,50 @@ func TestCanSearchByTitle(t *testing.T) { require.NoError(t, err) // search by entire phrase - query := newQuery("I want to say a wonderfully Hello to the WORLD! Hello-world") + query := newTestQuery("I want to say a wonderfully Hello to the WORLD! Hello-world") res, err := index.Search(context.Background(), nil, query, nil) require.NoError(t, err) require.Equal(t, int64(1), res.TotalHits) // search for word at start - query = newQuery("hello") + query = newTestQuery("hello") res, err = index.Search(context.Background(), nil, query, nil) require.NoError(t, err) require.Equal(t, int64(1), res.TotalHits) // search for word larger than ngram max size - query = newQuery("wonderfully") + query = newQueryByTitle("wonderfully") res, err = index.Search(context.Background(), nil, query, nil) require.NoError(t, err) require.Equal(t, int64(1), res.TotalHits) // search for word at end - query = newQuery("world") + query = newQueryByTitle("world") res, err = index.Search(context.Background(), nil, query, nil) require.NoError(t, err) require.Equal(t, int64(1), res.TotalHits) // can search for word substring anchored at start of word (edge ngram) - query = newQuery("worl") + query = newQueryByTitle("worl") res, err = index.Search(context.Background(), nil, query, nil) require.NoError(t, err) require.Equal(t, int64(1), res.TotalHits) // can search for multiple, non-consecutive words in title - query = newQuery("hello world") + query = newQueryByTitle("hello world") res, err = index.Search(context.Background(), nil, query, nil) require.NoError(t, err) require.Equal(t, int64(1), res.TotalHits) // can search for a term with a hyphen - query = newQuery("hello-world") + query = newQueryByTitle("hello-world") res, err = index.Search(context.Background(), nil, query, nil) require.NoError(t, err) require.Equal(t, int64(1), res.TotalHits) }) t.Run("title search will NOT match documents", func(t *testing.T) { - index := newTestDashboardsIndex(t) + index, _ := newTestDashboardsIndex(t, threshold, 2, 2, noop) err := index.Write(&resource.IndexableDocument{ RV: 1, Name: "name1", @@ -290,26 +338,79 @@ func TestCanSearchByTitle(t *testing.T) { require.NoError(t, err) // word that doesn't exist - query := newQuery("cats") + query := newQueryByTitle("cats") res, err := index.Search(context.Background(), nil, query, nil) require.NoError(t, err) require.Equal(t, int64(0), res.TotalHits) // string shorter than 3 chars (ngam min) - query = newQuery("ma") + query = newQueryByTitle("ma") res, err = index.Search(context.Background(), nil, query, nil) require.NoError(t, err) require.Equal(t, int64(0), res.TotalHits) // substring that doesn't exist - query = newQuery("A01") + query = newQueryByTitle("A01") res, err = index.Search(context.Background(), nil, query, nil) require.NoError(t, err) require.Equal(t, int64(0), res.TotalHits) }) + + t.Run("title search with character will match one document", func(t *testing.T) { + index, _ := newTestDashboardsIndex(t, threshold, 2, 2, noop) + err := index.Write(&resource.IndexableDocument{ + RV: 1, + Name: "name1", + Key: &resource.ResourceKey{ + Name: "aaa", + Namespace: key.Namespace, + Group: key.Group, + Resource: key.Resource, + }, + Title: "foo", + }) + require.NoError(t, err) + + for i, v := range search.TermCharacters { + err = index.Write(&resource.IndexableDocument{ + RV: int64(i), + Name: fmt.Sprintf("name%d", i), + Key: &resource.ResourceKey{ + Name: fmt.Sprintf("name%d", i), + Namespace: key.Namespace, + Group: key.Group, + Resource: key.Resource, + }, + Title: fmt.Sprintf(`test foo%d%sbar`, i, v), + }) + require.NoError(t, err) + } + + for i, v := range search.TermCharacters { + title := fmt.Sprintf(`test foo%d%sbar`, i, v) + query := newQueryByTitle(title) + res, err := index.Search(context.Background(), nil, query, nil) + require.NoError(t, err) + if res.TotalHits != 1 { + fmt.Printf("i: %d, v: %s, title: %s", i, v, title) + } + require.Equal(t, int64(1), res.TotalHits) + + // can search for a title with a term character suffix + title = fmt.Sprintf(`foo%d%s`, i, v) + query = newQueryByTitle(title) + res, err = index.Search(context.Background(), nil, query, nil) + require.NoError(t, err) + if res.TotalHits != 1 { + fmt.Printf("i: %d, v: %s, title: %s", i, v, title) + } + + require.Equal(t, int64(1), res.TotalHits) + } + }) } -func newQuery(query string) *resource.ResourceSearchRequest { +func newTestQuery(query string) *resource.ResourceSearchRequest { return &resource.ResourceSearchRequest{ Options: &resource.ListOptions{ Key: &resource.ResourceKey{ @@ -323,7 +424,21 @@ func newQuery(query string) *resource.ResourceSearchRequest { } } -func newTestDashboardsIndex(t *testing.T) resource.ResourceIndex { +func newQueryByTitle(query string) *resource.ResourceSearchRequest { + return &resource.ResourceSearchRequest{ + Options: &resource.ListOptions{ + Key: &resource.ResourceKey{ + Namespace: "default", + Group: "dashboard.grafana.app", + Resource: "dashboards", + }, + Fields: []*resource.Requirement{{Key: "title", Operator: "=", Values: []string{query}}}, + }, + Limit: 100000, + } +} + +func newTestDashboardsIndex(t TB, threshold int64, size int64, batchSize int64, writer IndexWriter) (resource.ResourceIndex, string) { key := &resource.ResourceKey{ Namespace: "default", Group: "dashboard.grafana.app", @@ -332,17 +447,18 @@ func newTestDashboardsIndex(t *testing.T) resource.ResourceIndex { tmpdir, err := os.MkdirTemp("", "grafana-bleve-test") require.NoError(t, err) - backend, err := NewBleveBackend(BleveOptions{ + backend, err := search.NewBleveBackend(search.BleveOptions{ Root: tmpdir, - FileThreshold: 9999, // use in-memory for tests + FileThreshold: threshold, // use in-memory for tests + BatchSize: int(batchSize), }, tracing.NewNoopTracerService(), featuremgmt.WithFeatures(featuremgmt.FlagUnifiedStorageSearchPermissionFiltering), nil) require.NoError(t, err) rv := int64(10) ctx := identity.WithRequester(context.Background(), &user.SignedInUser{Namespace: "ns"}) - info, err := DashboardBuilder(func(ctx context.Context, namespace string, blob resource.BlobSupport) (resource.DocumentBuilder, error) { - return &DashboardDocumentBuilder{ + info, err := search.DashboardBuilder(func(ctx context.Context, namespace string, blob resource.BlobSupport) (resource.DocumentBuilder, error) { + return &search.DashboardDocumentBuilder{ Namespace: namespace, Blob: blob, Stats: make(map[string]map[string]int64), // empty stats @@ -355,10 +471,16 @@ func newTestDashboardsIndex(t *testing.T) resource.ResourceIndex { Namespace: key.Namespace, Group: key.Group, Resource: key.Resource, - }, 2, rv, info.Fields, func(index resource.ResourceIndex) (int64, error) { return 0, nil }) + }, size, rv, info.Fields, writer) require.NoError(t, err) - return index + return index, tmpdir +} + +type IndexWriter func(index resource.ResourceIndex) (int64, error) + +var noop IndexWriter = func(index resource.ResourceIndex) (int64, error) { + return 0, nil } // helper to check which tokens are generated by an analyzer @@ -399,3 +521,15 @@ func debugIndexedTerms(index bleve.Index, field string) { } } } + +// TB is an interface that works for both *testing.T and *testing.B +type TB interface { + Log(args ...interface{}) + Logf(format string, args ...interface{}) + Error(args ...interface{}) + Errorf(format string, args ...interface{}) + Fatal(args ...interface{}) + Fatalf(format string, args ...interface{}) + Helper() + FailNow() +} diff --git a/pkg/storage/unified/search/document_test.go b/pkg/storage/unified/search/document_test.go index c11ada997a6..49c7e0c9e89 100644 --- a/pkg/storage/unified/search/document_test.go +++ b/pkg/storage/unified/search/document_test.go @@ -1,4 +1,4 @@ -package search +package search_test import ( "context" @@ -13,6 +13,7 @@ import ( "github.com/grafana/grafana/pkg/services/store/kind/dashboard" "github.com/grafana/grafana/pkg/storage/unified/resource" + "github.com/grafana/grafana/pkg/storage/unified/search" ) func doSnapshotTests(t *testing.T, builder resource.DocumentBuilder, kind string, key *resource.ResourceKey, names []string) { @@ -52,14 +53,14 @@ func TestDashboardDocumentBuilder(t *testing.T) { Resource: "dashboards", } - info, err := DashboardBuilder(func(ctx context.Context, namespace string, blob resource.BlobSupport) (resource.DocumentBuilder, error) { - return &DashboardDocumentBuilder{ + info, err := search.DashboardBuilder(func(ctx context.Context, namespace string, blob resource.BlobSupport) (resource.DocumentBuilder, error) { + return &search.DashboardDocumentBuilder{ Namespace: namespace, Blob: blob, Stats: map[string]map[string]int64{ "aaa": { - DASHBOARD_ERRORS_LAST_1_DAYS: 1, - DASHBOARD_ERRORS_LAST_7_DAYS: 1, + search.DASHBOARD_ERRORS_LAST_1_DAYS: 1, + search.DASHBOARD_ERRORS_LAST_7_DAYS: 1, }, }, DatasourceLookup: dashboard.CreateDatasourceLookup([]*dashboard.DatasourceQueryResult{{ diff --git a/pkg/storage/unified/sql/backend.go b/pkg/storage/unified/sql/backend.go index c4f2a82e725..d675998f97a 100644 --- a/pkg/storage/unified/sql/backend.go +++ b/pkg/storage/unified/sql/backend.go @@ -308,9 +308,6 @@ func (b *backend) WriteEvent(ctx context.Context, event resource.WriteEvent) (in // TODO: validate key ? switch event.Type { case resource.WatchEvent_ADDED: - if event.ObjectOld != nil { - return b.restore(ctx, event) - } return b.create(ctx, event) case resource.WatchEvent_MODIFIED: return b.update(ctx, event) @@ -488,73 +485,6 @@ func (b *backend) delete(ctx context.Context, event resource.WriteEvent) (int64, return rv, nil } -func (b *backend) restore(ctx context.Context, event resource.WriteEvent) (int64, error) { - ctx, span := b.tracer.Start(ctx, tracePrefix+"Restore") - defer span.End() - guid := uuid.New().String() - folder := "" - if event.Object != nil { - folder = event.Object.GetFolder() - } - rv, err := b.rvManager.ExecWithRV(ctx, event.Key, func(tx db.Tx) (string, error) { - // 1. Re-create resource - // Note: we may want to replace the write event with a create event, tbd. - if _, err := dbutil.Exec(ctx, tx, sqlResourceInsert, sqlResourceRequest{ - SQLTemplate: sqltemplate.New(b.dialect), - WriteEvent: event, - Folder: folder, - GUID: guid, - }); err != nil { - return guid, fmt.Errorf("insert into resource: %w", err) - } - - // 2. Insert into resource history - if _, err := dbutil.Exec(ctx, tx, sqlResourceHistoryInsert, sqlResourceRequest{ - SQLTemplate: sqltemplate.New(b.dialect), - WriteEvent: event, - Folder: folder, - GUID: guid, - }); err != nil { - return guid, fmt.Errorf("insert into resource history: %w", err) - } - _ = b.historyPruner.Add(pruningKey{ - namespace: event.Key.Namespace, - group: event.Key.Group, - resource: event.Key.Resource, - name: event.Key.Name, - }) - - // 3. Update all resource history entries with the new UID - // Note: we do not update any history entries that have a deletion timestamp included. This will become - // important once we start using finalizers, as the initial delete will show up as an update with a deletion timestamp included. - if _, err := dbutil.Exec(ctx, tx, sqlResoureceHistoryUpdateUid, sqlResourceHistoryUpdateRequest{ - SQLTemplate: sqltemplate.New(b.dialect), - WriteEvent: event, - OldUID: string(event.ObjectOld.GetUID()), - NewUID: string(event.Object.GetUID()), - }); err != nil { - return guid, fmt.Errorf("update history uid: %w", err) - } - - return guid, nil - }) - - if err != nil { - return 0, err - } - - b.notifier.send(ctx, &resource.WrittenEvent{ - Type: event.Type, - Key: event.Key, - PreviousRV: event.PreviousRV, - Value: event.Value, - ResourceVersion: rv, - Folder: folder, - }) - - return rv, nil -} - func (b *backend) ReadResource(ctx context.Context, req *resource.ReadRequest) *resource.BackendReadResponse { _, span := b.tracer.Start(ctx, tracePrefix+".Read") defer span.End() @@ -577,17 +507,6 @@ func (b *backend) ReadResource(ctx context.Context, req *resource.ReadRequest) * err := b.db.WithTx(ctx, ReadCommittedRO, func(ctx context.Context, tx db.Tx) error { var err error res, err = dbutil.QueryRow(ctx, tx, sr, readReq) - // if not found, look for latest deleted version (if requested) - if errors.Is(err, sql.ErrNoRows) && req.IncludeDeleted { - sr = sqlResourceHistoryRead - readReq2 := &sqlResourceReadRequest{ - SQLTemplate: sqltemplate.New(b.dialect), - Request: req, - Response: NewReadResponse(), - } - res, err = dbutil.QueryRow(ctx, tx, sr, readReq2) - return err - } return err }) @@ -625,9 +544,10 @@ func (b *backend) ListIterator(ctx context.Context, req *resource.ListRequest, c } type listIter struct { - rows db.Rows - offset int64 - listRV int64 + rows db.Rows + offset int64 + listRV int64 + sortAsc bool // any error err error @@ -642,11 +562,11 @@ type listIter struct { // ContinueToken implements resource.ListIterator. func (l *listIter) ContinueToken() string { - return resource.ContinueToken{ResourceVersion: l.listRV, StartOffset: l.offset}.String() + return resource.ContinueToken{ResourceVersion: l.listRV, StartOffset: l.offset, SortAscending: l.sortAsc}.String() } func (l *listIter) ContinueTokenWithCurrentRV() string { - return resource.ContinueToken{ResourceVersion: l.rv, StartOffset: l.offset}.String() + return resource.ContinueToken{ResourceVersion: l.rv, StartOffset: l.offset, SortAscending: l.sortAsc}.String() } func (l *listIter) Error() error { @@ -696,7 +616,7 @@ func (b *backend) listLatest(ctx context.Context, req *resource.ListRequest, cb return 0, fmt.Errorf("only works for the 'latest' resource version") } - iter := &listIter{} + iter := &listIter{sortAsc: false} err := b.db.WithTx(ctx, ReadCommittedRO, func(ctx context.Context, tx db.Tx) error { var err error iter.listRV, err = fetchLatestRV(ctx, tx, b.dialect, req.Options.Key.Group, req.Options.Key.Resource) @@ -730,8 +650,11 @@ func (b *backend) listLatest(ctx context.Context, req *resource.ListRequest, cb // listAtRevision fetches the resources from the resource_history table at a specific revision. func (b *backend) listAtRevision(ctx context.Context, req *resource.ListRequest, cb func(resource.ListIterator) error) (int64, error) { + ctx, span := b.tracer.Start(ctx, tracePrefix+"listAtRevision") + defer span.End() + // Get the RV - iter := &listIter{listRV: req.ResourceVersion} + iter := &listIter{listRV: req.ResourceVersion, sortAsc: false} if req.NextPageToken != "" { continueToken, err := resource.GetContinueToken(req.NextPageToken) if err != nil { @@ -748,6 +671,10 @@ func (b *backend) listAtRevision(ctx context.Context, req *resource.ListRequest, return 0, apierrors.NewBadRequest("expecting an explicit resource version query") } + // The query below has the potential to be EXTREMELY slow if the resource_history table is big. May be helpful to know + // which stack is calling this. + b.log.Debug("listAtRevision", "ns", req.Options.Key.Namespace, "group", req.Options.Key.Group, "resource", req.Options.Key.Resource, "rv", iter.listRV) + err := b.db.WithTx(ctx, ReadCommittedRO, func(ctx context.Context, tx db.Tx) error { limit := int64(0) // ignore limit if iter.offset > 0 { @@ -781,14 +708,37 @@ func (b *backend) listAtRevision(ctx context.Context, req *resource.ListRequest, return iter.listRV, err } -// listLatest fetches the resources from the resource table. +// getHistory fetches the resources from the resource table. func (b *backend) getHistory(ctx context.Context, req *resource.ListRequest, cb func(resource.ListIterator) error) (int64, error) { + // Backwards compatibility for ResourceVersionMatch + if req.VersionMatch != nil && req.GetVersionMatchV2() == resource.ResourceVersionMatchV2_UNKNOWN { + switch req.GetVersionMatch() { + case resource.ResourceVersionMatch_DEPRECATED_NotOlderThan: + // This is not a typo. The old implementation actually did behave like Unset. + req.VersionMatchV2 = resource.ResourceVersionMatchV2_Unset + case resource.ResourceVersionMatch_DEPRECATED_Exact: + req.VersionMatchV2 = resource.ResourceVersionMatchV2_Exact + default: + return 0, fmt.Errorf("unknown version match: %v", req.GetVersionMatch()) + } + + // Log the migration for debugging purposes + b.log.Debug("Old client request received, migrating from version_match to version_match_v2", + "oldValue", req.GetVersionMatch(), + "newValue", req.GetVersionMatchV2()) + } + listReq := sqlGetHistoryRequest{ SQLTemplate: sqltemplate.New(b.dialect), Key: req.Options.Key, Trash: req.Source == resource.ListRequest_TRASH, } + // We are assuming that users want history in ascending order + // when they are using NotOlderThan matching, and descending order + // for Unset (default) and Exact matching. + listReq.SortAscending = req.GetVersionMatchV2() == resource.ResourceVersionMatchV2_NotOlderThan + iter := &listIter{} if req.NextPageToken != "" { continueToken, err := resource.GetContinueToken(req.NextPageToken) @@ -796,10 +746,12 @@ func (b *backend) getHistory(ctx context.Context, req *resource.ListRequest, cb return 0, fmt.Errorf("get continue token: %w", err) } listReq.StartRV = continueToken.ResourceVersion + listReq.SortAscending = continueToken.SortAscending } + iter.sortAsc = listReq.SortAscending // Set ExactRV when using Exact matching - if req.VersionMatch == resource.ResourceVersionMatch_Exact { + if req.VersionMatchV2 == resource.ResourceVersionMatchV2_Exact { if req.ResourceVersion <= 0 { return 0, fmt.Errorf("expecting an explicit resource version query when using Exact matching") } @@ -807,7 +759,7 @@ func (b *backend) getHistory(ctx context.Context, req *resource.ListRequest, cb } // Set MinRV when using NotOlderThan matching to filter at the database level - if req.ResourceVersion > 0 && req.VersionMatch == resource.ResourceVersionMatch_NotOlderThan { + if req.ResourceVersion > 0 && req.VersionMatchV2 == resource.ResourceVersionMatchV2_NotOlderThan { listReq.MinRV = req.ResourceVersion } diff --git a/pkg/storage/unified/sql/backend_test.go b/pkg/storage/unified/sql/backend_test.go index 24b9317c568..f275b2b78e5 100644 --- a/pkg/storage/unified/sql/backend_test.go +++ b/pkg/storage/unified/sql/backend_test.go @@ -376,88 +376,6 @@ func TestBackend_delete(t *testing.T) { }) } -func TestBackend_restore(t *testing.T) { - t.Parallel() - meta, err := utils.MetaAccessor(&unstructured.Unstructured{ - Object: map[string]any{}, - }) - require.NoError(t, err) - meta.SetUID("new-uid") - oldMeta, err := utils.MetaAccessor(&unstructured.Unstructured{ - Object: map[string]any{}, - }) - require.NoError(t, err) - oldMeta.SetUID("old-uid") - event := resource.WriteEvent{ - Type: resource.WatchEvent_ADDED, - Key: resKey, - Object: meta, - ObjectOld: oldMeta, - } - - t.Run("happy path", func(t *testing.T) { - t.Parallel() - b, ctx := setupBackendTest(t) - - b.SQLMock.ExpectBegin() - expectSuccessfulResourceVersionExec(t, b.TestDBProvider, - func() { b.ExecWithResult("insert resource", 0, 1) }, - func() { b.ExecWithResult("insert resource_history", 0, 1) }, - func() { b.ExecWithResult("update resource_history", 0, 1) }, - ) - b.SQLMock.ExpectCommit() - - v, err := b.restore(ctx, event) - require.NoError(t, err) - require.Equal(t, int64(200), v) - }) - - t.Run("error restoring resource", func(t *testing.T) { - t.Parallel() - b, ctx := setupBackendTest(t) - - b.SQLMock.ExpectBegin() - b.ExecWithErr("insert resource", errTest) - b.SQLMock.ExpectRollback() - - v, err := b.restore(ctx, event) - require.Zero(t, v) - require.Error(t, err) - require.ErrorContains(t, err, "insert into resource") - }) - - t.Run("error inserting into resource history", func(t *testing.T) { - t.Parallel() - b, ctx := setupBackendTest(t) - - b.SQLMock.ExpectBegin() - b.ExecWithResult("insert resource", 0, 1) - b.ExecWithErr("insert resource_history", errTest) - b.SQLMock.ExpectRollback() - - v, err := b.restore(ctx, event) - require.Zero(t, v) - require.Error(t, err) - require.ErrorContains(t, err, "insert into resource history") - }) - - t.Run("error updating resource history uid", func(t *testing.T) { - t.Parallel() - b, ctx := setupBackendTest(t) - - b.SQLMock.ExpectBegin() - b.ExecWithResult("insert resource", 0, 1) - b.ExecWithResult("insert resource_history", 0, 1) - b.ExecWithErr("update resource_history", errTest) - b.SQLMock.ExpectRollback() - - v, err := b.restore(ctx, event) - require.Zero(t, v) - require.Error(t, err) - require.ErrorContains(t, err, "update history uid") - }) -} - func TestBackend_getHistory(t *testing.T) { t.Parallel() @@ -473,7 +391,7 @@ func TestBackend_getHistory(t *testing.T) { tests := []struct { name string - versionMatch resource.ResourceVersionMatch + versionMatch resource.ResourceVersionMatchV2 resourceVersion int64 expectedVersions []int64 expectedListRv int64 @@ -482,30 +400,37 @@ func TestBackend_getHistory(t *testing.T) { }{ { name: "with ResourceVersionMatch_NotOlderThan", - versionMatch: resource.ResourceVersionMatch_NotOlderThan, + versionMatch: resource.ResourceVersionMatchV2_NotOlderThan, resourceVersion: rv2, - expectedVersions: []int64{rv3, rv2}, + expectedVersions: []int64{rv2, rv3}, // Should be in ASC order due to NotOlderThan expectedListRv: rv3, expectedRowsCount: 2, }, + { + name: "with ResourceVersionMatch_NotOlderThan and ResourceVersion=0", + versionMatch: resource.ResourceVersionMatchV2_NotOlderThan, + resourceVersion: 0, + expectedVersions: []int64{rv1, rv2, rv3}, // Should be in ASC order due to NotOlderThan + expectedListRv: rv3, + expectedRowsCount: 3, + }, { name: "with ResourceVersionMatch_Exact", - versionMatch: resource.ResourceVersionMatch_Exact, + versionMatch: resource.ResourceVersionMatchV2_Exact, resourceVersion: rv2, expectedVersions: []int64{rv2}, expectedListRv: rv3, expectedRowsCount: 1, }, { - name: "without version matcher", - versionMatch: resource.ResourceVersionMatch_NotOlderThan, - expectedVersions: []int64{rv3, rv2, rv1}, + name: "with ResourceVersionMatch_Unset (default)", + expectedVersions: []int64{rv3, rv2, rv1}, // Should be in DESC order by default expectedListRv: rv3, expectedRowsCount: 3, }, { name: "error with ResourceVersionMatch_Exact and ResourceVersion <= 0", - versionMatch: resource.ResourceVersionMatch_Exact, + versionMatch: resource.ResourceVersionMatchV2_Exact, resourceVersion: 0, expectedErr: "expecting an explicit resource version query when using Exact matching", }, @@ -521,7 +446,7 @@ func TestBackend_getHistory(t *testing.T) { req := &resource.ListRequest{ Options: &resource.ListOptions{Key: key}, ResourceVersion: tc.resourceVersion, - VersionMatch: tc.versionMatch, + VersionMatchV2: tc.versionMatch, Source: resource.ListRequest_HISTORY, } @@ -536,14 +461,18 @@ func TestBackend_getHistory(t *testing.T) { // Callback that tracks returned items callback := func(iter resource.ListIterator) error { count := 0 + var seenVersions []int64 for iter.Next() { count++ currentRV := iter.ResourceVersion() + seenVersions = append(seenVersions, currentRV) expectedValue, ok := expectedValues[currentRV] require.True(t, ok, "Got unexpected RV: %d", currentRV) require.Equal(t, expectedValue, string(iter.Value())) } require.Equal(t, tc.expectedRowsCount, count) + // Verify the order matches what we expect + require.Equal(t, tc.expectedVersions, seenVersions, "Resource versions returned in incorrect order") return nil } @@ -585,3 +514,153 @@ func TestBackend_getHistory(t *testing.T) { }) } } + +// TestBackend_getHistoryPagination tests the ordering behavior for ResourceVersionMatch_NotOlderThan +// when using pagination, ensuring entries are returned in oldest-to-newest order. +func TestBackend_getHistoryPagination(t *testing.T) { + t.Parallel() + + // Common setup + key := &resource.ResourceKey{ + Namespace: "ns", + Group: "gr", + Resource: "rs", + Name: "nm", + } + + // Create resource versions that will be returned in our test + versions := make([]int64, 10) + for i := range versions { + versions[i] = int64(51 + i) + } + rv51, rv52, rv53, rv54, rv55, rv56, rv57, rv58, rv59, rv60 := versions[0], versions[1], versions[2], versions[3], versions[4], versions[5], versions[6], versions[7], versions[8], versions[9] + + t.Run("pagination with NotOlderThan should return entries from oldest to newest", func(t *testing.T) { + b, ctx := setupBackendTest(t) + + // Define all pages we want to test + pages := []struct { + versions []int64 + token *resource.ContinueToken + }{ + { + versions: []int64{rv51, rv52, rv53, rv54}, + token: nil, + }, + { + versions: []int64{rv55, rv56, rv57, rv58}, + token: &resource.ContinueToken{ + ResourceVersion: rv54, + StartOffset: 4, + SortAscending: true, + }, + }, + { + versions: []int64{rv59, rv60}, + token: &resource.ContinueToken{ + ResourceVersion: rv58, + StartOffset: 8, + SortAscending: true, + }, + }, + } + + var allItems []int64 + initialRV := rv51 + + // Test each page + for _, page := range pages { + req := &resource.ListRequest{ + Options: &resource.ListOptions{Key: key}, + ResourceVersion: initialRV, + VersionMatchV2: resource.ResourceVersionMatchV2_NotOlderThan, + Source: resource.ListRequest_HISTORY, + Limit: 4, + } + if page.token != nil { + req.NextPageToken = page.token.String() + } + + items := make([]int64, 0) + callback := func(iter resource.ListIterator) error { + for iter.Next() { + items = append(items, iter.ResourceVersion()) + } + return nil + } + + b.SQLMock.ExpectBegin() + historyRows := setupHistoryTest(b, page.versions, rv60) + b.SQLMock.ExpectQuery("SELECT .* FROM resource_history").WillReturnRows(historyRows) + b.SQLMock.ExpectCommit() + + listRv, err := b.getHistory(ctx, req, callback) + require.NoError(t, err) + require.Equal(t, rv60, listRv, "Head version should be the latest resource version (rv60)") + require.Equal(t, page.versions, items, "Items should be in ASC order") + + allItems = append(allItems, items...) + } + + // Verify complete sequence + expectedAllItems := []int64{rv51, rv52, rv53, rv54, rv55, rv56, rv57, rv58, rv59, rv60} + require.Equal(t, expectedAllItems, allItems) + }) + + t.Run("pagination with ResourceVersion=0 and NotOlderThan should return entries in ASC order", func(t *testing.T) { + b, ctx := setupBackendTest(t) + + req := &resource.ListRequest{ + Options: &resource.ListOptions{Key: key}, + ResourceVersion: 0, + VersionMatchV2: resource.ResourceVersionMatchV2_NotOlderThan, + Source: resource.ListRequest_HISTORY, + Limit: 4, + } + + // First batch of items we expect, in ASC order (because of NotOlderThan flag) + // Even with ResourceVersion=0, the order is ASC because we use SortAscending=true + expectedVersions := []int64{rv51, rv52, rv53, rv54} + items := make([]int64, 0) + + callback := func(iter resource.ListIterator) error { + for iter.Next() { + items = append(items, iter.ResourceVersion()) + } + return nil + } + + b.SQLMock.ExpectBegin() + historyRows := setupHistoryTest(b, expectedVersions, rv60) + b.SQLMock.ExpectQuery("SELECT .* FROM resource_history").WillReturnRows(historyRows) + b.SQLMock.ExpectCommit() + + listRv, err := b.getHistory(ctx, req, callback) + require.NoError(t, err) + require.Equal(t, rv60, listRv, "Head version should be the latest resource version (rv60)") + require.Equal(t, expectedVersions, items, "Items should be in ASC order even with ResourceVersion=0") + }) +} + +// setupHistoryTest creates the necessary mock expectations for a history test +func setupHistoryTest(b testBackend, resourceVersions []int64, latestRV int64) *sqlmock.Rows { + // Expect fetch latest RV call - set to the highest resource version + latestRVRows := sqlmock.NewRows([]string{"resource_version", "unix_timestamp"}). + AddRow(latestRV, 0) + b.SQLMock.ExpectQuery("SELECT .* FROM resource_version").WillReturnRows(latestRVRows) + + // Create the mock rows for the history items + cols := []string{"resource_version", "namespace", "name", "folder", "value"} + historyRows := sqlmock.NewRows(cols) + for _, rv := range resourceVersions { + historyRows.AddRow( + rv, // resource_version + "ns", // namespace + "nm", // name + "folder", // folder + []byte(fmt.Sprintf("rv-%d", rv)), // value + ) + } + + return historyRows +} diff --git a/pkg/storage/unified/sql/data/resource_history_get.sql b/pkg/storage/unified/sql/data/resource_history_get.sql index 066f0498810..379cc67967c 100644 --- a/pkg/storage/unified/sql/data/resource_history_get.sql +++ b/pkg/storage/unified/sql/data/resource_history_get.sql @@ -16,12 +16,20 @@ WHERE 1 = 1 AND {{ .Ident "action" }} = 3 {{ end }} {{ if (gt .StartRV 0) }} + {{ if .SortAscending }} + AND {{ .Ident "resource_version" }} > {{ .Arg .StartRV }} + {{ else }} AND {{ .Ident "resource_version" }} < {{ .Arg .StartRV }} {{ end }} + {{ end }} {{ if (gt .MinRV 0) }} AND {{ .Ident "resource_version" }} >= {{ .Arg .MinRV }} {{ end }} {{ if (gt .ExactRV 0) }} AND {{ .Ident "resource_version" }} = {{ .Arg .ExactRV }} {{ end }} +{{ if .SortAscending }} +ORDER BY resource_version ASC +{{ else }} ORDER BY resource_version DESC +{{ end }} diff --git a/pkg/storage/unified/sql/data/resource_history_read.sql b/pkg/storage/unified/sql/data/resource_history_read.sql index d78209817b2..3eb350e275d 100644 --- a/pkg/storage/unified/sql/data/resource_history_read.sql +++ b/pkg/storage/unified/sql/data/resource_history_read.sql @@ -14,12 +14,8 @@ SELECT AND {{ .Ident "group" }} = {{ .Arg .Request.Key.Group }} AND {{ .Ident "resource" }} = {{ .Arg .Request.Key.Resource }} AND {{ .Ident "name" }} = {{ .Arg .Request.Key.Name }} - {{ if .Request.IncludeDeleted }} - AND {{ .Ident "action" }} != 3 - AND {{ .Ident "value" }} NOT LIKE '%deletionTimestamp%' - {{ end }} {{ if gt .Request.ResourceVersion 0 }} - AND {{ .Ident "resource_version" }} {{ if .Request.IncludeDeleted }}={{ else }}<={{ end }} {{ .Arg .Request.ResourceVersion }} + AND {{ .Ident "resource_version" }} <= {{ .Arg .Request.ResourceVersion }} {{ end }} ORDER BY {{ .Ident "resource_version" }} DESC LIMIT 1 diff --git a/pkg/storage/unified/sql/data/resource_history_update_uid.sql b/pkg/storage/unified/sql/data/resource_history_update_uid.sql deleted file mode 100644 index 7624b5e9c53..00000000000 --- a/pkg/storage/unified/sql/data/resource_history_update_uid.sql +++ /dev/null @@ -1,8 +0,0 @@ -UPDATE {{ .Ident "resource_history" }} - SET {{ .Ident "value" }} = REPLACE({{ .Ident "value" }}, CONCAT('"uid":"', {{ .Arg .OldUID }}, '"'), CONCAT('"uid":"', {{ .Arg .NewUID }}, '"')) - WHERE {{ .Ident "name" }} = {{ .Arg .WriteEvent.Key.Name }} - AND {{ .Ident "namespace" }} = {{ .Arg .WriteEvent.Key.Namespace }} - AND {{ .Ident "group" }} = {{ .Arg .WriteEvent.Key.Group }} - AND {{ .Ident "resource" }} = {{ .Arg .WriteEvent.Key.Resource }} - AND {{ .Ident "action" }} != 3 - AND {{ .Ident "value" }} NOT LIKE '%deletionTimestamp%'; diff --git a/pkg/storage/unified/sql/queries.go b/pkg/storage/unified/sql/queries.go index 022c33f4775..0da9cd9187e 100644 --- a/pkg/storage/unified/sql/queries.go +++ b/pkg/storage/unified/sql/queries.go @@ -39,7 +39,6 @@ var ( sqlResourceUpdateRV = mustTemplate("resource_update_rv.sql") sqlResourceHistoryRead = mustTemplate("resource_history_read.sql") sqlResourceHistoryUpdateRV = mustTemplate("resource_history_update_rv.sql") - sqlResoureceHistoryUpdateUid = mustTemplate("resource_history_update_uid.sql") sqlResourceHistoryInsert = mustTemplate("resource_history_insert.sql") sqlResourceHistoryPoll = mustTemplate("resource_history_poll.sql") sqlResourceHistoryGet = mustTemplate("resource_history_get.sql") @@ -244,11 +243,12 @@ func (r *sqlResourceHistoryDeleteRequest) Validate() error { type sqlGetHistoryRequest struct { sqltemplate.SQLTemplate - Key *resource.ResourceKey - Trash bool // only deleted items - StartRV int64 // from NextPageToken - MinRV int64 // minimum resource version for NotOlderThan - ExactRV int64 // exact resource version for Exact + Key *resource.ResourceKey + Trash bool // only deleted items + StartRV int64 // from NextPageToken + MinRV int64 // minimum resource version for NotOlderThan + ExactRV int64 // exact resource version for Exact + SortAscending bool // if true, sort by resource_version ASC, otherwise DESC } func (r sqlGetHistoryRequest) Validate() error { @@ -281,19 +281,6 @@ func (r *sqlPruneHistoryRequest) Validate() error { return nil } -// update resource history - -type sqlResourceHistoryUpdateRequest struct { - sqltemplate.SQLTemplate - WriteEvent resource.WriteEvent - OldUID string - NewUID string -} - -func (r sqlResourceHistoryUpdateRequest) Validate() error { - return nil // TODO -} - type sqlResourceBlobInsertRequest struct { sqltemplate.SQLTemplate Now time.Time diff --git a/pkg/storage/unified/sql/queries_test.go b/pkg/storage/unified/sql/queries_test.go index 9858394bccc..6321604fbe4 100644 --- a/pkg/storage/unified/sql/queries_test.go +++ b/pkg/storage/unified/sql/queries_test.go @@ -176,26 +176,6 @@ func TestUnifiedStorageQueries(t *testing.T) { }, }, - sqlResoureceHistoryUpdateUid: { - { - Name: "modify uids in history", - Data: &sqlResourceHistoryUpdateRequest{ - SQLTemplate: mocks.NewTestingSQLTemplate(), - WriteEvent: resource.WriteEvent{ - Key: &resource.ResourceKey{ - Namespace: "nn", - Group: "gg", - Resource: "rr", - Name: "name", - }, - PreviousRV: 1234, - }, - OldUID: "old-uid", - NewUID: "new-uid", - }, - }, - }, - sqlResourceHistoryInsert: { { Name: "insert into resource_history", diff --git a/pkg/storage/unified/sql/testdata/mysql--resource_history_update_uid-modify uids in history.sql b/pkg/storage/unified/sql/testdata/mysql--resource_history_update_uid-modify uids in history.sql deleted file mode 100755 index 05431eea218..00000000000 --- a/pkg/storage/unified/sql/testdata/mysql--resource_history_update_uid-modify uids in history.sql +++ /dev/null @@ -1,8 +0,0 @@ -UPDATE `resource_history` - SET `value` = REPLACE(`value`, CONCAT('"uid":"', 'old-uid', '"'), CONCAT('"uid":"', 'new-uid', '"')) - WHERE `name` = 'name' - AND `namespace` = 'nn' - AND `group` = 'gg' - AND `resource` = 'rr' - AND `action` != 3 - AND `value` NOT LIKE '%deletionTimestamp%'; diff --git a/pkg/storage/unified/sql/testdata/postgres--resource_history_update_uid-modify uids in history.sql b/pkg/storage/unified/sql/testdata/postgres--resource_history_update_uid-modify uids in history.sql deleted file mode 100755 index 79192fa5a6e..00000000000 --- a/pkg/storage/unified/sql/testdata/postgres--resource_history_update_uid-modify uids in history.sql +++ /dev/null @@ -1,8 +0,0 @@ -UPDATE "resource_history" - SET "value" = REPLACE("value", CONCAT('"uid":"', 'old-uid', '"'), CONCAT('"uid":"', 'new-uid', '"')) - WHERE "name" = 'name' - AND "namespace" = 'nn' - AND "group" = 'gg' - AND "resource" = 'rr' - AND "action" != 3 - AND "value" NOT LIKE '%deletionTimestamp%'; diff --git a/pkg/storage/unified/sql/testdata/sqlite--resource_history_update_uid-modify uids in history.sql b/pkg/storage/unified/sql/testdata/sqlite--resource_history_update_uid-modify uids in history.sql deleted file mode 100755 index 79192fa5a6e..00000000000 --- a/pkg/storage/unified/sql/testdata/sqlite--resource_history_update_uid-modify uids in history.sql +++ /dev/null @@ -1,8 +0,0 @@ -UPDATE "resource_history" - SET "value" = REPLACE("value", CONCAT('"uid":"', 'old-uid', '"'), CONCAT('"uid":"', 'new-uid', '"')) - WHERE "name" = 'name' - AND "namespace" = 'nn' - AND "group" = 'gg' - AND "resource" = 'rr' - AND "action" != 3 - AND "value" NOT LIKE '%deletionTimestamp%'; diff --git a/pkg/storage/unified/testing/storage_backend.go b/pkg/storage/unified/testing/storage_backend.go index 2b55ea386c2..c45d13ce6ba 100644 --- a/pkg/storage/unified/testing/storage_backend.go +++ b/pkg/storage/unified/testing/storage_backend.go @@ -443,6 +443,7 @@ func runTestIntegrationBackendList(t *testing.T, backend resource.StorageBackend continueToken := &resource.ContinueToken{ ResourceVersion: rv8, StartOffset: 2, + SortAscending: false, } res, err := server.List(ctx, &resource.ListRequest{ NextPageToken: continueToken.String(), @@ -492,42 +493,142 @@ func runTestIntegrationBackendListHistory(t *testing.T, backend resource.Storage require.NoError(t, err) require.Greater(t, rvHistory5, rvHistory4) - t.Run("fetch first history page at revision with limit", func(t *testing.T) { - res, err := server.List(ctx, &resource.ListRequest{ - Limit: 3, - Source: resource.ListRequest_HISTORY, - Options: &resource.ListOptions{ - Key: &resource.ResourceKey{ - Namespace: ns, - Group: "group", - Resource: "resource", - Name: "item1", - }, - }, - }) - require.NoError(t, err) - require.NoError(t, err) - require.Nil(t, res.Error) - require.Len(t, res.Items, 3) - t.Log(res.Items) - // should be in desc order, so the newest RVs are returned first - require.Equal(t, "item1 MODIFIED", string(res.Items[0].Value)) - require.Equal(t, rvHistory5, res.Items[0].ResourceVersion) - require.Equal(t, "item1 MODIFIED", string(res.Items[1].Value)) - require.Equal(t, rvHistory4, res.Items[1].ResourceVersion) - require.Equal(t, "item1 MODIFIED", string(res.Items[2].Value)) - require.Equal(t, rvHistory3, res.Items[2].ResourceVersion) + t.Run("fetch history with different version matching", func(t *testing.T) { + baseKey := &resource.ResourceKey{ + Namespace: ns, + Group: "group", + Resource: "resource", + Name: "item1", + } - continueToken, err := resource.GetContinueToken(res.NextPageToken) - require.NoError(t, err) - // should return the furthest back RV as the next page token - require.Equal(t, rvHistory3, continueToken.ResourceVersion) + tests := []struct { + name string + request *resource.ListRequest + expectedVersions []int64 + expectedValues []string + minExpectedHeadRV int64 + expectedContinueRV int64 + expectedSortAsc bool + }{ + { + name: "NotOlderThan with rv1 (ASC order)", + request: &resource.ListRequest{ + Limit: 3, + Source: resource.ListRequest_HISTORY, + ResourceVersion: rv1, + VersionMatchV2: resource.ResourceVersionMatchV2_NotOlderThan, + Options: &resource.ListOptions{ + Key: baseKey, + }, + }, + expectedVersions: []int64{rv1, rvHistory1, rvHistory2}, + expectedValues: []string{"item1 ADDED", "item1 MODIFIED", "item1 MODIFIED"}, + minExpectedHeadRV: rvHistory2, + expectedContinueRV: rvHistory2, + expectedSortAsc: true, + }, + { + name: "NotOlderThan with rv=0 (ASC order)", + request: &resource.ListRequest{ + Limit: 3, + Source: resource.ListRequest_HISTORY, + ResourceVersion: 0, + VersionMatchV2: resource.ResourceVersionMatchV2_NotOlderThan, + Options: &resource.ListOptions{ + Key: baseKey, + }, + }, + expectedVersions: []int64{rv1, rvHistory1, rvHistory2}, + expectedValues: []string{"item1 ADDED", "item1 MODIFIED", "item1 MODIFIED"}, + minExpectedHeadRV: rvHistory2, + expectedContinueRV: rvHistory2, + expectedSortAsc: true, + }, + { + name: "ResourceVersionMatch_Unset (DESC order)", + request: &resource.ListRequest{ + Limit: 3, + Source: resource.ListRequest_HISTORY, + Options: &resource.ListOptions{ + Key: baseKey, + }, + }, + expectedVersions: []int64{rvHistory5, rvHistory4, rvHistory3}, + expectedValues: []string{"item1 MODIFIED", "item1 MODIFIED", "item1 MODIFIED"}, + minExpectedHeadRV: rvHistory5, + expectedContinueRV: rvHistory3, + expectedSortAsc: false, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + res, err := server.List(ctx, tc.request) + require.NoError(t, err) + require.Nil(t, res.Error) + require.Len(t, res.Items, 3) + + // Check versions and values match expectations + for i := 0; i < 3; i++ { + require.Equal(t, tc.expectedVersions[i], res.Items[i].ResourceVersion) + require.Equal(t, tc.expectedValues[i], string(res.Items[i].Value)) + } + + // Check resource version in response + require.GreaterOrEqual(t, res.ResourceVersion, tc.minExpectedHeadRV) + + // Check continue token + continueToken, err := resource.GetContinueToken(res.NextPageToken) + require.NoError(t, err) + require.Equal(t, tc.expectedContinueRV, continueToken.ResourceVersion) + require.Equal(t, tc.expectedSortAsc, continueToken.SortAscending) + }) + } + + // Test pagination for NotOlderThan (second page) + t.Run("second page with NotOlderThan", func(t *testing.T) { + // Get first page + firstRequest := &resource.ListRequest{ + Limit: 3, + Source: resource.ListRequest_HISTORY, + ResourceVersion: rv1, + VersionMatchV2: resource.ResourceVersionMatchV2_NotOlderThan, + Options: &resource.ListOptions{Key: baseKey}, + } + firstPageRes, err := server.List(ctx, firstRequest) + require.NoError(t, err) + + // Get continue token for second page + continueToken, err := resource.GetContinueToken(firstPageRes.NextPageToken) + require.NoError(t, err) + + // Get second page + secondPageRes, err := server.List(ctx, &resource.ListRequest{ + Limit: 3, + Source: resource.ListRequest_HISTORY, + ResourceVersion: rv1, + VersionMatchV2: resource.ResourceVersionMatchV2_NotOlderThan, + NextPageToken: continueToken.String(), + Options: &resource.ListOptions{Key: baseKey}, + }) + require.NoError(t, err) + require.Nil(t, secondPageRes.Error) + require.Len(t, secondPageRes.Items, 3) + + // Second page should continue in ascending order + expectedRVs := []int64{rvHistory3, rvHistory4, rvHistory5} + for i, expectedRV := range expectedRVs { + require.Equal(t, expectedRV, secondPageRes.Items[i].ResourceVersion) + require.Equal(t, "item1 MODIFIED", string(secondPageRes.Items[i].Value)) + } + }) }) t.Run("fetch second page of history at revision", func(t *testing.T) { continueToken := &resource.ContinueToken{ ResourceVersion: rvHistory3, StartOffset: 2, + SortAscending: false, } res, err := server.List(ctx, &resource.ListRequest{ NextPageToken: continueToken.String(), @@ -551,6 +652,123 @@ func runTestIntegrationBackendListHistory(t *testing.T, backend resource.Storage require.Equal(t, "item1 MODIFIED", string(res.Items[1].Value)) require.Equal(t, rvHistory1, res.Items[1].ResourceVersion) }) + + t.Run("paginated history with NotOlderThan returns items in ascending order", func(t *testing.T) { + // Create 10 versions of a resource to test pagination + ns2 := nsPrefix + "-ns2" + resourceKey := &resource.ResourceKey{ + Namespace: ns2, + Group: "group", + Resource: "resource", + Name: "paged-item", + } + + var resourceVersions []int64 + + // First create the initial resource + initialRV, err := writeEvent(ctx, backend, "paged-item", resource.WatchEvent_ADDED, WithNamespace(ns2)) + require.NoError(t, err) + resourceVersions = append(resourceVersions, initialRV) + + // Create 9 more versions with modifications + for i := 0; i < 9; i++ { + rv, err := writeEvent(ctx, backend, "paged-item", resource.WatchEvent_MODIFIED, WithNamespace(ns2)) + require.NoError(t, err) + resourceVersions = append(resourceVersions, rv) + } + + // Now we should have 10 versions total (1 ADDED + 9 MODIFIED) + require.Len(t, resourceVersions, 10) + + // Test pagination with limit of 3 and NotOlderThan, starting from the beginning + pages := []struct { + pageNumber int + pageSize int + startToken string + }{ + {pageNumber: 1, pageSize: 3, startToken: ""}, + {pageNumber: 2, pageSize: 3, startToken: ""}, // Will be set in the test + {pageNumber: 3, pageSize: 3, startToken: ""}, // Will be set in the test + {pageNumber: 4, pageSize: 1, startToken: ""}, // Will be set in the test - last page with remaining item + } + + var allItems []*resource.ResourceWrapper + + // Request first page with NotOlderThan and ResourceVersion=0 (should start from oldest) + for i, page := range pages { + req := &resource.ListRequest{ + Limit: int64(page.pageSize), + Source: resource.ListRequest_HISTORY, + ResourceVersion: 0, + VersionMatchV2: resource.ResourceVersionMatchV2_NotOlderThan, + Options: &resource.ListOptions{ + Key: resourceKey, + }, + } + + if i > 0 { + // For subsequent pages, use the continue token from the previous page + req.NextPageToken = pages[i].startToken + } + + res, err := server.List(ctx, req) + require.NoError(t, err) + require.Nil(t, res.Error) + + // First 3 pages should have exactly pageSize items + if i < 3 { + require.Len(t, res.Items, page.pageSize, "Page %d should have %d items", i+1, page.pageSize) + } else { + // Last page should have 1 item (10 items total with 3+3+3+1 distribution) + require.Len(t, res.Items, 1, "Last page should have 1 item") + } + + // Save continue token for next page if not the last page + if i < len(pages)-1 { + pages[i+1].startToken = res.NextPageToken + require.NotEmpty(t, res.NextPageToken, "Should have continue token for page %d", i+1) + } else { + // Last page should not have a continue token + require.Empty(t, res.NextPageToken, "Last page should not have continue token") + } + + // Add items to our collection + allItems = append(allItems, res.Items...) + + // Verify all items in current page are in ascending order + for j := 1; j < len(res.Items); j++ { + require.Less(t, res.Items[j-1].ResourceVersion, res.Items[j].ResourceVersion, + "Items within page %d should be in ascending order", i+1) + } + + // For pages after the first, verify first item of current page is greater than last item of previous page + if i > 0 && len(allItems) > page.pageSize { + prevPageLastIdx := len(allItems) - len(res.Items) - 1 + currentPageFirstIdx := len(allItems) - len(res.Items) + require.Greater(t, allItems[currentPageFirstIdx].ResourceVersion, allItems[prevPageLastIdx].ResourceVersion, + "First item of page %d should have higher RV than last item of page %d", i+1, i) + } + } + + // Verify we got all 10 items + require.Len(t, allItems, 10, "Should have retrieved all 10 items") + + // Verify all items are in ascending order of resource version + for i := 1; i < len(allItems); i++ { + require.Less(t, allItems[i-1].ResourceVersion, allItems[i].ResourceVersion, + "All items should be in ascending order of resource version") + } + + // Verify the first item is the initial ADDED event + require.Equal(t, initialRV, allItems[0].ResourceVersion, "First item should be the initial ADDED event") + require.Equal(t, "paged-item ADDED", string(allItems[0].Value)) + + // Verify all other items are MODIFIED events and correspond to our recorded resource versions + for i := 1; i < len(allItems); i++ { + require.Equal(t, "paged-item MODIFIED", string(allItems[i].Value)) + require.Equal(t, resourceVersions[i], allItems[i].ResourceVersion) + } + }) } func runTestIntegrationBlobSupport(t *testing.T, backend resource.StorageBackend, nsPrefix string) { diff --git a/pkg/tests/apis/openapi_snapshots/provisioning.grafana.app-v0alpha1.json b/pkg/tests/apis/openapi_snapshots/provisioning.grafana.app-v0alpha1.json index d74ca23d0be..5121b7861eb 100644 --- a/pkg/tests/apis/openapi_snapshots/provisioning.grafana.app-v0alpha1.json +++ b/pkg/tests/apis/openapi_snapshots/provisioning.grafana.app-v0alpha1.json @@ -1121,7 +1121,7 @@ "type": "boolean", "default": false }, - "prefix": { + "path": { "description": "Prefix in target file system", "type": "string" } @@ -1130,7 +1130,7 @@ "example": { "folder": "grafan-folder-ref", "branch": "target-branch", - "prefix": "prefix/in/repo/tree", + "path": "path/in/tree", "identifier": false } } @@ -1780,15 +1780,10 @@ "description": "Include the identifier in the exported metadata", "type": "boolean", "default": false - }, - "prefix": { - "description": "Target file prefix", - "type": "string" } } }, "example": { - "prefix": "prefix/in/repo/tree", "history": true, "identifier": false } @@ -2567,7 +2562,7 @@ "type": "boolean", "default": false }, - "prefix": { + "path": { "description": "Prefix in target file system", "type": "string" } @@ -3035,10 +3030,6 @@ "description": "Include the identifier in the exported metadata", "type": "boolean", "default": false - }, - "prefix": { - "description": "Target file prefix", - "type": "string" } } }, diff --git a/pkg/tests/apis/playlist/playlist_test.go b/pkg/tests/apis/playlist/playlist_test.go index a7b61abec50..a30aeba2fc2 100644 --- a/pkg/tests/apis/playlist/playlist_test.go +++ b/pkg/tests/apis/playlist/playlist_test.go @@ -10,11 +10,13 @@ import ( "testing" "github.com/stretchr/testify/require" + apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime/schema" + "github.com/grafana/grafana/pkg/apimachinery/utils" grafanarest "github.com/grafana/grafana/pkg/apiserver/rest" "github.com/grafana/grafana/pkg/services/apiserver/options" "github.com/grafana/grafana/pkg/services/featuremgmt" @@ -156,7 +158,7 @@ func TestIntegrationPlaylist(t *testing.T) { }) t.Run("with dual write (file, mode 5)", func(t *testing.T) { - doPlaylistTests(t, apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{ + helper := doPlaylistTests(t, apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{ AppModeProduction: true, DisableAnonymous: true, APIServerStorageType: "file", // write the files to disk @@ -169,6 +171,33 @@ func TestIntegrationPlaylist(t *testing.T) { featuremgmt.FlagKubernetesPlaylists, // Required so that legacy calls are also written }, })) + + client := helper.GetResourceClient(apis.ResourceClientArgs{ + User: helper.Org1.Editor, + GVR: gvr, + }) + + // Folder support needs to be enabled explicitly for this resource + t.Run("ensure writing folders is an error", func(t *testing.T) { + // Create works without folder + obj := helper.LoadYAMLOrJSONFile("testdata/playlist-generate.yaml") + out, err := client.Resource.Create(context.Background(), obj, metav1.CreateOptions{}) + require.NoError(t, err) + + meta, err := utils.MetaAccessor(out) + require.NoError(t, err) + require.Equal(t, int64(1), meta.GetGeneration()) + require.Equal(t, helper.Org1.Editor.Identity.GetUID(), meta.GetCreatedBy()) + require.Equal(t, "", meta.GetUpdatedBy()) + + meta, err = utils.MetaAccessor(obj) + require.NoError(t, err) + meta.SetFolder("FolderUID") + + _, err = client.Resource.Create(context.Background(), obj, metav1.CreateOptions{}) + require.Error(t, err) + require.True(t, apierrors.IsBadRequest(err)) + }) }) t.Run("with dual write (unified storage, mode 0)", func(t *testing.T) { diff --git a/pkg/tsdb/tempo/kinds/dataquery/types_dataquery_gen.go b/pkg/tsdb/tempo/kinds/dataquery/types_dataquery_gen.go index 874c4161741..457ed0c9baa 100644 --- a/pkg/tsdb/tempo/kinds/dataquery/types_dataquery_gen.go +++ b/pkg/tsdb/tempo/kinds/dataquery/types_dataquery_gen.go @@ -48,7 +48,7 @@ type TempoQuery struct { // Defines the maximum number of spans per spanset that are returned from Tempo Spss *int64 `json:"spss,omitempty"` Filters []TraceqlFilter `json:"filters"` - // Filters that are used to query the metrics summary + // deprecated Filters that are used to query the metrics summary GroupBy []TraceqlFilter `json:"groupBy,omitempty"` // The type of the table that is used to display the search results TableType *SearchTableType `json:"tableType,omitempty"` diff --git a/pkg/util/xorm/dialect_spanner.go b/pkg/util/xorm/dialect_spanner.go index 4269107cbb6..df4b0e3965e 100644 --- a/pkg/util/xorm/dialect_spanner.go +++ b/pkg/util/xorm/dialect_spanner.go @@ -9,7 +9,10 @@ import ( "strings" _ "github.com/googleapis/go-sql-spanner" + spannerdriver "github.com/googleapis/go-sql-spanner" "xorm.io/core" + + spannerext "github.com/grafana/grafana/pkg/extensions/spanner" ) func init() { @@ -370,3 +373,23 @@ func (s *spanner) GetIndexes(tableName string) (map[string]*core.Index, error) { } return indexes, res.Err() } + +func (s *spanner) CreateSequenceGenerator(db *sql.DB) (SequenceGenerator, error) { + dsn := s.DataSourceName() + connectorConfig, err := spannerdriver.ExtractConnectorConfig(dsn) + if err != nil { + return nil, err + } + + if spannerext.UsePlainText(connectorConfig) { + // Plain-text means we're either using spannertest or Spanner emulator. + // Switch to fake in-memory sequence number generator in that case. + // + // Using database-based sequence generator doesn't work with emulator, as emulator + // only supports single transaction. If there is already another transaction started + // generating new ID via database-based sequence generator would always fail. + return newInMemSequenceGenerator(), nil + } + + return newSequenceGenerator(db), nil +} diff --git a/pkg/util/xorm/engine.go b/pkg/util/xorm/engine.go index 8db50041ae2..4d29facbd77 100644 --- a/pkg/util/xorm/engine.go +++ b/pkg/util/xorm/engine.go @@ -44,7 +44,7 @@ type Engine struct { tagHandlers map[string]tagHandler defaultContext context.Context - sequenceGenerator *sequenceGenerator // If not nil, this generator is used to generate auto-increment values for inserts. + sequenceGenerator SequenceGenerator // If not nil, this generator is used to generate auto-increment values for inserts. } // CondDeleted returns the conditions whether a record is soft deleted. @@ -239,9 +239,6 @@ func (engine *Engine) NewSession() *Session { // Close the engine func (engine *Engine) Close() error { - if engine.sequenceGenerator != nil { - engine.sequenceGenerator.close() - } return engine.db.Close() } diff --git a/pkg/util/xorm/sequence.go b/pkg/util/xorm/sequence.go index bb74e2064b6..19192db96f3 100644 --- a/pkg/util/xorm/sequence.go +++ b/pkg/util/xorm/sequence.go @@ -3,65 +3,153 @@ package xorm import ( "context" "database/sql" - "errors" "fmt" + "sync" ) +// batchState represents the state of a sequence batch +type batchState struct { + mu sync.Mutex + nextValue int64 + lastValueInBatch int64 +} + type sequenceGenerator struct { db *sql.DB sequencesTable string + batchSize int64 + + mu sync.Mutex + batchStates map[string]*batchState // Track sequence batches per key (table:column) } func newSequenceGenerator(db *sql.DB) *sequenceGenerator { return &sequenceGenerator{ db: db, sequencesTable: "autoincrement_sequences", + batchSize: 100, // Default batch size + batchStates: make(map[string]*batchState), } } +func (sg *sequenceGenerator) Reset() { + sg.mu.Lock() + defer sg.mu.Unlock() + sg.batchStates = make(map[string]*batchState) +} + func (sg *sequenceGenerator) Next(ctx context.Context, table, column string) (int64, error) { - // Current implementation fetches new value for each Next call. key := fmt.Sprintf("%s:%s", table, column) + // First get or create the state with a global lock (only for map access) + sg.mu.Lock() + state, exists := sg.batchStates[key] + if !exists { + state = &batchState{ + nextValue: 0, + lastValueInBatch: -1, + } + sg.batchStates[key] = state + } + sg.mu.Unlock() // Release global lock as soon as possible + + // Now lock only the specific sequence state + state.mu.Lock() + defer state.mu.Unlock() + + // If we've used all values in the current batch, get a new batch + if state.nextValue > state.lastValueInBatch { + start, end, err := sg.allocateNewBatch(ctx, key) + if err != nil { + return 0, err + } + state.nextValue = start + state.lastValueInBatch = end + } + + // Return the next value from the batch + val := state.nextValue + state.nextValue++ + return val, nil +} + +// allocateNewBatch retrieves a new batch of sequence values from the database. +// It returns the start and end values of the new batch on success. +func (sg *sequenceGenerator) allocateNewBatch(ctx context.Context, key string) (start, end int64, retErr error) { tx, err := sg.db.BeginTx(ctx, &sql.TxOptions{Isolation: sql.LevelSerializable}) if err != nil { - return 0, err + return 0, 0, err } - // TODO: "FOR UPDATE". (Somehow this doesn't seem to be supported in Spanner emulator?) - r, err := tx.QueryContext(ctx, "SELECT next_value FROM "+sg.sequencesTable+" WHERE name = ?", key) - if err != nil { - err2 := tx.Rollback() - return 0, errors.Join(err, err2) - } - defer r.Close() - - // Sequence doesn't exist yet. Return 1, and put 2 into the table. - if !r.Next() { - if err := r.Err(); err != nil { - return 0, errors.Join(err, tx.Rollback()) + defer func() { + if retErr != nil { + tx.Rollback() } - val := int64(1) + }() - _, err := tx.ExecContext(ctx, "INSERT INTO "+sg.sequencesTable+" (name, next_value) VALUES(?, ?)", key, val+1) + // Query the current sequence value + rows, err := tx.QueryContext(ctx, "SELECT next_value FROM "+sg.sequencesTable+" WHERE name = ?", key) + if err != nil { + return 0, 0, err + } + defer rows.Close() + + // Handle case where sequence doesn't exist yet + if !rows.Next() { + if err = rows.Err(); err != nil { + return 0, 0, err + } + + // This is a new sequence - start from 1 and allocate a batch + batchEnd := sg.batchSize + nextBatchStart := batchEnd + 1 + + // Insert the next batch start value + _, err = tx.ExecContext(ctx, + "INSERT INTO "+sg.sequencesTable+" (name, next_value) VALUES(?, ?)", + key, nextBatchStart) if err != nil { - return 0, errors.Join(err, tx.Rollback()) + return 0, 0, err } - return val, tx.Commit() + // Commit the transaction + if err = tx.Commit(); err != nil { + return 0, 0, err + } + + return 1, batchEnd, nil } - var val int64 - if err := r.Scan(&val); err != nil { - return 0, errors.Join(err, tx.Rollback()) + // Sequence exists - read current value and allocate next batch + var batchStart int64 + if err = rows.Scan(&batchStart); err != nil { + return 0, 0, err } - _, err = tx.ExecContext(ctx, "UPDATE "+sg.sequencesTable+" SET next_value = ? WHERE name = ?", val+1, key) + batchEnd := batchStart + sg.batchSize - 1 + nextBatchStart := batchEnd + 1 + + // Update the next batch start value + _, err = tx.ExecContext(ctx, + "UPDATE "+sg.sequencesTable+" SET next_value = ? WHERE name = ?", + nextBatchStart, key) if err != nil { - return 0, errors.Join(err, tx.Rollback()) + return 0, 0, err } - return val, tx.Commit() + // Commit the transaction + if err = tx.Commit(); err != nil { + return 0, 0, err + } + + return batchStart, batchEnd, nil +} + +// SetBatchSize allows changing the batch size +func (sg *sequenceGenerator) SetBatchSize(size int64) { + sg.mu.Lock() + defer sg.mu.Unlock() + sg.batchSize = size } func (sg *sequenceGenerator) close() { diff --git a/pkg/util/xorm/sequence_inmem.go b/pkg/util/xorm/sequence_inmem.go new file mode 100644 index 00000000000..1d5cfacea76 --- /dev/null +++ b/pkg/util/xorm/sequence_inmem.go @@ -0,0 +1,46 @@ +package xorm + +import ( + "context" + "fmt" + "math/rand/v2" + "sync" +) + +type inMemSequenceGenerator struct { + sequencesMu sync.Mutex + nextValues map[string]int +} + +func newInMemSequenceGenerator() *inMemSequenceGenerator { + return &inMemSequenceGenerator{ + nextValues: make(map[string]int), + } +} + +func (g *inMemSequenceGenerator) Reset() { + g.sequencesMu.Lock() + defer g.sequencesMu.Unlock() + + g.nextValues = make(map[string]int) +} + +func (g *inMemSequenceGenerator) Next(_ context.Context, table, column string) (int64, error) { + if table == "migration_log" { + // Don't use sequential IDs for migration log entries, as we don't clean up migration_log table between tests, + // so restarting the sequence can lead to conflicting IDs. + return rand.Int64(), nil + } + + key := fmt.Sprintf("%s:%s", table, column) + + g.sequencesMu.Lock() + defer g.sequencesMu.Unlock() + + seq, ok := g.nextValues[key] + if !ok { + seq = 1 + } + g.nextValues[key] = seq + 1 + return int64(seq), nil +} diff --git a/pkg/util/xorm/sequence_test.go b/pkg/util/xorm/sequence_test.go index 289e65e4034..b49bb568c8b 100644 --- a/pkg/util/xorm/sequence_test.go +++ b/pkg/util/xorm/sequence_test.go @@ -2,6 +2,7 @@ package xorm import ( "context" + "sync" "testing" "github.com/stretchr/testify/require" @@ -29,3 +30,97 @@ func TestSequenceGenerator(t *testing.T) { require.NoError(t, err) require.Equal(t, int64(2), val) } + +func TestBatchSequenceAllocation(t *testing.T) { + eng, err := NewEngine("sqlite3", ":memory:") + require.NoError(t, err) + + _, err = eng.Exec("CREATE TABLE `autoincrement_sequences` (`name` STRING(128) NOT NULL PRIMARY KEY, `next_value` INT64 NOT NULL)") + require.NoError(t, err) + + // Create sequence generator with small batch size for testing + sg := newSequenceGenerator(eng.db.DB) + sg.SetBatchSize(10) + + // First batch (1-10) + for i := 1; i <= 10; i++ { + val, err := sg.Next(context.Background(), "test", "batch") + require.NoError(t, err) + require.Equal(t, int64(i), val) + } + + // Next value should trigger a new batch (11-20) + val, err := sg.Next(context.Background(), "test", "batch") + require.NoError(t, err) + require.Equal(t, int64(11), val) + + // Check database value is now set to next batch + var nextVal int64 + err = eng.db.QueryRow("SELECT next_value FROM autoincrement_sequences WHERE name = 'test:batch'").Scan(&nextVal) + require.NoError(t, err) + require.Equal(t, int64(21), nextVal, "Database should store the start of the next batch") + + // Continue getting values from the second batch + for i := 12; i <= 20; i++ { + val, err := sg.Next(context.Background(), "test", "batch") + require.NoError(t, err) + require.Equal(t, int64(i), val) + } +} + +func TestConcurrentSequenceAccess(t *testing.T) { + eng, err := NewEngine("sqlite3", ":memory:") + require.NoError(t, err) + + _, err = eng.Exec("CREATE TABLE `autoincrement_sequences` (`name` STRING(128) NOT NULL PRIMARY KEY, `next_value` INT64 NOT NULL)") + require.NoError(t, err) + + sg := newSequenceGenerator(eng.db.DB) + sg.SetBatchSize(100) + + // Launch multiple goroutines to get sequence values + const numRoutines = 50 + const valuesPerRoutine = 20 + results := make([]int64, numRoutines*valuesPerRoutine) + + var wg sync.WaitGroup + var mu sync.Mutex // To protect results slice + + ctx := context.Background() + + for i := 0; i < numRoutines; i++ { + wg.Add(1) + go func(routineID int) { + defer wg.Done() + + for j := 0; j < valuesPerRoutine; j++ { + val, err := sg.Next(ctx, "test", "concurrent") + require.NoError(t, err) + + // Store the result in our results array + mu.Lock() + results[routineID*valuesPerRoutine+j] = val + mu.Unlock() + } + }(i) + } + + wg.Wait() + + // Check that we have the expected number of values + require.Equal(t, numRoutines*valuesPerRoutine, len(results)) + + // Create a map to check for duplicates + seen := make(map[int64]bool) + for _, val := range results { + // Verify we haven't seen this value before + require.False(t, seen[val], "Found duplicate sequence value: %d", val) + seen[val] = true + } + + // Verify the range of values is correct (all values should be between 1 and numRoutines*valuesPerRoutine) + require.Equal(t, numRoutines*valuesPerRoutine, len(seen), "Should have exactly the right number of unique values") + for i := int64(1); i <= int64(numRoutines*valuesPerRoutine); i++ { + require.True(t, seen[i], "Missing sequence value: %d", i) + } +} diff --git a/pkg/util/xorm/xorm.go b/pkg/util/xorm/xorm.go index 25ce8bd952f..d1464af1da4 100644 --- a/pkg/util/xorm/xorm.go +++ b/pkg/util/xorm/xorm.go @@ -9,6 +9,7 @@ package xorm import ( "context" + "database/sql" "fmt" "os" "reflect" @@ -22,6 +23,8 @@ import ( const ( // Version show the xorm's version Version string = "0.8.0.1015" + + Spanner = "spanner" ) func regDrvsNDialects() bool { @@ -97,7 +100,7 @@ func NewEngine(driverName string, dataSourceName string) (*Engine, error) { switch uri.DbType { case core.SQLITE: engine.DatabaseTZ = time.UTC - case "spanner": + case Spanner: engine.DatabaseTZ = time.UTC // We need to specify "Z" to indicate that timestamp is in UTC. // Otherwise Spanner uses default America/Los_Angeles timezone. @@ -114,9 +117,30 @@ func NewEngine(driverName string, dataSourceName string) (*Engine, error) { runtime.SetFinalizer(engine, close) - if dialect.DBType() == "spanner" { - engine.sequenceGenerator = newSequenceGenerator(db.DB) + if ext, ok := dialect.(DialectExt); ok { + engine.sequenceGenerator, err = ext.CreateSequenceGenerator(db.DB) + if err != nil { + return nil, fmt.Errorf("failed to create sequence generator: %w", err) + } } return engine, nil } + +func (engine *Engine) ResetSequenceGenerator() { + if engine.sequenceGenerator != nil { + engine.sequenceGenerator.Reset() + } +} + +type SequenceGenerator interface { + Next(ctx context.Context, table, column string) (int64, error) + Reset() +} + +type DialectExt interface { + core.Dialect + + // CreateSequenceGenerator returns optional generator used to create AUTOINCREMENT ids for inserts. + CreateSequenceGenerator(db *sql.DB) (SequenceGenerator, error) +} diff --git a/public/api-merged.json b/public/api-merged.json index 6beecc94824..3ca4e15b213 100644 --- a/public/api-merged.json +++ b/public/api-merged.json @@ -12654,6 +12654,9 @@ "isPaused": { "type": "boolean" }, + "keepFiringFor": { + "$ref": "#/definitions/Duration" + }, "labels": { "type": "object", "additionalProperties": { @@ -12924,6 +12927,10 @@ "health": { "type": "string" }, + "keepFiringFor": { + "type": "number", + "format": "double" + }, "labels": { "$ref": "#/definitions/Labels" }, @@ -18688,9 +18695,22 @@ "Interval": { "$ref": "#/definitions/Duration" }, + "Labels": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "Limit": { + "type": "integer", + "format": "int64" + }, "Name": { "type": "string" }, + "QueryOffset": { + "type": "string" + }, "Rules": { "type": "array", "items": { @@ -18804,6 +18824,10 @@ "type": "boolean", "example": false }, + "keep_firing_for": { + "type": "string", + "format": "duration" + }, "labels": { "type": "object", "additionalProperties": { @@ -22869,7 +22893,6 @@ } }, "gettableAlerts": { - "description": "GettableAlerts gettable alerts", "type": "array", "items": { "type": "object", diff --git a/public/app/api/clients/provisioning/endpoints.gen.ts b/public/app/api/clients/provisioning/endpoints.gen.ts index 8f22200d13f..a7baef15950 100644 --- a/public/app/api/clients/provisioning/endpoints.gen.ts +++ b/public/app/api/clients/provisioning/endpoints.gen.ts @@ -497,7 +497,7 @@ export type CreateRepositoryExportApiArg = { /** Include the identifier in the exported metadata */ identifier: boolean; /** Prefix in target file system */ - prefix?: string; + path?: string; }; }; export type GetRepositoryFilesApiResponse = /** status 200 OK */ { @@ -587,8 +587,6 @@ export type CreateRepositoryMigrateApiArg = { history?: boolean; /** Include the identifier in the exported metadata */ identifier: boolean; - /** Target file prefix */ - prefix?: string; }; }; export type GetRepositoryRenderWithPathApiResponse = unknown; @@ -748,8 +746,6 @@ export type MigrateJobOptions = { history?: boolean; /** Include the identifier in the exported metadata */ identifier: boolean; - /** Target file prefix */ - prefix?: string; }; export type PullRequestJobOptions = { hash?: string; @@ -772,7 +768,7 @@ export type ExportJobOptions = { /** Include the identifier in the exported metadata */ identifier: boolean; /** Prefix in target file system */ - prefix?: string; + path?: string; }; export type JobSpec = { /** Possible enum values: diff --git a/public/app/api/clients/provisioning/utils/createOnCacheEntryAdded.ts b/public/app/api/clients/provisioning/utils/createOnCacheEntryAdded.ts index 724eb79193d..c3c4f38b8e1 100644 --- a/public/app/api/clients/provisioning/utils/createOnCacheEntryAdded.ts +++ b/public/app/api/clients/provisioning/utils/createOnCacheEntryAdded.ts @@ -48,17 +48,17 @@ export function createOnCacheEntryAdded< } const existingIndex = draft.items.findIndex((item) => item.metadata?.name === event.object.metadata.name); - if (event.type === 'ADDED') { - // Add the new item + if (event.type === 'ADDED' && existingIndex === -1) { // eslint-disable-next-line @typescript-eslint/consistent-type-assertions draft.items.push(event.object as unknown as T); - } else if (event.type === 'MODIFIED' && existingIndex !== -1) { - // Update the existing item if it exists - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - draft.items[existingIndex] = event.object as unknown as T; } else if (event.type === 'DELETED' && existingIndex !== -1) { // Remove the item if it exists draft.items.splice(existingIndex, 1); + } else if (existingIndex !== -1) { + // Could be ADDED or MODIFIED + // Update the existing item if it exists + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + draft.items[existingIndex] = event.object as unknown as T; } }); }); diff --git a/public/app/features/admin/ldap/LdapUserGroups.tsx b/public/app/features/admin/ldap/LdapUserGroups.tsx index 890ec4aee56..565048f1059 100644 --- a/public/app/features/admin/ldap/LdapUserGroups.tsx +++ b/public/app/features/admin/ldap/LdapUserGroups.tsx @@ -1,6 +1,6 @@ import { useMemo } from 'react'; -import { Tooltip, Icon, InteractiveTable, type CellProps, Column } from '@grafana/ui'; +import { Tooltip, Icon, InteractiveTable, type CellProps, Column, Stack } from '@grafana/ui'; import { Trans } from 'app/core/internationalization'; import { LdapRole } from 'app/types'; @@ -28,14 +28,12 @@ export const LdapUserGroups = ({ groups }: Props) => { header: 'Role', cell: (props: CellProps) => props.value || ( - <> - - No match{' '} - - - - - + + No match + + + + ), }, ], diff --git a/public/app/features/alerting/unified/components/rule-editor/NotificationsStep.tsx b/public/app/features/alerting/unified/components/rule-editor/NotificationsStep.tsx index 7d1d429b312..e36e91febb5 100644 --- a/public/app/features/alerting/unified/components/rule-editor/NotificationsStep.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/NotificationsStep.tsx @@ -270,22 +270,16 @@ function NeedHelpInfoForContactpoint() { <> Select a contact point to notify all recipients in it.
+ Muting, grouping, and timings options allow you to customize how notifications are sent.
- Notifications for firing alert instances are grouped based on folder and alert rule name.
- The wait time before sending the first notification for a new group of alerts is 30 seconds. -
- The waiting time before sending a notification about changes in the alert group after the first notification - has been sent is 5 minutes. -
- The wait time before resending a notification that has already been sent successfully is 4 hours. -
- Grouping and wait time values are defined in your default notification policy. + Alternatively, toggle the Advanced options button to route notifications using notification policies + for greater flexibility. } externalLink="https://grafana.com/docs/grafana/latest/alerting/fundamentals/notifications/" linkText="Read more about notifications" - title="Notify contact points" + title="Notify by selecting a contact point" /> ); } diff --git a/public/app/features/alerting/unified/components/rule-editor/QueryRows.tsx b/public/app/features/alerting/unified/components/rule-editor/QueryRows.tsx index 08c67ebf3d2..4e951c5110a 100644 --- a/public/app/features/alerting/unified/components/rule-editor/QueryRows.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/QueryRows.tsx @@ -14,6 +14,7 @@ import { getDataSourceSrv } from '@grafana/runtime'; import { DataQuery } from '@grafana/schema'; import { Button, Card, Icon, Stack } from '@grafana/ui'; import { QueryOperationRow } from 'app/core/components/QueryOperationRow/QueryOperationRow'; +import { isExpressionQuery } from 'app/features/expressions/guards'; import { getDatasourceSrv } from 'app/features/plugins/datasource_srv'; import { AlertDataQuery, AlertQuery } from 'app/types/unified-alerting-dto'; @@ -236,8 +237,10 @@ function copyModel(item: AlertQuery, settings: DataSourceInstanceSettings): Omit } function newModel(item: AlertQuery, settings: DataSourceInstanceSettings): Omit { - const isInstant = getInstantFromDataQuery(item.model, settings.type); - return { + const isExpression = isExpressionQuery(item); + const isInstant = isExpression ? false : getInstantFromDataQuery(item); + + const newQuery: Omit = { refId: item.refId, relativeTimeRange: item.relativeTimeRange, queryType: '', @@ -246,9 +249,14 @@ function newModel(item: AlertQuery, settings: DataSourceInstanceSettings): Omit< refId: item.refId, hide: false, datasource: getDataSourceRef(settings), - instant: isInstant, }, }; + + if (isInstant && !isExpressionQuery(item)) { + (newQuery as AlertQuery).model.instant = isInstant; + } + + return newQuery; } interface DatasourceNotFoundProps { diff --git a/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/AlertRuleForm.tsx b/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/AlertRuleForm.tsx index b99fcac732a..f68296b6fbb 100644 --- a/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/AlertRuleForm.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/AlertRuleForm.tsx @@ -320,7 +320,7 @@ export const AlertRuleForm = ({ existing, prefill, isManualRestore }: Props) => {/* Step 1 */} {/* Step 2 */} - + {/* Step 3-4-5 */} {showDataSourceDependantStep && ( <> diff --git a/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/ModifyExportRuleForm.tsx b/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/ModifyExportRuleForm.tsx index 8b867293622..32e4fd72f13 100644 --- a/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/ModifyExportRuleForm.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/ModifyExportRuleForm.tsx @@ -100,7 +100,7 @@ export function ModifyExportRuleForm({ ruleForm, alertUid }: ModifyExportRuleFor {/* Step 1 */} {/* Step 2 */} - + {/* Step 3-4-5 */} diff --git a/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/simplifiedRouting/AlertManagerRouting.tsx b/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/simplifiedRouting/AlertManagerRouting.tsx index de3cb8bfcc8..3d86b3e33cb 100644 --- a/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/simplifiedRouting/AlertManagerRouting.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/simplifiedRouting/AlertManagerRouting.tsx @@ -3,12 +3,14 @@ import { useEffect, useState } from 'react'; import { useFormContext } from 'react-hook-form'; import { GrafanaTheme2 } from '@grafana/data'; -import { CollapsableSection, Stack, useStyles2 } from '@grafana/ui'; +import { CollapsableSection, Stack, Text, useStyles2 } from '@grafana/ui'; +import { Trans, t } from 'app/core/internationalization'; import { RuleFormValues } from 'app/features/alerting/unified/types/rule-form'; import { AlertManagerDataSource } from 'app/features/alerting/unified/utils/datasource'; import { useContactPointsWithStatus } from '../../../contact-points/useContactPoints'; import { ContactPointWithMetadata } from '../../../contact-points/utils'; +import { NeedHelpInfo } from '../../NeedHelpInfo'; import { ContactPointDetails } from './contactPoint/ContactPointDetails'; import { ContactPointSelector } from './contactPoint/ContactPointSelector'; @@ -57,7 +59,7 @@ export function AlertManagerManualRouting({ alertManager }: AlertManagerManualRo
- Alertmanager: + Alertmanager: Alert manager logo {alertManagerName}
@@ -74,8 +76,37 @@ export function AlertManagerManualRouting({ alertManager }: AlertManagerManualRo label="Muting, grouping and timings (optional)" isOpen={hasRouteSettings} className={styles.collapsableSection} + contentClassName={styles.collapsableSectionContent} > + + + + Configure how notifications for this alert rule are sent. + + + +

+ {t( + 'alerting.rule-form.simple-routing.optional-settings.help-info1', + 'Mute timings allows you to temporarily pause notifications for a specific recurring period, such as a regular maintenance window or weekends.' + )} +

+ {t( + 'alerting.rule-form.simple-routing.optional-settings.help-info2', + 'Grouping and timing options combine multiple alerts within a specific period into a single notification, allowing you to customize default options.' + )} + + } + /> +
@@ -110,6 +141,9 @@ const getStyles = (theme: GrafanaTheme2) => ({ width: 'fit-content', fontSize: theme.typography.body.fontSize, }), + collapsableSectionContent: css({ + padding: '0', + }), routingSection: css({ display: 'flex', flexDirection: 'column', diff --git a/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/simplifiedRouting/route-settings/MuteTimingFields.tsx b/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/simplifiedRouting/route-settings/MuteTimingFields.tsx index 86608e72112..0a7810a37a4 100644 --- a/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/simplifiedRouting/route-settings/MuteTimingFields.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/simplifiedRouting/route-settings/MuteTimingFields.tsx @@ -1,6 +1,8 @@ +import { css } from '@emotion/css'; import { Controller, useFormContext } from 'react-hook-form'; -import { Field } from '@grafana/ui'; +import { GrafanaTheme2 } from '@grafana/data'; +import { Field, useStyles2 } from '@grafana/ui'; import MuteTimingsSelector from 'app/features/alerting/unified/components/alertmanager-entities/MuteTimingsSelector'; import { BaseAlertmanagerArgs } from 'app/features/alerting/unified/types/hooks'; import { RuleFormValues } from 'app/features/alerting/unified/types/rule-form'; @@ -8,6 +10,7 @@ import { mapMultiSelectValueToStrings } from 'app/features/alerting/unified/util /** Provides a form field for use in simplified routing, for selecting appropriate mute timings */ export function MuteTimingFields({ alertmanager }: BaseAlertmanagerArgs) { + const styles = useStyles2(getStyles); const { control, formState: { errors }, @@ -18,6 +21,7 @@ export function MuteTimingFields({ alertmanager }: BaseAlertmanagerArgs) { label="Mute timings" data-testid="am-mute-timing-select" description="Select a mute timing to define when not to send notifications for this alert rule" + className={styles.muteTimingField} invalid={!!errors.contactPoints?.[alertmanager]?.muteTimeIntervals} > ); } +const getStyles = (theme: GrafanaTheme2) => ({ + muteTimingField: css({ + marginTop: theme.spacing(1), + }), +}); diff --git a/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/QueryAndExpressionsStep.tsx b/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/QueryAndExpressionsStep.tsx index 20d3359a03e..463401721a0 100644 --- a/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/QueryAndExpressionsStep.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/QueryAndExpressionsStep.tsx @@ -23,7 +23,12 @@ import { } from '@grafana/ui'; import { Trans, t } from 'app/core/internationalization'; import { isExpressionQuery } from 'app/features/expressions/guards'; -import { ExpressionDatasourceUID, ExpressionQueryType, expressionTypes } from 'app/features/expressions/types'; +import { + ExpressionDatasourceUID, + ExpressionQuery, + ExpressionQueryType, + expressionTypes, +} from 'app/features/expressions/types'; import { AlertQuery } from 'app/types/unified-alerting-dto'; import { useRulesSourcesWithRuler } from '../../../hooks/useRuleSourcesWithRuler'; @@ -50,7 +55,7 @@ import { RuleEditorSection } from '../RuleEditorSection'; import { errorFromCurrentCondition, errorFromPreviewData, findRenamedDataQueryReferences, refIdExists } from '../util'; import { CloudDataSourceSelector } from './CloudDataSourceSelector'; -import { SimpleConditionEditor, SimpleConditionIdentifier, getSimpleConditionFromExpressions } from './SimpleCondition'; +import { SimpleConditionEditor, getSimpleConditionFromExpressions } from './SimpleCondition'; import { SmartAlertTypeDetector } from './SmartAlertTypeDetector'; import { DESCRIPTIONS } from './descriptions'; import { @@ -77,9 +82,15 @@ import { useAlertQueryRunner } from './useAlertQueryRunner'; interface Props { editingExistingRule: boolean; onDataChange: (error: string) => void; + /** + * The mode of the rule editor. + * - 'edit' standard rule editor mode + * - 'draft' non-saveable form mode used for exporting to provisioning formats + */ + mode: 'edit' | 'draft'; } -export const QueryAndExpressionsStep = ({ editingExistingRule, onDataChange }: Props) => { +export const QueryAndExpressionsStep = ({ editingExistingRule, onDataChange, mode }: Props) => { const { setValue, getValues, @@ -140,7 +151,7 @@ export const QueryAndExpressionsStep = ({ editingExistingRule, onDataChange }: P ); const simplifiedQueryStep = - isSwitchModeEnabled && isGrafanaAlertingType ? getValues('editorSettings.simplifiedQueryEditor') : false; + isSwitchModeEnabled && isGrafanaAlertingType ? editorSettings?.simplifiedQueryEditor : false; // If we switch to simple mode we need to update the simple condition with the data in the queries reducer useEffect(() => { @@ -158,15 +169,22 @@ export const QueryAndExpressionsStep = ({ editingExistingRule, onDataChange }: P // Grafana Managed rules and recording rules do return; } - // we need to be sure the condition is set once we switch to simple mode + if (simplifiedQueryStep) { - setValue('condition', SimpleConditionIdentifier.thresholdId); - runQueries(getValues('queries'), SimpleConditionIdentifier.thresholdId); + const lastExpression = expressionQueries.at(-1); + if (!lastExpression) { + return; + } + + const condition = lastExpression.refId; + // we need to be sure the condition is set once we switch to simple mode + setValue('condition', condition); + runQueries(getValues('queries'), condition); } else { runQueries(getValues('queries'), condition || (getValues('condition') ?? '')); } }, - [isCloudAlertRuleType, runQueries, getValues, simplifiedQueryStep, setValue] + [isCloudAlertRuleType, expressionQueries, simplifiedQueryStep, setValue, runQueries, getValues] ); // whenever we update the queries we have to update the form too @@ -241,7 +259,9 @@ export const QueryAndExpressionsStep = ({ editingExistingRule, onDataChange }: P // As a workaround we update form values as soon as possible to avoid stale state // This way we can access up to date queries in runQueriesPreview without waiting for re-render const previousQueries = getValues('queries'); - const expressionQueries = previousQueries.filter((query) => isExpressionQuery(query.model)); + + const expressionQueries = previousQueries.filter>(isExpressionQueryInAlert); + setValue('queries', [...updatedQueries, ...expressionQueries], { shouldValidate: false }); updateExpressionAndDatasource(updatedQueries); @@ -515,12 +535,14 @@ export const QueryAndExpressionsStep = ({ editingExistingRule, onDataChange }: P }} /> - + {mode === 'edit' && ( + + )} )} @@ -555,7 +577,7 @@ export const QueryAndExpressionsStep = ({ editingExistingRule, onDataChange }: P )} {/* We only show Switch for Grafana managed alerts */} - {isGrafanaAlertingType && !simplifiedQueryStep && ( + {isGrafanaAlertingType && !simplifiedQueryStep && mode === 'edit' && ( >, dispatch: Dispatch ) { - const reduceExpression = expressionQueriesList.find( - (query) => - query.model.type === ExpressionQueryType.reduce && query.model.refId === SimpleConditionIdentifier.reducerId - ); + // 1. make sure have have a reduce expression and that it is pointing to the data query + const reduceExpression = expressionQueriesList.find((query) => query.model.type === ExpressionQueryType.reduce); const newReduceExpression = reduceExpression ? produce(reduceExpression?.model, (draft) => { @@ -179,10 +172,7 @@ function updateThresholdFunction( expressionQueriesList: Array>, dispatch: Dispatch ) { - const thresholdExpression = expressionQueriesList.find( - (query) => - query.model.type === ExpressionQueryType.threshold && query.model.refId === SimpleConditionIdentifier.thresholdId - ); + const thresholdExpression = expressionQueriesList.find((query) => query.model.type === ExpressionQueryType.threshold); const newThresholdExpression = produce(thresholdExpression, (draft) => { if (draft && draft.model.conditions) { @@ -198,10 +188,7 @@ function updateThresholdValue( expressionQueriesList: Array>, dispatch: Dispatch ) { - const thresholdExpression = expressionQueriesList.find( - (query) => - query.model.type === ExpressionQueryType.threshold && query.model.refId === SimpleConditionIdentifier.thresholdId - ); + const thresholdExpression = expressionQueriesList.find((query) => query.model.type === ExpressionQueryType.threshold); const newThresholdExpression = produce(thresholdExpression, (draft) => { if (draft && draft.model.conditions) { @@ -212,13 +199,8 @@ function updateThresholdValue( } export function getSimpleConditionFromExpressions(expressions: Array>): SimpleCondition { - const reduceExpression = expressions.find( - (query) => query.model.type === ExpressionQueryType.reduce && query.refId === SimpleConditionIdentifier.reducerId - ); - const thresholdExpression = expressions.find( - (query) => - query.model.type === ExpressionQueryType.threshold && query.refId === SimpleConditionIdentifier.thresholdId - ); + const reduceExpression = expressions.find((query) => query.model.type === ExpressionQueryType.reduce); + const thresholdExpression = expressions.find((query) => query.model.type === ExpressionQueryType.threshold); const conditionsFromThreshold = thresholdExpression?.model.conditions ?? []; const whenField = reduceExpression?.model.reducer; const params = conditionsFromThreshold[0]?.evaluator?.params diff --git a/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/__snapshots__/areQueriesTransformableToSimpleCondition.test.ts b/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/__snapshots__/areQueriesTransformableToSimpleCondition.test.ts index 2a9294ada7d..96be5b02dc5 100644 --- a/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/__snapshots__/areQueriesTransformableToSimpleCondition.test.ts +++ b/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/__snapshots__/areQueriesTransformableToSimpleCondition.test.ts @@ -1,35 +1,50 @@ import { produce } from 'immer'; import { EvalFunction } from 'app/features/alerting/state/alertDef'; -import { dataQuery, reduceExpression, thresholdExpression } from 'app/features/alerting/unified/mocks'; +import { + mockDataQuery, + mockDataSource, + mockReduceExpression, + mockThresholdExpression, +} from 'app/features/alerting/unified/mocks'; import { areQueriesTransformableToSimpleCondition } from 'app/features/alerting/unified/rule-editor/formProcessing'; +import { setupDataSources } from 'app/features/alerting/unified/testSetup/datasources'; +import { DataSourceType } from 'app/features/alerting/unified/utils/datasource'; import { ExpressionQuery, ReducerMode } from 'app/features/expressions/types'; import { AlertDataQuery, AlertQuery } from 'app/types/unified-alerting-dto'; -const expressionQueries: Array> = [reduceExpression, thresholdExpression]; +const reduceExpression = mockReduceExpression({ expression: 'A', settings: { mode: ReducerMode.Strict } }); +const thresholdExpression = mockThresholdExpression({ expression: 'B' }); +const expressionQueries: Array> = [reduceExpression, thresholdExpression]; +const ds = mockDataSource({ type: DataSourceType.Prometheus, name: 'Mimir-cloud', uid: 'abc123' }); describe('areQueriesTransformableToSimpleCondition', () => { + beforeEach(() => { + setupDataSources(ds); + }); it('should return false if dataQueries length is not 1', () => { // zero dataQueries expect(areQueriesTransformableToSimpleCondition([], expressionQueries)).toBe(false); // more than one dataQueries - expect(areQueriesTransformableToSimpleCondition([dataQuery, dataQuery], expressionQueries)).toBe(false); + expect(areQueriesTransformableToSimpleCondition([mockDataQuery(), mockDataQuery()], expressionQueries)).toBe(false); }); + it('should return false if expressionQueries length is not 2', () => { - const dataQueries: Array> = [dataQuery]; + const dataQueries: Array> = [mockDataQuery()]; const result = areQueriesTransformableToSimpleCondition(dataQueries, []); expect(result).toBe(false); }); - it('should return false if the dataQuery refId does not match SimpleConditionIdentifier.queryId', () => { - const dataQueries: Array> = [ - { refId: 'notSimpleCondition', datasourceUid: 'abc123', queryType: '', model: { refId: 'notSimpleCondition' } }, - ]; + // notSimpleCondition + // reducer: + it('should return false if the mockDataQuery() refId does not match SimpleConditionIdentifier.queryId', () => { + const dataQueries: Array> = [mockDataQuery({ refId: 'foo' })]; const result = areQueriesTransformableToSimpleCondition(dataQueries, expressionQueries); expect(result).toBe(false); }); + it('should return false if no reduce expression is found with correct type and refId', () => { - const dataQueries: Array> = [dataQuery]; + const dataQueries: Array> = [mockDataQuery()]; const result = areQueriesTransformableToSimpleCondition(dataQueries, [ { ...reduceExpression, refId: 'hello' }, thresholdExpression, @@ -37,17 +52,25 @@ describe('areQueriesTransformableToSimpleCondition', () => { expect(result).toBe(false); }); - it('should return false if no threshold expression is found with correct type and refId', () => { - const dataQueries: Array> = [dataQuery]; + it('should return false if no threshold expression is found that points to reducer', () => { + const dataQueries: Array> = [mockDataQuery()]; const result = areQueriesTransformableToSimpleCondition(dataQueries, [ reduceExpression, - { ...thresholdExpression, refId: 'hello' }, + mockThresholdExpression({ expression: 'hello' }), + ]); + expect(result).toBe(false); + }); + + it('should return false if no threshold expression is found that points to instant data query', () => { + const dataQueries: Array> = [mockDataQuery({ instant: true })]; + const result = areQueriesTransformableToSimpleCondition(dataQueries, [ + mockThresholdExpression({ expression: 'hello' }), ]); expect(result).toBe(false); }); it('should return false if reduceExpression settings mode is not ReducerMode.Strict', () => { - const dataQueries: Array> = [dataQuery]; + const dataQueries: Array> = [mockDataQuery()]; const transformedReduceExpression = produce(reduceExpression, (draft) => { draft.model.settings = { mode: ReducerMode.DropNonNumbers }; }); @@ -60,7 +83,7 @@ describe('areQueriesTransformableToSimpleCondition', () => { }); it('should return false if thresholdExpression unloadEvaluator has a value', () => { - const dataQueries: Array> = [dataQuery]; + const dataQueries: Array> = [mockDataQuery()]; const transformedThresholdExpression = produce(thresholdExpression, (draft) => { draft.model.conditions = [ @@ -79,9 +102,17 @@ describe('areQueriesTransformableToSimpleCondition', () => { ]); expect(result).toBe(false); }); - it('should return true when all conditions are met', () => { - const dataQueries: Array> = [dataQuery]; - const result = areQueriesTransformableToSimpleCondition(dataQueries, expressionQueries); + + it('should return true when data query is connected to valid reducer and threshold', () => { + const result = areQueriesTransformableToSimpleCondition([mockDataQuery({ refId: 'A' })], expressionQueries); + expect(result).toBe(true); + }); + + it('should return true when all conditions are met for instant data query with threshold', () => { + const result = areQueriesTransformableToSimpleCondition( + [mockDataQuery({ instant: true })], + [mockThresholdExpression({ expression: 'A' })] + ); expect(result).toBe(true); }); }); diff --git a/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/__snapshots__/reducer.test.tsx.snap b/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/__snapshots__/reducer.test.tsx.snap index a316611bf55..292a6904f80 100644 --- a/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/__snapshots__/reducer.test.tsx.snap +++ b/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/__snapshots__/reducer.test.tsx.snap @@ -130,16 +130,16 @@ exports[`Query and expressions reducer should add reduce expression if there is }, "expression": "A", "reducer": "last", - "refId": "B", + "refId": "reducer", "type": "reduce", }, "queryType": "expression", - "refId": "B", + "refId": "reducer", }, { "datasourceUid": "__expr__", "model": { - "expression": "B", + "expression": "reducer", "refId": "C", "type": "threshold", }, @@ -218,6 +218,31 @@ exports[`Query and expressions reducer should remove first reducer 1`] = ` } `; +exports[`Query and expressions reducer should remove reducer even if reducer is not the first expression 1`] = ` +{ + "queries": [ + { + "datasourceUid": "abc123", + "model": { + "refId": "A", + }, + "queryType": "query", + "refId": "A", + }, + { + "datasourceUid": "__expr__", + "model": { + "expression": "A", + "refId": "C", + "type": "threshold", + }, + "queryType": "expression", + "refId": "C", + }, + ], +} +`; + exports[`Query and expressions reducer should rewire expressions 1`] = ` { "queries": [ diff --git a/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/reducer.test.tsx b/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/reducer.test.tsx index 2a112e089c3..73372e12a57 100644 --- a/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/reducer.test.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/reducer.test.tsx @@ -10,7 +10,6 @@ import { import { defaultCondition } from 'app/features/expressions/utils/expressionTypes'; import { AlertQuery } from 'app/types/unified-alerting-dto'; -import { SimpleConditionIdentifier } from './SimpleCondition'; import { QueriesAndExpressionsState, addNewDataQuery, @@ -28,22 +27,23 @@ import { } from './reducer'; const reduceExpression: AlertQuery = { - refId: SimpleConditionIdentifier.reducerId, + refId: 'B', queryType: 'expression', datasourceUid: '__expr__', model: { type: ExpressionQueryType.reduce, - refId: SimpleConditionIdentifier.reducerId, + refId: 'B', settings: { mode: ReducerMode.Strict }, + expression: 'A', }, }; const thresholdExpression: AlertQuery = { - refId: SimpleConditionIdentifier.thresholdId, + refId: 'C', queryType: 'expression', datasourceUid: '__expr__', model: { type: ExpressionQueryType.threshold, - refId: SimpleConditionIdentifier.thresholdId, + refId: 'C', }, }; @@ -400,7 +400,7 @@ describe('Query and expressions reducer', () => { expect(newState).toMatchSnapshot(); }); - it('should not remove first reducer if reducer is not the first expression', () => { + it('should remove reducer even if reducer is not the first expression', () => { const initialState: QueriesAndExpressionsState = { queries: [alertQuery, thresholdExpression, reduceExpression], }; @@ -412,7 +412,7 @@ describe('Query and expressions reducer', () => { expressionQueries: [thresholdExpression, reduceExpression], }) ); - expect(newState).toEqual(initialState); + expect(newState).toMatchSnapshot(); }); it('should not remove first reducer if reducer is not the second query', () => { diff --git a/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/reducer.ts b/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/reducer.ts index aab9e184a56..b3499b8c520 100644 --- a/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/reducer.ts +++ b/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/reducer.ts @@ -8,21 +8,25 @@ import { getNextRefId, rangeUtil, } from '@grafana/data'; -import { getDataSourceSrv } from '@grafana/runtime'; import { DataQuery } from '@grafana/schema'; import { dataSource as expressionDatasource } from 'app/features/expressions/ExpressionDatasource'; import { isExpressionQuery } from 'app/features/expressions/guards'; import { ExpressionDatasourceUID, ExpressionQuery, ExpressionQueryType } from 'app/features/expressions/types'; -import { defaultCondition } from 'app/features/expressions/utils/expressionTypes'; +import { + defaultCondition, + isReducerExpression, + isThresholdExpression, +} from 'app/features/expressions/utils/expressionTypes'; import { AlertQuery } from 'app/types/unified-alerting-dto'; import { logError } from '../../../Analytics'; -import { DataSourceType, getDefaultOrFirstCompatibleDataSource } from '../../../utils/datasource'; +import { getDefaultOrFirstCompatibleDataSource } from '../../../utils/datasource'; import { getDefaultQueries, getInstantFromDataQuery } from '../../../utils/rule-form'; import { createDagFromQueries, getOriginOfRefId } from '../dag'; import { queriesWithUpdatedReferences, refIdExists } from '../util'; -import { SimpleConditionIdentifier } from './SimpleCondition'; +// this one will be used as the refID when we create a new reducer for the threshold expression +export const NEW_REDUCER_REF = 'reducer'; export interface QueriesAndExpressionsState { queries: AlertQuery[]; @@ -64,9 +68,10 @@ export const updateMaxDataPoints = createAction<{ refId: string; maxDataPoints: export const updateMinInterval = createAction<{ refId: string; minInterval: string }>('updateMinInterval'); export const resetToSimpleCondition = createAction('resetToSimpleCondition'); -export const optimizeReduceExpression = createAction<{ updatedQueries: AlertQuery[]; expressionQueries: AlertQuery[] }>( - 'optimizeReduceExpression' -); +export const optimizeReduceExpression = createAction<{ + updatedQueries: AlertQuery[]; + expressionQueries: Array>; +}>('optimizeReduceExpression'); export const setRecordingRulesQueries = createAction<{ recordingRuleQueries: AlertQuery[]; expression: string }>( 'setRecordingRulesQueries' ); @@ -231,6 +236,7 @@ export const queriesAndExpressionsReducer = createReducer(initialState, (builder .addCase(rewireExpressions, (state, { payload }) => { state.queries = queriesWithUpdatedReferences(state.queries, payload.oldRefId, payload.newRefId); }) + // removes the reduce expression when we have a instant data query .addCase(optimizeReduceExpression, (state, { payload }) => { const { updatedQueries, expressionQueries } = payload; @@ -239,48 +245,29 @@ export const queriesAndExpressionsReducer = createReducer(initialState, (builder return; } - //sometimes we dont have data source in the model yet - const getDataSourceSettingsForFirstQuery = getDataSourceSrv().getInstanceSettings( - updatedQueries[0].datasourceUid - ); - - if (!getDataSourceSettingsForFirstQuery) { - return; - } - const type = getDataSourceSettingsForFirstQuery?.type; - - const firstQueryIsPromOrLoki = type === DataSourceType.Prometheus || type === DataSourceType.Loki; - - const isInstant = getInstantFromDataQuery(updatedQueries[0].model, type); - - const shouldRemoveReducer = - firstQueryIsPromOrLoki && updatedQueries.length === 1 && isInstant && expressionQueries.length === 2; - - const onlyOneExpressionNotReducer = - expressionQueries.length === 1 && - 'type' in expressionQueries[0].model && - expressionQueries[0].model.type !== ExpressionQueryType.reduce; - - // we only add the reduce expression if we have one data query and one expression query. For other cases we don't do anything, - // and let the user add the reducer manually. - const shouldAddReduceExpression = - firstQueryIsPromOrLoki && updatedQueries.length === 1 && !isInstant && onlyOneExpressionNotReducer; + const dataQuery = updatedQueries.at(0); + const isInstantDataQuery = dataQuery ? getInstantFromDataQuery(dataQuery) : false; + const shouldRemoveReducer = isInstantDataQuery && expressionQueries.length === 2; if (shouldRemoveReducer) { const reduceExpressionIndex = state.queries.findIndex( - (query) => isExpressionQuery(query.model) && query.model.type === ExpressionQueryType.reduce + (query) => + isExpressionQuery(query.model) && + isReducerExpression(query.model) && + query.model.expression === dataQuery?.refId ); - if (reduceExpressionIndex === 1) { - // means the reduce expression is the second query - state.queries.splice(reduceExpressionIndex, 1); - state.queries[1].model.expression = SimpleConditionIdentifier.queryId; - } + state.queries.splice(reduceExpressionIndex, 1); + state.queries[1].model.expression = dataQuery?.refId; } + + const shouldAddReduceExpression = + !isInstantDataQuery && expressionQueries.length === 1 && isThresholdExpression(expressionQueries[0].model); if (shouldAddReduceExpression) { // add reducer to the second position // we only update the refid and the model to point to the reducer expression - state.queries[1].model.expression = SimpleConditionIdentifier.reducerId; + state.queries[1].model.expression = NEW_REDUCER_REF; + // insert in second position the reducer expression state.queries.splice(1, 0, { datasourceUid: ExpressionDatasourceUID, @@ -288,10 +275,10 @@ export const queriesAndExpressionsReducer = createReducer(initialState, (builder type: ExpressionQueryType.reduce, reducer: ReducerID.last, conditions: [{ ...defaultCondition, query: { params: [] } }], - expression: SimpleConditionIdentifier.queryId, - refId: SimpleConditionIdentifier.reducerId, + expression: dataQuery?.refId, + refId: NEW_REDUCER_REF, }), - refId: SimpleConditionIdentifier.reducerId, + refId: NEW_REDUCER_REF, queryType: 'expression', }); } diff --git a/public/app/features/alerting/unified/components/rule-editor/util.test.ts b/public/app/features/alerting/unified/components/rule-editor/util.test.ts index 680367f38ec..13a4a6480b5 100644 --- a/public/app/features/alerting/unified/components/rule-editor/util.test.ts +++ b/public/app/features/alerting/unified/components/rule-editor/util.test.ts @@ -2,6 +2,7 @@ import { ExpressionDatasourceRef } from '@grafana/runtime/src/utils/DataSourceWi import { ClassicCondition, ExpressionQuery } from 'app/features/expressions/types'; import { AlertQuery } from 'app/types/unified-alerting-dto'; +import { NEW_REDUCER_REF } from './query-and-alert-condition/reducer'; import { containsPathSeparator, findRenamedDataQueryReferences, @@ -163,10 +164,10 @@ describe('rule-editor', () => { it('should rewire threshold expressions', () => { const queries: AlertQuery[] = [dataSource, reduceExpression, thresholdExpression]; - const rewiredQueries = queriesWithUpdatedReferences(queries, 'B', 'REDUCER'); + const rewiredQueries = queriesWithUpdatedReferences(queries, 'B', NEW_REDUCER_REF); const queryModel = rewiredQueries[2].model as ExpressionQuery; - expect(queryModel.expression).toBe('REDUCER'); + expect(queryModel.expression).toBe(NEW_REDUCER_REF); }); it('should rewire multiple expressions', () => { diff --git a/public/app/features/alerting/unified/mocks.ts b/public/app/features/alerting/unified/mocks.ts index 8e2701e0e04..4e0cd678109 100644 --- a/public/app/features/alerting/unified/mocks.ts +++ b/public/app/features/alerting/unified/mocks.ts @@ -56,7 +56,6 @@ import { import { DashboardSearchItem, DashboardSearchItemType } from '../../search/types'; -import { SimpleConditionIdentifier } from './components/rule-editor/query-and-alert-condition/SimpleCondition'; import { GRAFANA_RULES_SOURCE_NAME } from './utils/datasource'; import { parsePromQLStyleMatcherLooseSafe } from './utils/matchers'; @@ -772,30 +771,33 @@ export function mockDashboardDto( }; } -export const dataQuery: AlertQuery = { - refId: SimpleConditionIdentifier.queryId, +export const mockDataQuery = (partial: Partial = {}): AlertQuery => ({ + refId: partial?.refId ?? 'A', datasourceUid: 'abc123', queryType: '', - model: { refId: SimpleConditionIdentifier.queryId }, -}; + model: { refId: 'A', ...partial }, +}); -export const reduceExpression: AlertQuery = { - refId: SimpleConditionIdentifier.reducerId, +export const mockReduceExpression = (partial: Partial = {}): AlertQuery => ({ + refId: 'B', queryType: 'expression', datasourceUid: '__expr__', model: { type: ExpressionQueryType.reduce, - refId: SimpleConditionIdentifier.reducerId, + refId: 'B', settings: { mode: ReducerMode.Strict }, reducer: ReducerID.last, + ...partial, }, -}; -export const thresholdExpression: AlertQuery = { - refId: SimpleConditionIdentifier.thresholdId, +}); + +export const mockThresholdExpression = (partial: Partial = {}): AlertQuery => ({ + refId: 'C', queryType: 'expression', datasourceUid: '__expr__', model: { type: ExpressionQueryType.threshold, - refId: SimpleConditionIdentifier.thresholdId, + refId: 'C', + ...partial, }, -}; +}); diff --git a/public/app/features/alerting/unified/rule-editor/formDefaults.test.ts b/public/app/features/alerting/unified/rule-editor/formDefaults.test.ts index 800b95a6193..9dd60a76b8d 100644 --- a/public/app/features/alerting/unified/rule-editor/formDefaults.test.ts +++ b/public/app/features/alerting/unified/rule-editor/formDefaults.test.ts @@ -1,6 +1,6 @@ import { config } from '@grafana/runtime'; -import { mockAlertQuery, mockDataSource, reduceExpression, thresholdExpression } from '../mocks'; +import { mockAlertQuery, mockDataSource, mockReduceExpression, mockThresholdExpression } from '../mocks'; import { testWithFeatureToggles } from '../test/test-utils'; import { RuleFormType } from '../types/rule-form'; import { Annotation } from '../utils/constants'; @@ -73,7 +73,11 @@ describe('formValuesFromQueryParams', () => { it('should enable simplified query editor if queries are transformable to simple condition', () => { const result = formValuesFromQueryParams( JSON.stringify({ - queries: [mockAlertQuery(), reduceExpression, thresholdExpression], + queries: [ + mockAlertQuery(), + mockReduceExpression({ expression: 'A' }), + mockThresholdExpression({ expression: 'B' }), + ], }), RuleFormType.grafana ); @@ -85,7 +89,7 @@ describe('formValuesFromQueryParams', () => { it('should disable simplified query editor if queries are not transformable to simple condition', () => { const result = formValuesFromQueryParams( JSON.stringify({ - queries: [mockAlertQuery(), mockAlertQuery(), thresholdExpression], + queries: [mockAlertQuery(), mockAlertQuery(), mockThresholdExpression({ expression: 'B' })], }), RuleFormType.grafana ); diff --git a/public/app/features/alerting/unified/rule-editor/formProcessing.ts b/public/app/features/alerting/unified/rule-editor/formProcessing.ts index c9fa1951417..d9f79892ce5 100644 --- a/public/app/features/alerting/unified/rule-editor/formProcessing.ts +++ b/public/app/features/alerting/unified/rule-editor/formProcessing.ts @@ -1,14 +1,15 @@ -import { omit } from 'lodash'; +import { isEmpty, omit } from 'lodash'; import { config } from '@grafana/runtime'; import { isExpressionQuery } from 'app/features/expressions/guards'; -import { ExpressionQuery, ExpressionQueryType, ReducerMode } from 'app/features/expressions/types'; +import { ExpressionQuery, ExpressionQueryType } from 'app/features/expressions/types'; +import { isStrictReducer } from 'app/features/expressions/utils/expressionTypes'; import { AlertDataQuery, AlertQuery } from 'app/types/unified-alerting-dto'; -import { SimpleConditionIdentifier } from '../components/rule-editor/query-and-alert-condition/SimpleCondition'; import { KVObject, RuleFormValues } from '../types/rule-form'; import { defaultAnnotations } from '../utils/constants'; import { DataSourceType } from '../utils/datasource'; +import { getInstantFromDataQuery } from '../utils/rule-form'; export function setQueryEditorSettings(values: RuleFormValues): RuleFormValues { const isQuerySwitchModeEnabled = config.featureToggles.alertingQueryAndExpressionsStepMode ?? false; @@ -64,47 +65,63 @@ export function setInstantOrRange(values: RuleFormValues): RuleFormValues { }; } +/** + * A alert rule is "transformable" to a simple condition editor if + * 1. we have a single data query + * 2. we have _either_ + * 2.1 a reduce expression (pointing to the data query) _and_ a threshold expression pointing to the reducer + * 2.2 a threshold expression pointing to a (instant) data query + * ⚠️ do not assert on refIds or indexes of the queries + */ export function areQueriesTransformableToSimpleCondition( - dataQueries: Array>, + dataQueries: Array>, expressionQueries: Array> ) { + // 1. check if we only have a _single_ data query if (dataQueries.length !== 1) { return false; } - const singleReduceExpressionInInstantQuery = - 'instant' in dataQueries[0].model && dataQueries[0].model.instant && expressionQueries.length === 1; - if (expressionQueries.length !== 2 && !singleReduceExpressionInInstantQuery) { + // short-circuit when we have more than 2 expressions, we don't know what to do with that + if (expressionQueries.length > 2) { return false; } - const query = dataQueries[0]; + const dataQuery = dataQueries.at(0); - if (query.refId !== SimpleConditionIdentifier.queryId) { - return false; + // find the reduce or threshold expressions + const reduceExpression = expressionQueries.find((query) => query.model.type === ExpressionQueryType.reduce); + const thresholdExpression = expressionQueries.find((query) => query.model.type === ExpressionQueryType.threshold); + + // reducer should be set to "strict" mode + const reducerIsStrict = reduceExpression ? isStrictReducer(reduceExpression.model) : false; + // threshold expression shouldn't have an unload evaluator (custom recovery threshold) + const thresholdExpressionIsClean = + thresholdExpression?.model.conditions?.every((condition) => { + return isEmpty(condition.unloadEvaluator); + }) ?? true; + + const validReducerExpression = reduceExpression && reducerIsStrict; + const validThresholdExpression = thresholdExpression && thresholdExpressionIsClean; + + const thresholdPointingToReducer = thresholdExpression?.model.expression === reduceExpression?.refId; + const reducerPointingToDataQuery = reduceExpression?.model.expression === dataQuery?.refId; + + // 2.1 check for a reduce + threshold expression and their targets + if (validReducerExpression && reducerPointingToDataQuery && validThresholdExpression && thresholdPointingToReducer) { + return true; } - const reduceExpressionIndex = expressionQueries.findIndex( - (query) => query.model.type === ExpressionQueryType.reduce && query.refId === SimpleConditionIdentifier.reducerId - ); - const reduceExpression = expressionQueries.at(reduceExpressionIndex); - const reduceOk = - reduceExpression && - reduceExpressionIndex === 0 && - (reduceExpression.model.settings?.mode === ReducerMode.Strict || - reduceExpression.model.settings?.mode === undefined); + // 2.2 check for a single threshold expression pointing to an "instant" data query + const isInstantDataQuery = dataQuery ? getInstantFromDataQuery(dataQuery) : false; + const hasSingleThresholdExpression = expressionQueries.length === 1 && thresholdExpression; + const thresholdPointingToDataQuery = thresholdExpression?.model.expression === dataQuery?.refId; - const thresholdExpressionIndex = expressionQueries.findIndex( - (query) => - query.model.type === ExpressionQueryType.threshold && query.refId === SimpleConditionIdentifier.thresholdId - ); - const thresholdExpression = expressionQueries.at(thresholdExpressionIndex); - const conditions = thresholdExpression?.model.conditions ?? []; - const thresholdIndexOk = singleReduceExpressionInInstantQuery - ? thresholdExpressionIndex === 0 - : thresholdExpressionIndex === 1; - const thresholdOk = thresholdExpression && thresholdIndexOk && conditions[0]?.unloadEvaluator === undefined; - return (Boolean(reduceOk) || Boolean(singleReduceExpressionInInstantQuery)) && Boolean(thresholdOk); + if (isInstantDataQuery && hasSingleThresholdExpression && validThresholdExpression && thresholdPointingToDataQuery) { + return true; + } + + return false; } export function isExpressionQueryInAlert( diff --git a/public/app/features/alerting/unified/utils/rule-form.test.ts b/public/app/features/alerting/unified/utils/rule-form.test.ts index e8939c0c336..5c13f97c954 100644 --- a/public/app/features/alerting/unified/utils/rule-form.test.ts +++ b/public/app/features/alerting/unified/utils/rule-form.test.ts @@ -1,10 +1,18 @@ import { PromQuery } from '@grafana/prometheus'; -import { GrafanaAlertStateDecision, GrafanaRuleDefinition, RulerAlertingRuleDTO } from 'app/types/unified-alerting-dto'; +import { + AlertDataQuery, + AlertQuery, + GrafanaAlertStateDecision, + GrafanaRuleDefinition, + RulerAlertingRuleDTO, +} from 'app/types/unified-alerting-dto'; +import { mockDataSource } from '../mocks'; import { getDefaultFormValues } from '../rule-editor/formDefaults'; +import { setupDataSources } from '../testSetup/datasources'; import { AlertManagerManualRouting, RuleFormType, RuleFormValues } from '../types/rule-form'; -import { GRAFANA_RULES_SOURCE_NAME } from './datasource'; +import { DataSourceType, GRAFANA_RULES_SOURCE_NAME } from './datasource'; import { alertingRulerRuleToRuleForm, cleanAnnotations, @@ -12,6 +20,7 @@ import { formValuesToRulerGrafanaRuleDTO, formValuesToRulerRuleDTO, getContactPointsFromDTO, + getInstantFromDataQuery, getNotificationSettingsForDTO, } from './rule-form'; @@ -254,3 +263,71 @@ describe('cleanLabels', () => { expect(output).toStrictEqual([{ key: 'key', value: '' }]); }); }); + +describe('getInstantFromDataQuery', () => { + const query: AlertQuery = { + refId: 'Q', + datasourceUid: 'abc123', + queryType: '', + relativeTimeRange: { + from: 600, + to: 0, + }, + model: { + refId: 'Q', + }, + }; + + it('should return undefined if datasource UID is undefined', () => { + setupDataSources(mockDataSource({ type: DataSourceType.Prometheus, name: 'Mimir-cloud', uid: 'mimir-1' })); + const result = getInstantFromDataQuery({ ...query }); + expect(result).toBeUndefined(); + }); + + it('should return undefined if datasource type is not Prometheus or Loki', () => { + setupDataSources(mockDataSource({ type: DataSourceType.Alertmanager, name: 'aa', uid: 'aa-1' })); + const result = getInstantFromDataQuery({ ...query, datasourceUid: 'aa' }); + expect(result).toBeUndefined(); + }); + + it('should return true if datasource is Prometheus and instant is not defined', () => { + setupDataSources(mockDataSource({ type: DataSourceType.Prometheus, name: 'aa', uid: 'aa-1' })); + const result = getInstantFromDataQuery({ ...query, datasourceUid: 'aa' }); + + expect(result).toBe(true); + }); + + it('should return the value of instant if datasource is Prometheus and instant is defined', () => { + setupDataSources(mockDataSource({ type: DataSourceType.Prometheus, name: 'aa', uid: 'aa-1' })); + const result = getInstantFromDataQuery({ ...query, datasourceUid: 'aa', model: { refId: 'f', instant: false } }); + expect(result).toBe(false); + }); + + it('should return true if datasource is Loki and queryType is not defined', () => { + setupDataSources(mockDataSource({ type: DataSourceType.Loki, name: 'aa', uid: 'aa-1' })); + const result = getInstantFromDataQuery({ ...query, datasourceUid: 'aa' }); + expect(result).toBe(true); + }); + + it('should return true if datasource is Loki and queryType is instant', () => { + setupDataSources(mockDataSource({ type: DataSourceType.Loki, name: 'aa', uid: 'aa-1' })); + const result = getInstantFromDataQuery({ + ...query, + datasourceUid: 'aa', + model: { refId: 'f', queryType: 'instant' }, + }); + + expect(result).toBe(true); + }); + + it('should return false if datasource is Loki and queryType is not instant', () => { + setupDataSources(mockDataSource({ type: DataSourceType.Loki, name: 'aa', uid: 'aa-1' })); + const result = getInstantFromDataQuery({ + ...query, + datasourceUid: 'aa', + model: { refId: 'f', queryType: 'range' }, + }); + + expect(result).toBe(false); + }); +}); diff --git a/public/app/features/alerting/unified/utils/rule-form.ts b/public/app/features/alerting/unified/utils/rule-form.ts index 194b7bec552..7845c5e228f 100644 --- a/public/app/features/alerting/unified/utils/rule-form.ts +++ b/public/app/features/alerting/unified/utils/rule-form.ts @@ -803,19 +803,22 @@ export function isPromOrLokiQuery(model: AlertDataQuery): model is PromOrLokiQue return 'expr' in model; } -export function getInstantFromDataQuery(model: AlertDataQuery, type: string): boolean | undefined { - // if the datasource is not prometheus or loki, instant is defined in the model or defaults to undefined - if (type !== DataSourceType.Prometheus && type !== DataSourceType.Loki) { - if ('instant' in model) { - return model.instant; - } else { - if ('queryType' in model) { - return model.queryType === 'instant'; - } else { - return undefined; - } - } +export function getInstantFromDataQuery(query: AlertQuery): boolean | undefined { + const dataSourceUID = query.datasourceUid ?? query.model.datasource?.uid; + if (!dataSourceUID) { + return undefined; } + + // find the datasource type from the UID + const type = getDataSourceSrv().getInstanceSettings(dataSourceUID)?.type; + + // if the datasource is not prometheus or loki, return "undefined" + if (type !== DataSourceType.Prometheus && type !== DataSourceType.Loki) { + return undefined; + } + + const { model } = query; + // if the datasource is prometheus or loki, instant is defined in the model, or defaults to true const isInstantForPrometheus = 'instant' in model && model.instant !== undefined ? model.instant : true; const isInstantForLoki = 'queryType' in model && model.queryType !== undefined ? model.queryType === 'instant' : true; diff --git a/public/app/features/apiserver/client.ts b/public/app/features/apiserver/client.ts index cb53c57aa65..2af5503674f 100644 --- a/public/app/features/apiserver/client.ts +++ b/public/app/features/apiserver/client.ts @@ -1,6 +1,7 @@ import { Observable, from, retry, catchError, filter, map, mergeMap } from 'rxjs'; -import { BackendSrvRequest, config, getBackendSrv } from '@grafana/runtime'; +import { isLiveChannelMessageEvent, LiveChannelScope } from '@grafana/data'; +import { config, getBackendSrv, getGrafanaLiveSrv } from '@grafana/runtime'; import { contextSrv } from 'app/core/core'; import { getAPINamespace } from '../../api/utils'; @@ -19,20 +20,16 @@ import { K8sAPIGroupList, AnnoKeySavedFromUI, ResourceEvent, + GroupVersionResource, } from './types'; -export interface GroupVersionResource { - group: string; - version: string; - resource: string; -} - export class ScopedResourceClient implements ResourceClient { readonly url: string; + readonly gvr: GroupVersionResource; constructor(gvr: GroupVersionResource, namespaced = true) { const ns = namespaced ? `namespaces/${getAPINamespace()}/` : ''; - + this.gvr = gvr; this.url = `/apis/${gvr.group}/${gvr.version}/${ns}${gvr.resource}`; } @@ -40,26 +37,40 @@ export class ScopedResourceClient implements return getBackendSrv().get>(`${this.url}/${name}`); } - public watch( - params?: WatchOptions, - config?: Pick - ): Observable> { - const decoder = new TextDecoder(); - const { name, ...rest } = params ?? {}; // name needs to be added to fieldSelector + public watch(params?: WatchOptions): Observable> { const requestParams = { - ...rest, watch: true, labelSelector: this.parseListOptionsSelector(params?.labelSelector), fieldSelector: this.parseListOptionsSelector(params?.fieldSelector), }; - if (name) { + if (params?.name) { requestParams.fieldSelector = `metadata.name=${name}`; } + + // For now, watch over live only supports provisioning + if (this.gvr.group === 'provisioning.grafana.app') { + let query = ''; + if (requestParams.fieldSelector?.startsWith('metadata.name=')) { + query = requestParams.fieldSelector.substring('metadata.name'.length); + } + return getGrafanaLiveSrv() + .getStream>({ + scope: LiveChannelScope.Watch, + namespace: this.gvr.group, + path: `${this.gvr.version}/${this.gvr.resource}${query}/${config.bootData.user.uid}`, + }) + .pipe( + filter((event) => isLiveChannelMessageEvent(event)), + map((event) => event.message) + ); + } + + const decoder = new TextDecoder(); return getBackendSrv() .chunked({ url: this.url, params: requestParams, - ...config, + method: 'GET', }) .pipe( filter((response) => response.ok && response.data instanceof Uint8Array), @@ -73,7 +84,7 @@ export class ScopedResourceClient implements try { return JSON.parse(line); } catch (e) { - console.warn('Invalid JSON in watch stream:', e); + console.warn('Invalid JSON in watch stream:', e, line); return null; } }), diff --git a/public/app/features/apiserver/discovery.test.ts b/public/app/features/apiserver/discovery.test.ts new file mode 100644 index 00000000000..5b784197eea --- /dev/null +++ b/public/app/features/apiserver/discovery.test.ts @@ -0,0 +1,12 @@ +import { discoveryResources } from './discovery'; + +const discoverySnapshot = require('./snapshots/discovery-snapshot.json'); + +describe('simple typescript tests', () => { + it('simple', async () => { + const watchable = discoveryResources(discoverySnapshot) + .filter((v) => v.verbs.includes('watch')) + .map((v) => v.resource); + expect(watchable).toEqual(['user-storage', 'dashboards', 'dashboards', 'dashboards']); + }); +}); diff --git a/public/app/features/apiserver/discovery.ts b/public/app/features/apiserver/discovery.ts new file mode 100644 index 00000000000..ece21681d48 --- /dev/null +++ b/public/app/features/apiserver/discovery.ts @@ -0,0 +1,84 @@ +import { lastValueFrom, map } from 'rxjs'; + +import { FetchResponse, getBackendSrv } from '@grafana/runtime'; + +import { GroupVersionKind, ListMeta } from './types'; + +export type GroupDiscoveryResource = { + resource: string; + responseKind: GroupVersionKind; + scope: 'Namespaced' | 'Cluster'; + singularResource: string; + verbs: string[]; + subresources?: GroupDiscoverySubresource[]; +}; + +export type GroupDiscoverySubresource = { + subresource: string; + responseKind: GroupVersionKind; + verbs: string[]; +}; + +export type GroupDiscoveryVersion = { + version: string; + freshness: 'Current' | string; + resources: GroupDiscoveryResource[]; +}; + +export type GroupDiscoveryItem = { + metadata: { + name: string; + }; + versions: GroupDiscoveryVersion[]; +}; + +export type APIGroupDiscoveryList = { + metadata: ListMeta; + items: GroupDiscoveryItem[]; +}; + +export async function getAPIGroupDiscoveryList(): Promise { + return await lastValueFrom( + getBackendSrv() + .fetch({ + method: 'GET', + url: '/apis', + headers: { + Accept: + 'application/json;g=apidiscovery.k8s.io;v=v2;as=APIGroupDiscoveryList,application/json;g=apidiscovery.k8s.io;v=v2beta1;as=APIGroupDiscoveryList,application/json', + }, + }) + .pipe( + map((response: FetchResponse) => { + // Fill in the group+version before returning + for (let api of response.data.items) { + for (let version of api.versions) { + for (let resource of version.resources) { + resource.responseKind.group = api.metadata.name; + resource.responseKind.version = version.version; + if (resource.subresources) { + for (let sub of resource.subresources) { + sub.responseKind.group = api.metadata.name; + sub.responseKind.version = version.version; + } + } + } + } + } + return response.data; + }) + ) + ); +} + +export function discoveryResources(apis: APIGroupDiscoveryList): GroupDiscoveryResource[] { + const resources: GroupDiscoveryResource[] = []; + for (let api of apis.items) { + for (let version of api.versions) { + for (let resource of version.resources) { + resources.push(resource); + } + } + } + return resources; +} diff --git a/public/app/features/apiserver/snapshots/discovery-snapshot.json b/public/app/features/apiserver/snapshots/discovery-snapshot.json new file mode 100644 index 00000000000..36fc7f05aa3 --- /dev/null +++ b/public/app/features/apiserver/snapshots/discovery-snapshot.json @@ -0,0 +1,295 @@ +{ + "kind": "APIGroupDiscoveryList", + "apiVersion": "apidiscovery.k8s.io/v2", + "metadata": {}, + "items": [ + { + "metadata": { "name": "userstorage.grafana.app", "creationTimestamp": null }, + "versions": [ + { + "version": "v0alpha1", + "resources": [ + { + "resource": "user-storage", + "responseKind": { "group": "", "version": "", "kind": "UserStorage" }, + "scope": "Namespaced", + "singularResource": "user-storage", + "verbs": ["create", "delete", "deletecollection", "get", "list", "patch", "update", "watch"] + } + ], + "freshness": "Current" + } + ] + }, + { + "metadata": { "name": "notifications.alerting.grafana.app", "creationTimestamp": null }, + "versions": [ + { + "version": "v0alpha1", + "resources": [ + { + "resource": "receivers", + "responseKind": { "group": "", "version": "", "kind": "Receiver" }, + "scope": "Namespaced", + "singularResource": "receiver", + "verbs": ["create", "delete", "deletecollection", "get", "list", "patch", "update"] + }, + { + "resource": "routingtrees", + "responseKind": { "group": "", "version": "", "kind": "RoutingTree" }, + "scope": "Namespaced", + "singularResource": "routingtree", + "verbs": ["create", "delete", "deletecollection", "get", "list", "patch", "update"] + }, + { + "resource": "templategroups", + "responseKind": { "group": "", "version": "", "kind": "TemplateGroup" }, + "scope": "Namespaced", + "singularResource": "templategroup", + "verbs": ["create", "delete", "deletecollection", "get", "list", "patch", "update"] + }, + { + "resource": "timeintervals", + "responseKind": { "group": "", "version": "", "kind": "TimeInterval" }, + "scope": "Namespaced", + "singularResource": "timeinterval", + "verbs": ["create", "delete", "deletecollection", "get", "list", "patch", "update"] + } + ], + "freshness": "Current" + } + ] + }, + { + "metadata": { "name": "iam.grafana.app", "creationTimestamp": null }, + "versions": [ + { + "version": "v0alpha1", + "resources": [ + { + "resource": "serviceaccounts", + "responseKind": { "group": "", "version": "", "kind": "ServiceAccount" }, + "scope": "Namespaced", + "singularResource": "serviceaccount", + "verbs": ["get", "list"], + "subresources": [ + { + "subresource": "tokens", + "responseKind": { "group": "", "version": "", "kind": "UserTeamList" }, + "verbs": ["get"] + } + ] + }, + { + "resource": "ssosettings", + "responseKind": { "group": "", "version": "", "kind": "SSOSetting" }, + "scope": "Namespaced", + "singularResource": "ssosetting", + "verbs": ["delete", "get", "list", "patch", "update"] + }, + { + "resource": "teambindings", + "responseKind": { "group": "", "version": "", "kind": "TeamBinding" }, + "scope": "Namespaced", + "singularResource": "teambinding", + "verbs": ["get", "list"] + }, + { + "resource": "teams", + "responseKind": { "group": "", "version": "", "kind": "Team" }, + "scope": "Namespaced", + "singularResource": "team", + "verbs": ["get", "list"], + "subresources": [ + { + "subresource": "members", + "responseKind": { "group": "", "version": "", "kind": "TeamMemberList" }, + "verbs": ["get"] + } + ] + }, + { + "resource": "users", + "responseKind": { "group": "", "version": "", "kind": "User" }, + "scope": "Namespaced", + "singularResource": "user", + "verbs": ["get", "list"], + "subresources": [ + { + "subresource": "teams", + "responseKind": { "group": "", "version": "", "kind": "UserTeamList" }, + "verbs": ["get"] + } + ] + } + ], + "freshness": "Current" + } + ] + }, + { + "metadata": { "name": "folder.grafana.app", "creationTimestamp": null }, + "versions": [ + { + "version": "v0alpha1", + "resources": [ + { + "resource": "folders", + "responseKind": { "group": "", "version": "", "kind": "Folder" }, + "scope": "Namespaced", + "singularResource": "folder", + "verbs": ["create", "delete", "deletecollection", "get", "list", "patch", "update"], + "subresources": [ + { + "subresource": "access", + "responseKind": { "group": "", "version": "", "kind": "FolderAccessInfo" }, + "verbs": ["get"] + }, + { + "subresource": "counts", + "responseKind": { "group": "", "version": "", "kind": "DescendantCounts" }, + "verbs": ["get"] + }, + { + "subresource": "parents", + "responseKind": { "group": "", "version": "", "kind": "FolderInfoList" }, + "verbs": ["get"] + } + ] + } + ], + "freshness": "Current" + } + ] + }, + { + "metadata": { "name": "featuretoggle.grafana.app", "creationTimestamp": null }, + "versions": [ + { + "version": "v0alpha1", + "resources": [ + { + "resource": "features", + "responseKind": { "group": "", "version": "", "kind": "Feature" }, + "scope": "Cluster", + "singularResource": "feature", + "verbs": ["get", "list"] + }, + { + "resource": "featuretoggles", + "responseKind": { "group": "", "version": "", "kind": "FeatureToggles" }, + "scope": "Namespaced", + "singularResource": "featuretoggle", + "verbs": ["get", "list"] + } + ], + "freshness": "Current" + } + ] + }, + { + "metadata": { "name": "dashboard.grafana.app", "creationTimestamp": null }, + "versions": [ + { + "version": "v0alpha1", + "resources": [ + { + "resource": "dashboards", + "responseKind": { "group": "", "version": "", "kind": "Dashboard" }, + "scope": "Namespaced", + "singularResource": "dashboard", + "verbs": ["create", "delete", "deletecollection", "get", "list", "patch", "update", "watch"], + "subresources": [ + { + "subresource": "dto", + "responseKind": { "group": "", "version": "", "kind": "DashboardWithAccessInfo" }, + "verbs": ["get"] + } + ] + }, + { + "resource": "librarypanels", + "responseKind": { "group": "", "version": "", "kind": "LibraryPanel" }, + "scope": "Namespaced", + "singularResource": "librarypanel", + "verbs": ["get", "list"] + } + ], + "freshness": "Current" + }, + { + "version": "v1alpha1", + "resources": [ + { + "resource": "dashboards", + "responseKind": { "group": "", "version": "", "kind": "Dashboard" }, + "scope": "Namespaced", + "singularResource": "dashboard", + "verbs": ["create", "delete", "deletecollection", "get", "list", "patch", "update", "watch"], + "subresources": [ + { + "subresource": "dto", + "responseKind": { "group": "", "version": "", "kind": "DashboardWithAccessInfo" }, + "verbs": ["get"] + } + ] + }, + { + "resource": "librarypanels", + "responseKind": { "group": "", "version": "", "kind": "LibraryPanel" }, + "scope": "Namespaced", + "singularResource": "librarypanel", + "verbs": ["get", "list"] + } + ], + "freshness": "Current" + }, + { + "version": "v2alpha1", + "resources": [ + { + "resource": "dashboards", + "responseKind": { "group": "", "version": "", "kind": "Dashboard" }, + "scope": "Namespaced", + "singularResource": "dashboard", + "verbs": ["create", "delete", "deletecollection", "get", "list", "patch", "update", "watch"], + "subresources": [ + { + "subresource": "dto", + "responseKind": { "group": "", "version": "", "kind": "DashboardWithAccessInfo" }, + "verbs": ["get"] + } + ] + }, + { + "resource": "librarypanels", + "responseKind": { "group": "", "version": "", "kind": "LibraryPanel" }, + "scope": "Namespaced", + "singularResource": "librarypanel", + "verbs": ["get", "list"] + } + ], + "freshness": "Current" + } + ] + }, + { + "metadata": { "name": "playlist.grafana.app", "creationTimestamp": null }, + "versions": [ + { + "version": "v0alpha1", + "resources": [ + { + "resource": "playlists", + "responseKind": { "group": "", "version": "", "kind": "Playlist" }, + "scope": "Namespaced", + "singularResource": "playlist", + "verbs": ["create", "delete", "deletecollection", "get", "list", "patch", "update"] + } + ], + "freshness": "Current" + } + ] + } + ] +} diff --git a/public/app/features/apiserver/types.ts b/public/app/features/apiserver/types.ts index 06199f6d677..631f56f505c 100644 --- a/public/app/features/apiserver/types.ts +++ b/public/app/features/apiserver/types.ts @@ -111,6 +111,18 @@ type GrafanaLabels = { [DeprecatedInternalId]?: string; }; +export interface GroupVersionResource { + group: string; + version: string; + resource: string; +} + +export interface GroupVersionKind { + group: string; + version: string; + kind: string; +} + export interface Resource extends TypeMeta { metadata: ObjectMeta; spec: T; diff --git a/public/app/features/dashboard-scene/edit-pane/DashboardEditPane.tsx b/public/app/features/dashboard-scene/edit-pane/DashboardEditPane.tsx index 387fcf41dca..fbb8f414e4d 100644 --- a/public/app/features/dashboard-scene/edit-pane/DashboardEditPane.tsx +++ b/public/app/features/dashboard-scene/edit-pane/DashboardEditPane.tsx @@ -99,6 +99,10 @@ export class DashboardEditPane extends SceneObjectBase { } } + public getSelection(): SceneObject | SceneObject[] | undefined { + return this.state.selection?.getSelection(); + } + public selectObject(obj: SceneObject, id: string, multi?: boolean) { const prevItem = this.state.selection?.getFirstObject(); if (prevItem === obj && !multi) { diff --git a/public/app/features/dashboard-scene/scene/DashboardScene.tsx b/public/app/features/dashboard-scene/scene/DashboardScene.tsx index e7241022596..b2de2c14ca0 100644 --- a/public/app/features/dashboard-scene/scene/DashboardScene.tsx +++ b/public/app/features/dashboard-scene/scene/DashboardScene.tsx @@ -75,7 +75,7 @@ import { LayoutOrchestrator } from './layout-manager/LayoutOrchestrator'; import { LayoutRestorer } from './layouts-shared/LayoutRestorer'; import { addNewRowTo, addNewTabTo } from './layouts-shared/addNew'; import { DashboardLayoutManager } from './types/DashboardLayoutManager'; -import { LayoutParent } from './types/LayoutParent'; +import { isLayoutParent, LayoutParent } from './types/LayoutParent'; export const PERSISTED_PROPS = ['title', 'description', 'tags', 'editable', 'graphTooltip', 'links', 'meta', 'preload']; export const PANEL_SEARCH_VAR = 'systemPanelFilterVar'; @@ -500,6 +500,13 @@ export class DashboardScene extends SceneObjectBase impleme this.onEnterEditMode(); } + const selectedObject = this.state.editPane.getSelection(); + if (selectedObject && !Array.isArray(selectedObject) && isLayoutParent(selectedObject)) { + const layout = selectedObject.getLayout(); + layout.addPanel(vizPanel); + return; + } + // Add panel to layout this.state.body.addPanel(vizPanel); } @@ -609,10 +616,22 @@ export class DashboardScene extends SceneObjectBase impleme } public onCreateNewRow() { + const selectedObject = this.state.editPane.getSelection(); + if (selectedObject && !Array.isArray(selectedObject) && isLayoutParent(selectedObject)) { + const layout = selectedObject.getLayout(); + return addNewRowTo(layout); + } + return addNewRowTo(this.state.body); } public onCreateNewTab() { + const selectedObject = this.state.editPane.getSelection(); + if (selectedObject && !Array.isArray(selectedObject) && isLayoutParent(selectedObject)) { + const layout = selectedObject.getLayout(); + return addNewTabTo(layout); + } + return addNewTabTo(this.state.body); } @@ -686,7 +705,8 @@ export class DashboardScene extends SceneObjectBase impleme saveModel?: Dashboard | DashboardV2Spec, meta?: DashboardMeta | DashboardWithAccessInfo['metadata'] ): void { - this.serializer.initializeMapping(saveModel); + this.serializer.initializeElementMapping(saveModel); + this.serializer.initializeDSReferencesMapping(saveModel); const sortedModel = sortedDeepCloneWithoutNulls(saveModel); this.serializer.initialSaveModel = sortedModel; this.serializer.metadata = meta; diff --git a/public/app/features/dashboard-scene/scene/NavToolbarActions.tsx b/public/app/features/dashboard-scene/scene/NavToolbarActions.tsx index d8671716331..6b6c0b54f50 100644 --- a/public/app/features/dashboard-scene/scene/NavToolbarActions.tsx +++ b/public/app/features/dashboard-scene/scene/NavToolbarActions.tsx @@ -1,5 +1,5 @@ import { css } from '@emotion/css'; -import { memo, ReactNode, useEffect, useId, useState } from 'react'; +import { memo, ReactNode, useEffect, useState } from 'react'; import { GrafanaTheme2, store } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; @@ -10,10 +10,8 @@ import { ButtonGroup, Dropdown, Icon, - InlineLabel, Menu, Stack, - Switch, ToolbarButton, ToolbarButtonRow, useStyles2, @@ -39,15 +37,20 @@ import { isLibraryPanel } from '../utils/utils'; import { DashboardScene } from './DashboardScene'; import { GoToSnapshotOriginButton } from './GoToSnapshotOriginButton'; import ManagedDashboardNavBarBadge from './ManagedDashboardNavBarBadge'; +import { ToolbarActionsNew } from './new-toolbar/ToolbarActionsNew'; interface Props { dashboard: DashboardScene; } export const NavToolbarActions = memo(({ dashboard }) => { - const id = useId(); + const hasNewToolbar = config.featureToggles.dashboardNewLayouts && config.featureToggles.newDashboardSharingComponent; - const actions = ; + const actions = hasNewToolbar ? ( + + ) : ( + + ); return ; }); @@ -57,8 +60,7 @@ NavToolbarActions.displayName = 'NavToolbarActions'; * This part is split into a separate component to help test this */ export function ToolbarActions({ dashboard }: Props) { - const { isEditing, showHiddenElements, viewPanelScene, isDirty, uid, meta, editview, editPanel, editable } = - dashboard.useState(); + const { isEditing, viewPanelScene, isDirty, uid, meta, editview, editPanel, editable } = dashboard.useState(); const { isPlaying } = playlistSrv.useState(); const [isAddPanelMenuOpen, setIsAddPanelMenuOpen] = useState(false); @@ -77,7 +79,6 @@ export function ToolbarActions({ dashboard }: Props) { // Means we are not in settings view, fullscreen panel or edit panel const isShowingDashboard = !editview && !isViewingPanel && !isEditingPanel; const isEditingAndShowingDashboard = isEditing && isShowingDashboard; - const dashboardNewLayouts = config.featureToggles.dashboardNewLayouts; const folderRepo = useSelector((state) => selectFolderRepository(state, meta.folderUid)); const isManaged = Boolean(dashboard.isManagedRepository() || folderRepo); @@ -168,97 +169,75 @@ export function ToolbarActions({ dashboard }: Props) { addDynamicActions(toolbarActions, dynamicDashNavActions.right, 'icon-actions'); } - if (dashboardNewLayouts) { - leftActions.push({ - group: 'hidden-elements', - condition: isEditingAndShowingDashboard, - render: () => ( - - { - evt.stopPropagation(); - dashboard.onToggleHiddenElements(); - }} - data-testid={selectors.components.PageToolbar.itemButton('toggle_hidden_elements')} - /> - - Show hidden - - - ), - }); - } else { - toolbarActions.push({ - group: 'add-panel', - condition: isEditingAndShowingDashboard, - render: () => ( - { - setIsAddPanelMenuOpen(isOpen); - DashboardInteractions.toolbarAddClick(); - }} - overlay={() => ( - - { - const vizPanel = dashboard.onCreateNewPanel(); - DashboardInteractions.toolbarAddButtonClicked({ item: 'add_visualization' }); - dashboard.setState({ editPanel: buildPanelEditScene(vizPanel, true) }); - }} - /> - { - dashboard.onShowAddLibraryPanelDrawer(); - DashboardInteractions.toolbarAddButtonClicked({ item: 'add_library_panel' }); - }} - disabled={dashboard.isManagedRepository()} - /> - { - dashboard.onCreateNewRow(); - DashboardInteractions.toolbarAddButtonClicked({ item: 'add_row' }); - }} - /> - { - dashboard.pastePanel(); - DashboardInteractions.toolbarAddButtonClicked({ item: 'paste_panel' }); - }} - /> - - )} - placement="bottom" - offset={[0, 6]} + toolbarActions.push({ + group: 'add-panel', + condition: isEditingAndShowingDashboard, + render: () => ( + { + setIsAddPanelMenuOpen(isOpen); + DashboardInteractions.toolbarAddClick(); + }} + overlay={() => ( + + { + const vizPanel = dashboard.onCreateNewPanel(); + DashboardInteractions.toolbarAddButtonClicked({ item: 'add_visualization' }); + dashboard.setState({ editPanel: buildPanelEditScene(vizPanel, true) }); + }} + /> + { + dashboard.onShowAddLibraryPanelDrawer(); + DashboardInteractions.toolbarAddButtonClicked({ item: 'add_library_panel' }); + }} + disabled={dashboard.isManagedRepository()} + /> + { + dashboard.onCreateNewRow(); + DashboardInteractions.toolbarAddButtonClicked({ item: 'add_row' }); + }} + /> + { + dashboard.pastePanel(); + DashboardInteractions.toolbarAddButtonClicked({ item: 'paste_panel' }); + }} + /> + + )} + placement="bottom" + offset={[0, 6]} + > + - - ), - }); - } + Add + + + + ), + }); toolbarActions.push({ group: 'playlist-actions', @@ -419,27 +398,25 @@ export function ToolbarActions({ dashboard }: Props) { render: () => , }); - if (!dashboardNewLayouts) { - toolbarActions.push({ - group: 'settings', - condition: isEditing && dashboard.canEditDashboard() && isShowingDashboard, - render: () => ( - - ), - }); - } + toolbarActions.push({ + group: 'settings', + condition: isEditing && dashboard.canEditDashboard() && isShowingDashboard, + render: () => ( + + ), + }); toolbarActions.push({ group: 'main-buttons', @@ -627,24 +604,6 @@ export function ToolbarActions({ dashboard }: Props) { }, }); - // Will open a schema v2 editor drawer. Only available with new dashboard layouts. - toolbarActions.push({ - group: 'main-buttons', - condition: uid && dashboardNewLayouts, - render: () => { - return ( - } - key="schema-v2-button" - onClick={() => { - dashboard.openV2SchemaEditor(); - }} - /> - ); - }, - }); - const rightActionsElements: ReactNode[] = renderActionElements(toolbarActions); const leftActionsElements: ReactNode[] = renderActionElements(leftActions); const hasActionsToLeftAndRight = leftActionsElements.length > 0; diff --git a/public/app/features/dashboard-scene/scene/layout-default/DefaultGridLayoutManager.tsx b/public/app/features/dashboard-scene/scene/layout-default/DefaultGridLayoutManager.tsx index f8cb979f064..3628ea6f026 100644 --- a/public/app/features/dashboard-scene/scene/layout-default/DefaultGridLayoutManager.tsx +++ b/public/app/features/dashboard-scene/scene/layout-default/DefaultGridLayoutManager.tsx @@ -256,6 +256,7 @@ export class DefaultGridLayoutManager sceneGridLayout.setState({ children: [row, ...sceneGridLayout.state.children] }); + this.publishEvent(new NewObjectAddedToCanvasEvent(row), true); return row; } diff --git a/public/app/features/dashboard-scene/scene/layout-manager/LayoutOrchestrator.tsx b/public/app/features/dashboard-scene/scene/layout-manager/LayoutOrchestrator.tsx index 2a221189fee..9789f85d005 100644 --- a/public/app/features/dashboard-scene/scene/layout-manager/LayoutOrchestrator.tsx +++ b/public/app/features/dashboard-scene/scene/layout-manager/LayoutOrchestrator.tsx @@ -14,6 +14,11 @@ export class LayoutOrchestrator extends SceneObjectBase /** Offset from top-left corner of drag handle. */ public dragOffset = { top: 0, left: 0 }; + /** The drop zone closest to the current mouse position while dragging. */ + public activeDropZone: (DropZone & { layout: SceneObjectRef }) | undefined; + + private _sceneLayouts: SceneLayoutWithDragAndDrop[] = []; + /** Used in `ResponsiveGridLayout`'s `onPointerDown` method */ public onDragStart = (e: PointerEvent, panel: VizPanel) => { const closestLayoutItem = closestOfType(panel, isDashboardLayoutItem); @@ -27,18 +32,39 @@ export class LayoutOrchestrator extends SceneObjectBase return; } + this._sceneLayouts = sceneGraph + .findAllObjects(this.getRoot(), isSceneLayoutWithDragAndDrop) + .filter(isSceneLayoutWithDragAndDrop); + document.body.setPointerCapture(e.pointerId); + const targetRect = e.target.getBoundingClientRect(); this.dragOffset = { top: e.y - targetRect.top, left: e.x - targetRect.left }; - this.setState({ activeLayoutItemRef: closestLayoutItem.getRef() }); + + closestLayoutItem.containerRef.current?.style.setProperty('--x-pos', `${e.x}px`); + closestLayoutItem.containerRef.current?.style.setProperty('--y-pos', `${e.y}px`); + + const state: Partial = { activeLayoutItemRef: closestLayoutItem.getRef() }; + + this._adjustXY({ x: e.x, y: e.y }, closestLayoutItem); + + this.activeDropZone = this.findClosestDropZone({ x: e.clientX, y: e.clientY }); + if (this.activeDropZone) { + state.placeholder = new DropZonePlaceholder({ + top: this.activeDropZone.top, + left: this.activeDropZone.left, + width: this.activeDropZone.right - this.activeDropZone.left, + height: this.activeDropZone.bottom - this.activeDropZone.top, + }); + } + + this.setState(state); + document.addEventListener('pointermove', this.onDrag); document.addEventListener('pointerup', this.onDragEnd); document.body.classList.add('dragging-active'); }; - /** The drop zone closest to the current mouse position while dragging. */ - public activeDropZone: (DropZone & { layout: SceneObjectRef }) | undefined; - /** Called every tick while a panel is actively being dragged */ public onDrag = (e: PointerEvent) => { const layoutItemContainer = this.state.activeLayoutItemRef?.resolve().containerRef.current; @@ -49,9 +75,10 @@ export class LayoutOrchestrator extends SceneObjectBase const cursorPos: Point = { x: e.clientX, y: e.clientY }; - layoutItemContainer.style.setProperty('--x-pos', `${cursorPos.x}px`); - layoutItemContainer.style.setProperty('--y-pos', `${cursorPos.y}px`); + this._adjustXY(cursorPos); + const closestDropZone = this.findClosestDropZone(cursorPos); + if (!dropZonesAreEqual(this.activeDropZone, closestDropZone)) { this.activeDropZone = closestDropZone; if (this.activeDropZone) { @@ -90,15 +117,7 @@ export class LayoutOrchestrator extends SceneObjectBase } this.moveLayoutItem(activeLayoutItem, targetLayout); - this.setState({ - activeLayoutItemRef: undefined, - }); - this.state.placeholder?.setState({ - top: 0, - left: 0, - width: 0, - height: 0, - }); + this.setState({ activeLayoutItemRef: undefined, placeholder: undefined }); this.activeDropZone = undefined; activeLayoutItemContainer?.removeAttribute('style'); }; @@ -116,12 +135,9 @@ export class LayoutOrchestrator extends SceneObjectBase } public findClosestDropZone(p: Point) { - const sceneLayouts = sceneGraph - .findAllObjects(this.getRoot(), isSceneLayoutWithDragAndDrop) - .filter(isSceneLayoutWithDragAndDrop); let closestDropZone: (DropZone & { layout: SceneObjectRef }) | undefined = undefined; let closestDistance = Number.MAX_VALUE; - for (const layout of sceneLayouts) { + for (const layout of this._sceneLayouts) { const curClosestDropZone = layout.closestDropZone(p); if (curClosestDropZone.distanceToPoint < closestDistance) { closestDropZone = { ...curClosestDropZone, layout: layout.getRef() }; @@ -131,6 +147,12 @@ export class LayoutOrchestrator extends SceneObjectBase return closestDropZone; } + + private _adjustXY(p: Point, activeLayoutItem = this.state.activeLayoutItemRef?.resolve()) { + const container = activeLayoutItem?.containerRef.current; + container?.style.setProperty('--x-pos', `${p.x}px`); + container?.style.setProperty('--y-pos', `${p.y}px`); + } } function dropZonesAreEqual(a?: DropZone, b?: DropZone) { diff --git a/public/app/features/dashboard-scene/scene/layout-responsive-grid/ResponsiveGridLayout.tsx b/public/app/features/dashboard-scene/scene/layout-responsive-grid/ResponsiveGridLayout.tsx index 3c705b34540..8a94c0d92fb 100644 --- a/public/app/features/dashboard-scene/scene/layout-responsive-grid/ResponsiveGridLayout.tsx +++ b/public/app/features/dashboard-scene/scene/layout-responsive-grid/ResponsiveGridLayout.tsx @@ -26,6 +26,9 @@ export interface ResponsiveGridLayoutState extends SceneObjectState, ResponsiveG /** True when the items should be lazy loaded */ isLazy?: boolean; + + /** True when the items should be draggable */ + isDraggable?: boolean; } export interface ResponsiveGridLayoutOptions { @@ -87,7 +90,7 @@ export class ResponsiveGridLayout }; public isDraggable(): boolean { - return true; + return this.state.isDraggable ?? false; } public getDragClass(): string { diff --git a/public/app/features/dashboard-scene/scene/layout-responsive-grid/ResponsiveGridLayoutManager.tsx b/public/app/features/dashboard-scene/scene/layout-responsive-grid/ResponsiveGridLayoutManager.tsx index fe17f5ca802..0885e481c69 100644 --- a/public/app/features/dashboard-scene/scene/layout-responsive-grid/ResponsiveGridLayoutManager.tsx +++ b/public/app/features/dashboard-scene/scene/layout-responsive-grid/ResponsiveGridLayoutManager.tsx @@ -5,7 +5,12 @@ import { OptionsPaneItemDescriptor } from 'app/features/dashboard/components/Pan import { NewObjectAddedToCanvasEvent, ObjectRemovedFromCanvasEvent } from '../../edit-pane/shared'; import { joinCloneKeys } from '../../utils/clone'; import { dashboardSceneGraph } from '../../utils/dashboardSceneGraph'; -import { getGridItemKeyForPanelId, getPanelIdForVizPanel, getVizPanelKeyForPanelId } from '../../utils/utils'; +import { + forceRenderChildren, + getGridItemKeyForPanelId, + getPanelIdForVizPanel, + getVizPanelKeyForPanelId, +} from '../../utils/utils'; import { DashboardLayoutManager } from '../types/DashboardLayoutManager'; import { LayoutRegistryItem } from '../types/LayoutRegistryItem'; @@ -45,14 +50,6 @@ export class ResponsiveGridLayoutManager autoRows: 'minmax(300px, auto)', }; - public constructor(state: ResponsiveGridLayoutManagerState) { - super(state); - - // @ts-ignore - this.state.layout.getDragClassCancel = () => 'drag-cancel'; - this.state.layout.isDraggable = () => true; - } - public addPanel(vizPanel: VizPanel) { const panelId = dashboardSceneGraph.getNextPanelId(this); @@ -77,18 +74,14 @@ export class ResponsiveGridLayoutManager key: undefined, layout: this.state.layout.clone({ key: undefined, - children: this.state.layout.state.children.map((child) => { - if (child instanceof ResponsiveGridItem) { - return child.clone({ - key: undefined, - body: child.state.body.clone({ - key: getVizPanelKeyForPanelId(dashboardSceneGraph.getNextPanelId(child.state.body)), - }), - }); - } - - return child.clone({ key: undefined }); - }), + children: this.state.layout.state.children.map((child) => + child.clone({ + key: undefined, + body: child.state.body.clone({ + key: getVizPanelKeyForPanelId(dashboardSceneGraph.getNextPanelId(child.state.body)), + }), + }) + ), }), }); } @@ -135,6 +128,11 @@ export class ResponsiveGridLayoutManager return panels; } + public editModeChanged(isEditing: boolean) { + this.state.layout.setState({ isDraggable: isEditing }); + forceRenderChildren(this.state.layout, true); + } + public cloneLayout(ancestorKey: string, isSource: boolean): DashboardLayoutManager { return this.clone({ layout: this.state.layout.clone({ diff --git a/public/app/features/dashboard-scene/scene/layout-responsive-grid/ResponsiveGridLayoutRenderer.tsx b/public/app/features/dashboard-scene/scene/layout-responsive-grid/ResponsiveGridLayoutRenderer.tsx index 2139900b4c4..a3976fec4dc 100644 --- a/public/app/features/dashboard-scene/scene/layout-responsive-grid/ResponsiveGridLayoutRenderer.tsx +++ b/public/app/features/dashboard-scene/scene/layout-responsive-grid/ResponsiveGridLayoutRenderer.tsx @@ -59,8 +59,9 @@ export function ResponsiveGridLayoutRenderer({ model }: SceneComponentProps ({ position: 'relative', width: '100%', height: '100%', - overflow: 'hidden', }), dragging: css({ position: 'fixed', diff --git a/public/app/features/dashboard-scene/scene/layout-rows/RowsLayoutManager.tsx b/public/app/features/dashboard-scene/scene/layout-rows/RowsLayoutManager.tsx index 7ad15344c45..15e3efbcd31 100644 --- a/public/app/features/dashboard-scene/scene/layout-rows/RowsLayoutManager.tsx +++ b/public/app/features/dashboard-scene/scene/layout-rows/RowsLayoutManager.tsx @@ -89,6 +89,7 @@ export class RowsLayoutManager extends SceneObjectBase i public addNewRow(): RowItem { const row = new RowItem(); this.setState({ rows: [...this.state.rows, row] }); + this.publishEvent(new NewObjectAddedToCanvasEvent(row), true); return row; } @@ -112,7 +113,7 @@ export class RowsLayoutManager extends SceneObjectBase i }); } - public addRowAbove(row: RowItem) { + public addRowAbove(row: RowItem): RowItem { const index = this.state.rows.indexOf(row); const newRow = new RowItem(); const newRows = [...this.state.rows]; @@ -121,9 +122,11 @@ export class RowsLayoutManager extends SceneObjectBase i this.setState({ rows: newRows }); this.publishEvent(new NewObjectAddedToCanvasEvent(newRow), true); + + return newRow; } - public addRowBelow(row: RowItem) { + public addRowBelow(row: RowItem): RowItem { const rows = this.state.rows; let index = rows.indexOf(row); @@ -139,6 +142,8 @@ export class RowsLayoutManager extends SceneObjectBase i this.setState({ rows: newRows }); this.publishEvent(new NewObjectAddedToCanvasEvent(newRow), true); + + return newRow; } public removeRow(row: RowItem) { diff --git a/public/app/features/dashboard-scene/scene/layout-tabs/TabsLayoutManager.tsx b/public/app/features/dashboard-scene/scene/layout-tabs/TabsLayoutManager.tsx index 6478ff19c54..71af05bfce7 100644 --- a/public/app/features/dashboard-scene/scene/layout-tabs/TabsLayoutManager.tsx +++ b/public/app/features/dashboard-scene/scene/layout-tabs/TabsLayoutManager.tsx @@ -110,6 +110,7 @@ export class TabsLayoutManager extends SceneObjectBase i public addNewTab() { const newTab = new TabItem(); this.setState({ tabs: [...this.state.tabs, newTab], currentTabIndex: this.state.tabs.length }); + this.publishEvent(new NewObjectAddedToCanvasEvent(newTab), true); return newTab; } @@ -143,20 +144,24 @@ export class TabsLayoutManager extends SceneObjectBase i this.publishEvent(new ObjectRemovedFromCanvasEvent(tabToRemove), true); } - public addTabBefore(tab: TabItem) { + public addTabBefore(tab: TabItem): TabItem { const newTab = new TabItem(); const tabs = this.state.tabs.slice(); tabs.splice(tabs.indexOf(tab), 0, newTab); this.setState({ tabs, currentTabIndex: this.state.currentTabIndex }); this.publishEvent(new NewObjectAddedToCanvasEvent(newTab), true); + + return newTab; } - public addTabAfter(tab: TabItem) { + public addTabAfter(tab: TabItem): TabItem { const newTab = new TabItem(); const tabs = this.state.tabs.slice(); tabs.splice(tabs.indexOf(tab) + 1, 0, newTab); this.setState({ tabs, currentTabIndex: this.state.currentTabIndex + 1 }); this.publishEvent(new NewObjectAddedToCanvasEvent(newTab), true); + + return newTab; } public moveTabLeft(tab: TabItem) { diff --git a/public/app/features/dashboard-scene/scene/layouts-shared/LayoutRestorer.ts b/public/app/features/dashboard-scene/scene/layouts-shared/LayoutRestorer.ts index 9abd16994ef..e5ad93458ef 100644 --- a/public/app/features/dashboard-scene/scene/layouts-shared/LayoutRestorer.ts +++ b/public/app/features/dashboard-scene/scene/layouts-shared/LayoutRestorer.ts @@ -1,4 +1,5 @@ -import { vizPanelToSchemaV2 } from '../../serialization/transformSceneToSaveModelSchemaV2'; +import { VizPanel } from '@grafana/scenes'; + import { DashboardLayoutManager } from '../types/DashboardLayoutManager'; export class LayoutRestorer { @@ -8,12 +9,10 @@ export class LayoutRestorer { newLayout: DashboardLayoutManager, currentLayout: DashboardLayoutManager ): DashboardLayoutManager | undefined { - // If we have an old version of this layout and panels are the same we can reuse it + //If we have an old version of this layout and panels are the same we can reuse it const prevLayout = this.layoutMap[newLayout.descriptor.id]; if (prevLayout) { - const oldPanelSchema = prevLayout.getVizPanels().map(vizPanelToSchemaV2); - const newPanelSchema = newLayout.getVizPanels().map(vizPanelToSchemaV2); - if (JSON.stringify(oldPanelSchema) === JSON.stringify(newPanelSchema)) { + if (panelsAreUnchanged(prevLayout.getVizPanels(), newLayout.getVizPanels())) { return prevLayout; } } @@ -23,3 +22,28 @@ export class LayoutRestorer { return newLayout; } } + +/** + * Simple check that panels are in same order and same options but not a comprehensive check + * Ideally we should check if persisted state is the same but not possible anymore with current serialization code that requires all panels be connected to a DashboardScene + */ +function panelsAreUnchanged(a: VizPanel[], b: VizPanel[]) { + for (let i = 0; i < a.length; i++) { + const ap = a[i]; + const bp = b[i]; + + if (ap.state.key !== bp.state.key) { + return false; + } + + if (JSON.stringify(ap.state.options) !== JSON.stringify(bp.state.options)) { + return false; + } + + if (JSON.stringify(ap.state.fieldConfig) !== JSON.stringify(bp.state.fieldConfig)) { + return false; + } + } + + return true; +} diff --git a/public/app/features/dashboard-scene/scene/layouts-shared/addNew.ts b/public/app/features/dashboard-scene/scene/layouts-shared/addNew.ts index 0a844199114..d7c139d1716 100644 --- a/public/app/features/dashboard-scene/scene/layouts-shared/addNew.ts +++ b/public/app/features/dashboard-scene/scene/layouts-shared/addNew.ts @@ -1,5 +1,5 @@ import { config } from '@grafana/runtime'; -import { SceneGridRow } from '@grafana/scenes'; +import { sceneGraph, SceneGridRow } from '@grafana/scenes'; import { NewObjectAddedToCanvasEvent } from '../../edit-pane/shared'; import { DefaultGridLayoutManager } from '../layout-default/DefaultGridLayoutManager'; @@ -11,18 +11,25 @@ import { DashboardLayoutManager } from '../types/DashboardLayoutManager'; import { isLayoutParent } from '../types/LayoutParent'; export function addNewTabTo(layout: DashboardLayoutManager): TabItem { - if (layout instanceof TabsLayoutManager) { - const tab = layout.addNewTab(); - layout.publishEvent(new NewObjectAddedToCanvasEvent(tab), true); - return tab; - } - const layoutParent = layout.parent!; if (!isLayoutParent(layoutParent)) { throw new Error('Parent layout is not a LayoutParent'); } - const tabsLayout = TabsLayoutManager.createFromLayout(layoutParent.getLayout()); + // If layout parent is tab item we add new tab after it rather than create a nested tab + if (layoutParent instanceof TabItem) { + const tabsLayout = sceneGraph.getAncestor(layoutParent, TabsLayoutManager); + return tabsLayout.addTabAfter(layoutParent); + } + + if (layout instanceof TabsLayoutManager) { + return layout.addNewTab(); + } + + // Create new tabs layout and wrap the current layout in the first tab + const tabsLayout = TabsLayoutManager.createEmpty(); + tabsLayout.state.tabs[0].setState({ layout: layout.clone() }); + layoutParent.switchLayout(tabsLayout); const tab = tabsLayout.state.tabs[0]; @@ -37,18 +44,25 @@ export function addNewRowTo(layout: DashboardLayoutManager): RowItem | SceneGrid */ if (!config.featureToggles.dashboardNewLayouts) { if (layout instanceof DefaultGridLayoutManager) { - const row = layout.addNewRow(); - layout.publishEvent(new NewObjectAddedToCanvasEvent(row), true); - return row; + return layout.addNewRow(); } else { throw new Error('New dashboard layouts feature not enabled but new layout found'); } } + const layoutParent = layout.parent!; + if (!isLayoutParent(layoutParent)) { + throw new Error('Parent layout is not a LayoutParent'); + } + + // If adding we are adding a row to a row we add it below the current row + if (layoutParent instanceof RowItem) { + const rowsLayout = sceneGraph.getAncestor(layoutParent, RowsLayoutManager); + return rowsLayout.addRowBelow(layoutParent); + } + if (layout instanceof RowsLayoutManager) { - const row = layout.addNewRow(); - layout.publishEvent(new NewObjectAddedToCanvasEvent(row), true); - return row; + return layout.addNewRow(); } if (layout instanceof TabsLayoutManager) { @@ -59,11 +73,6 @@ export function addNewRowTo(layout: DashboardLayoutManager): RowItem | SceneGrid // If we want to add a row and current layout is custom grid or auto we migrate to rows layout // And wrap current layout in a row - const layoutParent = layout.parent!; - if (!isLayoutParent(layoutParent)) { - throw new Error('Parent layout is not a LayoutParent'); - } - const rowsLayout = RowsLayoutManager.createFromLayout(layoutParent.getLayout()); layoutParent.switchLayout(rowsLayout); diff --git a/public/app/features/dashboard-scene/scene/new-toolbar/LeftActions.tsx b/public/app/features/dashboard-scene/scene/new-toolbar/LeftActions.tsx new file mode 100644 index 00000000000..2318694ecd9 --- /dev/null +++ b/public/app/features/dashboard-scene/scene/new-toolbar/LeftActions.tsx @@ -0,0 +1,79 @@ +import { css } from '@emotion/css'; + +import { ToolbarButtonRow, useStyles2 } from '@grafana/ui'; + +import { dynamicDashNavActions } from '../../utils/registerDynamicDashNavAction'; +import { DashboardScene } from '../DashboardScene'; + +import { ManagedDashboardBadge } from './actions/ManagedDashboardBadge'; +import { OpenSnapshotOriginButton } from './actions/OpenSnapshotOriginButton'; +import { PublicDashboardBadge } from './actions/PublicDashboardBadge'; +import { StarButton } from './actions/StarButton'; +import { getDynamicActions, renderActionElements, useIsManagedRepository } from './utils'; + +export const LeftActions = ({ dashboard }: { dashboard: DashboardScene }) => { + const styles = useStyles2(getStyles); + const { editview, editPanel, isEditing, uid, meta, viewPanelScene } = dashboard.useState(); + + const hasEditView = Boolean(editview); + const isViewingPanel = Boolean(viewPanelScene); + const isEditingDashboard = Boolean(isEditing); + const isEditingPanel = Boolean(editPanel); + const isPublicDashboard = Boolean(meta.publicDashboardEnabled); + const hasUid = Boolean(uid); + const canEdit = Boolean(meta.canEdit); + const canStar = Boolean(meta.canStar); + const isSnapshot = Boolean(meta.isSnapshot); + const isShowingDashboard = !hasEditView && !isViewingPanel && !isEditingPanel; + const isManagedRepository = useIsManagedRepository(dashboard); + + const elements = renderActionElements( + [ + // This adds the presence indicators in enterprise + ...getDynamicActions(dynamicDashNavActions.left, 'left-dynamic', !isEditingPanel), + { + key: 'star-button', + component: StarButton, + group: 'actions', + condition: hasUid && canStar && isShowingDashboard && !isEditingDashboard, + }, + { + key: 'public-dashboard-badge', + component: PublicDashboardBadge, + group: 'actions', + condition: isPublicDashboard && hasUid && canStar && isShowingDashboard && !isEditingDashboard, + }, + { + key: 'managed-dashboard-badge', + component: ManagedDashboardBadge, + group: 'actions', + condition: isManagedRepository && canEdit, + }, + { + key: 'open-snapshot-origin-button', + component: OpenSnapshotOriginButton, + group: 'actions', + condition: isSnapshot && !isEditingDashboard, + }, + // This adds the presence indicators in enterprise + ...getDynamicActions(dynamicDashNavActions.right, 'right-dynamic', !isEditingPanel && !isEditingDashboard), + ], + dashboard + ); + + if (elements.length === 0) { + return null; + } + + return ( + + {elements} + + ); +}; + +const getStyles = () => ({ + container: css({ + flex: 1, + }), +}); diff --git a/public/app/features/dashboard-scene/scene/new-toolbar/RightActions.tsx b/public/app/features/dashboard-scene/scene/new-toolbar/RightActions.tsx new file mode 100644 index 00000000000..b809a9fb90a --- /dev/null +++ b/public/app/features/dashboard-scene/scene/new-toolbar/RightActions.tsx @@ -0,0 +1,154 @@ +import { css } from '@emotion/css'; + +import { ToolbarButtonRow, useStyles2 } from '@grafana/ui'; +import { contextSrv } from 'app/core/services/context_srv'; +import { playlistSrv } from 'app/features/playlist/PlaylistSrv'; + +import { isLibraryPanel } from '../../utils/utils'; +import { DashboardScene } from '../DashboardScene'; + +import { BackToDashboardButton } from './actions/BackToDashboardButton'; +import { DashboardSettingsButton } from './actions/DashboardSettingsButton'; +import { DiscardLibraryPanelButton } from './actions/DiscardLibraryPanelButton'; +import { DiscardPanelButton } from './actions/DiscardPanelButton'; +import { EditDashboardSwitch } from './actions/EditDashboardSwitch'; +import { EditSchemaV2Button } from './actions/EditSchemaV2Button'; +import { ExportDashboardButton } from './actions/ExportDashboardButton'; +import { MakeDashboardEditableButton } from './actions/MakeDashboardEditableButton'; +import { PlayListNextButton } from './actions/PlayListNextButton'; +import { PlayListPreviousButton } from './actions/PlayListPreviousButton'; +import { PlayListStopButton } from './actions/PlayListStopButton'; +import { SaveDashboard } from './actions/SaveDashboard'; +import { SaveLibraryPanelButton } from './actions/SaveLibraryPanelButton'; +import { ShareDashboardButton } from './actions/ShareDashboardButton'; +import { UnlinkLibraryPanelButton } from './actions/UnlinkLibraryPanelButton'; +import { renderActionElements } from './utils'; + +export const RightActions = ({ dashboard }: { dashboard: DashboardScene }) => { + const styles = useStyles2(getStyles); + const { editPanel, editable, editview, isEditing, uid, meta, viewPanelScene } = dashboard.useState(); + const { isPlaying } = playlistSrv.useState(); + + const isEditable = Boolean(editable); + const canSave = Boolean(meta.canSave); + const hasUid = Boolean(uid); + const isEditingDashboard = Boolean(isEditing); + const hasEditView = Boolean(editview); + const isEditingPanel = Boolean(editPanel); + const isViewingPanel = Boolean(viewPanelScene); + const isEditingLibraryPanel = isEditingPanel && isLibraryPanel(editPanel!.state.panelRef.resolve()); + const isShowingDashboard = !hasEditView && !isViewingPanel && !isEditingPanel; + const isEditingAndShowingDashboard = isEditingDashboard && isShowingDashboard; + const isSnapshot = Boolean(meta.isSnapshot); + const canSaveInFolder = contextSrv.hasEditPermissionInFolders; + + const showPanelButtons = isEditingPanel && !hasEditView && !isViewingPanel; + const showPlayButtons = isPlaying && isShowingDashboard && !isEditingDashboard; + const showShareButton = hasUid && !isSnapshot && !isPlaying; + + return ( + + {renderActionElements( + [ + { + key: 'play-list-previous-button', + component: PlayListPreviousButton, + group: 'playlist', + condition: showPlayButtons, + }, + { + key: 'play-list-stop-button', + component: PlayListStopButton, + group: 'playlist', + condition: showPlayButtons, + }, + { + key: 'play-list-next-button', + component: PlayListNextButton, + group: 'playlist', + condition: showPlayButtons, + }, + { + key: 'back-to-dashboard-button', + component: BackToDashboardButton, + group: 'panel', + condition: hasEditView || ((isViewingPanel || isEditingPanel) && !isEditingLibraryPanel), + }, + { + key: 'discard-panel-button', + component: DiscardPanelButton, + group: 'panel', + condition: showPanelButtons && !isEditingLibraryPanel, + }, + { + key: 'discard-library-panel-button', + component: DiscardLibraryPanelButton, + group: 'panel', + condition: showPanelButtons && isEditingLibraryPanel, + }, + { + key: 'unlink-library-panel-button', + component: UnlinkLibraryPanelButton, + group: 'panel', + condition: showPanelButtons && isEditingLibraryPanel, + }, + { + key: 'save-library-panel-button', + component: SaveLibraryPanelButton, + group: 'panel', + condition: showPanelButtons && isEditingLibraryPanel, + }, + { + key: 'edit-schema-v2-button', + component: EditSchemaV2Button, + group: 'dashboard', + condition: isEditingAndShowingDashboard && hasUid, + }, + { + key: 'dashboard-settings', + component: DashboardSettingsButton, + group: 'dashboard', + condition: isEditingAndShowingDashboard && dashboard.canEditDashboard(), + }, + { + key: 'save-dashboard', + component: SaveDashboard, + group: 'save-edit', + condition: isEditingDashboard && !isEditingLibraryPanel && (canSave || canSaveInFolder), + }, + { + key: 'make-dashboard-editable-button', + component: MakeDashboardEditableButton, + group: 'save-edit', + condition: !isEditing && dashboard.canEditDashboard() && !isViewingPanel && !isEditable, + }, + { + key: 'edit-dashboard-switch', + component: EditDashboardSwitch, + group: 'save-edit', + condition: dashboard.canEditDashboard() && !isEditingLibraryPanel && !isViewingPanel && isEditable, + }, + { + key: 'new-export-dashboard-button', + component: ExportDashboardButton, + group: 'export-share', + condition: showShareButton, + }, + { + key: 'new-share-dashboard-button', + component: ShareDashboardButton, + group: 'export-share', + condition: showShareButton, + }, + ], + dashboard + )} + + ); +}; + +const getStyles = () => ({ + container: css({ + flex: 1, + }), +}); diff --git a/public/app/features/dashboard-scene/scene/new-toolbar/ToolbarActionsNew.tsx b/public/app/features/dashboard-scene/scene/new-toolbar/ToolbarActionsNew.tsx new file mode 100644 index 00000000000..f7d5a9b7784 --- /dev/null +++ b/public/app/features/dashboard-scene/scene/new-toolbar/ToolbarActionsNew.tsx @@ -0,0 +1,19 @@ +import { Stack } from '@grafana/ui'; + +import { DashboardScene } from '../DashboardScene'; + +import { LeftActions } from './LeftActions'; +import { RightActions } from './RightActions'; + +interface Props { + dashboard: DashboardScene; +} + +export function ToolbarActionsNew({ dashboard }: Props) { + return ( + + + + + ); +} diff --git a/public/app/features/dashboard-scene/scene/new-toolbar/actions/BackToDashboardButton.tsx b/public/app/features/dashboard-scene/scene/new-toolbar/actions/BackToDashboardButton.tsx new file mode 100644 index 00000000000..1bc9ba5920f --- /dev/null +++ b/public/app/features/dashboard-scene/scene/new-toolbar/actions/BackToDashboardButton.tsx @@ -0,0 +1,22 @@ +import { selectors } from '@grafana/e2e-selectors'; +import { locationService } from '@grafana/runtime'; +import { Button } from '@grafana/ui'; +import { Trans } from 'app/core/internationalization'; + +import { ToolbarActionProps } from '../types'; + +export const BackToDashboardButton = ({ dashboard }: ToolbarActionProps) => ( + +); diff --git a/public/app/features/dashboard-scene/scene/new-toolbar/actions/DashboardSettingsButton.tsx b/public/app/features/dashboard-scene/scene/new-toolbar/actions/DashboardSettingsButton.tsx new file mode 100644 index 00000000000..917e0a02c0a --- /dev/null +++ b/public/app/features/dashboard-scene/scene/new-toolbar/actions/DashboardSettingsButton.tsx @@ -0,0 +1,14 @@ +import { selectors } from '@grafana/e2e-selectors'; +import { Icon, ToolbarButton } from '@grafana/ui'; +import { t } from 'app/core/internationalization'; + +import { ToolbarActionProps } from '../types'; + +export const DashboardSettingsButton = ({ dashboard }: ToolbarActionProps) => ( + } + onClick={() => dashboard.onOpenSettings()} + data-testid={selectors.components.NavToolbar.editDashboard.settingsButton} + /> +); diff --git a/public/app/features/dashboard-scene/scene/new-toolbar/actions/DiscardLibraryPanelButton.tsx b/public/app/features/dashboard-scene/scene/new-toolbar/actions/DiscardLibraryPanelButton.tsx new file mode 100644 index 00000000000..99d1397d11e --- /dev/null +++ b/public/app/features/dashboard-scene/scene/new-toolbar/actions/DiscardLibraryPanelButton.tsx @@ -0,0 +1,18 @@ +import { selectors } from '@grafana/e2e-selectors'; +import { Button } from '@grafana/ui'; +import { t, Trans } from 'app/core/internationalization'; + +import { ToolbarActionProps } from '../types'; + +export const DiscardLibraryPanelButton = ({ dashboard }: ToolbarActionProps) => ( + +); diff --git a/public/app/features/dashboard-scene/scene/new-toolbar/actions/DiscardPanelButton.tsx b/public/app/features/dashboard-scene/scene/new-toolbar/actions/DiscardPanelButton.tsx new file mode 100644 index 00000000000..a1deec24a08 --- /dev/null +++ b/public/app/features/dashboard-scene/scene/new-toolbar/actions/DiscardPanelButton.tsx @@ -0,0 +1,54 @@ +import { useEffect, useState } from 'react'; + +import { selectors } from '@grafana/e2e-selectors'; +import { Button } from '@grafana/ui'; +import { t, Trans } from 'app/core/internationalization'; + +import { PanelEditor } from '../../../panel-edit/PanelEditor'; +import { ToolbarActionProps } from '../types'; + +export const DiscardPanelButton = ({ dashboard }: ToolbarActionProps) => { + const isEditedPanelDirty = usePanelEditDirty(dashboard.state.editPanel); + + return ( + + ); +}; + +function usePanelEditDirty(panelEditor?: PanelEditor) { + const [isDirty, setIsDirty] = useState(); + + useEffect(() => { + if (panelEditor) { + const unsub = panelEditor.subscribeToState((state) => { + if (state.isDirty !== isDirty) { + setIsDirty(state.isDirty); + } + }); + + return () => unsub.unsubscribe(); + } + + return; + }, [panelEditor, isDirty]); + + return isDirty; +} diff --git a/public/app/features/dashboard-scene/scene/new-toolbar/actions/EditDashboardSwitch.tsx b/public/app/features/dashboard-scene/scene/new-toolbar/actions/EditDashboardSwitch.tsx new file mode 100644 index 00000000000..b5ff59c9566 --- /dev/null +++ b/public/app/features/dashboard-scene/scene/new-toolbar/actions/EditDashboardSwitch.tsx @@ -0,0 +1,28 @@ +import { selectors } from '@grafana/e2e-selectors'; +import { t } from 'app/core/internationalization'; +import { playlistSrv } from 'app/features/playlist/PlaylistSrv'; + +import { ToolbarActionProps } from '../types'; + +import { ToolbarSwitch } from './ToolbarSwitch'; + +export const EditDashboardSwitch = ({ dashboard }: ToolbarActionProps) => ( + { + evt.preventDefault(); + evt.stopPropagation(); + + if (!dashboard.state.isEditing) { + dashboard.onEnterEditMode(); + } else { + dashboard.exitEditMode({ skipConfirm: false }); + } + }} + /> +); diff --git a/public/app/features/dashboard-scene/scene/new-toolbar/actions/EditSchemaV2Button.tsx b/public/app/features/dashboard-scene/scene/new-toolbar/actions/EditSchemaV2Button.tsx new file mode 100644 index 00000000000..fc18a235bdc --- /dev/null +++ b/public/app/features/dashboard-scene/scene/new-toolbar/actions/EditSchemaV2Button.tsx @@ -0,0 +1,12 @@ +import { Icon, ToolbarButton } from '@grafana/ui'; +import { t } from 'app/core/internationalization'; + +import { ToolbarActionProps } from '../types'; + +export const EditSchemaV2Button = ({ dashboard }: ToolbarActionProps) => ( + } + onClick={() => dashboard.openV2SchemaEditor()} + /> +); diff --git a/public/app/features/dashboard-scene/scene/new-toolbar/actions/ExportDashboardButton.tsx b/public/app/features/dashboard-scene/scene/new-toolbar/actions/ExportDashboardButton.tsx new file mode 100644 index 00000000000..4d361dfcdfb --- /dev/null +++ b/public/app/features/dashboard-scene/scene/new-toolbar/actions/ExportDashboardButton.tsx @@ -0,0 +1,33 @@ +import { selectors as e2eSelectors } from '@grafana/e2e-selectors'; +import { locationService } from '@grafana/runtime'; +import { t } from 'app/core/internationalization'; +import { getTrackingSource, shareDashboardType } from 'app/features/dashboard/components/ShareModal/utils'; + +import ExportMenu from '../../../sharing/ExportButton/ExportMenu'; +import { DashboardInteractions } from '../../../utils/interactions'; +import { ToolbarActionProps } from '../types'; + +import { ShareExportDashboardButton } from './ShareExportDashboardButton'; + +const newExportButtonSelector = e2eSelectors.pages.Dashboard.DashNav.NewExportButton; + +export const ExportDashboardButton = ({ dashboard }: ToolbarActionProps) => ( + } + groupTestId={newExportButtonSelector.container} + buttonLabel={t('dashboard.toolbar.new.export.title', 'Export')} + buttonTooltip={t('dashboard.toolbar.new.export.tooltip', 'Export as JSON')} + buttonTestId={newExportButtonSelector.container} + onButtonClick={() => { + locationService.partial({ shareView: shareDashboardType.export }); + + DashboardInteractions.sharingCategoryClicked({ + item: shareDashboardType.export, + shareResource: getTrackingSource(), + }); + }} + arrowLabel={t('dashboard.toolbar.new.export.arrow', 'Export')} + arrowTestId={newExportButtonSelector.arrowMenu} + dashboard={dashboard} + /> +); diff --git a/public/app/features/dashboard-scene/scene/new-toolbar/actions/MakeDashboardEditableButton.tsx b/public/app/features/dashboard-scene/scene/new-toolbar/actions/MakeDashboardEditableButton.tsx new file mode 100644 index 00000000000..d544a6b9805 --- /dev/null +++ b/public/app/features/dashboard-scene/scene/new-toolbar/actions/MakeDashboardEditableButton.tsx @@ -0,0 +1,22 @@ +import { selectors } from '@grafana/e2e-selectors'; +import { Button } from '@grafana/ui'; +import { t, Trans } from 'app/core/internationalization'; +import { playlistSrv } from 'app/features/playlist/PlaylistSrv'; + +import { ToolbarActionProps } from '../types'; + +export const MakeDashboardEditableButton = ({ dashboard }: ToolbarActionProps) => ( + +); diff --git a/public/app/features/dashboard-scene/scene/new-toolbar/actions/ManagedDashboardBadge.tsx b/public/app/features/dashboard-scene/scene/new-toolbar/actions/ManagedDashboardBadge.tsx new file mode 100644 index 00000000000..a795fa60a03 --- /dev/null +++ b/public/app/features/dashboard-scene/scene/new-toolbar/actions/ManagedDashboardBadge.tsx @@ -0,0 +1,31 @@ +import { Badge } from '@grafana/ui'; +import { AnnoKeyManagerIdentity, AnnoKeyManagerKind, ManagerKind } from 'app/features/apiserver/types'; + +import { ToolbarActionProps } from '../types'; + +export const ManagedDashboardBadge = ({ dashboard }: ToolbarActionProps) => { + if (!dashboard.state.meta.k8s?.annotations) { + return null; + } + + let text = 'Provisioned'; + const kind = dashboard.state.meta.k8s.annotations[AnnoKeyManagerKind]; + const id = dashboard.state.meta.k8s.annotations[AnnoKeyManagerIdentity]; + + switch (kind) { + case ManagerKind.Terraform: + text = 'Terraform'; + break; + case ManagerKind.Kubectl: + text = 'Kubectl'; + break; + case ManagerKind.Plugin: + text = `Plugin: ${id}`; + break; + case ManagerKind.Repo: + text = 'Repository'; + break; + } + + return ; +}; diff --git a/public/app/features/dashboard-scene/scene/new-toolbar/actions/OpenSnapshotOriginButton.tsx b/public/app/features/dashboard-scene/scene/new-toolbar/actions/OpenSnapshotOriginButton.tsx new file mode 100644 index 00000000000..c59d06a74ae --- /dev/null +++ b/public/app/features/dashboard-scene/scene/new-toolbar/actions/OpenSnapshotOriginButton.tsx @@ -0,0 +1,6 @@ +import { GoToSnapshotOriginButton } from '../../GoToSnapshotOriginButton'; +import { ToolbarActionProps } from '../types'; + +export const OpenSnapshotOriginButton = ({ dashboard }: ToolbarActionProps) => ( + +); diff --git a/public/app/features/dashboard-scene/scene/new-toolbar/actions/PlayListNextButton.tsx b/public/app/features/dashboard-scene/scene/new-toolbar/actions/PlayListNextButton.tsx new file mode 100644 index 00000000000..de3c7583713 --- /dev/null +++ b/public/app/features/dashboard-scene/scene/new-toolbar/actions/PlayListNextButton.tsx @@ -0,0 +1,16 @@ +import { selectors } from '@grafana/e2e-selectors'; +import { ToolbarButton } from '@grafana/ui'; +import { t } from 'app/core/internationalization'; +import { playlistSrv } from 'app/features/playlist/PlaylistSrv'; + +import { ToolbarActionProps } from '../types'; + +export const PlayListNextButton = ({}: ToolbarActionProps) => ( + playlistSrv.next()} + narrow + /> +); diff --git a/public/app/features/dashboard-scene/scene/new-toolbar/actions/PlayListPreviousButton.tsx b/public/app/features/dashboard-scene/scene/new-toolbar/actions/PlayListPreviousButton.tsx new file mode 100644 index 00000000000..782253dc482 --- /dev/null +++ b/public/app/features/dashboard-scene/scene/new-toolbar/actions/PlayListPreviousButton.tsx @@ -0,0 +1,15 @@ +import { selectors } from '@grafana/e2e-selectors'; +import { ToolbarButton } from '@grafana/ui'; +import { t } from 'app/core/internationalization'; +import { playlistSrv } from 'app/features/playlist/PlaylistSrv'; + +import { ToolbarActionProps } from '../types'; + +export const PlayListPreviousButton = ({}: ToolbarActionProps) => ( + playlistSrv.prev()} + /> +); diff --git a/public/app/features/dashboard-scene/scene/new-toolbar/actions/PlayListStopButton.tsx b/public/app/features/dashboard-scene/scene/new-toolbar/actions/PlayListStopButton.tsx new file mode 100644 index 00000000000..0b8a0f10bac --- /dev/null +++ b/public/app/features/dashboard-scene/scene/new-toolbar/actions/PlayListStopButton.tsx @@ -0,0 +1,15 @@ +import { selectors } from '@grafana/e2e-selectors'; +import { ToolbarButton } from '@grafana/ui'; +import { Trans } from 'app/core/internationalization'; +import { playlistSrv } from 'app/features/playlist/PlaylistSrv'; + +import { ToolbarActionProps } from '../types'; + +export const PlayListStopButton = ({}: ToolbarActionProps) => ( + playlistSrv.stop()} + data-testid={selectors.pages.Dashboard.DashNav.playlistControls.stop} + > + Stop playlist + +); diff --git a/public/app/features/dashboard-scene/scene/new-toolbar/actions/PublicDashboardBadge.tsx b/public/app/features/dashboard-scene/scene/new-toolbar/actions/PublicDashboardBadge.tsx new file mode 100644 index 00000000000..0194fb76d8f --- /dev/null +++ b/public/app/features/dashboard-scene/scene/new-toolbar/actions/PublicDashboardBadge.tsx @@ -0,0 +1,28 @@ +import { css } from '@emotion/css'; + +import { selectors } from '@grafana/e2e-selectors'; +import { Badge, useStyles2 } from '@grafana/ui'; +import { t } from 'app/core/internationalization'; + +import { ToolbarActionProps } from '../types'; + +export const PublicDashboardBadge = ({}: ToolbarActionProps) => { + const styles = useStyles2(getStyles); + + return ( + + ); +}; + +const getStyles = () => ({ + badge: css({ + color: 'grey', + backgroundColor: 'transparent', + border: '1px solid', + }), +}); diff --git a/public/app/features/dashboard-scene/scene/new-toolbar/actions/SaveDashboard.tsx b/public/app/features/dashboard-scene/scene/new-toolbar/actions/SaveDashboard.tsx new file mode 100644 index 00000000000..3600add803f --- /dev/null +++ b/public/app/features/dashboard-scene/scene/new-toolbar/actions/SaveDashboard.tsx @@ -0,0 +1,80 @@ +import { selectors } from '@grafana/e2e-selectors'; +import { Button, ButtonGroup, Dropdown, Menu } from '@grafana/ui'; +import { t, Trans } from 'app/core/internationalization'; +import { contextSrv } from 'app/core/services/context_srv'; + +import { ToolbarActionProps } from '../types'; +import { useIsManagedRepository } from '../utils'; + +export const SaveDashboard = ({ dashboard }: ToolbarActionProps) => { + const { meta, isDirty, uid } = dashboard.state; + + const isNew = !Boolean(uid || dashboard.isManaged()); + const isManagedRepository = useIsManagedRepository(dashboard); + + // if we only can save + if (isNew) { + return ( + + ); + } + + // If we only can save as copy + if (contextSrv.hasEditPermissionInFolders && !meta.canSave && !meta.canMakeEditable && !isManagedRepository) { + return ( + + ); + } + + return ( + + + + dashboard.openSaveDrawer({})} + /> + dashboard.openSaveDrawer({ saveAsCopy: true })} + /> + + } + > + +); diff --git a/public/app/features/dashboard-scene/scene/new-toolbar/actions/ShareDashboardButton.tsx b/public/app/features/dashboard-scene/scene/new-toolbar/actions/ShareDashboardButton.tsx new file mode 100644 index 00000000000..8cacae1a4aa --- /dev/null +++ b/public/app/features/dashboard-scene/scene/new-toolbar/actions/ShareDashboardButton.tsx @@ -0,0 +1,40 @@ +import { useAsyncFn } from 'react-use'; + +import { selectors as e2eSelectors } from '@grafana/e2e-selectors'; +import { t } from 'app/core/internationalization'; + +import ShareMenu from '../../../sharing/ShareButton/ShareMenu'; +import { buildShareUrl } from '../../../sharing/ShareButton/utils'; +import { DashboardInteractions } from '../../../utils/interactions'; +import { ToolbarActionProps } from '../types'; + +import { ShareExportDashboardButton } from './ShareExportDashboardButton'; + +const newShareButtonSelector = e2eSelectors.pages.Dashboard.DashNav.newShareButton; + +export const ShareDashboardButton = ({ dashboard }: ToolbarActionProps) => { + const [_, buildUrl] = useAsyncFn(async () => { + DashboardInteractions.toolbarShareClick(); + return await buildShareUrl(dashboard); + }, [dashboard]); + + return ( + } + onMenuVisibilityChange={(isOpen) => { + if (isOpen) { + DashboardInteractions.toolbarShareDropdownClick(); + } + }} + groupTestId={newShareButtonSelector.shareLink} + buttonLabel={t('dashboard.toolbar.new.share.title', 'Share')} + buttonTooltip={t('dashboard.toolbar.new.share.tooltip', 'Copy link')} + buttonTestId={newShareButtonSelector.container} + onButtonClick={buildUrl} + arrowLabel={t('dashboard.toolbar.new.share.arrow', 'Share')} + arrowTestId={newShareButtonSelector.arrowMenu} + dashboard={dashboard} + variant={!dashboard.state.isEditing ? 'primary' : 'secondary'} + /> + ); +}; diff --git a/public/app/features/dashboard-scene/scene/new-toolbar/actions/ShareExportDashboardButton.tsx b/public/app/features/dashboard-scene/scene/new-toolbar/actions/ShareExportDashboardButton.tsx new file mode 100644 index 00000000000..bebed306fbe --- /dev/null +++ b/public/app/features/dashboard-scene/scene/new-toolbar/actions/ShareExportDashboardButton.tsx @@ -0,0 +1,100 @@ +import { css } from '@emotion/css'; +import { ReactElement, useState } from 'react'; + +import { Button, ButtonGroup, Dropdown, useStyles2 } from '@grafana/ui'; +import appEvents from 'app/core/app_events'; +import { t } from 'app/core/internationalization'; +import { ShowConfirmModalEvent } from 'app/types/events'; + +import { ToolbarActionProps } from '../types'; + +interface Props extends ToolbarActionProps { + menu: ReactElement | (() => ReactElement); + onMenuVisibilityChange?: (isOpen: boolean) => void; + groupTestId: string; + buttonLabel: string; + buttonTooltip: string; + buttonTestId: string; + onButtonClick?: () => void; + arrowLabel: string; + arrowTestId: string; + variant?: 'primary' | 'secondary'; +} + +export const ShareExportDashboardButton = ({ + dashboard, + menu, + onMenuVisibilityChange, + groupTestId, + buttonLabel, + buttonTooltip, + buttonTestId, + onButtonClick, + arrowLabel, + arrowTestId, + variant = 'secondary', +}: Props) => { + const styles = useStyles2(getStyles); + const [isOpen, setIsOpen] = useState(false); + + return ( + { + if (dashboard.state.isEditing && dashboard.state.isDirty) { + evt.preventDefault(); + evt.stopPropagation(); + + appEvents.publish( + new ShowConfirmModalEvent({ + title: t('dashboard.toolbar.new.share-export.modal.title', 'Save changes to dashboard?'), + text: t( + 'dashboard.toolbar.new.share-export.modal.text', + 'You have unsaved changes to this dashboard. You need to save them before you can share it.' + ), + icon: 'exclamation-triangle', + noText: t('dashboard.toolbar.new.share-export.modal.noText', 'Discard'), + yesText: t('dashboard.toolbar.new.share-export.modal.yesText', 'Save'), + yesButtonVariant: 'primary', + onConfirm: () => dashboard.openSaveDrawer({}), + }) + ); + } + }} + > + + { + if (dashboard.state.isEditing && dashboard.state.isDirty) { + return; + } + + onMenuVisibilityChange?.(isOpen); + + setIsOpen(isOpen); + }} + > + +); diff --git a/public/app/features/dashboard-scene/scene/new-toolbar/types.ts b/public/app/features/dashboard-scene/scene/new-toolbar/types.ts new file mode 100644 index 00000000000..6fe9a200b92 --- /dev/null +++ b/public/app/features/dashboard-scene/scene/new-toolbar/types.ts @@ -0,0 +1,14 @@ +import { FC } from 'react'; + +import { DashboardScene } from '../DashboardScene'; + +export interface ToolbarAction { + key: string; + component: FC; + group: string; + condition: boolean; +} + +export interface ToolbarActionProps { + dashboard: DashboardScene; +} diff --git a/public/app/features/dashboard-scene/scene/new-toolbar/utils.tsx b/public/app/features/dashboard-scene/scene/new-toolbar/utils.tsx new file mode 100644 index 00000000000..0b44e415a75 --- /dev/null +++ b/public/app/features/dashboard-scene/scene/new-toolbar/utils.tsx @@ -0,0 +1,62 @@ +import { ReactNode } from 'react'; + +import { NavToolbarSeparator } from 'app/core/components/AppChrome/NavToolbar/NavToolbarSeparator'; +import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv'; +import { selectFolderRepository } from 'app/features/provisioning/utils/selectors'; +import { useSelector } from 'app/types'; + +import { DynamicDashNavButtonModel } from '../../utils/registerDynamicDashNavAction'; +import { DashboardScene } from '../DashboardScene'; + +import { ToolbarAction } from './types'; + +export function renderActionElements(toolbarActions: ToolbarAction[], dashboard: DashboardScene): ReactNode[] { + const actionElements: ReactNode[] = []; + let lastGroup = ''; + + for (const action of toolbarActions) { + if (!action.condition) { + continue; + } + + if (lastGroup && lastGroup !== action.group) { + actionElements.push(); + } + + actionElements.push(); + lastGroup = action.group; + } + + return actionElements; +} + +export function getDynamicActions( + registeredActions: DynamicDashNavButtonModel[], + group: string, + condition: boolean +): ToolbarAction[] { + const dashboard = getDashboardSrv().getCurrent()!; + + return registeredActions.reduce((acc, action) => { + const props = { dashboard }; + + if (!action.show(props)) { + return acc; + } + + acc.push({ + key: acc.length.toString(), + group, + condition, + component: () => , + }); + + return acc; + }, []); +} + +export function useIsManagedRepository(dashboard: DashboardScene): boolean { + const folderRepo = useSelector((state) => selectFolderRepository(state, dashboard.state.meta.folderUid)); + + return Boolean(dashboard.isManagedRepository() || folderRepo); +} diff --git a/public/app/features/dashboard-scene/serialization/DashboardSceneSerializer.test.ts b/public/app/features/dashboard-scene/serialization/DashboardSceneSerializer.test.ts index b4d8d8306b3..a48e8da3870 100644 --- a/public/app/features/dashboard-scene/serialization/DashboardSceneSerializer.test.ts +++ b/public/app/features/dashboard-scene/serialization/DashboardSceneSerializer.test.ts @@ -392,7 +392,7 @@ describe('DashboardSceneSerializer', () => { ], }; - serializer.initializeMapping(saveModel); + serializer.initializeElementMapping(saveModel); const mapping = serializer.getElementPanelMapping(); expect(mapping.size).toBe(2); @@ -401,10 +401,10 @@ describe('DashboardSceneSerializer', () => { }); it('should handle empty or undefined panels in initializeMapping', () => { - serializer.initializeMapping(undefined); + serializer.initializeElementMapping(undefined); expect(serializer.getElementPanelMapping().size).toBe(0); - serializer.initializeMapping({ + serializer.initializeElementMapping({ title: 'hello', uid: 'my-uid', schemaVersion: 30, @@ -424,7 +424,7 @@ describe('DashboardSceneSerializer', () => { ], }; - serializer.initializeMapping(saveModel); + serializer.initializeElementMapping(saveModel); expect(serializer.getPanelIdForElement('panel-1')).toBe(1); expect(serializer.getPanelIdForElement('panel-2')).toBe(2); @@ -441,7 +441,7 @@ describe('DashboardSceneSerializer', () => { { id: 2, title: 'Panel 2', type: 'text' }, ], }; - serializer.initializeMapping(saveModel); + serializer.initializeElementMapping(saveModel); expect(serializer.getElementIdForPanel(1)).toBe('panel-1'); expect(serializer.getElementIdForPanel(2)).toBe('panel-2'); @@ -923,7 +923,7 @@ describe('DashboardSceneSerializer', () => { }); it('should initialize panel mapping correctly', () => { - serializer.initializeMapping(saveModel); + serializer.initializeElementMapping(saveModel); const mapping = serializer.getElementPanelMapping(); expect(mapping.size).toBe(2); @@ -932,15 +932,15 @@ describe('DashboardSceneSerializer', () => { }); it('should handle empty or undefined elements in initializeMapping', () => { - serializer.initializeMapping({} as DashboardV2Spec); + serializer.initializeElementMapping({} as DashboardV2Spec); expect(serializer.getElementPanelMapping().size).toBe(0); - serializer.initializeMapping({ elements: {} } as DashboardV2Spec); + serializer.initializeElementMapping({ elements: {} } as DashboardV2Spec); expect(serializer.getElementPanelMapping().size).toBe(0); }); it('should get panel id for element correctly', () => { - serializer.initializeMapping(saveModel); + serializer.initializeElementMapping(saveModel); expect(serializer.getPanelIdForElement('element-panel-a')).toBe(1); expect(serializer.getPanelIdForElement('element-panel-b')).toBe(2); @@ -948,7 +948,7 @@ describe('DashboardSceneSerializer', () => { }); it('should get element id for panel correctly', () => { - serializer.initializeMapping(saveModel); + serializer.initializeElementMapping(saveModel); expect(serializer.getElementIdForPanel(1)).toBe('element-panel-a'); expect(serializer.getElementIdForPanel(2)).toBe('element-panel-b'); @@ -958,6 +958,147 @@ describe('DashboardSceneSerializer', () => { }); }); + describe('Datasource References Mapping', () => { + describe('V2DashboardSerializer', () => { + let serializer: V2DashboardSerializer; + + beforeEach(() => { + serializer = new V2DashboardSerializer(); + }); + + it('should initialize datasource references mapping correctly for panels with undefined datasources', () => { + const saveModel: DashboardV2Spec = { + ...defaultDashboardV2Spec(), + title: 'Test Dashboard', + elements: { + 'panel-1': { + kind: 'Panel', + spec: { + id: 1, + title: 'Panel 1', + description: '', + links: [], + vizConfig: { + kind: 'timeseries', + spec: { + pluginVersion: '1.0.0', + options: {}, + fieldConfig: { defaults: {}, overrides: [] }, + }, + }, + data: { + kind: 'QueryGroup', + spec: { + queries: [ + { + kind: 'PanelQuery', + spec: { + refId: 'A', + hidden: false, + // No datasource defined + query: { kind: 'sql', spec: {} }, + }, + }, + { + kind: 'PanelQuery', + spec: { + refId: 'B', + hidden: false, + datasource: { uid: 'datasource-1', type: 'prometheus' }, + query: { kind: 'prometheus', spec: {} }, + }, + }, + ], + queryOptions: {}, + transformations: [], + }, + }, + }, + }, + 'panel-2': { + kind: 'Panel', + spec: { + id: 2, + title: 'Panel 2', + description: '', + links: [], + vizConfig: { + kind: 'timeseries', + spec: { + pluginVersion: '1.0.0', + options: {}, + fieldConfig: { defaults: {}, overrides: [] }, + }, + }, + data: { + kind: 'QueryGroup', + spec: { + queries: [ + { + kind: 'PanelQuery', + spec: { + refId: 'C', + hidden: false, + // No datasource defined + query: { kind: 'sql', spec: {} }, + }, + }, + ], + queryOptions: {}, + transformations: [], + }, + }, + }, + }, + }, + }; + + serializer.initializeElementMapping(saveModel); + serializer.initializeDSReferencesMapping(saveModel); + + const dsReferencesMap = serializer.getDSReferencesMapping(); + + // Panel 1 should have refId A in the map (no datasource) + expect(dsReferencesMap.panels.has('panel-1')).toBe(true); + expect(dsReferencesMap.panels.get('panel-1')?.has('A')).toBe(true); + expect(dsReferencesMap.panels.get('panel-1')?.has('B')).toBe(false); // Has datasource defined + + // Panel 2 should have refId C in the map + expect(dsReferencesMap.panels.has('panel-2')).toBe(true); + expect(dsReferencesMap.panels.get('panel-2')?.has('C')).toBe(true); + }); + + it('should handle empty or undefined elements in initializeDSReferencesMapping', () => { + serializer.initializeDSReferencesMapping(undefined); + expect(serializer.getDSReferencesMapping().panels.size).toBe(0); + + serializer.initializeDSReferencesMapping({} as DashboardV2Spec); + expect(serializer.getDSReferencesMapping().panels.size).toBe(0); + + serializer.initializeDSReferencesMapping({ elements: {} } as DashboardV2Spec); + expect(serializer.getDSReferencesMapping().panels.size).toBe(0); + }); + }); + + describe('V1DashboardSerializer', () => { + let serializer: V1DashboardSerializer; + + beforeEach(() => { + serializer = new V1DashboardSerializer(); + }); + + it('should return empty mapping object for V1 serializer', () => { + serializer.initializeDSReferencesMapping(undefined); + expect(serializer.getDSReferencesMapping()).toEqual({ + panels: expect.any(Map), + variables: expect.any(Set), + annotations: expect.any(Set), + }); + expect(serializer.getDSReferencesMapping().panels.size).toBe(0); + }); + }); + }); + describe('onSaveComplete', () => { it('should set the initialSaveModel correctly', () => { const serializer = new V2DashboardSerializer(); diff --git a/public/app/features/dashboard-scene/serialization/DashboardSceneSerializer.ts b/public/app/features/dashboard-scene/serialization/DashboardSceneSerializer.ts index 98c182adc49..a5c3ec6e4d4 100644 --- a/public/app/features/dashboard-scene/serialization/DashboardSceneSerializer.ts +++ b/public/app/features/dashboard-scene/serialization/DashboardSceneSerializer.ts @@ -30,7 +30,8 @@ export interface DashboardSceneSerializerLike { */ initialSaveModel?: I; metadata?: M; - initializeMapping(saveModel: T | undefined): void; + initializeElementMapping(saveModel: T | undefined): void; + initializeDSReferencesMapping(saveModel: T | undefined): void; getSaveModel: (s: DashboardScene) => T; getSaveAsModel: (s: DashboardScene, options: SaveDashboardAsOptions) => T; getDashboardChangesFromScene: ( @@ -47,6 +48,7 @@ export interface DashboardSceneSerializerLike { getPanelIdForElement: (elementId: string) => number | undefined; getElementIdForPanel: (panelId: number) => string | undefined; getElementPanelMapping: () => Map; + getDSReferencesMapping: () => DSReferencesMapping; } interface DashboardTrackingInfo { @@ -58,12 +60,23 @@ interface DashboardTrackingInfo { settings_livenow?: boolean; } +interface DSReferencesMapping { + panels: Map>; + variables: Set; + annotations: Set; +} + export class V1DashboardSerializer implements DashboardSceneSerializerLike { initialSaveModel?: Dashboard; metadata?: DashboardMeta; protected elementPanelMap = new Map(); + protected defaultDsReferencesMap = { + panels: new Map>(), // refIds as keys + variables: new Set(), // variable names as keys + annotations: new Set(), // annotation names as keys + }; - initializeMapping(saveModel: Dashboard | undefined) { + initializeElementMapping(saveModel: Dashboard | undefined) { this.elementPanelMap.clear(); if (!saveModel || !saveModel.panels) { @@ -81,6 +94,15 @@ export class V1DashboardSerializer implements DashboardSceneSerializerLike['metadata']; protected elementPanelMap = new Map(); + // map of elementId that will contain all the queries, variables and annotations that dont have a ds defined + protected defaultDsReferencesMap = { + panels: new Map>(), // refIds as keys + variables: new Set(), // variable names as keys + annotations: new Set(), // annotation names as keys + }; getElementPanelMapping() { return this.elementPanelMap; } - initializeMapping(saveModel: DashboardV2Spec | undefined) { + initializeElementMapping(saveModel: DashboardV2Spec | undefined) { this.elementPanelMap.clear(); if (!saveModel || !saveModel.elements) { @@ -206,6 +234,42 @@ export class V2DashboardSerializer }); } + initializeDSReferencesMapping(saveModel: DashboardV2Spec | undefined) { + // initialize the object + this.defaultDsReferencesMap = { + panels: new Map>(), + variables: new Set(), + annotations: new Set(), + }; + + // get all the element keys + const elementKeys = Object.keys(saveModel?.elements || {}); + elementKeys.forEach((key) => { + const elementPanel = saveModel?.elements[key]; + if (elementPanel?.kind === 'Panel') { + // check if the elementPanel.spec.datasource is defined + const panelQueries = elementPanel.spec.data.spec.queries; + + for (const query of panelQueries) { + if (!query.spec.datasource) { + const elementId = this.getElementIdForPanel(elementPanel.spec.id); + if (!this.defaultDsReferencesMap.panels.has(elementId)) { + this.defaultDsReferencesMap.panels.set(elementId, new Set()); + } + + const panelDsqueries = this.defaultDsReferencesMap.panels.get(elementId)!; + + panelDsqueries.add(query.spec.refId); + } + } + } + }); + } + + getDSReferencesMapping() { + return this.defaultDsReferencesMap; + } + getPanelIdForElement(elementId: string) { return this.elementPanelMap.get(elementId); } diff --git a/public/app/features/dashboard-scene/serialization/layoutSerializers/RowsLayoutSerializer.ts b/public/app/features/dashboard-scene/serialization/layoutSerializers/RowsLayoutSerializer.ts index ef3402cc304..c403b5a3d7a 100644 --- a/public/app/features/dashboard-scene/serialization/layoutSerializers/RowsLayoutSerializer.ts +++ b/public/app/features/dashboard-scene/serialization/layoutSerializers/RowsLayoutSerializer.ts @@ -16,9 +16,6 @@ export class RowsLayoutSerializer implements LayoutManagerSerializer { spec: { rows: layoutManager.state.rows.map((row) => { const layout = getLayout(row.state.layout); - if (layout.kind === 'RowsLayout') { - throw new Error('Nested RowsLayout is not supported'); - } const rowKind: RowsLayoutRowKind = { kind: 'RowsLayoutRow', spec: { diff --git a/public/app/features/dashboard-scene/serialization/layoutSerializers/TabsLayoutSerializer.ts b/public/app/features/dashboard-scene/serialization/layoutSerializers/TabsLayoutSerializer.ts index d5093fb4663..c3f7eedc9db 100644 --- a/public/app/features/dashboard-scene/serialization/layoutSerializers/TabsLayoutSerializer.ts +++ b/public/app/features/dashboard-scene/serialization/layoutSerializers/TabsLayoutSerializer.ts @@ -14,9 +14,6 @@ export class TabsLayoutSerializer implements LayoutManagerSerializer { spec: { tabs: layoutManager.state.tabs.map((tab) => { const layout = getLayout(tab.state.layout); - if (layout.kind === 'TabsLayout') { - throw new Error('Nested TabsLayout is not supported'); - } return { kind: 'TabsLayoutTab', spec: { diff --git a/public/app/features/dashboard-scene/serialization/layoutSerializers/utils.ts b/public/app/features/dashboard-scene/serialization/layoutSerializers/utils.ts index 637ce85c7a3..66bcaed53f7 100644 --- a/public/app/features/dashboard-scene/serialization/layoutSerializers/utils.ts +++ b/public/app/features/dashboard-scene/serialization/layoutSerializers/utils.ts @@ -182,13 +182,21 @@ function getPanelDataSource(panel: PanelKind): DataSourceRef | undefined { panel.spec.data.spec.queries.forEach((query) => { if (!datasource) { - datasource = query.spec.datasource; + if (!query.spec.datasource?.uid) { + const defaultDatasource = config.bootData.settings.defaultDatasource; + const dsList = config.bootData.settings.datasources; + // this is look up by type + const bestGuess = Object.values(dsList).find((ds) => ds.meta.id === query.spec.query.kind); + datasource = bestGuess ? { uid: bestGuess.uid, type: bestGuess.meta.id } : dsList[defaultDatasource]; + } else { + datasource = query.spec.datasource; + } } else if (datasource.uid !== query.spec.datasource?.uid || datasource.type !== query.spec.datasource?.type) { isMixedDatasource = true; } }); - return isMixedDatasource ? { type: 'mixed', uid: MIXED_DATASOURCE_NAME } : undefined; + return isMixedDatasource ? { type: 'mixed', uid: MIXED_DATASOURCE_NAME } : datasource; } function panelQueryKindToSceneQuery(query: PanelQueryKind): SceneDataQuery { diff --git a/public/app/features/dashboard-scene/serialization/transformSaveModelSchemaV2ToScene.ts b/public/app/features/dashboard-scene/serialization/transformSaveModelSchemaV2ToScene.ts index 1230f03b547..ceb9ebd7eeb 100644 --- a/public/app/features/dashboard-scene/serialization/transformSaveModelSchemaV2ToScene.ts +++ b/public/app/features/dashboard-scene/serialization/transformSaveModelSchemaV2ToScene.ts @@ -11,10 +11,6 @@ import { IntervalVariable, QueryVariable, SceneDataLayerControls, - SceneDataProvider, - SceneDataQuery, - SceneDataTransformer, - SceneQueryRunner, SceneRefreshPicker, SceneTimePicker, SceneTimeRange, @@ -23,7 +19,6 @@ import { TextBoxVariable, VariableValueSelectors, } from '@grafana/scenes'; -import { DataSourceRef } from '@grafana/schema/dist/esm/index.gen'; import { AdhocVariableKind, ConstantVariableKind, @@ -42,7 +37,6 @@ import { IntervalVariableKind, LibraryPanelKind, PanelKind, - PanelQueryKind, QueryVariableKind, TextVariableKind, } from '@grafana/schema/src/schema/dashboard/v2alpha0'; @@ -55,14 +49,12 @@ import { DeprecatedInternalId, } from 'app/features/apiserver/types'; import { DashboardWithAccessInfo } from 'app/features/dashboard/api/types'; -import { MIXED_DATASOURCE_NAME } from 'app/plugins/datasource/mixed/MixedDataSource'; import { DashboardMeta } from 'app/types'; import { addPanelsOnLoadBehavior } from '../addToDashboard/addPanelsOnLoadBehavior'; import { DashboardAnnotationsDataLayer } from '../scene/DashboardAnnotationsDataLayer'; import { DashboardControls } from '../scene/DashboardControls'; import { DashboardDataLayerSet } from '../scene/DashboardDataLayerSet'; -import { DashboardDatasourceBehaviour } from '../scene/DashboardDatasourceBehaviour'; import { registerDashboardMacro } from '../scene/DashboardMacro'; import { DashboardReloadBehavior } from '../scene/DashboardReloadBehavior'; import { DashboardScene } from '../scene/DashboardScene'; @@ -218,71 +210,6 @@ export function transformSaveModelSchemaV2ToScene(dto: DashboardWithAccessInfo { - if (!datasource) { - datasource = query.spec.datasource; - } else if (datasource.uid !== query.spec.datasource?.uid || datasource.type !== query.spec.datasource?.type) { - isMixedDatasource = true; - } - }); - - return isMixedDatasource ? { type: 'mixed', uid: MIXED_DATASOURCE_NAME } : datasource; -} - -function panelQueryKindToSceneQuery(query: PanelQueryKind): SceneDataQuery { - return { - refId: query.spec.refId, - datasource: query.spec.datasource, - hide: query.spec.hidden, - ...query.spec.query.spec, - }; -} - -export function createPanelDataProvider(panelKind: PanelKind): SceneDataProvider | undefined { - const panel = panelKind.spec; - const targets = panel.data?.spec.queries ?? []; - // Skip setting query runner for panels without queries - if (!targets?.length) { - return undefined; - } - - // Skip setting query runner for panel plugins with skipDataQuery - if (config.panels[panel.vizConfig.kind]?.skipDataQuery) { - return undefined; - } - - let dataProvider: SceneDataProvider | undefined = undefined; - const datasource = getPanelDataSource(panelKind); - - dataProvider = new SceneQueryRunner({ - datasource, - queries: targets.map(panelQueryKindToSceneQuery), - maxDataPoints: panel.data.spec.queryOptions.maxDataPoints ?? undefined, - maxDataPointsFromWidth: true, - cacheTimeout: panel.data.spec.queryOptions.cacheTimeout, - queryCachingTTL: panel.data.spec.queryOptions.queryCachingTTL, - minInterval: panel.data.spec.queryOptions.interval ?? undefined, - dataLayerFilter: { - panelId: panel.id, - }, - $behaviors: [new DashboardDatasourceBehaviour({})], - }); - - // Wrap inner data provider in a data transformer - return new SceneDataTransformer({ - $data: dataProvider, - transformations: panel.data.spec.transformations.map((transformation) => transformation.spec), - }); -} - function getVariables(dashboard: DashboardV2Spec, isSnapshot: boolean): SceneVariableSet | undefined { let variables: SceneVariableSet | undefined; diff --git a/public/app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2.test.ts b/public/app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2.test.ts index db16a475798..ff1df0ded25 100644 --- a/public/app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2.test.ts +++ b/public/app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2.test.ts @@ -17,6 +17,8 @@ import { SceneVariableSet, TextBoxVariable, VizPanel, + SceneDataQuery, + SceneQueryRunner, } from '@grafana/scenes'; import { DashboardCursorSync as DashboardCursorSyncV1, @@ -48,7 +50,38 @@ import { TabItem } from '../scene/layout-tabs/TabItem'; import { TabsLayoutManager } from '../scene/layout-tabs/TabsLayoutManager'; import { DashboardLayoutManager } from '../scene/types/DashboardLayoutManager'; -import { transformSceneToSaveModelSchemaV2 } from './transformSceneToSaveModelSchemaV2'; +import { getPersistedDSForQuery, transformSceneToSaveModelSchemaV2 } from './transformSceneToSaveModelSchemaV2'; + +// Mock dependencies +jest.mock('../utils/dashboardSceneGraph', () => { + const original = jest.requireActual('../utils/dashboardSceneGraph'); + return { + ...original, + dashboardSceneGraph: { + ...original.dashboardSceneGraph, + getElementIdentifierForVizPanel: jest.fn().mockImplementation((panel) => { + // Return the panel key if it exists, otherwise use panel-1 as default + return panel?.state?.key || 'panel-1'; + }), + }, + }; +}); + +jest.mock('../utils/utils', () => { + const original = jest.requireActual('../utils/utils'); + return { + ...original, + getDashboardSceneFor: jest.fn().mockImplementation(() => ({ + serializer: { + getDSReferencesMapping: jest.fn().mockReturnValue({ + panels: new Map([['panel-1', new Set(['A'])]]), + variables: new Set(), + annotations: new Set(), + }), + }, + })), + }; +}); function setupDashboardScene(state: Partial): DashboardScene { return new DashboardScene(state); @@ -100,7 +133,7 @@ describe('transformSceneToSaveModelSchemaV2', () => { // The intention is to have a complete dashboard scene // with all the possible properties set dashboardScene = setupDashboardScene({ - $data: new DashboardDataLayerSet({ annotationLayers }), + $data: new DashboardDataLayerSet({ annotationLayers: createAnnotationLayers() }), id: 1, title: 'Test Dashboard', description: 'Test Description', @@ -377,6 +410,84 @@ describe('transformSceneToSaveModelSchemaV2', () => { // check annotation layer 3 with no datasource has the default datasource defined as type expect(result.annotations?.[2].spec.datasource?.type).toBe('loki'); }); + + describe('getPersistedDSForQuery', () => { + it('should respect datasource reference mapping when determining query datasource', () => { + // Setup test data + const queryWithoutDS: SceneDataQuery = { + refId: 'A', + // No datasource defined originally + }; + const queryWithDS: SceneDataQuery = { + refId: 'B', + datasource: { uid: 'prometheus', type: 'prometheus' }, + }; + + // Mock query runner with runtime-resolved datasource + const queryRunner = new SceneQueryRunner({ + queries: [queryWithoutDS, queryWithDS], + datasource: { uid: 'default-ds', type: 'default' }, + }); + + // Get a reference to the DS references mapping + const dsReferencesMap = new Set(['A']); + + // Test the query without DS originally - should return undefined + const resultA = getPersistedDSForQuery(queryWithoutDS, queryRunner, dsReferencesMap); + expect(resultA).toBeUndefined(); + + // Test the query with DS originally - should return the original datasource + const resultB = getPersistedDSForQuery(queryWithDS, queryRunner, dsReferencesMap); + expect(resultB).toEqual({ uid: 'prometheus', type: 'prometheus' }); + + // Test a query with no DS originally but not in the mapping - should get the runner's datasource + const queryNotInMapping: SceneDataQuery = { + refId: 'C', + // No datasource, but not in mapping + }; + const resultC = getPersistedDSForQuery(queryNotInMapping, queryRunner, dsReferencesMap); + expect(resultC).toEqual({ uid: 'default-ds', type: 'default' }); + }); + }); + + describe('getDatasourceForQueries', () => { + it('should respect datasource reference mapping when determining which queries should have datasources saved', () => { + // Setup test data + const queryWithoutDS: SceneDataQuery = { + refId: 'A', + // No datasource defined originally + }; + const queryWithDS: SceneDataQuery = { + refId: 'B', + datasource: { uid: 'prometheus', type: 'prometheus' }, + }; + + // Mock query runner with runtime-resolved datasource + const queryRunner = new SceneQueryRunner({ + queries: [queryWithoutDS, queryWithDS], + datasource: { uid: 'default-ds', type: 'default' }, + }); + + // Get a reference to the DS references mapping + const dsReferencesMap = new Set(['A']); + + // Test the query without DS originally - should return undefined + const resultA = getPersistedDSForQuery(queryWithoutDS, queryRunner, dsReferencesMap); + expect(resultA).toBeUndefined(); + + // Test the query with DS originally - should return the original datasource + const resultB = getPersistedDSForQuery(queryWithDS, queryRunner, dsReferencesMap); + expect(resultB).toEqual({ uid: 'prometheus', type: 'prometheus' }); + + // Test a query with no DS originally but not in the mapping - should get the runner's datasource + const queryNotInMapping: SceneDataQuery = { + refId: 'C', + // No datasource, but not in mapping + }; + const resultC = getPersistedDSForQuery(queryNotInMapping, queryRunner, dsReferencesMap); + expect(resultC).toEqual({ uid: 'default-ds', type: 'default' }); + }); + }); }); function getMinimalSceneState(body: DashboardLayoutManager): Partial { @@ -571,49 +682,50 @@ describe('dynamic layouts', () => { }); }); -const annotationLayer1 = new DashboardAnnotationsDataLayer({ - key: 'layer1', - query: { - datasource: { - type: 'grafana', - uid: '-- Grafana --', - }, - name: 'query1', - enable: true, - iconColor: 'red', - }, - name: 'layer1', - isEnabled: true, - isHidden: false, -}); - -const annotationLayer2 = new DashboardAnnotationsDataLayer({ - key: 'layer2', - query: { - datasource: { - type: 'prometheus', - uid: 'abcdef', - }, - name: 'query2', - enable: true, - iconColor: 'blue', - }, - name: 'layer2', - isEnabled: true, - isHidden: true, -}); - -// this could happen if a dahboard was created from code and the datasource was not defined -const annotationLayer3NoDsDefined = new DashboardAnnotationsDataLayer({ - key: 'layer3', - query: { - name: 'query3', - enable: true, - iconColor: 'green', - }, - name: 'layer3', - isEnabled: true, - isHidden: true, -}); - -const annotationLayers = [annotationLayer1, annotationLayer2, annotationLayer3NoDsDefined]; +// Instead of reusing annotation layer objects, create a factory function to generate new ones each time +function createAnnotationLayers() { + return [ + new DashboardAnnotationsDataLayer({ + key: 'layer1', + query: { + datasource: { + type: 'grafana', + uid: '-- Grafana --', + }, + name: 'query1', + enable: true, + iconColor: 'red', + }, + name: 'layer1', + isEnabled: true, + isHidden: false, + }), + new DashboardAnnotationsDataLayer({ + key: 'layer2', + query: { + datasource: { + type: 'prometheus', + uid: 'abcdef', + }, + name: 'query2', + enable: true, + iconColor: 'blue', + }, + name: 'layer2', + isEnabled: true, + isHidden: true, + }), + // this could happen if a dahboard was created from code and the datasource was not defined + new DashboardAnnotationsDataLayer({ + key: 'layer3', + query: { + name: 'query3', + enable: true, + iconColor: 'green', + }, + name: 'layer3', + isEnabled: true, + isHidden: true, + }), + ]; +} diff --git a/public/app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2.ts b/public/app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2.ts index 605848c5b53..b88dfa68719 100644 --- a/public/app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2.ts +++ b/public/app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2.ts @@ -7,6 +7,7 @@ import { dataLayers, SceneDataQuery, SceneDataTransformer, + SceneQueryRunner, SceneVariableSet, VizPanel, } from '@grafana/scenes'; @@ -45,7 +46,13 @@ import { DashboardDataLayerSet } from '../scene/DashboardDataLayerSet'; import { DashboardScene, DashboardSceneState } from '../scene/DashboardScene'; import { PanelTimeRange } from '../scene/PanelTimeRange'; import { dashboardSceneGraph } from '../utils/dashboardSceneGraph'; -import { getLibraryPanelBehavior, getPanelIdForVizPanel, getQueryRunnerFor, isLibraryPanel } from '../utils/utils'; +import { + getDashboardSceneFor, + getLibraryPanelBehavior, + getPanelIdForVizPanel, + getQueryRunnerFor, + isLibraryPanel, +} from '../utils/utils'; import { getLayout } from './layoutSerializers/utils'; import { sceneVariablesSetToSchemaV2Variables } from './sceneVariablesSetToVariables'; @@ -239,16 +246,16 @@ function getVizPanelQueries(vizPanel: VizPanel): PanelQueryKind[] { const queries: PanelQueryKind[] = []; const queryRunner = getQueryRunnerFor(vizPanel); const vizPanelQueries = queryRunner?.state.queries; - const datasource = queryRunner?.state.datasource ?? getDefaultDataSourceRef(); - + const autoAssignedPanelDSRef = getAutoAssignedPanelDSRef(vizPanel); if (vizPanelQueries) { vizPanelQueries.forEach((query) => { + const queryDatasource = getPersistedDSForQuery(query, queryRunner, autoAssignedPanelDSRef); const dataQuery: DataQueryKind = { kind: getDataQueryKind(query), spec: omit(query, 'datasource', 'refId', 'hide'), }; const querySpec: PanelQuerySpec = { - datasource: query.datasource ?? datasource, + datasource: queryDatasource, query: dataQuery, refId: query.refId, hidden: Boolean(query.hide), @@ -584,3 +591,41 @@ function validateRowsLayout(layout: unknown) { throw new Error('Layout spec items is not an array'); } } + +/** + * Get a collection of panel queries refIds + * the refIds are the ones which did not have a datasource set + * @returns a set of panel queries refIds + */ +function getAutoAssignedPanelDSRef(vizPanel: VizPanel) { + const elementKey = dashboardSceneGraph.getElementIdentifierForVizPanel(vizPanel); + const scene = getDashboardSceneFor(vizPanel); + const elementMapReferences = scene.serializer.getDSReferencesMapping(); + + const panelQueries = elementMapReferences.panels.get(elementKey); + return panelQueries; +} + +/** + * Get the persisted datasource for a query + * When a query is created it could not have a datasource set + * we want to respect that and not overwrite it with the auto assigned datasources + * resolved in runtime + * @param query + * @param queryRunner + * @param autoAssignedPanelDsRef + * @returns + */ +export function getPersistedDSForQuery( + query: SceneDataQuery, + queryRunner: SceneQueryRunner, + autoAssignedPanelDsRef: Set | undefined +) { + // if the query has a refId and it is in the panelDsReferences then it did NOT have a datasource + const hasMatchingRefId = autoAssignedPanelDsRef?.has(query.refId); + if (hasMatchingRefId) { + return undefined; + } + + return query.datasource || queryRunner?.state?.datasource; +} diff --git a/public/app/features/dashboard-scene/sharing/ExportButton/ExportButton.tsx b/public/app/features/dashboard-scene/sharing/ExportButton/ExportButton.tsx index 5fd21ab76c6..9c4aea1cda6 100644 --- a/public/app/features/dashboard-scene/sharing/ExportButton/ExportButton.tsx +++ b/public/app/features/dashboard-scene/sharing/ExportButton/ExportButton.tsx @@ -10,7 +10,11 @@ import ExportMenu from './ExportMenu'; const newExportButtonSelector = e2eSelectors.pages.Dashboard.DashNav.NewExportButton; -export default function ExportButton({ dashboard }: { dashboard: DashboardScene }) { +interface Props { + dashboard: DashboardScene; +} + +export default function ExportButton({ dashboard }: Props) { const [isOpen, setIsOpen] = useState(false); const onMenuClick = useCallback((isOpen: boolean) => { diff --git a/public/app/features/dashboard/state/DashboardMigrator.test.ts b/public/app/features/dashboard/state/DashboardMigrator.test.ts index 70a80fb2da1..d2b6715e4a6 100644 --- a/public/app/features/dashboard/state/DashboardMigrator.test.ts +++ b/public/app/features/dashboard/state/DashboardMigrator.test.ts @@ -2450,6 +2450,121 @@ describe('when migrating time_options in timepicker', () => { }); }); +describe('when migrating table panels at schema version 24', () => { + let model: DashboardModel; + + beforeEach(() => { + config.featureToggles.autoMigrateOldPanels = true; + }); + + afterEach(() => { + config.featureToggles.autoMigrateOldPanels = false; + model = new DashboardModel({ + panels: [], + schemaVersion: 23, + }); + }); + + it('should migrate Angular table to table and set autoMigrateFrom', () => { + model = new DashboardModel({ + panels: [ + { + id: 1, + type: 'table', + // @ts-expect-error + legend: true, + styles: [{ thresholds: ['10', '20', '30'] }, { thresholds: ['100', '200', '300'] }], + targets: [{ refId: 'A' }, {}], + }, + ], + schemaVersion: 23, + }); + + // Verify the panel was migrated to table, yes this is intentional + // when autoMigrateOldPanels is enabled, we should migrate to table + // and add the autoMigrateFrom property + expect(model.panels[0].type).toBe('table'); + // Verify autoMigrateFrom was set + expect(model.panels[0].autoMigrateFrom).toBe('table-old'); + }); + + it('should migrate Angular table to table-old when autoMigrateOldPanels is disabled ', () => { + config.featureToggles.autoMigrateOldPanels = false; + model = new DashboardModel({ + panels: [ + { + id: 1, + type: 'table', + // @ts-expect-error + legend: true, + styles: [{ thresholds: ['10', '20', '30'] }, { thresholds: ['100', '200', '300'] }], + targets: [{ refId: 'A' }, {}], + }, + ], + schemaVersion: 23, + }); + + // Verify the panel was migrated to table, yes this is intentional + // when autoMigrateOldPanels is enabled, we should migrate to table + // and add the autoMigrateFrom property + expect(model.panels[0].type).toBe('table-old'); + // Verify autoMigrateFrom was set + expect(model.panels[0].autoMigrateFrom).toBe(undefined); + }); + + it('should not migrate Angular table without styles', () => { + model = new DashboardModel({ + panels: [ + { + id: 1, + type: 'table', + // No styles property + }, + ], + schemaVersion: 23, + }); + + // Verify the panel was not migrated + expect(model.panels[0].type).toBe('table'); + expect(model.panels[0].autoMigrateFrom).toBeUndefined(); + }); + + it('should not migrate React table (table2)', () => { + model = new DashboardModel({ + panels: [ + { + id: 1, + type: 'table2', + }, + ], + schemaVersion: 23, + }); + + // Verify the panel was not migrated + expect(model.panels[0].type).toBe('table2'); + expect(model.panels[0].autoMigrateFrom).toBeUndefined(); + }); + + it('should not migrate non-table panels when autoMigrateOldPanels is disabled', () => { + // disable autoMigrateOldPanels + config.featureToggles.autoMigrateOldPanels = false; + model = new DashboardModel({ + panels: [ + { + id: 1, + type: 'grafana-worldmap-panel', + title: 'world map panel', + }, + ], + schemaVersion: 23, + }); + + // Verify the panel was not migrated + expect(model.panels[0].type).toBe('grafana-worldmap-panel'); + expect(model.panels[0].autoMigrateFrom).toBeUndefined(); + }); +}); + function createRow(options: any, panelDescriptions: any[]) { const PANEL_HEIGHT_STEP = GRID_CELL_HEIGHT + GRID_CELL_VMARGIN; const { collapse, showTitle, title, repeat, repeatIteration } = options; diff --git a/public/app/features/dashboard/state/DashboardMigrator.ts b/public/app/features/dashboard/state/DashboardMigrator.ts index bab206f9792..e1f6b0a85fa 100644 --- a/public/app/features/dashboard/state/DashboardMigrator.ts +++ b/public/app/features/dashboard/state/DashboardMigrator.ts @@ -65,6 +65,7 @@ import { import { DashboardModel } from './DashboardModel'; import { PanelModel } from './PanelModel'; +import { getPanelPluginToMigrateTo } from './getPanelPluginToMigrateTo'; standardEditorsRegistry.setInit(getAllOptionEditors); standardFieldConfigEditorRegistry.setInit(getAllStandardFieldConfigs); @@ -626,6 +627,14 @@ export class DashboardMigrator { return panel; } panel.type = wasAngularTable ? 'table-old' : 'table'; + // Hacky way to call the automigrate feature + if (panel.type === 'table-old') { + const newType = getPanelPluginToMigrateTo(panel); + if (newType) { + panel.autoMigrateFrom = panel.type; + panel.type = newType; + } + } return panel; }); } diff --git a/public/app/features/expressions/utils/expressionTypes.ts b/public/app/features/expressions/utils/expressionTypes.ts index 514dfdcb907..dd648512b85 100644 --- a/public/app/features/expressions/utils/expressionTypes.ts +++ b/public/app/features/expressions/utils/expressionTypes.ts @@ -2,7 +2,7 @@ import { ReducerID } from '@grafana/data'; import { EvalFunction } from '../../alerting/state/alertDef'; import { isReducerType } from '../guards'; -import { ClassicCondition, ExpressionQuery, ExpressionQueryType, ReducerType } from '../types'; +import { ClassicCondition, ExpressionQuery, ExpressionQueryType, ReducerMode, ReducerType } from '../types'; export const getDefaults = (query: ExpressionQuery) => { switch (query.type) { @@ -69,3 +69,20 @@ export function getReducerType(value: string): ReducerType | undefined { } return undefined; } + +export function isStrictReducer(expressionModel: ExpressionQuery): boolean { + if (!isReducerExpression(expressionModel)) { + return false; + } + + const mode = expressionModel.settings?.mode; + return mode === ReducerMode.Strict || mode === undefined; +} + +export function isReducerExpression(expressionModel: ExpressionQuery) { + return expressionModel.type === ExpressionQueryType.reduce; +} + +export function isThresholdExpression(expressionModel: ExpressionQuery) { + return expressionModel.type === ExpressionQueryType.threshold; +} diff --git a/public/app/features/live/centrifuge/channel.ts b/public/app/features/live/centrifuge/channel.ts index fd2f6a109f1..2edb1fc470b 100644 --- a/public/app/features/live/centrifuge/channel.ts +++ b/public/app/features/live/centrifuge/channel.ts @@ -175,6 +175,8 @@ export class CentrifugeLiveChannel { }); } + publish = (data: unknown) => this.subscription?.publish(data); + /** * This will close and terminate all streams for this channel */ diff --git a/public/app/features/live/centrifuge/service.ts b/public/app/features/live/centrifuge/service.ts index 8fb666a604c..b99a78d2f10 100644 --- a/public/app/features/live/centrifuge/service.ts +++ b/public/app/features/live/centrifuge/service.ts @@ -20,6 +20,7 @@ import { FetchResponse } from '@grafana/runtime/src/services/backendSrv'; import { GrafanaLiveSrv, LiveDataStreamOptions, + LivePublishOptions, LiveQueryDataOptions, StreamingFrameAction, StreamingFrameOptions, @@ -42,7 +43,7 @@ export type CentrifugeSrvDeps = { export type StreamingDataQueryResponse = Omit & { data: [StreamingResponseData] }; -export type CentrifugeSrv = Omit & { +export type CentrifugeSrv = Omit & { getDataStream: (options: LiveDataStreamOptions) => Observable; getQueryData: ( options: LiveQueryDataOptions @@ -244,6 +245,13 @@ export class CentrifugeService implements CentrifugeSrv { getPresence: CentrifugeSrv['getPresence'] = (address) => { return this.getChannel(address).getPresence(); }; + + /** + * Publish into a channel. + */ + publish = async (address: LiveChannelAddress, data: unknown, options?: LivePublishOptions) => { + return this.getChannel(address).publish(data); + }; } // This is used to give a unique key for each stream. The actual value does not matter diff --git a/public/app/features/live/centrifuge/service.worker.ts b/public/app/features/live/centrifuge/service.worker.ts index a168bc34824..0c535bc9cb8 100644 --- a/public/app/features/live/centrifuge/service.worker.ts +++ b/public/app/features/live/centrifuge/service.worker.ts @@ -3,7 +3,7 @@ import './transferHandlers'; import * as comlink from 'comlink'; import { LiveChannelAddress } from '@grafana/data'; -import { LiveDataStreamOptions, LiveQueryDataOptions } from '@grafana/runtime'; +import { LiveDataStreamOptions, LivePublishOptions, LiveQueryDataOptions } from '@grafana/runtime'; import { remoteObservableAsObservable } from './remoteObservable'; import { CentrifugeService, CentrifugeSrvDeps } from './service'; @@ -42,6 +42,9 @@ const getPresence = async (address: LiveChannelAddress) => { return await centrifuge.getPresence(address); }; +const publish = (address: LiveChannelAddress, data: unknown, options?: LivePublishOptions) => + centrifuge.publish(address, data, options); + const workObj = { initialize, getConnectionState, @@ -49,6 +52,7 @@ const workObj = { getStream, getQueryData, getPresence, + publish, }; export type RemoteCentrifugeService = typeof workObj; diff --git a/public/app/features/live/centrifuge/serviceWorkerProxy.ts b/public/app/features/live/centrifuge/serviceWorkerProxy.ts index efb87e3cdd5..6f1b8fcc92d 100644 --- a/public/app/features/live/centrifuge/serviceWorkerProxy.ts +++ b/public/app/features/live/centrifuge/serviceWorkerProxy.ts @@ -47,4 +47,8 @@ export class CentrifugeServiceWorkerProxy implements CentrifugeSrv { this.centrifugeWorker.getStream(address) as Promise>>> ); }; + + publish: CentrifugeSrv['publish'] = (address, data, options) => { + return this.centrifugeWorker.publish(address, data, options); + }; } diff --git a/public/app/features/live/live.ts b/public/app/features/live/live.ts index e4ecc3699ea..43f665f5276 100644 --- a/public/app/features/live/live.ts +++ b/public/app/features/live/live.ts @@ -93,7 +93,11 @@ export class GrafanaLiveService implements GrafanaLiveSrv { * * @alpha -- experimental */ - publish: GrafanaLiveSrv['publish'] = async (address, data) => { + publish: GrafanaLiveSrv['publish'] = async (address, data, options) => { + if (options?.useSocket) { + return this.deps.centrifugeSrv.publish(address, data); + } + return this.deps.backendSrv.post(`api/live/publish`, { channel: toLiveChannelId(address), // orgId is from user data, diff --git a/public/app/features/logs/components/InfiniteScroll.test.tsx b/public/app/features/logs/components/InfiniteScroll.test.tsx index c6b92afaef5..9b9792c2849 100644 --- a/public/app/features/logs/components/InfiniteScroll.test.tsx +++ b/public/app/features/logs/components/InfiniteScroll.test.tsx @@ -62,11 +62,15 @@ function setup( ) { const { element, events } = getMockElement(startPosition); - function scrollTo(position: number) { + function scrollTo(position: number, timeStamp?: number) { element.scrollTop = position; act(() => { - events['scroll'](new Event('scroll')); + const event = new Event('scroll'); + if (timeStamp) { + jest.spyOn(event, 'timeStamp', 'get').mockReturnValue(timeStamp); + } + events['scroll'](event); }); // When scrolling top, we wait for the user to reach the top, and then for a new scrolling event @@ -153,7 +157,8 @@ describe('InfiniteScroll', () => { expect(await screen.findByTestId('contents')).toBeInTheDocument(); - scrollTo(endPosition); + scrollTo(endPosition - 1, 1); + scrollTo(endPosition, 600); expect(loadMoreMock).toHaveBeenCalled(); expect(await screen.findByTestId('Spinner')).toBeInTheDocument(); @@ -177,7 +182,8 @@ describe('InfiniteScroll', () => { expect(await screen.findByTestId('contents')).toBeInTheDocument(); - wheel(deltaY); + wheel(deltaY, 1); + wheel(deltaY, 600); expect(loadMoreMock).toHaveBeenCalled(); expect(await screen.findByTestId('Spinner')).toBeInTheDocument(); @@ -192,7 +198,8 @@ describe('InfiniteScroll', () => { element.clientHeight = 40; element.scrollHeight = element.clientHeight; - scrollTo(40); + scrollTo(39, 1); + scrollTo(40, 600); expect(loadMoreMock).not.toHaveBeenCalled(); expect(screen.queryByTestId('Spinner')).not.toBeInTheDocument(); @@ -207,7 +214,8 @@ describe('InfiniteScroll', () => { expect(await screen.findByTestId('contents')).toBeInTheDocument(); - scrollTo(endPosition); + scrollTo(endPosition - 1, 1); + scrollTo(endPosition, 600); expect(loadMoreMock).toHaveBeenCalledWith({ from: rows[rows.length - 1].timeEpochMs, @@ -224,7 +232,8 @@ describe('InfiniteScroll', () => { expect(await screen.findByTestId('contents')).toBeInTheDocument(); - scrollTo(endPosition); + scrollTo(endPosition - 1, 1); + scrollTo(endPosition, 600); expect(loadMoreMock).toHaveBeenCalledWith({ from: absoluteRange.from, @@ -246,7 +255,8 @@ describe('InfiniteScroll', () => { expect(await screen.findByTestId('contents')).toBeInTheDocument(); - scrollTo(endPosition); + scrollTo(endPosition - 1, 1); + scrollTo(endPosition, 600); expect(loadMoreMock).not.toHaveBeenCalled(); expect(screen.queryByTestId('Spinner')).not.toBeInTheDocument(); @@ -269,7 +279,8 @@ describe('InfiniteScroll', () => { expect(await screen.findByTestId('contents')).toBeInTheDocument(); - scrollTo(endPosition); + scrollTo(endPosition - 1, 1); + scrollTo(endPosition, 600); expect(loadMoreMock).not.toHaveBeenCalled(); expect(screen.queryByTestId('Spinner')).not.toBeInTheDocument(); diff --git a/public/app/features/logs/components/InfiniteScroll.tsx b/public/app/features/logs/components/InfiniteScroll.tsx index 36d748d2db1..5ebe5516332 100644 --- a/public/app/features/logs/components/InfiniteScroll.tsx +++ b/public/app/features/logs/components/InfiniteScroll.tsx @@ -98,6 +98,7 @@ export const InfiniteScroll = ({ } else if (scrollDirection === ScrollDirection.Bottom) { scrollBottom(); } + lastEvent.current = null; } function scrollTop() { @@ -245,7 +246,7 @@ export function shouldLoadMore( return ScrollDirection.NoScroll; } - if (lastEvent && shouldIgnoreChainOfEvents(event, lastEvent, countRef)) { + if (!lastEvent || shouldIgnoreChainOfEvents(event, lastEvent, countRef)) { return ScrollDirection.NoScroll; } diff --git a/public/app/features/scopes/ScopesApiClient.ts b/public/app/features/scopes/ScopesApiClient.ts index 443a7cad8c6..358d1b97e4d 100644 --- a/public/app/features/scopes/ScopesApiClient.ts +++ b/public/app/features/scopes/ScopesApiClient.ts @@ -62,6 +62,10 @@ export class ScopesApiClient { }); } + /** + * @param parent + * @param query Filters by title substring + */ async fetchNode(parent: string, query: string): Promise { try { const nodes = diff --git a/public/app/features/scopes/ScopesContextProvider.tsx b/public/app/features/scopes/ScopesContextProvider.tsx index 8e47b0e4b3a..9519b10be3b 100644 --- a/public/app/features/scopes/ScopesContextProvider.tsx +++ b/public/app/features/scopes/ScopesContextProvider.tsx @@ -1,6 +1,6 @@ -import { createContext, ReactNode, useMemo, useContext } from 'react'; +import { createContext, ReactNode, useMemo, useContext, useEffect } from 'react'; -import { config, ScopesContext } from '@grafana/runtime'; +import { config, locationService, ScopesContext } from '@grafana/runtime'; import { ScopesApiClient } from './ScopesApiClient'; import { ScopesService } from './ScopesService'; @@ -36,7 +36,7 @@ export function defaultScopesServices() { const dashboardService = new ScopesDashboardsService(client); const selectorService = new ScopesSelectorService(client, dashboardService); return { - scopesService: new ScopesService(selectorService, dashboardService), + scopesService: new ScopesService(selectorService, dashboardService, locationService), scopesSelectorService: selectorService, scopesDashboardsService: dashboardService, client, @@ -48,6 +48,12 @@ export const ScopesContextProvider = ({ children, services }: ScopesContextProvi return services ?? defaultScopesServices(); }, [services]); + useEffect(() => { + return () => { + memoizedServices.scopesService.cleanUp(); + }; + }, [memoizedServices]); + return ( diff --git a/public/app/features/scopes/ScopesService.ts b/public/app/features/scopes/ScopesService.ts index 2c836470194..21969652552 100644 --- a/public/app/features/scopes/ScopesService.ts +++ b/public/app/features/scopes/ScopesService.ts @@ -1,8 +1,8 @@ import { isEqual } from 'lodash'; -import { BehaviorSubject, Observable, combineLatest } from 'rxjs'; +import { BehaviorSubject, Observable, combineLatest, Subscription } from 'rxjs'; import { map, distinctUntilChanged } from 'rxjs/operators'; -import { ScopesContextValue, ScopesContextValueState } from '@grafana/runtime'; +import { LocationService, ScopesContextValue, ScopesContextValueState } from '@grafana/runtime'; import { ScopesDashboardsService } from './dashboards/ScopesDashboardsService'; import { ScopesSelectorService } from './selector/ScopesSelectorService'; @@ -24,9 +24,12 @@ export class ScopesService implements ScopesContextValue { // This will contain the combined state that will be public. private readonly _stateObservable: BehaviorSubject; + private subscriptions: Subscription[] = []; + constructor( private selectorService: ScopesSelectorService, - private dashboardsService: ScopesDashboardsService + private dashboardsService: ScopesDashboardsService, + private locationService: LocationService ) { this._state = new BehaviorSubject({ enabled: false, @@ -41,24 +44,64 @@ export class ScopesService implements ScopesContextValue { }); // We combine the latest emissions from this state + selectorService + dashboardsService. - combineLatest([ - this._state.asObservable(), - this.getSelectorServiceStateObservable(), - this.getDashboardsServiceStateObservable(), - ]) - .pipe( - // Map the 3 states into single ScopesContextValueState object - map( - ([thisState, selectorState, dashboardsState]): ScopesContextValueState => ({ - ...thisState, - value: selectorState.selectedScopes, - loading: selectorState.loading, - drawerOpened: dashboardsState.drawerOpened, - }) + this.subscriptions.push( + combineLatest([ + this._state.asObservable(), + this.getSelectorServiceStateObservable(), + this.getDashboardsServiceStateObservable(), + ]) + .pipe( + // Map the 3 states into single ScopesContextValueState object + map( + ([thisState, selectorState, dashboardsState]): ScopesContextValueState => ({ + ...thisState, + value: selectorState.selectedScopes, + loading: selectorState.loading, + drawerOpened: dashboardsState.drawerOpened, + }) + ) ) - ) - // We pass this into behaviourSubject so we get the 1 event buffer and we can access latest value. - .subscribe(this._stateObservable); + // We pass this into behaviourSubject so we get the 1 event buffer and we can access latest value. + .subscribe(this._stateObservable) + ); + + // Init from the URL when we first load + const queryParams = new URLSearchParams(locationService.getLocation().search); + this.changeScopes(queryParams.getAll('scopes')); + + // Update scopes state based on URL. + this.subscriptions.push( + locationService.getLocationObservable().subscribe((location) => { + if (!this.state.enabled) { + // We don't need to react on pages that don't interact with scopes. + return; + } + const queryParams = new URLSearchParams(location.search); + const scopes = queryParams.getAll('scopes'); + if (scopes.length) { + // We only update scopes but never delete them. This is to keep the scopes in memory if user navigates to + // page that does not use scopes (like from dashboard to dashboard list back to dashboard). If user + // changes the URL directly, it would trigger a reload so scopes would still be reset. + this.changeScopes(scopes); + } + }) + ); + + // Update the URL based on change in the scopes state + this.subscriptions.push( + selectorService.subscribeToState((state, prev) => { + const oldScopeNames = prev.selectedScopes.map((scope) => scope.scope.metadata.name); + const newScopeNames = state.selectedScopes.map((scope) => scope.scope.metadata.name); + if (!isEqual(oldScopeNames, newScopeNames)) { + this.locationService.partial( + { + scopes: newScopeNames, + }, + true + ); + } + }) + ); } /** @@ -97,6 +140,14 @@ export class ScopesService implements ScopesContextValue { public setEnabled = (enabled: boolean) => { if (this.state.enabled !== enabled) { this.updateState({ enabled }); + if (enabled) { + this.locationService.partial( + { + scopes: this.selectorService.state.selectedScopes.map(({ scope }) => scope.metadata.name), + }, + true + ); + } } }; @@ -127,4 +178,13 @@ export class ScopesService implements ScopesContextValue { distinctUntilChanged((prev, curr) => prev.drawerOpened === curr.drawerOpened) ); } + + /** + * Cleanup subscriptions so this can be garbage collected. + */ + public cleanUp() { + for (const sub of this.subscriptions) { + sub.unsubscribe(); + } + } } diff --git a/public/app/features/scopes/selector/ScopesSelectorService.ts b/public/app/features/scopes/selector/ScopesSelectorService.ts index 158be33d5f3..54b3aa6e5fb 100644 --- a/public/app/features/scopes/selector/ScopesSelectorService.ts +++ b/public/app/features/scopes/selector/ScopesSelectorService.ts @@ -1,4 +1,4 @@ -import { isEqual } from 'lodash'; +import { isEqual, last } from 'lodash'; import { ScopesApiClient } from '../ScopesApiClient'; import { ScopesServiceBase } from '../ScopesServiceBase'; @@ -14,7 +14,11 @@ export interface ScopesSelectorServiceState { opened: boolean; loadingNodeName: string | undefined; nodes: NodesMap; + + // Scopes that are selected and applied. selectedScopes: SelectedScope[]; + + // Representation of what is selected in the tree in the UI. This state may not be yet applied to the selectedScopes. treeScopes: TreeScope[]; } @@ -45,29 +49,47 @@ export class ScopesSelectorService extends ScopesServiceBase { - let nodes = { ...this.state.nodes }; - let currentLevel: NodesMap = nodes; - - for (let idx = 0; idx < path.length - 1; idx++) { - currentLevel = currentLevel[path[idx]].nodes; + if (path.length < 1) { + return; } - const loadingNodeName = path[path.length - 1]; - const currentNode = currentLevel[loadingNodeName]; + // Making a copy as we will be changing this in place and then updating state later. + // This though does not make a deep copy so you cannot rely on reference of nested nodes changing. + const nodes = { ...this.state.nodes }; + let currentLevel: NodesMap = nodes; + let loadingNodeName = path[0]; + if (path.length > 1) { + const pathToParent = path.slice(0, path.length - 1); + currentLevel = getNodesAtPath(nodes, pathToParent); + loadingNodeName = last(path)!; + } + + const currentNode = currentLevel[loadingNodeName]; const differentQuery = currentNode.query !== query; currentNode.expanded = expanded; currentNode.query = query; if (expanded || differentQuery) { + // Means we have to fetch the children of the node + this.updateState({ nodes, loadingNodeName }); - // fetchNodeApi does not throw just return empty object + // fetchNodeApi does not throw just returns empty object. + // Load all the children of the loadingNodeName const childNodes = await this.apiClient.fetchNode(loadingNodeName, query); if (loadingNodeName === this.state.loadingNodeName) { - const [selectedScopes, treeScopes] = this.getScopesAndTreeScopesWithPaths( + const [selectedScopes, treeScopes] = getScopesAndTreeScopesWithPaths( this.state.selectedScopes, this.state.treeScopes, path, @@ -95,6 +117,14 @@ export class ScopesSelectorService extends ScopesServiceBase { let treeScopes = [...this.state.treeScopes]; @@ -110,6 +140,8 @@ export class ScopesSelectorService extends ScopesServiceBase scopeName === linkId); if (selectedIdx === -1) { + // We prefetch the scope when clicking on it. This will mean that once the selection is applied in closeAndApply() + // we already have all the scopes in cache and don't need to fetch all of them again is multiple requests. this.apiClient.fetchScope(linkId!); const selectedFromSameNode = @@ -133,8 +165,14 @@ export class ScopesSelectorService extends ScopesServiceBase this.setNewScopes(scopeNames.map((scopeName) => ({ scopeName, path: [] }))); + /** + * Apply the selected scopes. Apart from setting the scopes it also fetches the scope metadata and also loads the + * related dashboards. + * @param treeScopes The scopes to be applied. If not provided the treeScopes state is used which was populated + * before for example by toggling the scopes in the scoped tree UI. + */ private setNewScopes = async (treeScopes = this.state.treeScopes) => { - if (isEqual(treeScopes, this.getTreeScopesFromSelectedScopes(this.state.selectedScopes))) { + if (isEqual(treeScopes, getTreeScopesFromSelectedScopes(this.state.selectedScopes))) { return; } @@ -142,7 +180,10 @@ export class ScopesSelectorService extends ScopesServiceBase scope.metadata.name)); selectedScopes = await this.apiClient.fetchMultipleScopes(treeScopes); @@ -151,6 +192,9 @@ export class ScopesSelectorService extends ScopesServiceBase this.setNewScopes([]); + /** + * Opens the scopes selector drawer and loads the root nodes if they are not loaded yet. + */ public open = async () => { if (Object.keys(this.state.nodes[''].nodes).length === 0) { await this.updateNode([''], true, ''); @@ -159,7 +203,7 @@ export class ScopesSelectorService extends ScopesServiceBase { - this.updateState({ opened: false, treeScopes: this.getTreeScopesFromSelectedScopes(this.state.selectedScopes) }); + // Reset the treeScopes if we don't want them actually applied. + this.updateState({ opened: false, treeScopes: getTreeScopesFromSelectedScopes(this.state.selectedScopes) }); }; public closeAndApply = () => { this.updateState({ opened: false }); this.setNewScopes(); }; +} - private closeNodes = (nodes: NodesMap): NodesMap => { - return Object.entries(nodes).reduce((acc, [id, node]) => { - acc[id] = { - ...node, - expanded: false, - nodes: this.closeNodes(node.nodes), - }; +/** + * Creates a deep copy of the node tree with expanded prop set to false. + * @param nodes + */ +function closeNodes(nodes: NodesMap): NodesMap { + return Object.entries(nodes).reduce((acc, [id, node]) => { + acc[id] = { + ...node, + expanded: false, + nodes: closeNodes(node.nodes), + }; - return acc; - }, {}); - }; + return acc; + }, {}); +} - private getTreeScopesFromSelectedScopes = (scopes: SelectedScope[]): TreeScope[] => { - return scopes.map(({ scope, path }) => ({ - scopeName: scope.metadata.name, - path, - })); - }; +function getTreeScopesFromSelectedScopes(scopes: SelectedScope[]): TreeScope[] { + return scopes.map(({ scope, path }) => ({ + scopeName: scope.metadata.name, + path, + })); +} - // helper func to get the selected/tree scopes together with their paths - // needed to maintain selected scopes in tree for example when navigating - // between categories or when loading scopes from URL to find the scope's path - private getScopesAndTreeScopesWithPaths = ( - selectedScopes: SelectedScope[], - treeScopes: TreeScope[], - path: string[], - childNodes: NodesMap - ): [SelectedScope[], TreeScope[]] => { - const childNodesArr = Object.values(childNodes); +// helper func to get the selected/tree scopes together with their paths +// needed to maintain selected scopes in tree for example when navigating +// between categories or when loading scopes from URL to find the scope's path +function getScopesAndTreeScopesWithPaths( + selectedScopes: SelectedScope[], + treeScopes: TreeScope[], + path: string[], + childNodes: NodesMap +): [SelectedScope[], TreeScope[]] { + const childNodesArr = Object.values(childNodes); - // Get all scopes without paths - // We use tree scopes as the list is always up to date as opposed to selected scopes which can be outdated - const scopeNamesWithoutPaths = treeScopes.filter(({ path }) => path.length === 0).map(({ scopeName }) => scopeName); + // Get all scopes without paths + // We use tree scopes as the list is always up to date as opposed to selected scopes which can be outdated + const scopeNamesWithoutPaths = treeScopes.filter(({ path }) => path.length === 0).map(({ scopeName }) => scopeName); - // We search for the path of each scope name without a path - const scopeNamesWithPaths = scopeNamesWithoutPaths.reduce>((acc, scopeName) => { - const possibleParent = childNodesArr.find((childNode) => childNode.selectable && childNode.linkId === scopeName); + // We search for the path of each scope name without a path + const scopeNamesWithPaths = scopeNamesWithoutPaths.reduce>((acc, scopeName) => { + const possibleParent = childNodesArr.find((childNode) => childNode.selectable && childNode.linkId === scopeName); - if (possibleParent) { - acc[scopeName] = [...path, possibleParent.name]; - } + if (possibleParent) { + acc[scopeName] = [...path, possibleParent.name]; + } - return acc; - }, {}); + return acc; + }, {}); - // Update the paths of the selected scopes based on what we found - const newSelectedScopes = selectedScopes.map((selectedScope) => { - if (selectedScope.path.length > 0) { - return selectedScope; - } + // Update the paths of the selected scopes based on what we found + const newSelectedScopes = selectedScopes.map((selectedScope) => { + if (selectedScope.path.length > 0) { + return selectedScope; + } - return { - ...selectedScope, - path: scopeNamesWithPaths[selectedScope.scope.metadata.name] ?? [], - }; - }); + return { + ...selectedScope, + path: scopeNamesWithPaths[selectedScope.scope.metadata.name] ?? [], + }; + }); - // Update the paths of the tree scopes based on what we found - const newTreeScopes = treeScopes.map((treeScope) => { - if (treeScope.path.length > 0) { - return treeScope; - } + // Update the paths of the tree scopes based on what we found + const newTreeScopes = treeScopes.map((treeScope) => { + if (treeScope.path.length > 0) { + return treeScope; + } - return { - ...treeScope, - path: scopeNamesWithPaths[treeScope.scopeName] ?? [], - }; - }); + return { + ...treeScope, + path: scopeNamesWithPaths[treeScope.scopeName] ?? [], + }; + }); - return [newSelectedScopes, newTreeScopes]; - }; + return [newSelectedScopes, newTreeScopes]; } function expandNodes(nodes: NodesMap, path: string[]): NodesMap { @@ -269,3 +318,13 @@ function expandNodes(nodes: NodesMap, path: string[]): NodesMap { return nodes; } + +function getNodesAtPath(nodes: NodesMap, path: string[]): NodesMap { + let currentNodes = nodes; + + for (const section of path) { + currentNodes = currentNodes[section].nodes; + } + + return currentNodes; +} diff --git a/public/app/features/scopes/tests/dashboardsList.test.ts b/public/app/features/scopes/tests/dashboardsList.test.ts index 8c89619b062..eec2829b144 100644 --- a/public/app/features/scopes/tests/dashboardsList.test.ts +++ b/public/app/features/scopes/tests/dashboardsList.test.ts @@ -1,4 +1,4 @@ -import { config } from '@grafana/runtime'; +import { config, locationService } from '@grafana/runtime'; import { ScopesService } from '../ScopesService'; import { ScopesDashboardsService } from '../dashboards/ScopesDashboardsService'; @@ -65,6 +65,7 @@ describe('Dashboards list', () => { }); afterEach(async () => { + locationService.replace(''); await resetScenes([fetchDashboardsSpy]); }); diff --git a/public/app/features/scopes/tests/selector.test.ts b/public/app/features/scopes/tests/selector.test.ts index 251d19c0db7..e430a95ab4f 100644 --- a/public/app/features/scopes/tests/selector.test.ts +++ b/public/app/features/scopes/tests/selector.test.ts @@ -1,4 +1,4 @@ -import { config } from '@grafana/runtime'; +import { config, locationService } from '@grafana/runtime'; import { getDashboardScenePageStateManager } from '../../dashboard-scene/pages/DashboardScenePageStateManager'; import { ScopesService } from '../ScopesService'; @@ -36,6 +36,7 @@ describe('Selector', () => { }); afterEach(async () => { + locationService.replace(''); await resetScenes([fetchSelectedScopesSpy, dashboardReloadSpy]); }); diff --git a/public/app/features/scopes/tests/tree.test.ts b/public/app/features/scopes/tests/tree.test.ts index 84f047a0768..51ebbc35219 100644 --- a/public/app/features/scopes/tests/tree.test.ts +++ b/public/app/features/scopes/tests/tree.test.ts @@ -1,4 +1,4 @@ -import { config } from '@grafana/runtime'; +import { config, locationService } from '@grafana/runtime'; import { ScopesService } from '../ScopesService'; import { ScopesSelectorService } from '../selector/ScopesSelectorService'; @@ -74,6 +74,7 @@ describe('Tree', () => { }); afterEach(async () => { + locationService.replace(''); await resetScenes([fetchNodesSpy, fetchScopeSpy]); }); diff --git a/public/app/features/transformers/docs/content.ts b/public/app/features/transformers/docs/content.ts index 10219399ad2..25d0a99ce0b 100644 --- a/public/app/features/transformers/docs/content.ts +++ b/public/app/features/transformers/docs/content.ts @@ -1218,31 +1218,12 @@ Use this transformation to address issues when a data source returns time series #### Available options -##### Multi-frame time series - -Use this option to transform the time series data frame from the wide format to the long format. This is particularly helpful when your data source delivers time series information in a format that needs to be reshaped for optimal compatibility with your visualization. - -**Example: Converting from wide to long format** - -| Timestamp | Value1 | Value2 | -|---------------------|--------|--------| -| 2023-01-01 00:00:00 | 10 | 20 | -| 2023-01-01 01:00:00 | 15 | 25 | - -**Transformed to:** - -| Timestamp | Variable | Value | -|---------------------|----------|-------| -| 2023-01-01 00:00:00 | Value1 | 10 | -| 2023-01-01 00:00:00 | Value2 | 20 | -| 2023-01-01 01:00:00 | Value1 | 15 | -| 2023-01-01 01:00:00 | Value2 | 25 | - - ##### Wide time series Select this option to transform the time series data frame from the long format to the wide format. If your data source returns time series data in a long format and your visualization requires a wide format, this transformation simplifies the process. +A wide time series combines data into a single frame with one shared, ascending time field. Time fields do not repeat and multiple values extend in separate columns. + **Example: Converting from long to wide format** | Timestamp | Variable | Value | @@ -1259,6 +1240,32 @@ Select this option to transform the time series data frame from the long format | 2023-01-01 00:00:00 | 10 | 20 | | 2023-01-01 01:00:00 | 15 | 25 | +##### Multi-frame time series + +Multi-frame time series break data into multiple frames that all contain two fields: a time field and a numeric value field. Time is always ascending. String values are represented as field labels. + +##### Long time series + +A long time series combines data into one frame, with the first field being an ascending time field. The time field might have duplicates. String values are in separate fields, and there might be more than one. + +**Example: Converting to long format** + +| Value1 | Value2 | Timestamp | +|--------|--------|---------------------| +| 10 | 20 | 2023-01-03 00:00:00 | +| 30 | 40 | 2023-01-02 00:00:00 | +| 50 | 60 | 2023-01-01 00:00:00 | +| 70 | 80 | 2023-01-01 00:00:00 | + +**Transformed to:** + +| Timestamp | Value1 | Value2 | +|---------------------|--------|--------| +| 2023-01-01 00:00:00 | 70 | 80 | +| 2023-01-01 01:00:00 | 50 | 60 | +| 2023-01-02 01:00:00 | 30 | 40 | +| 2023-01-03 01:00:00 | 10 | 20 | + `; }, links: [ diff --git a/public/app/plugins/datasource/azuremonitor/package.json b/public/app/plugins/datasource/azuremonitor/package.json index c28ed69b662..e418d1e98e8 100644 --- a/public/app/plugins/datasource/azuremonitor/package.json +++ b/public/app/plugins/datasource/azuremonitor/package.json @@ -2,14 +2,14 @@ "name": "@grafana-plugins/grafana-azure-monitor-datasource", "description": "Grafana data source for Azure Monitor", "private": true, - "version": "11.6.0-pre", + "version": "12.0.0-pre", "dependencies": { "@emotion/css": "11.13.5", - "@grafana/data": "11.6.0-pre", + "@grafana/data": "12.0.0-pre", "@grafana/plugin-ui": "0.10.1", - "@grafana/runtime": "11.6.0-pre", - "@grafana/schema": "11.6.0-pre", - "@grafana/ui": "11.6.0-pre", + "@grafana/runtime": "12.0.0-pre", + "@grafana/schema": "12.0.0-pre", + "@grafana/ui": "12.0.0-pre", "@kusto/monaco-kusto": "^10.0.0", "fast-deep-equal": "^3.1.3", "i18next": "^24.0.0", @@ -25,8 +25,8 @@ "tslib": "2.8.1" }, "devDependencies": { - "@grafana/e2e-selectors": "11.6.0-pre", - "@grafana/plugin-configs": "11.6.0-pre", + "@grafana/e2e-selectors": "12.0.0-pre", + "@grafana/plugin-configs": "12.0.0-pre", "@testing-library/dom": "10.4.0", "@testing-library/jest-dom": "6.6.3", "@testing-library/react": "16.2.0", diff --git a/public/app/plugins/datasource/cloud-monitoring/package.json b/public/app/plugins/datasource/cloud-monitoring/package.json index d22e00bcc4d..f99cfbfa1d5 100644 --- a/public/app/plugins/datasource/cloud-monitoring/package.json +++ b/public/app/plugins/datasource/cloud-monitoring/package.json @@ -2,15 +2,15 @@ "name": "@grafana-plugins/stackdriver", "description": "Grafana data source for Google Cloud Monitoring", "private": true, - "version": "11.6.0-pre", + "version": "12.0.0-pre", "dependencies": { "@emotion/css": "11.13.5", - "@grafana/data": "11.6.0-pre", + "@grafana/data": "12.0.0-pre", "@grafana/google-sdk": "0.1.2", "@grafana/plugin-ui": "0.10.1", - "@grafana/runtime": "11.6.0-pre", - "@grafana/schema": "11.6.0-pre", - "@grafana/ui": "11.6.0-pre", + "@grafana/runtime": "12.0.0-pre", + "@grafana/schema": "12.0.0-pre", + "@grafana/ui": "12.0.0-pre", "debounce-promise": "3.1.2", "fast-deep-equal": "^3.1.3", "i18next": "^24.0.0", @@ -26,8 +26,8 @@ "tslib": "2.8.1" }, "devDependencies": { - "@grafana/e2e-selectors": "11.6.0-pre", - "@grafana/plugin-configs": "11.6.0-pre", + "@grafana/e2e-selectors": "12.0.0-pre", + "@grafana/plugin-configs": "12.0.0-pre", "@testing-library/dom": "10.4.0", "@testing-library/jest-dom": "6.6.3", "@testing-library/react": "16.2.0", diff --git a/public/app/plugins/datasource/grafana-postgresql-datasource/package.json b/public/app/plugins/datasource/grafana-postgresql-datasource/package.json index 6cf8a965548..9c700d6f63c 100644 --- a/public/app/plugins/datasource/grafana-postgresql-datasource/package.json +++ b/public/app/plugins/datasource/grafana-postgresql-datasource/package.json @@ -2,22 +2,22 @@ "name": "@grafana-plugins/grafana-postgresql-datasource", "description": "PostgreSQL data source plugin", "private": true, - "version": "11.6.0-pre", + "version": "12.0.0-pre", "dependencies": { "@emotion/css": "11.13.5", - "@grafana/data": "11.6.0-pre", + "@grafana/data": "12.0.0-pre", "@grafana/plugin-ui": "0.10.1", - "@grafana/runtime": "11.6.0-pre", - "@grafana/sql": "11.6.0-pre", - "@grafana/ui": "11.6.0-pre", + "@grafana/runtime": "12.0.0-pre", + "@grafana/sql": "12.0.0-pre", + "@grafana/ui": "12.0.0-pre", "lodash": "4.17.21", "react": "18.3.1", "rxjs": "7.8.1", "tslib": "2.8.1" }, "devDependencies": { - "@grafana/e2e-selectors": "11.6.0-pre", - "@grafana/plugin-configs": "11.6.0-pre", + "@grafana/e2e-selectors": "12.0.0-pre", + "@grafana/plugin-configs": "12.0.0-pre", "@testing-library/dom": "10.4.0", "@testing-library/react": "16.2.0", "@testing-library/user-event": "14.6.1", diff --git a/public/app/plugins/datasource/grafana-pyroscope-datasource/package.json b/public/app/plugins/datasource/grafana-pyroscope-datasource/package.json index 1b9d1b831b4..2a54f816399 100644 --- a/public/app/plugins/datasource/grafana-pyroscope-datasource/package.json +++ b/public/app/plugins/datasource/grafana-pyroscope-datasource/package.json @@ -2,13 +2,13 @@ "name": "@grafana-plugins/grafana-pyroscope-datasource", "description": "Continuous profiling for analysis of CPU and memory usage, down to the line number and throughout time. Saving infrastructure cost, improving performance, and increasing reliability.", "private": true, - "version": "11.6.0-pre", + "version": "12.0.0-pre", "dependencies": { "@emotion/css": "11.13.5", - "@grafana/data": "11.6.0-pre", - "@grafana/runtime": "11.6.0-pre", - "@grafana/schema": "11.6.0-pre", - "@grafana/ui": "11.6.0-pre", + "@grafana/data": "12.0.0-pre", + "@grafana/runtime": "12.0.0-pre", + "@grafana/schema": "12.0.0-pre", + "@grafana/ui": "12.0.0-pre", "fast-deep-equal": "^3.1.3", "lodash": "4.17.21", "monaco-editor": "0.34.1", @@ -20,7 +20,7 @@ "tslib": "2.8.1" }, "devDependencies": { - "@grafana/plugin-configs": "11.6.0-pre", + "@grafana/plugin-configs": "12.0.0-pre", "@testing-library/dom": "10.4.0", "@testing-library/jest-dom": "6.6.3", "@testing-library/react": "16.2.0", diff --git a/public/app/plugins/datasource/grafana-testdata-datasource/package.json b/public/app/plugins/datasource/grafana-testdata-datasource/package.json index 1c283aa6baa..c6e7abc0000 100644 --- a/public/app/plugins/datasource/grafana-testdata-datasource/package.json +++ b/public/app/plugins/datasource/grafana-testdata-datasource/package.json @@ -2,13 +2,13 @@ "name": "@grafana-plugins/grafana-testdata-datasource", "description": "Generates test data in different forms", "private": true, - "version": "11.6.0-pre", + "version": "12.0.0-pre", "dependencies": { "@emotion/css": "11.13.5", - "@grafana/data": "11.6.0-pre", - "@grafana/runtime": "11.6.0-pre", - "@grafana/schema": "11.6.0-pre", - "@grafana/ui": "11.6.0-pre", + "@grafana/data": "12.0.0-pre", + "@grafana/runtime": "12.0.0-pre", + "@grafana/schema": "12.0.0-pre", + "@grafana/ui": "12.0.0-pre", "d3-random": "^3.0.1", "lodash": "4.17.21", "micro-memoize": "^4.1.2", @@ -21,8 +21,8 @@ "uuid": "11.0.5" }, "devDependencies": { - "@grafana/e2e-selectors": "11.6.0-pre", - "@grafana/plugin-configs": "11.6.0-pre", + "@grafana/e2e-selectors": "12.0.0-pre", + "@grafana/plugin-configs": "12.0.0-pre", "@testing-library/dom": "10.4.0", "@testing-library/react": "16.2.0", "@testing-library/user-event": "14.6.1", diff --git a/public/app/plugins/datasource/jaeger/package.json b/public/app/plugins/datasource/jaeger/package.json index 99574d165d1..e5eb514da34 100644 --- a/public/app/plugins/datasource/jaeger/package.json +++ b/public/app/plugins/datasource/jaeger/package.json @@ -2,7 +2,7 @@ "name": "@grafana-plugins/jaeger", "description": "Jaeger plugin for Grafana", "private": true, - "version": "11.6.0-pre", + "version": "12.0.0-pre", "dependencies": { "@emotion/css": "11.13.5", "@grafana/data": "workspace:*", diff --git a/public/app/plugins/datasource/loki/components/LokiQueryEditor.test.tsx b/public/app/plugins/datasource/loki/components/LokiQueryEditor.test.tsx index a821cdf8c38..9ab6f7cc667 100644 --- a/public/app/plugins/datasource/loki/components/LokiQueryEditor.test.tsx +++ b/public/app/plugins/datasource/loki/components/LokiQueryEditor.test.tsx @@ -151,7 +151,7 @@ describe('LokiQueryEditorSelector', () => { it('parses query when changing to builder mode', async () => { const { rerender } = renderWithProps({ refId: 'A', - expr: 'rate({instance="host.docker.internal:3000"}[$__interval])', + expr: 'rate({instance="host.docker.internal:3000"}[$__auto])', editorMode: QueryEditorMode.Code, }); await expectCodeEditor(); @@ -161,7 +161,7 @@ describe('LokiQueryEditorSelector', () => { {...defaultProps} query={{ refId: 'A', - expr: 'rate({instance="host.docker.internal:3000"}[$__interval])', + expr: 'rate({instance="host.docker.internal:3000"}[$__auto])', editorMode: QueryEditorMode.Builder, }} /> @@ -169,7 +169,7 @@ describe('LokiQueryEditorSelector', () => { await screen.findByText('host.docker.internal:3000'); expect(screen.getByText('Rate')).toBeInTheDocument(); - expect(screen.getByText('$__interval')).toBeInTheDocument(); + expect(screen.getByText('$__auto')).toBeInTheDocument(); }); it('renders the label browser button', async () => { diff --git a/public/app/plugins/datasource/loki/querybuilder/operationUtils.ts b/public/app/plugins/datasource/loki/querybuilder/operationUtils.ts index a3eb6702008..416f0e4c5fd 100644 --- a/public/app/plugins/datasource/loki/querybuilder/operationUtils.ts +++ b/public/app/plugins/datasource/loki/querybuilder/operationUtils.ts @@ -336,11 +336,14 @@ export function getLineFilterRenderer(operation: string, caseInsensitive?: boole return `${innerExpr} ${operation} ${delimiter}${params.join(`${delimiter} or ${delimiter}`)}${delimiter}`; }; } + function getRangeVectorParamDef(): QueryBuilderOperationParamDef { return { name: 'Range', type: 'string', - options: ['$__auto', '1m', '5m', '10m', '1h', '24h'], + options: ['$__auto'], + description: + 'Use the default value "$__auto". Change the "step" value in the query options to change the bucket size.', }; } diff --git a/public/app/plugins/datasource/mssql/package.json b/public/app/plugins/datasource/mssql/package.json index 4bc83b5cfc2..324d1f9f0d0 100644 --- a/public/app/plugins/datasource/mssql/package.json +++ b/public/app/plugins/datasource/mssql/package.json @@ -2,22 +2,22 @@ "name": "@grafana-plugins/mssql", "description": "MSSQL data source plugin", "private": true, - "version": "11.6.0-pre", + "version": "12.0.0-pre", "dependencies": { "@emotion/css": "11.13.5", - "@grafana/data": "11.6.0-pre", + "@grafana/data": "12.0.0-pre", "@grafana/plugin-ui": "0.10.1", - "@grafana/runtime": "11.6.0-pre", - "@grafana/sql": "11.6.0-pre", - "@grafana/ui": "11.6.0-pre", + "@grafana/runtime": "12.0.0-pre", + "@grafana/sql": "12.0.0-pre", + "@grafana/ui": "12.0.0-pre", "lodash": "4.17.21", "react": "18.3.1", "rxjs": "7.8.1", "tslib": "2.8.1" }, "devDependencies": { - "@grafana/e2e-selectors": "11.6.0-pre", - "@grafana/plugin-configs": "11.6.0-pre", + "@grafana/e2e-selectors": "12.0.0-pre", + "@grafana/plugin-configs": "12.0.0-pre", "@testing-library/dom": "10.4.0", "@testing-library/react": "16.2.0", "@testing-library/user-event": "14.6.1", diff --git a/public/app/plugins/datasource/mysql/package.json b/public/app/plugins/datasource/mysql/package.json index 7b659769481..7a157f78346 100644 --- a/public/app/plugins/datasource/mysql/package.json +++ b/public/app/plugins/datasource/mysql/package.json @@ -2,22 +2,22 @@ "name": "@grafana-plugins/mysql", "description": "MySQL data source plugin", "private": true, - "version": "11.6.0-pre", + "version": "12.0.0-pre", "dependencies": { "@emotion/css": "11.13.5", - "@grafana/data": "11.6.0-pre", + "@grafana/data": "12.0.0-pre", "@grafana/plugin-ui": "0.10.1", - "@grafana/runtime": "11.6.0-pre", - "@grafana/sql": "11.6.0-pre", - "@grafana/ui": "11.6.0-pre", + "@grafana/runtime": "12.0.0-pre", + "@grafana/sql": "12.0.0-pre", + "@grafana/ui": "12.0.0-pre", "lodash": "4.17.21", "react": "18.3.1", "rxjs": "7.8.1", "tslib": "2.8.1" }, "devDependencies": { - "@grafana/e2e-selectors": "11.6.0-pre", - "@grafana/plugin-configs": "11.6.0-pre", + "@grafana/e2e-selectors": "12.0.0-pre", + "@grafana/plugin-configs": "12.0.0-pre", "@testing-library/dom": "10.4.0", "@testing-library/react": "16.2.0", "@testing-library/user-event": "14.6.1", diff --git a/public/app/plugins/datasource/parca/package.json b/public/app/plugins/datasource/parca/package.json index 13394f1de77..f2c1816206d 100644 --- a/public/app/plugins/datasource/parca/package.json +++ b/public/app/plugins/datasource/parca/package.json @@ -2,13 +2,13 @@ "name": "@grafana-plugins/parca", "description": "Continuous profiling for analysis of CPU and memory usage, down to the line number and throughout time. Saving infrastructure cost, improving performance, and increasing reliability.", "private": true, - "version": "11.6.0-pre", + "version": "12.0.0-pre", "dependencies": { "@emotion/css": "11.13.5", - "@grafana/data": "11.6.0-pre", - "@grafana/runtime": "11.6.0-pre", - "@grafana/schema": "11.6.0-pre", - "@grafana/ui": "11.6.0-pre", + "@grafana/data": "12.0.0-pre", + "@grafana/runtime": "12.0.0-pre", + "@grafana/schema": "12.0.0-pre", + "@grafana/ui": "12.0.0-pre", "lodash": "4.17.21", "monaco-editor": "0.34.1", "react": "18.3.1", @@ -18,7 +18,7 @@ "tslib": "2.8.1" }, "devDependencies": { - "@grafana/plugin-configs": "11.6.0-pre", + "@grafana/plugin-configs": "12.0.0-pre", "@testing-library/dom": "10.4.0", "@testing-library/react": "16.2.0", "@testing-library/user-event": "14.6.1", diff --git a/public/app/plugins/datasource/tempo/SearchTraceQLEditor/AggregateByAlert.tsx b/public/app/plugins/datasource/tempo/SearchTraceQLEditor/AggregateByAlert.tsx new file mode 100644 index 00000000000..ccb979569df --- /dev/null +++ b/public/app/plugins/datasource/tempo/SearchTraceQLEditor/AggregateByAlert.tsx @@ -0,0 +1,20 @@ +import React from 'react'; + +import { Alert, Button } from '@grafana/ui'; + +import { TempoQuery } from '../dataquery.gen'; + +export function AggregateByAlert({ + query, + onChange, +}: { + query: TempoQuery; + onChange?: () => void; +}): React.ReactNode | null { + return query.groupBy ? ( + + The aggregate by feature has been removed. We recommend using Traces Drildown app instead.   + + + ) : null; +} diff --git a/public/app/plugins/datasource/tempo/SearchTraceQLEditor/GroupByField.test.tsx b/public/app/plugins/datasource/tempo/SearchTraceQLEditor/GroupByField.test.tsx deleted file mode 100644 index 8a35d0905c5..00000000000 --- a/public/app/plugins/datasource/tempo/SearchTraceQLEditor/GroupByField.test.tsx +++ /dev/null @@ -1,162 +0,0 @@ -import { render, screen } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; -import { useState } from 'react'; - -import { TraceqlSearchScope } from '../dataquery.gen'; -import { TempoDatasource } from '../datasource'; -import TempoLanguageProvider from '../language_provider'; -import { initTemplateSrv } from '../test/test_utils'; -import { TempoQuery } from '../types'; - -import { GroupByField } from './GroupByField'; - -describe('GroupByField', () => { - let user: ReturnType; - - const datasource: TempoDatasource = { - search: { - filters: [], - }, - } as unknown as TempoDatasource; - - const lp = new TempoLanguageProvider(datasource); - datasource.languageProvider = lp; - - let query: TempoQuery = { - refId: 'A', - queryType: 'traceqlSearch', - query: '', - filters: [], - groupBy: [{ id: 'group-by-id', scope: TraceqlSearchScope.Span, tag: 'component' }], - }; - - const onChange = (q: TempoQuery) => { - query = q; - }; - - jest.spyOn(lp, 'getMetricsSummaryTags').mockReturnValue(['component', 'http.method', 'http.status_code']); - jest.spyOn(lp, 'getTags').mockReturnValue(['component', 'http.method', 'http.status_code']); - - beforeEach(() => { - jest.useFakeTimers(); - // Need to use delay: null here to work with fakeTimers - // see https://github.com/testing-library/user-event/issues/833 - user = userEvent.setup({ delay: null }); - - initTemplateSrv([{ name: 'templateVariable1' }, { name: 'templateVariable2' }], {}); - }); - - afterEach(() => { - jest.useRealTimers(); - }); - - it('should only show add/remove tag when necessary', async () => { - const GroupByWithProps = () => { - const [query, setQuery] = useState({ - refId: 'A', - queryType: 'traceqlSearch', - key: 'Q-595a9bbc-2a25-49a7-9249-a52a0a475d83-0', - filters: [], - groupBy: [{ id: 'group-by-id', scope: TraceqlSearchScope.Span }], - }); - return ( - setQuery(q)} - isTagsLoading={false} - /> - ); - }; - render(); - expect(screen.queryAllByLabelText('Add tag').length).toBe(0); // not filled in the default tag, so no need to add another one - expect(screen.queryAllByLabelText(/Remove tag/).length).toBe(0); // mot filled in the default tag, so no values to remove - expect(screen.getAllByText('Select tag').length).toBe(1); - - await user.click(screen.getByText('Select tag')); - jest.advanceTimersByTime(1000); - await user.click(screen.getByText('http.method')); - jest.advanceTimersByTime(1000); - expect(screen.getAllByLabelText('Add tag').length).toBe(1); - expect(screen.getAllByLabelText(/Remove tag/).length).toBe(1); - - await user.click(screen.getByLabelText('Add tag')); - jest.advanceTimersByTime(1000); - expect(screen.queryAllByLabelText('Add tag').length).toBe(0); // not filled in the new tag, so no need to add another one - expect(screen.getAllByLabelText(/Remove tag/).length).toBe(2); // one for each tag - - await user.click(screen.getAllByLabelText(/Remove tag/)[1]); - jest.advanceTimersByTime(1000); - expect(screen.queryAllByLabelText('Add tag').length).toBe(1); // filled in the default tag, so can add another one - expect(screen.queryAllByLabelText(/Remove tag/).length).toBe(1); // filled in the default tag, so can remove values - - await user.click(screen.getAllByLabelText(/Remove tag/)[0]); - jest.advanceTimersByTime(1000); - expect(screen.queryAllByLabelText('Add tag').length).toBe(0); // not filled in the default tag, so no need to add another one - expect(screen.queryAllByLabelText(/Remove tag/).length).toBe(0); // mot filled in the default tag, so no values to remove - }); - - it('should update scope when new value is selected in scope input', async () => { - const { container } = render( - - ); - - const scopeSelect = container.querySelector(`input[aria-label="Select scope for filter 1"]`); - expect(scopeSelect).not.toBeNull(); - expect(scopeSelect).toBeInTheDocument(); - - if (scopeSelect) { - await user.click(scopeSelect); - jest.advanceTimersByTime(1000); - const resourceScope = await screen.findByText('resource'); - await user.click(resourceScope); - const groupByFilter = query.groupBy?.find((f) => f.id === 'group-by-id'); - expect(groupByFilter).not.toBeNull(); - expect(groupByFilter?.scope).toBe('resource'); - expect(groupByFilter?.tag).toBe(''); - } - }); - - it('should update tag when new value is selected in tag input', async () => { - const { container } = render( - - ); - - const tagSelect = container.querySelector(`input[aria-label="Select tag for filter 1"]`); - expect(tagSelect).not.toBeNull(); - expect(tagSelect).toBeInTheDocument(); - - if (tagSelect) { - await user.click(tagSelect); - jest.advanceTimersByTime(1000); - const tag = await screen.findByText('http.method'); - await user.click(tag); - const groupByFilter = query.groupBy?.find((f) => f.id === 'group-by-id'); - expect(groupByFilter).not.toBeNull(); - expect(groupByFilter?.tag).toBe('http.method'); - } - }); - - it('should allow selecting template variables', async () => { - const { container } = render( - - ); - - const tagSelect = container.querySelector(`input[aria-label="Select tag for filter 1"]`); - expect(tagSelect).not.toBeNull(); - expect(tagSelect).toBeInTheDocument(); - - if (tagSelect) { - await user.click(tagSelect); - jest.advanceTimersByTime(1000); - expect(await screen.findByText('$templateVariable1')).toBeInTheDocument(); - expect(await screen.findByText('$templateVariable2')).toBeInTheDocument(); - } - }); -}); diff --git a/public/app/plugins/datasource/tempo/SearchTraceQLEditor/GroupByField.tsx b/public/app/plugins/datasource/tempo/SearchTraceQLEditor/GroupByField.tsx deleted file mode 100644 index 14134fec7ec..00000000000 --- a/public/app/plugins/datasource/tempo/SearchTraceQLEditor/GroupByField.tsx +++ /dev/null @@ -1,193 +0,0 @@ -import { css } from '@emotion/css'; -import { useEffect, useMemo, useState } from 'react'; -import { v4 as uuidv4 } from 'uuid'; - -import { GrafanaTheme2 } from '@grafana/data'; -import { AccessoryButton } from '@grafana/plugin-ui'; -import { Alert, HorizontalGroup, InputActionMeta, Select, useStyles2 } from '@grafana/ui'; - -import { TraceqlFilter, TraceqlSearchScope } from '../dataquery.gen'; -import { TempoDatasource } from '../datasource'; -import { OPTIONS_LIMIT } from '../language_provider'; -import { TempoQuery } from '../types'; - -import InlineSearchField from './InlineSearchField'; -import { withTemplateVariableOptions } from './SearchField'; -import { replaceAt } from './utils'; - -interface Props { - datasource: TempoDatasource; - onChange: (value: TempoQuery) => void; - query: Partial & TempoQuery; - isTagsLoading: boolean; - addVariablesToOptions?: boolean; -} - -export const GroupByField = (props: Props) => { - const { datasource, onChange, query, isTagsLoading, addVariablesToOptions } = props; - const styles = useStyles2(getStyles); - const generateId = () => uuidv4().slice(0, 8); - const [tagQuery, setTagQuery] = useState(''); - - useEffect(() => { - if (!query.groupBy || query.groupBy.length === 0) { - onChange({ - ...query, - groupBy: [ - { - id: generateId(), - scope: TraceqlSearchScope.Span, - }, - ], - }); - } - }, [onChange, query]); - - const tagOptions = useMemo( - () => (f: TraceqlFilter) => { - const tags = datasource!.languageProvider.getMetricsSummaryTags(f.scope); - if (tagQuery.length === 0) { - return tags.slice(0, OPTIONS_LIMIT); - } - - const queryLowerCase = tagQuery.toLowerCase(); - return tags.filter((tag) => tag.toLowerCase().includes(queryLowerCase)).slice(0, OPTIONS_LIMIT); - }, - [datasource, tagQuery] - ); - - const addFilter = () => { - updateFilter({ - id: generateId(), - scope: TraceqlSearchScope.Span, - }); - }; - - const removeFilter = (filter: TraceqlFilter) => { - onChange({ ...query, groupBy: query.groupBy?.filter((f) => f.id !== filter.id) }); - }; - - const updateFilter = (filter: TraceqlFilter) => { - const copy = { ...query }; - copy.groupBy ||= []; - const indexOfFilter = copy.groupBy.findIndex((f) => f.id === filter.id); - if (indexOfFilter >= 0) { - copy.groupBy = replaceAt(copy.groupBy, indexOfFilter, filter); - } else { - copy.groupBy.push(filter); - } - onChange(copy); - }; - - const scopeOptions = Object.values(TraceqlSearchScope) - .filter((s) => { - // only add scope if it has tags - return datasource.languageProvider.getTags(s).length > 0; - }) - .map((t) => ({ label: t, value: t })); - - return ( - - <> - {query.groupBy?.map((f, i) => { - const tags = tagOptions(f) - ?.concat(f.tag !== undefined && f.tag !== '' && !tagOptions(f)?.includes(f.tag) ? [f.tag] : []) - .map((t) => ({ - label: t, - value: t, - })); - return ( -
- - { - updateFilter({ ...f, tag: v?.value }); - }} - options={addVariablesToOptions ? withTemplateVariableOptions(tags) : tags} - onInputChange={(value: string, { action }: InputActionMeta) => { - if (action === 'input-change') { - setTagQuery(value); - } - }} - onCloseMenu={() => setTagQuery('')} - placeholder="Select tag" - value={f.tag || ''} - /> - {(f.tag || (query.groupBy?.length ?? 0) > 1) && ( - removeFilter(f)} - tooltip="Remove tag" - title={`Remove tag for filter ${i + 1}`} - variant="secondary" - /> - )} - {f.tag && i === (query.groupBy?.length ?? 0) - 1 && ( - - addFilter()} - tooltip="Add tag" - variant="secondary" - /> - - )} - -
- ); - })} - {query.groupBy && query.groupBy.length > 0 && query.groupBy[0].tag && ( - - The aggregate by feature is deprecated. We recommend using Explore Traces instead. If you want to write your - own TraceQL queries to replicate this API, please check - - this page - - . - - )} - -
- ); -}; - -const getStyles = (theme: GrafanaTheme2) => ({ - addTag: css({ - marginLeft: theme.spacing(1), - }), - notice: css({ - width: '500px', - marginTop: theme.spacing(0.75), - }), - noticeLink: css({ - color: theme.colors.text.link, - textDecoration: 'underline', - marginLeft: '5px', - }), -}); diff --git a/public/app/plugins/datasource/tempo/SearchTraceQLEditor/TraceQLSearch.test.tsx b/public/app/plugins/datasource/tempo/SearchTraceQLEditor/TraceQLSearch.test.tsx index 69ee73631f7..5957dfd90d2 100644 --- a/public/app/plugins/datasource/tempo/SearchTraceQLEditor/TraceQLSearch.test.tsx +++ b/public/app/plugins/datasource/tempo/SearchTraceQLEditor/TraceQLSearch.test.tsx @@ -2,8 +2,6 @@ import { act, render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { useState } from 'react'; -import { config } from '@grafana/runtime'; - import { TraceqlSearchScope } from '../dataquery.gen'; import { TempoDatasource } from '../datasource'; import TempoLanguageProvider from '../language_provider'; @@ -238,26 +236,28 @@ describe('TraceQLSearch', () => { }); }); - it('should not render group by when feature toggle is not enabled', async () => { - await waitFor(() => { + it('should not render group by alert when query does not contain group by', async () => { + await act(async () => { render( ); - const groupBy = screen.queryByText('Aggregate by'); - expect(groupBy).toBeNull(); - expect(groupBy).not.toBeInTheDocument(); + expect(screen.queryByRole('button', { name: 'Remove aggregate by from this query' })).not.toBeInTheDocument(); }); }); - it('should render group by when feature toggle enabled', async () => { - config.featureToggles.metricsSummary = true; - await waitFor(() => { + it('should render group by alert when query contains group by', async () => { + const onChange = jest.fn(); + await waitFor(async () => { render( - + ); - const groupBy = screen.queryByText('Aggregate by'); - expect(groupBy).not.toBeNull(); - expect(groupBy).toBeInTheDocument(); + const button = screen.queryByRole('button', { name: 'Remove aggregate by from this query' }); + expect(button).toBeInTheDocument(); }); }); }); diff --git a/public/app/plugins/datasource/tempo/SearchTraceQLEditor/TraceQLSearch.tsx b/public/app/plugins/datasource/tempo/SearchTraceQLEditor/TraceQLSearch.tsx index 7ef9bd88303..fee68d5c771 100644 --- a/public/app/plugins/datasource/tempo/SearchTraceQLEditor/TraceQLSearch.tsx +++ b/public/app/plugins/datasource/tempo/SearchTraceQLEditor/TraceQLSearch.tsx @@ -13,8 +13,8 @@ import { TempoQueryBuilderOptions } from '../traceql/TempoQueryBuilderOptions'; import { traceqlGrammar } from '../traceql/traceql'; import { TempoQuery } from '../types'; +import { AggregateByAlert } from './AggregateByAlert'; import DurationInput from './DurationInput'; -import { GroupByField } from './GroupByField'; import InlineSearchField from './InlineSearchField'; import SearchField from './SearchField'; import TagsInput from './TagsInput'; @@ -237,15 +237,15 @@ const TraceQLSearch = ({ datasource, query, onChange, onClearResults, app, addVa addVariablesToOptions={addVariablesToOptions} /> - {config.featureToggles.metricsSummary && ( - - )} + { + delete query.groupBy; + onChange({ + ...query, + }); + }} + />
diff --git a/public/app/plugins/datasource/tempo/dataquery.cue b/public/app/plugins/datasource/tempo/dataquery.cue index ef5a4526dbb..c87f5eddb9e 100644 --- a/public/app/plugins/datasource/tempo/dataquery.cue +++ b/public/app/plugins/datasource/tempo/dataquery.cue @@ -47,7 +47,7 @@ composableKinds: DataQuery: { // Defines the maximum number of spans per spanset that are returned from Tempo spss?: int64 filters: [...#TraceqlFilter] - // Filters that are used to query the metrics summary + // deprecated Filters that are used to query the metrics summary groupBy?: [...#TraceqlFilter] // The type of the table that is used to display the search results tableType?: #SearchTableType diff --git a/public/app/plugins/datasource/tempo/dataquery.gen.ts b/public/app/plugins/datasource/tempo/dataquery.gen.ts index 757960330c8..f55757b5a47 100644 --- a/public/app/plugins/datasource/tempo/dataquery.gen.ts +++ b/public/app/plugins/datasource/tempo/dataquery.gen.ts @@ -17,7 +17,7 @@ export interface TempoQuery extends common.DataQuery { exemplars?: number; filters: Array; /** - * Filters that are used to query the metrics summary + * deprecated Filters that are used to query the metrics summary */ groupBy?: Array; /** diff --git a/public/app/plugins/datasource/tempo/datasource.test.ts b/public/app/plugins/datasource/tempo/datasource.test.ts index c51e27fdec8..cc5683941e8 100644 --- a/public/app/plugins/datasource/tempo/datasource.test.ts +++ b/public/app/plugins/datasource/tempo/datasource.test.ts @@ -172,16 +172,6 @@ describe('Tempo data source', () => { valueType: 'string', }, ], - groupBy: [ - { - id: 'groupBy1', - tag: '$interpolationVar', - }, - { - id: 'groupBy2', - tag: '$interpolationVar', - }, - ], }; } let templateSrv: TemplateSrv; @@ -214,8 +204,6 @@ describe('Tempo data source', () => { expect(queries[0].filters[0].value).toBe(textWithPipe); expect(queries[0].filters[1].value).toBe(text); expect(queries[0].filters[1].tag).toBe(text); - expect(queries[0].groupBy?.[0].tag).toBe(text); - expect(queries[0].groupBy?.[1].tag).toBe(text); }); it('when applying template variables', async () => { @@ -228,8 +216,6 @@ describe('Tempo data source', () => { expect(resp.filters[0].value).toBe(textWithPipe); expect(resp.filters[1].value).toBe(scopedText); expect(resp.filters[1].tag).toBe(scopedText); - expect(resp.groupBy?.[0].tag).toBe(scopedText); - expect(resp.groupBy?.[1].tag).toBe(scopedText); }); it('when serviceMapQuery is an array', async () => { @@ -352,18 +338,6 @@ describe('Tempo data source', () => { expect(edgesFrame.meta?.preferredVisualisationType).toBe('nodeGraph'); }); - it('should format metrics summary query correctly', () => { - const ds = new TempoDatasource(defaultSettings, {} as TemplateSrv); - const queryGroupBy = [ - { id: '1', scope: TraceqlSearchScope.Unscoped, tag: 'component' }, - { id: '2', scope: TraceqlSearchScope.Span, tag: 'name' }, - { id: '3', scope: TraceqlSearchScope.Resource, tag: 'service.name' }, - { id: '4', scope: TraceqlSearchScope.Intrinsic, tag: 'kind' }, - ]; - const groupBy = ds.formatGroupBy(queryGroupBy); - expect(groupBy).toEqual('.component, span.name, resource.service.name, kind'); - }); - describe('test the testDatasource function', () => { it('should return a success msg if response.ok is true', async () => { mockObservable = () => of({ ok: true }); diff --git a/public/app/plugins/datasource/tempo/datasource.ts b/public/app/plugins/datasource/tempo/datasource.ts index de07cc54e45..0791334f1c1 100644 --- a/public/app/plugins/datasource/tempo/datasource.ts +++ b/public/app/plugins/datasource/tempo/datasource.ts @@ -53,7 +53,6 @@ import { totalsMetric, } from './graphTransform'; import TempoLanguageProvider from './language_provider'; -import { createTableFrameFromMetricsSummaryQuery, emptyResponse, MetricsSummary } from './metricsSummary'; import { enhanceTraceQlMetricsResponse, formatTraceQLResponse, @@ -405,19 +404,18 @@ export class TempoDatasource extends DataSourceWithBackend this.hasGroupBy(t)); - if (target) { - const appliedQuery = this.applyVariables(target, options.scopedVars); - const queryFromFilters = this.languageProvider.generateQueryFromFilters(appliedQuery.filters); - subQueries.push(this.handleMetricsSummaryQuery(appliedQuery, queryFromFilters, options)); - } - } + if (targets.traceqlSearch[0].groupBy) { + return of({ + error: { + message: + 'The aggregate by query is deprecated. Please remove the current query and create a new one. Alternatively, you can use Traces Drilldown.', + }, + data: [], + }); + } - const traceqlSearchTargets = config.featureToggles.metricsSummary - ? targets.traceqlSearch.filter((t) => !this.hasGroupBy(t)) - : targets.traceqlSearch; + try { + const traceqlSearchTargets = targets.traceqlSearch; if (traceqlSearchTargets.length > 0) { const appliedQuery = this.applyVariables(traceqlSearchTargets[0], options.scopedVars); const queryFromFilters = this.languageProvider.generateQueryFromFilters(appliedQuery.filters); @@ -552,17 +550,6 @@ export class TempoDatasource extends DataSourceWithBackend { - const updatedFilter = { - ...filter, - tag: this.templateSrv.replace(filter.tag ?? '', scopedVars), - }; - - return updatedFilter; - }); - } - return { ...expandedQuery, query: this.templateSrv.replace(query.query ?? '', scopedVars, VariableFormatID.Pipe), @@ -572,22 +559,6 @@ export class TempoDatasource extends DataSourceWithBackend { - return groupBy - ?.filter((f) => f.tag) - .map((f) => { - if (f.scope === TraceqlSearchScope.Unscoped) { - return `.${f.tag}`; - } - return f.scope !== TraceqlSearchScope.Intrinsic ? `${f.scope}.${f.tag}` : f.tag; - }) - .join(', '); - }; - - hasGroupBy = (query: TempoQuery) => { - return query.groupBy?.find((gb) => gb.tag); - }; - /** * Handles the simplest of the queries where we have just a trace id and return trace data for it. * @param options @@ -731,93 +702,6 @@ export class TempoDatasource extends DataSourceWithBackend) => { - reportInteraction('grafana_traces_metrics_summary_queried', { - datasourceType: 'tempo', - app: options.app ?? '', - grafana_version: config.buildInfo.version, - filterCount: target.groupBy?.length ?? 0, - }); - - if (query === '{}') { - return of({ - error: { - message: - 'Please ensure you do not have an empty query. This is so filters are applied and the metrics summary is not generated from all spans.', - }, - data: emptyResponse, - }); - } - - const startTime = performance.now(); - const groupBy = target.groupBy ? this.formatGroupBy(target.groupBy) : ''; - return this._request('/api/metrics/summary', { - q: query, - groupBy, - start: options.range.from.unix(), - end: options.range.to.unix(), - }).pipe( - map((response) => { - if (!response.data.summaries) { - reportTempoQueryMetrics('grafana_traces_metrics_summary_response', options, { - success: false, - streaming: false, - latencyMs: Math.round(performance.now() - startTime), // rounded to nearest millisecond - query: query ?? '', - error: getErrorMessage(`No summary data for '${groupBy}'.`), - }); - return { - error: { - message: getErrorMessage(`No summary data for '${groupBy}'.`), - }, - data: emptyResponse, - }; - } - // Check if any of the results have series data as older versions of Tempo placed the series data in a different structure - const hasSeries = response.data.summaries.some((summary: MetricsSummary) => summary.series.length > 0); - if (!hasSeries) { - reportTempoQueryMetrics('grafana_traces_metrics_summary_response', options, { - success: false, - streaming: false, - latencyMs: Math.round(performance.now() - startTime), // rounded to nearest millisecond - query: query ?? '', - error: getErrorMessage(`No series data. Ensure you are using an up to date version of Tempo`), - }); - return { - error: { - message: getErrorMessage(`No series data. Ensure you are using an up to date version of Tempo`), - }, - data: emptyResponse, - }; - } - reportTempoQueryMetrics('grafana_traces_metrics_summary_response', options, { - success: true, - streaming: false, - latencyMs: Math.round(performance.now() - startTime), // rounded to nearest millisecond - query: query ?? '', - }); - return { - data: createTableFrameFromMetricsSummaryQuery(response.data.summaries, query, this.instanceSettings), - }; - }), - catchError((error) => { - reportTempoQueryMetrics('grafana_traces_metrics_summary_response', options, { - success: false, - streaming: false, - latencyMs: Math.round(performance.now() - startTime), // rounded to nearest millisecond - query: query ?? '', - error: getErrorMessage(error.data.message), - statusCode: error.status, - statusText: error.statusText, - }); - return of({ - error: { message: getErrorMessage(error.data.message) }, - data: emptyResponse, - }); - }) - ); - }; - // This function can probably be simplified by avoiding passing both `targets` and `query`, // since `query` is built from `targets`, if you look at how this function is currently called handleStreamingQuery( diff --git a/public/app/plugins/datasource/tempo/language_provider.test.ts b/public/app/plugins/datasource/tempo/language_provider.test.ts index 7aff5eb7b18..9a91c94176a 100644 --- a/public/app/plugins/datasource/tempo/language_provider.test.ts +++ b/public/app/plugins/datasource/tempo/language_provider.test.ts @@ -8,38 +8,6 @@ import { intrinsics } from './traceql/traceql'; import { Scope } from './types'; describe('Language_provider', () => { - describe('should get correct metrics summary tags', () => { - it('for API v1 tags', async () => { - const lp = setup(v1Tags); - const tags = lp.getMetricsSummaryTags(); - expect(tags).toEqual(['bar', 'foo']); - }); - - it('for API v2 intrinsic tags', async () => { - const lp = setup(undefined, v2Tags); - const tags = lp.getMetricsSummaryTags(TraceqlSearchScope.Intrinsic); - expect(tags).toEqual(uniq(['duration', 'kind', 'name', 'status'].concat(intrinsics))); - }); - - it('for API v2 resource tags', async () => { - const lp = setup(undefined, v2Tags); - const tags = lp.getMetricsSummaryTags(TraceqlSearchScope.Resource); - expect(tags).toEqual(['cluster', 'container']); - }); - - it('for API v2 span tags', async () => { - const lp = setup(undefined, v2Tags); - const tags = lp.getMetricsSummaryTags(TraceqlSearchScope.Span); - expect(tags).toEqual(['db']); - }); - - it('for API v2 unscoped tags', async () => { - const lp = setup(undefined, v2Tags); - const tags = lp.getMetricsSummaryTags(TraceqlSearchScope.Unscoped); - expect(tags).toEqual(['cluster', 'container', 'db']); - }); - }); - describe('should get correct tags', () => { it('for API v1 tags', async () => { const lp = setup(v1Tags); diff --git a/public/app/plugins/datasource/tempo/language_provider.ts b/public/app/plugins/datasource/tempo/language_provider.ts index 63639645ef1..e8c4d202dde 100644 --- a/public/app/plugins/datasource/tempo/language_provider.ts +++ b/public/app/plugins/datasource/tempo/language_provider.ts @@ -97,18 +97,6 @@ export default class TempoLanguageProvider extends LanguageProvider { return []; }; - getMetricsSummaryTags = (scope?: TraceqlSearchScope) => { - if (this.tagsV2 && scope) { - if (scope === TraceqlSearchScope.Unscoped) { - return getUnscopedTags(this.tagsV2); - } - return getTagsByScope(this.tagsV2, scope); - } else if (this.tagsV1) { - return this.tagsV1; - } - return []; - }; - getTraceqlAutocompleteTags = (scope?: string) => { if (this.tagsV2) { if (!scope) { diff --git a/public/app/plugins/datasource/tempo/metricsSummary.test.ts b/public/app/plugins/datasource/tempo/metricsSummary.test.ts deleted file mode 100644 index 15b351e564e..00000000000 --- a/public/app/plugins/datasource/tempo/metricsSummary.test.ts +++ /dev/null @@ -1,300 +0,0 @@ -import { defaultSettings } from './datasource.test'; -import { - createTableFrameFromMetricsSummaryQuery, - emptyResponse, - getConfigQuery, - transformToMetricsData, -} from './metricsSummary'; - -describe('MetricsSummary', () => { - describe('createTableFrameFromMetricsSummaryQuery', () => { - it('should return emptyResponse when state is LoadingState.Error', () => { - const result = createTableFrameFromMetricsSummaryQuery([], '', defaultSettings); - expect(result).toEqual(emptyResponse); - }); - - it('should return correctly when state is LoadingState.Done', () => { - const data = [ - { - spanCount: '10', - errorSpanCount: '1', - p50: '1', - p90: '2', - p95: '3', - p99: '4', - series: [ - { - key: 'span.http.status_code', - value: { - type: 3, - n: 208, - }, - }, - { - key: 'temperature', - value: { - type: 4, - f: 38.1, - }, - }, - ], - }, - ]; - const result = createTableFrameFromMetricsSummaryQuery( - data, - '{name="HTTP POST - post"} | by(resource.service.name)', - defaultSettings - ); - expect(result).toMatchInlineSnapshot(` - [ - { - "fields": [ - { - "config": { - "displayNameFromDS": "span.http.status_code", - "links": [ - { - "internal": { - "datasourceName": "tempo", - "datasourceUid": "gdev-tempo", - "query": { - "query": "{name="HTTP POST - post" && span.http.status_code=\${__data.fields["span.http.status_code"]} && temperature=\${__data.fields["temperature"]}} | by(resource.service.name)", - "queryType": "traceql", - }, - }, - "title": "Query in explore", - "url": "", - }, - ], - "noValue": "", - }, - "name": "span.http.status_code", - "type": "string", - "values": [ - 208, - ], - }, - { - "config": { - "displayNameFromDS": "temperature", - "links": [ - { - "internal": { - "datasourceName": "tempo", - "datasourceUid": "gdev-tempo", - "query": { - "query": "{name="HTTP POST - post" && span.http.status_code=\${__data.fields["span.http.status_code"]} && temperature=\${__data.fields["temperature"]}} | by(resource.service.name)", - "queryType": "traceql", - }, - }, - "title": "Query in explore", - "url": "", - }, - ], - "noValue": "", - }, - "name": "temperature", - "type": "string", - "values": [ - 38.1, - ], - }, - { - "config": { - "custom": { - "width": 150, - }, - "displayNameFromDS": "Span count", - }, - "name": "spanCount", - "type": "number", - "values": [ - 10, - ], - }, - { - "config": { - "custom": { - "width": 150, - }, - "displayNameFromDS": "Error", - "unit": "percent", - }, - "name": "errorPercentage", - "type": "number", - "values": [ - 10, - ], - }, - { - "config": { - "custom": { - "width": 150, - }, - "displayNameFromDS": "p50", - "unit": "ns", - }, - "name": "p50", - "type": "number", - "values": [ - 1, - ], - }, - { - "config": { - "custom": { - "width": 150, - }, - "displayNameFromDS": "p90", - "unit": "ns", - }, - "name": "p90", - "type": "number", - "values": [ - 2, - ], - }, - { - "config": { - "custom": { - "width": 150, - }, - "displayNameFromDS": "p95", - "unit": "ns", - }, - "name": "p95", - "type": "number", - "values": [ - 3, - ], - }, - { - "config": { - "custom": { - "width": 150, - }, - "displayNameFromDS": "p99", - "unit": "ns", - }, - "name": "p99", - "type": "number", - "values": [ - 4, - ], - }, - ], - "length": 1, - "meta": { - "preferredVisualisationType": "table", - }, - "name": "Metrics Summary", - "refId": "metrics-summary", - }, - ] - `); - }); - - it('transformToMetricsData should return correctly', () => { - const data = { - spanCount: '10', - errorSpanCount: '1', - p50: '1', - p90: '2', - p95: '3', - p99: '4', - series, - }; - const result = transformToMetricsData(data); - expect(result).toMatchInlineSnapshot(` - { - "contains_sink": "true", - "errorPercentage": 10, - "p50": 1, - "p90": 2, - "p95": 3, - "p99": 4, - "room": "kitchen", - "span.http.status_code": 208, - "spanCount": 10, - "spanKind": "server", - "spanStatus": "ok", - "temperature": 38.1, - "window_open": "8h", - } - `); - }); - - it('getConfigQuery should return correctly for empty target query', () => { - const result = getConfigQuery(series, '{}'); - expect(result).toEqual( - '{span.http.status_code=${__data.fields["span.http.status_code"]} && temperature=${__data.fields["temperature"]} && room="${__data.fields["room"]}" && contains_sink="${__data.fields["contains_sink"]}" && window_open="${__data.fields["window_open"]}" && spanStatus=${__data.fields["spanStatus"]} && spanKind=${__data.fields["spanKind"]}}' - ); - }); - - it('getConfigQuery should return correctly for target query', () => { - const result = getConfigQuery(series, '{name="HTTP POST - post"} | by(resource.service.name)'); - expect(result).toEqual( - '{name="HTTP POST - post" && span.http.status_code=${__data.fields["span.http.status_code"]} && temperature=${__data.fields["temperature"]} && room="${__data.fields["room"]}" && contains_sink="${__data.fields["contains_sink"]}" && window_open="${__data.fields["window_open"]}" && spanStatus=${__data.fields["spanStatus"]} && spanKind=${__data.fields["spanKind"]}} | by(resource.service.name)' - ); - }); - - it('getConfigQuery should return correctly for target query without brackets', () => { - const result = getConfigQuery(series, 'by(resource.service.name)'); - expect(result).toEqual( - '{span.http.status_code=${__data.fields["span.http.status_code"]} && temperature=${__data.fields["temperature"]} && room="${__data.fields["room"]}" && contains_sink="${__data.fields["contains_sink"]}" && window_open="${__data.fields["window_open"]}" && spanStatus=${__data.fields["spanStatus"]} && spanKind=${__data.fields["spanKind"]}} | by(resource.service.name)' - ); - }); - }); -}); - -const series = [ - { - key: 'span.http.status_code', - value: { - type: 3, - n: 208, - }, - }, - { - key: 'temperature', - value: { - type: 4, - f: 38.1, - }, - }, - { - key: 'room', - value: { - type: 5, - s: 'kitchen', - }, - }, - { - key: 'contains_sink', - value: { - type: 6, - b: 'true', - }, - }, - { - key: 'window_open', - value: { - type: 7, - d: '8h', - }, - }, - { - key: 'spanStatus', - value: { - type: 8, - status: 1, - }, - }, - { - key: 'spanKind', - value: { - type: 9, - kind: 3, - }, - }, -]; diff --git a/public/app/plugins/datasource/tempo/metricsSummary.ts b/public/app/plugins/datasource/tempo/metricsSummary.ts deleted file mode 100644 index 9aebb32853c..00000000000 --- a/public/app/plugins/datasource/tempo/metricsSummary.ts +++ /dev/null @@ -1,273 +0,0 @@ -import { - createDataFrame, - DataSourceInstanceSettings, - FieldDTO, - FieldType, - MutableDataFrame, - sortDataFrame, -} from '@grafana/data'; - -export type MetricsSummary = { - spanCount: string; - errorSpanCount?: string; - p50: string; - p90: string; - p95: string; - p99: string; - series: Series[]; -}; - -type Series = { - key: string; - value: { - type: number; - n?: number; - f?: number; - s?: string; - b?: string; - d?: string; - status?: number; - kind?: number; - }; -}; - -type MetricsData = { - spanCount: number; - errorPercentage: number | string; - p50: number; - p90: number; - p95: number; - p99: number; - [key: string]: string | number; -}; - -export function createTableFrameFromMetricsSummaryQuery( - data: MetricsSummary[], - targetQuery: string, - instanceSettings: DataSourceInstanceSettings -) { - let frame; - - if (!data.length) { - return emptyResponse; - } - - const dynamicMetrics: Record = {}; - data.forEach((res: MetricsSummary) => { - const configQuery = getConfigQuery(res.series, targetQuery); - res.series.forEach((series: Series) => { - dynamicMetrics[series.key] = { - name: `${series.key}`, - type: FieldType.string, - config: getConfig(series, configQuery, instanceSettings), - values: [], - }; - }); - }); - - frame = createDataFrame({ - name: 'Metrics Summary', - refId: 'metrics-summary', - fields: [ - ...Object.values(dynamicMetrics).sort((a, b) => a.name.localeCompare(b.name)), - { - name: 'spanCount', - type: FieldType.number, - config: { displayNameFromDS: 'Span count', custom: { width: 150 } }, - }, - { - name: 'errorPercentage', - type: FieldType.number, - config: { displayNameFromDS: 'Error', unit: 'percent', custom: { width: 150 } }, - }, - getPercentileRow('p50'), - getPercentileRow('p90'), - getPercentileRow('p95'), - getPercentileRow('p99'), - ], - meta: { - preferredVisualisationType: 'table', - }, - }); - - const metricsData = data.map(transformToMetricsData); - frame.length = metricsData.length; - for (const trace of metricsData) { - for (const field of frame.fields) { - field.values.push(trace[field.name]); - } - } - frame = sortDataFrame(frame, 0); - - return [frame]; -} - -export const transformToMetricsData = (data: MetricsSummary) => { - const errorPercentage = data.errorSpanCount - ? (getNumberForMetric(data.errorSpanCount) / getNumberForMetric(data.spanCount)) * 100 - : '0%'; - - const metricsData: MetricsData = { - spanCount: getNumberForMetric(data.spanCount), - errorPercentage, - p50: getNumberForMetric(data.p50), - p90: getNumberForMetric(data.p90), - p95: getNumberForMetric(data.p95), - p99: getNumberForMetric(data.p99), - }; - - data.series.forEach((series: Series) => { - metricsData[`${series.key}`] = getMetricValue(series) || ''; - }); - - return metricsData; -}; - -export const getConfigQuery = (series: Series[], targetQuery: string) => { - const queryParts = series.map((x: Series) => { - const isNumber = x.value.type === 3 || x.value.type === 4; - const isIntrinsic = x.value.type === 8 || x.value.type === 9; - const surround = isNumber || isIntrinsic ? '' : '"'; - return `${x.key}=${surround}` + '${__data.fields["' + x.key + '"]}' + `${surround}`; - }); - - let configQuery = ''; - const closingBracketIndex = targetQuery.indexOf('}'); - - if (closingBracketIndex !== -1) { - const queryAfterClosingBracket = targetQuery.substring(closingBracketIndex + 1); - configQuery = targetQuery.substring(0, closingBracketIndex); - if (queryParts.length > 0) { - configQuery += targetQuery.replace(/\s/g, '').includes('{}') ? '' : ' && '; - configQuery += `${queryParts.join(' && ')}`; - configQuery += `}`; - } - configQuery += `${queryAfterClosingBracket}`; - } else { - configQuery = `{${queryParts.join(' && ')}} | ${targetQuery}`; - } - - return configQuery; -}; - -const getConfig = (series: Series, query: string, instanceSettings: DataSourceInstanceSettings) => { - const commonConfig = { - displayNameFromDS: series.key, - noValue: '', - links: [ - { - title: 'Query in explore', - url: '', - internal: { - datasourceUid: instanceSettings.uid, - datasourceName: instanceSettings.name, - query: { - query, - queryType: 'traceql', - }, - }, - }, - ], - }; - - if (series.value.type === 7) { - return { - ...commonConfig, - unit: 'ns', - }; - } - return { ...commonConfig }; -}; - -const NO_VALUE = ''; - -const getMetricValue = (series: Series) => { - if (!series.value.type) { - return NO_VALUE; - } - - switch (series.value.type) { - case 3: - return series.value.n; - case 4: - return series.value.f; - case 5: - return series.value.s; - case 6: - return series.value.b; - case 7: - return series.value.d; - case 8: - return getSpanStatusCode(series.value.status); - case 9: - return getSpanKind(series.value.kind); - default: - return NO_VALUE; - } -}; - -// Values set according to Tempo enum: https://github.com/grafana/tempo/blob/main/pkg/traceql/enum_statics.go -const getSpanStatusCode = (statusCode: number | undefined) => { - if (!statusCode) { - return NO_VALUE; - } - - switch (statusCode) { - case 0: - return 'error'; - case 1: - return 'ok'; - default: - return 'unset'; - } -}; - -// Values set according to Tempo enum: https://github.com/grafana/tempo/blob/main/pkg/traceql/enum_statics.go -const getSpanKind = (kind: number | undefined) => { - if (!kind) { - return NO_VALUE; - } - - switch (kind) { - case 1: - return 'internal'; - case 2: - return 'client'; - case 3: - return 'server'; - case 4: - return 'producer'; - case 5: - return 'consumer'; - default: - return 'unspecified'; - } -}; - -const getPercentileRow = (name: string) => { - return { - name: name, - type: FieldType.number, - config: { - displayNameFromDS: name, - unit: 'ns', - custom: { - width: 150, - }, - }, - }; -}; - -const getNumberForMetric = (metric: string) => { - const number = parseInt(metric, 10); - return isNaN(number) ? 0 : number; -}; - -export const emptyResponse = new MutableDataFrame({ - name: 'Metrics Summary', - refId: 'metrics-summary', - fields: [], - meta: { - preferredVisualisationType: 'table', - }, -}); diff --git a/public/app/plugins/datasource/tempo/package.json b/public/app/plugins/datasource/tempo/package.json index 619a81e8f81..5db7a835310 100644 --- a/public/app/plugins/datasource/tempo/package.json +++ b/public/app/plugins/datasource/tempo/package.json @@ -2,7 +2,7 @@ "name": "@grafana-plugins/tempo", "description": "Grafana plugin for the Tempo data source.", "private": true, - "version": "11.6.0-pre", + "version": "12.0.0-pre", "dependencies": { "@emotion/css": "11.13.5", "@grafana/data": "workspace:*", @@ -39,7 +39,7 @@ "uuid": "11.0.5" }, "devDependencies": { - "@grafana/plugin-configs": "11.6.0-pre", + "@grafana/plugin-configs": "12.0.0-pre", "@testing-library/dom": "10.4.0", "@testing-library/jest-dom": "6.6.3", "@testing-library/react": "16.2.0", diff --git a/public/app/plugins/datasource/tempo/utils.ts b/public/app/plugins/datasource/tempo/utils.ts index 8bc5c2addd5..5c772239ff5 100644 --- a/public/app/plugins/datasource/tempo/utils.ts +++ b/public/app/plugins/datasource/tempo/utils.ts @@ -91,7 +91,6 @@ export const migrateFromSearchToTraceQLSearch = (query: TempoQuery) => { const migratedQuery: TempoQuery = { datasource: query.datasource, filters, - groupBy: query.groupBy, limit: query.limit, query: query.query, queryType: 'traceqlSearch', diff --git a/public/app/plugins/datasource/zipkin/package.json b/public/app/plugins/datasource/zipkin/package.json index b40fb1cf8ec..04eb32a75c0 100644 --- a/public/app/plugins/datasource/zipkin/package.json +++ b/public/app/plugins/datasource/zipkin/package.json @@ -2,7 +2,7 @@ "name": "@grafana-plugins/zipkin", "description": "Zipkin plugin for Grafana", "private": true, - "version": "11.6.0-pre", + "version": "12.0.0-pre", "dependencies": { "@emotion/css": "11.13.5", "@grafana/data": "workspace:*", diff --git a/public/app/plugins/panel/barchart/utils.ts b/public/app/plugins/panel/barchart/utils.ts index 9b25e678f1f..79d9629b11b 100644 --- a/public/app/plugins/panel/barchart/utils.ts +++ b/public/app/plugins/panel/barchart/utils.ts @@ -247,7 +247,7 @@ export const prepConfig = ({ series, totalSeries, color, orientation, options, t // use opacity from first numeric field let opacityField = frame.fields.find((f) => f.type === FieldType.number)!; - fillOpacity = (opacityField.config.custom.fillOpacity ?? 100) / 100; + fillOpacity = (opacityField?.config?.custom?.fillOpacity ?? 100) / 100; getColor = (seriesIdx: number, valueIdx: number) => { let field = frame.fields[seriesIdx]; diff --git a/public/app/plugins/panel/live/LiveChannelEditor.tsx b/public/app/plugins/panel/live/LiveChannelEditor.tsx index d5ba8f45fe1..3276992eb37 100644 --- a/public/app/plugins/panel/live/LiveChannelEditor.tsx +++ b/public/app/plugins/panel/live/LiveChannelEditor.tsx @@ -9,8 +9,9 @@ import { GrafanaTheme2, parseLiveChannelAddress, } from '@grafana/data'; -import { Select, Alert, Label, stylesFactory } from '@grafana/ui'; +import { Select, Alert, Label, stylesFactory, Combobox } from '@grafana/ui'; import { config } from 'app/core/config'; +import { discoveryResources, getAPIGroupDiscoveryList, GroupDiscoveryResource } from 'app/features/apiserver/discovery'; import { getManagedChannelInfo } from 'app/features/live/info'; import { LivePanelOptions } from './types'; @@ -22,6 +23,7 @@ const scopes: Array> = [ { label: 'Data Sources', value: LiveChannelScope.DataSource, description: 'Data sources with live support' }, { label: 'Plugins', value: LiveChannelScope.Plugin, description: 'Plugins with live support' }, { label: 'Stream', value: LiveChannelScope.Stream, description: 'data streams (eg, influx style)' }, + { label: 'Watch', value: LiveChannelScope.Watch, description: 'Watch k8s style resources' }, ]; export function LiveChannelEditor(props: Props) { @@ -93,6 +95,16 @@ export function LiveChannelEditor(props: Props) { }); }; + const getWatchableResources = async (v: string) => { + const apis = await getAPIGroupDiscoveryList(); + return discoveryResources(apis) + .filter((v) => v.verbs.includes('watch')) + .map((r) => ({ + value: `${r.responseKind.group}/${r.responseKind.version}/${r.resource}`, // must be string | number + resource: r, + })); + }; + const { scope, namespace, path } = props.value; const style = getStyles(config.theme2); @@ -109,6 +121,25 @@ export function LiveChannelEditor(props: Props) {