mirror of https://github.com/grafana/grafana.git
Secrets: make operations sync (#107732)
* Secrets: make operations sync * k8s gen / update query to list secure values to include the version * always store new version of a secret * make update-workspace * go mod tidy * update queries * update queries * improve and use testutils in decrypt_store_test * fix broken test * make update-workspace * ./hack/update-codegen.sh secret * update Test_SecureValueMetadataStorage_CreateAndRead * undo dependency changes * linter: fix remaining errors --------- Co-authored-by: Matheus Macabu <macabu.matheus@gmail.com> Co-authored-by: Matheus Macabu <macabu@users.noreply.github.com>
This commit is contained in:
parent
ded7912ea3
commit
8283d35e56
|
@ -9,88 +9,36 @@ require (
|
|||
google.golang.org/protobuf v1.36.6
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
k8s.io/apimachinery v0.33.2
|
||||
k8s.io/apiserver v0.33.2
|
||||
k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff
|
||||
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/blang/semver/v4 v4.0.0 // indirect
|
||||
github.com/cenkalti/backoff/v5 v5.0.2 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/coreos/go-semver v0.3.1 // indirect
|
||||
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
|
||||
github.com/go-logr/logr v1.4.2 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.21.0 // indirect
|
||||
github.com/go-openapi/jsonreference v0.21.0 // indirect
|
||||
github.com/go-openapi/swag v0.23.0 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang/protobuf v1.5.4 // indirect
|
||||
github.com/google/gnostic-models v0.6.9 // indirect
|
||||
github.com/google/go-cmp v0.7.0 // indirect
|
||||
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.1-0.20191002090509-6af20e3a5340 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect
|
||||
github.com/jonboulle/clockwork v0.5.0 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/kylelemons/godebug v1.1.0 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/onsi/ginkgo/v2 v2.22.2 // indirect
|
||||
github.com/onsi/gomega v1.36.2 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/prometheus/client_golang v1.22.0 // indirect
|
||||
github.com/prometheus/client_model v0.6.2 // indirect
|
||||
github.com/prometheus/common v0.64.0 // indirect
|
||||
github.com/prometheus/procfs v0.16.1 // indirect
|
||||
github.com/spf13/pflag v1.0.6 // indirect
|
||||
github.com/stretchr/objx v0.5.2 // indirect
|
||||
github.com/x448/float16 v0.8.4 // indirect
|
||||
go.etcd.io/bbolt v1.4.0 // indirect
|
||||
go.etcd.io/etcd/api/v3 v3.5.21 // indirect
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.5.21 // indirect
|
||||
go.etcd.io/etcd/client/v3 v3.5.21 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect
|
||||
go.opentelemetry.io/otel v1.36.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.36.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.36.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.36.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.36.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.36.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v1.6.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go.uber.org/zap v1.27.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk/metric v1.36.0 // indirect
|
||||
golang.org/x/net v0.41.0 // indirect
|
||||
golang.org/x/oauth2 v0.30.0 // indirect
|
||||
golang.org/x/sys v0.33.0 // indirect
|
||||
golang.org/x/term v0.32.0 // indirect
|
||||
golang.org/x/text v0.26.0 // indirect
|
||||
golang.org/x/time v0.11.0 // indirect
|
||||
golang.org/x/tools v0.34.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20250303144028-a0af3efb3deb // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237 // indirect
|
||||
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
k8s.io/api v0.33.2 // indirect
|
||||
k8s.io/client-go v0.33.2 // indirect
|
||||
k8s.io/component-base v0.33.2 // indirect
|
||||
k8s.io/klog/v2 v2.130.1 // indirect
|
||||
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 // indirect
|
||||
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect
|
||||
sigs.k8s.io/randfill v1.0.0 // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.6.0 // indirect
|
||||
|
|
|
@ -1,41 +1,11 @@
|
|||
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/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.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM=
|
||||
github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ=
|
||||
github.com/cenkalti/backoff/v5 v5.0.2 h1:rIfFVxEf1QsI7E1ZHfp/B4DF/6QBAUhmgkxc0H7Zss8=
|
||||
github.com/cenkalti/backoff/v5 v5.0.2/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
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/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/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4=
|
||||
github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec=
|
||||
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
|
||||
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
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/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g=
|
||||
github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
|
||||
github.com/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/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
|
||||
github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
|
||||
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
|
||||
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
|
||||
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=
|
||||
|
@ -46,156 +16,58 @@ github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF
|
|||
github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4=
|
||||
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
|
||||
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
|
||||
github.com/go-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/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI=
|
||||
github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||
github.com/golang/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.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
|
||||
github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
|
||||
github.com/google/gnostic-models v0.6.9 h1:MU/8wDLif2qCXZmzncUQ/BOfxWfthHi63KqpoNbWqVw=
|
||||
github.com/google/gnostic-models v0.6.9/go.mod h1:CiWsm0s6BSQd1hRn8/QmxqB6BesYcbSZxsz9b0KuDBw=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
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/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8=
|
||||
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
|
||||
github.com/google/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/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo=
|
||||
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA=
|
||||
github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250514132646-acbc7b54ed9e h1:BTKk7LHuG1kmAkucwTA7DuMbKpKvJTKrGdBmUNO4dfQ=
|
||||
github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250514132646-acbc7b54ed9e/go.mod h1:IA4SOwun8QyST9c5UNs/fN37XL6boXXDvRYFcFwbipg=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8=
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.1-0.20191002090509-6af20e3a5340 h1:uGoIog/wiQHI9GAxXO5TJbT0wWKH3O9HhOJW1F9c3fY=
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.1-0.20191002090509-6af20e3a5340/go.mod h1:3bDW6wMZJB7tiONtC/1Xpicra6Wp5GgbTbQWCbI5fkc=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI=
|
||||
github.com/jonboulle/clockwork v0.5.0 h1:Hyh9A8u51kptdkR+cqRpT1EebBwTn1oK9YfGYbdFz6I=
|
||||
github.com/jonboulle/clockwork v0.5.0/go.mod h1:3mZlmanh0g2NDKO5TWZVJAfofYk64M7XN3SzBPjZF60=
|
||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
||||
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
||||
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/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/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.22.2 h1:/3X8Panh8/WwhU/3Ssa6rCKqPLuAkVY2I0RoyDLySlU=
|
||||
github.com/onsi/ginkgo/v2 v2.22.2/go.mod h1:oeMosUL+8LtarXBHu/c0bx2D/K9zyQ6uX3cTyztHwsk=
|
||||
github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8=
|
||||
github.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY=
|
||||
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
||||
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_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM=
|
||||
github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q=
|
||||
github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0=
|
||||
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.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
|
||||
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
|
||||
github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||
github.com/prometheus/common v0.64.0 h1:pdZeA+g617P7oGv1CzdTzyeShxAGrTBsolKNOLQPGO4=
|
||||
github.com/prometheus/common v0.64.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8=
|
||||
github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=
|
||||
github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
|
||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||
github.com/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/soheilhy/cmux v0.1.5 h1:jjzc5WVemNEDTLwv9tlmemhC73tI08BNOIGwBOo10Js=
|
||||
github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0=
|
||||
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
|
||||
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
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.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.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
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/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75 h1:6fotK7otjonDflCTK0BCfls4SPy3NcCVb5dqqmbRknE=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75/go.mod h1:KO6IkyS8Y3j8OdNO85qEYBsRPuteD+YciPomcXdrMnk=
|
||||
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
||||
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
|
||||
github.com/xiang90/probing v0.0.0-20221125231312-a49e3df8f510 h1:S2dVYn90KE98chqDkyE9Z4N61UnQd+KOfgp5Iu53llk=
|
||||
github.com/xiang90/probing v0.0.0-20221125231312-a49e3df8f510/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
go.etcd.io/bbolt v1.4.0 h1:TU77id3TnN/zKr7CO/uk+fBCwF2jGcMuw2B/FMAzYIk=
|
||||
go.etcd.io/bbolt v1.4.0/go.mod h1:AsD+OCi/qPN1giOX1aiLAha3o1U8rAz65bvN4j0sRuk=
|
||||
go.etcd.io/etcd/api/v3 v3.5.21 h1:A6O2/JDb3tvHhiIz3xf9nJ7REHvtEFJJ3veW3FbCnS8=
|
||||
go.etcd.io/etcd/api/v3 v3.5.21/go.mod h1:c3aH5wcvXv/9dqIw2Y810LDXJfhSYdHQ0vxmP3CCHVY=
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.5.21 h1:lPBu71Y7osQmzlflM9OfeIV2JlmpBjqBNlLtcoBqUTc=
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.5.21/go.mod h1:BgqT/IXPjK9NkeSDjbzwsHySX3yIle2+ndz28nVsjUs=
|
||||
go.etcd.io/etcd/client/v2 v2.305.21 h1:eLiFfexc2mE+pTLz9WwnoEsX5JTTpLCYVivKkmVXIRA=
|
||||
go.etcd.io/etcd/client/v2 v2.305.21/go.mod h1:OKkn4hlYNf43hpjEM3Ke3aRdUkhSl8xjKjSf8eCq2J8=
|
||||
go.etcd.io/etcd/client/v3 v3.5.21 h1:T6b1Ow6fNjOLOtM0xSoKNQt1ASPCLWrF9XMHcH9pEyY=
|
||||
go.etcd.io/etcd/client/v3 v3.5.21/go.mod h1:mFYy67IOqmbRf/kRUvsHixzo3iG+1OF2W2+jVIQRAnU=
|
||||
go.etcd.io/etcd/pkg/v3 v3.5.21 h1:jUItxeKyrDuVuWhdh0HtjUANwyuzcb7/FAeUfABmQsk=
|
||||
go.etcd.io/etcd/pkg/v3 v3.5.21/go.mod h1:wpZx8Egv1g4y+N7JAsqi2zoUiBIUWznLjqJbylDjWgU=
|
||||
go.etcd.io/etcd/raft/v3 v3.5.21 h1:dOmE0mT55dIUsX77TKBLq+RgyumsQuYeiRQnW/ylugk=
|
||||
go.etcd.io/etcd/raft/v3 v3.5.21/go.mod h1:fmcuY5R2SNkklU4+fKVBQi2biVp5vafMrWUEj4TJ4Cs=
|
||||
go.etcd.io/etcd/server/v3 v3.5.21 h1:9w0/k12majtgarGmlMVuhwXRI2ob3/d1Ik3X5TKo0yU=
|
||||
go.etcd.io/etcd/server/v3 v3.5.21/go.mod h1:G1mOzdwuzKT1VRL7SqRchli/qcFrtLBTAQ4lV20sXXo=
|
||||
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/google.golang.org/grpc/otelgrpc v0.60.0 h1:x7wzEgXfnzJcHDwStJT+mxOz4etr2EcexjqhBvmoakw=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0/go.mod h1:rg+RlpR5dKwaS95IyyZqj5Wd4E13lk/msnTS0Xl9lJM=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q=
|
||||
go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg=
|
||||
go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.36.0 h1:dNzwXjZKpMpE2JhmO+9HsPl42NIXFIFSUSSs0fiqra0=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.36.0/go.mod h1:90PoxvaEB5n6AOdZvi+yWJQoE95U8Dhhw2bSyRqnTD0=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.36.0 h1:JgtbA0xkWHnTmYk7YusopJFX6uleBmAuZ8n05NEh8nQ=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.36.0/go.mod h1:179AK5aar5R3eS9FucPy6rggvU0g52cvKId8pv4+v0c=
|
||||
go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE=
|
||||
go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs=
|
||||
go.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs=
|
||||
|
@ -204,139 +76,58 @@ go.opentelemetry.io/otel/sdk/metric v1.36.0 h1:r0ntwwGosWGaa0CrSt8cuNuTcccMXERFw
|
|||
go.opentelemetry.io/otel/sdk/metric v1.36.0/go.mod h1:qTNOhFDfKRwX0yXOqJYegL5WRaW376QbB7P4Pb0qva4=
|
||||
go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w=
|
||||
go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA=
|
||||
go.opentelemetry.io/proto/otlp v1.6.0 h1:jQjP+AQyTf+Fe7OKj/MfkDrmK4MNVtw2NpXsf9fefDI=
|
||||
go.opentelemetry.io/proto/otlp v1.6.0/go.mod h1:cicgGehlFuNdgZkcALOCh3VE6K/u2tAjzlRhDwmVpZc=
|
||||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=
|
||||
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.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
|
||||
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.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI=
|
||||
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
||||
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
|
||||
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
|
||||
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/lint v0.0.0-20190930215403-16217165b5de/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-20181201002055-351d144fa1fc/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.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
|
||||
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
|
||||
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
|
||||
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/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-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-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
||||
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg=
|
||||
golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ=
|
||||
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.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
|
||||
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
|
||||
golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
|
||||
golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
|
||||
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
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-20191108193012-7d206e10da11/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-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo=
|
||||
golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg=
|
||||
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-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20250303144028-a0af3efb3deb h1:ITgPrl429bc6+2ZraNSzMDk3I95nmQln2fuPstKwFDE=
|
||||
google.golang.org/genproto v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:sAo5UzpjUwgFBCzupwhcLcxHVDK7vG5IqI30YnwX2eE=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237 h1:Kog3KlB4xevJlAcbbbzPfRG0+X9fdoGM+UBRKVz6Wr0=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237/go.mod h1:ezi0AVyMKDWy5xAncvjLWH7UcLBB5n7y2fQ8MzjJcto=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237 h1:cJfm9zPbe1e873mHJzmQ1nwVEeRDU/T1wXDK2kUSU34=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
|
||||
google.golang.org/grpc v1.18.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
|
||||
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.73.0 h1:VIWSmpI2MegBtTuFt5/JWy2oXxtjJ/e89Z70ImfD2ok=
|
||||
google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc=
|
||||
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
|
||||
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4=
|
||||
gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M=
|
||||
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
|
||||
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
|
||||
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.v3 v3.0.0-20200313102051-9f266ea9e77c/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-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
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=
|
||||
k8s.io/api v0.33.2 h1:YgwIS5jKfA+BZg//OQhkJNIfie/kmRsO0BmNaVSimvY=
|
||||
k8s.io/api v0.33.2/go.mod h1:fhrbphQJSM2cXzCWgqU29xLDuks4mu7ti9vveEnpSXs=
|
||||
k8s.io/apimachinery v0.33.2 h1:IHFVhqg59mb8PJWTLi8m1mAoepkUNYmptHsV+Z1m5jY=
|
||||
k8s.io/apimachinery v0.33.2/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM=
|
||||
k8s.io/apiserver v0.33.2 h1:KGTRbxn2wJagJowo29kKBp4TchpO1DRO3g+dB/KOJN4=
|
||||
k8s.io/apiserver v0.33.2/go.mod h1:9qday04wEAMLPWWo9AwqCZSiIn3OYSZacDyu/AcoM/M=
|
||||
k8s.io/client-go v0.33.2 h1:z8CIcc0P581x/J1ZYf4CNzRKxRvQAwoAolYPbtQes+E=
|
||||
k8s.io/client-go v0.33.2/go.mod h1:9mCgT4wROvL948w6f6ArJNb7yQd7QsvqavDeZHvNmHo=
|
||||
k8s.io/component-base v0.33.2 h1:sCCsn9s/dG3ZrQTX/Us0/Sx2R0G5kwa0wbZFYoVp/+0=
|
||||
k8s.io/component-base v0.33.2/go.mod h1:/41uw9wKzuelhN+u+/C59ixxf4tYQKW7p32ddkYNe2k=
|
||||
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
|
||||
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
|
||||
k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff h1:/usPimJzUKKu+m+TE36gUyGcf03XZEP0ZIKgKj35LS4=
|
||||
k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff/go.mod h1:5jIi+8yX4RIb8wk3XwBo5Pq2ccx4FP10ohkbSKCZoK8=
|
||||
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro=
|
||||
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
|
||||
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 h1:jpcvIRr3GLoUoEKRkHKSmGjxb6lWwrBlJsXc+eUYQHM=
|
||||
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw=
|
||||
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8=
|
||||
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo=
|
||||
sigs.k8s.io/randfill v0.0.0-20250304075658-069ef1bbf016/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
|
||||
|
|
|
@ -4,10 +4,8 @@ import (
|
|||
"fmt"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/fields"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apiserver/pkg/registry/generic"
|
||||
|
||||
"github.com/grafana/grafana/pkg/apimachinery/utils"
|
||||
)
|
||||
|
@ -33,7 +31,6 @@ var SecureValuesResourceInfo = utils.NewResourceInfo(
|
|||
{Name: "Description", Type: "string", Format: "string", Description: "Short description that explains the purpose of this SecureValue"},
|
||||
{Name: "Keeper", Type: "string", Format: "string", Description: "Storage of the secure value"},
|
||||
{Name: "Ref", Type: "string", Format: "string", Description: "If present, the reference to a secret"},
|
||||
{Name: "Status", Type: "string", Format: "string", Description: "The status of the secure value"},
|
||||
},
|
||||
// Decodes the object into a concrete type. Return order in the slice must be the same as in `Definition`.
|
||||
Reader: func(obj any) ([]interface{}, error) {
|
||||
|
@ -44,7 +41,6 @@ var SecureValuesResourceInfo = utils.NewResourceInfo(
|
|||
r.Spec.Description,
|
||||
r.Spec.Keeper,
|
||||
r.Spec.Ref,
|
||||
r.Status.Phase,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@ -92,13 +88,6 @@ var (
|
|||
AddToScheme = localSchemeBuilder.AddToScheme
|
||||
)
|
||||
|
||||
// Adds the status phase to the selectable fields, besides the generic metadata name and namespace.
|
||||
func SelectableSecureValueFields(obj *SecureValue) fields.Set {
|
||||
return generic.MergeFieldsSets(generic.ObjectMetaFieldsSet(&obj.ObjectMeta, false), fields.Set{
|
||||
"status.phase": string(obj.Status.Phase),
|
||||
})
|
||||
}
|
||||
|
||||
// Adds the list of known types to the given scheme.
|
||||
func AddKnownTypes(scheme *runtime.Scheme, version string) error {
|
||||
// TODO: do we need a type for the secure value decrypt?
|
||||
|
@ -112,20 +101,5 @@ func AddKnownTypes(scheme *runtime.Scheme, version string) error {
|
|||
// &secretV0.SecureValueActivityList{},
|
||||
)
|
||||
|
||||
err := scheme.AddFieldLabelConversionFunc(
|
||||
SecureValuesResourceInfo.GroupVersionKind(),
|
||||
func(label, value string) (string, string, error) {
|
||||
fieldSet := SelectableSecureValueFields(&SecureValue{})
|
||||
for key := range fieldSet {
|
||||
if label == key {
|
||||
return label, value, nil
|
||||
}
|
||||
}
|
||||
return "", "", fmt.Errorf("field label not supported for %s: %s", SecureValuesResourceInfo.GroupVersionKind(), label)
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -11,41 +11,18 @@ type SecureValue struct {
|
|||
// Standard object's metadata. It can only be one of `metav1.ObjectMeta` or `metav1.ListMeta`.
|
||||
// More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata
|
||||
// +optional
|
||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||
metav1.ObjectMeta `json:"metadata"`
|
||||
|
||||
// This is the actual secure value schema.
|
||||
Spec SecureValueSpec `json:"spec"`
|
||||
|
||||
// Read-only observed status of the `SecureValue`.
|
||||
// More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status
|
||||
Status SecureValueStatus `json:"status,omitempty"`
|
||||
Status SecureValueStatus `json:"status"`
|
||||
}
|
||||
|
||||
// +enum
|
||||
type SecureValuePhase string
|
||||
|
||||
const (
|
||||
// When the `SecureValue` is created, it will start in `Pending` phase to create the underlying secret asynchronously.
|
||||
SecureValuePhasePending SecureValuePhase = "Pending"
|
||||
|
||||
// If the creation of the secret is successful, it will move to the `Succeeded` phase.
|
||||
SecureValuePhaseSucceeded SecureValuePhase = "Succeeded"
|
||||
|
||||
// If the creation of the secret fails, it will move to the `Failed` phase.
|
||||
// Check the additional `status` fields for more information on what caused the failure.
|
||||
// This state is unrecoverable.
|
||||
SecureValuePhaseFailed SecureValuePhase = "Failed"
|
||||
)
|
||||
|
||||
type SecureValueStatus struct {
|
||||
// High-level summary of where the `SecureValue` is in its lifecycle.
|
||||
// One of: `Pending`, `Succeeded` or `Failed`.
|
||||
Phase SecureValuePhase `json:"phase"`
|
||||
|
||||
// A human readable message indicating details about why the `SecureValue` is in this phase.
|
||||
// Only applicable if the `phase=Failed`.
|
||||
// +optional
|
||||
Message string `json:"message,omitempty"`
|
||||
Version int64 `json:"version"`
|
||||
|
||||
// +optional
|
||||
ExternalID string `json:"externalId,omitempty"`
|
||||
|
@ -94,7 +71,7 @@ type SecureValueList struct {
|
|||
// Standard list's metadata. It can only be one of `metav1.ObjectMeta` or `metav1.ListMeta`.
|
||||
// More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata
|
||||
// +optional
|
||||
metav1.ListMeta `json:"metadata,omitempty"`
|
||||
metav1.ListMeta `json:"metadata"`
|
||||
|
||||
// Slice containing all secure values. This will NOT output decrypted values.
|
||||
Items []SecureValue `json:"items"`
|
||||
|
|
|
@ -554,7 +554,7 @@ func schema_pkg_apis_secret_v0alpha1_SecureValue(ref common.ReferenceCallback) c
|
|||
},
|
||||
},
|
||||
},
|
||||
Required: []string{"spec"},
|
||||
Required: []string{"spec", "status"},
|
||||
},
|
||||
},
|
||||
Dependencies: []string{
|
||||
|
@ -690,20 +690,11 @@ func schema_pkg_apis_secret_v0alpha1_SecureValueStatus(ref common.ReferenceCallb
|
|||
SchemaProps: spec.SchemaProps{
|
||||
Type: []string{"object"},
|
||||
Properties: map[string]spec.Schema{
|
||||
"phase": {
|
||||
"version": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "High-level summary of where the `SecureValue` is in its lifecycle. One of: `Pending`, `Succeeded` or `Failed`.\n\nPossible enum values:\n - `\"Failed\"` If the creation of the secret fails, it will move to the `Failed` phase. Check the additional `status` fields for more information on what caused the failure. This state is unrecoverable.\n - `\"Pending\"` When the `SecureValue` is created, it will start in `Pending` phase to create the underlying secret asynchronously.\n - `\"Succeeded\"` If the creation of the secret is successful, it will move to the `Succeeded` phase.",
|
||||
Default: "",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
Enum: []interface{}{"Failed", "Pending", "Succeeded"},
|
||||
},
|
||||
},
|
||||
"message": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "A human readable message indicating details about why the `SecureValue` is in this phase. Only applicable if the `phase=Failed`.",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
Default: 0,
|
||||
Type: []string{"integer"},
|
||||
Format: "int64",
|
||||
},
|
||||
},
|
||||
"externalId": {
|
||||
|
@ -713,7 +704,7 @@ func schema_pkg_apis_secret_v0alpha1_SecureValueStatus(ref common.ReferenceCallb
|
|||
},
|
||||
},
|
||||
},
|
||||
Required: []string{"phase"},
|
||||
Required: []string{"version"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -6,3 +6,4 @@ API rule violation: names_match,github.com/grafana/grafana/pkg/apis/secret/v0alp
|
|||
API rule violation: names_match,github.com/grafana/grafana/pkg/apis/secret/v0alpha1,KeeperSpec,Azure
|
||||
API rule violation: names_match,github.com/grafana/grafana/pkg/apis/secret/v0alpha1,KeeperSpec,HashiCorp
|
||||
API rule violation: names_match,github.com/grafana/grafana/pkg/apis/secret/v0alpha1,SecureValueStatus,ExternalID
|
||||
API rule violation: streaming_list_type_json_tags,github.com/grafana/grafana/pkg/apis/secret/v0alpha1,SecureValueList,ListMeta
|
||||
|
|
|
@ -1,64 +0,0 @@
|
|||
package contracts
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
type contextRequestIdKey struct{}
|
||||
|
||||
type OutboxMessageType string
|
||||
|
||||
func GetRequestId(ctx context.Context) string {
|
||||
v := ctx.Value(contextRequestIdKey{})
|
||||
requestId, ok := v.(string)
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
|
||||
return requestId
|
||||
}
|
||||
|
||||
func ContextWithRequestID(ctx context.Context, requestId string) context.Context {
|
||||
return context.WithValue(ctx, contextRequestIdKey{}, requestId)
|
||||
}
|
||||
|
||||
const (
|
||||
CreateSecretOutboxMessage OutboxMessageType = "create"
|
||||
UpdateSecretOutboxMessage OutboxMessageType = "update"
|
||||
DeleteSecretOutboxMessage OutboxMessageType = "delete"
|
||||
)
|
||||
|
||||
type AppendOutboxMessage struct {
|
||||
RequestID string
|
||||
Type OutboxMessageType
|
||||
Name string
|
||||
Namespace string
|
||||
EncryptedSecret string
|
||||
KeeperName *string
|
||||
ExternalID *string
|
||||
}
|
||||
|
||||
type OutboxMessage struct {
|
||||
RequestID string
|
||||
Type OutboxMessageType
|
||||
MessageID int64
|
||||
Name string
|
||||
Namespace string
|
||||
EncryptedSecret string
|
||||
KeeperName *string
|
||||
ExternalID *string
|
||||
// How many times this message has been received
|
||||
ReceiveCount int
|
||||
Created int64
|
||||
}
|
||||
|
||||
type OutboxQueue interface {
|
||||
// Appends a message to the outbox queue
|
||||
Append(ctx context.Context, message AppendOutboxMessage) (int64, error)
|
||||
// Receives at most n messages from the outbox queue
|
||||
ReceiveN(ctx context.Context, n uint) ([]OutboxMessage, error)
|
||||
// Deletes a message from the outbox queue
|
||||
Delete(ctx context.Context, messageID int64) error
|
||||
// Increments the number of times each message has been received by 1. Must be atomic.
|
||||
IncrementReceiveCount(ctx context.Context, messageIDs []int64) error
|
||||
}
|
|
@ -32,10 +32,9 @@ type ReadOpts struct {
|
|||
type SecureValueMetadataStorage interface {
|
||||
Create(ctx context.Context, sv *secretv0alpha1.SecureValue, actorUID string) (*secretv0alpha1.SecureValue, error)
|
||||
Read(ctx context.Context, namespace xkube.Namespace, name string, opts ReadOpts) (*secretv0alpha1.SecureValue, error)
|
||||
Update(ctx context.Context, sv *secretv0alpha1.SecureValue, actorUID string) (*secretv0alpha1.SecureValue, error)
|
||||
Delete(ctx context.Context, namespace xkube.Namespace, name string) error
|
||||
List(ctx context.Context, namespace xkube.Namespace) ([]secretv0alpha1.SecureValue, error)
|
||||
SetStatus(ctx context.Context, namespace xkube.Namespace, name string, status secretv0alpha1.SecureValueStatus) error
|
||||
SetExternalID(ctx context.Context, namespace xkube.Namespace, name string, externalID ExternalID) error
|
||||
SetVersionToActive(ctx context.Context, namespace xkube.Namespace, name string, version int64) error
|
||||
SetVersionToInactive(ctx context.Context, namespace xkube.Namespace, name string, version int64) error
|
||||
SetExternalID(ctx context.Context, namespace xkube.Namespace, name string, version int64, externalID ExternalID) error
|
||||
ReadForDecrypt(ctx context.Context, namespace xkube.Namespace, name string) (*DecryptSecureValue, error)
|
||||
}
|
||||
|
|
|
@ -5,10 +5,10 @@ import (
|
|||
"fmt"
|
||||
|
||||
claims "github.com/grafana/authlib/types"
|
||||
"github.com/grafana/grafana-app-sdk/logging"
|
||||
"github.com/grafana/grafana/pkg/apimachinery/utils"
|
||||
secretv0alpha1 "github.com/grafana/grafana/pkg/apis/secret/v0alpha1"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/secret/contracts"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/secret/tracectx"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/secret/xkube"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
|
@ -19,8 +19,8 @@ type SecureValueService struct {
|
|||
accessClient claims.AccessClient
|
||||
database contracts.Database
|
||||
secureValueMetadataStorage contracts.SecureValueMetadataStorage
|
||||
outboxQueue contracts.OutboxQueue
|
||||
encryptionManager contracts.EncryptionManager
|
||||
keeperMetadataStorage contracts.KeeperMetadataStorage
|
||||
keeperService contracts.KeeperService
|
||||
}
|
||||
|
||||
func ProvideSecureValueService(
|
||||
|
@ -28,16 +28,16 @@ func ProvideSecureValueService(
|
|||
accessClient claims.AccessClient,
|
||||
database contracts.Database,
|
||||
secureValueMetadataStorage contracts.SecureValueMetadataStorage,
|
||||
outboxQueue contracts.OutboxQueue,
|
||||
encryptionManager contracts.EncryptionManager,
|
||||
keeperMetadataStorage contracts.KeeperMetadataStorage,
|
||||
keeperService contracts.KeeperService,
|
||||
) *SecureValueService {
|
||||
return &SecureValueService{
|
||||
tracer: tracer,
|
||||
accessClient: accessClient,
|
||||
database: database,
|
||||
secureValueMetadataStorage: secureValueMetadataStorage,
|
||||
outboxQueue: outboxQueue,
|
||||
encryptionManager: encryptionManager,
|
||||
keeperMetadataStorage: keeperMetadataStorage,
|
||||
keeperService: keeperService,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -48,43 +48,91 @@ func (s *SecureValueService) Create(ctx context.Context, sv *secretv0alpha1.Secu
|
|||
attribute.String("actor", actorUID),
|
||||
))
|
||||
defer span.End()
|
||||
return s.createNewVersion(ctx, sv, actorUID)
|
||||
}
|
||||
|
||||
sv.Status = secretv0alpha1.SecureValueStatus{Phase: secretv0alpha1.SecureValuePhasePending, Message: "Creating secure value"}
|
||||
func (s *SecureValueService) Update(ctx context.Context, newSecureValue *secretv0alpha1.SecureValue, actorUID string) (*secretv0alpha1.SecureValue, bool, error) {
|
||||
ctx, span := s.tracer.Start(ctx, "SecureValueService.Update", trace.WithAttributes(
|
||||
attribute.String("name", newSecureValue.GetName()),
|
||||
attribute.String("namespace", newSecureValue.GetNamespace()),
|
||||
attribute.String("actor", actorUID),
|
||||
))
|
||||
defer span.End()
|
||||
|
||||
var out *secretv0alpha1.SecureValue
|
||||
|
||||
encryptedSecret, err := s.encryptionManager.Encrypt(ctx, sv.Namespace, []byte(sv.Spec.Value.DangerouslyExposeAndConsumeValue()))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("encrypting secure value secret: %w", err)
|
||||
}
|
||||
|
||||
// Specifically here so that the spans from the worker are not inside the transaction.
|
||||
requestID := tracectx.HexEncodeTraceFromContext(ctx)
|
||||
|
||||
if err := s.database.Transaction(ctx, func(ctx context.Context) error {
|
||||
createdSecureValue, err := s.secureValueMetadataStorage.Create(ctx, sv, actorUID)
|
||||
if newSecureValue.Spec.Value == "" {
|
||||
decrypted, err := s.secureValueMetadataStorage.ReadForDecrypt(ctx, xkube.Namespace(newSecureValue.Namespace), newSecureValue.Name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create securevalue: %w", err)
|
||||
}
|
||||
out = createdSecureValue
|
||||
|
||||
if _, err := s.outboxQueue.Append(ctx, contracts.AppendOutboxMessage{
|
||||
RequestID: requestID,
|
||||
Type: contracts.CreateSecretOutboxMessage,
|
||||
Name: sv.Name,
|
||||
Namespace: sv.Namespace,
|
||||
EncryptedSecret: string(encryptedSecret),
|
||||
KeeperName: sv.Spec.Keeper,
|
||||
}); err != nil {
|
||||
return fmt.Errorf("failed to append message to create secure value to outbox queue: %w", err)
|
||||
return nil, false, fmt.Errorf("reading secure value secret: %+w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}); err != nil {
|
||||
return out, err
|
||||
// TODO: does this need to be for update?
|
||||
keeperCfg, err := s.keeperMetadataStorage.GetKeeperConfig(ctx, newSecureValue.Namespace, newSecureValue.Spec.Keeper, contracts.ReadOpts{ForUpdate: true})
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf("fetching keeper config: namespace=%+v keeperName=%+v %w", newSecureValue.Namespace, newSecureValue.Spec.Keeper, err)
|
||||
}
|
||||
|
||||
keeper, err := s.keeperService.KeeperForConfig(keeperCfg)
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf("getting keeper for config: namespace=%+v keeperName=%+v %w", newSecureValue.Namespace, newSecureValue.Spec.Keeper, err)
|
||||
}
|
||||
logging.FromContext(ctx).Debug("retrieved keeper", "namespace", newSecureValue.Namespace, "keeperName", newSecureValue.Spec.Keeper, "type", keeperCfg.Type())
|
||||
|
||||
secret, err := keeper.Expose(ctx, keeperCfg, newSecureValue.Namespace, contracts.ExternalID(decrypted.ExternalID))
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf("reading secret value from keeper: %w", err)
|
||||
}
|
||||
|
||||
newSecureValue.Spec.Value = secret
|
||||
}
|
||||
|
||||
return out, nil
|
||||
const updateIsSync = true
|
||||
createdSv, err := s.createNewVersion(ctx, newSecureValue, actorUID)
|
||||
return createdSv, updateIsSync, err
|
||||
}
|
||||
|
||||
func (s *SecureValueService) createNewVersion(ctx context.Context, sv *secretv0alpha1.SecureValue, actorUID string) (*secretv0alpha1.SecureValue, error) {
|
||||
createdSv, err := s.secureValueMetadataStorage.Create(ctx, sv, actorUID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("creating secure value: %w", err)
|
||||
}
|
||||
createdSv.Status = secretv0alpha1.SecureValueStatus{
|
||||
Version: createdSv.Status.Version,
|
||||
}
|
||||
|
||||
// TODO: does this need to be for update?
|
||||
keeperCfg, err := s.keeperMetadataStorage.GetKeeperConfig(ctx, sv.Namespace, sv.Spec.Keeper, contracts.ReadOpts{ForUpdate: true})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("fetching keeper config: namespace=%+v keeperName=%+v %w", sv.Namespace, sv.Spec.Keeper, err)
|
||||
}
|
||||
|
||||
keeper, err := s.keeperService.KeeperForConfig(keeperCfg)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("getting keeper for config: namespace=%+v keeperName=%+v %w", sv.Namespace, sv.Spec.Keeper, err)
|
||||
}
|
||||
logging.FromContext(ctx).Debug("retrieved keeper", "namespace", sv.Namespace, "keeperName", sv.Spec.Keeper, "type", keeperCfg.Type())
|
||||
|
||||
// TODO: can we stop using external id?
|
||||
// TODO: store uses only the namespace and returns and id. It could be a kv instead.
|
||||
// TODO: check that the encrypted store works with multiple versions
|
||||
externalID, err := keeper.Store(ctx, keeperCfg, sv.Namespace, sv.Spec.Value.DangerouslyExposeAndConsumeValue())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("storing secure value in keeper: %w", err)
|
||||
}
|
||||
createdSv.Status.ExternalID = string(externalID)
|
||||
|
||||
if err := s.secureValueMetadataStorage.SetExternalID(ctx, xkube.Namespace(sv.Namespace), sv.Name, createdSv.Status.Version, externalID); err != nil {
|
||||
return nil, fmt.Errorf("setting secure value external id: %w", err)
|
||||
}
|
||||
|
||||
if err := s.secureValueMetadataStorage.SetVersionToActive(ctx, xkube.Namespace(sv.Namespace), sv.Name, createdSv.Status.Version); err != nil {
|
||||
return nil, fmt.Errorf("marking secure value version as active: %w", err)
|
||||
}
|
||||
|
||||
// In a single query:
|
||||
// TODO: set external id
|
||||
// TODO: set to active
|
||||
|
||||
return createdSv, nil
|
||||
}
|
||||
|
||||
func (s *SecureValueService) Read(ctx context.Context, namespace xkube.Namespace, name string) (*secretv0alpha1.SecureValue, error) {
|
||||
|
@ -139,84 +187,6 @@ func (s *SecureValueService) List(ctx context.Context, namespace xkube.Namespace
|
|||
}, nil
|
||||
}
|
||||
|
||||
func (s *SecureValueService) Update(ctx context.Context, newSecureValue *secretv0alpha1.SecureValue, actorUID string) (*secretv0alpha1.SecureValue, bool, error) {
|
||||
ctx, span := s.tracer.Start(ctx, "SecureValueService.Create", trace.WithAttributes(
|
||||
attribute.String("name", newSecureValue.GetName()),
|
||||
attribute.String("namespace", newSecureValue.GetNamespace()),
|
||||
attribute.String("actor", actorUID),
|
||||
))
|
||||
defer span.End()
|
||||
|
||||
// True when the effects of an update can be seen immediately.
|
||||
// Never true in this case since updating a secure value is async.
|
||||
const updateIsSync = false
|
||||
|
||||
var (
|
||||
out *secretv0alpha1.SecureValue
|
||||
encryptedSecret string
|
||||
)
|
||||
|
||||
if newSecureValue.Spec.Value != "" {
|
||||
buffer, err := s.encryptionManager.Encrypt(ctx, newSecureValue.Namespace, []byte(newSecureValue.Spec.Value.DangerouslyExposeAndConsumeValue()))
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf("encrypting secure value secret: %w", err)
|
||||
}
|
||||
encryptedSecret = string(buffer)
|
||||
}
|
||||
|
||||
// Especifically here so that the spans from the worker are not inside the transaction.
|
||||
requestID := tracectx.HexEncodeTraceFromContext(ctx)
|
||||
|
||||
if err := s.database.Transaction(ctx, func(ctx context.Context) error {
|
||||
sv, err := s.secureValueMetadataStorage.Read(ctx, xkube.Namespace(newSecureValue.Namespace), newSecureValue.Name, contracts.ReadOpts{ForUpdate: true})
|
||||
if err != nil {
|
||||
return fmt.Errorf("fetching secure value: %+w", err)
|
||||
}
|
||||
|
||||
if sv.Status.Phase == secretv0alpha1.SecureValuePhasePending {
|
||||
return contracts.ErrSecureValueOperationInProgress
|
||||
}
|
||||
|
||||
// Succeed immediately if the value is not going to be updated
|
||||
if encryptedSecret == "" {
|
||||
newSecureValue.Status = secretv0alpha1.SecureValueStatus{Phase: secretv0alpha1.SecureValuePhaseSucceeded}
|
||||
} else {
|
||||
newSecureValue.Status = secretv0alpha1.SecureValueStatus{
|
||||
Message: "Updating secure value",
|
||||
Phase: secretv0alpha1.SecureValuePhasePending,
|
||||
}
|
||||
}
|
||||
|
||||
// Current implementation replaces everything passed in the spec, so it is not a PATCH. Do we want/need to support that?
|
||||
updatedSecureValue, err := s.secureValueMetadataStorage.Update(ctx, newSecureValue, actorUID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to update secure value: %w", err)
|
||||
}
|
||||
out = updatedSecureValue
|
||||
|
||||
// Only the value needs to be updated asynchronously by the outbox worker
|
||||
if encryptedSecret != "" {
|
||||
if _, err := s.outboxQueue.Append(ctx, contracts.AppendOutboxMessage{
|
||||
RequestID: requestID,
|
||||
Type: contracts.UpdateSecretOutboxMessage,
|
||||
Name: newSecureValue.Name,
|
||||
Namespace: newSecureValue.Namespace,
|
||||
EncryptedSecret: encryptedSecret,
|
||||
KeeperName: newSecureValue.Spec.Keeper,
|
||||
ExternalID: &updatedSecureValue.Status.ExternalID,
|
||||
}); err != nil {
|
||||
return fmt.Errorf("failed to append message to update secure value to outbox queue: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}); err != nil {
|
||||
return out, updateIsSync, err
|
||||
}
|
||||
|
||||
return out, updateIsSync, nil
|
||||
}
|
||||
|
||||
func (s *SecureValueService) Delete(ctx context.Context, namespace xkube.Namespace, name string) (*secretv0alpha1.SecureValue, error) {
|
||||
ctx, span := s.tracer.Start(ctx, "SecureValueService.Delete", trace.WithAttributes(
|
||||
attribute.String("name", name),
|
||||
|
@ -224,45 +194,14 @@ func (s *SecureValueService) Delete(ctx context.Context, namespace xkube.Namespa
|
|||
))
|
||||
defer span.End()
|
||||
|
||||
// Set inside of the transaction callback
|
||||
var out *secretv0alpha1.SecureValue
|
||||
|
||||
// Especifically here so that the spans from the worker are not inside the transaction.
|
||||
requestID := tracectx.HexEncodeTraceFromContext(ctx)
|
||||
|
||||
if err := s.database.Transaction(ctx, func(ctx context.Context) error {
|
||||
sv, err := s.secureValueMetadataStorage.Read(ctx, namespace, name, contracts.ReadOpts{ForUpdate: true})
|
||||
if err != nil {
|
||||
return fmt.Errorf("fetching secure value: %+w", err)
|
||||
}
|
||||
|
||||
if sv.Status.Phase == secretv0alpha1.SecureValuePhasePending {
|
||||
return contracts.ErrSecureValueOperationInProgress
|
||||
}
|
||||
|
||||
sv.Status = secretv0alpha1.SecureValueStatus{Phase: secretv0alpha1.SecureValuePhasePending, Message: "Deleting secure value"}
|
||||
|
||||
if err := s.secureValueMetadataStorage.SetStatus(ctx, namespace, name, sv.Status); err != nil {
|
||||
return fmt.Errorf("setting secure value status phase: %+w", err)
|
||||
}
|
||||
|
||||
if _, err := s.outboxQueue.Append(ctx, contracts.AppendOutboxMessage{
|
||||
RequestID: requestID,
|
||||
Type: contracts.DeleteSecretOutboxMessage,
|
||||
Name: name,
|
||||
Namespace: namespace.String(),
|
||||
KeeperName: sv.Spec.Keeper,
|
||||
ExternalID: &sv.Status.ExternalID,
|
||||
}); err != nil {
|
||||
return fmt.Errorf("appending delete secure value message to outbox queue: %+w", err)
|
||||
}
|
||||
|
||||
out = sv
|
||||
|
||||
return nil
|
||||
}); err != nil {
|
||||
return out, err
|
||||
sv, err := s.secureValueMetadataStorage.Read(ctx, namespace, name, contracts.ReadOpts{ForUpdate: true})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("fetching secure value: %+w", err)
|
||||
}
|
||||
|
||||
return out, nil
|
||||
if err := s.secureValueMetadataStorage.SetVersionToInactive(ctx, namespace, name, sv.Status.Version); err != nil {
|
||||
return nil, fmt.Errorf("setting secure value version to inactive: %+w", err)
|
||||
}
|
||||
|
||||
return sv, nil
|
||||
}
|
||||
|
|
|
@ -0,0 +1,94 @@
|
|||
package service_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/grafana/grafana/pkg/apis/secret/v0alpha1"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/secret/contracts"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/secret/testutils"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/secret/xkube"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestCrud(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("creating a secure value creates new versions", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
sut := testutils.Setup(t)
|
||||
|
||||
sv1, err := sut.CreateSv(t.Context())
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create the same secure value twice
|
||||
input := sv1.DeepCopy()
|
||||
input.Spec.Description = "d2"
|
||||
input.Spec.Value = v0alpha1.NewExposedSecureValue("v2")
|
||||
|
||||
sv2, err := sut.CreateSv(t.Context(), testutils.CreateSvWithSv(input))
|
||||
require.NoError(t, err)
|
||||
require.True(t, sv2.Status.Version > sv1.Status.Version)
|
||||
|
||||
// Read the secure value
|
||||
sv, err := sut.SecureValueService.Read(t.Context(), xkube.Namespace(sv2.Namespace), sv2.Name)
|
||||
require.NoError(t, err)
|
||||
|
||||
// It should be the latest version
|
||||
require.Equal(t, sv2.Namespace, sv.Namespace)
|
||||
require.Equal(t, sv2.Name, sv.Name)
|
||||
require.Equal(t, "d2", sv.Spec.Description)
|
||||
require.Equal(t, sv2.Status.Version, sv.Status.Version)
|
||||
})
|
||||
|
||||
t.Run("updating a secure value creates new versions", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
sut := testutils.Setup(t)
|
||||
|
||||
// Create a secure value
|
||||
sv1, err := sut.CreateSv(t.Context())
|
||||
require.NoError(t, err)
|
||||
|
||||
ns := sv1.Namespace
|
||||
name := sv1.Name
|
||||
|
||||
// Update the secure value
|
||||
input := sv1.DeepCopy()
|
||||
input.Spec.Description = "d2"
|
||||
sv2, err := sut.UpdateSv(t.Context(), input)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Read the secure value
|
||||
sv3, err := sut.SecureValueService.Read(t.Context(), xkube.Namespace(ns), name)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Nothing has changed except for the updated field.
|
||||
require.Equal(t, ns, sv2.Namespace)
|
||||
require.Equal(t, name, sv2.Name)
|
||||
require.True(t, sv2.Status.Version > sv1.Status.Version)
|
||||
require.Equal(t, "d2", sv2.Spec.Description)
|
||||
|
||||
require.Equal(t, ns, sv3.Namespace)
|
||||
require.Equal(t, name, sv3.Name)
|
||||
require.Equal(t, sv2.Status.Version, sv3.Status.Version)
|
||||
require.Equal(t, "d2", sv3.Spec.Description)
|
||||
})
|
||||
|
||||
t.Run("deleting secure values", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
sut := testutils.Setup(t)
|
||||
|
||||
sv1, err := sut.CreateSv(t.Context())
|
||||
require.NoError(t, err)
|
||||
|
||||
sv2, err := sut.DeleteSv(t.Context(), sv1.Namespace, sv1.Name)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, sv1.Namespace, sv2.Namespace)
|
||||
require.Equal(t, sv1.Name, sv2.Name)
|
||||
|
||||
_, err = sut.SecureValueMetadataStorage.Read(t.Context(), xkube.Namespace(sv1.Namespace), sv1.Name, contracts.ReadOpts{})
|
||||
require.ErrorIs(t, err, contracts.ErrSecureValueNotFound)
|
||||
})
|
||||
}
|
|
@ -3,7 +3,6 @@ package testutils
|
|||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
secretv0alpha1 "github.com/grafana/grafana/pkg/apis/secret/v0alpha1"
|
||||
encryptionstorage "github.com/grafana/grafana/pkg/storage/secret/encryption"
|
||||
|
@ -12,11 +11,11 @@ import (
|
|||
|
||||
"github.com/grafana/grafana/pkg/infra/usagestats"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/secret/contracts"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/secret/decrypt"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/secret/encryption"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/secret/encryption/manager"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/secret/secretkeeper/sqlkeeper"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/secret/service"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/secret/worker"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/secret/xkube"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol/actest"
|
||||
|
@ -29,35 +28,28 @@ import (
|
|||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type setupConfig struct {
|
||||
workerCfg worker.Config
|
||||
keeperService contracts.KeeperService
|
||||
type SetupConfig struct {
|
||||
KeeperService contracts.KeeperService
|
||||
AllowList map[string]struct{}
|
||||
}
|
||||
|
||||
func defaultSetupCfg() setupConfig {
|
||||
return setupConfig{
|
||||
workerCfg: worker.Config{
|
||||
BatchSize: 10,
|
||||
ReceiveTimeout: 1 * time.Second,
|
||||
PollingInterval: time.Millisecond,
|
||||
MaxMessageProcessingAttempts: 5,
|
||||
},
|
||||
func defaultSetupCfg() SetupConfig {
|
||||
return SetupConfig{}
|
||||
}
|
||||
|
||||
func WithKeeperService(keeperService contracts.KeeperService) func(*SetupConfig) {
|
||||
return func(setupCfg *SetupConfig) {
|
||||
setupCfg.KeeperService = keeperService
|
||||
}
|
||||
}
|
||||
|
||||
func WithWorkerConfig(cfg worker.Config) func(*setupConfig) {
|
||||
return func(setupCfg *setupConfig) {
|
||||
setupCfg.workerCfg = cfg
|
||||
func WithMutateCfg(f func(*SetupConfig)) func(*SetupConfig) {
|
||||
return func(cfg *SetupConfig) {
|
||||
f(cfg)
|
||||
}
|
||||
}
|
||||
|
||||
func WithKeeperService(keeperService contracts.KeeperService) func(*setupConfig) {
|
||||
return func(setupCfg *setupConfig) {
|
||||
setupCfg.keeperService = keeperService
|
||||
}
|
||||
}
|
||||
|
||||
func Setup(t *testing.T, opts ...func(*setupConfig)) Sut {
|
||||
func Setup(t *testing.T, opts ...func(*SetupConfig)) Sut {
|
||||
setupCfg := defaultSetupCfg()
|
||||
for _, opt := range opts {
|
||||
opt(&setupCfg)
|
||||
|
@ -68,8 +60,6 @@ func Setup(t *testing.T, opts ...func(*setupConfig)) Sut {
|
|||
|
||||
database := database.ProvideDatabase(testDB, tracer)
|
||||
|
||||
outboxQueue := metadata.ProvideOutboxQueue(database, tracer, nil)
|
||||
|
||||
features := featuremgmt.WithFeatures(featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs, featuremgmt.FlagSecretsManagementAppPlatform)
|
||||
|
||||
keeperMetadataStorage, err := metadata.ProvideKeeperMetadataStorage(database, tracer, features, nil)
|
||||
|
@ -111,34 +101,24 @@ func Setup(t *testing.T, opts ...func(*setupConfig)) Sut {
|
|||
|
||||
var keeperService contracts.KeeperService = newKeeperServiceWrapper(sqlKeeper)
|
||||
|
||||
if setupCfg.keeperService != nil {
|
||||
keeperService = setupCfg.keeperService
|
||||
if setupCfg.KeeperService != nil {
|
||||
keeperService = setupCfg.KeeperService
|
||||
}
|
||||
|
||||
secureValueService := service.ProvideSecureValueService(tracer, accessClient, database, secureValueMetadataStorage, outboxQueue, encryptionManager)
|
||||
secureValueService := service.ProvideSecureValueService(tracer, accessClient, database, secureValueMetadataStorage, keeperMetadataStorage, keeperService)
|
||||
|
||||
worker, err := worker.NewWorker(
|
||||
setupCfg.workerCfg,
|
||||
tracer,
|
||||
database,
|
||||
outboxQueue,
|
||||
secureValueMetadataStorage,
|
||||
keeperMetadataStorage,
|
||||
keeperService,
|
||||
encryptionManager,
|
||||
features,
|
||||
nil, // metrics
|
||||
)
|
||||
decryptAuthorizer := decrypt.ProvideDecryptAuthorizer(tracer, setupCfg.AllowList)
|
||||
|
||||
decryptStorage, err := metadata.ProvideDecryptStorage(features, tracer, keeperService, keeperMetadataStorage, secureValueMetadataStorage, decryptAuthorizer, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
return Sut{Worker: worker, SecureValueService: secureValueService, SecureValueMetadataStorage: secureValueMetadataStorage, OutboxQueue: outboxQueue, Database: database}
|
||||
return Sut{SecureValueService: secureValueService, SecureValueMetadataStorage: secureValueMetadataStorage, Database: database, DecryptStorage: decryptStorage}
|
||||
}
|
||||
|
||||
type Sut struct {
|
||||
Worker *worker.Worker
|
||||
SecureValueService *service.SecureValueService
|
||||
SecureValueMetadataStorage contracts.SecureValueMetadataStorage
|
||||
OutboxQueue contracts.OutboxQueue
|
||||
DecryptStorage contracts.DecryptStorage
|
||||
Database *database.Database
|
||||
}
|
||||
|
||||
|
@ -163,9 +143,7 @@ func (s *Sut) CreateSv(ctx context.Context, opts ...func(*CreateSvConfig)) (*sec
|
|||
Description: "desc1",
|
||||
Value: secretv0alpha1.NewExposedSecureValue("v1"),
|
||||
},
|
||||
Status: secretv0alpha1.SecureValueStatus{
|
||||
Phase: secretv0alpha1.SecureValuePhasePending,
|
||||
},
|
||||
Status: secretv0alpha1.SecureValueStatus{},
|
||||
},
|
||||
}
|
||||
for _, opt := range opts {
|
||||
|
|
|
@ -1,44 +0,0 @@
|
|||
package worker
|
||||
|
||||
import (
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
const (
|
||||
namespace = "grafana_secrets_manager"
|
||||
subsystem = "outbox_worker"
|
||||
)
|
||||
|
||||
// OutboxMetrics is a struct that contains all the metrics for an implementation of the secrets service.
|
||||
type OutboxMetrics struct {
|
||||
OutboxMessageProcessingDuration *prometheus.HistogramVec
|
||||
}
|
||||
|
||||
func newOutboxMetrics() *OutboxMetrics {
|
||||
return &OutboxMetrics{
|
||||
OutboxMessageProcessingDuration: prometheus.NewHistogramVec(prometheus.HistogramOpts{
|
||||
Namespace: namespace,
|
||||
Subsystem: subsystem,
|
||||
Name: "message_processing_duration_seconds",
|
||||
Help: "Duration of outbox message processing",
|
||||
Buckets: prometheus.DefBuckets,
|
||||
}, []string{"message_type", "keeper_type"}),
|
||||
}
|
||||
}
|
||||
|
||||
// NewOutboxMetrics creates a new SecretsMetrics struct containing registered metrics
|
||||
func NewOutboxMetrics(reg prometheus.Registerer) *OutboxMetrics {
|
||||
m := newOutboxMetrics()
|
||||
|
||||
if reg != nil {
|
||||
reg.MustRegister(
|
||||
m.OutboxMessageProcessingDuration,
|
||||
)
|
||||
}
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
func NewTestMetrics() *OutboxMetrics {
|
||||
return newOutboxMetrics()
|
||||
}
|
|
@ -1,274 +0,0 @@
|
|||
package worker
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana-app-sdk/logging"
|
||||
secretv0alpha1 "github.com/grafana/grafana/pkg/apis/secret/v0alpha1"
|
||||
"github.com/grafana/grafana/pkg/registry"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/secret/contracts"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/secret/tracectx"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/secret/xkube"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
)
|
||||
|
||||
// Consumes and processes messages from the secure value outbox queue
|
||||
type Worker struct {
|
||||
config Config
|
||||
tracer trace.Tracer
|
||||
database contracts.Database
|
||||
outboxQueue contracts.OutboxQueue
|
||||
secureValueMetadataStorage contracts.SecureValueMetadataStorage
|
||||
keeperMetadataStorage contracts.KeeperMetadataStorage
|
||||
keeperService contracts.KeeperService
|
||||
encryptionManager contracts.EncryptionManager
|
||||
metrics *OutboxMetrics
|
||||
enabled bool
|
||||
}
|
||||
|
||||
// DefaultConfig for the secure value outbox worker.
|
||||
var DefaultConfig = Config{
|
||||
BatchSize: 20,
|
||||
ReceiveTimeout: 5 * time.Second,
|
||||
PollingInterval: 100 * time.Millisecond,
|
||||
MaxMessageProcessingAttempts: 10,
|
||||
}
|
||||
|
||||
// ProvideWorkerConfig used for wire.
|
||||
func ProvideWorkerConfig() Config {
|
||||
return DefaultConfig
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
// The max number of messages to fetch from the outbox queue in a batch
|
||||
BatchSize uint
|
||||
// How long to wait for a request to fetch messages from the outbox queue
|
||||
ReceiveTimeout time.Duration
|
||||
// How often to poll the outbox queue for new messages
|
||||
PollingInterval time.Duration
|
||||
// How many tries to try to process a message before marking the operation as failed
|
||||
MaxMessageProcessingAttempts uint
|
||||
}
|
||||
|
||||
func NewWorker(
|
||||
config Config,
|
||||
tracer trace.Tracer,
|
||||
database contracts.Database,
|
||||
outboxQueue contracts.OutboxQueue,
|
||||
secureValueMetadataStorage contracts.SecureValueMetadataStorage,
|
||||
keeperMetadataStorage contracts.KeeperMetadataStorage,
|
||||
keeperService contracts.KeeperService,
|
||||
encryptionManager contracts.EncryptionManager,
|
||||
features featuremgmt.FeatureToggles,
|
||||
reg prometheus.Registerer,
|
||||
) (*Worker, error) {
|
||||
if config.BatchSize == 0 {
|
||||
return nil, fmt.Errorf("config.BatchSize is required")
|
||||
}
|
||||
if config.ReceiveTimeout == 0 {
|
||||
return nil, fmt.Errorf("config.ReceiveTimeout is required")
|
||||
}
|
||||
if config.PollingInterval == 0 {
|
||||
return nil, fmt.Errorf("config.PollingInterval is required")
|
||||
}
|
||||
if config.MaxMessageProcessingAttempts == 0 {
|
||||
return nil, fmt.Errorf("config.MaxMessageProcessingAttempts is required")
|
||||
}
|
||||
|
||||
// Require both features to be enabled for the worker to run.
|
||||
enabled := features.IsEnabledGlobally(featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs) && features.IsEnabledGlobally(featuremgmt.FlagSecretsManagementAppPlatform)
|
||||
|
||||
return &Worker{
|
||||
config: config,
|
||||
tracer: tracer,
|
||||
database: database,
|
||||
outboxQueue: outboxQueue,
|
||||
secureValueMetadataStorage: secureValueMetadataStorage,
|
||||
keeperMetadataStorage: keeperMetadataStorage,
|
||||
keeperService: keeperService,
|
||||
encryptionManager: encryptionManager,
|
||||
metrics: NewOutboxMetrics(reg),
|
||||
enabled: enabled,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Ensure that Worker implements the BackgroundService interface, so we can start it as a background service.
|
||||
var _ registry.BackgroundService = (*Worker)(nil)
|
||||
|
||||
// Run is the main method to drive the worker
|
||||
func (w *Worker) Run(ctx context.Context) error {
|
||||
if !w.enabled {
|
||||
return nil
|
||||
}
|
||||
|
||||
logging.FromContext(ctx).Debug("starting worker control loop")
|
||||
|
||||
t := time.NewTicker(w.config.PollingInterval)
|
||||
defer t.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
// If the context was canceled
|
||||
case <-ctx.Done():
|
||||
// return the reason it was canceled
|
||||
return ctx.Err()
|
||||
|
||||
// Otherwise try to receive messages
|
||||
case <-t.C:
|
||||
if ctx.Err() != nil {
|
||||
return ctx.Err()
|
||||
}
|
||||
|
||||
if err := w.ReceiveAndProcessMessages(ctx); err != nil {
|
||||
logging.FromContext(ctx).Error("receiving outbox messages", "err", err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: don't rollback every message when a single error happens
|
||||
func (w *Worker) ReceiveAndProcessMessages(ctx context.Context) error {
|
||||
messageIDs := make([]int64, 0)
|
||||
|
||||
txErr := w.database.Transaction(ctx, func(ctx context.Context) error {
|
||||
timeoutCtx, cancel := context.WithTimeout(ctx, w.config.ReceiveTimeout)
|
||||
messages, err := w.outboxQueue.ReceiveN(timeoutCtx, w.config.BatchSize)
|
||||
cancel()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, message := range messages {
|
||||
messageIDs = append(messageIDs, message.MessageID)
|
||||
if err := w.processMessage(ctx, message); err != nil {
|
||||
return fmt.Errorf("processing message: %+v %w", message, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
// This call is made outside the transaction to make sure the receive count is updated on rollbacks.
|
||||
incrementErr := w.outboxQueue.IncrementReceiveCount(ctx, messageIDs)
|
||||
if incrementErr != nil {
|
||||
incrementErr = fmt.Errorf("incrementing receive count for outbox message: %w", incrementErr)
|
||||
}
|
||||
|
||||
return errors.Join(txErr, incrementErr)
|
||||
}
|
||||
|
||||
func (w *Worker) processMessage(ctx context.Context, message contracts.OutboxMessage) error {
|
||||
start := time.Now()
|
||||
keeperType := "unknown"
|
||||
defer func() {
|
||||
w.metrics.OutboxMessageProcessingDuration.WithLabelValues(string(message.Type), keeperType).Observe(time.Since(start).Seconds())
|
||||
}()
|
||||
logging.FromContext(ctx).Debug("processing message", "type", message.Type, "name", message.Name, "namespace", message.Namespace, "receiveCount", message.ReceiveCount)
|
||||
|
||||
opts := []trace.SpanStartOption{}
|
||||
// If there's no request ID in the message, start a new root span and log an error.
|
||||
ctx, err := tracectx.HexDecodeTraceIntoContext(ctx, message.RequestID)
|
||||
if err != nil {
|
||||
opts = append(opts, trace.WithNewRoot())
|
||||
logging.FromContext(ctx).Error("decoding trace context from message", "err", err.Error(), "message.requestID", message.RequestID)
|
||||
}
|
||||
|
||||
opts = append(opts, trace.WithAttributes(
|
||||
attribute.String("message.requestID", message.RequestID),
|
||||
attribute.Int64("message.id", message.MessageID),
|
||||
attribute.String("message.type", string(message.Type)),
|
||||
attribute.String("message.namespace", message.Namespace),
|
||||
attribute.String("message.secureValue.name", message.Name),
|
||||
attribute.Int("message.receive.count", message.ReceiveCount),
|
||||
))
|
||||
|
||||
ctx, span := w.tracer.Start(ctx, "Worker.ProcessMessage", opts...)
|
||||
defer span.End()
|
||||
|
||||
if message.ReceiveCount >= int(w.config.MaxMessageProcessingAttempts) {
|
||||
if err := w.secureValueMetadataStorage.SetStatus(ctx, xkube.Namespace(message.Namespace), message.Name, secretv0alpha1.SecureValueStatus{Phase: secretv0alpha1.SecureValuePhaseFailed, Message: fmt.Sprintf("Reached max number of attempts to complete operation: %s", message.Type)}); err != nil {
|
||||
return fmt.Errorf("setting secret metadata status to Succeeded: message=%+v", message)
|
||||
}
|
||||
if err := w.outboxQueue.Delete(ctx, message.MessageID); err != nil {
|
||||
return fmt.Errorf("deleting message from outbox queue: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
keeperCfg, err := w.keeperMetadataStorage.GetKeeperConfig(ctx, message.Namespace, message.KeeperName, contracts.ReadOpts{ForUpdate: true})
|
||||
if err != nil {
|
||||
return fmt.Errorf("fetching keeper config: namespace=%+v keeperName=%+v %w", message.Namespace, message.KeeperName, err)
|
||||
}
|
||||
keeperType = string(keeperCfg.Type())
|
||||
|
||||
keeper, err := w.keeperService.KeeperForConfig(keeperCfg)
|
||||
if err != nil {
|
||||
return fmt.Errorf("getting keeper for config: namespace=%+v keeperName=%+v %w", message.Namespace, message.KeeperName, err)
|
||||
}
|
||||
logging.FromContext(ctx).Debug("retrieved keeper", "namespace", message.Namespace, "keeperName", message.KeeperName, "type", keeperCfg.Type())
|
||||
|
||||
switch message.Type {
|
||||
case contracts.CreateSecretOutboxMessage:
|
||||
rawSecret, err := w.encryptionManager.Decrypt(ctx, message.Namespace, []byte(message.EncryptedSecret))
|
||||
if err != nil {
|
||||
return fmt.Errorf("decrypting secure value secret: %w", err)
|
||||
}
|
||||
|
||||
externalID, err := keeper.Store(ctx, keeperCfg, message.Namespace, string(rawSecret))
|
||||
if err != nil {
|
||||
return fmt.Errorf("storing secret: message=%+v %w", message, err)
|
||||
}
|
||||
|
||||
if err := w.secureValueMetadataStorage.SetExternalID(ctx, xkube.Namespace(message.Namespace), message.Name, externalID); err != nil {
|
||||
return fmt.Errorf("setting secret metadata externalID: externalID=%+v message=%+v %w", externalID, message, err)
|
||||
}
|
||||
|
||||
// Setting the status to Succeeded must be the last action
|
||||
// since it acts as a fence to clients.
|
||||
if err := w.secureValueMetadataStorage.SetStatus(ctx, xkube.Namespace(message.Namespace), message.Name, secretv0alpha1.SecureValueStatus{Phase: secretv0alpha1.SecureValuePhaseSucceeded}); err != nil {
|
||||
return fmt.Errorf("setting secret metadata status to Succeeded: message=%+v %w", message, err)
|
||||
}
|
||||
|
||||
case contracts.UpdateSecretOutboxMessage:
|
||||
rawSecret, err := w.encryptionManager.Decrypt(ctx, message.Namespace, []byte(message.EncryptedSecret))
|
||||
if err != nil {
|
||||
return fmt.Errorf("decrypting secure value secret: %w", err)
|
||||
}
|
||||
|
||||
if err := keeper.Update(ctx, keeperCfg, message.Namespace, contracts.ExternalID(*message.ExternalID), string(rawSecret)); err != nil {
|
||||
return fmt.Errorf("calling keeper to update secret: %w", err)
|
||||
}
|
||||
|
||||
// Setting the status to Succeeded must be the last action
|
||||
// since it acts as a fence to clients.
|
||||
if err := w.secureValueMetadataStorage.SetStatus(ctx, xkube.Namespace(message.Namespace), message.Name, secretv0alpha1.SecureValueStatus{Phase: secretv0alpha1.SecureValuePhaseSucceeded}); err != nil {
|
||||
return fmt.Errorf("setting secret metadata status to Succeeded: message=%+v", message)
|
||||
}
|
||||
|
||||
case contracts.DeleteSecretOutboxMessage:
|
||||
if err := keeper.Delete(ctx, keeperCfg, message.Namespace, contracts.ExternalID(*message.ExternalID)); err != nil {
|
||||
return fmt.Errorf("calling keeper to delete secret: %w", err)
|
||||
}
|
||||
if err := w.secureValueMetadataStorage.Delete(ctx, xkube.Namespace(message.Namespace), message.Name); err != nil {
|
||||
return fmt.Errorf("deleting secure value metadata: %+w", err)
|
||||
}
|
||||
|
||||
default:
|
||||
return fmt.Errorf("unhandled message type: %s", message.Type)
|
||||
}
|
||||
|
||||
// Delete the message from the queue after completing all operations because
|
||||
// if the message is deleted first, the response may be lost,
|
||||
// resulting in an error, but since the message was actually deleted
|
||||
// the worker would never retry.
|
||||
if err := w.outboxQueue.Delete(ctx, message.MessageID); err != nil {
|
||||
return fmt.Errorf("deleting message from outbox queue: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -1,246 +0,0 @@
|
|||
package worker_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
secretv0alpha1 "github.com/grafana/grafana/pkg/apis/secret/v0alpha1"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/secret/contracts"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/secret/testutils"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/secret/worker"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/secret/xkube"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type fakeKeeperService struct {
|
||||
keeperForConfigFunc func(cfg secretv0alpha1.KeeperConfig) (contracts.Keeper, error)
|
||||
}
|
||||
|
||||
func newFakeKeeperService(keeperForConfigFunc func(cfg secretv0alpha1.KeeperConfig) (contracts.Keeper, error)) *fakeKeeperService {
|
||||
return &fakeKeeperService{keeperForConfigFunc: keeperForConfigFunc}
|
||||
}
|
||||
|
||||
func (s *fakeKeeperService) KeeperForConfig(cfg secretv0alpha1.KeeperConfig) (contracts.Keeper, error) {
|
||||
return s.keeperForConfigFunc(cfg)
|
||||
}
|
||||
|
||||
func TestProcessMessage(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("secure value metadata status is set to Failed when processing a message fails too many times", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Given a worker that will attempt to process a message N times
|
||||
workerCfg := worker.Config{
|
||||
BatchSize: 10,
|
||||
ReceiveTimeout: 1 * time.Second,
|
||||
PollingInterval: time.Millisecond,
|
||||
MaxMessageProcessingAttempts: 2,
|
||||
}
|
||||
|
||||
// And an error that keeps happening
|
||||
keeperService := newFakeKeeperService(func(cfg secretv0alpha1.KeeperConfig) (contracts.Keeper, error) {
|
||||
return nil, fmt.Errorf("oops")
|
||||
})
|
||||
|
||||
sut := testutils.Setup(t, testutils.WithWorkerConfig(workerCfg), testutils.WithKeeperService(keeperService))
|
||||
ctx := context.Background()
|
||||
|
||||
// Queue a create secure value operation
|
||||
sv, err := sut.CreateSv(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
for range workerCfg.MaxMessageProcessingAttempts + 1 {
|
||||
// The secure value status should be Pending while the worker is trying to process the message
|
||||
sv, err = sut.SecureValueMetadataStorage.Read(ctx, xkube.Namespace(sv.Namespace), sv.Name, contracts.ReadOpts{})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, secretv0alpha1.SecureValuePhasePending, sv.Status.Phase)
|
||||
|
||||
// Worker tries to process messages
|
||||
_ = sut.Worker.ReceiveAndProcessMessages(ctx)
|
||||
}
|
||||
|
||||
// After the worker fails to process a message too many times,
|
||||
// the secure value status is changed to Failed
|
||||
sv, err = sut.SecureValueMetadataStorage.Read(ctx, xkube.Namespace(sv.Namespace), sv.Name, contracts.ReadOpts{})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, secretv0alpha1.SecureValuePhaseFailed, sv.Status.Phase)
|
||||
|
||||
messages, err := sut.OutboxQueue.ReceiveN(ctx, 100)
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, messages)
|
||||
})
|
||||
|
||||
t.Run("create sv: secure value metadata status is set to Succeeded when message is processed successfully", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
sut := testutils.Setup(t)
|
||||
ctx := context.Background()
|
||||
|
||||
// Queue a create secure value operation
|
||||
sv, err := sut.CreateSv(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Worker receives and processes the message
|
||||
require.NoError(t, sut.Worker.ReceiveAndProcessMessages(ctx))
|
||||
|
||||
// and sets the secure value status to Succeeded
|
||||
sv, err = sut.SecureValueMetadataStorage.Read(ctx, xkube.Namespace(sv.Namespace), sv.Name, contracts.ReadOpts{})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, secretv0alpha1.SecureValuePhaseSucceeded, sv.Status.Phase)
|
||||
|
||||
messages, err := sut.OutboxQueue.ReceiveN(ctx, 100)
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, messages)
|
||||
})
|
||||
|
||||
t.Run("update sv: secure value metadata status is set to Succeeded when message is processed successfully", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
sut := testutils.Setup(t)
|
||||
ctx := context.Background()
|
||||
|
||||
// Queue a create secure value operation
|
||||
sv, err := sut.CreateSv(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Worker receives and processes the message
|
||||
require.NoError(t, sut.Worker.ReceiveAndProcessMessages(ctx))
|
||||
|
||||
// and sets the secure value status to Succeeded
|
||||
sv, err = sut.SecureValueMetadataStorage.Read(ctx, xkube.Namespace(sv.Namespace), sv.Name, contracts.ReadOpts{})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, secretv0alpha1.SecureValuePhaseSucceeded, sv.Status.Phase)
|
||||
|
||||
sv.Spec.Description = "desc2"
|
||||
sv.Spec.Value = secretv0alpha1.NewExposedSecureValue("v2")
|
||||
|
||||
// Queue an update operation
|
||||
sv, err = sut.UpdateSv(ctx, sv)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, secretv0alpha1.SecureValuePhasePending, sv.Status.Phase)
|
||||
|
||||
// Worker receives and processes the message
|
||||
require.NoError(t, sut.Worker.ReceiveAndProcessMessages(ctx))
|
||||
updatedSv, err := sut.SecureValueMetadataStorage.Read(ctx, xkube.Namespace(sv.Namespace), sv.Name, contracts.ReadOpts{})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, secretv0alpha1.SecureValuePhaseSucceeded, updatedSv.Status.Phase)
|
||||
require.Equal(t, sv.Spec.Description, updatedSv.Spec.Description)
|
||||
|
||||
messages, err := sut.OutboxQueue.ReceiveN(ctx, 100)
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, messages)
|
||||
})
|
||||
|
||||
t.Run("delete sv: secure value metadata is deleted", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
sut := testutils.Setup(t)
|
||||
ctx := context.Background()
|
||||
|
||||
// Queue a create secure value operation
|
||||
sv, err := sut.CreateSv(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Worker receives and processes the message
|
||||
require.NoError(t, sut.Worker.ReceiveAndProcessMessages(ctx))
|
||||
|
||||
// and sets the secure value status to Succeeded
|
||||
sv, err = sut.SecureValueMetadataStorage.Read(ctx, xkube.Namespace(sv.Namespace), sv.Name, contracts.ReadOpts{})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, secretv0alpha1.SecureValuePhaseSucceeded, sv.Status.Phase)
|
||||
|
||||
// Queue a delete operation
|
||||
updatedSv, err := sut.DeleteSv(ctx, sv.Namespace, sv.Name)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, secretv0alpha1.SecureValuePhasePending, updatedSv.Status.Phase)
|
||||
|
||||
// Worker receives and processes the message
|
||||
require.NoError(t, sut.Worker.ReceiveAndProcessMessages(ctx))
|
||||
|
||||
// The secure value has been deleted
|
||||
_, err = sut.SecureValueMetadataStorage.Read(ctx, xkube.Namespace(sv.Namespace), sv.Name, contracts.ReadOpts{})
|
||||
require.ErrorIs(t, err, contracts.ErrSecureValueNotFound)
|
||||
|
||||
messages, err := sut.OutboxQueue.ReceiveN(ctx, 100)
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, messages)
|
||||
})
|
||||
|
||||
t.Run("when creating a secure value, the secret is encrypted before it is added to the outbox queue", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
sut := testutils.Setup(t)
|
||||
ctx := context.Background()
|
||||
|
||||
// Queue a create secure value operation
|
||||
var secret string
|
||||
_, err := sut.CreateSv(ctx, func(cfg *testutils.CreateSvConfig) {
|
||||
secret = string(cfg.Sv.Spec.Value)
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
messages, err := sut.OutboxQueue.ReceiveN(ctx, 100)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(messages))
|
||||
|
||||
encryptedSecret := messages[0].EncryptedSecret
|
||||
require.NotEmpty(t, secret)
|
||||
require.NotEmpty(t, encryptedSecret)
|
||||
require.NotEqual(t, secret, encryptedSecret)
|
||||
})
|
||||
|
||||
t.Run("when updating a secure value, the secret is encrypted before it is added to the outbox queue", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
sut := testutils.Setup(t)
|
||||
ctx := context.Background()
|
||||
|
||||
// Queue a create secure value operation
|
||||
sv, err := sut.CreateSv(ctx)
|
||||
require.NoError(t, err)
|
||||
sv.Spec.Value = secretv0alpha1.NewExposedSecureValue("v2")
|
||||
|
||||
require.NoError(t, sut.Worker.ReceiveAndProcessMessages(ctx))
|
||||
|
||||
newValue := "v2"
|
||||
sv.Spec.Value = secretv0alpha1.NewExposedSecureValue(newValue)
|
||||
|
||||
// Queue an update secure value operation
|
||||
_, err = sut.UpdateSv(ctx, sv)
|
||||
require.NoError(t, err)
|
||||
|
||||
messages, err := sut.OutboxQueue.ReceiveN(ctx, 100)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(messages))
|
||||
|
||||
encryptedSecret := messages[0].EncryptedSecret
|
||||
require.NotEmpty(t, encryptedSecret)
|
||||
require.NotEqual(t, newValue, encryptedSecret)
|
||||
})
|
||||
|
||||
t.Run("when deleting a secure value, no value is added to the outbox message", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
sut := testutils.Setup(t)
|
||||
ctx := context.Background()
|
||||
|
||||
// Queue a create secure value operation
|
||||
sv, err := sut.CreateSv(ctx)
|
||||
require.NoError(t, err)
|
||||
sv.Spec.Value = secretv0alpha1.NewExposedSecureValue("v2")
|
||||
|
||||
require.NoError(t, sut.Worker.ReceiveAndProcessMessages(ctx))
|
||||
|
||||
// Queue a delete secure value operation
|
||||
_, err = sut.DeleteSv(ctx, sv.Namespace, sv.Name)
|
||||
require.NoError(t, err)
|
||||
|
||||
messages, err := sut.OutboxQueue.ReceiveN(ctx, 100)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(messages))
|
||||
require.Empty(t, messages[0].EncryptedSecret)
|
||||
})
|
||||
}
|
|
@ -9,7 +9,6 @@ import (
|
|||
"github.com/grafana/grafana/pkg/infra/usagestats/statscollector"
|
||||
"github.com/grafana/grafana/pkg/registry"
|
||||
apiregistry "github.com/grafana/grafana/pkg/registry/apis"
|
||||
secretworker "github.com/grafana/grafana/pkg/registry/apis/secret/worker"
|
||||
appregistry "github.com/grafana/grafana/pkg/registry/apps"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol/dualwrite"
|
||||
"github.com/grafana/grafana/pkg/services/anonymous/anonimpl"
|
||||
|
@ -71,7 +70,6 @@ func ProvideBackgroundServiceRegistry(
|
|||
appRegistry *appregistry.Service,
|
||||
pluginDashboardUpdater *plugindashboardsservice.DashboardUpdater,
|
||||
dashboardServiceImpl *service.DashboardServiceImpl,
|
||||
secretManagerWorker *secretworker.Worker,
|
||||
// Need to make sure these are initialized, is there a better place to put them?
|
||||
_ dashboardsnapshots.Service,
|
||||
_ serviceaccounts.Service,
|
||||
|
@ -119,7 +117,6 @@ func ProvideBackgroundServiceRegistry(
|
|||
appRegistry,
|
||||
pluginDashboardUpdater,
|
||||
dashboardServiceImpl,
|
||||
secretManagerWorker,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -46,7 +46,6 @@ import (
|
|||
gsmEncryption "github.com/grafana/grafana/pkg/registry/apis/secret/encryption"
|
||||
encryptionManager "github.com/grafana/grafana/pkg/registry/apis/secret/encryption/manager"
|
||||
secretsecurevalueservice "github.com/grafana/grafana/pkg/registry/apis/secret/service"
|
||||
secretworker "github.com/grafana/grafana/pkg/registry/apis/secret/worker"
|
||||
appregistry "github.com/grafana/grafana/pkg/registry/apps"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol/acimpl"
|
||||
|
@ -431,15 +430,12 @@ var wireBasicSet = wire.NewSet(
|
|||
secretdecrypt.ProvideDecryptAllowList,
|
||||
secretencryption.ProvideDataKeyStorage,
|
||||
secretencryption.ProvideEncryptedValueStorage,
|
||||
secretmetadata.ProvideOutboxQueue,
|
||||
secretsecurevalueservice.ProvideSecureValueService,
|
||||
secretmigrator.NewWithEngine,
|
||||
secretdatabase.ProvideDatabase,
|
||||
wire.Bind(new(secretcontracts.Database), new(*secretdatabase.Database)),
|
||||
encryptionManager.ProvideEncryptionManager,
|
||||
gsmEncryption.ProvideThirdPartyProviderMap,
|
||||
secretworker.ProvideWorkerConfig,
|
||||
secretworker.NewWorker,
|
||||
// Unified storage
|
||||
resource.ProvideStorageMetrics,
|
||||
resource.ProvideIndexMetrics,
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -8,10 +8,8 @@ INSERT INTO {{ .Ident "secret_secure_value" }} (
|
|||
{{ .Ident "created_by" }},
|
||||
{{ .Ident "updated" }},
|
||||
{{ .Ident "updated_by" }},
|
||||
{{ .Ident "status_phase" }},
|
||||
{{ if .Row.Message.Valid }}
|
||||
{{ .Ident "status_message" }},
|
||||
{{ end }}
|
||||
{{ .Ident "active" }},
|
||||
{{ .Ident "version" }},
|
||||
{{ .Ident "description" }},
|
||||
{{ if .Row.Keeper.Valid }}
|
||||
{{ .Ident "keeper" }},
|
||||
|
@ -33,10 +31,8 @@ INSERT INTO {{ .Ident "secret_secure_value" }} (
|
|||
{{ .Arg .Row.CreatedBy }},
|
||||
{{ .Arg .Row.Updated }},
|
||||
{{ .Arg .Row.UpdatedBy }},
|
||||
{{ .Arg .Row.Phase }},
|
||||
{{ if .Row.Message.Valid }}
|
||||
{{ .Arg .Row.Message.String }},
|
||||
{{ end }}
|
||||
{{ .Arg .Row.Active }},
|
||||
{{ .Arg .Row.Version }},
|
||||
{{ .Arg .Row.Description }},
|
||||
{{ if .Row.Keeper.Valid }}
|
||||
{{ .Arg .Row.Keeper.String }},
|
||||
|
@ -48,4 +44,4 @@ INSERT INTO {{ .Ident "secret_secure_value" }} (
|
|||
{{ .Arg .Row.Ref.String }},
|
||||
{{ end }}
|
||||
{{ .Arg .Row.ExternalID }}
|
||||
);
|
||||
);
|
|
@ -1,4 +0,0 @@
|
|||
DELETE FROM {{ .Ident "secret_secure_value" }}
|
||||
WHERE {{ .Ident "namespace" }} = {{ .Arg .Namespace }} AND
|
||||
{{ .Ident "name" }} = {{ .Arg .Name }}
|
||||
;
|
|
@ -0,0 +1,10 @@
|
|||
SELECT
|
||||
{{ .Ident "version" }}
|
||||
FROM
|
||||
{{ .Ident "secret_secure_value" }}
|
||||
WHERE
|
||||
{{ .Ident "namespace" }} = {{ .Arg .Namespace }} AND
|
||||
{{ .Ident "name" }} = {{ .Arg .Name }}
|
||||
ORDER BY {{ .Ident "version" }} DESC
|
||||
LIMIT 1
|
||||
;
|
|
@ -8,15 +8,17 @@ SELECT
|
|||
{{ .Ident "created_by" }},
|
||||
{{ .Ident "updated" }},
|
||||
{{ .Ident "updated_by" }},
|
||||
{{ .Ident "status_phase" }},
|
||||
{{ .Ident "status_message" }},
|
||||
{{ .Ident "description" }},
|
||||
{{ .Ident "keeper" }},
|
||||
{{ .Ident "decrypters" }},
|
||||
{{ .Ident "ref" }},
|
||||
{{ .Ident "external_id" }}
|
||||
{{ .Ident "external_id" }},
|
||||
{{ .Ident "version" }},
|
||||
{{ .Ident "active" }}
|
||||
FROM
|
||||
{{ .Ident "secret_secure_value" }}
|
||||
WHERE {{ .Ident "namespace" }} = {{ .Arg .Namespace }}
|
||||
WHERE
|
||||
{{ .Ident "namespace" }} = {{ .Arg .Namespace }} AND
|
||||
{{ .Ident "active" }} = true
|
||||
ORDER BY {{ .Ident "updated" }} DESC
|
||||
;
|
||||
;
|
|
@ -5,7 +5,9 @@ SELECT
|
|||
{{ .Ident "keeper" }}
|
||||
FROM
|
||||
{{ .Ident "secret_secure_value" }}
|
||||
WHERE {{ .Ident "namespace" }} = {{ .Arg .Namespace }} AND
|
||||
{{ .Ident "name" }} IN ({{ .ArgList .UsedSecureValues }})
|
||||
WHERE
|
||||
{{ .Ident "namespace" }} = {{ .Arg .Namespace }} AND
|
||||
{{ .Ident "name" }} IN ({{ .ArgList .UsedSecureValues }}) AND
|
||||
{{ .Ident "active" }} = true
|
||||
{{ .SelectFor "UPDATE" }}
|
||||
;
|
||||
|
|
|
@ -1,33 +0,0 @@
|
|||
INSERT INTO {{ .Ident "secret_secure_value_outbox" }} (
|
||||
{{ .Ident "request_id" }},
|
||||
{{ .Ident "message_type" }},
|
||||
{{ .Ident "name" }},
|
||||
{{ .Ident "namespace" }},
|
||||
{{ if .Row.EncryptedSecret.Valid }}
|
||||
{{ .Ident "encrypted_secret" }},
|
||||
{{ end }}
|
||||
{{ if .Row.KeeperName.Valid }}
|
||||
{{ .Ident "keeper_name" }},
|
||||
{{ end }}
|
||||
{{ if .Row.ExternalID.Valid }}
|
||||
{{ .Ident "external_id" }},
|
||||
{{ end }}
|
||||
{{ .Ident "receive_count" }},
|
||||
{{ .Ident "created" }}
|
||||
) VALUES (
|
||||
{{ .Arg .Row.RequestID }},
|
||||
{{ .Arg .Row.MessageType }},
|
||||
{{ .Arg .Row.Name }},
|
||||
{{ .Arg .Row.Namespace }},
|
||||
{{ if .Row.EncryptedSecret.Valid }}
|
||||
{{ .Arg .Row.EncryptedSecret.String }},
|
||||
{{ end }}
|
||||
{{ if .Row.KeeperName.Valid }}
|
||||
{{ .Arg .Row.KeeperName.String }},
|
||||
{{ end }}
|
||||
{{ if .Row.ExternalID.Valid }}
|
||||
{{ .Arg .Row.ExternalID.String }},
|
||||
{{ end }}
|
||||
{{ .Arg .Row.ReceiveCount }},
|
||||
{{ .Arg .Row.Created }}
|
||||
);
|
|
@ -1,5 +0,0 @@
|
|||
DELETE FROM
|
||||
{{ .Ident "secret_secure_value_outbox" }}
|
||||
WHERE
|
||||
{{ .Ident "id" }} = {{ .Arg .MessageID }}
|
||||
;
|
|
@ -1,6 +0,0 @@
|
|||
SELECT
|
||||
{{ .Ident "id" }}
|
||||
FROM {{ .Ident "secret_secure_value_outbox" }}
|
||||
ORDER BY id ASC
|
||||
LIMIT {{ .Arg .ReceiveLimit }}
|
||||
;
|
|
@ -1,8 +0,0 @@
|
|||
SELECT
|
||||
{{ .Ident "created" }},
|
||||
{{ .Ident "message_type" }}
|
||||
FROM
|
||||
{{ .Ident "secret_secure_value_outbox" }}
|
||||
WHERE
|
||||
{{ .Ident "id" }} = {{ .Arg .MessageID }}
|
||||
;
|
|
@ -1,19 +0,0 @@
|
|||
SELECT
|
||||
{{ .Ident "request_id" }},
|
||||
{{ .Ident "id" }},
|
||||
{{ .Ident "message_type" }},
|
||||
{{ .Ident "name" }},
|
||||
{{ .Ident "namespace" }},
|
||||
{{ .Ident "encrypted_secret" }},
|
||||
{{ .Ident "keeper_name" }},
|
||||
{{ .Ident "external_id" }},
|
||||
{{ .Ident "receive_count" }},
|
||||
{{ .Ident "created" }}
|
||||
FROM
|
||||
{{ .Ident "secret_secure_value_outbox" }}
|
||||
WHERE
|
||||
{{ .Ident "id" }} IN ({{ .ArgList .MessageIDs }})
|
||||
ORDER BY
|
||||
{{ .Ident "id" }} ASC
|
||||
{{ .SelectFor "UPDATE SKIP LOCKED" }}
|
||||
;
|
|
@ -1,7 +0,0 @@
|
|||
UPDATE
|
||||
{{ .Ident "secret_secure_value_outbox" }}
|
||||
SET
|
||||
{{ .Ident "receive_count" }} = {{ .Ident "receive_count" }} + 1
|
||||
WHERE
|
||||
{{ .Ident "id" }} IN ({{ .ArgList .MessageIDs }})
|
||||
;
|
|
@ -8,18 +8,20 @@ SELECT
|
|||
{{ .Ident "created_by" }},
|
||||
{{ .Ident "updated" }},
|
||||
{{ .Ident "updated_by" }},
|
||||
{{ .Ident "status_phase" }},
|
||||
{{ .Ident "status_message" }},
|
||||
{{ .Ident "description" }},
|
||||
{{ .Ident "keeper" }},
|
||||
{{ .Ident "decrypters" }},
|
||||
{{ .Ident "ref" }},
|
||||
{{ .Ident "external_id" }}
|
||||
{{ .Ident "external_id" }},
|
||||
{{ .Ident "active" }},
|
||||
{{ .Ident "version" }}
|
||||
FROM
|
||||
{{ .Ident "secret_secure_value" }}
|
||||
WHERE {{ .Ident "namespace" }} = {{ .Arg .Namespace }} AND
|
||||
{{ .Ident "name" }} = {{ .Arg .Name }}
|
||||
WHERE
|
||||
{{ .Ident "namespace" }} = {{ .Arg .Namespace }} AND
|
||||
{{ .Ident "name" }} = {{ .Arg .Name }} AND
|
||||
{{ .Ident "active" }} = true
|
||||
{{ if .IsForUpdate }}
|
||||
{{ .SelectFor "UPDATE" }}
|
||||
{{ end }}
|
||||
;
|
||||
;
|
|
@ -2,9 +2,12 @@ SELECT
|
|||
{{ .Ident "keeper" }},
|
||||
{{ .Ident "decrypters" }},
|
||||
{{ .Ident "ref" }},
|
||||
{{ .Ident "external_id" }}
|
||||
{{ .Ident "external_id" }},
|
||||
{{ .Ident "active" }}
|
||||
FROM
|
||||
{{ .Ident "secret_secure_value" }}
|
||||
WHERE {{ .Ident "namespace" }} = {{ .Arg .Namespace }} AND
|
||||
{{ .Ident "name" }} = {{ .Arg .Name }}
|
||||
;
|
||||
WHERE
|
||||
{{ .Ident "namespace" }} = {{ .Arg .Namespace }} AND
|
||||
{{ .Ident "name" }} = {{ .Arg .Name }} AND
|
||||
{{ .Ident "active" }} = true
|
||||
;
|
|
@ -0,0 +1,8 @@
|
|||
UPDATE
|
||||
{{ .Ident "secret_secure_value" }}
|
||||
SET
|
||||
{{ .Ident "active" }} = ({{ .Ident "version" }} = {{ .Arg .Version}})
|
||||
WHERE
|
||||
{{ .Ident "namespace" }} = {{ .Arg .Namespace }} AND
|
||||
{{ .Ident "name" }} = {{ .Arg .Name }}
|
||||
;
|
|
@ -0,0 +1,9 @@
|
|||
UPDATE
|
||||
{{ .Ident "secret_secure_value" }}
|
||||
SET
|
||||
{{ .Ident "active" }} = false
|
||||
WHERE
|
||||
{{ .Ident "namespace" }} = {{ .Arg .Namespace }} AND
|
||||
{{ .Ident "name" }} = {{ .Arg .Name }} AND
|
||||
{{ .Ident "version" }} = {{ .Arg .Version }}
|
||||
;
|
|
@ -1,30 +0,0 @@
|
|||
UPDATE
|
||||
{{ .Ident "secret_secure_value" }}
|
||||
SET
|
||||
{{ .Ident "guid" }} = {{ .Arg .Row.GUID }},
|
||||
{{ .Ident "name" }} = {{ .Arg .Row.Name }},
|
||||
{{ .Ident "namespace" }} = {{ .Arg .Row.Namespace }},
|
||||
{{ .Ident "annotations" }} = {{ .Arg .Row.Annotations }},
|
||||
{{ .Ident "labels" }} = {{ .Arg .Row.Labels }},
|
||||
{{ .Ident "created" }} = {{ .Arg .Row.Created }},
|
||||
{{ .Ident "created_by" }} = {{ .Arg .Row.CreatedBy }},
|
||||
{{ .Ident "updated" }} = {{ .Arg .Row.Updated }},
|
||||
{{ .Ident "updated_by" }} = {{ .Arg .Row.UpdatedBy }},
|
||||
{{ .Ident "status_phase" }} = {{ .Arg .Row.Phase }},
|
||||
{{ if .Row.Message.Valid }}
|
||||
{{ .Ident "status_message" }} = {{ .Arg .Row.Message.String }},
|
||||
{{ end }}
|
||||
{{ .Ident "description" }} = {{ .Arg .Row.Description }},
|
||||
{{ if .Row.Keeper.Valid }}
|
||||
{{ .Ident "keeper" }} = {{ .Arg .Row.Keeper.String }},
|
||||
{{ end }}
|
||||
{{ if .Row.Decrypters.Valid }}
|
||||
{{ .Ident "decrypters" }} = {{ .Arg .Row.Decrypters.String }},
|
||||
{{ end }}
|
||||
{{ if .Row.Ref.Valid }}
|
||||
{{ .Ident "ref" }} = {{ .Arg .Row.Ref.String }},
|
||||
{{ end }}
|
||||
{{ .Ident "external_id" }} = {{ .Arg .Row.ExternalID }}
|
||||
WHERE {{ .Ident "namespace" }} = {{ .Arg .Row.Namespace }} AND
|
||||
{{ .Ident "name" }} = {{ .Arg .Row.Name }}
|
||||
;
|
|
@ -2,6 +2,8 @@ UPDATE
|
|||
{{ .Ident "secret_secure_value" }}
|
||||
SET
|
||||
{{ .Ident "external_id" }} = {{ .Arg .ExternalID }}
|
||||
WHERE {{ .Ident "namespace" }} = {{ .Arg .Namespace }} AND
|
||||
{{ .Ident "name" }} = {{ .Arg .Name }}
|
||||
;
|
||||
WHERE
|
||||
{{ .Ident "namespace" }} = {{ .Arg .Namespace }} AND
|
||||
{{ .Ident "name" }} = {{ .Arg .Name }} AND
|
||||
{{ .Ident "version" }} = {{ .Arg .Version }}
|
||||
;
|
|
@ -1,8 +0,0 @@
|
|||
UPDATE
|
||||
{{ .Ident "secret_secure_value" }}
|
||||
SET
|
||||
{{ .Ident "status_phase" }} = {{ .Arg .Phase }},
|
||||
{{ .Ident "status_message" }} = {{ .Arg .Message }}
|
||||
WHERE {{ .Ident "namespace" }} = {{ .Arg .Namespace }} AND
|
||||
{{ .Ident "name" }} = {{ .Arg .Name }}
|
||||
;
|
|
@ -1,4 +1,4 @@
|
|||
package metadata
|
||||
package metadata_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
@ -7,23 +7,11 @@ import (
|
|||
"github.com/grafana/authlib/authn"
|
||||
"github.com/grafana/authlib/types"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.opentelemetry.io/otel/trace/noop"
|
||||
|
||||
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
||||
secretv0alpha1 "github.com/grafana/grafana/pkg/apis/secret/v0alpha1"
|
||||
"github.com/grafana/grafana/pkg/infra/usagestats"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/secret/contracts"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/secret/decrypt"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/secret/encryption"
|
||||
encryptionmanager "github.com/grafana/grafana/pkg/registry/apis/secret/encryption/manager"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/secret/secretkeeper"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/secret/xkube"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/storage/secret/database"
|
||||
encryptionstorage "github.com/grafana/grafana/pkg/storage/secret/encryption"
|
||||
"github.com/grafana/grafana/pkg/storage/secret/migrator"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/secret/testutils"
|
||||
)
|
||||
|
||||
func TestIntegrationDecrypt(t *testing.T) {
|
||||
|
@ -39,9 +27,9 @@ func TestIntegrationDecrypt(t *testing.T) {
|
|||
ctx, cancel := context.WithCancel(context.Background())
|
||||
t.Cleanup(cancel)
|
||||
|
||||
decryptSvc, _, _, _ := setupDecryptTestService(t, nil)
|
||||
sut := testutils.Setup(t)
|
||||
|
||||
exposed, err := decryptSvc.Decrypt(ctx, "default", "name")
|
||||
exposed, err := sut.DecryptStorage.Decrypt(ctx, "default", "name")
|
||||
require.Error(t, err)
|
||||
require.Empty(t, exposed)
|
||||
})
|
||||
|
@ -55,9 +43,11 @@ func TestIntegrationDecrypt(t *testing.T) {
|
|||
// Create auth context with proper permissions
|
||||
authCtx := createAuthContext(ctx, "default", []string{"secret.grafana.app/securevalues/group1:decrypt"}, "svc", types.TypeUser)
|
||||
|
||||
decryptSvc, _, _, _ := setupDecryptTestService(t, map[string]struct{}{"group1": {}})
|
||||
sut := testutils.Setup(t, testutils.WithMutateCfg(func(sc *testutils.SetupConfig) {
|
||||
sc.AllowList = map[string]struct{}{"group1": {}}
|
||||
}))
|
||||
|
||||
exposed, err := decryptSvc.Decrypt(authCtx, "default", "non-existent-value")
|
||||
exposed, err := sut.DecryptStorage.Decrypt(authCtx, "default", "non-existent-value")
|
||||
require.ErrorIs(t, err, contracts.ErrDecryptNotFound)
|
||||
require.Empty(t, exposed)
|
||||
})
|
||||
|
@ -78,7 +68,9 @@ func TestIntegrationDecrypt(t *testing.T) {
|
|||
allowList := map[string]struct{}{"allowed-group": {}}
|
||||
|
||||
// Setup service
|
||||
decryptSvc, secureValueMetadataStorage, keeperService, keeperMetadataService := setupDecryptTestService(t, allowList)
|
||||
sut := testutils.Setup(t, testutils.WithMutateCfg(func(sc *testutils.SetupConfig) {
|
||||
sc.AllowList = allowList
|
||||
}))
|
||||
|
||||
// Create a secure value that is not in the allowlist
|
||||
spec := secretv0alpha1.SecureValueSpec{
|
||||
|
@ -90,9 +82,10 @@ func TestIntegrationDecrypt(t *testing.T) {
|
|||
sv.Name = svName
|
||||
sv.Namespace = "default"
|
||||
|
||||
newTestSecureValue(authCtx, t, secureValueMetadataStorage, keeperService, keeperMetadataService, sv, "actor-uid")
|
||||
_, err := sut.CreateSv(authCtx, testutils.CreateSvWithSv(sv))
|
||||
require.NoError(t, err)
|
||||
|
||||
exposed, err := decryptSvc.Decrypt(authCtx, "default", svName)
|
||||
exposed, err := sut.DecryptStorage.Decrypt(authCtx, "default", svName)
|
||||
require.ErrorIs(t, err, contracts.ErrDecryptNotAuthorized)
|
||||
require.Empty(t, exposed)
|
||||
})
|
||||
|
@ -112,7 +105,9 @@ func TestIntegrationDecrypt(t *testing.T) {
|
|||
allowList := map[string]struct{}{svcIdentity: {}}
|
||||
|
||||
// Setup service
|
||||
decryptSvc, secureValueMetadataStorage, keeperService, keeperMetadataService := setupDecryptTestService(t, allowList)
|
||||
sut := testutils.Setup(t, testutils.WithMutateCfg(func(sc *testutils.SetupConfig) {
|
||||
sc.AllowList = allowList
|
||||
}))
|
||||
|
||||
// Create a secure value that is in the allowlist
|
||||
spec := secretv0alpha1.SecureValueSpec{
|
||||
|
@ -124,9 +119,10 @@ func TestIntegrationDecrypt(t *testing.T) {
|
|||
sv.Name = "sv-test"
|
||||
sv.Namespace = "default"
|
||||
|
||||
newTestSecureValue(authCtx, t, secureValueMetadataStorage, keeperService, keeperMetadataService, sv, "actor-uid")
|
||||
_, err := sut.CreateSv(authCtx, testutils.CreateSvWithSv(sv))
|
||||
require.NoError(t, err)
|
||||
|
||||
exposed, err := decryptSvc.Decrypt(authCtx, "default", "sv-test")
|
||||
exposed, err := sut.DecryptStorage.Decrypt(authCtx, "default", "sv-test")
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, exposed)
|
||||
require.Equal(t, "value", exposed.DangerouslyExposeAndConsumeValue())
|
||||
|
@ -148,7 +144,9 @@ func TestIntegrationDecrypt(t *testing.T) {
|
|||
allowList := map[string]struct{}{svcIdentity: {}}
|
||||
|
||||
// Setup service
|
||||
decryptSvc, secureValueMetadataStorage, keeperService, keeperMetadataService := setupDecryptTestService(t, allowList)
|
||||
sut := testutils.Setup(t, testutils.WithMutateCfg(func(sc *testutils.SetupConfig) {
|
||||
sc.AllowList = allowList
|
||||
}))
|
||||
|
||||
// Create a secure value that is in the allowlist
|
||||
spec := secretv0alpha1.SecureValueSpec{
|
||||
|
@ -160,9 +158,10 @@ func TestIntegrationDecrypt(t *testing.T) {
|
|||
sv.Name = svName
|
||||
sv.Namespace = "default"
|
||||
|
||||
newTestSecureValue(authCtx, t, secureValueMetadataStorage, keeperService, keeperMetadataService, sv, "actor-uid")
|
||||
_, err := sut.CreateSv(authCtx, testutils.CreateSvWithSv(sv))
|
||||
require.NoError(t, err)
|
||||
|
||||
exposed, err := decryptSvc.Decrypt(authCtx, "default", svName)
|
||||
exposed, err := sut.DecryptStorage.Decrypt(authCtx, "default", svName)
|
||||
require.ErrorIs(t, err, contracts.ErrDecryptNotAuthorized)
|
||||
require.Empty(t, exposed)
|
||||
})
|
||||
|
@ -179,7 +178,7 @@ func TestIntegrationDecrypt(t *testing.T) {
|
|||
authCtx := createAuthContext(ctx, "default", []string{"secret.grafana.app/securevalues"}, svcIdentity, types.TypeUser)
|
||||
|
||||
// Setup service
|
||||
decryptSvc, secureValueMetadataStorage, keeperService, keeperMetadataService := setupDecryptTestService(t, map[string]struct{}{svcIdentity: {}})
|
||||
sut := testutils.Setup(t)
|
||||
|
||||
// Create a secure value
|
||||
spec := secretv0alpha1.SecureValueSpec{
|
||||
|
@ -191,9 +190,10 @@ func TestIntegrationDecrypt(t *testing.T) {
|
|||
sv.Name = "sv-test"
|
||||
sv.Namespace = "default"
|
||||
|
||||
newTestSecureValue(authCtx, t, secureValueMetadataStorage, keeperService, keeperMetadataService, sv, "actor-uid")
|
||||
_, err := sut.CreateSv(authCtx, testutils.CreateSvWithSv(sv))
|
||||
require.NoError(t, err)
|
||||
|
||||
exposed, err := decryptSvc.Decrypt(authCtx, "default", "sv-test")
|
||||
exposed, err := sut.DecryptStorage.Decrypt(authCtx, "default", "sv-test")
|
||||
require.ErrorIs(t, err, contracts.ErrDecryptNotAuthorized)
|
||||
require.Empty(t, exposed)
|
||||
})
|
||||
|
@ -211,7 +211,7 @@ func TestIntegrationDecrypt(t *testing.T) {
|
|||
authCtx := createAuthContext(ctx, "default", []string{"secret.grafana.app/securevalues/" + svName + ":read"}, svcIdentity, types.TypeUser)
|
||||
|
||||
// Setup service
|
||||
decryptSvc, secureValueMetadataStorage, keeperService, keeperMetadataService := setupDecryptTestService(t, map[string]struct{}{svcIdentity: {}})
|
||||
sut := testutils.Setup(t)
|
||||
|
||||
// Create a secure value
|
||||
spec := secretv0alpha1.SecureValueSpec{
|
||||
|
@ -223,9 +223,10 @@ func TestIntegrationDecrypt(t *testing.T) {
|
|||
sv.Name = svName
|
||||
sv.Namespace = "default"
|
||||
|
||||
newTestSecureValue(authCtx, t, secureValueMetadataStorage, keeperService, keeperMetadataService, sv, "actor-uid")
|
||||
_, err := sut.CreateSv(authCtx, testutils.CreateSvWithSv(sv))
|
||||
require.NoError(t, err)
|
||||
|
||||
exposed, err := decryptSvc.Decrypt(authCtx, "default", svName)
|
||||
exposed, err := sut.DecryptStorage.Decrypt(authCtx, "default", svName)
|
||||
require.ErrorIs(t, err, contracts.ErrDecryptNotAuthorized)
|
||||
require.Empty(t, exposed)
|
||||
})
|
||||
|
@ -242,7 +243,7 @@ func TestIntegrationDecrypt(t *testing.T) {
|
|||
authCtx := createAuthContext(ctx, "default", []string{"secret.grafana.app/securevalues/:decrypt"}, svcIdentity, types.TypeUser)
|
||||
|
||||
// Setup service
|
||||
decryptSvc, secureValueMetadataStorage, keeperService, keeperMetadataService := setupDecryptTestService(t, map[string]struct{}{svcIdentity: {}})
|
||||
sut := testutils.Setup(t)
|
||||
|
||||
// Create a secure value
|
||||
spec := secretv0alpha1.SecureValueSpec{
|
||||
|
@ -254,9 +255,10 @@ func TestIntegrationDecrypt(t *testing.T) {
|
|||
sv.Name = "sv-test"
|
||||
sv.Namespace = "default"
|
||||
|
||||
newTestSecureValue(authCtx, t, secureValueMetadataStorage, keeperService, keeperMetadataService, sv, "actor-uid")
|
||||
_, err := sut.CreateSv(authCtx, testutils.CreateSvWithSv(sv))
|
||||
require.NoError(t, err)
|
||||
|
||||
exposed, err := decryptSvc.Decrypt(authCtx, "default", "sv-test")
|
||||
exposed, err := sut.DecryptStorage.Decrypt(authCtx, "default", "sv-test")
|
||||
require.ErrorIs(t, err, contracts.ErrDecryptNotAuthorized)
|
||||
require.Empty(t, exposed)
|
||||
})
|
||||
|
@ -274,7 +276,7 @@ func TestIntegrationDecrypt(t *testing.T) {
|
|||
authCtx := createAuthContext(ctx, "default", []string{"wrong.group/securevalues/" + svName + ":decrypt"}, svcIdentity, types.TypeUser)
|
||||
|
||||
// Setup service
|
||||
decryptSvc, secureValueMetadataStorage, keeperService, keeperMetadataService := setupDecryptTestService(t, map[string]struct{}{svcIdentity: {}})
|
||||
sut := testutils.Setup(t)
|
||||
|
||||
// Create a secure value
|
||||
spec := secretv0alpha1.SecureValueSpec{
|
||||
|
@ -286,9 +288,10 @@ func TestIntegrationDecrypt(t *testing.T) {
|
|||
sv.Name = svName
|
||||
sv.Namespace = "default"
|
||||
|
||||
newTestSecureValue(authCtx, t, secureValueMetadataStorage, keeperService, keeperMetadataService, sv, "actor-uid")
|
||||
_, err := sut.CreateSv(authCtx, testutils.CreateSvWithSv(sv))
|
||||
require.NoError(t, err)
|
||||
|
||||
exposed, err := decryptSvc.Decrypt(authCtx, "default", svName)
|
||||
exposed, err := sut.DecryptStorage.Decrypt(authCtx, "default", svName)
|
||||
require.Error(t, err)
|
||||
require.Equal(t, err.Error(), "not authorized")
|
||||
require.Empty(t, exposed)
|
||||
|
@ -297,62 +300,6 @@ func TestIntegrationDecrypt(t *testing.T) {
|
|||
// TODO: add more tests for keeper failure scenarios, lets see how the async work will change this though.
|
||||
}
|
||||
|
||||
func setupDecryptTestService(t *testing.T, allowList map[string]struct{}) (*decryptStorage, contracts.SecureValueMetadataStorage, *secretkeeper.OSSKeeperService, contracts.KeeperMetadataStorage) {
|
||||
t.Helper()
|
||||
|
||||
// Initialize infra dependencies
|
||||
cfg := &setting.Cfg{
|
||||
SecretsManagement: setting.SecretsManagerSettings{
|
||||
SecretKey: "sdDkslslld",
|
||||
EncryptionProvider: "secretKey.v1",
|
||||
},
|
||||
}
|
||||
|
||||
features := featuremgmt.WithFeatures(
|
||||
featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs,
|
||||
featuremgmt.FlagSecretsManagementAppPlatform,
|
||||
)
|
||||
|
||||
db := sqlstore.NewTestStore(t, sqlstore.WithMigrator(migrator.New()))
|
||||
tracer := noop.NewTracerProvider().Tracer("test")
|
||||
database := database.ProvideDatabase(db, tracer)
|
||||
|
||||
// Initialize encryption manager and storage
|
||||
dataKeyStore, err := encryptionstorage.ProvideDataKeyStorage(database, tracer, features, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
encValueStore, err := encryptionstorage.ProvideEncryptedValueStorage(database, tracer, features)
|
||||
require.NoError(t, err)
|
||||
|
||||
encryptionManager, err := encryptionmanager.ProvideEncryptionManager(
|
||||
tracer,
|
||||
dataKeyStore,
|
||||
cfg,
|
||||
&usagestats.UsageStatsMock{},
|
||||
encryption.ProviderMap{},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Initialize the keeper service
|
||||
keeperService, err := secretkeeper.ProvideService(tracer, encValueStore, encryptionManager, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
keeperMetadataStorage, err := ProvideKeeperMetadataStorage(database, tracer, features, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Initialize the secure value storage
|
||||
secureValueMetadataStorage, err := ProvideSecureValueMetadataStorage(database, tracer, features, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
decryptAuthorizer := decrypt.ProvideDecryptAuthorizer(tracer, allowList)
|
||||
|
||||
// Initialize the decrypt storage
|
||||
decryptSvc, err := ProvideDecryptStorage(features, tracer, keeperService, keeperMetadataStorage, secureValueMetadataStorage, decryptAuthorizer, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
return decryptSvc.(*decryptStorage), secureValueMetadataStorage, keeperService, keeperMetadataStorage
|
||||
}
|
||||
|
||||
func createAuthContext(ctx context.Context, namespace string, permissions []string, svc string, identityType types.IdentityType) context.Context {
|
||||
requester := &identity.StaticRequester{
|
||||
Type: identityType,
|
||||
|
@ -371,32 +318,3 @@ func createAuthContext(ctx context.Context, namespace string, permissions []stri
|
|||
|
||||
return types.WithAuthInfo(ctx, requester)
|
||||
}
|
||||
|
||||
// This helper will also delete the secureValue from the db when the test is done.
|
||||
func newTestSecureValue(ctx context.Context, t *testing.T, db contracts.SecureValueMetadataStorage, keeperService *secretkeeper.OSSKeeperService, keeperMetadataStorage contracts.KeeperMetadataStorage, sv *secretv0alpha1.SecureValue, actorUID string) {
|
||||
t.Helper()
|
||||
|
||||
_, err := db.Create(ctx, sv, actorUID)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.NoError(t, err)
|
||||
|
||||
// Since creating secrets is async, store the secret in the keeper synchronously to make testing easier
|
||||
cfg, err := keeperMetadataStorage.GetKeeperConfig(ctx, sv.Namespace, sv.Spec.Keeper, contracts.ReadOpts{})
|
||||
require.NoError(t, err)
|
||||
|
||||
keeper, err := keeperService.KeeperForConfig(cfg)
|
||||
require.NoError(t, err)
|
||||
|
||||
externalID, err := keeper.Store(ctx, cfg, sv.Namespace, sv.Spec.Value.DangerouslyExposeAndConsumeValue())
|
||||
require.NoError(t, err)
|
||||
|
||||
// Set external id for the secure value
|
||||
err = db.SetExternalID(ctx, xkube.Namespace(sv.Namespace), sv.Name, externalID)
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Cleanup(func() {
|
||||
require.NoError(t, keeper.Delete(ctx, cfg, sv.Namespace, externalID))
|
||||
require.NoError(t, db.Delete(ctx, xkube.Namespace(sv.Namespace), sv.Name))
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package metadata
|
||||
package metadata_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
@ -10,6 +10,7 @@ import (
|
|||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||
"github.com/grafana/grafana/pkg/storage/secret/database"
|
||||
"github.com/grafana/grafana/pkg/storage/secret/metadata"
|
||||
"github.com/grafana/grafana/pkg/storage/secret/migrator"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.opentelemetry.io/otel/trace/noop"
|
||||
|
@ -340,7 +341,7 @@ func initStorage(t *testing.T) contracts.KeeperMetadataStorage {
|
|||
features := featuremgmt.WithFeatures(featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs, featuremgmt.FlagSecretsManagementAppPlatform)
|
||||
|
||||
// Initialize the keeper storage
|
||||
keeperMetadataStorage, err := ProvideKeeperMetadataStorage(db, tracer, features, nil)
|
||||
keeperMetadataStorage, err := metadata.ProvideKeeperMetadataStorage(db, tracer, features, nil)
|
||||
require.NoError(t, err)
|
||||
return keeperMetadataStorage
|
||||
}
|
||||
|
|
|
@ -13,16 +13,6 @@ const (
|
|||
|
||||
// StorageMetrics is a struct that contains all the metrics for all operations of secrets storage.
|
||||
type StorageMetrics struct {
|
||||
OutboxAppendDuration *prometheus.HistogramVec
|
||||
OutboxReceiveDuration prometheus.Histogram
|
||||
OutboxAppendCount *prometheus.CounterVec
|
||||
OutboxReceiveCount prometheus.Counter
|
||||
OutboxDeleteDuration prometheus.Histogram
|
||||
OutboxDeleteCount prometheus.Counter
|
||||
OutboxIncrementReceiveCountDuration prometheus.Histogram
|
||||
OutboxIncrementReceiveCountCount prometheus.Counter
|
||||
OutboxTotalMessageLifetimeDuration *prometheus.HistogramVec
|
||||
|
||||
KeeperMetadataCreateDuration *prometheus.HistogramVec
|
||||
KeeperMetadataCreateCount *prometheus.CounterVec
|
||||
KeeperMetadataUpdateDuration *prometheus.HistogramVec
|
||||
|
@ -55,67 +45,6 @@ type StorageMetrics struct {
|
|||
|
||||
func newStorageMetrics() *StorageMetrics {
|
||||
return &StorageMetrics{
|
||||
// Outbox metrics
|
||||
OutboxAppendDuration: prometheus.NewHistogramVec(prometheus.HistogramOpts{
|
||||
Namespace: namespace,
|
||||
Subsystem: subsystem,
|
||||
Name: "outbox_append_duration_seconds",
|
||||
Help: "Duration of outbox message append operations",
|
||||
Buckets: prometheus.DefBuckets,
|
||||
}, []string{"message_type"}),
|
||||
OutboxAppendCount: prometheus.NewCounterVec(prometheus.CounterOpts{
|
||||
Namespace: namespace,
|
||||
Subsystem: subsystem,
|
||||
Name: "outbox_append_count",
|
||||
Help: "Count of outbox message append operations",
|
||||
}, []string{"message_type"}),
|
||||
OutboxReceiveDuration: prometheus.NewHistogram(prometheus.HistogramOpts{
|
||||
Namespace: namespace,
|
||||
Subsystem: subsystem,
|
||||
Name: "outbox_receive_duration_seconds",
|
||||
Help: "Duration of outbox message receive operations",
|
||||
Buckets: prometheus.DefBuckets,
|
||||
}),
|
||||
OutboxReceiveCount: prometheus.NewCounter(prometheus.CounterOpts{
|
||||
Namespace: namespace,
|
||||
Subsystem: subsystem,
|
||||
Name: "outbox_receive_count",
|
||||
Help: "Count of outbox message receive operations",
|
||||
}),
|
||||
OutboxDeleteDuration: prometheus.NewHistogram(prometheus.HistogramOpts{
|
||||
Namespace: namespace,
|
||||
Subsystem: subsystem,
|
||||
Name: "outbox_delete_duration_seconds",
|
||||
Help: "Duration of outbox message delete operations",
|
||||
Buckets: prometheus.DefBuckets,
|
||||
}),
|
||||
OutboxDeleteCount: prometheus.NewCounter(prometheus.CounterOpts{
|
||||
Namespace: namespace,
|
||||
Subsystem: subsystem,
|
||||
Name: "outbox_delete_count",
|
||||
Help: "Count of outbox message delete operations",
|
||||
}),
|
||||
OutboxIncrementReceiveCountDuration: prometheus.NewHistogram(prometheus.HistogramOpts{
|
||||
Namespace: namespace,
|
||||
Subsystem: subsystem,
|
||||
Name: "outbox_increment_receive_count_duration_seconds",
|
||||
Help: "Duration of outbox message increment receive count operations",
|
||||
Buckets: prometheus.DefBuckets,
|
||||
}),
|
||||
OutboxIncrementReceiveCountCount: prometheus.NewCounter(prometheus.CounterOpts{
|
||||
Namespace: namespace,
|
||||
Subsystem: subsystem,
|
||||
Name: "outbox_increment_receive_count_count",
|
||||
Help: "Count of outbox message increment receive count operations",
|
||||
}),
|
||||
OutboxTotalMessageLifetimeDuration: prometheus.NewHistogramVec(prometheus.HistogramOpts{
|
||||
Namespace: namespace,
|
||||
Subsystem: subsystem,
|
||||
Name: "outbox_total_message_lifetime_duration_seconds",
|
||||
Help: "Total duration of outbox message lifetime",
|
||||
Buckets: prometheus.DefBuckets,
|
||||
}, []string{"message_type"}),
|
||||
|
||||
// Keeper metrics
|
||||
KeeperMetadataCreateDuration: prometheus.NewHistogramVec(prometheus.HistogramOpts{
|
||||
Namespace: namespace,
|
||||
|
@ -307,15 +236,6 @@ func NewStorageMetrics(reg prometheus.Registerer) *StorageMetrics {
|
|||
|
||||
if reg != nil {
|
||||
reg.MustRegister(
|
||||
m.OutboxAppendDuration,
|
||||
m.OutboxAppendCount,
|
||||
m.OutboxReceiveDuration,
|
||||
m.OutboxReceiveCount,
|
||||
m.OutboxDeleteDuration,
|
||||
m.OutboxDeleteCount,
|
||||
m.OutboxIncrementReceiveCountDuration,
|
||||
m.OutboxIncrementReceiveCountCount,
|
||||
m.OutboxTotalMessageLifetimeDuration,
|
||||
m.KeeperMetadataCreateDuration,
|
||||
m.KeeperMetadataCreateCount,
|
||||
m.KeeperMetadataUpdateDuration,
|
||||
|
|
|
@ -1,397 +0,0 @@
|
|||
package metadata
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/storage/secret/metadata/metrics"
|
||||
unifiedsql "github.com/grafana/grafana/pkg/storage/unified/sql"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/codes"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
|
||||
"github.com/grafana/grafana/pkg/registry/apis/secret/assert"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/secret/contracts"
|
||||
"github.com/grafana/grafana/pkg/storage/unified/sql/sqltemplate"
|
||||
)
|
||||
|
||||
type outboxStore struct {
|
||||
db contracts.Database
|
||||
dialect sqltemplate.Dialect
|
||||
tracer trace.Tracer
|
||||
metrics *metrics.StorageMetrics
|
||||
}
|
||||
|
||||
func ProvideOutboxQueue(
|
||||
db contracts.Database,
|
||||
tracer trace.Tracer,
|
||||
reg prometheus.Registerer,
|
||||
) contracts.OutboxQueue {
|
||||
return &outboxStore{
|
||||
db: db,
|
||||
dialect: sqltemplate.DialectForDriver(db.DriverName()),
|
||||
metrics: metrics.NewStorageMetrics(reg),
|
||||
tracer: tracer,
|
||||
}
|
||||
}
|
||||
|
||||
type outboxMessageDB struct {
|
||||
RequestID string
|
||||
MessageID int64
|
||||
MessageType contracts.OutboxMessageType
|
||||
Name string
|
||||
Namespace string
|
||||
EncryptedSecret sql.NullString
|
||||
KeeperName sql.NullString
|
||||
ExternalID sql.NullString
|
||||
ReceiveCount int
|
||||
Created int64
|
||||
}
|
||||
|
||||
func (s *outboxStore) Append(ctx context.Context, input contracts.AppendOutboxMessage) (messageID int64, err error) {
|
||||
ctx, span := s.tracer.Start(ctx, "outboxStore.Append", trace.WithAttributes(
|
||||
attribute.String("name", input.Name),
|
||||
attribute.String("namespace", input.Namespace),
|
||||
attribute.String("type", string(input.Type)),
|
||||
attribute.String("requestID", input.RequestID),
|
||||
))
|
||||
defer span.End()
|
||||
|
||||
defer func() {
|
||||
if err != nil {
|
||||
span.SetStatus(codes.Error, "failed to append outbox message")
|
||||
span.RecordError(err)
|
||||
}
|
||||
|
||||
if messageID != 0 {
|
||||
span.SetAttributes(attribute.Int64("messageID", messageID))
|
||||
}
|
||||
}()
|
||||
|
||||
assert.True(input.Type != "", "outboxStore.Append: outbox message type is required")
|
||||
|
||||
start := time.Now()
|
||||
messageID, err = s.insertMessage(ctx, input)
|
||||
if err != nil {
|
||||
return messageID, fmt.Errorf("inserting message into outbox table: %+w", err)
|
||||
}
|
||||
|
||||
s.metrics.OutboxAppendDuration.WithLabelValues(string(input.Type)).Observe(time.Since(start).Seconds())
|
||||
s.metrics.OutboxAppendCount.WithLabelValues(string(input.Type)).Inc()
|
||||
|
||||
return messageID, nil
|
||||
}
|
||||
|
||||
func (s *outboxStore) insertMessage(ctx context.Context, input contracts.AppendOutboxMessage) (int64, error) {
|
||||
keeperName := sql.NullString{}
|
||||
if input.KeeperName != nil {
|
||||
keeperName = sql.NullString{
|
||||
Valid: true,
|
||||
String: *input.KeeperName,
|
||||
}
|
||||
}
|
||||
|
||||
externalID := sql.NullString{}
|
||||
if input.ExternalID != nil {
|
||||
externalID = sql.NullString{
|
||||
Valid: true,
|
||||
String: *input.ExternalID,
|
||||
}
|
||||
}
|
||||
|
||||
encryptedSecret := sql.NullString{}
|
||||
if input.Type == contracts.CreateSecretOutboxMessage || input.Type == contracts.UpdateSecretOutboxMessage {
|
||||
encryptedSecret = sql.NullString{
|
||||
Valid: true,
|
||||
String: input.EncryptedSecret,
|
||||
}
|
||||
}
|
||||
|
||||
req := appendSecureValueOutbox{
|
||||
SQLTemplate: sqltemplate.New(s.dialect),
|
||||
Row: &outboxMessageDB{
|
||||
RequestID: input.RequestID,
|
||||
MessageType: input.Type,
|
||||
Name: input.Name,
|
||||
Namespace: input.Namespace,
|
||||
EncryptedSecret: encryptedSecret,
|
||||
KeeperName: keeperName,
|
||||
ExternalID: externalID,
|
||||
ReceiveCount: 0,
|
||||
Created: time.Now().UTC().UnixMilli(),
|
||||
},
|
||||
}
|
||||
|
||||
query, err := sqltemplate.Execute(sqlSecureValueOutboxAppend, req)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("execute template %q: %w", sqlSecureValueOutboxAppend.Name(), err)
|
||||
}
|
||||
|
||||
result, err := s.db.ExecContext(ctx, query, req.GetArgs()...)
|
||||
if err != nil {
|
||||
if unifiedsql.IsRowAlreadyExistsError(err) {
|
||||
return 0, contracts.ErrSecureValueOperationInProgress
|
||||
}
|
||||
return 0, fmt.Errorf("inserting message into secure value outbox table: %w", err)
|
||||
}
|
||||
|
||||
rowsAffected, err := result.RowsAffected()
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("get rows affected: %w", err)
|
||||
}
|
||||
|
||||
if rowsAffected != 1 {
|
||||
return 0, fmt.Errorf("expected to affect 1 row, but affected %d", rowsAffected)
|
||||
}
|
||||
|
||||
id, err := result.LastInsertId()
|
||||
if err != nil {
|
||||
return id, fmt.Errorf("fetching last inserted id: %w", err)
|
||||
}
|
||||
|
||||
return id, nil
|
||||
}
|
||||
|
||||
func (s *outboxStore) ReceiveN(ctx context.Context, limit uint) ([]contracts.OutboxMessage, error) {
|
||||
messageIDs, err := s.fetchMessageIdsInQueue(ctx, limit)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("fetching message ids from queue: %w", err)
|
||||
}
|
||||
// If queue is empty
|
||||
if len(messageIDs) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
req := receiveNSecureValueOutbox{
|
||||
SQLTemplate: sqltemplate.New(s.dialect),
|
||||
MessageIDs: messageIDs,
|
||||
}
|
||||
|
||||
start := time.Now()
|
||||
query, err := sqltemplate.Execute(sqlSecureValueOutboxReceiveN, req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("execute template %q: %w", sqlSecureValueOutboxReceiveN.Name(), err)
|
||||
}
|
||||
|
||||
rows, err := s.db.QueryContext(ctx, query, req.GetArgs()...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("fetching rows from secure value outbox table: %w", err)
|
||||
}
|
||||
defer func() { _ = rows.Close() }()
|
||||
|
||||
messages := make([]contracts.OutboxMessage, 0)
|
||||
|
||||
for rows.Next() {
|
||||
var row outboxMessageDB
|
||||
if err := rows.Scan(
|
||||
&row.RequestID,
|
||||
&row.MessageID,
|
||||
&row.MessageType,
|
||||
&row.Name,
|
||||
&row.Namespace,
|
||||
&row.EncryptedSecret,
|
||||
&row.KeeperName,
|
||||
&row.ExternalID,
|
||||
&row.ReceiveCount,
|
||||
&row.Created,
|
||||
); err != nil {
|
||||
return nil, fmt.Errorf("scanning row from secure value outbox table: %w", err)
|
||||
}
|
||||
|
||||
var keeperName *string
|
||||
if row.KeeperName.Valid {
|
||||
keeperName = &row.KeeperName.String
|
||||
}
|
||||
|
||||
var externalID *string
|
||||
if row.ExternalID.Valid {
|
||||
externalID = &row.ExternalID.String
|
||||
}
|
||||
|
||||
msg := contracts.OutboxMessage{
|
||||
RequestID: row.RequestID,
|
||||
Type: row.MessageType,
|
||||
MessageID: row.MessageID,
|
||||
Name: row.Name,
|
||||
Namespace: row.Namespace,
|
||||
KeeperName: keeperName,
|
||||
ExternalID: externalID,
|
||||
ReceiveCount: row.ReceiveCount,
|
||||
Created: row.Created,
|
||||
}
|
||||
|
||||
if row.MessageType != contracts.DeleteSecretOutboxMessage && row.EncryptedSecret.Valid {
|
||||
msg.EncryptedSecret = row.EncryptedSecret.String
|
||||
}
|
||||
|
||||
messages = append(messages, msg)
|
||||
}
|
||||
|
||||
if err := rows.Err(); err != nil {
|
||||
return messages, fmt.Errorf("reading rows: %w", err)
|
||||
}
|
||||
|
||||
s.metrics.OutboxReceiveDuration.Observe(time.Since(start).Seconds())
|
||||
s.metrics.OutboxReceiveCount.Add(float64(len(messages)))
|
||||
|
||||
return messages, nil
|
||||
}
|
||||
|
||||
func (s *outboxStore) fetchMessageIdsInQueue(ctx context.Context, limit uint) ([]int64, error) {
|
||||
req := fetchMessageIDsOutbox{
|
||||
SQLTemplate: sqltemplate.New(s.dialect),
|
||||
ReceiveLimit: limit,
|
||||
}
|
||||
|
||||
query, err := sqltemplate.Execute(sqlSecureValueOutboxFetchMessageIDs, req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("execute template %q: %w", sqlSecureValueOutboxFetchMessageIDs.Name(), err)
|
||||
}
|
||||
|
||||
rows, err := s.db.QueryContext(ctx, query, req.GetArgs()...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("fetching rows from secure value outbox table: %w", err)
|
||||
}
|
||||
defer func() { _ = rows.Close() }()
|
||||
|
||||
messageIDs := make([]int64, 0, limit)
|
||||
|
||||
for rows.Next() {
|
||||
var id int64
|
||||
if err := rows.Scan(&id); err != nil {
|
||||
return nil, fmt.Errorf("scanning row; %w", err)
|
||||
}
|
||||
messageIDs = append(messageIDs, id)
|
||||
}
|
||||
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, fmt.Errorf("reading rows: %w", err)
|
||||
}
|
||||
|
||||
return messageIDs, nil
|
||||
}
|
||||
|
||||
func (s *outboxStore) Delete(ctx context.Context, messageID int64) (err error) {
|
||||
ctx, span := s.tracer.Start(ctx, "outboxStore.Append", trace.WithAttributes(
|
||||
attribute.Int64("messageID", messageID),
|
||||
))
|
||||
defer span.End()
|
||||
|
||||
defer func() {
|
||||
if err != nil {
|
||||
span.SetStatus(codes.Error, "failed to delete message from outbox")
|
||||
span.RecordError(err)
|
||||
}
|
||||
}()
|
||||
|
||||
assert.True(messageID != 0, "outboxStore.Delete: messageID is required")
|
||||
|
||||
start := time.Now()
|
||||
if err := s.deleteMessage(ctx, messageID); err != nil {
|
||||
return fmt.Errorf("deleting message from outbox table %+w", err)
|
||||
}
|
||||
|
||||
s.metrics.OutboxDeleteDuration.Observe(time.Since(start).Seconds())
|
||||
s.metrics.OutboxDeleteCount.Inc()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *outboxStore) deleteMessage(ctx context.Context, messageID int64) error {
|
||||
tsReq := getOutboxMessageTimestamp{
|
||||
SQLTemplate: sqltemplate.New(s.dialect),
|
||||
MessageID: messageID,
|
||||
}
|
||||
|
||||
// First query the object so we can get the timestamp and calculate the total lifetime
|
||||
timestampQuery, err := sqltemplate.Execute(sqlSecureValueOutboxQueryTimestamp, tsReq)
|
||||
if err != nil {
|
||||
return fmt.Errorf("execute template %q: %w", sqlSecureValueOutboxQueryTimestamp.Name(), err)
|
||||
}
|
||||
|
||||
rows, err := s.db.QueryContext(ctx, timestampQuery, tsReq.GetArgs()...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("querying timestamp from secure value outbox table: %w", err)
|
||||
}
|
||||
|
||||
if !rows.Next() {
|
||||
_ = rows.Close()
|
||||
return fmt.Errorf("no row found for message id=%v", messageID)
|
||||
}
|
||||
|
||||
var timestamp int64
|
||||
var messageType string
|
||||
if err := rows.Scan(×tamp, &messageType); err != nil {
|
||||
_ = rows.Close()
|
||||
return fmt.Errorf("scanning timestamp: %w", err)
|
||||
}
|
||||
|
||||
// Explicitly close rows and check for errors before proceeding
|
||||
if err := rows.Close(); err != nil {
|
||||
return fmt.Errorf("closing rows: %w", err)
|
||||
}
|
||||
|
||||
if err := rows.Err(); err != nil {
|
||||
return fmt.Errorf("rows error: %w", err)
|
||||
}
|
||||
|
||||
totalLifetime := time.Since(time.UnixMilli(timestamp))
|
||||
s.metrics.OutboxTotalMessageLifetimeDuration.WithLabelValues(messageType).Observe(totalLifetime.Seconds())
|
||||
|
||||
// Then delete the object
|
||||
delReq := deleteSecureValueOutbox{
|
||||
SQLTemplate: sqltemplate.New(s.dialect),
|
||||
MessageID: messageID,
|
||||
}
|
||||
|
||||
query, err := sqltemplate.Execute(sqlSecureValueOutboxDelete, delReq)
|
||||
if err != nil {
|
||||
return fmt.Errorf("execute template %q: %w", sqlSecureValueOutboxDelete.Name(), err)
|
||||
}
|
||||
|
||||
result, err := s.db.ExecContext(ctx, query, delReq.GetArgs()...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("deleting message id=%v from secure value outbox table: %w", messageID, err)
|
||||
}
|
||||
|
||||
rowsAffected, err := result.RowsAffected()
|
||||
if err != nil {
|
||||
return fmt.Errorf("get rows affected: %w", err)
|
||||
}
|
||||
|
||||
// TODO: Presumably it's a bug if we delete 0 rows?
|
||||
if rowsAffected > 1 {
|
||||
return fmt.Errorf("bug: deleted more than one row from the outbox table, should delete only one at a time: deleted=%v", rowsAffected)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *outboxStore) IncrementReceiveCount(ctx context.Context, messageIDs []int64) error {
|
||||
if len(messageIDs) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
req := incrementReceiveCountOutbox{
|
||||
SQLTemplate: sqltemplate.New(s.dialect),
|
||||
MessageIDs: messageIDs,
|
||||
}
|
||||
|
||||
start := time.Now()
|
||||
query, err := sqltemplate.Execute(sqlSecureValueOutboxUpdateReceiveCount, req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("execute template %q: %w", sqlSecureValueOutboxUpdateReceiveCount.Name(), err)
|
||||
}
|
||||
|
||||
_, err = s.db.ExecContext(ctx, query, req.GetArgs()...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("updating outbox messages receive count: %w", err)
|
||||
}
|
||||
|
||||
s.metrics.OutboxIncrementReceiveCountDuration.Observe(time.Since(start).Seconds())
|
||||
s.metrics.OutboxIncrementReceiveCountCount.Add(float64(len(messageIDs)))
|
||||
|
||||
return nil
|
||||
}
|
|
@ -1,269 +0,0 @@
|
|||
package metadata
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"slices"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/registry/apis/secret/contracts"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||
"github.com/grafana/grafana/pkg/storage/secret/database"
|
||||
"github.com/grafana/grafana/pkg/storage/secret/migrator"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.opentelemetry.io/otel/trace/noop"
|
||||
)
|
||||
|
||||
type outboxStoreModel struct {
|
||||
rows []contracts.OutboxMessage
|
||||
}
|
||||
|
||||
func newOutboxStoreModel() *outboxStoreModel {
|
||||
return &outboxStoreModel{}
|
||||
}
|
||||
|
||||
func (model *outboxStoreModel) Append(messageID int64, message contracts.AppendOutboxMessage) {
|
||||
model.rows = append(model.rows, contracts.OutboxMessage{
|
||||
Type: message.Type,
|
||||
MessageID: messageID,
|
||||
Name: message.Name,
|
||||
Namespace: message.Namespace,
|
||||
EncryptedSecret: message.EncryptedSecret,
|
||||
KeeperName: message.KeeperName,
|
||||
ExternalID: message.ExternalID,
|
||||
})
|
||||
}
|
||||
|
||||
func (model *outboxStoreModel) ReceiveN(n uint) []contracts.OutboxMessage {
|
||||
maxMessages := min(len(model.rows), int(n))
|
||||
if maxMessages == 0 {
|
||||
return nil
|
||||
}
|
||||
return model.rows[:maxMessages]
|
||||
}
|
||||
|
||||
func (model *outboxStoreModel) Delete(messageID int64) {
|
||||
oldLen := len(model.rows)
|
||||
model.rows = slices.DeleteFunc(model.rows, func(m contracts.OutboxMessage) bool {
|
||||
return m.MessageID == messageID
|
||||
})
|
||||
if len(model.rows) != oldLen-1 {
|
||||
panic("Delete: deleted more than one message")
|
||||
}
|
||||
}
|
||||
|
||||
func TestOutboxStoreModel(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
model := newOutboxStoreModel()
|
||||
|
||||
require.Empty(t, model.ReceiveN(10))
|
||||
|
||||
appendOutboxMessage := contracts.AppendOutboxMessage{
|
||||
Type: contracts.CreateSecretOutboxMessage,
|
||||
Name: "s-1",
|
||||
Namespace: "n-1",
|
||||
EncryptedSecret: "value",
|
||||
ExternalID: nil,
|
||||
}
|
||||
|
||||
outboxMessage1 := contracts.OutboxMessage{
|
||||
MessageID: 1,
|
||||
Type: contracts.CreateSecretOutboxMessage,
|
||||
Name: "s-1",
|
||||
Namespace: "n-1",
|
||||
EncryptedSecret: "value",
|
||||
ExternalID: nil,
|
||||
}
|
||||
|
||||
outboxMessage2 := contracts.OutboxMessage{
|
||||
MessageID: 2,
|
||||
Type: contracts.CreateSecretOutboxMessage,
|
||||
Name: "s-1",
|
||||
Namespace: "n-1",
|
||||
EncryptedSecret: "value",
|
||||
ExternalID: nil,
|
||||
}
|
||||
|
||||
model.Append(1, appendOutboxMessage)
|
||||
|
||||
require.Equal(t, []contracts.OutboxMessage{outboxMessage1}, model.ReceiveN(10))
|
||||
|
||||
model.Append(2, appendOutboxMessage)
|
||||
|
||||
require.Equal(t, []contracts.OutboxMessage{outboxMessage1, outboxMessage2}, model.ReceiveN(10))
|
||||
|
||||
model.Delete(outboxMessage1.MessageID)
|
||||
|
||||
require.Equal(t, []contracts.OutboxMessage{outboxMessage2}, model.ReceiveN(5))
|
||||
|
||||
model.Delete(outboxMessage2.MessageID)
|
||||
|
||||
require.Empty(t, model.ReceiveN(1))
|
||||
}
|
||||
|
||||
func TestOutboxStoreSecureValueOperationInProgress(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("Append returns error when the queue already contains an operation for the secure value", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testDB := sqlstore.NewTestStore(t, sqlstore.WithMigrator(migrator.New()))
|
||||
tracer := noop.NewTracerProvider().Tracer("test")
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
outbox := ProvideOutboxQueue(database.ProvideDatabase(testDB, tracer), tracer, nil)
|
||||
|
||||
_, err := outbox.Append(ctx, contracts.AppendOutboxMessage{
|
||||
RequestID: "1",
|
||||
Type: contracts.CreateSecretOutboxMessage,
|
||||
Name: "name1",
|
||||
Namespace: "ns1",
|
||||
EncryptedSecret: "v1",
|
||||
KeeperName: nil,
|
||||
ExternalID: nil,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = outbox.Append(ctx, contracts.AppendOutboxMessage{
|
||||
RequestID: "1",
|
||||
Type: contracts.UpdateSecretOutboxMessage,
|
||||
Name: "name1",
|
||||
Namespace: "ns1",
|
||||
EncryptedSecret: "v1",
|
||||
KeeperName: nil,
|
||||
ExternalID: nil,
|
||||
})
|
||||
|
||||
require.ErrorIs(t, err, contracts.ErrSecureValueOperationInProgress)
|
||||
})
|
||||
}
|
||||
|
||||
func TestOutboxStore(t *testing.T) {
|
||||
testDB := sqlstore.NewTestStore(t, sqlstore.WithMigrator(migrator.New()))
|
||||
tracer := noop.NewTracerProvider().Tracer("test")
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
outbox := ProvideOutboxQueue(database.ProvideDatabase(testDB, tracer), tracer, nil)
|
||||
|
||||
m1 := contracts.AppendOutboxMessage{
|
||||
Type: contracts.CreateSecretOutboxMessage,
|
||||
Name: "s-1",
|
||||
Namespace: "n-1",
|
||||
EncryptedSecret: "value",
|
||||
ExternalID: nil,
|
||||
}
|
||||
m2 := contracts.AppendOutboxMessage{
|
||||
Type: contracts.CreateSecretOutboxMessage,
|
||||
Name: "s-1",
|
||||
Namespace: "n-2",
|
||||
EncryptedSecret: "value",
|
||||
ExternalID: nil,
|
||||
}
|
||||
|
||||
messages, err := outbox.ReceiveN(ctx, 10)
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, messages)
|
||||
|
||||
messageID1, err := outbox.Append(ctx, m1)
|
||||
require.NoError(t, err)
|
||||
|
||||
for range 2 {
|
||||
messages, err = outbox.ReceiveN(ctx, 10)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(messages))
|
||||
require.Equal(t, messageID1, messages[0].MessageID)
|
||||
}
|
||||
|
||||
messageID2, err := outbox.Append(ctx, m2)
|
||||
require.NoError(t, err)
|
||||
|
||||
messages, err = outbox.ReceiveN(ctx, 3)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 2, len(messages))
|
||||
require.Equal(t, messageID1, messages[0].MessageID)
|
||||
require.Equal(t, messageID2, messages[1].MessageID)
|
||||
|
||||
require.NoError(t, outbox.Delete(ctx, messageID1))
|
||||
messages, err = outbox.ReceiveN(ctx, 10)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(messages))
|
||||
require.Equal(t, messageID2, messages[0].MessageID)
|
||||
|
||||
require.NoError(t, outbox.Delete(ctx, messageID2))
|
||||
|
||||
messages, err = outbox.ReceiveN(ctx, 100)
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, messages)
|
||||
}
|
||||
|
||||
func TestOutboxStoreProperty(t *testing.T) {
|
||||
seed := time.Now().UnixMicro()
|
||||
rng := rand.New(rand.NewSource(seed))
|
||||
|
||||
defer func() {
|
||||
if t.Failed() {
|
||||
fmt.Printf("TestOutboxStoreProperty: SEED=%+v\n\n", seed)
|
||||
}
|
||||
}()
|
||||
|
||||
// The number of iterations was decided arbitrarily based on the time the test takes to run
|
||||
for range 10 {
|
||||
testDB := sqlstore.NewTestStore(t, sqlstore.WithMigrator(migrator.New()))
|
||||
tracer := noop.NewTracerProvider().Tracer("test")
|
||||
|
||||
outbox := ProvideOutboxQueue(database.ProvideDatabase(testDB, tracer), tracer, nil)
|
||||
|
||||
model := newOutboxStoreModel()
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
for i := range 100 {
|
||||
n := rng.Intn(3)
|
||||
switch n {
|
||||
case 0:
|
||||
time.Sleep(1 * time.Microsecond)
|
||||
message := contracts.AppendOutboxMessage{
|
||||
Type: contracts.CreateSecretOutboxMessage,
|
||||
Name: fmt.Sprintf("s-%d", i),
|
||||
Namespace: fmt.Sprintf("n-%d", i),
|
||||
EncryptedSecret: "value",
|
||||
ExternalID: nil,
|
||||
}
|
||||
messageID, err := outbox.Append(ctx, message)
|
||||
require.NoError(t, err)
|
||||
|
||||
model.Append(messageID, message)
|
||||
|
||||
case 1:
|
||||
n := uint(rng.Intn(10))
|
||||
messages, err := outbox.ReceiveN(ctx, n)
|
||||
require.NoError(t, err)
|
||||
|
||||
modelMessages := model.ReceiveN(n)
|
||||
|
||||
require.Equal(t, len(modelMessages), len(messages))
|
||||
for i := range len(modelMessages) {
|
||||
require.Equal(t, modelMessages[i].MessageID, messages[i].MessageID)
|
||||
}
|
||||
|
||||
case 2:
|
||||
if len(model.rows) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
message := model.rows[rng.Intn(len(model.rows))]
|
||||
|
||||
model.Delete(message.MessageID)
|
||||
require.NoError(t, outbox.Delete(ctx, message.MessageID))
|
||||
|
||||
default:
|
||||
panic(fmt.Sprintf("unhandled action: %+v", n))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -27,18 +27,12 @@ var (
|
|||
sqlSecureValueRead = mustTemplate("secure_value_read.sql")
|
||||
sqlSecureValueList = mustTemplate("secure_value_list.sql")
|
||||
sqlSecureValueCreate = mustTemplate("secure_value_create.sql")
|
||||
sqlSecureValueDelete = mustTemplate("secure_value_delete.sql")
|
||||
sqlSecureValueUpdate = mustTemplate("secure_value_update.sql")
|
||||
sqlSecureValueUpdateExternalId = mustTemplate("secure_value_updateExternalId.sql")
|
||||
sqlSecureValueUpdateStatus = mustTemplate("secure_value_updateStatus.sql")
|
||||
sqlSecureValueReadForDecrypt = mustTemplate("secure_value_read_for_decrypt.sql")
|
||||
|
||||
sqlSecureValueOutboxAppend = mustTemplate("secure_value_outbox_append.sql")
|
||||
sqlSecureValueOutboxFetchMessageIDs = mustTemplate("secure_value_outbox_fetch_message_ids.sql")
|
||||
sqlSecureValueOutboxReceiveN = mustTemplate("secure_value_outbox_receiveN.sql")
|
||||
sqlSecureValueOutboxDelete = mustTemplate("secure_value_outbox_delete.sql")
|
||||
sqlSecureValueOutboxUpdateReceiveCount = mustTemplate("secure_value_outbox_update_receive_count.sql")
|
||||
sqlSecureValueOutboxQueryTimestamp = mustTemplate("secure_value_outbox_query_timestamp.sql")
|
||||
sqlGetLatestSecureValueVersion = mustTemplate("secure_value_get_latest_version.sql")
|
||||
sqlSecureValueSetVersionToActive = mustTemplate("secure_value_set_version_to_active.sql")
|
||||
sqlSecureValueSetVersionToInactive = mustTemplate("secure_value_set_version_to_inactive.sql")
|
||||
)
|
||||
|
||||
func mustTemplate(filename string) *template.Template {
|
||||
|
@ -150,6 +144,38 @@ func (r readSecureValue) Validate() error {
|
|||
return nil // TODO
|
||||
}
|
||||
|
||||
type getLatestSecureValueVersion struct {
|
||||
sqltemplate.SQLTemplate
|
||||
Namespace string
|
||||
Name string
|
||||
}
|
||||
|
||||
func (r getLatestSecureValueVersion) Validate() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type secureValueSetVersionToActive struct {
|
||||
sqltemplate.SQLTemplate
|
||||
Namespace string
|
||||
Name string
|
||||
Version int64
|
||||
}
|
||||
|
||||
func (r secureValueSetVersionToActive) Validate() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type secureValueSetVersionToInactive struct {
|
||||
sqltemplate.SQLTemplate
|
||||
Namespace string
|
||||
Name string
|
||||
Version int64
|
||||
}
|
||||
|
||||
func (r secureValueSetVersionToInactive) Validate() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type listSecureValue struct {
|
||||
sqltemplate.SQLTemplate
|
||||
Namespace string
|
||||
|
@ -170,23 +196,12 @@ func (r createSecureValue) Validate() error {
|
|||
return nil // TODO
|
||||
}
|
||||
|
||||
// Delete
|
||||
type deleteSecureValue struct {
|
||||
sqltemplate.SQLTemplate
|
||||
Namespace string
|
||||
Name string
|
||||
}
|
||||
|
||||
// Validate is only used if we use `dbutil` from `unifiedstorage`
|
||||
func (r deleteSecureValue) Validate() error {
|
||||
return nil // TODO
|
||||
}
|
||||
|
||||
// Update externalId
|
||||
type updateExternalIdSecureValue struct {
|
||||
sqltemplate.SQLTemplate
|
||||
Namespace string
|
||||
Name string
|
||||
Version int64
|
||||
ExternalID string
|
||||
}
|
||||
|
||||
|
@ -195,33 +210,6 @@ func (r updateExternalIdSecureValue) Validate() error {
|
|||
return nil // TODO
|
||||
}
|
||||
|
||||
// Update secure value
|
||||
type updateSecureValue struct {
|
||||
sqltemplate.SQLTemplate
|
||||
Namespace string
|
||||
Name string
|
||||
Row *secureValueDB
|
||||
}
|
||||
|
||||
// Validate is only used if we use `dbutil` from `unifiedstorage`
|
||||
func (r updateSecureValue) Validate() error {
|
||||
return nil // TODO
|
||||
}
|
||||
|
||||
// update status message
|
||||
type updateStatusSecureValue struct {
|
||||
sqltemplate.SQLTemplate
|
||||
Namespace string
|
||||
Name string
|
||||
Phase string
|
||||
Message string
|
||||
}
|
||||
|
||||
// Validate is only used if we use `dbutil` from `unifiedstorage`
|
||||
func (r updateStatusSecureValue) Validate() error {
|
||||
return nil // TODO
|
||||
}
|
||||
|
||||
type readSecureValueForDecrypt struct {
|
||||
sqltemplate.SQLTemplate
|
||||
Namespace string
|
||||
|
@ -229,48 +217,3 @@ type readSecureValueForDecrypt struct {
|
|||
}
|
||||
|
||||
func (r readSecureValueForDecrypt) Validate() error { return nil }
|
||||
|
||||
/*************************************/
|
||||
/**-- Secure Value Outbox Queries --**/
|
||||
/*************************************/
|
||||
type appendSecureValueOutbox struct {
|
||||
sqltemplate.SQLTemplate
|
||||
Row *outboxMessageDB
|
||||
}
|
||||
|
||||
func (appendSecureValueOutbox) Validate() error { return nil }
|
||||
|
||||
type receiveNSecureValueOutbox struct {
|
||||
sqltemplate.SQLTemplate
|
||||
MessageIDs []int64
|
||||
}
|
||||
|
||||
func (receiveNSecureValueOutbox) Validate() error { return nil }
|
||||
|
||||
type fetchMessageIDsOutbox struct {
|
||||
sqltemplate.SQLTemplate
|
||||
ReceiveLimit uint
|
||||
}
|
||||
|
||||
func (fetchMessageIDsOutbox) Validate() error { return nil }
|
||||
|
||||
type deleteSecureValueOutbox struct {
|
||||
sqltemplate.SQLTemplate
|
||||
MessageID int64
|
||||
}
|
||||
|
||||
func (deleteSecureValueOutbox) Validate() error { return nil }
|
||||
|
||||
type getOutboxMessageTimestamp struct {
|
||||
sqltemplate.SQLTemplate
|
||||
MessageID int64
|
||||
}
|
||||
|
||||
func (getOutboxMessageTimestamp) Validate() error { return nil }
|
||||
|
||||
type incrementReceiveCountOutbox struct {
|
||||
sqltemplate.SQLTemplate
|
||||
MessageIDs []int64
|
||||
}
|
||||
|
||||
func (incrementReceiveCountOutbox) Validate() error { return nil }
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package metadata
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"testing"
|
||||
"text/template"
|
||||
|
||||
|
@ -123,6 +122,27 @@ func TestSecureValueQueries(t *testing.T) {
|
|||
mocks.CheckQuerySnapshots(t, mocks.TemplateTestSetup{
|
||||
RootDir: "testdata",
|
||||
Templates: map[*template.Template][]mocks.TemplateTestCase{
|
||||
sqlGetLatestSecureValueVersion: {
|
||||
{
|
||||
Name: "get latest secure value version",
|
||||
Data: &getLatestSecureValueVersion{
|
||||
SQLTemplate: mocks.NewTestingSQLTemplate(),
|
||||
Name: "name",
|
||||
Namespace: "ns",
|
||||
},
|
||||
},
|
||||
},
|
||||
sqlSecureValueSetVersionToActive: {
|
||||
{
|
||||
Name: "set secure value version to active",
|
||||
Data: &secureValueSetVersionToActive{
|
||||
SQLTemplate: mocks.NewTestingSQLTemplate(),
|
||||
Name: "name",
|
||||
Namespace: "ns",
|
||||
Version: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
sqlSecureValueRead: {
|
||||
{
|
||||
Name: "read",
|
||||
|
@ -166,8 +186,7 @@ func TestSecureValueQueries(t *testing.T) {
|
|||
CreatedBy: "user:ryan",
|
||||
Updated: 5678,
|
||||
UpdatedBy: "user:cameron",
|
||||
Phase: "creating",
|
||||
Message: toNullString(nil),
|
||||
Version: 1,
|
||||
Description: "description",
|
||||
Keeper: toNullString(nil),
|
||||
Decrypters: toNullString(nil),
|
||||
|
@ -190,72 +209,7 @@ func TestSecureValueQueries(t *testing.T) {
|
|||
CreatedBy: "user:ryan",
|
||||
Updated: 5678,
|
||||
UpdatedBy: "user:cameron",
|
||||
Phase: "creating",
|
||||
Message: toNullString(ptr.To("message_test")),
|
||||
Description: "description",
|
||||
Keeper: toNullString(ptr.To("keeper_test")),
|
||||
Decrypters: toNullString(ptr.To("decrypters_test")),
|
||||
Ref: toNullString(ptr.To("ref_test")),
|
||||
ExternalID: "extId",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
sqlSecureValueDelete: {
|
||||
{
|
||||
Name: "delete",
|
||||
Data: &deleteSecureValue{
|
||||
SQLTemplate: mocks.NewTestingSQLTemplate(),
|
||||
Name: "name",
|
||||
Namespace: "ns",
|
||||
},
|
||||
},
|
||||
},
|
||||
sqlSecureValueUpdate: {
|
||||
{
|
||||
Name: "update-null",
|
||||
Data: &updateSecureValue{
|
||||
SQLTemplate: mocks.NewTestingSQLTemplate(),
|
||||
Name: "name",
|
||||
Namespace: "ns",
|
||||
Row: &secureValueDB{
|
||||
GUID: "abc",
|
||||
Name: "name",
|
||||
Namespace: "ns",
|
||||
Annotations: `{"x":"XXXX"}`,
|
||||
Labels: `{"a":"AAA", "b", "BBBB"}`,
|
||||
Created: 1234,
|
||||
CreatedBy: "user:ryan",
|
||||
Updated: 5678,
|
||||
UpdatedBy: "user:cameron",
|
||||
Phase: "creating",
|
||||
Message: toNullString(nil),
|
||||
Description: "description",
|
||||
Keeper: toNullString(nil),
|
||||
Decrypters: toNullString(nil),
|
||||
Ref: toNullString(nil),
|
||||
ExternalID: "extId",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "update-not-null",
|
||||
Data: &updateSecureValue{
|
||||
SQLTemplate: mocks.NewTestingSQLTemplate(),
|
||||
Name: "name",
|
||||
Namespace: "ns",
|
||||
Row: &secureValueDB{
|
||||
GUID: "abc",
|
||||
Name: "name",
|
||||
Namespace: "ns",
|
||||
Annotations: `{"x":"XXXX"}`,
|
||||
Labels: `{"a":"AAA", "b", "BBBB"}`,
|
||||
Created: 1234,
|
||||
CreatedBy: "user:ryan",
|
||||
Updated: 5678,
|
||||
UpdatedBy: "user:cameron",
|
||||
Phase: "creating",
|
||||
Message: toNullString(ptr.To("message_test")),
|
||||
Version: 1,
|
||||
Description: "description",
|
||||
Keeper: toNullString(ptr.To("keeper_test")),
|
||||
Decrypters: toNullString(ptr.To("decrypters_test")),
|
||||
|
@ -276,18 +230,6 @@ func TestSecureValueQueries(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
sqlSecureValueUpdateStatus: {
|
||||
{
|
||||
Name: "updateStatus",
|
||||
Data: &updateStatusSecureValue{
|
||||
SQLTemplate: mocks.NewTestingSQLTemplate(),
|
||||
Name: "name",
|
||||
Namespace: "ns",
|
||||
Phase: "Succeeded",
|
||||
Message: "message-1",
|
||||
},
|
||||
},
|
||||
},
|
||||
sqlSecureValueReadForDecrypt: {
|
||||
{
|
||||
Name: "read-for-decrypt",
|
||||
|
@ -301,111 +243,3 @@ func TestSecureValueQueries(t *testing.T) {
|
|||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestSecureValueOutboxQueries(t *testing.T) {
|
||||
mocks.CheckQuerySnapshots(t, mocks.TemplateTestSetup{
|
||||
RootDir: "testdata",
|
||||
Templates: map[*template.Template][]mocks.TemplateTestCase{
|
||||
sqlSecureValueOutboxUpdateReceiveCount: {
|
||||
{
|
||||
|
||||
Name: "update-receive-count",
|
||||
Data: &incrementReceiveCountOutbox{
|
||||
SQLTemplate: mocks.NewTestingSQLTemplate(),
|
||||
MessageIDs: []int64{1, 2, 3},
|
||||
},
|
||||
},
|
||||
},
|
||||
sqlSecureValueOutboxAppend: {
|
||||
{
|
||||
Name: "no-encrypted-secret",
|
||||
Data: &appendSecureValueOutbox{
|
||||
SQLTemplate: mocks.NewTestingSQLTemplate(),
|
||||
Row: &outboxMessageDB{
|
||||
MessageID: 1,
|
||||
MessageType: "some-type",
|
||||
Name: "name",
|
||||
Namespace: "namespace",
|
||||
ExternalID: sql.NullString{Valid: true, String: "external-id"},
|
||||
KeeperName: sql.NullString{Valid: true, String: "keeper"},
|
||||
Created: 1234,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "no-external-id",
|
||||
Data: &appendSecureValueOutbox{
|
||||
SQLTemplate: mocks.NewTestingSQLTemplate(),
|
||||
Row: &outboxMessageDB{
|
||||
MessageID: 1,
|
||||
MessageType: "some-type",
|
||||
Name: "name",
|
||||
Namespace: "namespace",
|
||||
EncryptedSecret: sql.NullString{Valid: true, String: "encrypted"},
|
||||
KeeperName: sql.NullString{Valid: true, String: "keeper"},
|
||||
Created: 1234,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "no-keeper-name",
|
||||
Data: &appendSecureValueOutbox{
|
||||
SQLTemplate: mocks.NewTestingSQLTemplate(),
|
||||
Row: &outboxMessageDB{
|
||||
MessageID: 1,
|
||||
MessageType: "some-type",
|
||||
Name: "name",
|
||||
Namespace: "namespace",
|
||||
EncryptedSecret: sql.NullString{Valid: true, String: "encrypted"},
|
||||
ExternalID: sql.NullString{Valid: true, String: "external-id"},
|
||||
Created: 1234,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "all-fields-present",
|
||||
Data: &appendSecureValueOutbox{
|
||||
SQLTemplate: mocks.NewTestingSQLTemplate(),
|
||||
Row: &outboxMessageDB{
|
||||
MessageID: 1,
|
||||
MessageType: "some-type",
|
||||
Name: "name",
|
||||
Namespace: "namespace",
|
||||
EncryptedSecret: sql.NullString{Valid: true, String: "encrypted"},
|
||||
ExternalID: sql.NullString{Valid: true, String: ""}, // can be empty string
|
||||
KeeperName: sql.NullString{Valid: true, String: "keeper"},
|
||||
Created: 1234,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
sqlSecureValueOutboxFetchMessageIDs: {
|
||||
{
|
||||
Name: "basic",
|
||||
Data: &fetchMessageIDsOutbox{
|
||||
SQLTemplate: mocks.NewTestingSQLTemplate(),
|
||||
ReceiveLimit: 10,
|
||||
},
|
||||
},
|
||||
},
|
||||
sqlSecureValueOutboxReceiveN: {
|
||||
{
|
||||
Name: "basic",
|
||||
Data: &receiveNSecureValueOutbox{
|
||||
SQLTemplate: mocks.NewTestingSQLTemplate(),
|
||||
MessageIDs: []int64{1, 2, 3},
|
||||
},
|
||||
},
|
||||
},
|
||||
sqlSecureValueOutboxDelete: {
|
||||
{
|
||||
Name: "basic",
|
||||
Data: &deleteSecureValueOutbox{
|
||||
SQLTemplate: mocks.NewTestingSQLTemplate(),
|
||||
MessageID: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
|
|
@ -29,8 +29,8 @@ type secureValueDB struct {
|
|||
UpdatedBy string
|
||||
|
||||
// Kubernetes Status
|
||||
Phase string
|
||||
Message sql.NullString
|
||||
Active bool
|
||||
Version int64
|
||||
|
||||
// Spec
|
||||
Description string
|
||||
|
@ -74,8 +74,8 @@ func (sv *secureValueDB) toKubernetes() (*secretv0alpha1.SecureValue, error) {
|
|||
Decrypters: decrypters,
|
||||
},
|
||||
Status: secretv0alpha1.SecureValueStatus{
|
||||
Phase: secretv0alpha1.SecureValuePhase(sv.Phase),
|
||||
ExternalID: sv.ExternalID,
|
||||
Version: sv.Version,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -85,10 +85,7 @@ func (sv *secureValueDB) toKubernetes() (*secretv0alpha1.SecureValue, error) {
|
|||
if sv.Ref.Valid {
|
||||
resource.Spec.Ref = &sv.Ref.String
|
||||
}
|
||||
if sv.Message.Valid {
|
||||
resource.Status.Message = sv.Message.String
|
||||
}
|
||||
resource.Status.Phase = secretv0alpha1.SecureValuePhase(sv.Phase)
|
||||
|
||||
resource.Status.ExternalID = sv.ExternalID
|
||||
|
||||
// Set all meta fields here for consistency.
|
||||
|
@ -131,24 +128,6 @@ func toCreateRow(sv *secretv0alpha1.SecureValue, actorUID string) (*secureValueD
|
|||
return row, nil
|
||||
}
|
||||
|
||||
// toUpdateRow maps a Kubernetes resource into a DB row for existing resources being updated.
|
||||
func toUpdateRow(currentRow *secureValueDB, newSecureValue *secretv0alpha1.SecureValue, actorUID, externalID string) (*secureValueDB, error) {
|
||||
row, err := toRow(newSecureValue, externalID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create: %w", err)
|
||||
}
|
||||
|
||||
now := time.Now().UTC().Unix()
|
||||
|
||||
row.GUID = currentRow.GUID
|
||||
row.Created = currentRow.Created
|
||||
row.CreatedBy = currentRow.CreatedBy
|
||||
row.Updated = now
|
||||
row.UpdatedBy = actorUID
|
||||
|
||||
return row, nil
|
||||
}
|
||||
|
||||
// toRow maps a Kubernetes resource into a DB row.
|
||||
func toRow(sv *secretv0alpha1.SecureValue, externalID string) (*secureValueDB, error) {
|
||||
var annotations string
|
||||
|
@ -201,11 +180,6 @@ func toRow(sv *secretv0alpha1.SecureValue, externalID string) (*secureValueDB, e
|
|||
return nil, fmt.Errorf("failed to get resource version: %w", err)
|
||||
}
|
||||
|
||||
var statusMessage *string
|
||||
if sv.Status.Message != "" {
|
||||
statusMessage = &sv.Status.Message
|
||||
}
|
||||
|
||||
return &secureValueDB{
|
||||
GUID: string(sv.UID),
|
||||
Name: sv.Name,
|
||||
|
@ -217,8 +191,7 @@ func toRow(sv *secretv0alpha1.SecureValue, externalID string) (*secureValueDB, e
|
|||
Updated: updatedTimestamp,
|
||||
UpdatedBy: meta.GetUpdatedBy(),
|
||||
|
||||
Phase: string(sv.Status.Phase),
|
||||
Message: toNullString(statusMessage),
|
||||
Version: sv.Status.Version,
|
||||
|
||||
Description: sv.Spec.Description,
|
||||
Keeper: toNullString(sv.Spec.Keeper),
|
||||
|
@ -233,6 +206,7 @@ type secureValueForDecrypt struct {
|
|||
Keeper sql.NullString
|
||||
Decrypters sql.NullString
|
||||
Ref sql.NullString
|
||||
Active bool
|
||||
ExternalID string
|
||||
}
|
||||
|
||||
|
|
|
@ -55,33 +55,18 @@ func (s *secureValueMetadataStorage) Create(ctx context.Context, sv *secretv0alp
|
|||
))
|
||||
defer span.End()
|
||||
|
||||
sv.Status.Phase = secretv0alpha1.SecureValuePhasePending
|
||||
sv.Status.Message = "Creating secure value"
|
||||
// Set inside of the transaction callback
|
||||
var row *secureValueDB
|
||||
|
||||
row, err := toCreateRow(sv, actorUID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("to create row: %w", err)
|
||||
}
|
||||
|
||||
req := createSecureValue{
|
||||
SQLTemplate: sqltemplate.New(s.dialect),
|
||||
Row: row,
|
||||
}
|
||||
|
||||
query, err := sqltemplate.Execute(sqlSecureValueCreate, req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("execute template %q: %w", sqlSecureValueCreate.Name(), err)
|
||||
}
|
||||
|
||||
err = s.db.Transaction(ctx, func(ctx context.Context) error {
|
||||
if row.Keeper.Valid {
|
||||
err := s.db.Transaction(ctx, func(ctx context.Context) error {
|
||||
if sv.Spec.Keeper != nil {
|
||||
// Validate before inserting that the chosen `keeper` exists.
|
||||
|
||||
// -- This is a copy of KeeperMetadataStore.read, which is not public at the moment, and is not defined in contract.KeeperMetadataStorage
|
||||
req := &readKeeper{
|
||||
SQLTemplate: sqltemplate.New(s.dialect),
|
||||
Namespace: row.Namespace,
|
||||
Name: row.Keeper.String,
|
||||
Namespace: sv.Namespace,
|
||||
Name: *sv.Spec.Keeper,
|
||||
IsForUpdate: true,
|
||||
}
|
||||
|
||||
|
@ -101,23 +86,61 @@ func (s *secureValueMetadataStorage) Create(ctx context.Context, sv *secretv0alp
|
|||
}
|
||||
}
|
||||
|
||||
res, err := s.db.ExecContext(ctx, query, req.GetArgs()...)
|
||||
latestVersion, err := s.getLatestVersion(ctx, xkube.Namespace(sv.Namespace), sv.Name)
|
||||
if err != nil {
|
||||
if sql.IsRowAlreadyExistsError(err) {
|
||||
return fmt.Errorf("namespace=%+v name=%+v %w", sv.Namespace, sv.Name, contracts.ErrSecureValueAlreadyExists)
|
||||
return fmt.Errorf("fetching latest secure value version: %w", err)
|
||||
}
|
||||
|
||||
version := int64(1)
|
||||
if latestVersion != nil {
|
||||
version = *latestVersion + 1
|
||||
}
|
||||
|
||||
// Some other concurrent request may have created the version we're trying to create,
|
||||
// if that's the case, we'll retry with a new version up to max attempts.
|
||||
maxAttempts := 3
|
||||
attempts := 0
|
||||
for {
|
||||
sv.Status.Version = version
|
||||
|
||||
row, err = toCreateRow(sv, actorUID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("to create row: %w", err)
|
||||
}
|
||||
return fmt.Errorf("inserting row: %w", err)
|
||||
}
|
||||
|
||||
rowsAffected, err := res.RowsAffected()
|
||||
if err != nil {
|
||||
return fmt.Errorf("getting rows affected: %w", err)
|
||||
}
|
||||
req := createSecureValue{
|
||||
SQLTemplate: sqltemplate.New(s.dialect),
|
||||
Row: row,
|
||||
}
|
||||
|
||||
if rowsAffected != 1 {
|
||||
return fmt.Errorf("expected 1 row affected, got %d for %s on %s", rowsAffected, row.Name, row.Namespace)
|
||||
query, err := sqltemplate.Execute(sqlSecureValueCreate, req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("execute template %q: %w", sqlSecureValueCreate.Name(), err)
|
||||
}
|
||||
|
||||
res, err := s.db.ExecContext(ctx, query, req.GetArgs()...)
|
||||
if err != nil {
|
||||
if sql.IsRowAlreadyExistsError(err) {
|
||||
if attempts < maxAttempts {
|
||||
attempts += 1
|
||||
version += 1
|
||||
continue
|
||||
}
|
||||
return fmt.Errorf("namespace=%+v name=%+v %w", sv.Namespace, sv.Name, contracts.ErrSecureValueAlreadyExists)
|
||||
}
|
||||
return fmt.Errorf("inserting row: %w", err)
|
||||
}
|
||||
|
||||
rowsAffected, err := res.RowsAffected()
|
||||
if err != nil {
|
||||
return fmt.Errorf("getting rows affected: %w", err)
|
||||
}
|
||||
|
||||
if rowsAffected != 1 {
|
||||
return fmt.Errorf("expected 1 row affected, got %d for %s on %s", rowsAffected, row.Name, row.Namespace)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("db failure: %w", err)
|
||||
|
@ -134,6 +157,138 @@ func (s *secureValueMetadataStorage) Create(ctx context.Context, sv *secretv0alp
|
|||
return createdSecureValue, nil
|
||||
}
|
||||
|
||||
func (s *secureValueMetadataStorage) getLatestVersion(ctx context.Context, namespace xkube.Namespace, name string) (*int64, error) {
|
||||
ctx, span := s.tracer.Start(ctx, "SecureValueMetadataStorage.getLatestVersion", trace.WithAttributes(
|
||||
attribute.String("name", name),
|
||||
attribute.String("namespace", namespace.String()),
|
||||
))
|
||||
defer span.End()
|
||||
|
||||
req := getLatestSecureValueVersion{
|
||||
SQLTemplate: sqltemplate.New(s.dialect),
|
||||
Namespace: namespace.String(),
|
||||
Name: name,
|
||||
}
|
||||
|
||||
q, err := sqltemplate.Execute(sqlGetLatestSecureValueVersion, req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("execute template %q: %w", sqlGetLatestSecureValueVersion.Name(), err)
|
||||
}
|
||||
|
||||
rows, err := s.db.QueryContext(ctx, q, req.GetArgs()...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("fetching latest version for secure value: namespace=%+v name=%+v %w", namespace, name, err)
|
||||
}
|
||||
defer func() { _ = rows.Close() }()
|
||||
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, fmt.Errorf("error executing query: %w", err)
|
||||
}
|
||||
|
||||
if !rows.Next() {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var version int64
|
||||
if err := rows.Scan(&version); err != nil {
|
||||
return nil, fmt.Errorf("scanning version from returned rows: %w", err)
|
||||
}
|
||||
|
||||
return &version, nil
|
||||
}
|
||||
|
||||
func (s *secureValueMetadataStorage) ReadForDecrypt(ctx context.Context, namespace xkube.Namespace, name string) (*contracts.DecryptSecureValue, error) {
|
||||
start := time.Now()
|
||||
ctx, span := s.tracer.Start(ctx, "SecureValueMetadataStorage.ReadForDecrypt", trace.WithAttributes(
|
||||
attribute.String("name", name),
|
||||
attribute.String("namespace", namespace.String()),
|
||||
))
|
||||
defer span.End()
|
||||
|
||||
req := readSecureValueForDecrypt{
|
||||
SQLTemplate: sqltemplate.New(s.dialect),
|
||||
Namespace: namespace.String(),
|
||||
Name: name,
|
||||
}
|
||||
|
||||
query, err := sqltemplate.Execute(sqlSecureValueReadForDecrypt, req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("execute template %q: %w", sqlSecureValueReadForDecrypt.Name(), err)
|
||||
}
|
||||
|
||||
res, err := s.db.QueryContext(ctx, query, req.GetArgs()...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("reading row: %w", err)
|
||||
}
|
||||
defer func() { _ = res.Close() }()
|
||||
|
||||
var row secureValueForDecrypt
|
||||
if !res.Next() {
|
||||
return nil, contracts.ErrSecureValueNotFound
|
||||
}
|
||||
if err := res.Scan(
|
||||
&row.Keeper, &row.Decrypters,
|
||||
&row.Ref, &row.ExternalID, &row.Active); err != nil {
|
||||
return nil, fmt.Errorf("failed to scan secure value row: %w", err)
|
||||
}
|
||||
|
||||
if err := res.Err(); err != nil {
|
||||
return nil, fmt.Errorf("read rows error: %w", err)
|
||||
}
|
||||
|
||||
if !row.Active {
|
||||
return nil, fmt.Errorf("bug: read an inactive version: row=%+v", row)
|
||||
}
|
||||
|
||||
secureValue, err := row.toDecrypt()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("convert to kubernetes object: %w", err)
|
||||
}
|
||||
|
||||
s.metrics.SecureValueGetForDecryptDuration.Observe(time.Since(start).Seconds())
|
||||
|
||||
return secureValue, nil
|
||||
}
|
||||
|
||||
func (s *secureValueMetadataStorage) readActiveVersion(ctx context.Context, namespace xkube.Namespace, name string, opts contracts.ReadOpts) (secureValueDB, error) {
|
||||
req := readSecureValue{
|
||||
SQLTemplate: sqltemplate.New(s.dialect),
|
||||
Namespace: namespace.String(),
|
||||
Name: name,
|
||||
IsForUpdate: opts.ForUpdate,
|
||||
}
|
||||
|
||||
query, err := sqltemplate.Execute(sqlSecureValueRead, req)
|
||||
if err != nil {
|
||||
return secureValueDB{}, fmt.Errorf("execute template %q: %w", sqlSecureValueRead.Name(), err)
|
||||
}
|
||||
|
||||
res, err := s.db.QueryContext(ctx, query, req.GetArgs()...)
|
||||
if err != nil {
|
||||
return secureValueDB{}, fmt.Errorf("reading row: %w", err)
|
||||
}
|
||||
defer func() { _ = res.Close() }()
|
||||
|
||||
var secureValue secureValueDB
|
||||
if !res.Next() {
|
||||
return secureValueDB{}, contracts.ErrSecureValueNotFound
|
||||
}
|
||||
|
||||
if err := res.Scan(
|
||||
&secureValue.GUID, &secureValue.Name, &secureValue.Namespace,
|
||||
&secureValue.Annotations, &secureValue.Labels,
|
||||
&secureValue.Created, &secureValue.CreatedBy,
|
||||
&secureValue.Updated, &secureValue.UpdatedBy,
|
||||
&secureValue.Description, &secureValue.Keeper, &secureValue.Decrypters, &secureValue.Ref, &secureValue.ExternalID, &secureValue.Active, &secureValue.Version); err != nil {
|
||||
return secureValueDB{}, fmt.Errorf("failed to scan secure value row: %w", err)
|
||||
}
|
||||
|
||||
if err := res.Err(); err != nil {
|
||||
return secureValueDB{}, fmt.Errorf("read rows error: %w", err)
|
||||
}
|
||||
return secureValue, nil
|
||||
}
|
||||
|
||||
func (s *secureValueMetadataStorage) Read(ctx context.Context, namespace xkube.Namespace, name string, opts contracts.ReadOpts) (*secretv0alpha1.SecureValue, error) {
|
||||
start := time.Now()
|
||||
ctx, span := s.tracer.Start(ctx, "SecureValueMetadataStorage.Read", trace.WithAttributes(
|
||||
|
@ -143,7 +298,7 @@ func (s *secureValueMetadataStorage) Read(ctx context.Context, namespace xkube.N
|
|||
))
|
||||
defer span.End()
|
||||
|
||||
secureValue, err := s.read(ctx, namespace, name, opts)
|
||||
secureValue, err := s.readActiveVersion(ctx, namespace, name, opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -159,134 +314,6 @@ func (s *secureValueMetadataStorage) Read(ctx context.Context, namespace xkube.N
|
|||
return secureValueKub, nil
|
||||
}
|
||||
|
||||
func (s *secureValueMetadataStorage) Update(ctx context.Context, newSecureValue *secretv0alpha1.SecureValue, actorUID string) (*secretv0alpha1.SecureValue, error) {
|
||||
start := time.Now()
|
||||
ctx, span := s.tracer.Start(ctx, "SecureValueMetadataStorage.Update", trace.WithAttributes(
|
||||
attribute.String("name", newSecureValue.GetName()),
|
||||
attribute.String("namespace", newSecureValue.GetNamespace()),
|
||||
attribute.String("actorUID", actorUID),
|
||||
))
|
||||
defer span.End()
|
||||
|
||||
var newRow *secureValueDB
|
||||
|
||||
err := s.db.Transaction(ctx, func(ctx context.Context) error {
|
||||
read, err := s.read(ctx, xkube.Namespace(newSecureValue.Namespace), newSecureValue.Name, contracts.ReadOpts{ForUpdate: true})
|
||||
if err != nil {
|
||||
return fmt.Errorf("reading secure value: %w", err)
|
||||
}
|
||||
|
||||
// TODO: Confirm the ExternalID should come from the read model.
|
||||
var updateErr error
|
||||
newRow, updateErr = toUpdateRow(&read, newSecureValue, actorUID, read.ExternalID)
|
||||
if updateErr != nil {
|
||||
return fmt.Errorf("model to update row: %w", updateErr)
|
||||
}
|
||||
|
||||
if newRow.Keeper.Valid {
|
||||
// Validate before updating that the new `keeper` exists.
|
||||
|
||||
// -- This is a copy of KeeperMetadataStore.read, which is not public at the moment, and is not defined in contract.KeeperMetadataStorage
|
||||
req := &readKeeper{
|
||||
SQLTemplate: sqltemplate.New(s.dialect),
|
||||
Namespace: newRow.Namespace,
|
||||
Name: newRow.Keeper.String,
|
||||
IsForUpdate: true,
|
||||
}
|
||||
|
||||
query, err := sqltemplate.Execute(sqlKeeperRead, req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("execute template %q: %w", sqlKeeperRead.Name(), err)
|
||||
}
|
||||
|
||||
res, err := s.db.QueryContext(ctx, query, req.GetArgs()...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("getting row: %w", err)
|
||||
}
|
||||
defer func() { _ = res.Close() }()
|
||||
|
||||
if !res.Next() {
|
||||
return contracts.ErrKeeperNotFound
|
||||
}
|
||||
}
|
||||
|
||||
req := &updateSecureValue{
|
||||
SQLTemplate: sqltemplate.New(s.dialect),
|
||||
Namespace: newRow.Namespace,
|
||||
Name: newRow.Name,
|
||||
Row: newRow,
|
||||
}
|
||||
|
||||
query, err := sqltemplate.Execute(sqlSecureValueUpdate, req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("execute template %q: %w", sqlSecureValueUpdate.Name(), err)
|
||||
}
|
||||
|
||||
result, err := s.db.ExecContext(ctx, query, req.GetArgs()...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("updating row: %w", err)
|
||||
}
|
||||
|
||||
rowsAffected, err := result.RowsAffected()
|
||||
if err != nil {
|
||||
return fmt.Errorf("getting rows affected: %w", err)
|
||||
}
|
||||
|
||||
if rowsAffected != 1 {
|
||||
return fmt.Errorf("expected 1 row affected, got %d for %s on %s", rowsAffected, newRow.Name, newRow.Namespace)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("db failure: %w", err)
|
||||
}
|
||||
|
||||
secureValue, err := newRow.toKubernetes()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("convert to kubernetes object: %w", err)
|
||||
}
|
||||
|
||||
s.metrics.SecureValueMetadataUpdateDuration.Observe(time.Since(start).Seconds())
|
||||
s.metrics.SecureValueMetadataUpdateCount.Inc()
|
||||
|
||||
return secureValue, nil
|
||||
}
|
||||
|
||||
func (s *secureValueMetadataStorage) Delete(ctx context.Context, namespace xkube.Namespace, name string) error {
|
||||
start := time.Now()
|
||||
ctx, span := s.tracer.Start(ctx, "SecureValueMetadataStorage.Delete", trace.WithAttributes(
|
||||
attribute.String("name", name),
|
||||
attribute.String("namespace", namespace.String()),
|
||||
))
|
||||
defer span.End()
|
||||
|
||||
req := deleteSecureValue{
|
||||
SQLTemplate: sqltemplate.New(s.dialect),
|
||||
Namespace: namespace.String(),
|
||||
Name: name,
|
||||
}
|
||||
|
||||
query, err := sqltemplate.Execute(sqlSecureValueDelete, req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("execute template %q: %w", sqlSecureValueDelete.Name(), err)
|
||||
}
|
||||
|
||||
res, err := s.db.ExecContext(ctx, query, req.GetArgs()...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("deleting secure value row: %w", err)
|
||||
}
|
||||
|
||||
if rowsAffected, err := res.RowsAffected(); err != nil || rowsAffected != 1 {
|
||||
return fmt.Errorf("deleting secure value rowsAffected=%d error=%w", rowsAffected, err)
|
||||
}
|
||||
|
||||
s.metrics.SecureValueMetadataDeleteDuration.Observe(time.Since(start).Seconds())
|
||||
s.metrics.SecureValueMetadataDeleteCount.Inc()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *secureValueMetadataStorage) List(ctx context.Context, namespace xkube.Namespace) (svList []secretv0alpha1.SecureValue, error error) {
|
||||
start := time.Now()
|
||||
ctx, span := s.tracer.Start(ctx, "SecureValueMetadataStorage.List", trace.WithAttributes(
|
||||
|
@ -323,15 +350,18 @@ func (s *secureValueMetadataStorage) List(ctx context.Context, namespace xkube.N
|
|||
&row.Labels,
|
||||
&row.Created, &row.CreatedBy,
|
||||
&row.Updated, &row.UpdatedBy,
|
||||
&row.Phase, &row.Message,
|
||||
&row.Description, &row.Keeper, &row.Decrypters,
|
||||
&row.Ref, &row.ExternalID,
|
||||
&row.Ref, &row.ExternalID, &row.Version, &row.Active,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error reading secure value row: %w", err)
|
||||
}
|
||||
|
||||
if !row.Active {
|
||||
return nil, fmt.Errorf("bug: read an inactive version: row=%+v", row)
|
||||
}
|
||||
|
||||
secureValue, err := row.toKubernetes()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("convert to kubernetes object: %w", err)
|
||||
|
@ -349,12 +379,86 @@ func (s *secureValueMetadataStorage) List(ctx context.Context, namespace xkube.N
|
|||
return secureValues, nil
|
||||
}
|
||||
|
||||
func (s *secureValueMetadataStorage) SetExternalID(ctx context.Context, namespace xkube.Namespace, name string, externalID contracts.ExternalID) error {
|
||||
func (s *secureValueMetadataStorage) SetVersionToActive(ctx context.Context, namespace xkube.Namespace, name string, version int64) error {
|
||||
ctx, span := s.tracer.Start(ctx, "SecureValueMetadataStorage.SetExternalID", trace.WithAttributes(
|
||||
attribute.String("name", name),
|
||||
attribute.String("namespace", namespace.String()),
|
||||
attribute.Int64("version", version),
|
||||
))
|
||||
defer span.End()
|
||||
|
||||
req := secureValueSetVersionToActive{
|
||||
SQLTemplate: sqltemplate.New(s.dialect),
|
||||
Namespace: namespace.String(),
|
||||
Name: name,
|
||||
Version: version,
|
||||
}
|
||||
|
||||
q, err := sqltemplate.Execute(sqlSecureValueSetVersionToActive, req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("execute template %q: %w", sqlSecureValueSetVersionToActive.Name(), err)
|
||||
}
|
||||
|
||||
res, err := s.db.ExecContext(ctx, q, req.GetArgs()...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("setting secure value version to active: namespace=%+v name=%+v version=%+v %w", namespace, name, version, err)
|
||||
}
|
||||
|
||||
// validate modified cound
|
||||
modifiedCount, err := res.RowsAffected()
|
||||
if err != nil {
|
||||
return fmt.Errorf("fetching number of modified rows: %w", err)
|
||||
}
|
||||
if modifiedCount == 0 {
|
||||
return fmt.Errorf("expected to modify at least one row but modified 0: modifiedCount=%d", modifiedCount)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *secureValueMetadataStorage) SetVersionToInactive(ctx context.Context, namespace xkube.Namespace, name string, version int64) error {
|
||||
ctx, span := s.tracer.Start(ctx, "SecureValueMetadataStorage.SetExternalID", trace.WithAttributes(
|
||||
attribute.String("name", name),
|
||||
attribute.String("namespace", namespace.String()),
|
||||
attribute.Int64("version", version),
|
||||
))
|
||||
defer span.End()
|
||||
|
||||
req := secureValueSetVersionToInactive{
|
||||
SQLTemplate: sqltemplate.New(s.dialect),
|
||||
Namespace: namespace.String(),
|
||||
Name: name,
|
||||
Version: version,
|
||||
}
|
||||
|
||||
q, err := sqltemplate.Execute(sqlSecureValueSetVersionToInactive, req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("execute template %q: %w", sqlSecureValueSetVersionToInactive.Name(), err)
|
||||
}
|
||||
|
||||
res, err := s.db.ExecContext(ctx, q, req.GetArgs()...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("setting secure value version to active: namespace=%+v name=%+v version=%+v %w", namespace, name, version, err)
|
||||
}
|
||||
|
||||
modifiedCount, err := res.RowsAffected()
|
||||
if err != nil {
|
||||
return fmt.Errorf("fetching number of modified rows: %w", err)
|
||||
}
|
||||
if modifiedCount > 1 {
|
||||
return fmt.Errorf("expected to modify at at most one row but modified more: modifiedCount=%d", modifiedCount)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *secureValueMetadataStorage) SetExternalID(ctx context.Context, namespace xkube.Namespace, name string, version int64, externalID contracts.ExternalID) error {
|
||||
start := time.Now()
|
||||
ctx, span := s.tracer.Start(ctx, "SecureValueMetadataStorage.SetExternalID", trace.WithAttributes(
|
||||
attribute.String("name", name),
|
||||
attribute.String("namespace", namespace.String()),
|
||||
attribute.String("externalID", externalID.String()),
|
||||
attribute.Int64("version", version),
|
||||
))
|
||||
defer span.End()
|
||||
|
||||
|
@ -362,6 +466,7 @@ func (s *secureValueMetadataStorage) SetExternalID(ctx context.Context, namespac
|
|||
SQLTemplate: sqltemplate.New(s.dialect),
|
||||
Namespace: namespace.String(),
|
||||
Name: name,
|
||||
Version: version,
|
||||
ExternalID: externalID.String(),
|
||||
}
|
||||
|
||||
|
@ -387,134 +492,3 @@ func (s *secureValueMetadataStorage) SetExternalID(ctx context.Context, namespac
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *secureValueMetadataStorage) SetStatus(ctx context.Context, namespace xkube.Namespace, name string, status secretv0alpha1.SecureValueStatus) error {
|
||||
start := time.Now()
|
||||
ctx, span := s.tracer.Start(ctx, "SecureValueMetadataStorage.SetStatus", trace.WithAttributes(
|
||||
attribute.String("name", name),
|
||||
attribute.String("namespace", namespace.String()),
|
||||
attribute.String("status.phase", string(status.Phase)),
|
||||
attribute.String("status.message", status.Message),
|
||||
attribute.String("status.externalID", status.ExternalID),
|
||||
))
|
||||
defer span.End()
|
||||
|
||||
req := updateStatusSecureValue{
|
||||
SQLTemplate: sqltemplate.New(s.dialect),
|
||||
Namespace: namespace.String(),
|
||||
Name: name,
|
||||
Phase: string(status.Phase),
|
||||
Message: status.Message,
|
||||
}
|
||||
|
||||
q, err := sqltemplate.Execute(sqlSecureValueUpdateStatus, req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("execute template %q: %w", sqlSecureValueUpdateStatus.Name(), err)
|
||||
}
|
||||
|
||||
res, err := s.db.ExecContext(ctx, q, req.GetArgs()...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("setting secure value status to Succeeded id: namespace=%+v name=%+v %w", namespace, name, err)
|
||||
}
|
||||
|
||||
// validate modified cound
|
||||
modifiedCount, err := res.RowsAffected()
|
||||
if err != nil {
|
||||
return fmt.Errorf("getting updated rows update status secure value: %w", err)
|
||||
}
|
||||
if modifiedCount > 1 {
|
||||
return fmt.Errorf("secureValueMetadataStorage.SetExternalID: modified more than one secret, this is a bug, check the where condition: modifiedCount=%d", modifiedCount)
|
||||
}
|
||||
s.metrics.SecureValueSetStatusDuration.Observe(time.Since(start).Seconds())
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *secureValueMetadataStorage) ReadForDecrypt(ctx context.Context, namespace xkube.Namespace, name string) (*contracts.DecryptSecureValue, error) {
|
||||
start := time.Now()
|
||||
ctx, span := s.tracer.Start(ctx, "SecureValueMetadataStorage.ReadForDecrypt", trace.WithAttributes(
|
||||
attribute.String("name", name),
|
||||
attribute.String("namespace", namespace.String()),
|
||||
))
|
||||
defer span.End()
|
||||
|
||||
req := readSecureValueForDecrypt{
|
||||
SQLTemplate: sqltemplate.New(s.dialect),
|
||||
Namespace: namespace.String(),
|
||||
Name: name,
|
||||
}
|
||||
|
||||
query, err := sqltemplate.Execute(sqlSecureValueReadForDecrypt, req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("execute template %q: %w", sqlSecureValueReadForDecrypt.Name(), err)
|
||||
}
|
||||
|
||||
res, err := s.db.QueryContext(ctx, query, req.GetArgs()...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("reading row: %w", err)
|
||||
}
|
||||
defer func() { _ = res.Close() }()
|
||||
|
||||
var row secureValueForDecrypt
|
||||
if !res.Next() {
|
||||
return nil, contracts.ErrSecureValueNotFound
|
||||
}
|
||||
if err := res.Scan(
|
||||
&row.Keeper, &row.Decrypters,
|
||||
&row.Ref, &row.ExternalID); err != nil {
|
||||
return nil, fmt.Errorf("failed to scan secure value row: %w", err)
|
||||
}
|
||||
|
||||
if err := res.Err(); err != nil {
|
||||
return nil, fmt.Errorf("read rows error: %w", err)
|
||||
}
|
||||
|
||||
secureValue, err := row.toDecrypt()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("convert to kubernetes object: %w", err)
|
||||
}
|
||||
|
||||
s.metrics.SecureValueGetForDecryptDuration.Observe(time.Since(start).Seconds())
|
||||
|
||||
return secureValue, nil
|
||||
}
|
||||
|
||||
func (s *secureValueMetadataStorage) read(ctx context.Context, namespace xkube.Namespace, name string, opts contracts.ReadOpts) (secureValueDB, error) {
|
||||
req := readSecureValue{
|
||||
SQLTemplate: sqltemplate.New(s.dialect),
|
||||
Namespace: namespace.String(),
|
||||
Name: name,
|
||||
IsForUpdate: opts.ForUpdate,
|
||||
}
|
||||
|
||||
query, err := sqltemplate.Execute(sqlSecureValueRead, req)
|
||||
if err != nil {
|
||||
return secureValueDB{}, fmt.Errorf("execute template %q: %w", sqlSecureValueRead.Name(), err)
|
||||
}
|
||||
|
||||
res, err := s.db.QueryContext(ctx, query, req.GetArgs()...)
|
||||
if err != nil {
|
||||
return secureValueDB{}, fmt.Errorf("reading row: %w", err)
|
||||
}
|
||||
defer func() { _ = res.Close() }()
|
||||
|
||||
var secureValue secureValueDB
|
||||
if !res.Next() {
|
||||
return secureValueDB{}, contracts.ErrSecureValueNotFound
|
||||
}
|
||||
|
||||
if err := res.Scan(
|
||||
&secureValue.GUID, &secureValue.Name, &secureValue.Namespace,
|
||||
&secureValue.Annotations, &secureValue.Labels,
|
||||
&secureValue.Created, &secureValue.CreatedBy,
|
||||
&secureValue.Updated, &secureValue.UpdatedBy,
|
||||
&secureValue.Phase, &secureValue.Message,
|
||||
&secureValue.Description, &secureValue.Keeper, &secureValue.Decrypters, &secureValue.Ref, &secureValue.ExternalID); err != nil {
|
||||
return secureValueDB{}, fmt.Errorf("failed to scan secure value row: %w", err)
|
||||
}
|
||||
|
||||
if err := res.Err(); err != nil {
|
||||
return secureValueDB{}, fmt.Errorf("read rows error: %w", err)
|
||||
}
|
||||
return secureValue, nil
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package metadata
|
||||
package metadata_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
@ -10,6 +10,7 @@ import (
|
|||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||
"github.com/grafana/grafana/pkg/storage/secret/database"
|
||||
"github.com/grafana/grafana/pkg/storage/secret/metadata"
|
||||
"github.com/grafana/grafana/pkg/storage/secret/migrator"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.opentelemetry.io/otel/trace/noop"
|
||||
|
@ -43,11 +44,11 @@ func Test_SecureValueMetadataStorage_CreateAndRead(t *testing.T) {
|
|||
features := featuremgmt.WithFeatures(featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs, featuremgmt.FlagSecretsManagementAppPlatform)
|
||||
|
||||
// Initialize the secure value storage
|
||||
secureValueStorage, err := ProvideSecureValueMetadataStorage(db, tracer, features, nil)
|
||||
secureValueStorage, err := metadata.ProvideSecureValueMetadataStorage(db, tracer, features, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Initialize the keeper storage
|
||||
keeperStorage, err := ProvideKeeperMetadataStorage(db, tracer, features, nil)
|
||||
keeperStorage, err := metadata.ProvideKeeperMetadataStorage(db, tracer, features, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Run("create and read a secure value", func(t *testing.T) {
|
||||
|
@ -73,7 +74,8 @@ func Test_SecureValueMetadataStorage_CreateAndRead(t *testing.T) {
|
|||
require.Equal(t, "default", createdSecureValue.Namespace)
|
||||
require.Equal(t, "test description", createdSecureValue.Spec.Description)
|
||||
require.Equal(t, keeperName, *createdSecureValue.Spec.Keeper)
|
||||
require.Equal(t, secretv0alpha1.SecureValuePhasePending, createdSecureValue.Status.Phase)
|
||||
|
||||
require.NoError(t, secureValueStorage.SetVersionToActive(ctx, xkube.Namespace(createdSecureValue.Namespace), createdSecureValue.Name, createdSecureValue.Status.Version))
|
||||
|
||||
// Read the secure value back
|
||||
readSecureValue, err := secureValueStorage.Read(ctx, xkube.Namespace("default"), "sv-test", contracts.ReadOpts{})
|
||||
|
@ -83,7 +85,6 @@ func Test_SecureValueMetadataStorage_CreateAndRead(t *testing.T) {
|
|||
require.Equal(t, "default", readSecureValue.Namespace)
|
||||
require.Equal(t, "test description", readSecureValue.Spec.Description)
|
||||
require.Equal(t, keeperName, *readSecureValue.Spec.Keeper)
|
||||
require.Equal(t, secretv0alpha1.SecureValuePhasePending, readSecureValue.Status.Phase)
|
||||
|
||||
// List secure values and verify our value is in the list
|
||||
secureValues, err := secureValueStorage.List(ctx, xkube.Namespace("default"))
|
||||
|
@ -98,7 +99,6 @@ func Test_SecureValueMetadataStorage_CreateAndRead(t *testing.T) {
|
|||
require.Equal(t, "default", sv.Namespace)
|
||||
require.Equal(t, "test description", sv.Spec.Description)
|
||||
require.Equal(t, keeperName, *sv.Spec.Keeper)
|
||||
require.Equal(t, secretv0alpha1.SecureValuePhasePending, sv.Status.Phase)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
@ -125,6 +125,8 @@ func Test_SecureValueMetadataStorage_CreateAndRead(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
require.NotNil(t, createdSecureValue)
|
||||
|
||||
require.NoError(t, secureValueStorage.SetVersionToActive(ctx, xkube.Namespace(createdSecureValue.Namespace), createdSecureValue.Name, createdSecureValue.Status.Version))
|
||||
|
||||
// Read the secure value to verify it exists
|
||||
readSecureValue, err := secureValueStorage.Read(ctx, xkube.Namespace("default"), "sv-test-2", contracts.ReadOpts{})
|
||||
require.NoError(t, err)
|
||||
|
@ -132,7 +134,7 @@ func Test_SecureValueMetadataStorage_CreateAndRead(t *testing.T) {
|
|||
require.Equal(t, "sv-test-2", readSecureValue.Name)
|
||||
|
||||
// Delete the secure value
|
||||
err = secureValueStorage.Delete(ctx, xkube.Namespace("default"), "sv-test-2")
|
||||
err = secureValueStorage.SetVersionToInactive(ctx, xkube.Namespace("default"), "sv-test-2", readSecureValue.Status.Version)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Try to read the deleted secure value - should return error
|
||||
|
|
|
@ -8,8 +8,8 @@ INSERT INTO `secret_secure_value` (
|
|||
`created_by`,
|
||||
`updated`,
|
||||
`updated_by`,
|
||||
`status_phase`,
|
||||
`status_message`,
|
||||
`active`,
|
||||
`version`,
|
||||
`description`,
|
||||
`keeper`,
|
||||
`decrypters`,
|
||||
|
@ -25,8 +25,8 @@ INSERT INTO `secret_secure_value` (
|
|||
'user:ryan',
|
||||
5678,
|
||||
'user:cameron',
|
||||
'creating',
|
||||
'message_test',
|
||||
FALSE,
|
||||
1,
|
||||
'description',
|
||||
'keeper_test',
|
||||
'decrypters_test',
|
||||
|
|
|
@ -8,7 +8,8 @@ INSERT INTO `secret_secure_value` (
|
|||
`created_by`,
|
||||
`updated`,
|
||||
`updated_by`,
|
||||
`status_phase`,
|
||||
`active`,
|
||||
`version`,
|
||||
`description`,
|
||||
`external_id`
|
||||
) VALUES (
|
||||
|
@ -21,7 +22,8 @@ INSERT INTO `secret_secure_value` (
|
|||
'user:ryan',
|
||||
5678,
|
||||
'user:cameron',
|
||||
'creating',
|
||||
FALSE,
|
||||
1,
|
||||
'description',
|
||||
'extId'
|
||||
);
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
DELETE FROM `secret_secure_value`
|
||||
WHERE `namespace` = 'ns' AND
|
||||
`name` = 'name'
|
||||
;
|
10
pkg/storage/secret/metadata/testdata/mysql--secure_value_get_latest_version-get latest secure value version.sql
vendored
Executable file
10
pkg/storage/secret/metadata/testdata/mysql--secure_value_get_latest_version-get latest secure value version.sql
vendored
Executable file
|
@ -0,0 +1,10 @@
|
|||
SELECT
|
||||
`version`
|
||||
FROM
|
||||
`secret_secure_value`
|
||||
WHERE
|
||||
`namespace` = 'ns' AND
|
||||
`name` = 'name'
|
||||
ORDER BY `version` DESC
|
||||
LIMIT 1
|
||||
;
|
|
@ -8,15 +8,17 @@ SELECT
|
|||
`created_by`,
|
||||
`updated`,
|
||||
`updated_by`,
|
||||
`status_phase`,
|
||||
`status_message`,
|
||||
`description`,
|
||||
`keeper`,
|
||||
`decrypters`,
|
||||
`ref`,
|
||||
`external_id`
|
||||
`external_id`,
|
||||
`version`,
|
||||
`active`
|
||||
FROM
|
||||
`secret_secure_value`
|
||||
WHERE `namespace` = 'ns'
|
||||
WHERE
|
||||
`namespace` = 'ns' AND
|
||||
`active` = true
|
||||
ORDER BY `updated` DESC
|
||||
;
|
||||
|
|
|
@ -3,7 +3,9 @@ SELECT
|
|||
`keeper`
|
||||
FROM
|
||||
`secret_secure_value`
|
||||
WHERE `namespace` = 'ns' AND
|
||||
`name` IN ('a', 'b')
|
||||
WHERE
|
||||
`namespace` = 'ns' AND
|
||||
`name` IN ('a', 'b') AND
|
||||
`active` = true
|
||||
FOR UPDATE
|
||||
;
|
||||
|
|
|
@ -1,21 +0,0 @@
|
|||
INSERT INTO `secret_secure_value_outbox` (
|
||||
`request_id`,
|
||||
`message_type`,
|
||||
`name`,
|
||||
`namespace`,
|
||||
`encrypted_secret`,
|
||||
`keeper_name`,
|
||||
`external_id`,
|
||||
`receive_count`,
|
||||
`created`
|
||||
) VALUES (
|
||||
'',
|
||||
'some-type',
|
||||
'name',
|
||||
'namespace',
|
||||
'encrypted',
|
||||
'keeper',
|
||||
'',
|
||||
0,
|
||||
1234
|
||||
);
|
|
@ -1,19 +0,0 @@
|
|||
INSERT INTO `secret_secure_value_outbox` (
|
||||
`request_id`,
|
||||
`message_type`,
|
||||
`name`,
|
||||
`namespace`,
|
||||
`keeper_name`,
|
||||
`external_id`,
|
||||
`receive_count`,
|
||||
`created`
|
||||
) VALUES (
|
||||
'',
|
||||
'some-type',
|
||||
'name',
|
||||
'namespace',
|
||||
'keeper',
|
||||
'external-id',
|
||||
0,
|
||||
1234
|
||||
);
|
|
@ -1,19 +0,0 @@
|
|||
INSERT INTO `secret_secure_value_outbox` (
|
||||
`request_id`,
|
||||
`message_type`,
|
||||
`name`,
|
||||
`namespace`,
|
||||
`encrypted_secret`,
|
||||
`keeper_name`,
|
||||
`receive_count`,
|
||||
`created`
|
||||
) VALUES (
|
||||
'',
|
||||
'some-type',
|
||||
'name',
|
||||
'namespace',
|
||||
'encrypted',
|
||||
'keeper',
|
||||
0,
|
||||
1234
|
||||
);
|
|
@ -1,19 +0,0 @@
|
|||
INSERT INTO `secret_secure_value_outbox` (
|
||||
`request_id`,
|
||||
`message_type`,
|
||||
`name`,
|
||||
`namespace`,
|
||||
`encrypted_secret`,
|
||||
`external_id`,
|
||||
`receive_count`,
|
||||
`created`
|
||||
) VALUES (
|
||||
'',
|
||||
'some-type',
|
||||
'name',
|
||||
'namespace',
|
||||
'encrypted',
|
||||
'external-id',
|
||||
0,
|
||||
1234
|
||||
);
|
|
@ -1,5 +0,0 @@
|
|||
DELETE FROM
|
||||
`secret_secure_value_outbox`
|
||||
WHERE
|
||||
`id` = 1
|
||||
;
|
|
@ -1,6 +0,0 @@
|
|||
SELECT
|
||||
`id`
|
||||
FROM `secret_secure_value_outbox`
|
||||
ORDER BY id ASC
|
||||
LIMIT 10
|
||||
;
|
|
@ -1,19 +0,0 @@
|
|||
SELECT
|
||||
`request_id`,
|
||||
`id`,
|
||||
`message_type`,
|
||||
`name`,
|
||||
`namespace`,
|
||||
`encrypted_secret`,
|
||||
`keeper_name`,
|
||||
`external_id`,
|
||||
`receive_count`,
|
||||
`created`
|
||||
FROM
|
||||
`secret_secure_value_outbox`
|
||||
WHERE
|
||||
`id` IN (1, 2, 3)
|
||||
ORDER BY
|
||||
`id` ASC
|
||||
FOR UPDATE SKIP LOCKED
|
||||
;
|
|
@ -1,7 +0,0 @@
|
|||
UPDATE
|
||||
`secret_secure_value_outbox`
|
||||
SET
|
||||
`receive_count` = `receive_count` + 1
|
||||
WHERE
|
||||
`id` IN (1, 2, 3)
|
||||
;
|
|
@ -8,16 +8,18 @@ SELECT
|
|||
`created_by`,
|
||||
`updated`,
|
||||
`updated_by`,
|
||||
`status_phase`,
|
||||
`status_message`,
|
||||
`description`,
|
||||
`keeper`,
|
||||
`decrypters`,
|
||||
`ref`,
|
||||
`external_id`
|
||||
`external_id`,
|
||||
`active`,
|
||||
`version`
|
||||
FROM
|
||||
`secret_secure_value`
|
||||
WHERE `namespace` = 'ns' AND
|
||||
`name` = 'name'
|
||||
WHERE
|
||||
`namespace` = 'ns' AND
|
||||
`name` = 'name' AND
|
||||
`active` = true
|
||||
FOR UPDATE
|
||||
;
|
||||
|
|
|
@ -8,15 +8,17 @@ SELECT
|
|||
`created_by`,
|
||||
`updated`,
|
||||
`updated_by`,
|
||||
`status_phase`,
|
||||
`status_message`,
|
||||
`description`,
|
||||
`keeper`,
|
||||
`decrypters`,
|
||||
`ref`,
|
||||
`external_id`
|
||||
`external_id`,
|
||||
`active`,
|
||||
`version`
|
||||
FROM
|
||||
`secret_secure_value`
|
||||
WHERE `namespace` = 'ns' AND
|
||||
`name` = 'name'
|
||||
WHERE
|
||||
`namespace` = 'ns' AND
|
||||
`name` = 'name' AND
|
||||
`active` = true
|
||||
;
|
||||
|
|
|
@ -2,9 +2,12 @@ SELECT
|
|||
`keeper`,
|
||||
`decrypters`,
|
||||
`ref`,
|
||||
`external_id`
|
||||
`external_id`,
|
||||
`active`
|
||||
FROM
|
||||
`secret_secure_value`
|
||||
WHERE `namespace` = 'ns' AND
|
||||
`name` = 'name'
|
||||
WHERE
|
||||
`namespace` = 'ns' AND
|
||||
`name` = 'name' AND
|
||||
`active` = true
|
||||
;
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
UPDATE
|
||||
`secret_secure_value`
|
||||
SET
|
||||
`active` = (`version` = 1)
|
||||
WHERE
|
||||
`namespace` = 'ns' AND
|
||||
`name` = 'name'
|
||||
;
|
|
@ -1,22 +0,0 @@
|
|||
UPDATE
|
||||
`secret_secure_value`
|
||||
SET
|
||||
`guid` = 'abc',
|
||||
`name` = 'name',
|
||||
`namespace` = 'ns',
|
||||
`annotations` = '{"x":"XXXX"}',
|
||||
`labels` = '{"a":"AAA", "b", "BBBB"}',
|
||||
`created` = 1234,
|
||||
`created_by` = 'user:ryan',
|
||||
`updated` = 5678,
|
||||
`updated_by` = 'user:cameron',
|
||||
`status_phase` = 'creating',
|
||||
`status_message` = 'message_test',
|
||||
`description` = 'description',
|
||||
`keeper` = 'keeper_test',
|
||||
`decrypters` = 'decrypters_test',
|
||||
`ref` = 'ref_test',
|
||||
`external_id` = 'extId'
|
||||
WHERE `namespace` = 'ns' AND
|
||||
`name` = 'name'
|
||||
;
|
|
@ -1,18 +0,0 @@
|
|||
UPDATE
|
||||
`secret_secure_value`
|
||||
SET
|
||||
`guid` = 'abc',
|
||||
`name` = 'name',
|
||||
`namespace` = 'ns',
|
||||
`annotations` = '{"x":"XXXX"}',
|
||||
`labels` = '{"a":"AAA", "b", "BBBB"}',
|
||||
`created` = 1234,
|
||||
`created_by` = 'user:ryan',
|
||||
`updated` = 5678,
|
||||
`updated_by` = 'user:cameron',
|
||||
`status_phase` = 'creating',
|
||||
`description` = 'description',
|
||||
`external_id` = 'extId'
|
||||
WHERE `namespace` = 'ns' AND
|
||||
`name` = 'name'
|
||||
;
|
|
@ -2,6 +2,8 @@ UPDATE
|
|||
`secret_secure_value`
|
||||
SET
|
||||
`external_id` = 'extId'
|
||||
WHERE `namespace` = 'ns' AND
|
||||
`name` = 'name'
|
||||
WHERE
|
||||
`namespace` = 'ns' AND
|
||||
`name` = 'name' AND
|
||||
`version` = 0
|
||||
;
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
UPDATE
|
||||
`secret_secure_value`
|
||||
SET
|
||||
`status_phase` = 'Succeeded',
|
||||
`status_message` = 'message-1'
|
||||
WHERE `namespace` = 'ns' AND
|
||||
`name` = 'name'
|
||||
;
|
|
@ -8,8 +8,8 @@ INSERT INTO "secret_secure_value" (
|
|||
"created_by",
|
||||
"updated",
|
||||
"updated_by",
|
||||
"status_phase",
|
||||
"status_message",
|
||||
"active",
|
||||
"version",
|
||||
"description",
|
||||
"keeper",
|
||||
"decrypters",
|
||||
|
@ -25,8 +25,8 @@ INSERT INTO "secret_secure_value" (
|
|||
'user:ryan',
|
||||
5678,
|
||||
'user:cameron',
|
||||
'creating',
|
||||
'message_test',
|
||||
FALSE,
|
||||
1,
|
||||
'description',
|
||||
'keeper_test',
|
||||
'decrypters_test',
|
||||
|
|
|
@ -8,7 +8,8 @@ INSERT INTO "secret_secure_value" (
|
|||
"created_by",
|
||||
"updated",
|
||||
"updated_by",
|
||||
"status_phase",
|
||||
"active",
|
||||
"version",
|
||||
"description",
|
||||
"external_id"
|
||||
) VALUES (
|
||||
|
@ -21,7 +22,8 @@ INSERT INTO "secret_secure_value" (
|
|||
'user:ryan',
|
||||
5678,
|
||||
'user:cameron',
|
||||
'creating',
|
||||
FALSE,
|
||||
1,
|
||||
'description',
|
||||
'extId'
|
||||
);
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
DELETE FROM "secret_secure_value"
|
||||
WHERE "namespace" = 'ns' AND
|
||||
"name" = 'name'
|
||||
;
|
|
@ -0,0 +1,10 @@
|
|||
SELECT
|
||||
"version"
|
||||
FROM
|
||||
"secret_secure_value"
|
||||
WHERE
|
||||
"namespace" = 'ns' AND
|
||||
"name" = 'name'
|
||||
ORDER BY "version" DESC
|
||||
LIMIT 1
|
||||
;
|
|
@ -8,15 +8,17 @@ SELECT
|
|||
"created_by",
|
||||
"updated",
|
||||
"updated_by",
|
||||
"status_phase",
|
||||
"status_message",
|
||||
"description",
|
||||
"keeper",
|
||||
"decrypters",
|
||||
"ref",
|
||||
"external_id"
|
||||
"external_id",
|
||||
"version",
|
||||
"active"
|
||||
FROM
|
||||
"secret_secure_value"
|
||||
WHERE "namespace" = 'ns'
|
||||
WHERE
|
||||
"namespace" = 'ns' AND
|
||||
"active" = true
|
||||
ORDER BY "updated" DESC
|
||||
;
|
||||
|
|
|
@ -3,7 +3,9 @@ SELECT
|
|||
"keeper"
|
||||
FROM
|
||||
"secret_secure_value"
|
||||
WHERE "namespace" = 'ns' AND
|
||||
"name" IN ('a', 'b')
|
||||
WHERE
|
||||
"namespace" = 'ns' AND
|
||||
"name" IN ('a', 'b') AND
|
||||
"active" = true
|
||||
FOR UPDATE
|
||||
;
|
||||
|
|
|
@ -1,21 +0,0 @@
|
|||
INSERT INTO "secret_secure_value_outbox" (
|
||||
"request_id",
|
||||
"message_type",
|
||||
"name",
|
||||
"namespace",
|
||||
"encrypted_secret",
|
||||
"keeper_name",
|
||||
"external_id",
|
||||
"receive_count",
|
||||
"created"
|
||||
) VALUES (
|
||||
'',
|
||||
'some-type',
|
||||
'name',
|
||||
'namespace',
|
||||
'encrypted',
|
||||
'keeper',
|
||||
'',
|
||||
0,
|
||||
1234
|
||||
);
|
|
@ -1,19 +0,0 @@
|
|||
INSERT INTO "secret_secure_value_outbox" (
|
||||
"request_id",
|
||||
"message_type",
|
||||
"name",
|
||||
"namespace",
|
||||
"keeper_name",
|
||||
"external_id",
|
||||
"receive_count",
|
||||
"created"
|
||||
) VALUES (
|
||||
'',
|
||||
'some-type',
|
||||
'name',
|
||||
'namespace',
|
||||
'keeper',
|
||||
'external-id',
|
||||
0,
|
||||
1234
|
||||
);
|
|
@ -1,19 +0,0 @@
|
|||
INSERT INTO "secret_secure_value_outbox" (
|
||||
"request_id",
|
||||
"message_type",
|
||||
"name",
|
||||
"namespace",
|
||||
"encrypted_secret",
|
||||
"keeper_name",
|
||||
"receive_count",
|
||||
"created"
|
||||
) VALUES (
|
||||
'',
|
||||
'some-type',
|
||||
'name',
|
||||
'namespace',
|
||||
'encrypted',
|
||||
'keeper',
|
||||
0,
|
||||
1234
|
||||
);
|
|
@ -1,19 +0,0 @@
|
|||
INSERT INTO "secret_secure_value_outbox" (
|
||||
"request_id",
|
||||
"message_type",
|
||||
"name",
|
||||
"namespace",
|
||||
"encrypted_secret",
|
||||
"external_id",
|
||||
"receive_count",
|
||||
"created"
|
||||
) VALUES (
|
||||
'',
|
||||
'some-type',
|
||||
'name',
|
||||
'namespace',
|
||||
'encrypted',
|
||||
'external-id',
|
||||
0,
|
||||
1234
|
||||
);
|
|
@ -1,5 +0,0 @@
|
|||
DELETE FROM
|
||||
"secret_secure_value_outbox"
|
||||
WHERE
|
||||
"id" = 1
|
||||
;
|
|
@ -1,6 +0,0 @@
|
|||
SELECT
|
||||
"id"
|
||||
FROM "secret_secure_value_outbox"
|
||||
ORDER BY id ASC
|
||||
LIMIT 10
|
||||
;
|
|
@ -1,19 +0,0 @@
|
|||
SELECT
|
||||
"request_id",
|
||||
"id",
|
||||
"message_type",
|
||||
"name",
|
||||
"namespace",
|
||||
"encrypted_secret",
|
||||
"keeper_name",
|
||||
"external_id",
|
||||
"receive_count",
|
||||
"created"
|
||||
FROM
|
||||
"secret_secure_value_outbox"
|
||||
WHERE
|
||||
"id" IN (1, 2, 3)
|
||||
ORDER BY
|
||||
"id" ASC
|
||||
FOR UPDATE SKIP LOCKED
|
||||
;
|
|
@ -1,7 +0,0 @@
|
|||
UPDATE
|
||||
"secret_secure_value_outbox"
|
||||
SET
|
||||
"receive_count" = "receive_count" + 1
|
||||
WHERE
|
||||
"id" IN (1, 2, 3)
|
||||
;
|
|
@ -8,16 +8,18 @@ SELECT
|
|||
"created_by",
|
||||
"updated",
|
||||
"updated_by",
|
||||
"status_phase",
|
||||
"status_message",
|
||||
"description",
|
||||
"keeper",
|
||||
"decrypters",
|
||||
"ref",
|
||||
"external_id"
|
||||
"external_id",
|
||||
"active",
|
||||
"version"
|
||||
FROM
|
||||
"secret_secure_value"
|
||||
WHERE "namespace" = 'ns' AND
|
||||
"name" = 'name'
|
||||
WHERE
|
||||
"namespace" = 'ns' AND
|
||||
"name" = 'name' AND
|
||||
"active" = true
|
||||
FOR UPDATE
|
||||
;
|
||||
|
|
|
@ -8,15 +8,17 @@ SELECT
|
|||
"created_by",
|
||||
"updated",
|
||||
"updated_by",
|
||||
"status_phase",
|
||||
"status_message",
|
||||
"description",
|
||||
"keeper",
|
||||
"decrypters",
|
||||
"ref",
|
||||
"external_id"
|
||||
"external_id",
|
||||
"active",
|
||||
"version"
|
||||
FROM
|
||||
"secret_secure_value"
|
||||
WHERE "namespace" = 'ns' AND
|
||||
"name" = 'name'
|
||||
WHERE
|
||||
"namespace" = 'ns' AND
|
||||
"name" = 'name' AND
|
||||
"active" = true
|
||||
;
|
||||
|
|
|
@ -2,9 +2,12 @@ SELECT
|
|||
"keeper",
|
||||
"decrypters",
|
||||
"ref",
|
||||
"external_id"
|
||||
"external_id",
|
||||
"active"
|
||||
FROM
|
||||
"secret_secure_value"
|
||||
WHERE "namespace" = 'ns' AND
|
||||
"name" = 'name'
|
||||
WHERE
|
||||
"namespace" = 'ns' AND
|
||||
"name" = 'name' AND
|
||||
"active" = true
|
||||
;
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
UPDATE
|
||||
"secret_secure_value"
|
||||
SET
|
||||
"active" = ("version" = 1)
|
||||
WHERE
|
||||
"namespace" = 'ns' AND
|
||||
"name" = 'name'
|
||||
;
|
|
@ -1,22 +0,0 @@
|
|||
UPDATE
|
||||
"secret_secure_value"
|
||||
SET
|
||||
"guid" = 'abc',
|
||||
"name" = 'name',
|
||||
"namespace" = 'ns',
|
||||
"annotations" = '{"x":"XXXX"}',
|
||||
"labels" = '{"a":"AAA", "b", "BBBB"}',
|
||||
"created" = 1234,
|
||||
"created_by" = 'user:ryan',
|
||||
"updated" = 5678,
|
||||
"updated_by" = 'user:cameron',
|
||||
"status_phase" = 'creating',
|
||||
"status_message" = 'message_test',
|
||||
"description" = 'description',
|
||||
"keeper" = 'keeper_test',
|
||||
"decrypters" = 'decrypters_test',
|
||||
"ref" = 'ref_test',
|
||||
"external_id" = 'extId'
|
||||
WHERE "namespace" = 'ns' AND
|
||||
"name" = 'name'
|
||||
;
|
|
@ -1,18 +0,0 @@
|
|||
UPDATE
|
||||
"secret_secure_value"
|
||||
SET
|
||||
"guid" = 'abc',
|
||||
"name" = 'name',
|
||||
"namespace" = 'ns',
|
||||
"annotations" = '{"x":"XXXX"}',
|
||||
"labels" = '{"a":"AAA", "b", "BBBB"}',
|
||||
"created" = 1234,
|
||||
"created_by" = 'user:ryan',
|
||||
"updated" = 5678,
|
||||
"updated_by" = 'user:cameron',
|
||||
"status_phase" = 'creating',
|
||||
"description" = 'description',
|
||||
"external_id" = 'extId'
|
||||
WHERE "namespace" = 'ns' AND
|
||||
"name" = 'name'
|
||||
;
|
|
@ -2,6 +2,8 @@ UPDATE
|
|||
"secret_secure_value"
|
||||
SET
|
||||
"external_id" = 'extId'
|
||||
WHERE "namespace" = 'ns' AND
|
||||
"name" = 'name'
|
||||
WHERE
|
||||
"namespace" = 'ns' AND
|
||||
"name" = 'name' AND
|
||||
"version" = 0
|
||||
;
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
UPDATE
|
||||
"secret_secure_value"
|
||||
SET
|
||||
"status_phase" = 'Succeeded',
|
||||
"status_message" = 'message-1'
|
||||
WHERE "namespace" = 'ns' AND
|
||||
"name" = 'name'
|
||||
;
|
|
@ -8,8 +8,8 @@ INSERT INTO "secret_secure_value" (
|
|||
"created_by",
|
||||
"updated",
|
||||
"updated_by",
|
||||
"status_phase",
|
||||
"status_message",
|
||||
"active",
|
||||
"version",
|
||||
"description",
|
||||
"keeper",
|
||||
"decrypters",
|
||||
|
@ -25,8 +25,8 @@ INSERT INTO "secret_secure_value" (
|
|||
'user:ryan',
|
||||
5678,
|
||||
'user:cameron',
|
||||
'creating',
|
||||
'message_test',
|
||||
FALSE,
|
||||
1,
|
||||
'description',
|
||||
'keeper_test',
|
||||
'decrypters_test',
|
||||
|
|
|
@ -8,7 +8,8 @@ INSERT INTO "secret_secure_value" (
|
|||
"created_by",
|
||||
"updated",
|
||||
"updated_by",
|
||||
"status_phase",
|
||||
"active",
|
||||
"version",
|
||||
"description",
|
||||
"external_id"
|
||||
) VALUES (
|
||||
|
@ -21,7 +22,8 @@ INSERT INTO "secret_secure_value" (
|
|||
'user:ryan',
|
||||
5678,
|
||||
'user:cameron',
|
||||
'creating',
|
||||
FALSE,
|
||||
1,
|
||||
'description',
|
||||
'extId'
|
||||
);
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
DELETE FROM "secret_secure_value"
|
||||
WHERE "namespace" = 'ns' AND
|
||||
"name" = 'name'
|
||||
;
|
10
pkg/storage/secret/metadata/testdata/sqlite--secure_value_get_latest_version-get latest secure value version.sql
vendored
Executable file
10
pkg/storage/secret/metadata/testdata/sqlite--secure_value_get_latest_version-get latest secure value version.sql
vendored
Executable file
|
@ -0,0 +1,10 @@
|
|||
SELECT
|
||||
"version"
|
||||
FROM
|
||||
"secret_secure_value"
|
||||
WHERE
|
||||
"namespace" = 'ns' AND
|
||||
"name" = 'name'
|
||||
ORDER BY "version" DESC
|
||||
LIMIT 1
|
||||
;
|
|
@ -8,15 +8,17 @@ SELECT
|
|||
"created_by",
|
||||
"updated",
|
||||
"updated_by",
|
||||
"status_phase",
|
||||
"status_message",
|
||||
"description",
|
||||
"keeper",
|
||||
"decrypters",
|
||||
"ref",
|
||||
"external_id"
|
||||
"external_id",
|
||||
"version",
|
||||
"active"
|
||||
FROM
|
||||
"secret_secure_value"
|
||||
WHERE "namespace" = 'ns'
|
||||
WHERE
|
||||
"namespace" = 'ns' AND
|
||||
"active" = true
|
||||
ORDER BY "updated" DESC
|
||||
;
|
||||
|
|
|
@ -3,6 +3,8 @@ SELECT
|
|||
"keeper"
|
||||
FROM
|
||||
"secret_secure_value"
|
||||
WHERE "namespace" = 'ns' AND
|
||||
"name" IN ('a', 'b')
|
||||
WHERE
|
||||
"namespace" = 'ns' AND
|
||||
"name" IN ('a', 'b') AND
|
||||
"active" = true
|
||||
;
|
||||
|
|
|
@ -1,21 +0,0 @@
|
|||
INSERT INTO "secret_secure_value_outbox" (
|
||||
"request_id",
|
||||
"message_type",
|
||||
"name",
|
||||
"namespace",
|
||||
"encrypted_secret",
|
||||
"keeper_name",
|
||||
"external_id",
|
||||
"receive_count",
|
||||
"created"
|
||||
) VALUES (
|
||||
'',
|
||||
'some-type',
|
||||
'name',
|
||||
'namespace',
|
||||
'encrypted',
|
||||
'keeper',
|
||||
'',
|
||||
0,
|
||||
1234
|
||||
);
|
|
@ -1,19 +0,0 @@
|
|||
INSERT INTO "secret_secure_value_outbox" (
|
||||
"request_id",
|
||||
"message_type",
|
||||
"name",
|
||||
"namespace",
|
||||
"keeper_name",
|
||||
"external_id",
|
||||
"receive_count",
|
||||
"created"
|
||||
) VALUES (
|
||||
'',
|
||||
'some-type',
|
||||
'name',
|
||||
'namespace',
|
||||
'keeper',
|
||||
'external-id',
|
||||
0,
|
||||
1234
|
||||
);
|
|
@ -1,19 +0,0 @@
|
|||
INSERT INTO "secret_secure_value_outbox" (
|
||||
"request_id",
|
||||
"message_type",
|
||||
"name",
|
||||
"namespace",
|
||||
"encrypted_secret",
|
||||
"keeper_name",
|
||||
"receive_count",
|
||||
"created"
|
||||
) VALUES (
|
||||
'',
|
||||
'some-type',
|
||||
'name',
|
||||
'namespace',
|
||||
'encrypted',
|
||||
'keeper',
|
||||
0,
|
||||
1234
|
||||
);
|
|
@ -1,19 +0,0 @@
|
|||
INSERT INTO "secret_secure_value_outbox" (
|
||||
"request_id",
|
||||
"message_type",
|
||||
"name",
|
||||
"namespace",
|
||||
"encrypted_secret",
|
||||
"external_id",
|
||||
"receive_count",
|
||||
"created"
|
||||
) VALUES (
|
||||
'',
|
||||
'some-type',
|
||||
'name',
|
||||
'namespace',
|
||||
'encrypted',
|
||||
'external-id',
|
||||
0,
|
||||
1234
|
||||
);
|
|
@ -1,5 +0,0 @@
|
|||
DELETE FROM
|
||||
"secret_secure_value_outbox"
|
||||
WHERE
|
||||
"id" = 1
|
||||
;
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue