diff --git a/.betterer.results b/.betterer.results index 0fad192cedb..bba659d251f 100644 --- a/.betterer.results +++ b/.betterer.results @@ -4776,10 +4776,9 @@ exports[`better eslint`] = { [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "2"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "3"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "4"], - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "5"], + [0, 0, 0, "No untranslated strings. Wrap text with ", "5"], [0, 0, 0, "No untranslated strings. Wrap text with ", "6"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "7"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "8"] + [0, 0, 0, "No untranslated strings. Wrap text with ", "7"] ], "public/app/features/plugins/admin/components/InstallControls/index.tsx:5381": [ [0, 0, 0, "Do not re-export imported variable (\`./InstallControlsButton\`)", "0"], diff --git a/.citools/swagger/go.mod b/.citools/swagger/go.mod new file mode 100644 index 00000000000..80a7feab758 --- /dev/null +++ b/.citools/swagger/go.mod @@ -0,0 +1,65 @@ +module swagger + +go 1.24.1 + +tool github.com/go-swagger/go-swagger/cmd/swagger + +require ( + github.com/Masterminds/goutils v1.1.1 // indirect + github.com/Masterminds/semver/v3 v3.2.1 // indirect + github.com/Masterminds/sprig/v3 v3.2.3 // indirect + github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/fsnotify/fsnotify v1.7.0 // indirect + github.com/go-openapi/analysis v0.23.0 // indirect + github.com/go-openapi/errors v0.22.0 // indirect + github.com/go-openapi/inflect v0.21.0 // indirect + github.com/go-openapi/jsonpointer v0.21.0 // indirect + github.com/go-openapi/jsonreference v0.21.0 // indirect + github.com/go-openapi/loads v0.22.0 // indirect + github.com/go-openapi/runtime v0.28.0 // indirect + github.com/go-openapi/spec v0.21.0 // indirect + github.com/go-openapi/strfmt v0.23.0 // indirect + github.com/go-openapi/swag v0.23.0 // indirect + github.com/go-openapi/validate v0.24.0 // indirect + github.com/go-swagger/go-swagger v0.30.6-0.20240310114303-db51e79a0e37 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/gorilla/handlers v1.5.2 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect + github.com/huandu/xstrings v1.4.0 // indirect + github.com/imdario/mergo v0.3.16 // indirect + github.com/jessevdk/go-flags v1.5.0 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/kr/pretty v0.3.1 // indirect + github.com/kr/text v0.2.0 // indirect + github.com/magiconair/properties v1.8.7 // indirect + github.com/mailru/easyjson v0.7.7 // indirect + github.com/mitchellh/copystructure v1.2.0 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/mitchellh/reflectwalk v1.0.2 // indirect + github.com/oklog/ulid v1.3.1 // indirect + github.com/pelletier/go-toml/v2 v2.1.1 // indirect + github.com/rogpeppe/go-internal v1.12.0 // indirect + github.com/sagikazarmark/locafero v0.4.0 // indirect + github.com/sagikazarmark/slog-shim v0.1.0 // indirect + github.com/shopspring/decimal v1.3.1 // indirect + github.com/sourcegraph/conc v0.3.0 // indirect + github.com/spf13/afero v1.11.0 // indirect + github.com/spf13/cast v1.6.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/spf13/viper v1.18.2 // indirect + github.com/subosito/gotenv v1.6.0 // indirect + github.com/toqueteos/webbrowser v1.2.0 // indirect + go.mongodb.org/mongo-driver v1.14.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + golang.org/x/crypto v0.31.0 // indirect + golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 // indirect + golang.org/x/mod v0.17.0 // indirect + golang.org/x/sync v0.10.0 // indirect + golang.org/x/sys v0.28.0 // indirect + golang.org/x/text v0.21.0 // indirect + golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect + gopkg.in/ini.v1 v1.67.0 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/.citools/swagger/go.sum b/.citools/swagger/go.sum new file mode 100644 index 00000000000..53aa93f8a54 --- /dev/null +++ b/.citools/swagger/go.sum @@ -0,0 +1,181 @@ +github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= +github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= +github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= +github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0= +github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= +github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA= +github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM= +github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= +github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= +github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= +github.com/go-openapi/analysis v0.23.0 h1:aGday7OWupfMs+LbmLZG4k0MYXIANxcuBTYUC03zFCU= +github.com/go-openapi/analysis v0.23.0/go.mod h1:9mz9ZWaSlV8TvjQHLl2mUW2PbZtemkE8yA5v22ohupo= +github.com/go-openapi/errors v0.22.0 h1:c4xY/OLxUBSTiepAg3j/MHuAv5mJhnf53LLMWFB+u/w= +github.com/go-openapi/errors v0.22.0/go.mod h1:J3DmZScxCDufmIMsdOuDHxJbdOGC0xtUynjIx092vXE= +github.com/go-openapi/inflect v0.21.0 h1:FoBjBTQEcbg2cJUWX6uwL9OyIW8eqc9k4KhN4lfbeYk= +github.com/go-openapi/inflect v0.21.0/go.mod h1:INezMuUu7SJQc2AyR3WO0DqqYUJSj8Kb4hBd7WtjlAw= +github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= +github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= +github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ= +github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4= +github.com/go-openapi/loads v0.22.0 h1:ECPGd4jX1U6NApCGG1We+uEozOAvXvJSF4nnwHZ8Aco= +github.com/go-openapi/loads v0.22.0/go.mod h1:yLsaTCS92mnSAZX5WWoxszLj0u+Ojl+Zs5Stn1oF+rs= +github.com/go-openapi/runtime v0.28.0 h1:gpPPmWSNGo214l6n8hzdXYhPuJcGtziTOgUpvsFWGIQ= +github.com/go-openapi/runtime v0.28.0/go.mod h1:QN7OzcS+XuYmkQLw05akXk0jRH/eZ3kb18+1KwW9gyc= +github.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9ZY= +github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk= +github.com/go-openapi/strfmt v0.23.0 h1:nlUS6BCqcnAk0pyhi9Y+kdDVZdZMHfEKQiS4HaMgO/c= +github.com/go-openapi/strfmt v0.23.0/go.mod h1:NrtIpfKtWIygRkKVsxh7XQMDQW5HKQl6S5ik2elW+K4= +github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= +github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= +github.com/go-openapi/validate v0.24.0 h1:LdfDKwNbpB6Vn40xhTdNZAnfLECL81w+VX3BumrGD58= +github.com/go-openapi/validate v0.24.0/go.mod h1:iyeX1sEufmv3nPbBdX3ieNviWnOZaJ1+zquzJEf2BAQ= +github.com/go-swagger/go-swagger v0.30.6-0.20240310114303-db51e79a0e37 h1:KFcZmKdZmapAog2+eL1buervAYrYolBZk7fMecPPDmo= +github.com/go-swagger/go-swagger v0.30.6-0.20240310114303-db51e79a0e37/go.mod h1:i1/E+d8iPNReSE7y04FaVu5OPKB3il5cn+T1Egogg3I= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyEE= +github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU= +github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= +github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= +github.com/jessevdk/go-flags v1.5.0 h1:1jKYvbxEjfUl0fmqTCOfonvskHHXMjBySTLW4y9LFvc= +github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= +github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= +github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= +github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= +github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= +github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI= +github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= +github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= +github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= +github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= +github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= +github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= +github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= +github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= +github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= +github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= +github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ= +github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= +github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= +github.com/toqueteos/webbrowser v1.2.0 h1:tVP/gpK69Fx+qMJKsLE7TD8LuGWPnEV71wBN9rrstGQ= +github.com/toqueteos/webbrowser v1.2.0/go.mod h1:XWoZq4cyp9WeUeak7w7LXRUQf1F1ATJMir8RTqb4ayM= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +go.mongodb.org/mongo-driver v1.14.0 h1:P98w8egYRjYe3XDjxhYJagTokP/H6HzlsnojRgZRd80= +go.mongodb.org/mongo-driver v1.14.0/go.mod h1:Vzb0Mk/pa7e6cWw85R4F/endUC3u0U9jGcNU603k65c= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= +golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= +golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= +golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 h1:LfspQV/FYTatPTr/3HzIcmiUFH7PGP+OQ6mgDYo3yuQ= +golang.org/x/exp v0.0.0-20240222234643-814bf88cf225/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= +golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index f0cb1652a5c..bdf666d3fc6 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -317,6 +317,7 @@ /Makefile @grafana/grafana-developer-enablement-squad /scripts/build/ @grafana/grafana-developer-enablement-squad /scripts/list-release-artifacts.sh @grafana/grafana-developer-enablement-squad +/scripts/releasefinder.sh @baldm0mma /.trivyignore @grafana/grafana-backend-services-squad # OSS Plugin Partnerships backend code diff --git a/Dockerfile b/Dockerfile index 08360d8b06c..76d1101e0e3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -67,6 +67,7 @@ COPY .citools/lefthook .citools/lefthook COPY .citools/jb .citools/jb COPY .citools/drone .citools/drone COPY .citools/golangci-lint .citools/golangci-lint +COPY .citools/swagger ./citools/swagger # Include vendored dependencies COPY pkg/util/xorm pkg/util/xorm diff --git a/Makefile b/Makefile index 74fedb47854..9f022f833b2 100644 --- a/Makefile +++ b/Makefile @@ -60,15 +60,15 @@ NGALERT_SPEC_TARGET = pkg/services/ngalert/api/tooling/api.json $(NGALERT_SPEC_TARGET): +$(MAKE) -C pkg/services/ngalert/api/tooling api.json -$(MERGED_SPEC_TARGET): swagger-oss-gen swagger-enterprise-gen $(NGALERT_SPEC_TARGET) $(SWAGGER) ## Merge generated and ngalert API specs +$(MERGED_SPEC_TARGET): swagger-oss-gen swagger-enterprise-gen $(NGALERT_SPEC_TARGET) ## Merge generated and ngalert API specs # known conflicts DsPermissionType, AddApiKeyCommand, Json, Duration (identical models referenced by both specs) - $(SWAGGER) mixin -q $(SPEC_TARGET) $(ENTERPRISE_SPEC_TARGET) $(NGALERT_SPEC_TARGET) --ignore-conflicts -o $(MERGED_SPEC_TARGET) + GODEBUG=gotypesalias=0 $(GO) tool swagger mixin -q $(SPEC_TARGET) $(ENTERPRISE_SPEC_TARGET) $(NGALERT_SPEC_TARGET) --ignore-conflicts -o $(MERGED_SPEC_TARGET) .PHONY: swagger-oss-gen -swagger-oss-gen: $(SWAGGER) ## Generate API Swagger specification +swagger-oss-gen: ## Generate API Swagger specification @echo "re-generating swagger for OSS" rm -f $(SPEC_TARGET) - SWAGGER_GENERATE_EXTENSION=false $(SWAGGER) generate spec -q -m -w pkg/server -o $(SPEC_TARGET) \ + SWAGGER_GENERATE_EXTENSION=false GODEBUG=gotypesalias=0 $(GO) tool swagger generate spec -q -m -w pkg/server -o $(SPEC_TARGET) \ -x "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" \ -x "github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2/options" \ -x "github.com/prometheus/alertmanager" \ @@ -83,10 +83,10 @@ ifeq ("$(wildcard $(ENTERPRISE_EXT_FILE))","") ## if enterprise is not enabled swagger-enterprise-gen: @echo "skipping re-generating swagger for enterprise: not enabled" else -swagger-enterprise-gen: $(SWAGGER) ## Generate API Swagger specification +swagger-enterprise-gen: ## Generate API Swagger specification @echo "re-generating swagger for enterprise" rm -f $(ENTERPRISE_SPEC_TARGET) - SWAGGER_GENERATE_EXTENSION=false $(SWAGGER) generate spec -q -m -w pkg/server -o $(ENTERPRISE_SPEC_TARGET) \ + SWAGGER_GENERATE_EXTENSION=false GODEBUG=gotypesalias=0 $(GO) tool swagger generate spec -q -m -w pkg/server -o $(ENTERPRISE_SPEC_TARGET) \ -x "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" \ -x "github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2/options" \ -x "github.com/prometheus/alertmanager" \ @@ -99,8 +99,8 @@ endif swagger-gen: gen-go $(MERGED_SPEC_TARGET) swagger-validate .PHONY: swagger-validate -swagger-validate: $(MERGED_SPEC_TARGET) $(SWAGGER) ## Validate API spec - $(SWAGGER) validate --skip-warnings $(<) +swagger-validate: $(MERGED_SPEC_TARGET) # Validate API spec + GODEBUG=gotypesalias=0 $(GO) tool swagger validate --skip-warnings $(<) .PHONY: swagger-clean swagger-clean: @@ -441,7 +441,6 @@ protobuf: ## Compile protobuf definitions go install google.golang.org/protobuf/cmd/protoc-gen-go go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.4.0 buf generate pkg/plugins/backendplugin/pluginextensionv2 --template pkg/plugins/backendplugin/pluginextensionv2/buf.gen.yaml - buf generate pkg/plugins/backendplugin/secretsmanagerplugin --template pkg/plugins/backendplugin/secretsmanagerplugin/buf.gen.yaml buf generate pkg/apis/secret/v0alpha1/decrypt --template pkg/apis/secret/v0alpha1/decrypt/buf.gen.yaml buf generate pkg/storage/unified/resource --template pkg/storage/unified/resource/buf.gen.yaml buf generate pkg/services/authz/proto/v1 --template pkg/services/authz/proto/v1/buf.gen.yaml diff --git a/contribute/backend/recommended-practices.md b/contribute/backend/recommended-practices.md index e1d940614a8..4a96a0f3f72 100644 --- a/contribute/backend/recommended-practices.md +++ b/contribute/backend/recommended-practices.md @@ -119,7 +119,7 @@ For example: Today it's only possible to provision data sources and dashboards, but we want to support it throughout Grafana in the future. -### 109 - Use `context.Context` +## 109 - Use `context.Context` **State:** Completed. diff --git a/docs/sources/administration/data-source-management/teamlbac/_index.md b/docs/sources/administration/data-source-management/teamlbac/_index.md index 4bf449af022..af7c8f180cb 100644 --- a/docs/sources/administration/data-source-management/teamlbac/_index.md +++ b/docs/sources/administration/data-source-management/teamlbac/_index.md @@ -50,7 +50,7 @@ This feature addresses two common challenges faced by Grafana users: LBAC for data sources lets Grafana Teams use the same dashboard with different access control rules. To set up LBAC for data sources for a Loki data source, refer to [Configure LBAC for Loki Data Source](https://grafana.com/docs/grafana//administration/data-source-management/teamlbac/configure-teamlbac-for-loki/). -To set up LBAC for data sources for a Loki data source, refer to [Configure LBAC for Prometheus Data Source](https://grafana.com/docs/grafana//administration/data-source-management/teamlbac/configure-teamlbac-for-prometheus/). +To set up LBAC for data sources for a Prometheus data source, refer to [Configure LBAC for Prometheus Data Source](https://grafana.com/docs/grafana//administration/data-source-management/teamlbac/configure-teamlbac-for-prometheus/). ## Logs and Metrics Filtering with LBAC diff --git a/docs/sources/administration/service-accounts/_index.md b/docs/sources/administration/service-accounts/_index.md index b853496c094..dc2799d9e3e 100644 --- a/docs/sources/administration/service-accounts/_index.md +++ b/docs/sources/administration/service-accounts/_index.md @@ -63,6 +63,8 @@ refs: You can use a service account to run automated workloads in Grafana, such as dashboard provisioning, configuration, or report generation. Create service accounts and tokens to authenticate applications, such as Terraform, with the Grafana API. +A service account is not the same as a Grafana [user](/docs/grafana/latest/administration/user-management/). The role of a service account is primarily to automate work using the Grafana API. + {{< admonition type="note" >}} Service accounts replace [API keys](ref:migrate-api-keys) as the primary way to authenticate applications that interact with Grafana. {{< /admonition >}} diff --git a/docs/sources/dashboards/share-dashboards-panels/_index.md b/docs/sources/dashboards/share-dashboards-panels/_index.md index d424a4b75fb..41231f250f4 100644 --- a/docs/sources/dashboards/share-dashboards-panels/_index.md +++ b/docs/sources/dashboards/share-dashboards-panels/_index.md @@ -41,7 +41,7 @@ refs: - pattern: /docs/grafana/ destination: /docs/grafana//dashboards/share-dashboards-panels/shared-dashboards/ - pattern: /docs/grafana-cloud/ - destination: /docs/grafana/grafana-cloud/visualizations/dashboards/share-dashboards-panels/shared-dashboards/ + destination: /docs/grafana-cloud/visualizations/dashboards/share-dashboards-panels/shared-dashboards/ configure-report: - pattern: /docs/grafana/ destination: /docs/grafana//dashboards/create-reports/#create-a-report diff --git a/docs/sources/datasources/google-cloud-monitoring/_index.md b/docs/sources/datasources/google-cloud-monitoring/_index.md index 94dfe06d7a3..61b37b747fd 100644 --- a/docs/sources/datasources/google-cloud-monitoring/_index.md +++ b/docs/sources/datasources/google-cloud-monitoring/_index.md @@ -55,6 +55,42 @@ Only users with the organization administrator role can add data sources. Once you've added the Google Cloud Monitoring data source, you can [configure it](#configure-the-data-source) so that your Grafana instance's users can create queries in its [query editor](query-editor/) and apply [annotations](#annotations) when they [build dashboards](ref:build-dashboards) and use [Explore](ref:explore). +## Before you begin + +Complete the following before you configure the data source. + +### Configure Google authentication + +Before you can request data from Google Cloud Monitoring, you must configure authentication. +All requests to Google APIs are performed on the server-side by the Grafana backend. + +For authentication options and configuration details, refer to [Google authentication](google-authentication/). + +When configuring Google authentication, keep in mind the following additional steps related to Google Cloud Monitoring. + +#### Configure a GCP Service Account + +When you [create a Google Cloud Platform (GCP) Service Account and key file](google-authentication/#create-a-gcp-service-account-and-key-file), the Service Account must have the **Monitoring Viewer** role (**Role > Select a role > Monitoring > Monitoring Viewer**): + +{{< figure src="/static/img/docs/v71/cloudmonitoring_service_account_choose_role.png" max-width="600px" class="docs-image--no-shadow" caption="Choose role" >}} + +#### Grant the GCE Default Service Account scope + +If Grafana is running on a Google Compute Engine (GCE) virtual machine, when you [Configure a GCE Default Service Account](google-authentication/#configure-a-gce-default-service-account), you must also grant that Service Account access to the "Cloud Monitoring API" scope. + +### Enable necessary Google Cloud Platform APIs + +Before you can request data from Google Cloud Monitoring, you must first enable necessary APIs on the Google end. + +1. Open the Monitoring and Cloud Resource Manager API pages: + + - [Monitoring API](https://console.cloud.google.com/apis/library/monitoring.googleapis.com) + - [Cloud Resource Manager API](https://console.cloud.google.com/apis/library/cloudresourcemanager.googleapis.com) + +1. On each page, click the `Enable` button. + + {{< figure src="/static/img/docs/v71/cloudmonitoring_enable_api.png" max-width="450px" class="docs-image--no-shadow" caption="Enable GCP APIs" >}} + ## Configure the data source To configure basic settings for the data source, complete the following steps: @@ -73,41 +109,9 @@ To configure basic settings for the data source, complete the following steps: | **Name** | Sets the name you use to refer to the data source in panels and queries. | | **Default** | Sets whether the data source is pre-selected for new panels. | -### Configure Google authentication - -Before you can request data from Google Cloud Monitoring, you must configure authentication. -All requests to Google APIs are performed on the server-side by the Grafana backend. - -For authentication options and configuration details, refer to [Google authentication](google-authentication/). - -When configuring Google authentication, note these additional Google Cloud Monitoring-specific steps: - -#### Configure a GCP Service Account - -When you [create a Google Cloud Platform (GCP) Service Account and key file](google-authentication/#create-a-gcp-service-account-and-key-file), the Service Account must have the **Monitoring Viewer** role (**Role > Select a role > Monitoring > Monitoring Viewer**): - -{{< figure src="/static/img/docs/v71/cloudmonitoring_service_account_choose_role.png" max-width="600px" class="docs-image--no-shadow" caption="Choose role" >}} - -#### Grant the GCE Default Service Account scope - -If Grafana is running on a Google Compute Engine (GCE) virtual machine, then when you [Configure a GCE Default Service Account](google-authentication/#configure-a-gce-default-service-account), you must also grant that Service Account access to the "Cloud Monitoring API" scope. - -### Enable necessary Google Cloud Platform APIs - -Before you can request data from Google Cloud Monitoring, you must first enable necessary APIs on the Google end. - -1. Open the Monitoring and Cloud Resource Manager API pages: - - - [Monitoring API](https://console.cloud.google.com/apis/library/monitoring.googleapis.com) - - [Cloud Resource Manager API](https://console.cloud.google.com/apis/library/cloudresourcemanager.googleapis.com) - -1. On each page, click the `Enable` button. - - {{< figure src="/static/img/docs/v71/cloudmonitoring_enable_api.png" max-width="450px" class="docs-image--no-shadow" caption="Enable GCP APIs" >}} - ### Provision the data source -You can define and configure the data source in YAML files as part of Grafana's provisioning system. +You can define and configure the data source in YAML files as part of the Grafana provisioning system. For more information about provisioning, and for available configuration options, refer to [Provisioning Grafana](ref:provisioning-data-sources). #### Provisioning examples diff --git a/docs/sources/developers/plugins/plugin.schema.json b/docs/sources/developers/plugins/plugin.schema.json index 91896ef88f0..8319f19c274 100644 --- a/docs/sources/developers/plugins/plugin.schema.json +++ b/docs/sources/developers/plugins/plugin.schema.json @@ -10,12 +10,12 @@ "id": { "type": "string", "description": "Unique name of the plugin. If the plugin is published on grafana.com, then the plugin `id` has to follow the naming conventions.", - "pattern": "^[0-9a-z]+\\-([0-9a-z]+\\-)?(app|panel|datasource|secretsmanager)$" + "pattern": "^[0-9a-z]+\\-([0-9a-z]+\\-)?(app|panel|datasource)$" }, "type": { "type": "string", "description": "Plugin type.", - "enum": ["app", "datasource", "panel", "renderer", "secretsmanager"] + "enum": ["app", "datasource", "panel", "renderer"] }, "info": { "type": "object", @@ -183,11 +183,11 @@ "properties": { "id": { "type": "string", - "pattern": "^[0-9a-z]+\\-([0-9a-z]+\\-)?(app|panel|datasource|secretsmanager)$" + "pattern": "^[0-9a-z]+\\-([0-9a-z]+\\-)?(app|panel|datasource)$" }, "type": { "type": "string", - "enum": ["app", "datasource", "panel", "secretsmanager"] + "enum": ["app", "datasource", "panel"] }, "name": { "type": "string" @@ -280,7 +280,7 @@ }, "type": { "type": "string", - "enum": ["dashboard", "page", "panel", "datasource", "secretsmanager"] + "enum": ["dashboard", "page", "panel", "datasource"] }, "name": { "type": "string" @@ -647,7 +647,7 @@ "id": { "type": "string", "description": "A unique identifier for your exposed component. This is used to reference the component in other plugins. It must be in the following format: '{PLUGIN_ID}/name-of-component/v1'.", - "pattern": "^[0-9a-z]+-([0-9a-z]+-)?(app|panel|datasource|secretsmanager)\\/[a-zA-Z0-9_-]+\\/v[0-9_.-]+$" + "pattern": "^[0-9a-z]+-([0-9a-z]+-)?(app|panel|datasource)\\/[a-zA-Z0-9_-]+\\/v[0-9_.-]+$" }, "title": { "type": "string", @@ -670,7 +670,7 @@ "id": { "type": "string", "description": "A unique identifier for your extension point. This is used to reference the extension point in other plugins. It must be in the following format: '{PLUGIN_ID}/name-of-my-extension-point/v1'.", - "pattern": "^[0-9a-z]+-([0-9a-z]+-)?(app|panel|datasource|secretsmanager)\\/[a-zA-Z0-9_-]+\\/v[0-9_.-]+$" + "pattern": "^[0-9a-z]+-([0-9a-z]+-)?(app|panel|datasource)\\/[a-zA-Z0-9_-]+\\/v[0-9_.-]+$" }, "title": { "type": "string", diff --git a/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md b/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md index 66b0aab612d..786bdcd6bde 100644 --- a/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md +++ b/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md @@ -73,6 +73,7 @@ Most [generally available](https://grafana.com/docs/release-life-cycle/#general- | `newFiltersUI` | Enables new combobox style UI for the Ad hoc filters variable in scenes architecture | Yes | | `alertingQueryAndExpressionsStepMode` | Enables step mode for alerting queries and expressions | Yes | | `useSessionStorageForRedirection` | Use session storage for handling the redirection after login | Yes | +| `pluginsSriChecks` | Enables SRI checks for plugin assets | | | `userStorageAPI` | Enables the user storage API | Yes | | `azureMonitorDisableLogLimit` | Disables the log limit restriction for Azure Monitor when true. The limit is enabled by default. | | | `preinstallAutoUpdate` | Enables automatic updates for pre-installed plugins | Yes | @@ -147,7 +148,6 @@ Experimental features might be changed or removed without prior notice. | `alertStateHistoryLokiOnly` | Disable Grafana alerts from emitting annotations when a remote Loki instance is available. | | `extraThemes` | Enables extra themes | | `lokiPredefinedOperations` | Adds predefined query operations to Loki query editor | -| `frontendSandboxMonitorOnly` | Enables monitor only in the plugin frontend sandbox (if enabled) | | `awsDatasourcesTempCredentials` | Support temporary security credentials in AWS plugins for Grafana Cloud customers | | `mlExpressions` | Enable support for Machine Learning in server-side expressions | | `metricsSummary` | Enables metrics summary queries in the Tempo data source | @@ -203,7 +203,6 @@ Experimental features might be changed or removed without prior notice. | `homeSetupGuide` | Used in Home for users who want to return to the onboarding flow or quickly find popular config pages | | `appSidecar` | Enable the app sidecar feature that allows rendering 2 apps at the same time | | `rolePickerDrawer` | Enables the new role picker drawer design | -| `pluginsSriChecks` | Enables SRI checks for plugin assets | | `unifiedStorageBigObjectsSupport` | Enables to save big objects in blob storage | | `timeRangeProvider` | Enables time pickers sync | | `prometheusUsesCombobox` | Use new combobox component for Prometheus query editor | diff --git a/docs/sources/setup-grafana/configure-security/configure-security-hardening/index.md b/docs/sources/setup-grafana/configure-security/configure-security-hardening/index.md index f5e66394faf..b06fad81904 100644 --- a/docs/sources/setup-grafana/configure-security/configure-security-hardening/index.md +++ b/docs/sources/setup-grafana/configure-security/configure-security-hardening/index.md @@ -118,6 +118,18 @@ Example: hide_version = true ``` +### Enable auth for metrics + +By default, metrics from Grafana itself can be accessed without authentication. This can lead to inadvertent information leakage. + +To enable basic authentication for the metrics endpoint: + +```toml +# If both are set, basic auth will be required for the metrics endpoints +basic_auth_username = +basic_auth_password = +``` + ### Enforce domain verification If set to `true`, the Grafana server redirects requests that have a Host-header value that is mismatched to the actual domain. This might help to mitigate some DNS rebinding attacks. diff --git a/go.mod b/go.mod index 7bc11ba770b..c58a948ebc8 100644 --- a/go.mod +++ b/go.mod @@ -33,8 +33,8 @@ require ( github.com/beevik/etree v1.4.1 // @grafana/grafana-backend-group github.com/benbjohnson/clock v1.3.5 // @grafana/alerting-backend github.com/blang/semver/v4 v4.0.0 // indirect; @grafana/grafana-developer-enablement-squad - github.com/blevesearch/bleve/v2 v2.4.3 // @grafana/grafana-search-and-storage - github.com/blevesearch/bleve_index_api v1.1.12 // @grafana/grafana-search-and-storage + github.com/blevesearch/bleve/v2 v2.4.4-0.20250319135056-b82baf10b205 // @grafana/grafana-search-and-storage + github.com/blevesearch/bleve_index_api v1.2.3 // @grafana/grafana-search-and-storage github.com/blugelabs/bluge v0.2.2 // @grafana/grafana-backend-group github.com/blugelabs/bluge_segment_api v0.2.0 // @grafana/grafana-backend-group github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 // @grafana/grafana-backend-group @@ -124,7 +124,7 @@ require ( github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c //@grafana/identity-access-team github.com/mocktools/go-smtp-mock/v2 v2.3.1 // @grafana/grafana-backend-group github.com/modern-go/reflect2 v1.0.2 // @grafana/alerting-backend - github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // @grafana/alerting-backend + github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect; @grafana/alerting-backend github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f // @grafana/grafana-operator-experience-squad github.com/olekukonko/tablewriter v0.0.5 // @grafana/grafana-backend-group github.com/openfga/api/proto v0.0.0-20250127102726-f9709139a369 // @grafana/identity-access-team @@ -288,21 +288,21 @@ require ( github.com/beorn7/perks v1.0.1 // indirect github.com/bits-and-blooms/bitset v1.12.0 // indirect github.com/blevesearch/geo v0.1.20 // indirect - github.com/blevesearch/go-faiss v1.0.23 // indirect + github.com/blevesearch/go-faiss v1.0.24 // indirect github.com/blevesearch/go-porterstemmer v1.0.3 // indirect github.com/blevesearch/gtreap v0.1.1 // indirect github.com/blevesearch/mmap-go v1.0.4 // indirect - github.com/blevesearch/scorch_segment_api/v2 v2.2.16 // indirect + github.com/blevesearch/scorch_segment_api/v2 v2.3.5 // indirect github.com/blevesearch/segment v0.9.1 // indirect github.com/blevesearch/snowballstem v0.9.0 // indirect github.com/blevesearch/upsidedown_store_api v1.0.2 // indirect - github.com/blevesearch/vellum v1.0.10 // indirect - github.com/blevesearch/zapx/v11 v11.3.10 // indirect - github.com/blevesearch/zapx/v12 v12.3.10 // indirect - github.com/blevesearch/zapx/v13 v13.3.10 // indirect - github.com/blevesearch/zapx/v14 v14.3.10 // indirect - github.com/blevesearch/zapx/v15 v15.3.16 // indirect - github.com/blevesearch/zapx/v16 v16.1.8 // indirect + github.com/blevesearch/vellum v1.1.0 // indirect + github.com/blevesearch/zapx/v11 v11.4.1 // indirect + github.com/blevesearch/zapx/v12 v12.4.1 // indirect + github.com/blevesearch/zapx/v13 v13.4.1 // indirect + github.com/blevesearch/zapx/v14 v14.4.1 // indirect + github.com/blevesearch/zapx/v15 v15.4.1 // indirect + github.com/blevesearch/zapx/v16 v16.2.2-0.20250305220028-89edb0ef9aa9 // indirect github.com/blugelabs/ice v1.0.0 // indirect github.com/blugelabs/ice/v2 v2.0.1 // indirect github.com/bufbuild/protocompile v0.4.0 // indirect @@ -561,6 +561,7 @@ require ( require ( github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.49.0 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.49.0 // indirect + github.com/RoaringBitmap/roaring/v2 v2.4.5 // indirect github.com/bluele/gcache v0.0.2 // indirect github.com/envoyproxy/go-control-plane/envoy v1.32.3 // indirect github.com/open-feature/go-sdk-contrib/providers/ofrep v0.1.5 // indirect diff --git a/go.sum b/go.sum index fafd5bcdac2..4272556ca58 100644 --- a/go.sum +++ b/go.sum @@ -765,6 +765,8 @@ github.com/RoaringBitmap/roaring v0.9.1/go.mod h1:h1B7iIUOmnAeb5ytYMvnHJwxMc6LUr github.com/RoaringBitmap/roaring v0.9.4/go.mod h1:icnadbWcNyfEHlYdr+tDlOTih1Bf/h+rzPpv4sbomAA= github.com/RoaringBitmap/roaring v1.9.3 h1:t4EbC5qQwnisr5PrP9nt0IRhRTb9gMUgQF4t4S2OByM= github.com/RoaringBitmap/roaring v1.9.3/go.mod h1:6AXUsoIEzDTFFQCe1RbGA6uFONMhvejWj5rqITANK90= +github.com/RoaringBitmap/roaring/v2 v2.4.5 h1:uGrrMreGjvAtTBobc0g5IrW1D5ldxDQYe2JW2gggRdg= +github.com/RoaringBitmap/roaring/v2 v2.4.5/go.mod h1:FiJcsfkGje/nZBZgCu0ZxCPOKD/hVXDS2dXi7/eUFE0= github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= github.com/VividCortex/mysqlerr v0.0.0-20170204212430-6c6b55f8796f h1:HR5nRmUQgXrwqZOwZ2DAc/aCi3Bu3xENpspW935vxu0= @@ -898,14 +900,14 @@ github.com/bits-and-blooms/bitset v1.12.0 h1:U/q1fAF7xXRhFCrhROzIfffYnu+dlS38vCZ github.com/bits-and-blooms/bitset v1.12.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= 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/blevesearch/bleve/v2 v2.4.3 h1:XDYj+1prgX84L2Cf+V3ojrOPqXxy0qxyd2uLMmeuD+4= -github.com/blevesearch/bleve/v2 v2.4.3/go.mod h1:hEPDPrbYw3vyrm5VOa36GyS4bHWuIf4Fflp7460QQXY= -github.com/blevesearch/bleve_index_api v1.1.12 h1:P4bw9/G/5rulOF7SJ9l4FsDoo7UFJ+5kexNy1RXfegY= -github.com/blevesearch/bleve_index_api v1.1.12/go.mod h1:PbcwjIcRmjhGbkS/lJCpfgVSMROV6TRubGGAODaK1W8= +github.com/blevesearch/bleve/v2 v2.4.4-0.20250319135056-b82baf10b205 h1:u6DQJ1k4FKwRNtsrVhIRQenNdtz31way7/LgWCluFzA= +github.com/blevesearch/bleve/v2 v2.4.4-0.20250319135056-b82baf10b205/go.mod h1:nSmFOQ7M264rKoM3jf63Gl2G+ylCgZGovPgL6ZEQYzU= +github.com/blevesearch/bleve_index_api v1.2.3 h1:aJJPhTb30zc68qg2HbvCZLAF1Eh5Hhm296mDAUFaUeg= +github.com/blevesearch/bleve_index_api v1.2.3/go.mod h1:rKQDl4u51uwafZxFrPD1R7xFOwKnzZW7s/LSeK4lgo0= github.com/blevesearch/geo v0.1.20 h1:paaSpu2Ewh/tn5DKn/FB5SzvH0EWupxHEIwbCk/QPqM= github.com/blevesearch/geo v0.1.20/go.mod h1:DVG2QjwHNMFmjo+ZgzrIq2sfCh6rIHzy9d9d0B59I6w= -github.com/blevesearch/go-faiss v1.0.23 h1:Wmc5AFwDLKGl2L6mjLX1Da3vCL0EKa2uHHSorcIS1Uc= -github.com/blevesearch/go-faiss v1.0.23/go.mod h1:OMGQwOaRRYxrmeNdMrXJPvVx8gBnvE5RYrr0BahNnkk= +github.com/blevesearch/go-faiss v1.0.24 h1:K79IvKjoKHdi7FdiXEsAhxpMuns0x4fM0BO93bW5jLI= +github.com/blevesearch/go-faiss v1.0.24/go.mod h1:OMGQwOaRRYxrmeNdMrXJPvVx8gBnvE5RYrr0BahNnkk= github.com/blevesearch/go-porterstemmer v1.0.3 h1:GtmsqID0aZdCSNiY8SkuPJ12pD4jI+DdXTAn4YRcHCo= github.com/blevesearch/go-porterstemmer v1.0.3/go.mod h1:angGc5Ht+k2xhJdZi511LtmxuEf0OVpvUUNrwmM1P7M= github.com/blevesearch/gtreap v0.1.1 h1:2JWigFrzDMR+42WGIN/V2p0cUvn4UP3C4Q5nmaZGW8Y= @@ -914,8 +916,8 @@ github.com/blevesearch/mmap-go v1.0.2/go.mod h1:ol2qBqYaOUsGdm7aRMRrYGgPvnwLe6Y+ github.com/blevesearch/mmap-go v1.0.3/go.mod h1:pYvKl/grLQrBxuaRYgoTssa4rVujYYeenDp++2E+yvs= github.com/blevesearch/mmap-go v1.0.4 h1:OVhDhT5B/M1HNPpYPBKIEJaD0F3Si+CrEKULGCDPWmc= github.com/blevesearch/mmap-go v1.0.4/go.mod h1:EWmEAOmdAS9z/pi/+Toxu99DnsbhG1TIxUoRmJw/pSs= -github.com/blevesearch/scorch_segment_api/v2 v2.2.16 h1:uGvKVvG7zvSxCwcm4/ehBa9cCEuZVE+/zvrSl57QUVY= -github.com/blevesearch/scorch_segment_api/v2 v2.2.16/go.mod h1:VF5oHVbIFTu+znY1v30GjSpT5+9YFs9dV2hjvuh34F0= +github.com/blevesearch/scorch_segment_api/v2 v2.3.5 h1:t6NVTdS8xp1xHMJoe5WjxdZWLKhsIIb6kB0U49rhAno= +github.com/blevesearch/scorch_segment_api/v2 v2.3.5/go.mod h1:7gG1vsL3lu2L+7RuEtZ7PJhxGYE+AV9zjxTSHuXFMdM= github.com/blevesearch/segment v0.9.0/go.mod h1:9PfHYUdQCgHktBgvtUOF4x+pc4/l8rdH0u5spnW85UQ= github.com/blevesearch/segment v0.9.1 h1:+dThDy+Lvgj5JMxhmOVlgFfkUtZV2kw49xax4+jTfSU= github.com/blevesearch/segment v0.9.1/go.mod h1:zN21iLm7+GnBHWTao9I+Au/7MBiL8pPFtJBJTsk6kQw= @@ -925,20 +927,20 @@ github.com/blevesearch/upsidedown_store_api v1.0.2 h1:U53Q6YoWEARVLd1OYNc9kvhBMG github.com/blevesearch/upsidedown_store_api v1.0.2/go.mod h1:M01mh3Gpfy56Ps/UXHjEO/knbqyQ1Oamg8If49gRwrQ= github.com/blevesearch/vellum v1.0.5/go.mod h1:atE0EH3fvk43zzS7t1YNdNC7DbmcC3uz+eMD5xZ2OyQ= github.com/blevesearch/vellum v1.0.7/go.mod h1:doBZpmRhwTsASB4QdUZANlJvqVAUdUyX0ZK7QJCTeBE= -github.com/blevesearch/vellum v1.0.10 h1:HGPJDT2bTva12hrHepVT3rOyIKFFF4t7Gf6yMxyMIPI= -github.com/blevesearch/vellum v1.0.10/go.mod h1:ul1oT0FhSMDIExNjIxHqJoGpVrBpKCdgDQNxfqgJt7k= -github.com/blevesearch/zapx/v11 v11.3.10 h1:hvjgj9tZ9DeIqBCxKhi70TtSZYMdcFn7gDb71Xo/fvk= -github.com/blevesearch/zapx/v11 v11.3.10/go.mod h1:0+gW+FaE48fNxoVtMY5ugtNHHof/PxCqh7CnhYdnMzQ= -github.com/blevesearch/zapx/v12 v12.3.10 h1:yHfj3vXLSYmmsBleJFROXuO08mS3L1qDCdDK81jDl8s= -github.com/blevesearch/zapx/v12 v12.3.10/go.mod h1:0yeZg6JhaGxITlsS5co73aqPtM04+ycnI6D1v0mhbCs= -github.com/blevesearch/zapx/v13 v13.3.10 h1:0KY9tuxg06rXxOZHg3DwPJBjniSlqEgVpxIqMGahDE8= -github.com/blevesearch/zapx/v13 v13.3.10/go.mod h1:w2wjSDQ/WBVeEIvP0fvMJZAzDwqwIEzVPnCPrz93yAk= -github.com/blevesearch/zapx/v14 v14.3.10 h1:SG6xlsL+W6YjhX5N3aEiL/2tcWh3DO75Bnz77pSwwKU= -github.com/blevesearch/zapx/v14 v14.3.10/go.mod h1:qqyuR0u230jN1yMmE4FIAuCxmahRQEOehF78m6oTgns= -github.com/blevesearch/zapx/v15 v15.3.16 h1:Ct3rv7FUJPfPk99TI/OofdC+Kpb4IdyfdMH48sb+FmE= -github.com/blevesearch/zapx/v15 v15.3.16/go.mod h1:Turk/TNRKj9es7ZpKK95PS7f6D44Y7fAFy8F4LXQtGg= -github.com/blevesearch/zapx/v16 v16.1.8 h1:Bxzpw6YQpFs7UjoCV1+RvDw6fmAT2GZxldwX8b3wVBM= -github.com/blevesearch/zapx/v16 v16.1.8/go.mod h1:JqQlOqlRVaYDkpLIl3JnKql8u4zKTNlVEa3nLsi0Gn8= +github.com/blevesearch/vellum v1.1.0 h1:CinkGyIsgVlYf8Y2LUQHvdelgXr6PYuvoDIajq6yR9w= +github.com/blevesearch/vellum v1.1.0/go.mod h1:QgwWryE8ThtNPxtgWJof5ndPfx0/YMBh+W2weHKPw8Y= +github.com/blevesearch/zapx/v11 v11.4.1 h1:qFCPlFbsEdwbbckJkysptSQOsHn4s6ZOHL5GMAIAVHA= +github.com/blevesearch/zapx/v11 v11.4.1/go.mod h1:qNOGxIqdPC1MXauJCD9HBG487PxviTUUbmChFOAosGs= +github.com/blevesearch/zapx/v12 v12.4.1 h1:K77bhypII60a4v8mwvav7r4IxWA8qxhNjgF9xGdb9eQ= +github.com/blevesearch/zapx/v12 v12.4.1/go.mod h1:QRPrlPOzAxBNMI0MkgdD+xsTqx65zbuPr3Ko4Re49II= +github.com/blevesearch/zapx/v13 v13.4.1 h1:EnkEMZFUK0lsW/jOJJF2xOcp+W8TjEsyeN5BeAZEYYE= +github.com/blevesearch/zapx/v13 v13.4.1/go.mod h1:e6duBMlCvgbH9rkzNMnUa9hRI9F7ri2BRcHfphcmGn8= +github.com/blevesearch/zapx/v14 v14.4.1 h1:G47kGCshknBZzZAtjcnIAMn3oNx8XBLxp8DMq18ogyE= +github.com/blevesearch/zapx/v14 v14.4.1/go.mod h1:O7sDxiaL2r2PnCXbhh1Bvm7b4sP+jp4unE9DDPWGoms= +github.com/blevesearch/zapx/v15 v15.4.1 h1:B5IoTMUCEzFdc9FSQbhVOxAY+BO17c05866fNruiI7g= +github.com/blevesearch/zapx/v15 v15.4.1/go.mod h1:b/MreHjYeQoLjyY2+UaM0hGZZUajEbE0xhnr1A2/Q6Y= +github.com/blevesearch/zapx/v16 v16.2.2-0.20250305220028-89edb0ef9aa9 h1:a5GLOQkJqCnpD/lTfNiEVQnJ1e2CVOR8JFO6u1D7bLE= +github.com/blevesearch/zapx/v16 v16.2.2-0.20250305220028-89edb0ef9aa9/go.mod h1:v8B9VjBtELr2qRO0f3RoRG8RAZg3Jml/5SYuBT0hvGw= github.com/bluele/gcache v0.0.2 h1:WcbfdXICg7G/DGBh1PFfcirkWOQV+v077yF1pSy3DGw= github.com/bluele/gcache v0.0.2/go.mod h1:m15KV+ECjptwSPxKhOhQoAFQVtUFjTVkc3H8o0t/fp0= github.com/blugelabs/bluge v0.2.2 h1:gat8CqE6P6tOgeX30XGLOVNTC26cpM2RWVcreXWtYcM= @@ -3429,6 +3431,7 @@ gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= diff --git a/go.work b/go.work index dbda98bd6dd..905df9f4e80 100644 --- a/go.work +++ b/go.work @@ -12,6 +12,7 @@ use ( ./.citools/golangci-lint ./.citools/jb ./.citools/lefthook + ./.citools/swagger ./apps/advisor ./apps/alerting/notifications ./apps/dashboard @@ -41,3 +42,7 @@ replace github.com/getkin/kin-openapi => github.com/getkin/kin-openapi v0.126.0 replace github.com/prometheus/alertmanager => github.com/grafana/prometheus-alertmanager v0.25.1-0.20240930132144-b5e64e81e8d3 replace github.com/crewjam/saml => github.com/grafana/saml v0.4.15-0.20240917091248-ae3bbdad8a56 + +// Can be removed once Bleve releases v2.5.0. +// We've pinned this for: https://github.com/blevesearch/bleve/commit/af9e3111dadfedf9d30f0448506b4a57fecc8550 +replace github.com/blevesearch/bleve/v2 => github.com/blevesearch/bleve/v2 v2.4.4-0.20250319135056-b82baf10b205 diff --git a/go.work.sum b/go.work.sum index dff972bcfb7..fb21639617c 100644 --- a/go.work.sum +++ b/go.work.sum @@ -535,7 +535,6 @@ github.com/DataDog/sketches-go v1.4.6 h1:acd5fb+QdUzGrosfNLwrIhqyrbMORpvBy7mE+vH github.com/DataDog/sketches-go v1.4.6/go.mod h1:7Y8GN8Jf66DLyDhc94zuWA3uHEt/7ttt8jHOBWWrSOg= github.com/DmitriyVTitov/size v1.5.0 h1:/PzqxYrOyOUX1BXj6J9OuVRVGe+66VL4D9FlUaW515g= github.com/DmitriyVTitov/size v1.5.0/go.mod h1:le6rNI4CoLQV1b9gzp1+3d7hMAD/uu2QcJ+aYbNgiU0= -github.com/FZambia/eagle v0.2.0/go.mod h1:LKMYBwGYhao5sJI0TppvQ4SvvldFj9gITxrl8NvGwG0= github.com/GoogleCloudPlatform/cloudsql-proxy v1.36.0 h1:kAtNAWwvTt5+iew6baV0kbOrtjYTXPtWNSyOFlcxkBU= github.com/GoogleCloudPlatform/cloudsql-proxy v1.36.0/go.mod h1:VRKXU8C7Y/aUKjRBTGfw0Ndv4YqNxlB8zAPJJDxbASE= github.com/GoogleCloudPlatform/grpc-gcp-go/grpcgcp v1.5.0/go.mod h1:dppbR7CwXD4pgtV9t3wD1812RaLDcBjtblcDF5f1vI0= @@ -633,14 +632,26 @@ github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQ github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932 h1:mXoPYz/Ul5HYEDvkta6I8/rnYM5gSdSV2tJ6XbZuEtY= github.com/bitly/go-simplejson v0.5.0 h1:6IH+V8/tVMab511d5bn4M7EwGXZf9Hj6i2xSwkNEM+Y= github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= +github.com/blevesearch/bleve_index_api v1.1.12/go.mod h1:PbcwjIcRmjhGbkS/lJCpfgVSMROV6TRubGGAODaK1W8= +github.com/blevesearch/bleve_index_api v1.2.1/go.mod h1:rKQDl4u51uwafZxFrPD1R7xFOwKnzZW7s/LSeK4lgo0= +github.com/blevesearch/go-faiss v1.0.23/go.mod h1:OMGQwOaRRYxrmeNdMrXJPvVx8gBnvE5RYrr0BahNnkk= github.com/blevesearch/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:kDy+zgJFJJoJYBvdfBSiZYBbdsUL0XcjHYWezpQBGPA= github.com/blevesearch/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:9eJDeqxJ3E7WnLebQUlPD7ZjSce7AnDb9vjGmMCbD0A= github.com/blevesearch/goleveldb v1.0.1 h1:iAtV2Cu5s0GD1lwUiekkFHe2gTMCCNVj2foPclDLIFI= github.com/blevesearch/goleveldb v1.0.1/go.mod h1:WrU8ltZbIp0wAoig/MHbrPCXSOLpe79nz5lv5nqfYrQ= +github.com/blevesearch/scorch_segment_api/v2 v2.2.16/go.mod h1:VF5oHVbIFTu+znY1v30GjSpT5+9YFs9dV2hjvuh34F0= +github.com/blevesearch/scorch_segment_api/v2 v2.3.3/go.mod h1:LXidEjeenMdbcLKP/UdZi1HJOny61FbhslAh5SgN5Ik= github.com/blevesearch/snowball v0.6.1 h1:cDYjn/NCH+wwt2UdehaLpr2e4BwLIjN4V/TdLsL+B5A= github.com/blevesearch/snowball v0.6.1/go.mod h1:ZF0IBg5vgpeoUhnMza2v0A/z8m1cWPlwhke08LpNusg= github.com/blevesearch/stempel v0.2.0 h1:CYzVPaScODMvgE9o+kf6D4RJ/VRomyi9uHF+PtB+Afc= github.com/blevesearch/stempel v0.2.0/go.mod h1:wjeTHqQv+nQdbPuJ/YcvOjTInA2EIc6Ks1FoSUzSLvc= +github.com/blevesearch/vellum v1.0.10/go.mod h1:ul1oT0FhSMDIExNjIxHqJoGpVrBpKCdgDQNxfqgJt7k= +github.com/blevesearch/zapx/v11 v11.3.10/go.mod h1:0+gW+FaE48fNxoVtMY5ugtNHHof/PxCqh7CnhYdnMzQ= +github.com/blevesearch/zapx/v12 v12.3.10/go.mod h1:0yeZg6JhaGxITlsS5co73aqPtM04+ycnI6D1v0mhbCs= +github.com/blevesearch/zapx/v13 v13.3.10/go.mod h1:w2wjSDQ/WBVeEIvP0fvMJZAzDwqwIEzVPnCPrz93yAk= +github.com/blevesearch/zapx/v14 v14.3.10/go.mod h1:qqyuR0u230jN1yMmE4FIAuCxmahRQEOehF78m6oTgns= +github.com/blevesearch/zapx/v15 v15.3.16/go.mod h1:Turk/TNRKj9es7ZpKK95PS7f6D44Y7fAFy8F4LXQtGg= +github.com/blevesearch/zapx/v16 v16.1.8/go.mod h1:JqQlOqlRVaYDkpLIl3JnKql8u4zKTNlVEa3nLsi0Gn8= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY= github.com/boombuler/barcode v1.0.1 h1:NDBbPmhS+EqABEs5Kg3n/5ZNjy73Pz7SIV+KCeqyXcs= github.com/bradleyjkemp/cupaloy/v2 v2.6.0 h1:knToPYa2xtfg42U3I6punFEjaGFKWQRXJwj0JTv4mTs= @@ -660,8 +671,6 @@ github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7N github.com/bytedance/sonic v1.10.0-rc3 h1:uNSnscRapXTwUgTyOF0GVljYD08p9X/Lbr9MweSV3V0= github.com/bytedance/sonic v1.10.0-rc3/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4= github.com/campoy/embedmd v1.0.0 h1:V4kI2qTJJLf4J29RzI/MAt2c3Bl4dQSYPuflzwFH2hY= -github.com/centrifugal/centrifuge v0.34.4/go.mod h1:VliWwN01/Iom9tLFJyk4E5FdKcn3cbJhnlt0XTnlolo= -github.com/centrifugal/protocol v0.16.0/go.mod h1:7V5vI30VcoxJe4UD87xi7bOsvI0bmEhvbQuMjrFM2L4= github.com/checkpoint-restore/go-criu/v4 v4.1.0 h1:WW2B2uxx9KWF6bGlHqhm8Okiafwwx7Y2kcpn8lCpjgo= github.com/checkpoint-restore/go-criu/v5 v5.0.0 h1:TW8f/UvntYoVDMN1K2HlT82qH1rb0sOjpGw3m6Ym+i4= github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0= @@ -745,6 +754,7 @@ github.com/couchbase/moss v0.2.0 h1:VCYrMzFwEryyhRSeI+/b3tRBSeTpi/8gn5Kf6dxqn+o= github.com/couchbase/moss v0.2.0/go.mod h1:9MaHIaRuy9pvLPUJxB8sh8OrLfyDczECVL37grCIubs= github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk= github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creasty/defaults v1.8.0 h1:z27FJxCAa0JKt3utc0sCImAEb+spPucmKoOdLHvHYKk= github.com/creasty/defaults v1.8.0/go.mod h1:iGzKe6pbEHnpMPtfDXZEr0NVxWnPTjb1bbDy08fPzYM= github.com/crewjam/httperr v0.2.0 h1:b2BfXR8U3AlIHwNeFFvZ+BV1LFvKLlzMjzaTnZMybNo= @@ -1052,6 +1062,7 @@ github.com/jon-whit/go-grpc-prometheus v1.4.0 h1:/wmpGDJcLXuEjXryWhVYEGt9YBRhtLw github.com/jon-whit/go-grpc-prometheus v1.4.0/go.mod h1:iTPm+Iuhh3IIqR0iGZ91JJEg5ax6YQEe1I0f6vtBuao= github.com/joncrlsn/dque v0.0.0-20211108142734-c2ef48c5192a h1:sfe532Ipn7GX0V6mHdynBk393rDmqgI0QmjLK7ct7TU= github.com/joncrlsn/dque v0.0.0-20211108142734-c2ef48c5192a/go.mod h1:dNKs71rs2VJGBAmttu7fouEsRQlRjxy0p1Sx+T5wbpY= +github.com/json-iterator/go v0.0.0-20171115153421-f7279a603ede/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/jstemmer/go-junit-report v0.9.1 h1:6QPYqodiu3GuPL+7mfx+NwDdp2eTkp9IfEUpgAwUN0o= github.com/jsternberg/zap-logfmt v1.2.0 h1:1v+PK4/B48cy8cfQbxL4FmmNZrjnIMr2BsnyEmXqv2o= github.com/jsternberg/zap-logfmt v1.2.0/go.mod h1:kz+1CUmCutPWABnNkOu9hOHKdT2q3TDYCcsFy9hpqb0= @@ -1120,7 +1131,6 @@ github.com/matryer/moq v0.3.3/go.mod h1:RJ75ZZZD71hejp39j4crZLsEDszGk6iH4v4YsWFK github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-shellwords v1.0.3 h1:K/VxK7SZ+cvuPgFSLKi5QPI9Vr/ipOf4C1gN+ntueUk= github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg= -github.com/maypok86/otter v1.2.4/go.mod h1:mKLfoI7v1HOmQMwFgX4QkRk23mX6ge3RDvjdHOWG4R4= github.com/mfridman/xflag v0.1.0 h1:TWZrZwG1QklFX5S4j1vxfF1sZbZeZSGofMwPMLAF29M= github.com/mfridman/xflag v0.1.0/go.mod h1:/483ywM5ZO5SuMVjrIGquYNE5CzLrj5Ux/LxWWnjRaE= github.com/mgechev/dots v0.0.0-20210922191527-e955255bf517 h1:zpIH83+oKzcpryru8ceC6BxnoG8TBrhgAvRg8obzup0= @@ -1271,7 +1281,6 @@ github.com/rabbitmq/amqp091-go v1.9.0 h1:qrQtyzB4H8BQgEuJwhmVQqVHB9O4+MNDJCCAcpc github.com/rabbitmq/amqp091-go v1.9.0/go.mod h1:+jPrT9iY2eLjRaMSRHUhc3z14E/l85kv/f+6luSD3pc= github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM= github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= -github.com/redis/rueidis v1.0.53/go.mod h1:by+34b0cFXndxtYmPAHpoTHO5NkosDlBvhexoTURIxM= github.com/relvacode/iso8601 v1.4.0 h1:GsInVSEJfkYuirYFxa80nMLbH2aydgZpIf52gYZXUJs= github.com/relvacode/iso8601 v1.4.0/go.mod h1:FlNp+jz+TXpyRqgmM7tnzHHzBnz776kmAH2h3sZCn0I= github.com/richardartoul/molecule v1.0.0 h1:+LFA9cT7fn8KF39zy4dhOnwcOwRoqKiBkPqKqya+8+U= @@ -1293,10 +1302,8 @@ github.com/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtm github.com/schollz/progressbar/v3 v3.14.6 h1:GyjwcWBAf+GFDMLziwerKvpuS7ZF+mNTAXIB2aspiZs= github.com/schollz/progressbar/v3 v3.14.6/go.mod h1:Nrzpuw3Nl0srLY0VlTvC4V6RL50pcEymjy6qyJAaLa0= github.com/seccomp/libseccomp-golang v0.9.1 h1:NJjM5DNFOs0s3kYE1WUOr6G8V97sdt46rlXTMfXGWBo= -github.com/segmentio/encoding v0.4.1/go.mod h1:/d03Cd8PoaDeceuhUUUQWjU0KhWjrmYrWPgtJHYZSnI= github.com/segmentio/fasthash v1.0.3 h1:EI9+KE1EwvMLBWwjpRDc+fEM+prwxDYbslddQGtrmhM= github.com/segmentio/fasthash v1.0.3/go.mod h1:waKX8l2N8yckOgmSsXJi7x1ZfdKZ4x7KRMzBtS3oedY= -github.com/shadowspore/fossil-delta v0.0.0-20241213113458-1d797d70cbe3/go.mod h1:aJIMhRsunltJR926EB2MUg8qHemFQDreSB33pyto2Ps= github.com/shirou/gopsutil/v3 v3.24.4 h1:dEHgzZXt4LMNm+oYELpzl9YCqV65Yr/6SfrvgRBtXeU= github.com/shirou/gopsutil/v3 v3.24.4/go.mod h1:lTd2mdiOspcqLgAnr9/nGi71NkeMpWKdmhuxm9GusH8= github.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI= @@ -1311,6 +1318,7 @@ github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5I github.com/spf13/afero v1.10.0/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g= +github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad h1:fiWzISvDn0Csy5H0iwgAuJGQTUpVfEMJJd4nRFXogbc= github.com/stefanberger/go-pkcs11uri v0.0.0-20201008174630-78d3cae3a980 h1:lIOOHPEbXzO3vnmx2gok1Tfs31Q8GQqKLc8vVqyQq/I= github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= @@ -1409,6 +1417,7 @@ github.com/zenazn/goji v1.0.1/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxt gitlab.com/nyarla/go-crypt v0.0.0-20160106005555-d9a5dc2b789b h1:7gd+rd8P3bqcn/96gOZa3F5dpJr/vEiDQYlNb/y2uNs= go.einride.tech/aip v0.68.0 h1:4seM66oLzTpz50u4K1zlJyOXQ3tCzcJN7I22tKkjipw= go.einride.tech/aip v0.68.0/go.mod h1:7y9FF8VtPWqpxuAxl0KQWqaULxW4zFIesD6zF5RIHHg= +go.etcd.io/bbolt v1.3.7/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw= go.etcd.io/etcd v0.5.0-alpha.5.0.20200910180754-dd1b699fc489 h1:1JFLBqwIgdyHN1ZtgjTBwO+blA6gVOmZurpiMEsETKo= go.etcd.io/etcd/api/v3 v3.5.12/go.mod h1:Ot+o0SWSyT6uHhA56al1oCED0JImsRiU9Dc26+C2a+4= go.etcd.io/etcd/client/pkg/v3 v3.5.12/go.mod h1:seTzl2d9APP8R5Y2hFL3NVlD6qC/dOT+3kvrqPyTas4= diff --git a/package.json b/package.json index 726f78741af..358559cd0e8 100644 --- a/package.json +++ b/package.json @@ -277,8 +277,8 @@ "@grafana/prometheus": "workspace:*", "@grafana/runtime": "workspace:*", "@grafana/saga-icons": "workspace:*", - "@grafana/scenes": "6.3.1", - "@grafana/scenes-react": "6.3.1", + "@grafana/scenes": "6.5.2", + "@grafana/scenes-react": "6.5.2", "@grafana/schema": "workspace:*", "@grafana/sql": "workspace:*", "@grafana/ui": "workspace:*", diff --git a/packages/grafana-data/src/types/config.ts b/packages/grafana-data/src/types/config.ts index bc679cd8a0d..c982e0fe25c 100644 --- a/packages/grafana-data/src/types/config.ts +++ b/packages/grafana-data/src/types/config.ts @@ -215,7 +215,6 @@ export interface GrafanaConfig { unifiedAlerting: UnifiedAlertingConfig; angularSupportEnabled: boolean; feedbackLinksEnabled: boolean; - secretsManagerPluginEnabled: boolean; supportBundlesEnabled: boolean; secureSocksDSProxyEnabled: boolean; googleAnalyticsId: string | undefined; diff --git a/packages/grafana-data/src/types/featureToggles.gen.ts b/packages/grafana-data/src/types/featureToggles.gen.ts index ebda65fd2d7..43e3ccea8ef 100644 --- a/packages/grafana-data/src/types/featureToggles.gen.ts +++ b/packages/grafana-data/src/types/featureToggles.gen.ts @@ -826,6 +826,7 @@ export interface FeatureToggles { managedDualWriter?: boolean; /** * Enables SRI checks for plugin assets + * @default false */ pluginsSriChecks?: boolean; /** diff --git a/packages/grafana-data/src/types/plugin.ts b/packages/grafana-data/src/types/plugin.ts index 4ea27114a24..f4d18780b5b 100644 --- a/packages/grafana-data/src/types/plugin.ts +++ b/packages/grafana-data/src/types/plugin.ts @@ -17,7 +17,6 @@ export enum PluginType { datasource = 'datasource', app = 'app', renderer = 'renderer', - secretsmanager = 'secretsmanager', } /** Describes status of {@link https://grafana.com/docs/grafana/latest/plugins/plugin-signatures/ | plugin signature} */ diff --git a/packages/grafana-data/src/utils/valueMappings.test.ts b/packages/grafana-data/src/utils/valueMappings.test.ts index 2b60616d850..90686b52895 100644 --- a/packages/grafana-data/src/utils/valueMappings.test.ts +++ b/packages/grafana-data/src/utils/valueMappings.test.ts @@ -185,6 +185,40 @@ describe('Format value with value mappings', () => { }); }); +describe('Range mapping with null From or null To', () => { + expect( + getValueMappingResult( + [ + { + type: MappingType.RangeToText, + options: { + from: 0, + to: null, + result: { text: 'pos' }, + }, + }, + ], + 100 + ) + ).toEqual({ text: 'pos' }); + + expect( + getValueMappingResult( + [ + { + type: MappingType.RangeToText, + options: { + from: null, + to: 0, + result: { text: 'neg' }, + }, + }, + ], + -100 + ) + ).toEqual({ text: 'neg' }); +}); + describe('Format value with regex mappings', () => { it('should return correct regular expression result', () => { const value = 'www.foo.com'; diff --git a/packages/grafana-data/src/utils/valueMappings.ts b/packages/grafana-data/src/utils/valueMappings.ts index d7ab2cf6a24..910cf55511f 100644 --- a/packages/grafana-data/src/utils/valueMappings.ts +++ b/packages/grafana-data/src/utils/valueMappings.ts @@ -35,13 +35,17 @@ export function getValueMappingResult(valueMappings: ValueMapping[], value: any) continue; } - const isNumFrom = !isNaN(vm.options.from!); - if (isNumFrom && valueAsNumber < vm.options.from!) { + const from = vm.options.from ?? -Infinity; + + const isNumFrom = !isNaN(from); + if (isNumFrom && valueAsNumber < from) { continue; } - const isNumTo = !isNaN(vm.options.to!); - if (isNumTo && valueAsNumber > vm.options.to!) { + const to = vm.options.to ?? Infinity; + + const isNumTo = !isNaN(to); + if (isNumTo && valueAsNumber > to) { continue; } diff --git a/packages/grafana-runtime/src/config.ts b/packages/grafana-runtime/src/config.ts index 0329e86548a..9ab13022f2e 100644 --- a/packages/grafana-runtime/src/config.ts +++ b/packages/grafana-runtime/src/config.ts @@ -120,7 +120,6 @@ export class GrafanaBootConfig implements GrafanaConfig { rendererDefaultImageWidth = 1000; rendererDefaultImageHeight = 500; rendererDefaultImageScale = 1; - secretsManagerPluginEnabled = false; supportBundlesEnabled = false; http2Enabled = false; dateFormats?: SystemDateFormatSettings; diff --git a/packages/grafana-ui/src/components/Collapse/CollapsableSection.tsx b/packages/grafana-ui/src/components/Collapse/CollapsableSection.tsx index 34e0f95db20..caab6779ff1 100644 --- a/packages/grafana-ui/src/components/Collapse/CollapsableSection.tsx +++ b/packages/grafana-ui/src/components/Collapse/CollapsableSection.tsx @@ -126,5 +126,7 @@ const collapsableSectionStyles = (theme: GrafanaTheme2) => ({ }), label: css({ display: 'flex', + fontWeight: theme.typography.fontWeightMedium, + color: theme.colors.text.maxContrast, }), }); diff --git a/pkg/api/admin_encryption.go b/pkg/api/admin_encryption.go index e7bb65070f2..74a04357042 100644 --- a/pkg/api/admin_encryption.go +++ b/pkg/api/admin_encryption.go @@ -6,7 +6,6 @@ import ( "github.com/grafana/grafana/pkg/api/response" contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model" - skv "github.com/grafana/grafana/pkg/services/secrets/kvstore" ) func (hs *HTTPServer) AdminRotateDataEncryptionKeys(c *contextmodel.ReqContext) response.Response { @@ -50,51 +49,3 @@ func (hs *HTTPServer) AdminRollbackSecrets(c *contextmodel.ReqContext) response. return response.Respond(http.StatusOK, "Secrets rolled back successfully") } - -// To migrate to the plugin, it must be installed and configured -// so as not to lose access to migrated secrets -func (hs *HTTPServer) AdminMigrateSecretsToPlugin(c *contextmodel.ReqContext) response.Response { - if skv.EvaluateRemoteSecretsPlugin(c.Req.Context(), hs.secretsPluginManager, hs.Cfg) != nil { - hs.log.Warn("Received secrets plugin migration request while plugin is not available") - return response.Respond(http.StatusBadRequest, "Secrets plugin is not available") - } - err := hs.secretsPluginMigrator.TriggerPluginMigration(c.Req.Context(), true) - if err != nil { - hs.log.Error("Failed to trigger secret migration to plugin", "error", err.Error()) - return response.Respond(http.StatusInternalServerError, "Secret migration to plugin failed") - } - return response.Respond(http.StatusOK, "Secret migration to plugin triggered successfully") -} - -// To migrate from the plugin, it must be installed only -// as it is possible the user disabled it and then wants to migrate -func (hs *HTTPServer) AdminMigrateSecretsFromPlugin(c *contextmodel.ReqContext) response.Response { - if hs.secretsPluginManager.SecretsManager(c.Req.Context()) == nil { - hs.log.Warn("Received secrets plugin migration request while plugin is not installed") - return response.Respond(http.StatusBadRequest, "Secrets plugin is not installed") - } - err := hs.secretsPluginMigrator.TriggerPluginMigration(c.Req.Context(), false) - if err != nil { - hs.log.Error("Failed to trigger secret migration from plugin", "error", err.Error()) - return response.Respond(http.StatusInternalServerError, "Secret migration from plugin failed") - } - return response.Respond(http.StatusOK, "Secret migration from plugin triggered successfully") -} - -func (hs *HTTPServer) AdminDeleteAllSecretsManagerPluginSecrets(c *contextmodel.ReqContext) response.Response { - if hs.secretsPluginManager.SecretsManager(c.Req.Context()) == nil { - hs.log.Warn("Received secrets plugin deletion request while plugin is not installed") - return response.Respond(http.StatusBadRequest, "Secrets plugin is not installed") - } - items, err := hs.secretsStore.GetAll(c.Req.Context()) - if err != nil { - return response.Respond(http.StatusInternalServerError, "an error occurred while retrieving secrets") - } - for _, item := range items { - err := hs.secretsStore.Del(c.Req.Context(), *item.OrgId, *item.Namespace, *item.Type) - if err != nil { - return response.Respond(http.StatusInternalServerError, fmt.Sprintf("error deleting key with org=%v namespace=%v type=%v. error=%v", *item.OrgId, *item.Namespace, *item.Type, err.Error())) - } - } - return response.Respond(http.StatusOK, fmt.Sprintf("All %d Secrets Manager plugin secrets deleted", len(items))) -} diff --git a/pkg/api/api.go b/pkg/api/api.go index bb4aa87dc5d..77efde07038 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -573,9 +573,6 @@ func (hs *HTTPServer) registerRoutes() { adminRoute.Post("/encryption/reencrypt-data-keys", reqGrafanaAdmin, routing.Wrap(hs.AdminReEncryptEncryptionKeys)) adminRoute.Post("/encryption/reencrypt-secrets", reqGrafanaAdmin, routing.Wrap(hs.AdminReEncryptSecrets)) adminRoute.Post("/encryption/rollback-secrets", reqGrafanaAdmin, routing.Wrap(hs.AdminRollbackSecrets)) - adminRoute.Post("/encryption/migrate-secrets/to-plugin", reqGrafanaAdmin, routing.Wrap(hs.AdminMigrateSecretsToPlugin)) - adminRoute.Post("/encryption/migrate-secrets/from-plugin", reqGrafanaAdmin, routing.Wrap(hs.AdminMigrateSecretsFromPlugin)) - adminRoute.Post("/encryption/delete-secretsmanagerplugin-secrets", reqGrafanaAdmin, routing.Wrap(hs.AdminDeleteAllSecretsManagerPluginSecrets)) adminRoute.Post("/provisioning/dashboards/reload", authorize(ac.EvalPermission(ActionProvisioningReload, ScopeProvisionersDashboards)), routing.Wrap(hs.AdminProvisioningReloadDashboards)) adminRoute.Post("/provisioning/plugins/reload", authorize(ac.EvalPermission(ActionProvisioningReload, ScopeProvisionersPlugins)), routing.Wrap(hs.AdminProvisioningReloadPlugins)) diff --git a/pkg/api/datasources.go b/pkg/api/datasources.go index 0eef6a7ce16..8906ab381a4 100644 --- a/pkg/api/datasources.go +++ b/pkg/api/datasources.go @@ -26,7 +26,6 @@ import ( ) var datasourcesLogger = log.New("datasources") -var secretsPluginError datasources.ErrDatasourceSecretsPluginUserFriendly // swagger:route GET /datasources datasources getDataSources // @@ -178,9 +177,6 @@ func (hs *HTTPServer) DeleteDataSourceById(c *contextmodel.ReqContext) response. err = hs.DataSourcesService.DeleteDataSource(c.Req.Context(), cmd) if err != nil { - if errors.As(err, &secretsPluginError) { - return response.Error(http.StatusInternalServerError, "Failed to delete datasource: "+err.Error(), err) - } return response.Error(http.StatusInternalServerError, "Failed to delete datasource", err) } @@ -257,9 +253,6 @@ func (hs *HTTPServer) DeleteDataSourceByUID(c *contextmodel.ReqContext) response err = hs.DataSourcesService.DeleteDataSource(c.Req.Context(), cmd) if err != nil { - if errors.As(err, &secretsPluginError) { - return response.Error(http.StatusInternalServerError, "Failed to delete datasource: "+err.Error(), err) - } return response.Error(http.StatusInternalServerError, "Failed to delete datasource", err) } @@ -307,9 +300,6 @@ func (hs *HTTPServer) DeleteDataSourceByName(c *contextmodel.ReqContext) respons cmd := &datasources.DeleteDataSourceCommand{Name: name, OrgID: c.SignedInUser.GetOrgID()} err = hs.DataSourcesService.DeleteDataSource(c.Req.Context(), cmd) if err != nil { - if errors.As(err, &secretsPluginError) { - return response.Error(http.StatusInternalServerError, "Failed to delete datasource: "+err.Error(), err) - } return response.Error(http.StatusInternalServerError, "Failed to delete datasource", err) } @@ -405,10 +395,6 @@ func (hs *HTTPServer) AddDataSource(c *contextmodel.ReqContext) response.Respons return response.Error(http.StatusConflict, err.Error(), err) } - if errors.As(err, &secretsPluginError) { - return response.Error(http.StatusInternalServerError, "Failed to add datasource: "+err.Error(), err) - } - return response.ErrOrFallback(http.StatusInternalServerError, "Failed to add datasource", err) } @@ -532,10 +518,6 @@ func (hs *HTTPServer) updateDataSourceByID(c *contextmodel.ReqContext, ds *datas return response.Error(http.StatusConflict, "Datasource has already been updated by someone else. Please reload and try again", err) } - if errors.As(err, &secretsPluginError) { - return response.Error(http.StatusInternalServerError, "Failed to update datasource: "+err.Error(), err) - } - return response.ErrOrFallback(http.StatusInternalServerError, "Failed to update datasource", err) } diff --git a/pkg/api/ds_query.go b/pkg/api/ds_query.go index 2ff08a727b7..eee746165b2 100644 --- a/pkg/api/ds_query.go +++ b/pkg/api/ds_query.go @@ -29,11 +29,6 @@ func (hs *HTTPServer) handleQueryMetricsError(err error) *response.NormalRespons return response.Error(http.StatusNotFound, "Data source not found", err) } - var secretsPlugin datasources.ErrDatasourceSecretsPluginUserFriendly - if errors.As(err, &secretsPlugin) { - return response.Error(http.StatusInternalServerError, fmt.Sprint("Secrets Plugin error: ", err.Error()), err) - } - return response.ErrOrFallback(http.StatusInternalServerError, "Query data error", err) } diff --git a/pkg/api/ds_query_test.go b/pkg/api/ds_query_test.go index 60d6d3a2a47..f0f20e7a7ad 100644 --- a/pkg/api/ds_query_test.go +++ b/pkg/api/ds_query_test.go @@ -1,7 +1,6 @@ package api import ( - "bytes" "context" "encoding/json" "errors" @@ -25,7 +24,6 @@ import ( fakeDatasources "github.com/grafana/grafana/pkg/services/datasources/fakes" "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginconfig" "github.com/grafana/grafana/pkg/services/pluginsintegration/plugincontext" - "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginsettings" pluginSettings "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginsettings/service" "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginstore" "github.com/grafana/grafana/pkg/services/query" @@ -40,11 +38,6 @@ type fakeDataSourceRequestValidator struct { err error } -type secretsErrorResponseBody struct { - Error string `json:"error"` - Message string `json:"message"` -} - func (rv *fakeDataSourceRequestValidator) Validate(ds *datasources.DataSource, req *http.Request) error { return rv.err } @@ -93,64 +86,6 @@ func TestAPIEndpoint_Metrics_QueryMetricsV2(t *testing.T) { }) } -func TestAPIEndpoint_Metrics_PluginDecryptionFailure(t *testing.T) { - cfg := setting.NewCfg() - ds := &fakeDatasources.FakeDataSourceService{SimulatePluginFailure: true} - db := &dbtest.FakeDB{ExpectedError: pluginsettings.ErrPluginSettingNotFound} - pcp := plugincontext.ProvideService(cfg, localcache.ProvideService(), - &pluginstore.FakePluginStore{ - PluginList: []pluginstore.Plugin{ - { - JSONData: plugins.JSONData{ - ID: "grafana", - }, - }, - }, - }, - &fakeDatasources.FakeCacheService{}, - ds, pluginSettings.ProvideService(db, secretstest.NewFakeSecretsService()), pluginconfig.NewFakePluginRequestConfigProvider(), - ) - qds := query.ProvideService( - cfg, - nil, - nil, - &fakeDataSourceRequestValidator{}, - &fakePluginClient{ - QueryDataHandlerFunc: func(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) { - resp := backend.Responses{ - "A": backend.DataResponse{ - Error: errors.New("query failed"), - }, - } - return &backend.QueryDataResponse{Responses: resp}, nil - }, - }, - pcp, - ) - httpServer := SetupAPITestServer(t, func(hs *HTTPServer) { - hs.queryDataService = qds - hs.QuotaService = quotatest.New(false, nil) - hs.pluginContextProvider = pcp - }) - - t.Run("Status code is 500 and a secrets plugin error is returned if there is a problem getting secrets from the remote plugin", func(t *testing.T) { - req := httpServer.NewPostRequest("/api/ds/query", strings.NewReader(reqValid)) - webtest.RequestWithSignedInUser(req, &user.SignedInUser{UserID: 1, OrgID: 1, Permissions: map[int64]map[string][]string{1: {datasources.ActionQuery: []string{datasources.ScopeAll}}}}) - resp, err := httpServer.SendJSON(req) - require.NoError(t, err) - require.Equal(t, http.StatusInternalServerError, resp.StatusCode) - buf := new(bytes.Buffer) - _, err = buf.ReadFrom(resp.Body) - require.NoError(t, err) - require.NoError(t, resp.Body.Close()) - var resObj secretsErrorResponseBody - err = json.Unmarshal(buf.Bytes(), &resObj) - require.NoError(t, err) - require.Equal(t, "", resObj.Error) - require.Contains(t, resObj.Message, "Secrets Plugin error:") - }) -} - var reqValid = `{ "from": "", "to": "", diff --git a/pkg/api/dtos/frontend_settings.go b/pkg/api/dtos/frontend_settings.go index c495df3f929..60fe760ce06 100644 --- a/pkg/api/dtos/frontend_settings.go +++ b/pkg/api/dtos/frontend_settings.go @@ -220,30 +220,29 @@ type FrontendSettingsDTO struct { LicenseInfo FrontendSettingsLicenseInfoDTO `json:"licenseInfo"` - FeatureToggles map[string]bool `json:"featureToggles"` - AnonymousEnabled bool `json:"anonymousEnabled"` - AnonymousDeviceLimit int64 `json:"anonymousDeviceLimit"` - RendererAvailable bool `json:"rendererAvailable"` - RendererVersion string `json:"rendererVersion"` - RendererDefaultImageWidth int `json:"rendererDefaultImageWidth"` - RendererDefaultImageHeight int `json:"rendererDefaultImageHeight"` - RendererDefaultImageScale float64 `json:"rendererDefaultImageScale"` - SecretsManagerPluginEnabled bool `json:"secretsManagerPluginEnabled"` - Http2Enabled bool `json:"http2Enabled"` - GrafanaJavascriptAgent setting.GrafanaJavascriptAgent `json:"grafanaJavascriptAgent"` - PluginCatalogURL string `json:"pluginCatalogURL"` - PluginAdminEnabled bool `json:"pluginAdminEnabled"` - PluginAdminExternalManageEnabled bool `json:"pluginAdminExternalManageEnabled"` - PluginCatalogHiddenPlugins []string `json:"pluginCatalogHiddenPlugins"` - PluginCatalogManagedPlugins []string `json:"pluginCatalogManagedPlugins"` - PluginCatalogPreinstalledPlugins []setting.InstallPlugin `json:"pluginCatalogPreinstalledPlugins"` - ExpressionsEnabled bool `json:"expressionsEnabled"` - AwsAllowedAuthProviders []string `json:"awsAllowedAuthProviders"` - AwsAssumeRoleEnabled bool `json:"awsAssumeRoleEnabled"` - SupportBundlesEnabled bool `json:"supportBundlesEnabled"` - SnapshotEnabled bool `json:"snapshotEnabled"` - SecureSocksDSProxyEnabled bool `json:"secureSocksDSProxyEnabled"` - ReportingStaticContext map[string]string `json:"reportingStaticContext"` + FeatureToggles map[string]bool `json:"featureToggles"` + AnonymousEnabled bool `json:"anonymousEnabled"` + AnonymousDeviceLimit int64 `json:"anonymousDeviceLimit"` + RendererAvailable bool `json:"rendererAvailable"` + RendererVersion string `json:"rendererVersion"` + RendererDefaultImageWidth int `json:"rendererDefaultImageWidth"` + RendererDefaultImageHeight int `json:"rendererDefaultImageHeight"` + RendererDefaultImageScale float64 `json:"rendererDefaultImageScale"` + Http2Enabled bool `json:"http2Enabled"` + GrafanaJavascriptAgent setting.GrafanaJavascriptAgent `json:"grafanaJavascriptAgent"` + PluginCatalogURL string `json:"pluginCatalogURL"` + PluginAdminEnabled bool `json:"pluginAdminEnabled"` + PluginAdminExternalManageEnabled bool `json:"pluginAdminExternalManageEnabled"` + PluginCatalogHiddenPlugins []string `json:"pluginCatalogHiddenPlugins"` + PluginCatalogManagedPlugins []string `json:"pluginCatalogManagedPlugins"` + PluginCatalogPreinstalledPlugins []setting.InstallPlugin `json:"pluginCatalogPreinstalledPlugins"` + ExpressionsEnabled bool `json:"expressionsEnabled"` + AwsAllowedAuthProviders []string `json:"awsAllowedAuthProviders"` + AwsAssumeRoleEnabled bool `json:"awsAssumeRoleEnabled"` + SupportBundlesEnabled bool `json:"supportBundlesEnabled"` + SnapshotEnabled bool `json:"snapshotEnabled"` + SecureSocksDSProxyEnabled bool `json:"secureSocksDSProxyEnabled"` + ReportingStaticContext map[string]string `json:"reportingStaticContext"` PluginDependencies map[string][]plugins.Dependency `json:"pluginDependants"` Azure FrontendSettingsAzureDTO `json:"azure"` diff --git a/pkg/api/frontendsettings.go b/pkg/api/frontendsettings.go index c83822eaecf..6a9ee667d25 100644 --- a/pkg/api/frontendsettings.go +++ b/pkg/api/frontendsettings.go @@ -23,7 +23,6 @@ import ( "github.com/grafana/grafana/pkg/services/licensing" "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginsettings" "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginstore" - "github.com/grafana/grafana/pkg/services/secrets/kvstore" "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/tsdb/grafanads" "github.com/grafana/grafana/pkg/util" @@ -174,7 +173,6 @@ func (hs *HTTPServer) getFrontendSettings(c *contextmodel.ReqContext) (*dtos.Fro } hasAccess := accesscontrol.HasAccess(hs.AccessControl, c) - secretsManagerPluginEnabled := kvstore.EvaluateRemoteSecretsPlugin(c.Req.Context(), hs.secretsPluginManager, hs.Cfg) == nil trustedTypesDefaultPolicyEnabled := (hs.Cfg.CSPEnabled && strings.Contains(hs.Cfg.CSPTemplate, "require-trusted-types-for")) || (hs.Cfg.CSPReportOnlyEnabled && strings.Contains(hs.Cfg.CSPReportOnlyTemplate, "require-trusted-types-for")) isCloudMigrationTarget := hs.Features.IsEnabled(c.Req.Context(), featuremgmt.FlagOnPremToCloudMigrations) && hs.Cfg.CloudMigration.IsTarget featureToggles := hs.Features.GetEnabled(c.Req.Context()) @@ -280,7 +278,6 @@ func (hs *HTTPServer) getFrontendSettings(c *contextmodel.ReqContext) (*dtos.Fro RendererDefaultImageWidth: hs.Cfg.RendererDefaultImageWidth, RendererDefaultImageHeight: hs.Cfg.RendererDefaultImageHeight, RendererDefaultImageScale: hs.Cfg.RendererDefaultImageScale, - SecretsManagerPluginEnabled: secretsManagerPluginEnabled, Http2Enabled: hs.Cfg.Protocol == setting.HTTP2Scheme, GrafanaJavascriptAgent: hs.Cfg.GrafanaJavascriptAgent, PluginCatalogURL: hs.Cfg.PluginCatalogURL, diff --git a/pkg/api/http_server.go b/pkg/api/http_server.go index 21f6f2dcd56..de1c4dc810c 100644 --- a/pkg/api/http_server.go +++ b/pkg/api/http_server.go @@ -169,10 +169,9 @@ type HTTPServer struct { Listener net.Listener EncryptionService encryption.Internal SecretsService secrets.Service - secretsPluginManager plugins.SecretsPluginManager secretsStore secretsKV.SecretsKVStore secretsMigrator secrets.Migrator - secretsPluginMigrator spm.SecretMigrationProvider + secretMigrationProvider spm.SecretMigrationProvider DataSourcesService datasources.DataSourceService cleanUpService *cleanup.CleanUpService tracer tracing.Tracer @@ -264,8 +263,8 @@ func ProvideHTTPServer(opts ServerOptions, cfg *setting.Cfg, routeRegister routi dashboardPermissionsService accesscontrol.DashboardPermissionsService, dashboardVersionService dashver.Service, starService star.Service, csrfService csrf.Service, managedPlugins managedplugins.Manager, playlistService playlist.Service, apiKeyService apikey.Service, kvStore kvstore.KVStore, - secretsMigrator secrets.Migrator, secretsPluginManager plugins.SecretsPluginManager, secretsService secrets.Service, - secretsPluginMigrator spm.SecretMigrationProvider, secretsStore secretsKV.SecretsKVStore, + secretsMigrator secrets.Migrator, secretsService secrets.Service, + secretMigrationProvider spm.SecretMigrationProvider, secretsStore secretsKV.SecretsKVStore, publicDashboardsApi *publicdashboardsApi.Api, userService user.Service, tempUserService tempUser.Service, loginAttemptService loginAttempt.Service, orgService org.Service, orgDeletionService org.DeletionService, teamService team.Service, accesscontrolService accesscontrol.Service, navTreeService navtree.Service, @@ -329,9 +328,8 @@ func ProvideHTTPServer(opts ServerOptions, cfg *setting.Cfg, routeRegister routi SocialService: socialService, EncryptionService: encryptionService, SecretsService: secretsService, - secretsPluginManager: secretsPluginManager, secretsMigrator: secretsMigrator, - secretsPluginMigrator: secretsPluginMigrator, + secretMigrationProvider: secretMigrationProvider, secretsStore: secretsStore, DataSourcesService: dataSourcesService, searchUsersService: searchUsersService, diff --git a/pkg/plugins/backendplugin/grpcplugin/client.go b/pkg/plugins/backendplugin/grpcplugin/client.go index 35920fef2cf..1c4a2b186bb 100644 --- a/pkg/plugins/backendplugin/grpcplugin/client.go +++ b/pkg/plugins/backendplugin/grpcplugin/client.go @@ -12,7 +12,6 @@ import ( "github.com/grafana/grafana/pkg/plugins/backendplugin" "github.com/grafana/grafana/pkg/plugins/backendplugin/pluginextensionv2" - "github.com/grafana/grafana/pkg/plugins/backendplugin/secretsmanagerplugin" "github.com/grafana/grafana/pkg/plugins/log" ) @@ -31,14 +30,13 @@ var handshake = goplugin.HandshakeConfig{ // pluginSet is list of plugins supported on v2. var pluginSet = map[int]goplugin.PluginSet{ grpcplugin.ProtocolVersion: { - "diagnostics": &grpcplugin.DiagnosticsGRPCPlugin{}, - "resource": &grpcplugin.ResourceGRPCPlugin{}, - "data": &grpcplugin.DataGRPCPlugin{}, - "stream": &grpcplugin.StreamGRPCPlugin{}, - "admission": &grpcplugin.AdmissionGRPCPlugin{}, - "conversion": &grpcplugin.ConversionGRPCPlugin{}, - "renderer": &pluginextensionv2.RendererGRPCPlugin{}, - "secretsmanager": &secretsmanagerplugin.SecretsManagerGRPCPlugin{}, + "diagnostics": &grpcplugin.DiagnosticsGRPCPlugin{}, + "resource": &grpcplugin.ResourceGRPCPlugin{}, + "data": &grpcplugin.DataGRPCPlugin{}, + "stream": &grpcplugin.StreamGRPCPlugin{}, + "admission": &grpcplugin.AdmissionGRPCPlugin{}, + "conversion": &grpcplugin.ConversionGRPCPlugin{}, + "renderer": &pluginextensionv2.RendererGRPCPlugin{}, }, } @@ -84,19 +82,15 @@ func newClientConfig(executablePath string, args []string, env []string, skipHos // StartRendererFunc callback function called when a renderer plugin is started. type StartRendererFunc func(pluginID string, renderer pluginextensionv2.RendererPlugin, logger log.Logger) error -// StartSecretsManagerFunc callback function called when a secrets manager plugin is started. -type StartSecretsManagerFunc func(pluginID string, secretsmanager secretsmanagerplugin.SecretsManagerPlugin, logger log.Logger) error - // PluginDescriptor is a descriptor used for registering backend plugins. type PluginDescriptor struct { - pluginID string - executablePath string - executableArgs []string - skipHostEnvVars bool - managed bool - versionedPlugins map[int]goplugin.PluginSet - startRendererFn StartRendererFunc - startSecretsManagerFn StartSecretsManagerFunc + pluginID string + executablePath string + executableArgs []string + skipHostEnvVars bool + managed bool + versionedPlugins map[int]goplugin.PluginSet + startRendererFn StartRendererFunc } // NewBackendPlugin creates a new backend plugin factory used for registering a backend plugin. @@ -131,14 +125,3 @@ func NewRendererPlugin(pluginID, executablePath string, startFn StartRendererFun startRendererFn: startFn, }) } - -// NewSecretsManagerPlugin creates a new secrets manager plugin factory used for registering a backend secrets manager plugin. -func NewSecretsManagerPlugin(pluginID, executablePath string, startFn StartSecretsManagerFunc) backendplugin.PluginFactoryFunc { - return newPlugin(PluginDescriptor{ - pluginID: pluginID, - executablePath: executablePath, - managed: false, - versionedPlugins: pluginSet, - startSecretsManagerFn: startFn, - }) -} diff --git a/pkg/plugins/backendplugin/grpcplugin/client_v2.go b/pkg/plugins/backendplugin/grpcplugin/client_v2.go index ade867a2ace..7babf11c2df 100644 --- a/pkg/plugins/backendplugin/grpcplugin/client_v2.go +++ b/pkg/plugins/backendplugin/grpcplugin/client_v2.go @@ -16,7 +16,6 @@ import ( "github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/plugins/backendplugin/pluginextensionv2" - "github.com/grafana/grafana/pkg/plugins/backendplugin/secretsmanagerplugin" "github.com/grafana/grafana/pkg/plugins/log" ) @@ -32,7 +31,6 @@ type ClientV2 struct { grpcplugin.AdmissionClient grpcplugin.ConversionClient pluginextensionv2.RendererPlugin - secretsmanagerplugin.SecretsManagerPlugin } func newClientV2(descriptor PluginDescriptor, logger log.Logger, rpcClient plugin.ClientProtocol) (*ClientV2, error) { @@ -71,11 +69,6 @@ func newClientV2(descriptor PluginDescriptor, logger log.Logger, rpcClient plugi return nil, err } - rawSecretsManager, err := rpcClient.Dispense("secretsmanager") - if err != nil { - return nil, err - } - c := &ClientV2{} if rawDiagnostics != nil { if diagnosticsClient, ok := rawDiagnostics.(grpcplugin.DiagnosticsClient); ok { @@ -119,24 +112,12 @@ func newClientV2(descriptor PluginDescriptor, logger log.Logger, rpcClient plugi } } - if rawSecretsManager != nil { - if secretsManagerPlugin, ok := rawSecretsManager.(secretsmanagerplugin.SecretsManagerPlugin); ok { - c.SecretsManagerPlugin = secretsManagerPlugin - } - } - if descriptor.startRendererFn != nil { if err := descriptor.startRendererFn(descriptor.pluginID, c.RendererPlugin, logger); err != nil { return nil, err } } - if descriptor.startSecretsManagerFn != nil { - if err := descriptor.startSecretsManagerFn(descriptor.pluginID, c.SecretsManagerPlugin, logger); err != nil { - return nil, err - } - } - return c, nil } diff --git a/pkg/plugins/backendplugin/provider/provider.go b/pkg/plugins/backendplugin/provider/provider.go index 685c06a2df4..db9e3309a34 100644 --- a/pkg/plugins/backendplugin/provider/provider.go +++ b/pkg/plugins/backendplugin/provider/provider.go @@ -8,7 +8,6 @@ import ( "github.com/grafana/grafana/pkg/plugins/backendplugin/coreplugin" "github.com/grafana/grafana/pkg/plugins/backendplugin/grpcplugin" "github.com/grafana/grafana/pkg/plugins/backendplugin/pluginextensionv2" - "github.com/grafana/grafana/pkg/plugins/backendplugin/secretsmanagerplugin" "github.com/grafana/grafana/pkg/plugins/log" ) @@ -21,7 +20,7 @@ type Service struct { func New(providers ...PluginBackendProvider) *Service { if len(providers) == 0 { - return New(SecretsManagerProvider, DefaultProvider) + return New(DefaultProvider) } return &Service{ providerChain: providers, @@ -29,7 +28,7 @@ func New(providers ...PluginBackendProvider) *Service { } func ProvideService(coreRegistry *coreplugin.Registry) *Service { - return New(coreRegistry.BackendFactoryProvider(), SecretsManagerProvider, DefaultProvider) + return New(coreRegistry.BackendFactoryProvider(), DefaultProvider) } func (s *Service) BackendFactory(ctx context.Context, p *plugins.Plugin) backendplugin.PluginFactoryFunc { @@ -53,18 +52,6 @@ var RendererProvider PluginBackendProvider = func(_ context.Context, p *plugins. ) } -var SecretsManagerProvider PluginBackendProvider = func(_ context.Context, p *plugins.Plugin) backendplugin.PluginFactoryFunc { - if !p.IsSecretsManager() { - return nil - } - return grpcplugin.NewSecretsManagerPlugin(p.ID, p.ExecutablePath(), - func(pluginID string, secretsmanager secretsmanagerplugin.SecretsManagerPlugin, logger log.Logger) error { - p.SecretsManager = secretsmanager - return nil - }, - ) -} - var DefaultProvider = PluginBackendProvider(func(_ context.Context, p *plugins.Plugin) backendplugin.PluginFactoryFunc { return grpcplugin.NewBackendPlugin(p.ID, p.ExecutablePath(), p.SkipHostEnvVars) }) diff --git a/pkg/plugins/backendplugin/secretsmanagerplugin/buf.gen.yaml b/pkg/plugins/backendplugin/secretsmanagerplugin/buf.gen.yaml deleted file mode 100644 index 99beef65289..00000000000 --- a/pkg/plugins/backendplugin/secretsmanagerplugin/buf.gen.yaml +++ /dev/null @@ -1,9 +0,0 @@ -version: v1 -plugins: - - plugin: go - out: pkg/plugins/backendplugin/secretsmanagerplugin - opt: paths=source_relative - - plugin: go-grpc - out: pkg/plugins/backendplugin/secretsmanagerplugin - opt: - - paths=source_relative diff --git a/pkg/plugins/backendplugin/secretsmanagerplugin/buf.yaml b/pkg/plugins/backendplugin/secretsmanagerplugin/buf.yaml deleted file mode 100644 index 1a5194568a9..00000000000 --- a/pkg/plugins/backendplugin/secretsmanagerplugin/buf.yaml +++ /dev/null @@ -1,7 +0,0 @@ -version: v1 -breaking: - use: - - FILE -lint: - use: - - DEFAULT diff --git a/pkg/plugins/backendplugin/secretsmanagerplugin/secretsmanager.pb.go b/pkg/plugins/backendplugin/secretsmanagerplugin/secretsmanager.pb.go deleted file mode 100644 index bdecccee0d8..00000000000 --- a/pkg/plugins/backendplugin/secretsmanagerplugin/secretsmanager.pb.go +++ /dev/null @@ -1,921 +0,0 @@ -// Code generated by protoc-gen-go. DO NOT EDIT. -// versions: -// protoc-gen-go v1.36.5 -// protoc (unknown) -// source: secretsmanager.proto - -package secretsmanagerplugin - -import ( - protoreflect "google.golang.org/protobuf/reflect/protoreflect" - protoimpl "google.golang.org/protobuf/runtime/protoimpl" - reflect "reflect" - sync "sync" - unsafe "unsafe" -) - -const ( - // Verify that this generated code is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) - // Verify that runtime/protoimpl is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) -) - -type Key struct { - state protoimpl.MessageState `protogen:"open.v1"` - OrgId int64 `protobuf:"varint,1,opt,name=orgId,proto3" json:"orgId,omitempty"` - Namespace string `protobuf:"bytes,2,opt,name=namespace,proto3" json:"namespace,omitempty"` - Type string `protobuf:"bytes,3,opt,name=type,proto3" json:"type,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *Key) Reset() { - *x = Key{} - mi := &file_secretsmanager_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *Key) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*Key) ProtoMessage() {} - -func (x *Key) ProtoReflect() protoreflect.Message { - mi := &file_secretsmanager_proto_msgTypes[0] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use Key.ProtoReflect.Descriptor instead. -func (*Key) Descriptor() ([]byte, []int) { - return file_secretsmanager_proto_rawDescGZIP(), []int{0} -} - -func (x *Key) GetOrgId() int64 { - if x != nil { - return x.OrgId - } - return 0 -} - -func (x *Key) GetNamespace() string { - if x != nil { - return x.Namespace - } - return "" -} - -func (x *Key) GetType() string { - if x != nil { - return x.Type - } - return "" -} - -type Item struct { - state protoimpl.MessageState `protogen:"open.v1"` - Key *Key `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` - Value string `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *Item) Reset() { - *x = Item{} - mi := &file_secretsmanager_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *Item) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*Item) ProtoMessage() {} - -func (x *Item) ProtoReflect() protoreflect.Message { - mi := &file_secretsmanager_proto_msgTypes[1] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use Item.ProtoReflect.Descriptor instead. -func (*Item) Descriptor() ([]byte, []int) { - return file_secretsmanager_proto_rawDescGZIP(), []int{1} -} - -func (x *Item) GetKey() *Key { - if x != nil { - return x.Key - } - return nil -} - -func (x *Item) GetValue() string { - if x != nil { - return x.Value - } - return "" -} - -type GetSecretRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - KeyDescriptor *Key `protobuf:"bytes,1,opt,name=keyDescriptor,proto3" json:"keyDescriptor,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *GetSecretRequest) Reset() { - *x = GetSecretRequest{} - mi := &file_secretsmanager_proto_msgTypes[2] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *GetSecretRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*GetSecretRequest) ProtoMessage() {} - -func (x *GetSecretRequest) ProtoReflect() protoreflect.Message { - mi := &file_secretsmanager_proto_msgTypes[2] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use GetSecretRequest.ProtoReflect.Descriptor instead. -func (*GetSecretRequest) Descriptor() ([]byte, []int) { - return file_secretsmanager_proto_rawDescGZIP(), []int{2} -} - -func (x *GetSecretRequest) GetKeyDescriptor() *Key { - if x != nil { - return x.KeyDescriptor - } - return nil -} - -type GetSecretResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - UserFriendlyError string `protobuf:"bytes,1,opt,name=userFriendlyError,proto3" json:"userFriendlyError,omitempty"` - DecryptedValue string `protobuf:"bytes,2,opt,name=decryptedValue,proto3" json:"decryptedValue,omitempty"` - Exists bool `protobuf:"varint,3,opt,name=exists,proto3" json:"exists,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *GetSecretResponse) Reset() { - *x = GetSecretResponse{} - mi := &file_secretsmanager_proto_msgTypes[3] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *GetSecretResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*GetSecretResponse) ProtoMessage() {} - -func (x *GetSecretResponse) ProtoReflect() protoreflect.Message { - mi := &file_secretsmanager_proto_msgTypes[3] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use GetSecretResponse.ProtoReflect.Descriptor instead. -func (*GetSecretResponse) Descriptor() ([]byte, []int) { - return file_secretsmanager_proto_rawDescGZIP(), []int{3} -} - -func (x *GetSecretResponse) GetUserFriendlyError() string { - if x != nil { - return x.UserFriendlyError - } - return "" -} - -func (x *GetSecretResponse) GetDecryptedValue() string { - if x != nil { - return x.DecryptedValue - } - return "" -} - -func (x *GetSecretResponse) GetExists() bool { - if x != nil { - return x.Exists - } - return false -} - -type SetSecretRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - KeyDescriptor *Key `protobuf:"bytes,1,opt,name=keyDescriptor,proto3" json:"keyDescriptor,omitempty"` - Value string `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *SetSecretRequest) Reset() { - *x = SetSecretRequest{} - mi := &file_secretsmanager_proto_msgTypes[4] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *SetSecretRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*SetSecretRequest) ProtoMessage() {} - -func (x *SetSecretRequest) ProtoReflect() protoreflect.Message { - mi := &file_secretsmanager_proto_msgTypes[4] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use SetSecretRequest.ProtoReflect.Descriptor instead. -func (*SetSecretRequest) Descriptor() ([]byte, []int) { - return file_secretsmanager_proto_rawDescGZIP(), []int{4} -} - -func (x *SetSecretRequest) GetKeyDescriptor() *Key { - if x != nil { - return x.KeyDescriptor - } - return nil -} - -func (x *SetSecretRequest) GetValue() string { - if x != nil { - return x.Value - } - return "" -} - -type SetSecretResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - UserFriendlyError string `protobuf:"bytes,1,opt,name=userFriendlyError,proto3" json:"userFriendlyError,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *SetSecretResponse) Reset() { - *x = SetSecretResponse{} - mi := &file_secretsmanager_proto_msgTypes[5] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *SetSecretResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*SetSecretResponse) ProtoMessage() {} - -func (x *SetSecretResponse) ProtoReflect() protoreflect.Message { - mi := &file_secretsmanager_proto_msgTypes[5] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use SetSecretResponse.ProtoReflect.Descriptor instead. -func (*SetSecretResponse) Descriptor() ([]byte, []int) { - return file_secretsmanager_proto_rawDescGZIP(), []int{5} -} - -func (x *SetSecretResponse) GetUserFriendlyError() string { - if x != nil { - return x.UserFriendlyError - } - return "" -} - -type DeleteSecretRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - KeyDescriptor *Key `protobuf:"bytes,1,opt,name=keyDescriptor,proto3" json:"keyDescriptor,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *DeleteSecretRequest) Reset() { - *x = DeleteSecretRequest{} - mi := &file_secretsmanager_proto_msgTypes[6] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *DeleteSecretRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*DeleteSecretRequest) ProtoMessage() {} - -func (x *DeleteSecretRequest) ProtoReflect() protoreflect.Message { - mi := &file_secretsmanager_proto_msgTypes[6] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use DeleteSecretRequest.ProtoReflect.Descriptor instead. -func (*DeleteSecretRequest) Descriptor() ([]byte, []int) { - return file_secretsmanager_proto_rawDescGZIP(), []int{6} -} - -func (x *DeleteSecretRequest) GetKeyDescriptor() *Key { - if x != nil { - return x.KeyDescriptor - } - return nil -} - -type DeleteSecretResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - UserFriendlyError string `protobuf:"bytes,1,opt,name=userFriendlyError,proto3" json:"userFriendlyError,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *DeleteSecretResponse) Reset() { - *x = DeleteSecretResponse{} - mi := &file_secretsmanager_proto_msgTypes[7] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *DeleteSecretResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*DeleteSecretResponse) ProtoMessage() {} - -func (x *DeleteSecretResponse) ProtoReflect() protoreflect.Message { - mi := &file_secretsmanager_proto_msgTypes[7] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use DeleteSecretResponse.ProtoReflect.Descriptor instead. -func (*DeleteSecretResponse) Descriptor() ([]byte, []int) { - return file_secretsmanager_proto_rawDescGZIP(), []int{7} -} - -func (x *DeleteSecretResponse) GetUserFriendlyError() string { - if x != nil { - return x.UserFriendlyError - } - return "" -} - -type ListSecretsRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - KeyDescriptor *Key `protobuf:"bytes,1,opt,name=keyDescriptor,proto3" json:"keyDescriptor,omitempty"` - AllOrganizations bool `protobuf:"varint,2,opt,name=allOrganizations,proto3" json:"allOrganizations,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *ListSecretsRequest) Reset() { - *x = ListSecretsRequest{} - mi := &file_secretsmanager_proto_msgTypes[8] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *ListSecretsRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*ListSecretsRequest) ProtoMessage() {} - -func (x *ListSecretsRequest) ProtoReflect() protoreflect.Message { - mi := &file_secretsmanager_proto_msgTypes[8] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use ListSecretsRequest.ProtoReflect.Descriptor instead. -func (*ListSecretsRequest) Descriptor() ([]byte, []int) { - return file_secretsmanager_proto_rawDescGZIP(), []int{8} -} - -func (x *ListSecretsRequest) GetKeyDescriptor() *Key { - if x != nil { - return x.KeyDescriptor - } - return nil -} - -func (x *ListSecretsRequest) GetAllOrganizations() bool { - if x != nil { - return x.AllOrganizations - } - return false -} - -type ListSecretsResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - UserFriendlyError string `protobuf:"bytes,1,opt,name=userFriendlyError,proto3" json:"userFriendlyError,omitempty"` - Keys []*Key `protobuf:"bytes,2,rep,name=keys,proto3" json:"keys,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *ListSecretsResponse) Reset() { - *x = ListSecretsResponse{} - mi := &file_secretsmanager_proto_msgTypes[9] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *ListSecretsResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*ListSecretsResponse) ProtoMessage() {} - -func (x *ListSecretsResponse) ProtoReflect() protoreflect.Message { - mi := &file_secretsmanager_proto_msgTypes[9] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use ListSecretsResponse.ProtoReflect.Descriptor instead. -func (*ListSecretsResponse) Descriptor() ([]byte, []int) { - return file_secretsmanager_proto_rawDescGZIP(), []int{9} -} - -func (x *ListSecretsResponse) GetUserFriendlyError() string { - if x != nil { - return x.UserFriendlyError - } - return "" -} - -func (x *ListSecretsResponse) GetKeys() []*Key { - if x != nil { - return x.Keys - } - return nil -} - -type GetAllSecretsRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *GetAllSecretsRequest) Reset() { - *x = GetAllSecretsRequest{} - mi := &file_secretsmanager_proto_msgTypes[10] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *GetAllSecretsRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*GetAllSecretsRequest) ProtoMessage() {} - -func (x *GetAllSecretsRequest) ProtoReflect() protoreflect.Message { - mi := &file_secretsmanager_proto_msgTypes[10] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use GetAllSecretsRequest.ProtoReflect.Descriptor instead. -func (*GetAllSecretsRequest) Descriptor() ([]byte, []int) { - return file_secretsmanager_proto_rawDescGZIP(), []int{10} -} - -type GetAllSecretsResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - UserFriendlyError string `protobuf:"bytes,1,opt,name=userFriendlyError,proto3" json:"userFriendlyError,omitempty"` - Items []*Item `protobuf:"bytes,2,rep,name=items,proto3" json:"items,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *GetAllSecretsResponse) Reset() { - *x = GetAllSecretsResponse{} - mi := &file_secretsmanager_proto_msgTypes[11] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *GetAllSecretsResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*GetAllSecretsResponse) ProtoMessage() {} - -func (x *GetAllSecretsResponse) ProtoReflect() protoreflect.Message { - mi := &file_secretsmanager_proto_msgTypes[11] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use GetAllSecretsResponse.ProtoReflect.Descriptor instead. -func (*GetAllSecretsResponse) Descriptor() ([]byte, []int) { - return file_secretsmanager_proto_rawDescGZIP(), []int{11} -} - -func (x *GetAllSecretsResponse) GetUserFriendlyError() string { - if x != nil { - return x.UserFriendlyError - } - return "" -} - -func (x *GetAllSecretsResponse) GetItems() []*Item { - if x != nil { - return x.Items - } - return nil -} - -type RenameSecretRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - KeyDescriptor *Key `protobuf:"bytes,1,opt,name=keyDescriptor,proto3" json:"keyDescriptor,omitempty"` - NewNamespace string `protobuf:"bytes,2,opt,name=newNamespace,proto3" json:"newNamespace,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *RenameSecretRequest) Reset() { - *x = RenameSecretRequest{} - mi := &file_secretsmanager_proto_msgTypes[12] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *RenameSecretRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*RenameSecretRequest) ProtoMessage() {} - -func (x *RenameSecretRequest) ProtoReflect() protoreflect.Message { - mi := &file_secretsmanager_proto_msgTypes[12] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use RenameSecretRequest.ProtoReflect.Descriptor instead. -func (*RenameSecretRequest) Descriptor() ([]byte, []int) { - return file_secretsmanager_proto_rawDescGZIP(), []int{12} -} - -func (x *RenameSecretRequest) GetKeyDescriptor() *Key { - if x != nil { - return x.KeyDescriptor - } - return nil -} - -func (x *RenameSecretRequest) GetNewNamespace() string { - if x != nil { - return x.NewNamespace - } - return "" -} - -type RenameSecretResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - UserFriendlyError string `protobuf:"bytes,1,opt,name=userFriendlyError,proto3" json:"userFriendlyError,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *RenameSecretResponse) Reset() { - *x = RenameSecretResponse{} - mi := &file_secretsmanager_proto_msgTypes[13] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *RenameSecretResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*RenameSecretResponse) ProtoMessage() {} - -func (x *RenameSecretResponse) ProtoReflect() protoreflect.Message { - mi := &file_secretsmanager_proto_msgTypes[13] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use RenameSecretResponse.ProtoReflect.Descriptor instead. -func (*RenameSecretResponse) Descriptor() ([]byte, []int) { - return file_secretsmanager_proto_rawDescGZIP(), []int{13} -} - -func (x *RenameSecretResponse) GetUserFriendlyError() string { - if x != nil { - return x.UserFriendlyError - } - return "" -} - -var File_secretsmanager_proto protoreflect.FileDescriptor - -var file_secretsmanager_proto_rawDesc = string([]byte{ - 0x0a, 0x14, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, - 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x14, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x6d, - 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x22, 0x4d, 0x0a, 0x03, - 0x4b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x6f, 0x72, 0x67, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x05, 0x6f, 0x72, 0x67, 0x49, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x6e, 0x61, 0x6d, - 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6e, 0x61, - 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0x49, 0x0a, 0x04, 0x49, - 0x74, 0x65, 0x6d, 0x12, 0x2b, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x19, 0x2e, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, - 0x72, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2e, 0x4b, 0x65, 0x79, 0x52, 0x03, 0x6b, 0x65, 0x79, - 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x53, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x53, 0x65, 0x63, - 0x72, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x3f, 0x0a, 0x0d, 0x6b, 0x65, - 0x79, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x19, 0x2e, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x6d, 0x61, 0x6e, 0x61, 0x67, - 0x65, 0x72, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2e, 0x4b, 0x65, 0x79, 0x52, 0x0d, 0x6b, 0x65, - 0x79, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x22, 0x81, 0x01, 0x0a, 0x11, - 0x47, 0x65, 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x2c, 0x0a, 0x11, 0x75, 0x73, 0x65, 0x72, 0x46, 0x72, 0x69, 0x65, 0x6e, 0x64, 0x6c, - 0x79, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x75, 0x73, - 0x65, 0x72, 0x46, 0x72, 0x69, 0x65, 0x6e, 0x64, 0x6c, 0x79, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x12, - 0x26, 0x0a, 0x0e, 0x64, 0x65, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x56, 0x61, 0x6c, 0x75, - 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x64, 0x65, 0x63, 0x72, 0x79, 0x70, 0x74, - 0x65, 0x64, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x65, 0x78, 0x69, 0x73, 0x74, - 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x65, 0x78, 0x69, 0x73, 0x74, 0x73, 0x22, - 0x69, 0x0a, 0x10, 0x53, 0x65, 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x12, 0x3f, 0x0a, 0x0d, 0x6b, 0x65, 0x79, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, - 0x70, 0x74, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x73, 0x65, 0x63, - 0x72, 0x65, 0x74, 0x73, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x70, 0x6c, 0x75, 0x67, 0x69, - 0x6e, 0x2e, 0x4b, 0x65, 0x79, 0x52, 0x0d, 0x6b, 0x65, 0x79, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, - 0x70, 0x74, 0x6f, 0x72, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x41, 0x0a, 0x11, 0x53, 0x65, - 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x2c, 0x0a, 0x11, 0x75, 0x73, 0x65, 0x72, 0x46, 0x72, 0x69, 0x65, 0x6e, 0x64, 0x6c, 0x79, 0x45, - 0x72, 0x72, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x75, 0x73, 0x65, 0x72, - 0x46, 0x72, 0x69, 0x65, 0x6e, 0x64, 0x6c, 0x79, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x56, 0x0a, - 0x13, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x12, 0x3f, 0x0a, 0x0d, 0x6b, 0x65, 0x79, 0x44, 0x65, 0x73, 0x63, 0x72, - 0x69, 0x70, 0x74, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x73, 0x65, - 0x63, 0x72, 0x65, 0x74, 0x73, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x70, 0x6c, 0x75, 0x67, - 0x69, 0x6e, 0x2e, 0x4b, 0x65, 0x79, 0x52, 0x0d, 0x6b, 0x65, 0x79, 0x44, 0x65, 0x73, 0x63, 0x72, - 0x69, 0x70, 0x74, 0x6f, 0x72, 0x22, 0x44, 0x0a, 0x14, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x53, - 0x65, 0x63, 0x72, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2c, 0x0a, - 0x11, 0x75, 0x73, 0x65, 0x72, 0x46, 0x72, 0x69, 0x65, 0x6e, 0x64, 0x6c, 0x79, 0x45, 0x72, 0x72, - 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x75, 0x73, 0x65, 0x72, 0x46, 0x72, - 0x69, 0x65, 0x6e, 0x64, 0x6c, 0x79, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x81, 0x01, 0x0a, 0x12, - 0x4c, 0x69, 0x73, 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x12, 0x3f, 0x0a, 0x0d, 0x6b, 0x65, 0x79, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, - 0x74, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x73, 0x65, 0x63, 0x72, - 0x65, 0x74, 0x73, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, - 0x2e, 0x4b, 0x65, 0x79, 0x52, 0x0d, 0x6b, 0x65, 0x79, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, - 0x74, 0x6f, 0x72, 0x12, 0x2a, 0x0a, 0x10, 0x61, 0x6c, 0x6c, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, - 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x61, - 0x6c, 0x6c, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, - 0x72, 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2c, 0x0a, 0x11, 0x75, 0x73, 0x65, 0x72, 0x46, 0x72, - 0x69, 0x65, 0x6e, 0x64, 0x6c, 0x79, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x11, 0x75, 0x73, 0x65, 0x72, 0x46, 0x72, 0x69, 0x65, 0x6e, 0x64, 0x6c, 0x79, 0x45, - 0x72, 0x72, 0x6f, 0x72, 0x12, 0x2d, 0x0a, 0x04, 0x6b, 0x65, 0x79, 0x73, 0x18, 0x02, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x6d, 0x61, 0x6e, 0x61, - 0x67, 0x65, 0x72, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2e, 0x4b, 0x65, 0x79, 0x52, 0x04, 0x6b, - 0x65, 0x79, 0x73, 0x22, 0x16, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x41, 0x6c, 0x6c, 0x53, 0x65, 0x63, - 0x72, 0x65, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x77, 0x0a, 0x15, 0x47, - 0x65, 0x74, 0x41, 0x6c, 0x6c, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2c, 0x0a, 0x11, 0x75, 0x73, 0x65, 0x72, 0x46, 0x72, 0x69, 0x65, - 0x6e, 0x64, 0x6c, 0x79, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x11, 0x75, 0x73, 0x65, 0x72, 0x46, 0x72, 0x69, 0x65, 0x6e, 0x64, 0x6c, 0x79, 0x45, 0x72, 0x72, - 0x6f, 0x72, 0x12, 0x30, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x1a, 0x2e, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x6d, 0x61, 0x6e, 0x61, 0x67, - 0x65, 0x72, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2e, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x05, 0x69, - 0x74, 0x65, 0x6d, 0x73, 0x22, 0x7a, 0x0a, 0x13, 0x52, 0x65, 0x6e, 0x61, 0x6d, 0x65, 0x53, 0x65, - 0x63, 0x72, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x3f, 0x0a, 0x0d, 0x6b, - 0x65, 0x79, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x6d, 0x61, 0x6e, 0x61, - 0x67, 0x65, 0x72, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2e, 0x4b, 0x65, 0x79, 0x52, 0x0d, 0x6b, - 0x65, 0x79, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x12, 0x22, 0x0a, 0x0c, - 0x6e, 0x65, 0x77, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x0c, 0x6e, 0x65, 0x77, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, - 0x22, 0x44, 0x0a, 0x14, 0x52, 0x65, 0x6e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2c, 0x0a, 0x11, 0x75, 0x73, 0x65, 0x72, - 0x46, 0x72, 0x69, 0x65, 0x6e, 0x64, 0x6c, 0x79, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x11, 0x75, 0x73, 0x65, 0x72, 0x46, 0x72, 0x69, 0x65, 0x6e, 0x64, 0x6c, - 0x79, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x32, 0xe8, 0x04, 0x0a, 0x0e, 0x53, 0x65, 0x63, 0x72, 0x65, - 0x74, 0x73, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x12, 0x5c, 0x0a, 0x09, 0x47, 0x65, 0x74, - 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x12, 0x26, 0x2e, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, - 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2e, 0x47, 0x65, - 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x27, - 0x2e, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x70, - 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5c, 0x0a, 0x09, 0x53, 0x65, 0x74, 0x53, 0x65, - 0x63, 0x72, 0x65, 0x74, 0x12, 0x26, 0x2e, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x6d, 0x61, - 0x6e, 0x61, 0x67, 0x65, 0x72, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2e, 0x53, 0x65, 0x74, 0x53, - 0x65, 0x63, 0x72, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x27, 0x2e, 0x73, - 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x70, 0x6c, 0x75, - 0x67, 0x69, 0x6e, 0x2e, 0x53, 0x65, 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x65, 0x0a, 0x0c, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x53, - 0x65, 0x63, 0x72, 0x65, 0x74, 0x12, 0x29, 0x2e, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x6d, - 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2e, 0x44, 0x65, 0x6c, - 0x65, 0x74, 0x65, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x2a, 0x2e, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, - 0x72, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x53, 0x65, - 0x63, 0x72, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x62, 0x0a, 0x0b, - 0x4c, 0x69, 0x73, 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x12, 0x28, 0x2e, 0x73, 0x65, - 0x63, 0x72, 0x65, 0x74, 0x73, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x70, 0x6c, 0x75, 0x67, - 0x69, 0x6e, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x29, 0x2e, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x6d, - 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2e, 0x4c, 0x69, 0x73, - 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x65, 0x0a, 0x0c, 0x52, 0x65, 0x6e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, - 0x12, 0x29, 0x2e, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, - 0x72, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2e, 0x52, 0x65, 0x6e, 0x61, 0x6d, 0x65, 0x53, 0x65, - 0x63, 0x72, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x73, 0x65, - 0x63, 0x72, 0x65, 0x74, 0x73, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x70, 0x6c, 0x75, 0x67, - 0x69, 0x6e, 0x2e, 0x52, 0x65, 0x6e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x68, 0x0a, 0x0d, 0x47, 0x65, 0x74, 0x41, 0x6c, - 0x6c, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x12, 0x2a, 0x2e, 0x73, 0x65, 0x63, 0x72, 0x65, - 0x74, 0x73, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2e, - 0x47, 0x65, 0x74, 0x41, 0x6c, 0x6c, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x6d, 0x61, - 0x6e, 0x61, 0x67, 0x65, 0x72, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2e, 0x47, 0x65, 0x74, 0x41, - 0x6c, 0x6c, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x42, 0x19, 0x5a, 0x17, 0x2e, 0x2f, 0x3b, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x6d, - 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x62, 0x06, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x33, -}) - -var ( - file_secretsmanager_proto_rawDescOnce sync.Once - file_secretsmanager_proto_rawDescData []byte -) - -func file_secretsmanager_proto_rawDescGZIP() []byte { - file_secretsmanager_proto_rawDescOnce.Do(func() { - file_secretsmanager_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_secretsmanager_proto_rawDesc), len(file_secretsmanager_proto_rawDesc))) - }) - return file_secretsmanager_proto_rawDescData -} - -var file_secretsmanager_proto_msgTypes = make([]protoimpl.MessageInfo, 14) -var file_secretsmanager_proto_goTypes = []any{ - (*Key)(nil), // 0: secretsmanagerplugin.Key - (*Item)(nil), // 1: secretsmanagerplugin.Item - (*GetSecretRequest)(nil), // 2: secretsmanagerplugin.GetSecretRequest - (*GetSecretResponse)(nil), // 3: secretsmanagerplugin.GetSecretResponse - (*SetSecretRequest)(nil), // 4: secretsmanagerplugin.SetSecretRequest - (*SetSecretResponse)(nil), // 5: secretsmanagerplugin.SetSecretResponse - (*DeleteSecretRequest)(nil), // 6: secretsmanagerplugin.DeleteSecretRequest - (*DeleteSecretResponse)(nil), // 7: secretsmanagerplugin.DeleteSecretResponse - (*ListSecretsRequest)(nil), // 8: secretsmanagerplugin.ListSecretsRequest - (*ListSecretsResponse)(nil), // 9: secretsmanagerplugin.ListSecretsResponse - (*GetAllSecretsRequest)(nil), // 10: secretsmanagerplugin.GetAllSecretsRequest - (*GetAllSecretsResponse)(nil), // 11: secretsmanagerplugin.GetAllSecretsResponse - (*RenameSecretRequest)(nil), // 12: secretsmanagerplugin.RenameSecretRequest - (*RenameSecretResponse)(nil), // 13: secretsmanagerplugin.RenameSecretResponse -} -var file_secretsmanager_proto_depIdxs = []int32{ - 0, // 0: secretsmanagerplugin.Item.key:type_name -> secretsmanagerplugin.Key - 0, // 1: secretsmanagerplugin.GetSecretRequest.keyDescriptor:type_name -> secretsmanagerplugin.Key - 0, // 2: secretsmanagerplugin.SetSecretRequest.keyDescriptor:type_name -> secretsmanagerplugin.Key - 0, // 3: secretsmanagerplugin.DeleteSecretRequest.keyDescriptor:type_name -> secretsmanagerplugin.Key - 0, // 4: secretsmanagerplugin.ListSecretsRequest.keyDescriptor:type_name -> secretsmanagerplugin.Key - 0, // 5: secretsmanagerplugin.ListSecretsResponse.keys:type_name -> secretsmanagerplugin.Key - 1, // 6: secretsmanagerplugin.GetAllSecretsResponse.items:type_name -> secretsmanagerplugin.Item - 0, // 7: secretsmanagerplugin.RenameSecretRequest.keyDescriptor:type_name -> secretsmanagerplugin.Key - 2, // 8: secretsmanagerplugin.SecretsManager.GetSecret:input_type -> secretsmanagerplugin.GetSecretRequest - 4, // 9: secretsmanagerplugin.SecretsManager.SetSecret:input_type -> secretsmanagerplugin.SetSecretRequest - 6, // 10: secretsmanagerplugin.SecretsManager.DeleteSecret:input_type -> secretsmanagerplugin.DeleteSecretRequest - 8, // 11: secretsmanagerplugin.SecretsManager.ListSecrets:input_type -> secretsmanagerplugin.ListSecretsRequest - 12, // 12: secretsmanagerplugin.SecretsManager.RenameSecret:input_type -> secretsmanagerplugin.RenameSecretRequest - 10, // 13: secretsmanagerplugin.SecretsManager.GetAllSecrets:input_type -> secretsmanagerplugin.GetAllSecretsRequest - 3, // 14: secretsmanagerplugin.SecretsManager.GetSecret:output_type -> secretsmanagerplugin.GetSecretResponse - 5, // 15: secretsmanagerplugin.SecretsManager.SetSecret:output_type -> secretsmanagerplugin.SetSecretResponse - 7, // 16: secretsmanagerplugin.SecretsManager.DeleteSecret:output_type -> secretsmanagerplugin.DeleteSecretResponse - 9, // 17: secretsmanagerplugin.SecretsManager.ListSecrets:output_type -> secretsmanagerplugin.ListSecretsResponse - 13, // 18: secretsmanagerplugin.SecretsManager.RenameSecret:output_type -> secretsmanagerplugin.RenameSecretResponse - 11, // 19: secretsmanagerplugin.SecretsManager.GetAllSecrets:output_type -> secretsmanagerplugin.GetAllSecretsResponse - 14, // [14:20] is the sub-list for method output_type - 8, // [8:14] is the sub-list for method input_type - 8, // [8:8] is the sub-list for extension type_name - 8, // [8:8] is the sub-list for extension extendee - 0, // [0:8] is the sub-list for field type_name -} - -func init() { file_secretsmanager_proto_init() } -func file_secretsmanager_proto_init() { - if File_secretsmanager_proto != nil { - return - } - type x struct{} - out := protoimpl.TypeBuilder{ - File: protoimpl.DescBuilder{ - GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: unsafe.Slice(unsafe.StringData(file_secretsmanager_proto_rawDesc), len(file_secretsmanager_proto_rawDesc)), - NumEnums: 0, - NumMessages: 14, - NumExtensions: 0, - NumServices: 1, - }, - GoTypes: file_secretsmanager_proto_goTypes, - DependencyIndexes: file_secretsmanager_proto_depIdxs, - MessageInfos: file_secretsmanager_proto_msgTypes, - }.Build() - File_secretsmanager_proto = out.File - file_secretsmanager_proto_goTypes = nil - file_secretsmanager_proto_depIdxs = nil -} diff --git a/pkg/plugins/backendplugin/secretsmanagerplugin/secretsmanager.proto b/pkg/plugins/backendplugin/secretsmanagerplugin/secretsmanager.proto deleted file mode 100644 index d967a05fa7b..00000000000 --- a/pkg/plugins/backendplugin/secretsmanagerplugin/secretsmanager.proto +++ /dev/null @@ -1,78 +0,0 @@ -syntax = "proto3"; -package secretsmanagerplugin; - -option go_package = "./;secretsmanagerplugin"; - -message Key { - int64 orgId = 1; - string namespace = 2; - string type = 3; -} - -message Item { - Key key = 1; - string value = 2; -} - -message GetSecretRequest { - Key keyDescriptor = 1; -} - -message GetSecretResponse { - string userFriendlyError = 1; - string decryptedValue = 2; - bool exists = 3; -} - -message SetSecretRequest { - Key keyDescriptor = 1; - string value = 2; -} - -message SetSecretResponse { - string userFriendlyError = 1; -} - -message DeleteSecretRequest { - Key keyDescriptor = 1; -} - -message DeleteSecretResponse { - string userFriendlyError = 1; -} - -message ListSecretsRequest { - Key keyDescriptor = 1; - bool allOrganizations = 2; -} - -message ListSecretsResponse { - string userFriendlyError = 1; - repeated Key keys = 2; -} - -message GetAllSecretsRequest { -} - -message GetAllSecretsResponse { - string userFriendlyError = 1; - repeated Item items = 2; -} - -message RenameSecretRequest { - Key keyDescriptor = 1; - string newNamespace = 2; -} - -message RenameSecretResponse { - string userFriendlyError = 1; -} - -service SecretsManager { - rpc GetSecret(GetSecretRequest) returns (GetSecretResponse); - rpc SetSecret(SetSecretRequest) returns (SetSecretResponse); - rpc DeleteSecret(DeleteSecretRequest) returns (DeleteSecretResponse); - rpc ListSecrets(ListSecretsRequest) returns (ListSecretsResponse); - rpc RenameSecret(RenameSecretRequest) returns (RenameSecretResponse); - rpc GetAllSecrets(GetAllSecretsRequest) returns (GetAllSecretsResponse); -} diff --git a/pkg/plugins/backendplugin/secretsmanagerplugin/secretsmanager_grcp_plugin.go b/pkg/plugins/backendplugin/secretsmanagerplugin/secretsmanager_grcp_plugin.go deleted file mode 100644 index ac65764e928..00000000000 --- a/pkg/plugins/backendplugin/secretsmanagerplugin/secretsmanager_grcp_plugin.go +++ /dev/null @@ -1,61 +0,0 @@ -package secretsmanagerplugin - -import ( - "context" - - "github.com/hashicorp/go-plugin" - "google.golang.org/grpc" -) - -type SecretsManagerPlugin interface { - SecretsManagerClient -} - -type SecretsManagerGRPCPlugin struct { - plugin.NetRPCUnsupportedPlugin -} - -func (p *SecretsManagerGRPCPlugin) GRPCServer(broker *plugin.GRPCBroker, s *grpc.Server) error { - return nil -} - -func (p *SecretsManagerGRPCPlugin) GRPCClient(ctx context.Context, broker *plugin.GRPCBroker, c *grpc.ClientConn) (any, error) { - return &SecretsManagerGRPCClient{NewSecretsManagerClient(c)}, nil -} - -type SecretsManagerGRPCClient struct { - SecretsManagerClient -} - -// Get an item from the store -func (sm *SecretsManagerGRPCClient) GetSecret(ctx context.Context, req *GetSecretRequest, opts ...grpc.CallOption) (*GetSecretResponse, error) { - return sm.SecretsManagerClient.GetSecret(ctx, req) -} - -// Set an item in the store -func (sm *SecretsManagerGRPCClient) SetSecret(ctx context.Context, req *SetSecretRequest, opts ...grpc.CallOption) (*SetSecretResponse, error) { - return sm.SecretsManagerClient.SetSecret(ctx, req) -} - -// Del deletes an item from the store. -func (sm *SecretsManagerGRPCClient) DeleteSecret(ctx context.Context, req *DeleteSecretRequest, opts ...grpc.CallOption) (*DeleteSecretResponse, error) { - return sm.SecretsManagerClient.DeleteSecret(ctx, req) -} - -// Keys get all keys for a given namespace. -func (sm *SecretsManagerGRPCClient) ListSecrets(ctx context.Context, req *ListSecretsRequest, opts ...grpc.CallOption) (*ListSecretsResponse, error) { - return sm.SecretsManagerClient.ListSecrets(ctx, req) -} - -// Rename an item in the store -func (sm *SecretsManagerGRPCClient) RenameSecret(ctx context.Context, req *RenameSecretRequest, opts ...grpc.CallOption) (*RenameSecretResponse, error) { - return sm.SecretsManagerClient.RenameSecret(ctx, req) -} - -// Get all items from the store -func (sm *SecretsManagerGRPCClient) GetAllSecrets(ctx context.Context, req *GetAllSecretsRequest, opts ...grpc.CallOption) (*GetAllSecretsResponse, error) { - return sm.SecretsManagerClient.GetAllSecrets(ctx, req) -} - -var _ SecretsManagerClient = &SecretsManagerGRPCClient{} -var _ plugin.GRPCPlugin = &SecretsManagerGRPCPlugin{} diff --git a/pkg/plugins/backendplugin/secretsmanagerplugin/secretsmanager_grpc.pb.go b/pkg/plugins/backendplugin/secretsmanagerplugin/secretsmanager_grpc.pb.go deleted file mode 100644 index 3f23d1715fe..00000000000 --- a/pkg/plugins/backendplugin/secretsmanagerplugin/secretsmanager_grpc.pb.go +++ /dev/null @@ -1,300 +0,0 @@ -// Code generated by protoc-gen-go-grpc. DO NOT EDIT. -// versions: -// - protoc-gen-go-grpc v1.4.0 -// - protoc (unknown) -// source: secretsmanager.proto - -package secretsmanagerplugin - -import ( - context "context" - grpc "google.golang.org/grpc" - codes "google.golang.org/grpc/codes" - status "google.golang.org/grpc/status" -) - -// This is a compile-time assertion to ensure that this generated file -// is compatible with the grpc package it is being compiled against. -// Requires gRPC-Go v1.62.0 or later. -const _ = grpc.SupportPackageIsVersion8 - -const ( - SecretsManager_GetSecret_FullMethodName = "/secretsmanagerplugin.SecretsManager/GetSecret" - SecretsManager_SetSecret_FullMethodName = "/secretsmanagerplugin.SecretsManager/SetSecret" - SecretsManager_DeleteSecret_FullMethodName = "/secretsmanagerplugin.SecretsManager/DeleteSecret" - SecretsManager_ListSecrets_FullMethodName = "/secretsmanagerplugin.SecretsManager/ListSecrets" - SecretsManager_RenameSecret_FullMethodName = "/secretsmanagerplugin.SecretsManager/RenameSecret" - SecretsManager_GetAllSecrets_FullMethodName = "/secretsmanagerplugin.SecretsManager/GetAllSecrets" -) - -// SecretsManagerClient is the client API for SecretsManager service. -// -// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. -type SecretsManagerClient interface { - GetSecret(ctx context.Context, in *GetSecretRequest, opts ...grpc.CallOption) (*GetSecretResponse, error) - SetSecret(ctx context.Context, in *SetSecretRequest, opts ...grpc.CallOption) (*SetSecretResponse, error) - DeleteSecret(ctx context.Context, in *DeleteSecretRequest, opts ...grpc.CallOption) (*DeleteSecretResponse, error) - ListSecrets(ctx context.Context, in *ListSecretsRequest, opts ...grpc.CallOption) (*ListSecretsResponse, error) - RenameSecret(ctx context.Context, in *RenameSecretRequest, opts ...grpc.CallOption) (*RenameSecretResponse, error) - GetAllSecrets(ctx context.Context, in *GetAllSecretsRequest, opts ...grpc.CallOption) (*GetAllSecretsResponse, error) -} - -type secretsManagerClient struct { - cc grpc.ClientConnInterface -} - -func NewSecretsManagerClient(cc grpc.ClientConnInterface) SecretsManagerClient { - return &secretsManagerClient{cc} -} - -func (c *secretsManagerClient) GetSecret(ctx context.Context, in *GetSecretRequest, opts ...grpc.CallOption) (*GetSecretResponse, error) { - cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) - out := new(GetSecretResponse) - err := c.cc.Invoke(ctx, SecretsManager_GetSecret_FullMethodName, in, out, cOpts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *secretsManagerClient) SetSecret(ctx context.Context, in *SetSecretRequest, opts ...grpc.CallOption) (*SetSecretResponse, error) { - cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) - out := new(SetSecretResponse) - err := c.cc.Invoke(ctx, SecretsManager_SetSecret_FullMethodName, in, out, cOpts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *secretsManagerClient) DeleteSecret(ctx context.Context, in *DeleteSecretRequest, opts ...grpc.CallOption) (*DeleteSecretResponse, error) { - cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) - out := new(DeleteSecretResponse) - err := c.cc.Invoke(ctx, SecretsManager_DeleteSecret_FullMethodName, in, out, cOpts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *secretsManagerClient) ListSecrets(ctx context.Context, in *ListSecretsRequest, opts ...grpc.CallOption) (*ListSecretsResponse, error) { - cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) - out := new(ListSecretsResponse) - err := c.cc.Invoke(ctx, SecretsManager_ListSecrets_FullMethodName, in, out, cOpts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *secretsManagerClient) RenameSecret(ctx context.Context, in *RenameSecretRequest, opts ...grpc.CallOption) (*RenameSecretResponse, error) { - cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) - out := new(RenameSecretResponse) - err := c.cc.Invoke(ctx, SecretsManager_RenameSecret_FullMethodName, in, out, cOpts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *secretsManagerClient) GetAllSecrets(ctx context.Context, in *GetAllSecretsRequest, opts ...grpc.CallOption) (*GetAllSecretsResponse, error) { - cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) - out := new(GetAllSecretsResponse) - err := c.cc.Invoke(ctx, SecretsManager_GetAllSecrets_FullMethodName, in, out, cOpts...) - if err != nil { - return nil, err - } - return out, nil -} - -// SecretsManagerServer is the server API for SecretsManager service. -// All implementations must embed UnimplementedSecretsManagerServer -// for forward compatibility -type SecretsManagerServer interface { - GetSecret(context.Context, *GetSecretRequest) (*GetSecretResponse, error) - SetSecret(context.Context, *SetSecretRequest) (*SetSecretResponse, error) - DeleteSecret(context.Context, *DeleteSecretRequest) (*DeleteSecretResponse, error) - ListSecrets(context.Context, *ListSecretsRequest) (*ListSecretsResponse, error) - RenameSecret(context.Context, *RenameSecretRequest) (*RenameSecretResponse, error) - GetAllSecrets(context.Context, *GetAllSecretsRequest) (*GetAllSecretsResponse, error) - mustEmbedUnimplementedSecretsManagerServer() -} - -// UnimplementedSecretsManagerServer must be embedded to have forward compatible implementations. -type UnimplementedSecretsManagerServer struct { -} - -func (UnimplementedSecretsManagerServer) GetSecret(context.Context, *GetSecretRequest) (*GetSecretResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method GetSecret not implemented") -} -func (UnimplementedSecretsManagerServer) SetSecret(context.Context, *SetSecretRequest) (*SetSecretResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method SetSecret not implemented") -} -func (UnimplementedSecretsManagerServer) DeleteSecret(context.Context, *DeleteSecretRequest) (*DeleteSecretResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method DeleteSecret not implemented") -} -func (UnimplementedSecretsManagerServer) ListSecrets(context.Context, *ListSecretsRequest) (*ListSecretsResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method ListSecrets not implemented") -} -func (UnimplementedSecretsManagerServer) RenameSecret(context.Context, *RenameSecretRequest) (*RenameSecretResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method RenameSecret not implemented") -} -func (UnimplementedSecretsManagerServer) GetAllSecrets(context.Context, *GetAllSecretsRequest) (*GetAllSecretsResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method GetAllSecrets not implemented") -} -func (UnimplementedSecretsManagerServer) mustEmbedUnimplementedSecretsManagerServer() {} - -// UnsafeSecretsManagerServer may be embedded to opt out of forward compatibility for this service. -// Use of this interface is not recommended, as added methods to SecretsManagerServer will -// result in compilation errors. -type UnsafeSecretsManagerServer interface { - mustEmbedUnimplementedSecretsManagerServer() -} - -func RegisterSecretsManagerServer(s grpc.ServiceRegistrar, srv SecretsManagerServer) { - s.RegisterService(&SecretsManager_ServiceDesc, srv) -} - -func _SecretsManager_GetSecret_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(GetSecretRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(SecretsManagerServer).GetSecret(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: SecretsManager_GetSecret_FullMethodName, - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(SecretsManagerServer).GetSecret(ctx, req.(*GetSecretRequest)) - } - return interceptor(ctx, in, info, handler) -} - -func _SecretsManager_SetSecret_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(SetSecretRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(SecretsManagerServer).SetSecret(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: SecretsManager_SetSecret_FullMethodName, - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(SecretsManagerServer).SetSecret(ctx, req.(*SetSecretRequest)) - } - return interceptor(ctx, in, info, handler) -} - -func _SecretsManager_DeleteSecret_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(DeleteSecretRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(SecretsManagerServer).DeleteSecret(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: SecretsManager_DeleteSecret_FullMethodName, - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(SecretsManagerServer).DeleteSecret(ctx, req.(*DeleteSecretRequest)) - } - return interceptor(ctx, in, info, handler) -} - -func _SecretsManager_ListSecrets_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(ListSecretsRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(SecretsManagerServer).ListSecrets(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: SecretsManager_ListSecrets_FullMethodName, - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(SecretsManagerServer).ListSecrets(ctx, req.(*ListSecretsRequest)) - } - return interceptor(ctx, in, info, handler) -} - -func _SecretsManager_RenameSecret_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(RenameSecretRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(SecretsManagerServer).RenameSecret(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: SecretsManager_RenameSecret_FullMethodName, - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(SecretsManagerServer).RenameSecret(ctx, req.(*RenameSecretRequest)) - } - return interceptor(ctx, in, info, handler) -} - -func _SecretsManager_GetAllSecrets_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(GetAllSecretsRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(SecretsManagerServer).GetAllSecrets(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: SecretsManager_GetAllSecrets_FullMethodName, - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(SecretsManagerServer).GetAllSecrets(ctx, req.(*GetAllSecretsRequest)) - } - return interceptor(ctx, in, info, handler) -} - -// SecretsManager_ServiceDesc is the grpc.ServiceDesc for SecretsManager service. -// It's only intended for direct use with grpc.RegisterService, -// and not to be introspected or modified (even as a copy) -var SecretsManager_ServiceDesc = grpc.ServiceDesc{ - ServiceName: "secretsmanagerplugin.SecretsManager", - HandlerType: (*SecretsManagerServer)(nil), - Methods: []grpc.MethodDesc{ - { - MethodName: "GetSecret", - Handler: _SecretsManager_GetSecret_Handler, - }, - { - MethodName: "SetSecret", - Handler: _SecretsManager_SetSecret_Handler, - }, - { - MethodName: "DeleteSecret", - Handler: _SecretsManager_DeleteSecret_Handler, - }, - { - MethodName: "ListSecrets", - Handler: _SecretsManager_ListSecrets_Handler, - }, - { - MethodName: "RenameSecret", - Handler: _SecretsManager_RenameSecret_Handler, - }, - { - MethodName: "GetAllSecrets", - Handler: _SecretsManager_GetAllSecrets_Handler, - }, - }, - Streams: []grpc.StreamDesc{}, - Metadata: "secretsmanager.proto", -} diff --git a/pkg/plugins/ifaces.go b/pkg/plugins/ifaces.go index 368537db337..fd227184de5 100644 --- a/pkg/plugins/ifaces.go +++ b/pkg/plugins/ifaces.go @@ -98,11 +98,6 @@ type BackendFactoryProvider interface { BackendFactory(ctx context.Context, p *Plugin) backendplugin.PluginFactoryFunc } -type SecretsPluginManager interface { - // SecretsManager returns a secretsmanager plugin - SecretsManager(ctx context.Context) *Plugin -} - type StaticRouteResolver interface { Routes(ctx context.Context) []*StaticRoute } diff --git a/pkg/plugins/manager/loader/loader_test.go b/pkg/plugins/manager/loader/loader_test.go index 80526b94dd5..23cd3be818e 100644 --- a/pkg/plugins/manager/loader/loader_test.go +++ b/pkg/plugins/manager/loader/loader_test.go @@ -90,6 +90,7 @@ func TestLoader_Load(t *testing.T) { }, Links: []plugins.InfoLink{ {Name: "Raise issue", URL: "https://github.com/grafana/grafana/issues/new"}, + {Name: "Documentation", URL: "https://grafana.com/docs/grafana/latest/datasources/aws-cloudwatch/"}, }, }, Includes: []*plugins.Includes{ diff --git a/pkg/plugins/manager/pipeline/initialization/steps_test.go b/pkg/plugins/manager/pipeline/initialization/steps_test.go index da00444e93e..4892784b0ee 100644 --- a/pkg/plugins/manager/pipeline/initialization/steps_test.go +++ b/pkg/plugins/manager/pipeline/initialization/steps_test.go @@ -64,30 +64,6 @@ func TestInitializer_Initialize(t *testing.T) { require.NotNil(t, c) }) - t.Run("secretsmanager", func(t *testing.T) { - p := &plugins.Plugin{ - JSONData: plugins.JSONData{ - ID: "test", - Type: plugins.TypeSecretsManager, - Dependencies: plugins.Dependencies{ - GrafanaVersion: ">=8.x", - }, - Backend: true, - }, - Class: plugins.ClassExternal, - } - - stepFunc := BackendClientInitStep(&fakeEnvVarsProvider{}, &fakeBackendProvider{plugin: p}, fakes.InitializeNoopTracerForTest()) - - var err error - p, err = stepFunc(context.Background(), p) - require.NoError(t, err) - - c, exists := p.Client() - require.True(t, exists) - require.NotNil(t, c) - }) - t.Run("non backend plugin app", func(t *testing.T) { p := &plugins.Plugin{ JSONData: plugins.JSONData{ diff --git a/pkg/plugins/plugins.go b/pkg/plugins/plugins.go index f815d07c6b1..cdbde14df77 100644 --- a/pkg/plugins/plugins.go +++ b/pkg/plugins/plugins.go @@ -17,7 +17,6 @@ import ( "github.com/grafana/grafana/pkg/plugins/auth" "github.com/grafana/grafana/pkg/plugins/backendplugin" "github.com/grafana/grafana/pkg/plugins/backendplugin/pluginextensionv2" - "github.com/grafana/grafana/pkg/plugins/backendplugin/secretsmanagerplugin" "github.com/grafana/grafana/pkg/plugins/log" "github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/util" @@ -58,10 +57,9 @@ type Plugin struct { ExternalService *auth.ExternalService - Renderer pluginextensionv2.RendererPlugin - SecretsManager secretsmanagerplugin.SecretsManagerPlugin - client backendplugin.Plugin - log log.Logger + Renderer pluginextensionv2.RendererPlugin + client backendplugin.Plugin + log log.Logger SkipHostEnvVars bool @@ -126,7 +124,7 @@ type JSONData struct { SDK bool `json:"sdk,omitempty"` MultiValueFilterOperators bool `json:"multiValueFilterOperators,omitempty"` - // Backend (Datasource + Renderer + SecretsManager) + // Backend (Datasource + Renderer) Executable string `json:"executable,omitempty"` // App Service Auth Registration @@ -442,10 +440,6 @@ func (p *Plugin) ExecutablePath() string { return p.executablePath("plugin_start") } - if p.IsSecretsManager() { - return p.executablePath("secrets_plugin_start") - } - return p.executablePath(p.Executable) } @@ -486,10 +480,6 @@ func (p *Plugin) IsRenderer() bool { return p.Type == TypeRenderer } -func (p *Plugin) IsSecretsManager() bool { - return p.Type == TypeSecretsManager -} - func (p *Plugin) IsApp() bool { return p.Type == TypeApp } @@ -519,22 +509,20 @@ var PluginTypes = []Type{ TypePanel, TypeApp, TypeRenderer, - TypeSecretsManager, } type Type string const ( - TypeDataSource Type = "datasource" - TypePanel Type = "panel" - TypeApp Type = "app" - TypeRenderer Type = "renderer" - TypeSecretsManager Type = "secretsmanager" + TypeDataSource Type = "datasource" + TypePanel Type = "panel" + TypeApp Type = "app" + TypeRenderer Type = "renderer" ) func (pt Type) IsValid() bool { switch pt { - case TypeDataSource, TypePanel, TypeApp, TypeRenderer, TypeSecretsManager: + case TypeDataSource, TypePanel, TypeApp, TypeRenderer: return true } return false diff --git a/pkg/registry/apis/query/client/plugin.go b/pkg/registry/apis/query/client/plugin.go index 434f3b049f9..7301f819be7 100644 --- a/pkg/registry/apis/query/client/plugin.go +++ b/pkg/registry/apis/query/client/plugin.go @@ -2,7 +2,6 @@ package client import ( "context" - "errors" "fmt" "net/http" "sync" @@ -13,9 +12,11 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" + "github.com/grafana/grafana/pkg/apimachinery/identity" query "github.com/grafana/grafana/pkg/apis/query/v0alpha1" "github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/registry/apis/query/clientapi" + "github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/datasources" "github.com/grafana/grafana/pkg/services/pluginsintegration/plugincontext" "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginstore" @@ -26,6 +27,7 @@ import ( type pluginClient struct { pluginClient plugins.Client pCtxProvider *plugincontext.Provider + ac accesscontrol.AccessControl } type pluginRegistry struct { @@ -43,10 +45,11 @@ var _ clientapi.QueryDataClient = (*pluginClient)(nil) var _ query.DataSourceApiServerRegistry = (*pluginRegistry)(nil) // NewQueryClientForPluginClient creates a client that delegates to the internal plugins.Client stack -func NewQueryClientForPluginClient(p plugins.Client, ctx *plugincontext.Provider) clientapi.QueryDataClient { +func NewQueryClientForPluginClient(p plugins.Client, ctx *plugincontext.Provider, accessControl accesscontrol.AccessControl) clientapi.QueryDataClient { return &pluginClient{ pluginClient: p, pCtxProvider: ctx, + ac: accessControl, } } @@ -59,6 +62,17 @@ func NewDataSourceRegistryFromStore(pluginStore pluginstore.Store, } } +func (d *pluginClient) CanQueryDataSource(ctx context.Context, uid string) (bool, error) { + user, err := identity.GetRequester(ctx) + if err != nil { + return false, err + } + + requiredScope := "datasources:uid:" + uid + evaluate := accesscontrol.EvalPermission(datasources.ActionQuery, requiredScope) + return d.ac.Evaluate(ctx, user, evaluate) +} + // ExecuteQueryData implements QueryHelper. func (d *pluginClient) QueryData(ctx context.Context, req data.QueryDataRequest) (*backend.QueryDataResponse, error) { queries, dsRef, err := data.ToDataSourceQueries(req) @@ -69,17 +83,23 @@ func (d *pluginClient) QueryData(ctx context.Context, req data.QueryDataRequest) return nil, fmt.Errorf("expected single datasource request") } + canQuery, err := d.CanQueryDataSource(ctx, dsRef.UID) + if err != nil { + return nil, err + } + + if !canQuery { + status := metav1.Status{ + Status: metav1.StatusFailure, + Code: http.StatusForbidden, + Message: "Access denied to the data source", + } + return nil, &apierrors.StatusError{ErrStatus: status} + } + // NOTE: this depends on uid unique across datasources settings, err := d.pCtxProvider.GetDataSourceInstanceSettings(ctx, dsRef.UID) if err != nil { - if errors.Is(err, datasources.ErrDataSourceNotFound) { - status := metav1.Status{ - Status: metav1.StatusFailure, - Code: http.StatusNotFound, - Message: "datasource not found", - } - return nil, &apierrors.StatusError{ErrStatus: status} - } return nil, err } diff --git a/pkg/registry/apis/query/register.go b/pkg/registry/apis/query/register.go index 44f15087832..83ca935a662 100644 --- a/pkg/registry/apis/query/register.go +++ b/pkg/registry/apis/query/register.go @@ -127,7 +127,7 @@ func RegisterAPIService(features featuremgmt.FeatureToggles, builder, err := NewQueryAPIBuilder( features, &CommonDataSourceClientSupplier{ - Client: client.NewQueryClientForPluginClient(pluginClient, pCtxProvider), + Client: client.NewQueryClientForPluginClient(pluginClient, pCtxProvider, accessControl), }, ar, client.NewDataSourceRegistryFromStore(pluginStore, dataSourcesService), diff --git a/pkg/registry/apis/secret/accesscontrol.go b/pkg/registry/apis/secret/accesscontrol.go new file mode 100644 index 00000000000..e93fe22d1cc --- /dev/null +++ b/pkg/registry/apis/secret/accesscontrol.go @@ -0,0 +1,125 @@ +package secret + +import ( + "github.com/grafana/grafana/pkg/services/accesscontrol" + "github.com/grafana/grafana/pkg/services/org" +) + +const ( + // SecureValues + ActionSecretSecureValuesCreate = "secret.securevalues:create" // CREATE. + ActionSecretSecureValuesWrite = "secret.securevalues:write" // UPDATE. + ActionSecretSecureValuesRead = "secret.securevalues:read" // GET + LIST. + ActionSecretSecureValuesDelete = "secret.securevalues:delete" // DELETE. + + // Keepers + ActionSecretKeepersCreate = "secret.keepers:create" // CREATE. + ActionSecretKeepersWrite = "secret.keepers:write" // UPDATE. + ActionSecretKeepersRead = "secret.keepers:read" // GET + LIST. + ActionSecretKeepersDelete = "secret.keepers:delete" // DELETE. +) + +var ( + ScopeProviderSecretSecureValues = accesscontrol.NewScopeProvider("secret.securevalues") + ScopeProviderSecretKeepers = accesscontrol.NewScopeProvider("secret.keepers") + + ScopeAllSecureValues = ScopeProviderSecretSecureValues.GetResourceAllScope() + ScopeAllKeepers = ScopeProviderSecretKeepers.GetResourceAllScope() +) + +func RegisterAccessControlRoles(service accesscontrol.Service) error { + // SecureValues + secureValuesReader := accesscontrol.RoleRegistration{ + Role: accesscontrol.RoleDTO{ + Name: "fixed:secret.securevalues:reader", + DisplayName: "Secrets Manager secure values reader", + Description: "Read and list secure values.", + Group: "Secrets Manager", + Permissions: []accesscontrol.Permission{ + { + Action: ActionSecretSecureValuesRead, + Scope: ScopeAllSecureValues, + }, + }, + }, + Grants: []string{string(org.RoleAdmin)}, + } + + secureValuesWriter := accesscontrol.RoleRegistration{ + Role: accesscontrol.RoleDTO{ + Name: "fixed:secret.securevalues:writer", + DisplayName: "Secrets Manager secure values writer", + Description: "Create, update and delete secure values.", + Group: "Secrets Manager", + Permissions: []accesscontrol.Permission{ + { + Action: ActionSecretSecureValuesCreate, + Scope: ScopeAllSecureValues, + }, + { + Action: ActionSecretSecureValuesRead, + Scope: ScopeAllSecureValues, + }, + { + Action: ActionSecretSecureValuesWrite, + Scope: ScopeAllSecureValues, + }, + { + Action: ActionSecretSecureValuesDelete, + Scope: ScopeAllSecureValues, + }, + }, + }, + Grants: []string{string(org.RoleAdmin)}, + } + + // Keepers + keepersReader := accesscontrol.RoleRegistration{ + Role: accesscontrol.RoleDTO{ + Name: "fixed:secret.keepers:reader", + DisplayName: "Secrets Manager keepers reader", + Description: "Read and list keepers.", + Group: "Secrets Manager", + Permissions: []accesscontrol.Permission{ + { + Action: ActionSecretKeepersRead, + Scope: ScopeAllKeepers, + }, + }, + }, + Grants: []string{string(org.RoleAdmin)}, + } + + keepersWriter := accesscontrol.RoleRegistration{ + Role: accesscontrol.RoleDTO{ + Name: "fixed:secret.keepers:writer", + DisplayName: "Secrets Manager keepers writer", + Description: "Create, update and delete keepers.", + Group: "Secrets Manager", + Permissions: []accesscontrol.Permission{ + { + Action: ActionSecretKeepersCreate, + Scope: ScopeAllKeepers, + }, + { + Action: ActionSecretKeepersRead, + Scope: ScopeAllKeepers, + }, + { + Action: ActionSecretKeepersWrite, + Scope: ScopeAllKeepers, + }, + { + Action: ActionSecretKeepersDelete, + Scope: ScopeAllKeepers, + }, + }, + }, + Grants: []string{string(org.RoleAdmin)}, + } + + return service.DeclareFixedRoles( + secureValuesReader, secureValuesWriter, + keepersReader, keepersWriter, + ) +} diff --git a/pkg/registry/apis/secret/contracts/keeper.go b/pkg/registry/apis/secret/contracts/keeper.go index 0f9ab5e62bb..edd996ed4e9 100644 --- a/pkg/registry/apis/secret/contracts/keeper.go +++ b/pkg/registry/apis/secret/contracts/keeper.go @@ -2,12 +2,18 @@ package contracts import ( "context" + "errors" secretv0alpha1 "github.com/grafana/grafana/pkg/apis/secret/v0alpha1" "github.com/grafana/grafana/pkg/registry/apis/secret/xkube" "k8s.io/apimachinery/pkg/apis/meta/internalversion" ) +var ( + ErrKeeperNotFound = errors.New("keeper not found") +) + +// KeeperMetadataStorage is the interface for wiring and dependency injection. type KeeperMetadataStorage interface { Create(ctx context.Context, keeper *secretv0alpha1.Keeper) (*secretv0alpha1.Keeper, error) Read(ctx context.Context, namespace xkube.Namespace, name string) (*secretv0alpha1.Keeper, error) diff --git a/pkg/registry/apis/secret/contracts/secure_value.go b/pkg/registry/apis/secret/contracts/secure_value.go index 5a848731872..a3109e1d44f 100644 --- a/pkg/registry/apis/secret/contracts/secure_value.go +++ b/pkg/registry/apis/secret/contracts/secure_value.go @@ -2,12 +2,18 @@ package contracts import ( "context" + "errors" secretv0alpha1 "github.com/grafana/grafana/pkg/apis/secret/v0alpha1" "github.com/grafana/grafana/pkg/registry/apis/secret/xkube" "k8s.io/apimachinery/pkg/apis/meta/internalversion" ) +var ( + ErrSecureValueNotFound = errors.New("secure value not found") +) + +// SecureValueMetadataStorage is the interface for wiring and dependency injection. type SecureValueMetadataStorage interface { Create(ctx context.Context, sv *secretv0alpha1.SecureValue) (*secretv0alpha1.SecureValue, error) Read(ctx context.Context, namespace xkube.Namespace, name string) (*secretv0alpha1.SecureValue, error) diff --git a/pkg/registry/apis/secret/register.go b/pkg/registry/apis/secret/register.go index 457df144c48..9b2ccfe85f6 100644 --- a/pkg/registry/apis/secret/register.go +++ b/pkg/registry/apis/secret/register.go @@ -21,6 +21,7 @@ import ( "github.com/grafana/grafana/pkg/registry/apis/secret/contracts" "github.com/grafana/grafana/pkg/registry/apis/secret/reststorage" "github.com/grafana/grafana/pkg/services/accesscontrol" + authsvc "github.com/grafana/grafana/pkg/services/apiserver/auth/authorizer" "github.com/grafana/grafana/pkg/services/apiserver/builder" "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/setting" @@ -68,6 +69,10 @@ func RegisterAPIService( return nil, nil } + if err := RegisterAccessControlRoles(accessControlService); err != nil { + return nil, fmt.Errorf("register secret access control roles: %w", err) + } + builder := NewSecretAPIBuilder( tracer, secureValueMetadataStorage, @@ -148,7 +153,7 @@ func (b *SecretAPIBuilder) GetOpenAPIDefinitions() common.GetOpenAPIDefinitions // For Secrets, this is not the case, but if we want to make it so, we need to update this ResourceAuthorizer to check the containing folder. // If we ever want to do that, get guidance from IAM first as well. func (b *SecretAPIBuilder) GetAuthorizer() authorizer.Authorizer { - return nil + return authsvc.NewResourceAuthorizer(b.accessClient) } // Register additional routes with the server. diff --git a/pkg/registry/apis/secret/reststorage/keeper.go b/pkg/registry/apis/secret/reststorage/keeper.go deleted file mode 100644 index 7803d28d1ad..00000000000 --- a/pkg/registry/apis/secret/reststorage/keeper.go +++ /dev/null @@ -1,128 +0,0 @@ -package reststorage - -import ( - "context" - "fmt" - - "k8s.io/apimachinery/pkg/apis/meta/internalversion" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/util/validation/field" - "k8s.io/apiserver/pkg/admission" - "k8s.io/apiserver/pkg/endpoints/request" - "k8s.io/apiserver/pkg/registry/rest" - - "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" -) - -var ( - _ rest.Scoper = (*KeeperRest)(nil) - _ rest.SingularNameProvider = (*KeeperRest)(nil) - _ rest.Getter = (*KeeperRest)(nil) - _ rest.Lister = (*KeeperRest)(nil) - _ rest.Storage = (*KeeperRest)(nil) - _ rest.Creater = (*KeeperRest)(nil) - _ rest.Updater = (*KeeperRest)(nil) - _ rest.GracefulDeleter = (*KeeperRest)(nil) -) - -// KeeperRest is an ddimplementation of CRUDL operations on a `keeper` backed by TODO. -type KeeperRest struct { - storage contracts.KeeperMetadataStorage - resource utils.ResourceInfo - tableConverter rest.TableConvertor -} - -// NewKeeperRest is a returns a constructed `*KeeperRest`. -func NewKeeperRest(storage contracts.KeeperMetadataStorage, resource utils.ResourceInfo) *KeeperRest { - return &KeeperRest{storage, resource, resource.TableConverter()} -} - -// New returns an empty `*Keeper` that is used by the `Create` method. -func (s *KeeperRest) New() runtime.Object { - return s.resource.NewFunc() -} - -// Destroy is called when? [TODO] -func (s *KeeperRest) Destroy() {} - -// NamespaceScoped returns `true` because the storage is namespaced (== org). -func (s *KeeperRest) NamespaceScoped() bool { - return true -} - -// GetSingularName is used by `kubectl` discovery to have singular name representation of resources. -func (s *KeeperRest) GetSingularName() string { - return s.resource.GetSingularName() -} - -// NewList returns an empty `*KeeperList` that is used by the `List` method. -func (s *KeeperRest) NewList() runtime.Object { - return s.resource.NewListFunc() -} - -// ConvertToTable is used by Kubernetes and converts objects to `metav1.Table`. -func (s *KeeperRest) ConvertToTable(ctx context.Context, object runtime.Object, tableOptions runtime.Object) (*metav1.Table, error) { - return s.tableConverter.ConvertToTable(ctx, object, tableOptions) -} - -// List calls the inner `store` (persistence) and returns a list of `Keepers` within a `namespace` filtered by the `options`. -func (s *KeeperRest) List(ctx context.Context, options *internalversion.ListOptions) (runtime.Object, error) { - _, ok := request.NamespaceFrom(ctx) - if !ok { - return nil, fmt.Errorf("missing namespace") - } - - return &secretv0alpha1.KeeperList{Items: make([]secretv0alpha1.Keeper, 0)}, nil -} - -// Get calls the inner `store` (persistence) and returns a `Keeper` by `name`. -func (s *KeeperRest) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) { - _, ok := request.NamespaceFrom(ctx) - if !ok { - return nil, fmt.Errorf("missing namespace") - } - - return nil, s.resource.NewNotFound(name) -} - -// Create a new `Keeper`. Does some validation and allows empty `name` (generated). -func (s *KeeperRest) Create( - ctx context.Context, - obj runtime.Object, - createValidation rest.ValidateObjectFunc, - options *metav1.CreateOptions, -) (runtime.Object, error) { - return nil, nil -} - -// Update a `Keeper`'s `value`. The second return parameter indicates whether the resource was newly created. -func (s *KeeperRest) Update( - ctx context.Context, - name string, - objInfo rest.UpdatedObjectInfo, - createValidation rest.ValidateObjectFunc, - updateValidation rest.ValidateObjectUpdateFunc, - forceAllowCreate bool, - options *metav1.UpdateOptions, -) (runtime.Object, bool, error) { - return nil, false, nil -} - -// Delete calls the inner `store` (persistence) in order to delete the `Keeper`. -// The second return parameter `bool` indicates whether the delete was intant or not. It always is for `Keepers`. -func (s *KeeperRest) Delete(ctx context.Context, name string, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions) (runtime.Object, bool, error) { - _, ok := request.NamespaceFrom(ctx) - if !ok { - return nil, false, fmt.Errorf("missing namespace") - } - - return nil, true, nil -} - -// ValidateKeeper does basic spec validation of a keeper. -func ValidateKeeper(keeper *secretv0alpha1.Keeper, operation admission.Operation) field.ErrorList { - return nil -} diff --git a/pkg/registry/apis/secret/reststorage/keeper_rest.go b/pkg/registry/apis/secret/reststorage/keeper_rest.go new file mode 100644 index 00000000000..77926723e78 --- /dev/null +++ b/pkg/registry/apis/secret/reststorage/keeper_rest.go @@ -0,0 +1,356 @@ +package reststorage + +import ( + "context" + "errors" + "fmt" + "strings" + + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/apis/meta/internalversion" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/apiserver/pkg/admission" + "k8s.io/apiserver/pkg/endpoints/request" + "k8s.io/apiserver/pkg/registry/rest" + + "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/xkube" +) + +var ( + _ rest.Scoper = (*KeeperRest)(nil) + _ rest.SingularNameProvider = (*KeeperRest)(nil) + _ rest.Getter = (*KeeperRest)(nil) + _ rest.Lister = (*KeeperRest)(nil) + _ rest.Storage = (*KeeperRest)(nil) + _ rest.Creater = (*KeeperRest)(nil) + _ rest.Updater = (*KeeperRest)(nil) + _ rest.GracefulDeleter = (*KeeperRest)(nil) +) + +// KeeperRest is an implementation of CRUDL operations on a `keeper` backed by TODO. +type KeeperRest struct { + storage contracts.KeeperMetadataStorage + resource utils.ResourceInfo + tableConverter rest.TableConvertor +} + +// NewKeeperRest is a returns a constructed `*KeeperRest`. +func NewKeeperRest(storage contracts.KeeperMetadataStorage, resource utils.ResourceInfo) *KeeperRest { + return &KeeperRest{storage, resource, resource.TableConverter()} +} + +// New returns an empty `*Keeper` that is used by the `Create` method. +func (s *KeeperRest) New() runtime.Object { + return s.resource.NewFunc() +} + +// Destroy is called when? [TODO] +func (s *KeeperRest) Destroy() {} + +// NamespaceScoped returns `true` because the storage is namespaced (== org). +func (s *KeeperRest) NamespaceScoped() bool { + return true +} + +// GetSingularName is used by `kubectl` discovery to have singular name representation of resources. +func (s *KeeperRest) GetSingularName() string { + return s.resource.GetSingularName() +} + +// NewList returns an empty `*KeeperList` that is used by the `List` method. +func (s *KeeperRest) NewList() runtime.Object { + return s.resource.NewListFunc() +} + +// ConvertToTable is used by Kubernetes and converts objects to `metav1.Table`. +func (s *KeeperRest) ConvertToTable(ctx context.Context, object runtime.Object, tableOptions runtime.Object) (*metav1.Table, error) { + return s.tableConverter.ConvertToTable(ctx, object, tableOptions) +} + +// List calls the inner `store` (persistence) and returns a list of `Keepers` within a `namespace` filtered by the `options`. +func (s *KeeperRest) List(ctx context.Context, options *internalversion.ListOptions) (runtime.Object, error) { + namespace, ok := request.NamespaceFrom(ctx) + if !ok { + return nil, fmt.Errorf("missing namespace") + } + + keepersList, err := s.storage.List(ctx, xkube.Namespace(namespace), options) + if err != nil { + return nil, fmt.Errorf("failed to list keepers: %w", err) + } + + return keepersList, nil +} + +// Get calls the inner `store` (persistence) and returns a `Keeper` by `name`. +func (s *KeeperRest) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) { + namespace, ok := request.NamespaceFrom(ctx) + if !ok { + return nil, fmt.Errorf("missing namespace") + } + + kp, err := s.storage.Read(ctx, xkube.Namespace(namespace), name) + if err != nil { + if errors.Is(err, contracts.ErrKeeperNotFound) { + return nil, s.resource.NewNotFound(name) + } + return nil, fmt.Errorf("failed to read keeper: %w", err) + } + + return kp, nil +} + +// Create a new `Keeper`. Does some validation and allows empty `name` (generated). +func (s *KeeperRest) Create( + ctx context.Context, + obj runtime.Object, + createValidation rest.ValidateObjectFunc, + options *metav1.CreateOptions, +) (runtime.Object, error) { + kp, ok := obj.(*secretv0alpha1.Keeper) + if !ok { + return nil, fmt.Errorf("expected Keeper for create") + } + + if err := createValidation(ctx, obj); err != nil { + return nil, err + } + + createdKeeper, err := s.storage.Create(ctx, kp) + if err != nil { + var kErr xkube.ErrorLister + if errors.As(err, &kErr) { + return nil, apierrors.NewInvalid(kp.GroupVersionKind().GroupKind(), kp.Name, kErr.ErrorList()) + } + + return nil, fmt.Errorf("failed to create keeper: %w", err) + } + + return createdKeeper, nil +} + +// Update a `Keeper`'s `value`. The second return parameter indicates whether the resource was newly created. +func (s *KeeperRest) Update( + ctx context.Context, + name string, + objInfo rest.UpdatedObjectInfo, + createValidation rest.ValidateObjectFunc, + updateValidation rest.ValidateObjectUpdateFunc, + forceAllowCreate bool, + options *metav1.UpdateOptions, +) (runtime.Object, bool, error) { + oldObj, err := s.Get(ctx, name, &metav1.GetOptions{}) + if err != nil { + return nil, false, err + } + + // Makes sure the UID and ResourceVersion are OK. + // TODO: this also makes it so the labels and annotations are additive, unless we check and remove manually. + newObj, err := objInfo.UpdatedObject(ctx, oldObj) + if err != nil { + return nil, false, fmt.Errorf("k8s updated object: %w", err) + } + + // The current supported behavior for `Update` is to replace the entire `spec` with the new one. + // Each provider-specific setting of a keeper lives at the top-level, so it makes it possible to change a provider + // during an update. Otherwise both old and new providers would be merged in the `newObj` which is not allowed. + if err := updateValidation(ctx, newObj, oldObj); err != nil { + return nil, false, err + } + + newKeeper, ok := newObj.(*secretv0alpha1.Keeper) + if !ok { + return nil, false, fmt.Errorf("expected Keeper for update") + } + + // TODO: do we need to do this here again? Probably not, but double-check! + newKeeper.Annotations = xkube.CleanAnnotations(newKeeper.Annotations) + + // Current implementation replaces everything passed in the spec, so it is not a PATCH. Do we want/need to support that? + updatedKeeper, err := s.storage.Update(ctx, newKeeper) + if err != nil { + var kErr xkube.ErrorLister + if errors.As(err, &kErr) { + return nil, false, apierrors.NewInvalid(newKeeper.GroupVersionKind().GroupKind(), newKeeper.Name, kErr.ErrorList()) + } + + return nil, false, fmt.Errorf("failed to update keeper: %w", err) + } + + return updatedKeeper, false, nil +} + +// Delete calls the inner `store` (persistence) in order to delete the `Keeper`. +// The second return parameter `bool` indicates whether the delete was intant or not. It always is for `Keepers`. +func (s *KeeperRest) Delete(ctx context.Context, name string, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions) (runtime.Object, bool, error) { + namespace, ok := request.NamespaceFrom(ctx) + if !ok { + return nil, false, fmt.Errorf("missing namespace") + } + + if err := s.storage.Delete(ctx, xkube.Namespace(namespace), name); err != nil { + return nil, false, fmt.Errorf("failed to delete keeper: %w", err) + } + + return nil, true, nil +} + +// ValidateKeeper does basic spec validation of a keeper. +func ValidateKeeper(keeper *secretv0alpha1.Keeper, operation admission.Operation) field.ErrorList { + // Only validate Create and Update for now. + if operation != admission.Create && operation != admission.Update { + return nil + } + + errs := make(field.ErrorList, 0) + + if keeper.Spec.Title == "" { + errs = append(errs, field.Required(field.NewPath("spec", "title"), "a `title` is required")) + } + + // Only one keeper type can be configured. Return early and don't validate the specific keeper fields. + if err := validateKeepers(keeper); err != nil { + errs = append(errs, err) + + return errs + } + + // TODO: Improve SQL keeper validation. + // SQL keeper is not allowed to use `secureValueName` in credentials fields to avoid depending on another keeper. + if keeper.IsSqlKeeper() { + if keeper.Spec.SQL.Encryption.AWS != nil { + if keeper.Spec.SQL.Encryption.AWS.AccessKeyID.SecureValueName != "" { + errs = append(errs, field.Forbidden(field.NewPath("spec", "aws", "accessKeyId"), "secureValueName cannot be used with SQL keeper")) + } + + if keeper.Spec.SQL.Encryption.AWS.SecretAccessKey.SecureValueName != "" { + errs = append(errs, field.Forbidden(field.NewPath("spec", "aws", "secretAccessKey"), "secureValueName cannot be used with SQL keeper")) + } + } + + if keeper.Spec.SQL.Encryption.Azure != nil && keeper.Spec.SQL.Encryption.Azure.ClientSecret.SecureValueName != "" { + errs = append(errs, field.Forbidden(field.NewPath("spec", "azure", "clientSecret"), "secureValueName cannot be used with SQL keeper")) + } + + if keeper.Spec.SQL.Encryption.HashiCorp != nil && keeper.Spec.SQL.Encryption.HashiCorp.Token.SecureValueName != "" { + errs = append(errs, field.Forbidden(field.NewPath("spec", "hashicorp", "token"), "secureValueName cannot be used with SQL keeper")) + } + } + + if keeper.Spec.AWS != nil { + if err := validateCredentialValue(field.NewPath("spec", "aws", "accessKeyId"), keeper.Spec.AWS.AccessKeyID); err != nil { + errs = append(errs, err) + } + + if err := validateCredentialValue(field.NewPath("spec", "aws", "secretAccessKey"), keeper.Spec.AWS.SecretAccessKey); err != nil { + errs = append(errs, err) + } + } + + if keeper.Spec.Azure != nil { + if keeper.Spec.Azure.KeyVaultName == "" { + errs = append(errs, field.Required(field.NewPath("spec", "azure", "keyVaultName"), "a `keyVaultName` is required")) + } + + if keeper.Spec.Azure.TenantID == "" { + errs = append(errs, field.Required(field.NewPath("spec", "azure", "tenantId"), "a `tenantId` is required")) + } + + if keeper.Spec.Azure.ClientID == "" { + errs = append(errs, field.Required(field.NewPath("spec", "azure", "clientId"), "a `clientId` is required")) + } + + if err := validateCredentialValue(field.NewPath("spec", "azure", "clientSecret"), keeper.Spec.Azure.ClientSecret); err != nil { + errs = append(errs, err) + } + } + + if keeper.Spec.GCP != nil { + if keeper.Spec.GCP.ProjectID == "" { + errs = append(errs, field.Required(field.NewPath("spec", "gcp", "projectId"), "a `projectId` is required")) + } + + if keeper.Spec.GCP.CredentialsFile == "" { + errs = append(errs, field.Required(field.NewPath("spec", "gcp", "credentialsFile"), "a `credentialsFile` is required")) + } + } + + if keeper.Spec.HashiCorp != nil { + if keeper.Spec.HashiCorp.Address == "" { + errs = append(errs, field.Required(field.NewPath("spec", "hashicorp", "address"), "a `address` is required")) + } + + if err := validateCredentialValue(field.NewPath("spec", "hashicorp", "token"), keeper.Spec.HashiCorp.Token); err != nil { + errs = append(errs, err) + } + } + + return errs +} + +func validateKeepers(keeper *secretv0alpha1.Keeper) *field.Error { + availableKeepers := map[string]bool{ + "sql": keeper.Spec.SQL != nil, + "aws": keeper.Spec.AWS != nil, + "azure": keeper.Spec.Azure != nil, + "gcp": keeper.Spec.GCP != nil, + "hashicorp": keeper.Spec.HashiCorp != nil, + } + + configuredKeepers := make([]string, 0) + + for keeperKind, notNil := range availableKeepers { + if notNil { + configuredKeepers = append(configuredKeepers, keeperKind) + } + } + + if len(configuredKeepers) == 0 { + return field.Required(field.NewPath("spec"), "at least one `keeper` must be present") + } + + if len(configuredKeepers) > 1 { + return field.Invalid( + field.NewPath("spec"), + strings.Join(configuredKeepers, " & "), + "only one `keeper` can be present at a time but found more", + ) + } + + return nil +} + +func validateCredentialValue(path *field.Path, credentials secretv0alpha1.CredentialValue) *field.Error { + availableOptions := map[string]bool{ + "secureValueName": credentials.SecureValueName != "", + "valueFromEnv": credentials.ValueFromEnv != "", + "valueFromConfig": credentials.ValueFromConfig != "", + } + + configuredCredentials := make([]string, 0) + + for credentialKind, notEmpty := range availableOptions { + if notEmpty { + configuredCredentials = append(configuredCredentials, credentialKind) + } + } + + if len(configuredCredentials) == 0 { + return field.Required(path, "one of `secureValueName`, `valueFromEnv` or `valueFromConfig` must be present") + } + + if len(configuredCredentials) > 1 { + return field.Invalid( + path, + strings.Join(configuredCredentials, " & "), + "only one of `secureValueName`, `valueFromEnv` or `valueFromConfig` must be present at a time but found more", + ) + } + + return nil +} diff --git a/pkg/registry/apis/secret/reststorage/keeper_rest_test.go b/pkg/registry/apis/secret/reststorage/keeper_rest_test.go new file mode 100644 index 00000000000..abc1ac52b63 --- /dev/null +++ b/pkg/registry/apis/secret/reststorage/keeper_rest_test.go @@ -0,0 +1,332 @@ +package reststorage + +import ( + "testing" + + secretv0alpha1 "github.com/grafana/grafana/pkg/apis/secret/v0alpha1" + "github.com/stretchr/testify/require" + "k8s.io/apiserver/pkg/admission" +) + +func TestValidateKeeper(t *testing.T) { + t.Run("when creating a new keeper", func(t *testing.T) { + t.Run("the `title` must be present", func(t *testing.T) { + keeper := &secretv0alpha1.Keeper{ + Spec: secretv0alpha1.KeeperSpec{ + SQL: &secretv0alpha1.SQLKeeperConfig{}, + }, + } + + errs := ValidateKeeper(keeper, admission.Create) + require.Len(t, errs, 1) + require.Equal(t, "spec.title", errs[0].Field) + }) + }) + + t.Run("only one `keeper` must be present", func(t *testing.T) { + keeper := &secretv0alpha1.Keeper{ + Spec: secretv0alpha1.KeeperSpec{ + Title: "title", + SQL: &secretv0alpha1.SQLKeeperConfig{}, + AWS: &secretv0alpha1.AWSKeeperConfig{}, + Azure: &secretv0alpha1.AzureKeeperConfig{}, + GCP: &secretv0alpha1.GCPKeeperConfig{}, + HashiCorp: &secretv0alpha1.HashiCorpKeeperConfig{}, + }, + } + + errs := ValidateKeeper(keeper, admission.Create) + require.Len(t, errs, 1) + require.Equal(t, "spec", errs[0].Field) + }) + + t.Run("at least one `keeper` must be present", func(t *testing.T) { + keeper := &secretv0alpha1.Keeper{ + Spec: secretv0alpha1.KeeperSpec{ + Title: "title", + }, + } + + errs := ValidateKeeper(keeper, admission.Create) + require.Len(t, errs, 1) + require.Equal(t, "spec", errs[0].Field) + }) + + t.Run("aws keeper validation", func(t *testing.T) { + validKeeperAWS := &secretv0alpha1.Keeper{ + Spec: secretv0alpha1.KeeperSpec{ + Title: "title", + AWS: &secretv0alpha1.AWSKeeperConfig{ + AWSCredentials: secretv0alpha1.AWSCredentials{ + AccessKeyID: secretv0alpha1.CredentialValue{ + ValueFromEnv: "some-value", + }, + SecretAccessKey: secretv0alpha1.CredentialValue{ + SecureValueName: "some-value", + }, + KMSKeyID: "optional", + }, + }, + }, + } + + t.Run("`accessKeyId` must be present", func(t *testing.T) { + t.Run("at least one of the credential value must be present", func(t *testing.T) { + keeper := validKeeperAWS.DeepCopy() + keeper.Spec.AWS.AccessKeyID = secretv0alpha1.CredentialValue{} + + errs := ValidateKeeper(keeper, admission.Create) + require.Len(t, errs, 1) + require.Equal(t, "spec.aws.accessKeyId", errs[0].Field) + }) + + t.Run("at most one of the credential value must be present", func(t *testing.T) { + keeper := validKeeperAWS.DeepCopy() + keeper.Spec.AWS.AccessKeyID = secretv0alpha1.CredentialValue{ + SecureValueName: "a", + ValueFromEnv: "b", + ValueFromConfig: "c", + } + + errs := ValidateKeeper(keeper, admission.Create) + require.Len(t, errs, 1) + require.Equal(t, "spec.aws.accessKeyId", errs[0].Field) + }) + }) + + t.Run("`secretAccessKey` must be present", func(t *testing.T) { + t.Run("at least one of the credential value must be present", func(t *testing.T) { + keeper := validKeeperAWS.DeepCopy() + keeper.Spec.AWS.SecretAccessKey = secretv0alpha1.CredentialValue{} + + errs := ValidateKeeper(keeper, admission.Create) + require.Len(t, errs, 1) + require.Equal(t, "spec.aws.secretAccessKey", errs[0].Field) + }) + + t.Run("at most one of the credential value must be present", func(t *testing.T) { + keeper := validKeeperAWS.DeepCopy() + keeper.Spec.AWS.SecretAccessKey = secretv0alpha1.CredentialValue{ + SecureValueName: "a", + ValueFromEnv: "b", + ValueFromConfig: "c", + } + + errs := ValidateKeeper(keeper, admission.Create) + require.Len(t, errs, 1) + require.Equal(t, "spec.aws.secretAccessKey", errs[0].Field) + }) + }) + }) + + t.Run("azure keeper validation", func(t *testing.T) { + validKeeperAzure := &secretv0alpha1.Keeper{ + Spec: secretv0alpha1.KeeperSpec{ + Title: "title", + Azure: &secretv0alpha1.AzureKeeperConfig{ + AzureCredentials: secretv0alpha1.AzureCredentials{ + KeyVaultName: "kv-name", + TenantID: "tenant-id", + ClientID: "client-id", + ClientSecret: secretv0alpha1.CredentialValue{ + ValueFromConfig: "config.path.value", + }, + }, + }, + }, + } + + t.Run("`keyVaultName` must be present", func(t *testing.T) { + keeper := validKeeperAzure.DeepCopy() + keeper.Spec.Azure.KeyVaultName = "" + + errs := ValidateKeeper(keeper, admission.Create) + require.Len(t, errs, 1) + require.Equal(t, "spec.azure.keyVaultName", errs[0].Field) + }) + + t.Run("`tenantId` must be present", func(t *testing.T) { + keeper := validKeeperAzure.DeepCopy() + keeper.Spec.Azure.TenantID = "" + + errs := ValidateKeeper(keeper, admission.Create) + require.Len(t, errs, 1) + require.Equal(t, "spec.azure.tenantId", errs[0].Field) + }) + + t.Run("`clientId` must be present", func(t *testing.T) { + keeper := validKeeperAzure.DeepCopy() + keeper.Spec.Azure.ClientID = "" + + errs := ValidateKeeper(keeper, admission.Create) + require.Len(t, errs, 1) + require.Equal(t, "spec.azure.clientId", errs[0].Field) + }) + + t.Run("`clientSecret` must be present", func(t *testing.T) { + t.Run("at least one of the credential value must be present", func(t *testing.T) { + keeper := validKeeperAzure.DeepCopy() + keeper.Spec.Azure.ClientSecret = secretv0alpha1.CredentialValue{} + + errs := ValidateKeeper(keeper, admission.Create) + require.Len(t, errs, 1) + require.Equal(t, "spec.azure.clientSecret", errs[0].Field) + }) + + t.Run("at most one of the credential value must be present", func(t *testing.T) { + keeper := validKeeperAzure.DeepCopy() + keeper.Spec.Azure.ClientSecret = secretv0alpha1.CredentialValue{ + SecureValueName: "a", + ValueFromEnv: "b", + ValueFromConfig: "c", + } + + errs := ValidateKeeper(keeper, admission.Create) + require.Len(t, errs, 1) + require.Equal(t, "spec.azure.clientSecret", errs[0].Field) + }) + }) + }) + + t.Run("gcp keeper validation", func(t *testing.T) { + validKeeperGCP := &secretv0alpha1.Keeper{ + Spec: secretv0alpha1.KeeperSpec{ + Title: "title", + GCP: &secretv0alpha1.GCPKeeperConfig{ + GCPCredentials: secretv0alpha1.GCPCredentials{ + ProjectID: "project-id", + CredentialsFile: "/path/to/credentials/file.json", + }, + }, + }, + } + + t.Run("`projectId` must be present", func(t *testing.T) { + keeper := validKeeperGCP.DeepCopy() + keeper.Spec.GCP.ProjectID = "" + + errs := ValidateKeeper(keeper, admission.Create) + require.Len(t, errs, 1) + require.Equal(t, "spec.gcp.projectId", errs[0].Field) + }) + + t.Run("`credentialsFile` must be present", func(t *testing.T) { + keeper := validKeeperGCP.DeepCopy() + keeper.Spec.GCP.CredentialsFile = "" + + errs := ValidateKeeper(keeper, admission.Create) + require.Len(t, errs, 1) + require.Equal(t, "spec.gcp.credentialsFile", errs[0].Field) + }) + }) + + t.Run("hashicorp keeper validation", func(t *testing.T) { + validKeeperHashiCorp := &secretv0alpha1.Keeper{ + Spec: secretv0alpha1.KeeperSpec{ + Title: "title", + HashiCorp: &secretv0alpha1.HashiCorpKeeperConfig{ + HashiCorpCredentials: secretv0alpha1.HashiCorpCredentials{ + Address: "http://address", + Token: secretv0alpha1.CredentialValue{ + ValueFromConfig: "config.path.value", + }, + }, + }, + }, + } + + t.Run("`address` must be present", func(t *testing.T) { + keeper := validKeeperHashiCorp.DeepCopy() + keeper.Spec.HashiCorp.Address = "" + + errs := ValidateKeeper(keeper, admission.Create) + require.Len(t, errs, 1) + require.Equal(t, "spec.hashicorp.address", errs[0].Field) + }) + + t.Run("`token` must be present", func(t *testing.T) { + t.Run("at least one of the credential value must be present", func(t *testing.T) { + keeper := validKeeperHashiCorp.DeepCopy() + keeper.Spec.HashiCorp.Token = secretv0alpha1.CredentialValue{} + + errs := ValidateKeeper(keeper, admission.Create) + require.Len(t, errs, 1) + require.Equal(t, "spec.hashicorp.token", errs[0].Field) + }) + + t.Run("at most one of the credential value must be present", func(t *testing.T) { + keeper := validKeeperHashiCorp.DeepCopy() + keeper.Spec.HashiCorp.Token = secretv0alpha1.CredentialValue{ + SecureValueName: "a", + ValueFromEnv: "b", + ValueFromConfig: "c", + } + + errs := ValidateKeeper(keeper, admission.Create) + require.Len(t, errs, 1) + require.Equal(t, "spec.hashicorp.token", errs[0].Field) + }) + }) + }) + + t.Run("sql keeper validation", func(t *testing.T) { + t.Run("does not allow usage of `secureValueName` in credentials", func(t *testing.T) { + providers := []struct { + name string + enc secretv0alpha1.Encryption + expectedErrors int + }{ + { + name: "aws", + enc: secretv0alpha1.Encryption{ + AWS: &secretv0alpha1.AWSCredentials{ + AccessKeyID: secretv0alpha1.CredentialValue{ + SecureValueName: "not-empty", + }, + SecretAccessKey: secretv0alpha1.CredentialValue{ + SecureValueName: "not-empty", + }, + }, + }, + expectedErrors: 2, + }, + { + name: "azure", + enc: secretv0alpha1.Encryption{ + Azure: &secretv0alpha1.AzureCredentials{ + ClientSecret: secretv0alpha1.CredentialValue{ + SecureValueName: "not-empty", + }, + }, + }, + expectedErrors: 1, + }, + { + name: "hashicorp", + enc: secretv0alpha1.Encryption{ + HashiCorp: &secretv0alpha1.HashiCorpCredentials{ + Token: secretv0alpha1.CredentialValue{ + SecureValueName: "not-empty", + }, + }, + }, + expectedErrors: 1, + }, + } + + for _, tc := range providers { + t.Run("when using credentials for "+tc.name, func(t *testing.T) { + keeper := &secretv0alpha1.Keeper{ + Spec: secretv0alpha1.KeeperSpec{ + Title: "title", + SQL: &secretv0alpha1.SQLKeeperConfig{Encryption: &tc.enc}, + }, + } + + errs := ValidateKeeper(keeper, admission.Create) + require.Len(t, errs, tc.expectedErrors) + }) + } + }) + }) +} diff --git a/pkg/registry/apis/secret/reststorage/secure_value.go b/pkg/registry/apis/secret/reststorage/secure_value.go deleted file mode 100644 index 10dc2695170..00000000000 --- a/pkg/registry/apis/secret/reststorage/secure_value.go +++ /dev/null @@ -1,129 +0,0 @@ -package reststorage - -import ( - "context" - "fmt" - - "k8s.io/apimachinery/pkg/apis/meta/internalversion" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/util/validation/field" - "k8s.io/apiserver/pkg/admission" - "k8s.io/apiserver/pkg/endpoints/request" - "k8s.io/apiserver/pkg/registry/rest" - - "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" -) - -var ( - _ rest.Scoper = (*SecureValueRest)(nil) - _ rest.SingularNameProvider = (*SecureValueRest)(nil) - _ rest.Getter = (*SecureValueRest)(nil) - _ rest.Lister = (*SecureValueRest)(nil) - _ rest.Storage = (*SecureValueRest)(nil) - _ rest.Creater = (*SecureValueRest)(nil) - _ rest.Updater = (*SecureValueRest)(nil) - _ rest.GracefulDeleter = (*SecureValueRest)(nil) -) - -// SecureValueRest is an implementation of CRUDL operations on a `securevalue` backed by a persistence layer `store`. -type SecureValueRest struct { - storage contracts.SecureValueMetadataStorage - resource utils.ResourceInfo - tableConverter rest.TableConvertor -} - -// NewSecureValueRest is a returns a constructed `*SecureValueRest`. -func NewSecureValueRest(storage contracts.SecureValueMetadataStorage, resource utils.ResourceInfo) *SecureValueRest { - return &SecureValueRest{storage, resource, resource.TableConverter()} -} - -// New returns an empty `*SecureValue` that is used by the `Create` method. -func (s *SecureValueRest) New() runtime.Object { - return s.resource.NewFunc() -} - -// Destroy is called when? [TODO] -func (s *SecureValueRest) Destroy() {} - -// NamespaceScoped returns `true` because the storage is namespaced (== org). -func (s *SecureValueRest) NamespaceScoped() bool { - return true -} - -// GetSingularName is used by `kubectl` discovery to have singular name representation of resources. -func (s *SecureValueRest) GetSingularName() string { - return s.resource.GetSingularName() -} - -// NewList returns an empty `*SecureValueList` that is used by the `List` method. -func (s *SecureValueRest) NewList() runtime.Object { - return s.resource.NewListFunc() -} - -// ConvertToTable is used by Kubernetes and converts objects to `metav1.Table`. -func (s *SecureValueRest) ConvertToTable(ctx context.Context, object runtime.Object, tableOptions runtime.Object) (*metav1.Table, error) { - return s.tableConverter.ConvertToTable(ctx, object, tableOptions) -} - -// List calls the inner `store` (persistence) and returns a list of `securevalues` within a `namespace` filtered by the `options`. -func (s *SecureValueRest) List(ctx context.Context, options *internalversion.ListOptions) (runtime.Object, error) { - _, ok := request.NamespaceFrom(ctx) - if !ok { - return nil, fmt.Errorf("missing namespace") - } - - return &secretv0alpha1.SecureValueList{Items: make([]secretv0alpha1.SecureValue, 0)}, nil -} - -// Get calls the inner `store` (persistence) and returns a `securevalue` by `name`. It will NOT return the decrypted `value`. -func (s *SecureValueRest) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) { - _, ok := request.NamespaceFrom(ctx) - if !ok { - return nil, fmt.Errorf("missing namespace") - } - - return nil, s.resource.NewNotFound(name) -} - -// Create a new `securevalue`. Does some validation and allows empty `name` (generated). -func (s *SecureValueRest) Create( - ctx context.Context, - obj runtime.Object, - createValidation rest.ValidateObjectFunc, - options *metav1.CreateOptions, -) (runtime.Object, error) { - return nil, nil -} - -// Update a `securevalue`'s `value`. The second return parameter indicates whether the resource was newly created. -// Currently does not support "create on update" functionality. If the securevalue does not yet exist, it returns an error. -func (s *SecureValueRest) Update( - ctx context.Context, - name string, - objInfo rest.UpdatedObjectInfo, - createValidation rest.ValidateObjectFunc, - updateValidation rest.ValidateObjectUpdateFunc, - forceAllowCreate bool, - options *metav1.UpdateOptions, -) (runtime.Object, bool, error) { - return nil, false, nil -} - -// Delete calls the inner `store` (persistence) in order to delete the `securevalue`. -// The second return parameter `bool` indicates whether the delete was instant or not. It always is for `securevalues`. -func (s *SecureValueRest) Delete(ctx context.Context, name string, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions) (runtime.Object, bool, error) { - _, ok := request.NamespaceFrom(ctx) - if !ok { - return nil, false, fmt.Errorf("missing namespace") - } - - return nil, true, nil -} - -// ValidateSecureValue does basic spec validation of a securevalue. -func ValidateSecureValue(sv, oldSv *secretv0alpha1.SecureValue, operation admission.Operation, decryptersAllowList map[string]struct{}) field.ErrorList { - return nil -} diff --git a/pkg/registry/apis/secret/reststorage/secure_value_rest.go b/pkg/registry/apis/secret/reststorage/secure_value_rest.go new file mode 100644 index 00000000000..f0b043108e9 --- /dev/null +++ b/pkg/registry/apis/secret/reststorage/secure_value_rest.go @@ -0,0 +1,315 @@ +package reststorage + +import ( + "context" + "errors" + "fmt" + "strconv" + "strings" + + "k8s.io/apimachinery/pkg/apis/meta/internalversion" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/apiserver/pkg/admission" + "k8s.io/apiserver/pkg/endpoints/request" + "k8s.io/apiserver/pkg/registry/rest" + + "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/xkube" +) + +var ( + _ rest.Scoper = (*SecureValueRest)(nil) + _ rest.SingularNameProvider = (*SecureValueRest)(nil) + _ rest.Getter = (*SecureValueRest)(nil) + _ rest.Lister = (*SecureValueRest)(nil) + _ rest.Storage = (*SecureValueRest)(nil) + _ rest.Creater = (*SecureValueRest)(nil) + _ rest.Updater = (*SecureValueRest)(nil) + _ rest.GracefulDeleter = (*SecureValueRest)(nil) +) + +// SecureValueRest is an implementation of CRUDL operations on a `securevalue` backed by a persistence layer `store`. +type SecureValueRest struct { + storage contracts.SecureValueMetadataStorage + resource utils.ResourceInfo + tableConverter rest.TableConvertor +} + +// NewSecureValueRest is a returns a constructed `*SecureValueRest`. +func NewSecureValueRest(storage contracts.SecureValueMetadataStorage, resource utils.ResourceInfo) *SecureValueRest { + return &SecureValueRest{storage, resource, resource.TableConverter()} +} + +// New returns an empty `*SecureValue` that is used by the `Create` method. +func (s *SecureValueRest) New() runtime.Object { + return s.resource.NewFunc() +} + +// Destroy is called when? [TODO] +func (s *SecureValueRest) Destroy() {} + +// NamespaceScoped returns `true` because the storage is namespaced (== org). +func (s *SecureValueRest) NamespaceScoped() bool { + return true +} + +// GetSingularName is used by `kubectl` discovery to have singular name representation of resources. +func (s *SecureValueRest) GetSingularName() string { + return s.resource.GetSingularName() +} + +// NewList returns an empty `*SecureValueList` that is used by the `List` method. +func (s *SecureValueRest) NewList() runtime.Object { + return s.resource.NewListFunc() +} + +// ConvertToTable is used by Kubernetes and converts objects to `metav1.Table`. +func (s *SecureValueRest) ConvertToTable(ctx context.Context, object runtime.Object, tableOptions runtime.Object) (*metav1.Table, error) { + return s.tableConverter.ConvertToTable(ctx, object, tableOptions) +} + +// List calls the inner `store` (persistence) and returns a list of `securevalues` within a `namespace` filtered by the `options`. +func (s *SecureValueRest) List(ctx context.Context, options *internalversion.ListOptions) (runtime.Object, error) { + namespace, ok := request.NamespaceFrom(ctx) + if !ok { + return nil, fmt.Errorf("missing namespace") + } + + secureValueList, err := s.storage.List(ctx, xkube.Namespace(namespace), options) + if err != nil { + return nil, fmt.Errorf("failed to list secure values: %w", err) + } + + return secureValueList, nil +} + +// Get calls the inner `store` (persistence) and returns a `securevalue` by `name`. It will NOT return the decrypted `value`. +func (s *SecureValueRest) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) { + namespace, ok := request.NamespaceFrom(ctx) + if !ok { + return nil, fmt.Errorf("missing namespace") + } + + sv, err := s.storage.Read(ctx, xkube.Namespace(namespace), name) + if err != nil { + if errors.Is(err, contracts.ErrSecureValueNotFound) { + return nil, s.resource.NewNotFound(name) + } + + return nil, fmt.Errorf("failed to read secure value: %w", err) + } + + return sv, nil +} + +// Create a new `securevalue`. Does some validation and allows empty `name` (generated). +func (s *SecureValueRest) Create( + ctx context.Context, + obj runtime.Object, + createValidation rest.ValidateObjectFunc, + options *metav1.CreateOptions, +) (runtime.Object, error) { + sv, ok := obj.(*secretv0alpha1.SecureValue) + if !ok { + return nil, fmt.Errorf("expected SecureValue for create") + } + + if err := createValidation(ctx, obj); err != nil { + return nil, err + } + + createdSecureValue, err := s.storage.Create(ctx, sv) + if err != nil { + return nil, fmt.Errorf("failed to create secure value: %w", err) + } + + return createdSecureValue, nil +} + +// Update a `securevalue`'s `value`. The second return parameter indicates whether the resource was newly created. +// Currently does not support "create on update" functionality. If the securevalue does not yet exist, it returns an error. +func (s *SecureValueRest) Update( + ctx context.Context, + name string, + objInfo rest.UpdatedObjectInfo, + createValidation rest.ValidateObjectFunc, + updateValidation rest.ValidateObjectUpdateFunc, + forceAllowCreate bool, + options *metav1.UpdateOptions, +) (runtime.Object, bool, error) { + oldObj, err := s.Get(ctx, name, &metav1.GetOptions{}) + if err != nil { + return nil, false, err + } + + // Makes sure the UID and ResourceVersion are OK. + // TODO: this also makes it so the labels and annotations are additive, unless we check and remove manually. + newObj, err := objInfo.UpdatedObject(ctx, oldObj) + if err != nil { + return nil, false, fmt.Errorf("k8s updated object: %w", err) + } + + if err := updateValidation(ctx, newObj, oldObj); err != nil { + return nil, false, err + } + + newSecureValue, ok := newObj.(*secretv0alpha1.SecureValue) + if !ok { + return nil, false, fmt.Errorf("expected SecureValue for update") + } + + // TODO: do we need to do this here again? Probably not, but double-check! + newSecureValue.Annotations = xkube.CleanAnnotations(newSecureValue.Annotations) + + // Current implementation replaces everything passed in the spec, so it is not a PATCH. Do we want/need to support that? + updatedSecureValue, err := s.storage.Update(ctx, newSecureValue) + if err != nil { + return nil, false, fmt.Errorf("failed to update secure value: %w", err) + } + + return updatedSecureValue, false, nil +} + +// Delete calls the inner `store` (persistence) in order to delete the `securevalue`. +// The second return parameter `bool` indicates whether the delete was instant or not. It always is for `securevalues`. +func (s *SecureValueRest) Delete(ctx context.Context, name string, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions) (runtime.Object, bool, error) { + namespace, ok := request.NamespaceFrom(ctx) + if !ok { + return nil, false, fmt.Errorf("missing namespace") + } + + if err := s.storage.Delete(ctx, xkube.Namespace(namespace), name); err != nil { + return nil, false, fmt.Errorf("delete secure value: %w", err) + } + + return nil, true, nil +} + +// ValidateSecureValue does basic spec validation of a securevalue. +func ValidateSecureValue(sv, oldSv *secretv0alpha1.SecureValue, operation admission.Operation, decryptersAllowList map[string]struct{}) field.ErrorList { + errs := make(field.ErrorList, 0) + + // Operation-specific field validation. + switch operation { + case admission.Create: + errs = validateSecureValueCreate(sv) + + // If we plan to support PATCH-style updates, we shouldn't be requiring fields to be set. + case admission.Update: + errs = validateSecureValueUpdate(sv, oldSv) + + case admission.Delete: + case admission.Connect: + } + + // General validations. + if errs := validateDecrypters(sv.Spec.Decrypters, decryptersAllowList); len(errs) > 0 { + return errs + } + + return errs +} + +// validateSecureValueCreate does basic spec validation of a securevalue for the Create operation. +func validateSecureValueCreate(sv *secretv0alpha1.SecureValue) field.ErrorList { + errs := make(field.ErrorList, 0) + + if sv.Spec.Title == "" { + errs = append(errs, field.Required(field.NewPath("spec", "title"), "a `title` is required")) + } + + if sv.Spec.Keeper == "" { + errs = append(errs, field.Required(field.NewPath("spec", "keeper"), "a `keeper` is required")) + } + + if sv.Spec.Value == "" && sv.Spec.Ref == "" { + errs = append(errs, field.Required(field.NewPath("spec"), "either a `value` or `ref` is required")) + } + + if sv.Spec.Value != "" && sv.Spec.Ref != "" { + errs = append(errs, field.Forbidden(field.NewPath("spec"), "only one of `value` or `ref` can be set")) + } + + return errs +} + +// validateSecureValueUpdate does basic spec validation of a securevalue for the Update operation. +func validateSecureValueUpdate(sv, oldSv *secretv0alpha1.SecureValue) field.ErrorList { + errs := make(field.ErrorList, 0) + + // For updates, an `old` object is required. + if oldSv == nil { + errs = append(errs, field.InternalError(field.NewPath("spec"), errors.New("old object is nil"))) + + return errs + } + + // Only validate if one of the fields is being changed/set. + if sv.Spec.Value != "" || sv.Spec.Ref != "" { + if oldSv.Spec.Ref != "" && sv.Spec.Value != "" { + errs = append(errs, field.Forbidden(field.NewPath("spec"), "cannot set `value` when `ref` was already previously set")) + } + + if oldSv.Spec.Ref == "" && sv.Spec.Ref != "" { + errs = append(errs, field.Forbidden(field.NewPath("spec"), "cannot set `ref` when `value` was already previously set")) + } + } + + // Keeper cannot be changed. + if sv.Spec.Keeper != oldSv.Spec.Keeper { + errs = append(errs, field.Forbidden(field.NewPath("spec"), "the `keeper` cannot be changed")) + } + + return errs +} + +// validateDecrypters validates that (if populated) the `decrypters` must match "actor_{name}" and must be unique. +func validateDecrypters(decrypters []string, decryptersAllowList map[string]struct{}) field.ErrorList { + errs := make(field.ErrorList, 0) + + decrypterNames := make(map[string]struct{}, 0) + + for i, decrypter := range decrypters { + // Allow List: decrypters must match exactly and be in the allowed list to be able to decrypt. + // This means an allow list item should have the format "actor_{name}" and not just "{name}". + if len(decryptersAllowList) > 0 { + if _, exists := decryptersAllowList[decrypter]; !exists { + errs = append( + errs, + field.Invalid(field.NewPath("spec", "decrypters", "["+strconv.Itoa(i)+"]"), decrypter, fmt.Sprintf("allowed values: %v", decryptersAllowList)), + ) + + return errs + } + + continue + } + + actor, name, found := strings.Cut(strings.TrimSpace(decrypter), "_") + if !found || actor != "actor" || name == "" { + errs = append( + errs, + field.Invalid(field.NewPath("spec", "decrypters", "["+strconv.Itoa(i)+"]"), decrypter, "a decrypter must have the format `actor_{name}`"), + ) + + continue + } + + if _, exists := decrypterNames[name]; exists { + errs = append( + errs, + field.Invalid(field.NewPath("spec", "decrypters", "["+strconv.Itoa(i)+"]"), decrypter, "decrypters must be unique"), + ) + + continue + } + + decrypterNames[name] = struct{}{} + } + + return errs +} diff --git a/pkg/registry/apis/secret/reststorage/secure_value_rest_test.go b/pkg/registry/apis/secret/reststorage/secure_value_rest_test.go new file mode 100644 index 00000000000..3002be94706 --- /dev/null +++ b/pkg/registry/apis/secret/reststorage/secure_value_rest_test.go @@ -0,0 +1,269 @@ +package reststorage + +import ( + "fmt" + "maps" + "slices" + "testing" + + "github.com/stretchr/testify/require" + "k8s.io/apiserver/pkg/admission" + + secretv0alpha1 "github.com/grafana/grafana/pkg/apis/secret/v0alpha1" +) + +func TestValidateSecureValue(t *testing.T) { + t.Run("when creating a new securevalue", func(t *testing.T) { + validSecureValue := &secretv0alpha1.SecureValue{ + Spec: secretv0alpha1.SecureValueSpec{ + Title: "title", + Value: "value", + Keeper: "keeper", + Decrypters: []string{"actor_app1", "actor_app2"}, + }, + } + + t.Run("the `title` must be present", func(t *testing.T) { + sv := validSecureValue.DeepCopy() + sv.Spec.Title = "" + + errs := ValidateSecureValue(sv, nil, admission.Create, nil) + require.Len(t, errs, 1) + require.Equal(t, "spec.title", errs[0].Field) + }) + + t.Run("the `keeper` must be present", func(t *testing.T) { + sv := validSecureValue.DeepCopy() + sv.Spec.Keeper = "" + + errs := ValidateSecureValue(sv, nil, admission.Create, nil) + require.Len(t, errs, 1) + require.Equal(t, "spec.keeper", errs[0].Field) + }) + + t.Run("either a `value` or `ref` must be present but not both", func(t *testing.T) { + sv := validSecureValue.DeepCopy() + sv.Spec.Value = "" + sv.Spec.Ref = "" + + errs := ValidateSecureValue(sv, nil, admission.Create, nil) + require.Len(t, errs, 1) + require.Equal(t, "spec", errs[0].Field) + + sv.Spec.Value = "value" + sv.Spec.Ref = "value" + + errs = ValidateSecureValue(sv, nil, admission.Create, nil) + require.Len(t, errs, 1) + require.Equal(t, "spec", errs[0].Field) + }) + }) + + t.Run("when updating a securevalue", func(t *testing.T) { + t.Run("when trying to switch from a `value` (old) to a `ref` (new), it returns an error", func(t *testing.T) { + oldSv := &secretv0alpha1.SecureValue{ + Spec: secretv0alpha1.SecureValueSpec{ + Ref: "", // empty `ref` means a `value` was present. + }, + } + + sv := &secretv0alpha1.SecureValue{ + Spec: secretv0alpha1.SecureValueSpec{ + Ref: "ref", + }, + } + + errs := ValidateSecureValue(sv, oldSv, admission.Update, nil) + require.Len(t, errs, 1) + require.Equal(t, "spec", errs[0].Field) + }) + + t.Run("when trying to switch from a `ref` (old) to a `value` (new), it returns an error", func(t *testing.T) { + oldSv := &secretv0alpha1.SecureValue{ + Spec: secretv0alpha1.SecureValueSpec{ + Ref: "non-empty", + }, + } + + sv := &secretv0alpha1.SecureValue{ + Spec: secretv0alpha1.SecureValueSpec{ + Value: "value", + }, + } + + errs := ValidateSecureValue(sv, oldSv, admission.Update, nil) + require.Len(t, errs, 1) + require.Equal(t, "spec", errs[0].Field) + }) + + t.Run("when both `value` and `ref` are set, it returns an error", func(t *testing.T) { + oldSv := &secretv0alpha1.SecureValue{ + Spec: secretv0alpha1.SecureValueSpec{ + Ref: "non-empty", + }, + } + + sv := &secretv0alpha1.SecureValue{ + Spec: secretv0alpha1.SecureValueSpec{ + Value: "value", + Ref: "ref", + }, + } + + errs := ValidateSecureValue(sv, oldSv, admission.Update, nil) + require.Len(t, errs, 1) + require.Equal(t, "spec", errs[0].Field) + + oldSv = &secretv0alpha1.SecureValue{ + Spec: secretv0alpha1.SecureValueSpec{ + Value: "non-empty", + }, + } + + errs = ValidateSecureValue(sv, oldSv, admission.Update, nil) + require.Len(t, errs, 1) + require.Equal(t, "spec", errs[0].Field) + }) + + t.Run("when no changes are made, it returns no errors", func(t *testing.T) { + oldSv := &secretv0alpha1.SecureValue{ + Spec: secretv0alpha1.SecureValueSpec{ + Title: "old-title", + }, + } + + sv := &secretv0alpha1.SecureValue{ + Spec: secretv0alpha1.SecureValueSpec{ + Title: "new-title", + }, + } + + errs := ValidateSecureValue(sv, oldSv, admission.Update, nil) + require.Empty(t, errs) + }) + + t.Run("when the old object is `nil` it returns an error", func(t *testing.T) { + sv := &secretv0alpha1.SecureValue{} + + errs := ValidateSecureValue(sv, nil, admission.Update, nil) + require.Len(t, errs, 1) + require.Equal(t, "spec", errs[0].Field) + }) + + t.Run("when trying to change the `keeper`, it returns an error", func(t *testing.T) { + oldSv := &secretv0alpha1.SecureValue{ + Spec: secretv0alpha1.SecureValueSpec{ + Keeper: "a-keeper", + }, + } + + sv := &secretv0alpha1.SecureValue{ + Spec: secretv0alpha1.SecureValueSpec{ + Keeper: "another-keeper", + }, + } + + errs := ValidateSecureValue(sv, oldSv, admission.Update, nil) + require.Len(t, errs, 1) + require.Equal(t, "spec", errs[0].Field) + }) + }) + + t.Run("`decrypters` must have unique items", func(t *testing.T) { + sv := &secretv0alpha1.SecureValue{ + Spec: secretv0alpha1.SecureValueSpec{ + Title: "title", Keeper: "keeper", Ref: "ref", + + Decrypters: []string{ + "actor_app1", + "actor_app1", + }, + }, + } + + errs := ValidateSecureValue(sv, nil, admission.Create, nil) + require.Len(t, errs, 1) + require.Equal(t, "spec.decrypters.[1]", errs[0].Field) + }) + + t.Run("`decrypters` must match the expected format", func(t *testing.T) { + sv := &secretv0alpha1.SecureValue{ + Spec: secretv0alpha1.SecureValueSpec{ + Title: "title", Keeper: "keeper", Ref: "ref", + + Decrypters: []string{ + "app1", + "_app1", + "actr_app1", + "actor_ ", + "actor_", + }, + }, + } + + errs := ValidateSecureValue(sv, nil, admission.Create, nil) + require.Len(t, errs, len(sv.Spec.Decrypters)) + + for i, err := range errs { + require.Equal(t, fmt.Sprintf("spec.decrypters.[%d]", i), err.Field) + require.Contains(t, err.Error(), "a decrypter must have the format `actor_{name}`") + } + }) + + t.Run("when set, the `decrypters` must be one of the allowed in the allow list", func(t *testing.T) { + allowList := map[string]struct{}{"actor_app1": {}, "actor_app2": {}} + decrypters := slices.Collect(maps.Keys(allowList)) + + t.Run("no matches, returns an error", func(t *testing.T) { + sv := &secretv0alpha1.SecureValue{ + Spec: secretv0alpha1.SecureValueSpec{ + Title: "title", Keeper: "keeper", Ref: "ref", + + Decrypters: []string{"actor_app3"}, + }, + } + + errs := ValidateSecureValue(sv, nil, admission.Create, allowList) + require.Len(t, errs, 1) + }) + + t.Run("no decrypters, returns no error", func(t *testing.T) { + sv := &secretv0alpha1.SecureValue{ + Spec: secretv0alpha1.SecureValueSpec{ + Title: "title", Keeper: "keeper", Ref: "ref", + + Decrypters: []string{}, + }, + } + + errs := ValidateSecureValue(sv, nil, admission.Create, allowList) + require.Empty(t, errs) + }) + + t.Run("one match, returns no errors", func(t *testing.T) { + sv := &secretv0alpha1.SecureValue{ + Spec: secretv0alpha1.SecureValueSpec{ + Title: "title", Keeper: "keeper", Ref: "ref", + + Decrypters: []string{decrypters[0]}, + }, + } + + errs := ValidateSecureValue(sv, nil, admission.Create, allowList) + require.Empty(t, errs) + }) + + t.Run("all matches, returns no errors", func(t *testing.T) { + sv := &secretv0alpha1.SecureValue{ + Spec: secretv0alpha1.SecureValueSpec{ + Title: "title", Keeper: "keeper", Ref: "ref", + + Decrypters: decrypters, + }, + } + + errs := ValidateSecureValue(sv, nil, admission.Create, allowList) + require.Empty(t, errs) + }) + }) +} diff --git a/pkg/registry/apis/secret/xkube/annotations.go b/pkg/registry/apis/secret/xkube/annotations.go new file mode 100644 index 00000000000..21e7cbbac74 --- /dev/null +++ b/pkg/registry/apis/secret/xkube/annotations.go @@ -0,0 +1,24 @@ +package xkube + +import "github.com/grafana/grafana/pkg/apimachinery/utils" + +var ( + // Exclude these annotations + skipAnnotations = map[string]bool{ + "kubectl.kubernetes.io/last-applied-configuration": true, // force server side apply + utils.AnnoKeyCreatedBy: true, + utils.AnnoKeyUpdatedBy: true, + utils.AnnoKeyUpdatedTimestamp: true, + } +) + +func CleanAnnotations(anno map[string]string) map[string]string { + copy := make(map[string]string) + for k, v := range anno { + if skipAnnotations[k] { + continue + } + copy[k] = v + } + return copy +} diff --git a/pkg/registry/apis/secret/xkube/errors.go b/pkg/registry/apis/secret/xkube/errors.go new file mode 100644 index 00000000000..3de10eeffcf --- /dev/null +++ b/pkg/registry/apis/secret/xkube/errors.go @@ -0,0 +1,9 @@ +package xkube + +import "k8s.io/apimachinery/pkg/util/validation/field" + +// ErrorLister is an interface compatible with errors that also returns a list of Kubernetes field errors. +type ErrorLister interface { + error + ErrorList() field.ErrorList +} diff --git a/pkg/semconv/attributes.go b/pkg/semconv/attributes.go index c05fd9f4e5b..07fc5fa6fd6 100644 --- a/pkg/semconv/attributes.go +++ b/pkg/semconv/attributes.go @@ -4,25 +4,6 @@ package semconv import "go.opentelemetry.io/otel/attribute" -// Describes Grafana service attributes. -const ( - // GrafanaServiceNameKey is the attribute Key conforming to the - // "grafana.service.name" semantic conventions. It represents the service - // name. - // - // Type: string - // RequirementLevel: Optional - // Stability: stable - // Examples: 'grafana-apiserver' - grafanaServiceNameKey = attribute.Key("grafana.service.name") -) - -// GrafanaServiceName returns an attribute KeyValue conforming to the -// "grafana.service.name" semantic conventions. It represents the service name. -func GrafanaServiceName(val string) attribute.KeyValue { - return grafanaServiceNameKey.String(val) -} - // Describes Grafana datasource attributes. const ( // GrafanaDatasourceTypeKey is the attribute Key conforming to the @@ -131,8 +112,6 @@ var ( GrafanaPluginTypeApp = grafanaPluginTypeKey.String("app") // Renderer Plugin GrafanaPluginTypeRenderer = grafanaPluginTypeKey.String("renderer") - // Secret Manager Plugin - GrafanaPluginTypeSecretmanager = grafanaPluginTypeKey.String("secretmanager") ) // GrafanaPluginId returns an attribute KeyValue conforming to the @@ -140,3 +119,22 @@ var ( func GrafanaPluginId(val string) attribute.KeyValue { return grafanaPluginIdKey.String(val) } + +// Describes Grafana service attributes. +const ( + // GrafanaServiceNameKey is the attribute Key conforming to the + // "grafana.service.name" semantic conventions. It represents the service + // name. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'grafana-apiserver' + grafanaServiceNameKey = attribute.Key("grafana.service.name") +) + +// GrafanaServiceName returns an attribute KeyValue conforming to the +// "grafana.service.name" semantic conventions. It represents the service name. +func GrafanaServiceName(val string) attribute.KeyValue { + return grafanaServiceNameKey.String(val) +} diff --git a/pkg/semconv/model/registry/plugin.yml b/pkg/semconv/model/registry/plugin.yml index 89f088fe2cb..431794bcd34 100644 --- a/pkg/semconv/model/registry/plugin.yml +++ b/pkg/semconv/model/registry/plugin.yml @@ -31,10 +31,6 @@ groups: value: "renderer" brief: 'Renderer Plugin' stability: stable - - id: secretmanager - value: "secretmanager" - brief: 'Secret Manager Plugin' - stability: stable brief: The plugin type. examples: datasource - stability: stable \ No newline at end of file + stability: stable diff --git a/pkg/server/wire.go b/pkg/server/wire.go index a3cefc7fb77..6b6bb52d3bc 100644 --- a/pkg/server/wire.go +++ b/pkg/server/wire.go @@ -363,8 +363,6 @@ var wireBasicSet = wire.NewSet( loginattemptimpl.ProvideService, wire.Bind(new(loginattempt.Service), new(*loginattemptimpl.Service)), secretsMigrations.ProvideDataSourceMigrationService, - secretsMigrations.ProvideMigrateToPluginService, - secretsMigrations.ProvideMigrateFromPluginService, secretsMigrations.ProvideSecretMigrationProvider, wire.Bind(new(secretsMigrations.SecretMigrationProvider), new(*secretsMigrations.SecretMigrationProviderImpl)), resourcepermissions.NewActionSetService, diff --git a/pkg/services/accesscontrol/permreg/permreg.go b/pkg/services/accesscontrol/permreg/permreg.go index 361b2600d08..1d46d749b38 100644 --- a/pkg/services/accesscontrol/permreg/permreg.go +++ b/pkg/services/accesscontrol/permreg/permreg.go @@ -76,24 +76,26 @@ func ProvidePermissionRegistry() PermissionRegistry { func newPermissionRegistry() *permissionRegistry { // defaultKindScopes maps the most specific accepted scope prefix for a given kind (folders, dashboards, etc) defaultKindScopes := map[string]string{ - "teams": "teams:id:", - "users": "users:id:", - "datasources": "datasources:uid:", - "dashboards": "dashboards:uid:", - "folders": "folders:uid:", - "annotations": "annotations:type:", - "apikeys": "apikeys:id:", - "orgs": "orgs:id:", - "plugins": "plugins:id:", - "provisioners": "provisioners:", - "reports": "reports:id:", - "permissions": "permissions:type:", - "serviceaccounts": "serviceaccounts:id:", - "settings": "settings:", - "global.users": "global.users:id:", - "roles": "roles:uid:", - "services": "services:", - "receivers": "receivers:uid:", + "teams": "teams:id:", + "users": "users:id:", + "datasources": "datasources:uid:", + "dashboards": "dashboards:uid:", + "folders": "folders:uid:", + "annotations": "annotations:type:", + "apikeys": "apikeys:id:", + "orgs": "orgs:id:", + "plugins": "plugins:id:", + "provisioners": "provisioners:", + "reports": "reports:id:", + "permissions": "permissions:type:", + "serviceaccounts": "serviceaccounts:id:", + "settings": "settings:", + "global.users": "global.users:id:", + "roles": "roles:uid:", + "services": "services:", + "receivers": "receivers:uid:", + "secret.securevalues": "secret.securevalues:uid:", + "secret.keepers": "secret.keepers:uid:", } return &permissionRegistry{ actionScopePrefixes: make(map[string]PrefixSet, 200), diff --git a/pkg/services/authz/rbac/mapper.go b/pkg/services/authz/rbac/mapper.go index 7635a193f7f..c930bb92fcb 100644 --- a/pkg/services/authz/rbac/mapper.go +++ b/pkg/services/authz/rbac/mapper.go @@ -63,6 +63,10 @@ func newMapper() mapper { "iam.grafana.app": { "teams": newResourceTranslation("teams", "id", false), }, + "secret.grafana.app": { + "securevalues": newResourceTranslation("secret.securevalues", "uid", false), + "keepers": newResourceTranslation("secret.keepers", "uid", false), + }, } } diff --git a/pkg/services/authz/rbac/store/permission_query.sql b/pkg/services/authz/rbac/store/permission_query.sql index dfa82390541..fea4353c259 100644 --- a/pkg/services/authz/rbac/store/permission_query.sql +++ b/pkg/services/authz/rbac/store/permission_query.sql @@ -1,21 +1,21 @@ SELECT p.kind, p.attribute, p.identifier, p.scope FROM {{ .Ident .PermissionTable }} as p +INNER JOIN ( + SELECT role_id FROM {{ .Ident .BuiltinRoleTable }} as br WHERE (br.role = {{ .Arg .Query.Role }} AND (br.org_id = {{ .Arg .Query.OrgID }} OR br.org_id = 0)) + {{ if .Query.IsServerAdmin }} + OR (br.role = 'Grafana Admin') + {{ end }} + {{ if .Query.UserID }} + UNION ALL + SELECT role_id FROM {{ .Ident .UserRoleTable }} as ur WHERE ur.user_id = {{ .Arg .Query.UserID }} AND (ur.org_id = {{ .Arg .Query.OrgID }} OR ur.org_id = 0) + {{ end }} + {{ if .Query.TeamIDs }} + UNION ALL + SELECT role_id FROM {{ .Ident .TeamRoleTable }} as tr WHERE tr.team_id IN ({{ .ArgList .Query.TeamIDs }}) AND tr.org_id = {{ .Arg .Query.OrgID }} + {{ end }} +) as roles ON p.role_id = roles.role_id WHERE {{ if .Query.ActionSets }} p.action IN ({{ .ArgList .Query.ActionSets }}, {{ .Arg .Query.Action }}) {{ else }} p.action = {{ .Arg .Query.Action }} - {{ end }} -AND p.role_id IN ( - SELECT role_id FROM {{ .Ident .BuiltinRoleTable }} as br WHERE (br.role = {{ .Arg .Query.Role }} AND (br.org_id = {{ .Arg .Query.OrgID }} OR br.org_id = 0)) - {{ if .Query.IsServerAdmin }} - OR (br.role = 'Grafana Admin') - {{ end }} - {{ if .Query.UserID }} - UNION - SELECT role_id FROM {{ .Ident .UserRoleTable }} as ur WHERE ur.user_id = {{ .Arg .Query.UserID }} AND (ur.org_id = {{ .Arg .Query.OrgID }} OR ur.org_id = 0) - {{ end }} - {{ if .Query.TeamIDs }} - UNION - SELECT role_id FROM {{ .Ident .TeamRoleTable }} as tr WHERE tr.team_id IN ({{ .ArgList .Query.TeamIDs }}) AND tr.org_id = {{ .Arg .Query.OrgID }} - {{ end }} -) + {{ end }} \ No newline at end of file diff --git a/pkg/services/authz/rbac/store/testdata/mysql--permission_query-With_action_sets.sql b/pkg/services/authz/rbac/store/testdata/mysql--permission_query-With_action_sets.sql index 0393be57d3f..443e4b6f518 100755 --- a/pkg/services/authz/rbac/store/testdata/mysql--permission_query-With_action_sets.sql +++ b/pkg/services/authz/rbac/store/testdata/mysql--permission_query-With_action_sets.sql @@ -1,8 +1,8 @@ SELECT p.kind, p.attribute, p.identifier, p.scope FROM `grafana`.`permission` as p +INNER JOIN ( + SELECT role_id FROM `grafana`.`builtin_role` as br WHERE (br.role = 'Viewer' AND (br.org_id = 1 OR br.org_id = 0)) + UNION ALL + SELECT role_id FROM `grafana`.`user_role` as ur WHERE ur.user_id = 1 AND (ur.org_id = 1 OR ur.org_id = 0) +) as roles ON p.role_id = roles.role_id WHERE p.action IN ('folders:edit', 'folders:admin', 'folders:create') -AND p.role_id IN ( - SELECT role_id FROM `grafana`.`builtin_role` as br WHERE (br.role = 'Viewer' AND (br.org_id = 1 OR br.org_id = 0)) - UNION - SELECT role_id FROM `grafana`.`user_role` as ur WHERE ur.user_id = 1 AND (ur.org_id = 1 OR ur.org_id = 0) -) diff --git a/pkg/services/authz/rbac/store/testdata/mysql--permission_query-admin_user.sql b/pkg/services/authz/rbac/store/testdata/mysql--permission_query-admin_user.sql index ba9f7064c91..39cdf20bd5d 100755 --- a/pkg/services/authz/rbac/store/testdata/mysql--permission_query-admin_user.sql +++ b/pkg/services/authz/rbac/store/testdata/mysql--permission_query-admin_user.sql @@ -1,9 +1,9 @@ SELECT p.kind, p.attribute, p.identifier, p.scope FROM `grafana`.`permission` as p +INNER JOIN ( + SELECT role_id FROM `grafana`.`builtin_role` as br WHERE (br.role = 'Admin' AND (br.org_id = 1 OR br.org_id = 0)) + OR (br.role = 'Grafana Admin') + UNION ALL + SELECT role_id FROM `grafana`.`user_role` as ur WHERE ur.user_id = 1 AND (ur.org_id = 1 OR ur.org_id = 0) +) as roles ON p.role_id = roles.role_id WHERE p.action = 'folders:read' -AND p.role_id IN ( - SELECT role_id FROM `grafana`.`builtin_role` as br WHERE (br.role = 'Admin' AND (br.org_id = 1 OR br.org_id = 0)) - OR (br.role = 'Grafana Admin') - UNION - SELECT role_id FROM `grafana`.`user_role` as ur WHERE ur.user_id = 1 AND (ur.org_id = 1 OR ur.org_id = 0) -) diff --git a/pkg/services/authz/rbac/store/testdata/mysql--permission_query-anonymous_user.sql b/pkg/services/authz/rbac/store/testdata/mysql--permission_query-anonymous_user.sql index 99959aeef2d..040afd50301 100755 --- a/pkg/services/authz/rbac/store/testdata/mysql--permission_query-anonymous_user.sql +++ b/pkg/services/authz/rbac/store/testdata/mysql--permission_query-anonymous_user.sql @@ -1,6 +1,6 @@ SELECT p.kind, p.attribute, p.identifier, p.scope FROM `grafana`.`permission` as p +INNER JOIN ( + SELECT role_id FROM `grafana`.`builtin_role` as br WHERE (br.role = 'Viewer' AND (br.org_id = 1 OR br.org_id = 0)) +) as roles ON p.role_id = roles.role_id WHERE p.action = 'folders:read' -AND p.role_id IN ( - SELECT role_id FROM `grafana`.`builtin_role` as br WHERE (br.role = 'Viewer' AND (br.org_id = 1 OR br.org_id = 0)) -) diff --git a/pkg/services/authz/rbac/store/testdata/mysql--permission_query-user_with_teams.sql b/pkg/services/authz/rbac/store/testdata/mysql--permission_query-user_with_teams.sql index d47a6b8226d..efd22a9b118 100755 --- a/pkg/services/authz/rbac/store/testdata/mysql--permission_query-user_with_teams.sql +++ b/pkg/services/authz/rbac/store/testdata/mysql--permission_query-user_with_teams.sql @@ -1,10 +1,10 @@ SELECT p.kind, p.attribute, p.identifier, p.scope FROM `grafana`.`permission` as p +INNER JOIN ( + SELECT role_id FROM `grafana`.`builtin_role` as br WHERE (br.role = 'None' AND (br.org_id = 1 OR br.org_id = 0)) + UNION ALL + SELECT role_id FROM `grafana`.`user_role` as ur WHERE ur.user_id = 1 AND (ur.org_id = 1 OR ur.org_id = 0) + UNION ALL + SELECT role_id FROM `grafana`.`team_role` as tr WHERE tr.team_id IN (1, 2) AND tr.org_id = 1 +) as roles ON p.role_id = roles.role_id WHERE p.action = 'folders:read' -AND p.role_id IN ( - SELECT role_id FROM `grafana`.`builtin_role` as br WHERE (br.role = 'None' AND (br.org_id = 1 OR br.org_id = 0)) - UNION - SELECT role_id FROM `grafana`.`user_role` as ur WHERE ur.user_id = 1 AND (ur.org_id = 1 OR ur.org_id = 0) - UNION - SELECT role_id FROM `grafana`.`team_role` as tr WHERE tr.team_id IN (1, 2) AND tr.org_id = 1 -) diff --git a/pkg/services/authz/rbac/store/testdata/mysql--permission_query-viewer_user.sql b/pkg/services/authz/rbac/store/testdata/mysql--permission_query-viewer_user.sql index 15f8dde3169..17746dd3319 100755 --- a/pkg/services/authz/rbac/store/testdata/mysql--permission_query-viewer_user.sql +++ b/pkg/services/authz/rbac/store/testdata/mysql--permission_query-viewer_user.sql @@ -1,8 +1,8 @@ SELECT p.kind, p.attribute, p.identifier, p.scope FROM `grafana`.`permission` as p +INNER JOIN ( + SELECT role_id FROM `grafana`.`builtin_role` as br WHERE (br.role = 'Viewer' AND (br.org_id = 1 OR br.org_id = 0)) + UNION ALL + SELECT role_id FROM `grafana`.`user_role` as ur WHERE ur.user_id = 1 AND (ur.org_id = 1 OR ur.org_id = 0) +) as roles ON p.role_id = roles.role_id WHERE p.action = 'folders:read' -AND p.role_id IN ( - SELECT role_id FROM `grafana`.`builtin_role` as br WHERE (br.role = 'Viewer' AND (br.org_id = 1 OR br.org_id = 0)) - UNION - SELECT role_id FROM `grafana`.`user_role` as ur WHERE ur.user_id = 1 AND (ur.org_id = 1 OR ur.org_id = 0) -) diff --git a/pkg/services/authz/rbac/store/testdata/postgres--permission_query-With_action_sets.sql b/pkg/services/authz/rbac/store/testdata/postgres--permission_query-With_action_sets.sql index 8c35badfb31..5759d3b0017 100755 --- a/pkg/services/authz/rbac/store/testdata/postgres--permission_query-With_action_sets.sql +++ b/pkg/services/authz/rbac/store/testdata/postgres--permission_query-With_action_sets.sql @@ -1,8 +1,8 @@ SELECT p.kind, p.attribute, p.identifier, p.scope FROM "grafana"."permission" as p +INNER JOIN ( + SELECT role_id FROM "grafana"."builtin_role" as br WHERE (br.role = 'Viewer' AND (br.org_id = 1 OR br.org_id = 0)) + UNION ALL + SELECT role_id FROM "grafana"."user_role" as ur WHERE ur.user_id = 1 AND (ur.org_id = 1 OR ur.org_id = 0) +) as roles ON p.role_id = roles.role_id WHERE p.action IN ('folders:edit', 'folders:admin', 'folders:create') -AND p.role_id IN ( - SELECT role_id FROM "grafana"."builtin_role" as br WHERE (br.role = 'Viewer' AND (br.org_id = 1 OR br.org_id = 0)) - UNION - SELECT role_id FROM "grafana"."user_role" as ur WHERE ur.user_id = 1 AND (ur.org_id = 1 OR ur.org_id = 0) -) diff --git a/pkg/services/authz/rbac/store/testdata/postgres--permission_query-admin_user.sql b/pkg/services/authz/rbac/store/testdata/postgres--permission_query-admin_user.sql index c78b0cbd2aa..45e8dc2b6fd 100755 --- a/pkg/services/authz/rbac/store/testdata/postgres--permission_query-admin_user.sql +++ b/pkg/services/authz/rbac/store/testdata/postgres--permission_query-admin_user.sql @@ -1,9 +1,9 @@ SELECT p.kind, p.attribute, p.identifier, p.scope FROM "grafana"."permission" as p +INNER JOIN ( + SELECT role_id FROM "grafana"."builtin_role" as br WHERE (br.role = 'Admin' AND (br.org_id = 1 OR br.org_id = 0)) + OR (br.role = 'Grafana Admin') + UNION ALL + SELECT role_id FROM "grafana"."user_role" as ur WHERE ur.user_id = 1 AND (ur.org_id = 1 OR ur.org_id = 0) +) as roles ON p.role_id = roles.role_id WHERE p.action = 'folders:read' -AND p.role_id IN ( - SELECT role_id FROM "grafana"."builtin_role" as br WHERE (br.role = 'Admin' AND (br.org_id = 1 OR br.org_id = 0)) - OR (br.role = 'Grafana Admin') - UNION - SELECT role_id FROM "grafana"."user_role" as ur WHERE ur.user_id = 1 AND (ur.org_id = 1 OR ur.org_id = 0) -) diff --git a/pkg/services/authz/rbac/store/testdata/postgres--permission_query-anonymous_user.sql b/pkg/services/authz/rbac/store/testdata/postgres--permission_query-anonymous_user.sql index 20544064211..8af7430873d 100755 --- a/pkg/services/authz/rbac/store/testdata/postgres--permission_query-anonymous_user.sql +++ b/pkg/services/authz/rbac/store/testdata/postgres--permission_query-anonymous_user.sql @@ -1,6 +1,6 @@ SELECT p.kind, p.attribute, p.identifier, p.scope FROM "grafana"."permission" as p +INNER JOIN ( + SELECT role_id FROM "grafana"."builtin_role" as br WHERE (br.role = 'Viewer' AND (br.org_id = 1 OR br.org_id = 0)) +) as roles ON p.role_id = roles.role_id WHERE p.action = 'folders:read' -AND p.role_id IN ( - SELECT role_id FROM "grafana"."builtin_role" as br WHERE (br.role = 'Viewer' AND (br.org_id = 1 OR br.org_id = 0)) -) diff --git a/pkg/services/authz/rbac/store/testdata/postgres--permission_query-user_with_teams.sql b/pkg/services/authz/rbac/store/testdata/postgres--permission_query-user_with_teams.sql index ae269975c83..e07071250ea 100755 --- a/pkg/services/authz/rbac/store/testdata/postgres--permission_query-user_with_teams.sql +++ b/pkg/services/authz/rbac/store/testdata/postgres--permission_query-user_with_teams.sql @@ -1,10 +1,10 @@ SELECT p.kind, p.attribute, p.identifier, p.scope FROM "grafana"."permission" as p +INNER JOIN ( + SELECT role_id FROM "grafana"."builtin_role" as br WHERE (br.role = 'None' AND (br.org_id = 1 OR br.org_id = 0)) + UNION ALL + SELECT role_id FROM "grafana"."user_role" as ur WHERE ur.user_id = 1 AND (ur.org_id = 1 OR ur.org_id = 0) + UNION ALL + SELECT role_id FROM "grafana"."team_role" as tr WHERE tr.team_id IN (1, 2) AND tr.org_id = 1 +) as roles ON p.role_id = roles.role_id WHERE p.action = 'folders:read' -AND p.role_id IN ( - SELECT role_id FROM "grafana"."builtin_role" as br WHERE (br.role = 'None' AND (br.org_id = 1 OR br.org_id = 0)) - UNION - SELECT role_id FROM "grafana"."user_role" as ur WHERE ur.user_id = 1 AND (ur.org_id = 1 OR ur.org_id = 0) - UNION - SELECT role_id FROM "grafana"."team_role" as tr WHERE tr.team_id IN (1, 2) AND tr.org_id = 1 -) diff --git a/pkg/services/authz/rbac/store/testdata/postgres--permission_query-viewer_user.sql b/pkg/services/authz/rbac/store/testdata/postgres--permission_query-viewer_user.sql index 3a78a730602..a2a6695ef1d 100755 --- a/pkg/services/authz/rbac/store/testdata/postgres--permission_query-viewer_user.sql +++ b/pkg/services/authz/rbac/store/testdata/postgres--permission_query-viewer_user.sql @@ -1,8 +1,8 @@ SELECT p.kind, p.attribute, p.identifier, p.scope FROM "grafana"."permission" as p +INNER JOIN ( + SELECT role_id FROM "grafana"."builtin_role" as br WHERE (br.role = 'Viewer' AND (br.org_id = 1 OR br.org_id = 0)) + UNION ALL + SELECT role_id FROM "grafana"."user_role" as ur WHERE ur.user_id = 1 AND (ur.org_id = 1 OR ur.org_id = 0) +) as roles ON p.role_id = roles.role_id WHERE p.action = 'folders:read' -AND p.role_id IN ( - SELECT role_id FROM "grafana"."builtin_role" as br WHERE (br.role = 'Viewer' AND (br.org_id = 1 OR br.org_id = 0)) - UNION - SELECT role_id FROM "grafana"."user_role" as ur WHERE ur.user_id = 1 AND (ur.org_id = 1 OR ur.org_id = 0) -) diff --git a/pkg/services/authz/rbac/store/testdata/sqlite--permission_query-With_action_sets.sql b/pkg/services/authz/rbac/store/testdata/sqlite--permission_query-With_action_sets.sql index 8c35badfb31..5759d3b0017 100755 --- a/pkg/services/authz/rbac/store/testdata/sqlite--permission_query-With_action_sets.sql +++ b/pkg/services/authz/rbac/store/testdata/sqlite--permission_query-With_action_sets.sql @@ -1,8 +1,8 @@ SELECT p.kind, p.attribute, p.identifier, p.scope FROM "grafana"."permission" as p +INNER JOIN ( + SELECT role_id FROM "grafana"."builtin_role" as br WHERE (br.role = 'Viewer' AND (br.org_id = 1 OR br.org_id = 0)) + UNION ALL + SELECT role_id FROM "grafana"."user_role" as ur WHERE ur.user_id = 1 AND (ur.org_id = 1 OR ur.org_id = 0) +) as roles ON p.role_id = roles.role_id WHERE p.action IN ('folders:edit', 'folders:admin', 'folders:create') -AND p.role_id IN ( - SELECT role_id FROM "grafana"."builtin_role" as br WHERE (br.role = 'Viewer' AND (br.org_id = 1 OR br.org_id = 0)) - UNION - SELECT role_id FROM "grafana"."user_role" as ur WHERE ur.user_id = 1 AND (ur.org_id = 1 OR ur.org_id = 0) -) diff --git a/pkg/services/authz/rbac/store/testdata/sqlite--permission_query-admin_user.sql b/pkg/services/authz/rbac/store/testdata/sqlite--permission_query-admin_user.sql index c78b0cbd2aa..45e8dc2b6fd 100755 --- a/pkg/services/authz/rbac/store/testdata/sqlite--permission_query-admin_user.sql +++ b/pkg/services/authz/rbac/store/testdata/sqlite--permission_query-admin_user.sql @@ -1,9 +1,9 @@ SELECT p.kind, p.attribute, p.identifier, p.scope FROM "grafana"."permission" as p +INNER JOIN ( + SELECT role_id FROM "grafana"."builtin_role" as br WHERE (br.role = 'Admin' AND (br.org_id = 1 OR br.org_id = 0)) + OR (br.role = 'Grafana Admin') + UNION ALL + SELECT role_id FROM "grafana"."user_role" as ur WHERE ur.user_id = 1 AND (ur.org_id = 1 OR ur.org_id = 0) +) as roles ON p.role_id = roles.role_id WHERE p.action = 'folders:read' -AND p.role_id IN ( - SELECT role_id FROM "grafana"."builtin_role" as br WHERE (br.role = 'Admin' AND (br.org_id = 1 OR br.org_id = 0)) - OR (br.role = 'Grafana Admin') - UNION - SELECT role_id FROM "grafana"."user_role" as ur WHERE ur.user_id = 1 AND (ur.org_id = 1 OR ur.org_id = 0) -) diff --git a/pkg/services/authz/rbac/store/testdata/sqlite--permission_query-anonymous_user.sql b/pkg/services/authz/rbac/store/testdata/sqlite--permission_query-anonymous_user.sql index 20544064211..8af7430873d 100755 --- a/pkg/services/authz/rbac/store/testdata/sqlite--permission_query-anonymous_user.sql +++ b/pkg/services/authz/rbac/store/testdata/sqlite--permission_query-anonymous_user.sql @@ -1,6 +1,6 @@ SELECT p.kind, p.attribute, p.identifier, p.scope FROM "grafana"."permission" as p +INNER JOIN ( + SELECT role_id FROM "grafana"."builtin_role" as br WHERE (br.role = 'Viewer' AND (br.org_id = 1 OR br.org_id = 0)) +) as roles ON p.role_id = roles.role_id WHERE p.action = 'folders:read' -AND p.role_id IN ( - SELECT role_id FROM "grafana"."builtin_role" as br WHERE (br.role = 'Viewer' AND (br.org_id = 1 OR br.org_id = 0)) -) diff --git a/pkg/services/authz/rbac/store/testdata/sqlite--permission_query-user_with_teams.sql b/pkg/services/authz/rbac/store/testdata/sqlite--permission_query-user_with_teams.sql index ae269975c83..e07071250ea 100755 --- a/pkg/services/authz/rbac/store/testdata/sqlite--permission_query-user_with_teams.sql +++ b/pkg/services/authz/rbac/store/testdata/sqlite--permission_query-user_with_teams.sql @@ -1,10 +1,10 @@ SELECT p.kind, p.attribute, p.identifier, p.scope FROM "grafana"."permission" as p +INNER JOIN ( + SELECT role_id FROM "grafana"."builtin_role" as br WHERE (br.role = 'None' AND (br.org_id = 1 OR br.org_id = 0)) + UNION ALL + SELECT role_id FROM "grafana"."user_role" as ur WHERE ur.user_id = 1 AND (ur.org_id = 1 OR ur.org_id = 0) + UNION ALL + SELECT role_id FROM "grafana"."team_role" as tr WHERE tr.team_id IN (1, 2) AND tr.org_id = 1 +) as roles ON p.role_id = roles.role_id WHERE p.action = 'folders:read' -AND p.role_id IN ( - SELECT role_id FROM "grafana"."builtin_role" as br WHERE (br.role = 'None' AND (br.org_id = 1 OR br.org_id = 0)) - UNION - SELECT role_id FROM "grafana"."user_role" as ur WHERE ur.user_id = 1 AND (ur.org_id = 1 OR ur.org_id = 0) - UNION - SELECT role_id FROM "grafana"."team_role" as tr WHERE tr.team_id IN (1, 2) AND tr.org_id = 1 -) diff --git a/pkg/services/authz/rbac/store/testdata/sqlite--permission_query-viewer_user.sql b/pkg/services/authz/rbac/store/testdata/sqlite--permission_query-viewer_user.sql index 3a78a730602..a2a6695ef1d 100755 --- a/pkg/services/authz/rbac/store/testdata/sqlite--permission_query-viewer_user.sql +++ b/pkg/services/authz/rbac/store/testdata/sqlite--permission_query-viewer_user.sql @@ -1,8 +1,8 @@ SELECT p.kind, p.attribute, p.identifier, p.scope FROM "grafana"."permission" as p +INNER JOIN ( + SELECT role_id FROM "grafana"."builtin_role" as br WHERE (br.role = 'Viewer' AND (br.org_id = 1 OR br.org_id = 0)) + UNION ALL + SELECT role_id FROM "grafana"."user_role" as ur WHERE ur.user_id = 1 AND (ur.org_id = 1 OR ur.org_id = 0) +) as roles ON p.role_id = roles.role_id WHERE p.action = 'folders:read' -AND p.role_id IN ( - SELECT role_id FROM "grafana"."builtin_role" as br WHERE (br.role = 'Viewer' AND (br.org_id = 1 OR br.org_id = 0)) - UNION - SELECT role_id FROM "grafana"."user_role" as ur WHERE ur.user_id = 1 AND (ur.org_id = 1 OR ur.org_id = 0) -) diff --git a/pkg/services/datasources/fakes/fake_datasource_service.go b/pkg/services/datasources/fakes/fake_datasource_service.go index 43a71852c43..c23024b4e5b 100644 --- a/pkg/services/datasources/fakes/fake_datasource_service.go +++ b/pkg/services/datasources/fakes/fake_datasource_service.go @@ -122,11 +122,7 @@ func (s *FakeDataSourceService) GetHTTPTransport(ctx context.Context, ds *dataso } func (s *FakeDataSourceService) DecryptedValues(ctx context.Context, ds *datasources.DataSource) (map[string]string, error) { - if s.SimulatePluginFailure { - return nil, datasources.ErrDatasourceSecretsPluginUserFriendly{Err: "unknown error"} - } - values := make(map[string]string) - return values, nil + return make(map[string]string), nil } func (s *FakeDataSourceService) DecryptedValue(ctx context.Context, ds *datasources.DataSource, key string) (string, bool, error) { diff --git a/pkg/services/datasources/models.go b/pkg/services/datasources/models.go index 84592aadda6..eb4f51f389c 100644 --- a/pkg/services/datasources/models.go +++ b/pkg/services/datasources/models.go @@ -145,15 +145,6 @@ func (ds DataSource) AllowedCookies() []string { return []string{} } -// Specific error type for grpc secrets management so that we can show more detailed plugin errors to users -type ErrDatasourceSecretsPluginUserFriendly struct { - Err string -} - -func (e ErrDatasourceSecretsPluginUserFriendly) Error() string { - return e.Err -} - // ---------------------- // COMMANDS diff --git a/pkg/services/featuremgmt/registry.go b/pkg/services/featuremgmt/registry.go index 35a4f9d66d0..da680c84add 100644 --- a/pkg/services/featuremgmt/registry.go +++ b/pkg/services/featuremgmt/registry.go @@ -376,7 +376,7 @@ var ( { Name: "frontendSandboxMonitorOnly", Description: "Enables monitor only in the plugin frontend sandbox (if enabled)", - Stage: FeatureStageExperimental, + Stage: FeatureStagePrivatePreview, FrontendOnly: true, Owner: grafanaPluginsPlatformSquad, }, @@ -1421,8 +1421,9 @@ var ( { Name: "pluginsSriChecks", Description: "Enables SRI checks for plugin assets", - Stage: FeatureStageExperimental, + Stage: FeatureStageGeneralAvailability, Owner: grafanaPluginsPlatformSquad, + Expression: "false", // disabled by default }, { Name: "unifiedStorageBigObjectsSupport", diff --git a/pkg/services/featuremgmt/toggles_gen.csv b/pkg/services/featuremgmt/toggles_gen.csv index adbc4852c09..9d6dc6fd264 100644 --- a/pkg/services/featuremgmt/toggles_gen.csv +++ b/pkg/services/featuremgmt/toggles_gen.csv @@ -49,7 +49,7 @@ enableDatagridEditing,preview,@grafana/dataviz-squad,false,false,true extraThemes,experimental,@grafana/grafana-frontend-platform,false,false,true lokiPredefinedOperations,experimental,@grafana/observability-logs,false,false,true pluginsFrontendSandbox,privatePreview,@grafana/plugins-platform-backend,false,false,false -frontendSandboxMonitorOnly,experimental,@grafana/plugins-platform-backend,false,false,true +frontendSandboxMonitorOnly,privatePreview,@grafana/plugins-platform-backend,false,false,true pluginsDetailsRightPanel,privatePreview,@grafana/plugins-platform-backend,false,false,true sqlDatasourceDatabaseSelection,preview,@grafana/dataviz-squad,false,false,true recordedQueriesMulti,GA,@grafana/observability-metrics,false,false,false @@ -186,7 +186,7 @@ unifiedStorageSearch,experimental,@grafana/search-and-storage,false,false,false unifiedStorageSearchSprinkles,experimental,@grafana/search-and-storage,false,false,false unifiedStorageSearchPermissionFiltering,experimental,@grafana/search-and-storage,false,false,false managedDualWriter,experimental,@grafana/search-and-storage,false,false,false -pluginsSriChecks,experimental,@grafana/plugins-platform-backend,false,false,false +pluginsSriChecks,GA,@grafana/plugins-platform-backend,false,false,false unifiedStorageBigObjectsSupport,experimental,@grafana/search-and-storage,false,false,false timeRangeProvider,experimental,@grafana/grafana-frontend-platform,false,false,false prometheusUsesCombobox,experimental,@grafana/oss-big-tent,false,false,false diff --git a/pkg/services/featuremgmt/toggles_gen.json b/pkg/services/featuremgmt/toggles_gen.json index ea5b78a2cdd..0d0fe4b0a57 100644 --- a/pkg/services/featuremgmt/toggles_gen.json +++ b/pkg/services/featuremgmt/toggles_gen.json @@ -1913,12 +1913,15 @@ { "metadata": { "name": "frontendSandboxMonitorOnly", - "resourceVersion": "1718727528075", - "creationTimestamp": "2023-07-05T11:48:25Z" + "resourceVersion": "1741965568775", + "creationTimestamp": "2023-07-05T11:48:25Z", + "annotations": { + "grafana.app/updatedTimestamp": "2025-03-14 15:19:28.77575 +0000 UTC" + } }, "spec": { "description": "Enables monitor only in the plugin frontend sandbox (if enabled)", - "stage": "experimental", + "stage": "privatePreview", "codeowner": "@grafana/plugins-platform-backend", "frontend": true } @@ -3281,13 +3284,17 @@ { "metadata": { "name": "pluginsSriChecks", - "resourceVersion": "1727785264632", - "creationTimestamp": "2024-10-04T12:55:09Z" + "resourceVersion": "1742405194835", + "creationTimestamp": "2024-10-04T12:55:09Z", + "annotations": { + "grafana.app/updatedTimestamp": "2025-03-19 17:26:34.83562 +0000 UTC" + } }, "spec": { "description": "Enables SRI checks for plugin assets", - "stage": "experimental", - "codeowner": "@grafana/plugins-platform-backend" + "stage": "GA", + "codeowner": "@grafana/plugins-platform-backend", + "expression": "false" } }, { diff --git a/pkg/services/navtree/navtreeimpl/navtree.go b/pkg/services/navtree/navtreeimpl/navtree.go index facf246f171..e859596520f 100644 --- a/pkg/services/navtree/navtreeimpl/navtree.go +++ b/pkg/services/navtree/navtreeimpl/navtree.go @@ -23,6 +23,8 @@ import ( "github.com/grafana/grafana/pkg/services/star" "github.com/grafana/grafana/pkg/services/supportbundles/supportbundlesimpl" "github.com/grafana/grafana/pkg/setting" + + "github.com/open-feature/go-sdk/openfeature" ) type ServiceImpl struct { @@ -34,6 +36,7 @@ type ServiceImpl struct { pluginSettings pluginsettings.Service starService star.Service features featuremgmt.FeatureToggles + openFeature *featuremgmt.OpenFeatureService dashboardService dashboards.DashboardService accesscontrolService ac.Service kvStore kvstore.KVStore @@ -54,7 +57,7 @@ type NavigationAppConfig struct { func ProvideService(cfg *setting.Cfg, accessControl ac.AccessControl, pluginStore pluginstore.Store, pluginSettings pluginsettings.Service, starService star.Service, features featuremgmt.FeatureToggles, dashboardService dashboards.DashboardService, accesscontrolService ac.Service, kvStore kvstore.KVStore, apiKeyService apikey.Service, - license licensing.Licensing, authnService authn.Service) navtree.Service { + license licensing.Licensing, authnService authn.Service, openFeature *featuremgmt.OpenFeatureService) navtree.Service { service := &ServiceImpl{ cfg: cfg, log: log.New("navtree service"), @@ -64,6 +67,7 @@ func ProvideService(cfg *setting.Cfg, accessControl ac.AccessControl, pluginStor pluginSettings: pluginSettings, starService: starService, features: features, + openFeature: openFeature, dashboardService: dashboardService, accesscontrolService: accesscontrolService, kvStore: kvStore, @@ -80,6 +84,7 @@ func ProvideService(cfg *setting.Cfg, accessControl ac.AccessControl, pluginStor func (s *ServiceImpl) GetNavTree(c *contextmodel.ReqContext, prefs *pref.Preference) (*navtree.NavTreeRoot, error) { hasAccess := ac.HasAccess(s.accessControl, c) treeRoot := &navtree.NavTreeRoot{} + ctx := c.Req.Context() treeRoot.AddSection(s.getHomeNode(c, prefs)) @@ -184,7 +189,8 @@ func (s *ServiceImpl) GetNavTree(c *contextmodel.ReqContext, prefs *pref.Prefere treeRoot.RemoveSectionByID(navtree.NavIDCfg) } - if s.features.IsEnabled(c.Req.Context(), featuremgmt.FlagPinNavItems) && c.IsSignedIn { + enabled := s.openFeature.Client.Boolean(ctx, featuremgmt.FlagPinNavItems, true, openfeature.TransactionContext(ctx)) + if enabled && c.IsSignedIn { treeRoot.AddSection(&navtree.NavLink{ Text: "Bookmarks", Id: navtree.NavIDBookmarks, diff --git a/pkg/services/ngalert/api/tooling/definitions/alertmanager.go b/pkg/services/ngalert/api/tooling/definitions/alertmanager.go index 5a9c6a5b868..23cfdae71d8 100644 --- a/pkg/services/ngalert/api/tooling/definitions/alertmanager.go +++ b/pkg/services/ngalert/api/tooling/definitions/alertmanager.go @@ -7,7 +7,6 @@ import ( "time" "github.com/go-openapi/strfmt" - "github.com/mohae/deepcopy" amv2 "github.com/prometheus/alertmanager/api/v2/models" "github.com/prometheus/alertmanager/config" "github.com/prometheus/common/model" @@ -698,26 +697,6 @@ func (c *PostableUserConfig) validate() error { return nil } -// Decrypt returns a copy of the configuration struct with decrypted secure settings in receivers. -func (c *PostableUserConfig) Decrypt(decryptFn func(payload []byte) ([]byte, error)) (PostableUserConfig, error) { - newCfg, ok := deepcopy.Copy(c).(*PostableUserConfig) - if !ok { - return PostableUserConfig{}, fmt.Errorf("failed to copy config") - } - - // Iterate through receivers and decrypt secure settings. - for _, rcv := range newCfg.AlertmanagerConfig.Receivers { - for _, gmr := range rcv.PostableGrafanaReceivers.GrafanaManagedReceivers { - decrypted, err := gmr.DecryptSecureSettings(decryptFn) - if err != nil { - return PostableUserConfig{}, err - } - gmr.SecureSettings = decrypted - } - } - return *newCfg, nil -} - // GetGrafanaReceiverMap returns a map that associates UUIDs to grafana receivers func (c *PostableUserConfig) GetGrafanaReceiverMap() map[string]*PostableGrafanaReceiver { UIDs := make(map[string]*PostableGrafanaReceiver) diff --git a/pkg/services/ngalert/remote/alertmanager.go b/pkg/services/ngalert/remote/alertmanager.go index 5928c8f6dcb..3f0821889d2 100644 --- a/pkg/services/ngalert/remote/alertmanager.go +++ b/pkg/services/ngalert/remote/alertmanager.go @@ -255,17 +255,22 @@ func (am *Alertmanager) CompareAndSendConfiguration(ctx context.Context, config if err := am.autogenFn(ctx, am.log, am.orgID, &c.AlertmanagerConfig, true); err != nil { return err } - decrypted, err := am.decryptConfiguration(ctx, c) + rawDecrypted, configHash, err := am.decryptConfiguration(ctx, c) if err != nil { return err } // Send the configuration only if we need to. - if !am.shouldSendConfig(ctx, decrypted) { + if !am.shouldSendConfig(ctx, configHash) { return nil } - isDefault, err := am.isDefaultConfiguration(decrypted) + isDefault, err := am.isDefaultConfiguration(configHash) + if err != nil { + return err + } + + decrypted, err := notifier.Load(rawDecrypted) if err != nil { return err } @@ -273,27 +278,37 @@ func (am *Alertmanager) CompareAndSendConfiguration(ctx context.Context, config return am.sendConfiguration(ctx, decrypted, config.ConfigurationHash, config.CreatedAt, isDefault) } -func (am *Alertmanager) isDefaultConfiguration(cfg *apimodels.PostableUserConfig) (bool, error) { - rawCfg, err := json.Marshal(cfg) - if err != nil { - return false, err - } - - configHash := fmt.Sprintf("%x", md5.Sum(rawCfg)) - - return configHash == am.defaultConfigHash, nil +func (am *Alertmanager) isDefaultConfiguration(configHash [16]byte) (bool, error) { + return fmt.Sprintf("%x", configHash) == am.defaultConfigHash, nil } -func (am *Alertmanager) decryptConfiguration(ctx context.Context, cfg *apimodels.PostableUserConfig) (*apimodels.PostableUserConfig, error) { +// decryptConfiguration decrypts the configuration in-place and returns the decrypted configuration alongside its hash. +// Should not be used outside of this package and the specific use case of decrypting the configuration before sending +// it to the remote Alertmanager. +func (am *Alertmanager) decryptConfiguration(ctx context.Context, cfg *apimodels.PostableUserConfig) ([]byte, [16]byte, error) { fn := func(payload []byte) ([]byte, error) { return am.decrypt(ctx, payload) } - decrypted, err := cfg.Decrypt(fn) - if err != nil { - return nil, fmt.Errorf("unable to decrypt the configuration: %w", err) + + // Iterate through receivers and decrypt secure settings. + // It's not necessary to be careful about not modifying the original, as it's used only in a specific context where + // the config is read from json and then immediately sent to the remote Alertmanager. + for _, rcv := range cfg.AlertmanagerConfig.Receivers { + for _, gmr := range rcv.PostableGrafanaReceivers.GrafanaManagedReceivers { + decrypted, err := gmr.DecryptSecureSettings(fn) + if err != nil { + return nil, [16]byte{}, fmt.Errorf("unable to decrypt settings on receiver %q (uid: %q): %w", gmr.Name, gmr.UID, err) + } + gmr.SecureSettings = decrypted + } } - return &decrypted, nil + rawDecrypted, err := json.Marshal(cfg) + if err != nil { + return nil, [16]byte{}, fmt.Errorf("unable to marshal decrypted configuration: %w", err) + } + + return rawDecrypted, md5.Sum(rawDecrypted), nil } func (am *Alertmanager) sendConfiguration(ctx context.Context, decrypted *apimodels.PostableUserConfig, hash string, createdAt int64, isDefault bool) error { @@ -345,7 +360,12 @@ func (am *Alertmanager) SaveAndApplyConfig(ctx context.Context, cfg *apimodels.P if err := am.autogenFn(ctx, am.log, am.orgID, &cfg.AlertmanagerConfig, false); err != nil { return err } - decrypted, err := am.decryptConfiguration(ctx, cfg) + rawDecrypted, _, err := am.decryptConfiguration(ctx, cfg) + if err != nil { + return err + } + + decrypted, err := notifier.Load(rawDecrypted) if err != nil { return err } @@ -364,7 +384,12 @@ func (am *Alertmanager) SaveAndApplyDefaultConfig(ctx context.Context) error { if err := am.autogenFn(ctx, am.log, am.orgID, &c.AlertmanagerConfig, true); err != nil { return err } - decrypted, err := am.decryptConfiguration(ctx, c) + rawDecrypted, _, err := am.decryptConfiguration(ctx, c) + if err != nil { + return err + } + + decrypted, err := notifier.Load(rawDecrypted) if err != nil { return err } @@ -634,7 +659,7 @@ func (am *Alertmanager) getFullState(ctx context.Context) (string, error) { // shouldSendConfig compares the remote Alertmanager configuration with our local one. // It returns true if the configurations are different. -func (am *Alertmanager) shouldSendConfig(ctx context.Context, config *apimodels.PostableUserConfig) bool { +func (am *Alertmanager) shouldSendConfig(ctx context.Context, hash [16]byte) bool { rc, err := am.mimirClient.GetGrafanaAlertmanagerConfig(ctx) if err != nil { // Log the error and return true so we try to upload our config anyway. @@ -651,12 +676,7 @@ func (am *Alertmanager) shouldSendConfig(ctx context.Context, config *apimodels. am.log.Error("Unable to marshal the remote Alertmanager configuration for comparison", "err", err) return true } - rawInternal, err := json.Marshal(config) - if err != nil { - am.log.Error("Unable to marshal the internal Alertmanager configuration for comparison", "err", err) - return true - } - return md5.Sum(rawRemote) != md5.Sum(rawInternal) + return md5.Sum(rawRemote) != hash } // shouldSendState compares the remote Alertmanager state with our local one. diff --git a/pkg/services/ngalert/remote/alertmanager_test.go b/pkg/services/ngalert/remote/alertmanager_test.go index 7ad086dc7f2..3d9f10e4396 100644 --- a/pkg/services/ngalert/remote/alertmanager_test.go +++ b/pkg/services/ngalert/remote/alertmanager_test.go @@ -23,6 +23,8 @@ import ( "github.com/grafana/alerting/definition" alertingModels "github.com/grafana/alerting/models" "github.com/grafana/alerting/notify" + "gopkg.in/yaml.v3" + "github.com/grafana/grafana/pkg/infra/db" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/tracing" @@ -39,13 +41,12 @@ import ( "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/tests/testsuite" "github.com/grafana/grafana/pkg/util" - "gopkg.in/yaml.v3" ) const ( // Valid Grafana Alertmanager configurations. - testGrafanaConfig = `{"template_files":{},"alertmanager_config":{"route":{"receiver":"grafana-default-email","group_by":["grafana_folder","alertname"]},"receivers":[{"name":"grafana-default-email","grafana_managed_receiver_configs":[{"uid":"","name":"some other name","type":"email","disableResolveMessage":false,"settings":{"addresses":"\u003cexample@email.com\u003e"}}]}]}}` - testGrafanaConfigWithSecret = `{"template_files":{},"alertmanager_config":{"route":{"receiver":"grafana-default-email","group_by":["grafana_folder","alertname"]},"receivers":[{"name":"grafana-default-email","grafana_managed_receiver_configs":[{"uid":"dde6ntuob69dtf","name":"WH","type":"webhook","disableResolveMessage":false,"settings":{"url":"http://localhost:8080","username":"test"},"secureSettings":{"password":"test"}}]}]}}` + testGrafanaConfig = `{"template_files":{},"alertmanager_config":{"time_intervals":[{"name":"weekends","time_intervals":[{"weekdays":["saturday","sunday"],"location":"Africa/Accra"}]}],"route":{"receiver":"grafana-default-email","group_by":["grafana_folder","alertname"]},"receivers":[{"name":"grafana-default-email","grafana_managed_receiver_configs":[{"uid":"","name":"some other name","type":"email","disableResolveMessage":false,"settings":{"addresses":"\u003cexample@email.com\u003e"}}]}]}}` + testGrafanaConfigWithSecret = `{"template_files":{},"alertmanager_config":{"time_intervals":[{"name":"weekends","time_intervals":[{"weekdays":["saturday","sunday"],"location":"Africa/Accra"}]}],"route":{"receiver":"grafana-default-email","group_by":["grafana_folder","alertname"]},"receivers":[{"name":"grafana-default-email","grafana_managed_receiver_configs":[{"uid":"dde6ntuob69dtf","name":"WH","type":"webhook","disableResolveMessage":false,"settings":{"url":"http://localhost:8080","username":"test"},"secureSettings":{"password":"test"}}]}]}}` testGrafanaDefaultConfigWithDifferentFieldOrder = `{"alertmanager_config":{"route":{"group_by":["alertname","grafana_folder"],"receiver":"grafana-default-email"},"receivers":[{"grafana_managed_receiver_configs":[{"uid":"","name":"email receiver","type":"email","settings":{"addresses":""}}],"name":"grafana-default-email"}]}}` // Valid Alertmanager state base64 encoded. @@ -287,14 +288,14 @@ func TestCompareAndSendConfiguration(t *testing.T) { strings.Replace(testGrafanaConfigWithSecret, `"password":"test"`, `"password":"!"`, 1), NoopAutogenFn, nil, - "unable to decrypt the configuration: failed to decode value for key 'password': illegal base64 data at input byte 0", + `unable to decrypt settings on receiver "WH" (uid: "dde6ntuob69dtf"): failed to decode value for key 'password': illegal base64 data at input byte 0`, }, { "decrypt error", testGrafanaConfigWithSecret, NoopAutogenFn, nil, - fmt.Sprintf("unable to decrypt the configuration: failed to decrypt value for key 'password': %s", errTest.Error()), + fmt.Sprintf(`unable to decrypt settings on receiver "WH" (uid: "dde6ntuob69dtf"): failed to decrypt value for key 'password': %s`, errTest.Error()), }, { "error from autogen function", @@ -443,7 +444,9 @@ func Test_isDefaultConfiguration(t *testing.T) { defaultConfig: string(rawDefaultCfg), defaultConfigHash: fmt.Sprintf("%x", md5.Sum(rawDefaultCfg)), } - isDefault, _ := am.isDefaultConfiguration(test.config) + raw, err := json.Marshal(test.config) + require.NoError(tt, err) + isDefault, _ := am.isDefaultConfiguration(md5.Sum(raw)) require.Equal(tt, test.expected, isDefault) }) } diff --git a/pkg/services/pluginsintegration/loader/loader_test.go b/pkg/services/pluginsintegration/loader/loader_test.go index 016e1e4dc26..95ab7d70af5 100644 --- a/pkg/services/pluginsintegration/loader/loader_test.go +++ b/pkg/services/pluginsintegration/loader/loader_test.go @@ -89,6 +89,7 @@ func TestLoader_Load(t *testing.T) { }, Links: []plugins.InfoLink{ {Name: "Raise issue", URL: "https://github.com/grafana/grafana/issues/new"}, + {Name: "Documentation", URL: "https://grafana.com/docs/grafana/latest/datasources/aws-cloudwatch/"}, }, }, Includes: []*plugins.Includes{ diff --git a/pkg/services/pluginsintegration/pipeline/pipeline.go b/pkg/services/pluginsintegration/pipeline/pipeline.go index 458e02694bd..f43f95c3268 100644 --- a/pkg/services/pluginsintegration/pipeline/pipeline.go +++ b/pkg/services/pluginsintegration/pipeline/pipeline.go @@ -27,7 +27,7 @@ func ProvideDiscoveryStage(cfg *config.PluginManagementCfg, pf finder.Finder, pr FindFunc: pf.Find, FindFilterFuncs: []discovery.FindFilterFunc{ discovery.NewPermittedPluginTypesFilterStep([]plugins.Type{ - plugins.TypeDataSource, plugins.TypeApp, plugins.TypePanel, plugins.TypeSecretsManager, + plugins.TypeDataSource, plugins.TypeApp, plugins.TypePanel, }), func(ctx context.Context, _ plugins.Class, b []*plugins.FoundBundle) ([]*plugins.FoundBundle, error) { return NewDuplicatePluginIDFilterStep(pr).Filter(ctx, b) diff --git a/pkg/services/pluginsintegration/pluginsintegration.go b/pkg/services/pluginsintegration/pluginsintegration.go index da0319b3d7f..afe0ae6c5b4 100644 --- a/pkg/services/pluginsintegration/pluginsintegration.go +++ b/pkg/services/pluginsintegration/pluginsintegration.go @@ -70,7 +70,6 @@ var WireSet = wire.NewSet( wire.Bind(new(pluginconfig.PluginRequestConfigProvider), new(*pluginconfig.RequestConfigProvider)), pluginstore.ProvideService, wire.Bind(new(pluginstore.Store), new(*pluginstore.Service)), - wire.Bind(new(plugins.SecretsPluginManager), new(*pluginstore.Service)), wire.Bind(new(plugins.StaticRouteResolver), new(*pluginstore.Service)), process.ProvideService, wire.Bind(new(process.Manager), new(*process.Service)), diff --git a/pkg/services/pluginsintegration/pluginstore/store.go b/pkg/services/pluginsintegration/pluginstore/store.go index e4d188572d2..20ef15379cf 100644 --- a/pkg/services/pluginsintegration/pluginstore/store.go +++ b/pkg/services/pluginsintegration/pluginstore/store.go @@ -94,15 +94,6 @@ func (s *Service) Plugins(ctx context.Context, pluginTypes ...plugins.Type) []Pl return pluginsList } -func (s *Service) SecretsManager(ctx context.Context) *plugins.Plugin { - for _, p := range s.availablePlugins(ctx) { - if p.IsSecretsManager() { - return p - } - } - return nil -} - // plugin finds a plugin with `pluginID` from the registry that is not decommissioned func (s *Service) plugin(ctx context.Context, pluginID string) (*plugins.Plugin, bool) { p, exists := s.pluginRegistry.Plugin(ctx, pluginID, "") // version is not required since Grafana only supports single versions of a plugin diff --git a/pkg/services/pluginsintegration/pluginstore/store_test.go b/pkg/services/pluginsintegration/pluginstore/store_test.go index 587d7e6d68c..cead243e33e 100644 --- a/pkg/services/pluginsintegration/pluginstore/store_test.go +++ b/pkg/services/pluginsintegration/pluginstore/store_test.go @@ -120,7 +120,6 @@ func TestStore_Routes(t *testing.T) { t.Run("Routes returns all static routes for non-decommissioned plugins", func(t *testing.T) { p1 := &plugins.Plugin{JSONData: plugins.JSONData{ID: "a-test-renderer", Type: plugins.TypeRenderer}, FS: fakes.NewFakePluginFS("/some/dir")} p2 := &plugins.Plugin{JSONData: plugins.JSONData{ID: "b-test-panel", Type: plugins.TypePanel}, FS: fakes.NewFakePluginFS("/grafana/")} - p3 := &plugins.Plugin{JSONData: plugins.JSONData{ID: "c-test-secrets", Type: plugins.TypeSecretsManager}, FS: fakes.NewFakePluginFS("./secrets"), Class: plugins.ClassCore} p4 := &plugins.Plugin{JSONData: plugins.JSONData{ID: "d-test-datasource", Type: plugins.TypeDataSource}, FS: fakes.NewFakePluginFS("../test")} p5 := &plugins.Plugin{JSONData: plugins.JSONData{ID: "e-test-app", Type: plugins.TypeApp}, FS: fakes.NewFakePluginFS("any/path")} p6 := &plugins.Plugin{JSONData: plugins.JSONData{ID: "f-test-app", Type: plugins.TypeApp}} @@ -130,7 +129,6 @@ func TestStore_Routes(t *testing.T) { Store: map[string]*plugins.Plugin{ p1.ID: p1, p2.ID: p2, - p3.ID: p3, p4.ID: p4, p5.ID: p5, p6.ID: p6, @@ -146,27 +144,6 @@ func TestStore_Routes(t *testing.T) { }) } -func TestStore_SecretsManager(t *testing.T) { - t.Run("Renderer returns a single (non-decommissioned) secrets manager plugin", func(t *testing.T) { - p1 := &plugins.Plugin{JSONData: plugins.JSONData{ID: "test-renderer", Type: plugins.TypeRenderer}} - p2 := &plugins.Plugin{JSONData: plugins.JSONData{ID: "test-panel", Type: plugins.TypePanel}} - p3 := &plugins.Plugin{JSONData: plugins.JSONData{ID: "test-secrets", Type: plugins.TypeSecretsManager}} - p4 := &plugins.Plugin{JSONData: plugins.JSONData{ID: "test-datasource", Type: plugins.TypeDataSource}} - - ps := New(&fakes.FakePluginRegistry{ - Store: map[string]*plugins.Plugin{ - p1.ID: p1, - p2.ID: p2, - p3.ID: p3, - p4.ID: p4, - }, - }, &fakes.FakeLoader{}) - - r := ps.SecretsManager(context.Background()) - require.Equal(t, p3, r) - }) -} - func TestProcessManager_shutdown(t *testing.T) { p := &plugins.Plugin{JSONData: plugins.JSONData{ID: "test-datasource", Type: plugins.TypeDataSource}} // Backend: true backend := &fakes.FakeBackendPlugin{} diff --git a/pkg/services/pluginsintegration/renderer/renderer_test.go b/pkg/services/pluginsintegration/renderer/renderer_test.go index 2f7f5d0c913..755622d2e52 100644 --- a/pkg/services/pluginsintegration/renderer/renderer_test.go +++ b/pkg/services/pluginsintegration/renderer/renderer_test.go @@ -39,7 +39,7 @@ func TestRenderer(t *testing.T) { r, exists := m.Renderer(context.Background()) require.False(t, exists) - require.Equal(t, 4, numLoaded) + require.Equal(t, 3, numLoaded) require.Equal(t, 0, numUnloaded) require.Nil(t, r) }) diff --git a/pkg/services/pluginsintegration/renderer/testdata/plugins/secrets-manager/plugin.json b/pkg/services/pluginsintegration/renderer/testdata/plugins/secrets-manager/plugin.json deleted file mode 100644 index 635a3ff47b9..00000000000 --- a/pkg/services/pluginsintegration/renderer/testdata/plugins/secrets-manager/plugin.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "type": "secretsmanager" -} diff --git a/pkg/services/secrets/kvstore/kvstore.go b/pkg/services/secrets/kvstore/kvstore.go index 50a37421f45..2025c7c3118 100644 --- a/pkg/services/secrets/kvstore/kvstore.go +++ b/pkg/services/secrets/kvstore/kvstore.go @@ -5,13 +5,8 @@ import ( "time" "github.com/grafana/grafana/pkg/infra/db" - "github.com/grafana/grafana/pkg/infra/kvstore" "github.com/grafana/grafana/pkg/infra/log" - "github.com/grafana/grafana/pkg/plugins" - "github.com/grafana/grafana/pkg/plugins/backendplugin/secretsmanagerplugin" - "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/secrets" - "github.com/grafana/grafana/pkg/setting" ) const ( @@ -22,45 +17,8 @@ const ( func ProvideService( sqlStore db.DB, secretsService secrets.Service, - pluginsManager plugins.SecretsPluginManager, - kvstore kvstore.KVStore, - features featuremgmt.FeatureToggles, - cfg *setting.Cfg, ) (SecretsKVStore, error) { - var logger = log.New("secrets.kvstore") - var store SecretsKVStore - ctx := context.Background() - store = NewSQLSecretsKVStore(sqlStore, secretsService, logger) - err := EvaluateRemoteSecretsPlugin(ctx, pluginsManager, cfg) - if err != nil { - logger.Debug("secrets manager evaluator returned false", "reason", err.Error()) - } else { - // Attempt to start the plugin - var secretsPlugin secretsmanagerplugin.SecretsManagerPlugin - secretsPlugin, err = StartAndReturnPlugin(pluginsManager, ctx) - namespacedKVStore := GetNamespacedKVStore(kvstore) - if err != nil || secretsPlugin == nil { - logger.Error("failed to start remote secrets management plugin") - if isFatal, readErr := IsPluginStartupErrorFatal(ctx, namespacedKVStore); isFatal || readErr != nil { - // plugin error was fatal or there was an error determining if the error was fatal - logger.Error("secrets management plugin is required to start -- exiting app") - if readErr != nil { - return nil, readErr - } - return nil, err - } - } else { - // as the plugin is installed, SecretsKVStoreSQL is now replaced with - // an instance of SecretsKVStorePlugin with the sql store as a fallback - // (used for migration and in case a secret is not found). - store = NewPluginSecretsKVStore(secretsPlugin, secretsService, namespacedKVStore, features, WithCache(store, 5*time.Second, 5*time.Minute), logger) - } - } - - if err != nil { - logger.Debug("secrets kvstore is using the default (SQL) implementation for secrets management") - } - + store := NewSQLSecretsKVStore(sqlStore, secretsService, log.New("secrets.kvstore")) return WithCache(store, 5*time.Second, 5*time.Minute), nil } diff --git a/pkg/services/secrets/kvstore/migrations/from_plugin_mig.go b/pkg/services/secrets/kvstore/migrations/from_plugin_mig.go deleted file mode 100644 index d8232e9b229..00000000000 --- a/pkg/services/secrets/kvstore/migrations/from_plugin_mig.go +++ /dev/null @@ -1,108 +0,0 @@ -package migrations - -import ( - "context" - "errors" - "fmt" - - "github.com/grafana/grafana/pkg/infra/db" - "github.com/grafana/grafana/pkg/infra/kvstore" - "github.com/grafana/grafana/pkg/plugins" - "github.com/grafana/grafana/pkg/plugins/backendplugin/secretsmanagerplugin" - "github.com/grafana/grafana/pkg/services/secrets" - secretskvs "github.com/grafana/grafana/pkg/services/secrets/kvstore" - "github.com/grafana/grafana/pkg/setting" -) - -var errPluginUnavailable = errors.New("remote secret management plugin is unavailable") - -// MigrateFromPluginService This migrator will handle migration of the configured plugin secrets back to Grafana unified secrets -type MigrateFromPluginService struct { - cfg *setting.Cfg - sqlStore db.DB - secretsService secrets.Service - manager plugins.SecretsPluginManager - kvstore kvstore.KVStore -} - -func ProvideMigrateFromPluginService( - cfg *setting.Cfg, - sqlStore db.DB, - secretsService secrets.Service, - manager plugins.SecretsPluginManager, - kvstore kvstore.KVStore, - -) *MigrateFromPluginService { - return &MigrateFromPluginService{ - cfg: cfg, - sqlStore: sqlStore, - secretsService: secretsService, - manager: manager, - kvstore: kvstore, - } -} - -func (s *MigrateFromPluginService) Migrate(ctx context.Context) error { - logger.Debug("starting migration of plugin secrets to unified secrets") - // access the plugin directly - plugin, err := secretskvs.StartAndReturnPlugin(s.manager, context.Background()) - if err != nil { - return errPluginUnavailable - } - // Get full list of secrets from the plugin - res, err := plugin.GetAllSecrets(ctx, &secretsmanagerplugin.GetAllSecretsRequest{}) - if err != nil { - logger.Error("Failed to retrieve all secrets from plugin") - return err - } - totalSecrets := len(res.Items) - logger.Debug("retrieved all secrets from plugin", "num secrets", totalSecrets) - // create a secret sql store manually - secretsSql := secretskvs.NewSQLSecretsKVStore(s.sqlStore, s.secretsService, logger) - for i, item := range res.Items { - logger.Debug(fmt.Sprintf("Migrating secret %d of %d", i+1, totalSecrets), "current", i+1, "secretCount", totalSecrets) - // Add to sql store - err = secretsSql.Set(ctx, item.Key.OrgId, item.Key.Namespace, item.Key.Type, item.Value) - if err != nil { - logger.Error("Error adding secret to unified secrets", "orgId", item.Key.OrgId, - "namespace", item.Key.Namespace, "type", item.Key.Type) - return err - } - } - - for i, item := range res.Items { - logger.Debug(fmt.Sprintf("Cleaning secret %d of %d", i+1, totalSecrets), "current", i+1, "secretCount", totalSecrets) - // Delete from the plugin - _, err := plugin.DeleteSecret(ctx, &secretsmanagerplugin.DeleteSecretRequest{ - KeyDescriptor: &secretsmanagerplugin.Key{ - OrgId: item.Key.OrgId, - Namespace: item.Key.Namespace, - Type: item.Key.Type, - }}) - if err != nil { - logger.Error("Error deleting secret from plugin after migration", "orgId", item.Key.OrgId, - "namespace", item.Key.Namespace, "type", item.Key.Type) - continue - } - } - logger.Debug("Completed migration of secrets from plugin") - - // The plugin is no longer needed at the moment - err = secretskvs.SetPluginStartupErrorFatal(ctx, secretskvs.GetNamespacedKVStore(s.kvstore), false) - if err != nil { - logger.Error("Failed to remove plugin error fatal flag", "error", err.Error()) - } - // Reset the fatal flag setter in case another secret is created on the plugin - secretskvs.ResetPlugin() - - logger.Debug("Shutting down secrets plugin now that migration is complete") - // if `use_plugin` wasn't set, stop the plugin after migration - if !s.cfg.SectionWithEnvOverrides("secrets").Key("use_plugin").MustBool(false) { - err := s.manager.SecretsManager(ctx).Stop(ctx) - if err != nil { - // Log a warning but don't throw an error - logger.Error("Error stopping secrets plugin after migration", "error", err.Error()) - } - } - return nil -} diff --git a/pkg/services/secrets/kvstore/migrations/from_plugin_mig_test.go b/pkg/services/secrets/kvstore/migrations/from_plugin_mig_test.go deleted file mode 100644 index 7181fa168ac..00000000000 --- a/pkg/services/secrets/kvstore/migrations/from_plugin_mig_test.go +++ /dev/null @@ -1,92 +0,0 @@ -package migrations - -import ( - "context" - "testing" - - "github.com/stretchr/testify/require" - - "github.com/grafana/grafana/pkg/infra/db" - "github.com/grafana/grafana/pkg/infra/kvstore" - "github.com/grafana/grafana/pkg/infra/log" - "github.com/grafana/grafana/pkg/plugins/backendplugin/secretsmanagerplugin" - "github.com/grafana/grafana/pkg/services/secrets/fakes" - secretskvs "github.com/grafana/grafana/pkg/services/secrets/kvstore" - secretsManager "github.com/grafana/grafana/pkg/services/secrets/manager" - "github.com/grafana/grafana/pkg/setting" -) - -// This tests will create a mock sql database and an inmemory -// implementation of the secret manager to simulate the plugin. -func TestPluginSecretMigrationService_MigrateFromPlugin(t *testing.T) { - ctx := context.Background() - - t.Run("migrate secrets from secrets plugin to Grafana", func(t *testing.T) { - // --- SETUP - migratorService, plugin, sqlStore := setupTestMigrateFromPluginService(t) - - addSecretToPluginStore(t, plugin, ctx, 1, "secret-1", "bogus", "value-1") - addSecretToPluginStore(t, plugin, ctx, 1, "secret-2", "bogus", "value-2") - - // --- EXECUTION - err := migratorService.Migrate(ctx) - require.NoError(t, err) - - // --- VALIDATIONS - validatePluginSecretsWereDeleted(t, plugin, ctx) - - validateSecretWasStoredInSql(t, sqlStore, ctx, 1, "secret-1", "bogus", "value-1") - validateSecretWasStoredInSql(t, sqlStore, ctx, 1, "secret-2", "bogus", "value-2") - }) -} - -// Set up services used in migration -func setupTestMigrateFromPluginService(t *testing.T) (*MigrateFromPluginService, secretsmanagerplugin.SecretsManagerPlugin, *secretskvs.SecretsKVStoreSQL) { - t.Helper() - - // this is to init the sql secret store inside the migration - sqlStore := db.InitTestDB(t) - secretsService := secretsManager.SetupTestService(t, fakes.NewFakeSecretsStore()) - manager := secretskvs.NewFakeSecretsPluginManager(t, false) - migratorService := ProvideMigrateFromPluginService( - setting.NewCfg(), - sqlStore, - secretsService, - manager, - kvstore.ProvideService(sqlStore), - ) - - secretsSql := secretskvs.NewSQLSecretsKVStore(sqlStore, secretsService, log.New("test.logger")) - - return migratorService, manager.SecretsManager(context.Background()).SecretsManager, secretsSql -} - -func addSecretToPluginStore(t *testing.T, plugin secretsmanagerplugin.SecretsManagerPlugin, ctx context.Context, orgId int64, namespace string, typ string, value string) { - t.Helper() - _, err := plugin.SetSecret(ctx, &secretsmanagerplugin.SetSecretRequest{ - KeyDescriptor: &secretsmanagerplugin.Key{ - OrgId: orgId, - Namespace: namespace, - Type: typ, - }, - Value: value, - }) - require.NoError(t, err) -} - -// validates that secrets on the plugin were deleted -func validatePluginSecretsWereDeleted(t *testing.T, plugin secretsmanagerplugin.SecretsManagerPlugin, ctx context.Context) { - t.Helper() - res, err := plugin.GetAllSecrets(ctx, &secretsmanagerplugin.GetAllSecretsRequest{}) - require.NoError(t, err) - require.Equal(t, 0, len(res.Items)) -} - -// validates that secrets are in sql -func validateSecretWasStoredInSql(t *testing.T, sqlStore *secretskvs.SecretsKVStoreSQL, ctx context.Context, orgId int64, namespace string, typ string, expectedValue string) { - t.Helper() - res, exists, err := sqlStore.Get(ctx, orgId, namespace, typ) - require.NoError(t, err) - require.True(t, exists) - require.Equal(t, expectedValue, res) -} diff --git a/pkg/services/secrets/kvstore/migrations/migrator.go b/pkg/services/secrets/kvstore/migrations/migrator.go index 3c2671327ca..7cfce10183a 100644 --- a/pkg/services/secrets/kvstore/migrations/migrator.go +++ b/pkg/services/secrets/kvstore/migrations/migrator.go @@ -8,7 +8,6 @@ import ( "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/serverlock" "github.com/grafana/grafana/pkg/registry" - "github.com/grafana/grafana/pkg/setting" ) var logger = log.New("secret.migration") @@ -22,40 +21,20 @@ type SecretMigrationService interface { type SecretMigrationProvider interface { registry.BackgroundService - TriggerPluginMigration(ctx context.Context, toPlugin bool) error } type SecretMigrationProviderImpl struct { - services []SecretMigrationService - ServerLockService *serverlock.ServerLockService - migrateToPluginService *MigrateToPluginService - migrateFromPluginService *MigrateFromPluginService + services []SecretMigrationService + ServerLockService *serverlock.ServerLockService } func ProvideSecretMigrationProvider( - cfg *setting.Cfg, serverLockService *serverlock.ServerLockService, dataSourceSecretMigrationService *DataSourceSecretMigrationService, - migrateToPluginService *MigrateToPluginService, - migrateFromPluginService *MigrateFromPluginService, ) *SecretMigrationProviderImpl { - services := make([]SecretMigrationService, 0) - services = append(services, dataSourceSecretMigrationService) - // Plugin migration should always be last; should either migrate to or from, not both - // This is because the migrateTo checks for use_plugin = true, in which case we should always - // migrate by default to ensure users don't lose access to secrets. If migration has - // already occurred, the migrateTo function will be called but it won't do anything - if cfg.SectionWithEnvOverrides("secrets").Key("migrate_from_plugin").MustBool(false) { - services = append(services, migrateFromPluginService) - } else { - services = append(services, migrateToPluginService) - } - return &SecretMigrationProviderImpl{ - ServerLockService: serverLockService, - services: services, - migrateToPluginService: migrateToPluginService, - migrateFromPluginService: migrateFromPluginService, + ServerLockService: serverLockService, + services: []SecretMigrationService{dataSourceSecretMigrationService}, } } @@ -83,23 +62,3 @@ func (s *SecretMigrationProviderImpl) Migrate(ctx context.Context) error { } return nil } - -// TriggerPluginMigration Kick off a migration to or from the plugin. This will block until all services have exited. -func (s *SecretMigrationProviderImpl) TriggerPluginMigration(ctx context.Context, toPlugin bool) error { - // Don't migrate if there is already one happening - return s.ServerLockService.LockExecuteAndRelease(ctx, actionName, time.Minute*10, func(context.Context) { - var err error - if toPlugin { - err = s.migrateToPluginService.Migrate(ctx) - } else { - err = s.migrateFromPluginService.Migrate(ctx) - } - if err != nil { - direction := "from_plugin" - if toPlugin { - direction = "to_plugin" - } - logger.Error("Failed to migrate plugin secrets", "direction", direction, "error", err.Error()) - } - }) -} diff --git a/pkg/services/secrets/kvstore/migrations/to_plugin_mig.go b/pkg/services/secrets/kvstore/migrations/to_plugin_mig.go deleted file mode 100644 index 5a0f0d4047b..00000000000 --- a/pkg/services/secrets/kvstore/migrations/to_plugin_mig.go +++ /dev/null @@ -1,120 +0,0 @@ -package migrations - -import ( - "context" - "errors" - "fmt" - - "github.com/grafana/grafana/pkg/infra/db" - "github.com/grafana/grafana/pkg/infra/kvstore" - "github.com/grafana/grafana/pkg/plugins" - "github.com/grafana/grafana/pkg/services/secrets" - secretskvs "github.com/grafana/grafana/pkg/services/secrets/kvstore" - "github.com/grafana/grafana/pkg/setting" -) - -var errSecretStoreIsNotPlugin = errors.New("SecretsKVStore is not a SecretsKVStorePlugin") - -// MigrateToPluginService This migrator will handle migration of datasource secrets (aka Unified secrets) -// into the plugin secrets configured -type MigrateToPluginService struct { - secretsStore secretskvs.SecretsKVStore - cfg *setting.Cfg - sqlStore db.DB - secretsService secrets.Service - kvstore kvstore.KVStore - manager plugins.SecretsPluginManager -} - -func ProvideMigrateToPluginService( - secretsStore secretskvs.SecretsKVStore, - cfg *setting.Cfg, - sqlStore db.DB, - secretsService secrets.Service, - kvstore kvstore.KVStore, - manager plugins.SecretsPluginManager, -) *MigrateToPluginService { - return &MigrateToPluginService{ - secretsStore: secretsStore, - cfg: cfg, - sqlStore: sqlStore, - secretsService: secretsService, - kvstore: kvstore, - manager: manager, - } -} - -func (s *MigrateToPluginService) Migrate(ctx context.Context) error { - err := secretskvs.EvaluateRemoteSecretsPlugin(ctx, s.manager, s.cfg) - hasStarted := secretskvs.HasPluginStarted(ctx, s.manager) - if err == nil && hasStarted { - logger.Debug("starting migration of unified secrets to the plugin") - // we need to get the fallback store since in this scenario the secrets store would be the plugin. - tmpStore, err := secretskvs.GetUnwrappedStoreFromCache(s.secretsStore) - if err != nil { - tmpStore = s.secretsStore - logger.Warn("secret store is not cached, this is unexpected - continuing migration anyway.") - } - pluginStore, ok := tmpStore.(*secretskvs.SecretsKVStorePlugin) - if !ok { - return errSecretStoreIsNotPlugin - } - fallbackStore := pluginStore.Fallback() - - // before we start migrating, check see if plugin startup failures were already fatal - namespacedKVStore := secretskvs.GetNamespacedKVStore(s.kvstore) - wasFatal, err := secretskvs.IsPluginStartupErrorFatal(ctx, namespacedKVStore) - if err != nil { - logger.Warn("unable to determine whether plugin startup failures are fatal - continuing migration anyway.") - } - - var allSec []secretskvs.Item - var totalSec int - // during migration we need to have fallback enabled while we move secrets to plugin - err = pluginStore.WithFallbackEnabled(func() error { - // get all secrets in the fallback store - allSec, err = fallbackStore.GetAll(ctx) - if err != nil { - return nil - } - totalSec := len(allSec) - logger.Debug(fmt.Sprintf("Total amount of secrets to migrate: %d", totalSec)) - - // We just set it again as the current secret store should be the plugin secret - for i, sec := range allSec { - logger.Debug(fmt.Sprintf("Migrating secret %d of %d", i+1, totalSec), "current", i+1, "secretCount", totalSec) - err = pluginStore.Set(ctx, *sec.OrgId, *sec.Namespace, *sec.Type, sec.Value) - if err != nil { - return err - } - } - return nil - }) - if err != nil { - return err - } - - // as no err was returned, when we delete all the secrets from the sql store - logger.Debug("migrated unified secrets to plugin", "number of secrets", totalSec) - for index, sec := range allSec { - logger.Debug(fmt.Sprintf("Cleaning secret %d of %d", index+1, totalSec), "current", index+1, "secretCount", totalSec) - - err = fallbackStore.Del(ctx, *sec.OrgId, *sec.Namespace, *sec.Type) - if err != nil { - logger.Error("plugin migrator encountered error while deleting unified secrets") - if index == 0 && !wasFatal { - // old unified secrets still exists, so plugin startup errors are still not fatal, unless they were before we started - err := secretskvs.SetPluginStartupErrorFatal(ctx, namespacedKVStore, false) - if err != nil { - logger.Error("error reverting plugin failure fatal status", "error", err.Error()) - } else { - logger.Debug("application will continue to function without the secrets plugin") - } - } - return err - } - } - logger.Debug("deleted unified secrets after migration", "number of secrets", totalSec) - } - return nil -} diff --git a/pkg/services/secrets/kvstore/migrations/to_plugin_mig_test.go b/pkg/services/secrets/kvstore/migrations/to_plugin_mig_test.go deleted file mode 100644 index b2ad6dbf956..00000000000 --- a/pkg/services/secrets/kvstore/migrations/to_plugin_mig_test.go +++ /dev/null @@ -1,152 +0,0 @@ -package migrations - -import ( - "context" - "errors" - "testing" - "time" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "gopkg.in/ini.v1" - - "github.com/grafana/grafana/pkg/infra/db" - "github.com/grafana/grafana/pkg/infra/db/dbtest" - "github.com/grafana/grafana/pkg/infra/kvstore" - "github.com/grafana/grafana/pkg/services/featuremgmt" - "github.com/grafana/grafana/pkg/services/secrets/fakes" - secretskvs "github.com/grafana/grafana/pkg/services/secrets/kvstore" - secretsManager "github.com/grafana/grafana/pkg/services/secrets/manager" - "github.com/grafana/grafana/pkg/setting" -) - -// This tests will create a mock sql database and an inmemory -// implementation of the secret manager to simulate the plugin. -func TestPluginSecretMigrationService_MigrateToPlugin(t *testing.T) { - ctx := context.Background() - - t.Run("migration run ok - 2 secrets migrated", func(t *testing.T) { - // --- SETUP - migratorService, secretsStore, sqlSecretStore := setupTestMigrateToPluginService(t) - var orgId int64 = 1 - namespace1, namespace2 := "namespace-test", "namespace-test2" - typ := "type-test" - value := "SUPER_SECRET" - - addSecretToSqlStore(t, sqlSecretStore, ctx, orgId, namespace1, typ, value) - addSecretToSqlStore(t, sqlSecretStore, ctx, orgId, namespace2, typ, value) - - // --- EXECUTION - err := migratorService.Migrate(ctx) - require.NoError(t, err) - - // --- VALIDATIONS - validateSqlSecretWasDeleted(t, sqlSecretStore, ctx, orgId, namespace1, typ) - validateSqlSecretWasDeleted(t, sqlSecretStore, ctx, orgId, namespace2, typ) - - validateSecretWasStoredInPlugin(t, secretsStore, ctx, orgId, namespace1, typ) - validateSecretWasStoredInPlugin(t, secretsStore, ctx, orgId, namespace1, typ) - }) -} - -// With fatal flag unset, do a migration with backwards compatibility disabled. When unified secrets are deleted, return an error on the first deletion -// Should result in the fatal flag remaining unset -func TestFatalPluginErr_MigrationTestWithErrorDeletingUnifiedSecrets(t *testing.T) { - p, err := secretskvs.SetupFatalCrashTest(t, false, false, true) - assert.NoError(t, err) - - migration := setupTestMigratorServiceWithDeletionError(t, p.SecretsKVStore, &dbtest.FakeDB{ - ExpectedError: errors.New("random error"), - }, p.KVStore) - err = migration.Migrate(context.Background()) - assert.Error(t, err) - assert.Equal(t, "mocked del error", err.Error()) - - isFatal, err := secretskvs.IsPluginStartupErrorFatal(context.Background(), secretskvs.GetNamespacedKVStore(p.KVStore)) - assert.NoError(t, err) - assert.False(t, isFatal) -} - -func addSecretToSqlStore(t *testing.T, sqlSecretStore secretskvs.SecretsKVStore, ctx context.Context, orgId int64, namespace1 string, typ string, value string) { - t.Helper() - err := sqlSecretStore.Set(ctx, orgId, namespace1, typ, value) - require.NoError(t, err) -} - -// validates that secrets on the sql store were deleted. -func validateSqlSecretWasDeleted(t *testing.T, sqlSecretStore secretskvs.SecretsKVStore, ctx context.Context, orgId int64, namespace1 string, typ string) { - t.Helper() - res, err := sqlSecretStore.Keys(ctx, orgId, namespace1, typ) - require.NoError(t, err) - require.Equal(t, 0, len(res)) -} - -// validates that secrets should be on the plugin -func validateSecretWasStoredInPlugin(t *testing.T, secretsStore secretskvs.SecretsKVStore, ctx context.Context, orgId int64, namespace1 string, typ string) { - t.Helper() - resPlugin, err := secretsStore.Keys(ctx, orgId, namespace1, typ) - require.NoError(t, err) - require.Equal(t, 1, len(resPlugin)) -} - -// Set up services used in migration -func setupTestMigrateToPluginService(t *testing.T) (*MigrateToPluginService, secretskvs.SecretsKVStore, secretskvs.SecretsKVStore) { - t.Helper() - - rawCfg := ` - [secrets] - use_plugin = true - ` - raw, err := ini.Load([]byte(rawCfg)) - require.NoError(t, err) - cfg := &setting.Cfg{Raw: raw} - sqlStore := db.InitTestDB(t) - - // this would be the plugin - mocked at the moment - fallbackStore := secretskvs.WithCache(secretskvs.NewFakeSQLSecretsKVStore(t, sqlStore), time.Minute*5, time.Minute*5) - secretsStoreForPlugin := secretskvs.WithCache(secretskvs.NewFakePluginSecretsKVStore(t, featuremgmt.WithFeatures(), fallbackStore), time.Minute*5, time.Minute*5) - - // this is to init the sql secret store inside the migration - secretsService := secretsManager.SetupTestService(t, fakes.NewFakeSecretsStore()) - manager := secretskvs.NewFakeSecretsPluginManager(t, false) - migratorService := ProvideMigrateToPluginService( - secretsStoreForPlugin, - cfg, - sqlStore, - secretsService, - kvstore.ProvideService(sqlStore), - manager, - ) - - return migratorService, secretsStoreForPlugin, fallbackStore -} - -func setupTestMigratorServiceWithDeletionError( - t *testing.T, - secretskv secretskvs.SecretsKVStore, - sqlStore db.DB, - kvstore kvstore.KVStore, -) *MigrateToPluginService { - t.Helper() - t.Cleanup(secretskvs.ResetPlugin) - cfg := secretskvs.SetupTestConfig(t) - secretsService := secretsManager.SetupTestService(t, fakes.NewFakeSecretsStore()) - manager := secretskvs.NewFakeSecretsPluginManager(t, false) - migratorService := ProvideMigrateToPluginService( - secretskv, - cfg, - sqlStore, - secretsService, - kvstore, - manager, - ) - fallback := secretskvs.NewFakeSecretsKVStore() - var orgId int64 = 1 - str := "random string" - err := fallback.Set(context.Background(), orgId, str, str, "bogus") - require.NoError(t, err) - fallback.DeletionError(true) - err = secretskvs.ReplaceFallback(t, secretskv, fallback) - require.NoError(t, err) - return migratorService -} diff --git a/pkg/services/secrets/kvstore/model.go b/pkg/services/secrets/kvstore/model.go index 463fd7c2563..c89869d0afb 100644 --- a/pkg/services/secrets/kvstore/model.go +++ b/pkg/services/secrets/kvstore/model.go @@ -5,9 +5,7 @@ import ( ) const ( - QuitOnPluginStartupFailureKey = "quit_on_secrets_plugin_startup_failure" - PluginNamespace = "secretsmanagerplugin" - DataSourceSecretType = "datasource" + DataSourceSecretType = "datasource" ) // Item stored in k/v store. diff --git a/pkg/services/secrets/kvstore/plugin.go b/pkg/services/secrets/kvstore/plugin.go deleted file mode 100644 index a944da822f2..00000000000 --- a/pkg/services/secrets/kvstore/plugin.go +++ /dev/null @@ -1,288 +0,0 @@ -package kvstore - -import ( - "context" - "errors" - "fmt" - "sync" - - "github.com/grafana/grafana/pkg/infra/kvstore" - "github.com/grafana/grafana/pkg/infra/log" - "github.com/grafana/grafana/pkg/plugins" - smp "github.com/grafana/grafana/pkg/plugins/backendplugin/secretsmanagerplugin" - "github.com/grafana/grafana/pkg/services/datasources" - "github.com/grafana/grafana/pkg/services/featuremgmt" - "github.com/grafana/grafana/pkg/services/secrets" - "github.com/grafana/grafana/pkg/setting" -) - -var ( - fatalFlagOnce sync.Once - startupOnce sync.Once - errPluginDisabledByConfig = errors.New("remote secret management plugin disabled because the property `secrets.use_plugin` is not set to `true`") - errPluginNotInstalled = errors.New("remote secret management plugin disabled because there is no installed plugin of type `secretsmanager`") -) - -// SecretsKVStorePlugin provides a key/value store backed by the Grafana plugin gRPC interface -type SecretsKVStorePlugin struct { - sync.Mutex - log log.Logger - secretsPlugin smp.SecretsManagerPlugin - secretsService secrets.Service - kvstore *kvstore.NamespacedKVStore - backwardsCompatibilityDisabled bool - fallbackEnabled bool - fallbackStore SecretsKVStore -} - -func NewPluginSecretsKVStore( - secretsPlugin smp.SecretsManagerPlugin, - secretsService secrets.Service, - kvstore *kvstore.NamespacedKVStore, - features featuremgmt.FeatureToggles, - fallback SecretsKVStore, - logger log.Logger, -) *SecretsKVStorePlugin { - return &SecretsKVStorePlugin{ - secretsPlugin: secretsPlugin, - secretsService: secretsService, - log: logger, - kvstore: kvstore, - backwardsCompatibilityDisabled: features.IsEnabledGlobally(featuremgmt.FlagDisableSecretsCompatibility), - fallbackStore: fallback, - } -} - -// Get an item from the store -// If it is the first time a secret has been retrieved and backwards compatibility is disabled, mark plugin startup errors fatal -func (kv *SecretsKVStorePlugin) Get(ctx context.Context, orgId int64, namespace string, typ string) (string, bool, error) { - req := &smp.GetSecretRequest{ - KeyDescriptor: &smp.Key{ - OrgId: orgId, - Namespace: namespace, - Type: typ, - }, - } - - res, err := kv.secretsPlugin.GetSecret(ctx, req) - if res.UserFriendlyError != "" { - err = wrapUserFriendlySecretError(res.UserFriendlyError) - } - - if res.Exists { - updateFatalFlag(ctx, kv) - } - - if kv.fallbackEnabled { - if err != nil || res.UserFriendlyError != "" || !res.Exists { - res.DecryptedValue, res.Exists, err = kv.fallbackStore.Get(ctx, orgId, namespace, typ) - } - } - - return res.DecryptedValue, res.Exists, err -} - -// Set an item in the store -// If it is the first time a secret has been set and backwards compatibility is disabled, mark plugin startup errors fatal -func (kv *SecretsKVStorePlugin) Set(ctx context.Context, orgId int64, namespace string, typ string, value string) error { - req := &smp.SetSecretRequest{ - KeyDescriptor: &smp.Key{ - OrgId: orgId, - Namespace: namespace, - Type: typ, - }, - Value: value, - } - - res, err := kv.secretsPlugin.SetSecret(ctx, req) - if err == nil && res.UserFriendlyError != "" { - err = wrapUserFriendlySecretError(res.UserFriendlyError) - } - - updateFatalFlag(ctx, kv) - - return err -} - -// Del deletes an item from the store. -func (kv *SecretsKVStorePlugin) Del(ctx context.Context, orgId int64, namespace string, typ string) error { - req := &smp.DeleteSecretRequest{ - KeyDescriptor: &smp.Key{ - OrgId: orgId, - Namespace: namespace, - Type: typ, - }, - } - - res, err := kv.secretsPlugin.DeleteSecret(ctx, req) - if err == nil && res.UserFriendlyError != "" { - err = wrapUserFriendlySecretError(res.UserFriendlyError) - } - - return err -} - -// Keys get all keys for a given namespace. To query for all -// organizations the constant 'kvstore.AllOrganizations' can be passed as orgId. -func (kv *SecretsKVStorePlugin) Keys(ctx context.Context, orgId int64, namespace string, typ string) ([]Key, error) { - req := &smp.ListSecretsRequest{ - KeyDescriptor: &smp.Key{ - OrgId: orgId, - Namespace: namespace, - Type: typ, - }, - AllOrganizations: orgId == AllOrganizations, - } - - res, err := kv.secretsPlugin.ListSecrets(ctx, req) - if err != nil { - return nil, err - } else if res.UserFriendlyError != "" { - err = wrapUserFriendlySecretError(res.UserFriendlyError) - } - - return parseKeys(res.Keys), err -} - -// Rename an item in the store -func (kv *SecretsKVStorePlugin) Rename(ctx context.Context, orgId int64, namespace string, typ string, newNamespace string) error { - req := &smp.RenameSecretRequest{ - KeyDescriptor: &smp.Key{ - OrgId: orgId, - Namespace: namespace, - Type: typ, - }, - NewNamespace: newNamespace, - } - - res, err := kv.secretsPlugin.RenameSecret(ctx, req) - if err == nil && res.UserFriendlyError != "" { - err = wrapUserFriendlySecretError(res.UserFriendlyError) - } - - return err -} - -func (kv *SecretsKVStorePlugin) GetAll(ctx context.Context) ([]Item, error) { - req := &smp.GetAllSecretsRequest{} - - res, err := kv.secretsPlugin.GetAllSecrets(ctx, req) - if err != nil { - return nil, err - } else if res.UserFriendlyError != "" { - err = wrapUserFriendlySecretError(res.UserFriendlyError) - } - - return parseItems(res.Items), err -} - -func (kv *SecretsKVStorePlugin) Fallback() SecretsKVStore { - return kv.fallbackStore -} - -func (kv *SecretsKVStorePlugin) WithFallbackEnabled(fn func() error) error { - kv.Lock() - defer kv.Unlock() - kv.fallbackEnabled = true - err := fn() - kv.fallbackEnabled = false - return err -} - -func parseKeys(keys []*smp.Key) []Key { - newKeys := make([]Key, 0, len(keys)) - - for _, k := range keys { - newKey := Key{OrgId: k.OrgId, Namespace: k.Namespace, Type: k.Type} - newKeys = append(newKeys, newKey) - } - - return newKeys -} - -func parseItems(items []*smp.Item) []Item { - newItems := make([]Item, 0, len(items)) - - for _, i := range items { - newItem := Item{OrgId: &i.Key.OrgId, Namespace: &i.Key.Namespace, Type: &i.Key.Type, Value: i.Value} - newItems = append(newItems, newItem) - } - - return newItems -} - -func updateFatalFlag(ctx context.Context, skv *SecretsKVStorePlugin) { - // This function makes the most sense in here because it handles all possible scenarios: - // - User changed backwards compatibility flag, so we have to migrate secrets either to or from the plugin (get or set) - // - Migration is on, so we migrate secrets to the plugin (set) - // - User doesn't migrate, but stores a new secret in the plugin (set) - // Rather than updating the flag in several places, it is cleaner to just do this check once - // Very early on. Once backwards compatibility to legacy secrets is gone in Grafana 10, this can go away as well - fatalFlagOnce.Do(func() { - skv.log.Debug("Updating plugin startup error fatal flag") - var err error - if isFatal, _ := IsPluginStartupErrorFatal(ctx, skv.kvstore); !isFatal && skv.backwardsCompatibilityDisabled { - err = SetPluginStartupErrorFatal(ctx, skv.kvstore, true) - } else if isFatal && !skv.backwardsCompatibilityDisabled { - err = SetPluginStartupErrorFatal(ctx, skv.kvstore, false) - } - if err != nil { - skv.log.Error("failed to set plugin error fatal flag", err.Error()) - } - }) -} - -func wrapUserFriendlySecretError(ufe string) datasources.ErrDatasourceSecretsPluginUserFriendly { - return datasources.ErrDatasourceSecretsPluginUserFriendly{Err: ufe} -} - -func GetNamespacedKVStore(kv kvstore.KVStore) *kvstore.NamespacedKVStore { - return kvstore.WithNamespace(kv, kvstore.AllOrganizations, PluginNamespace) -} - -func IsPluginStartupErrorFatal(ctx context.Context, kvstore *kvstore.NamespacedKVStore) (bool, error) { - _, exists, err := kvstore.Get(ctx, QuitOnPluginStartupFailureKey) - if err != nil { - return false, fmt.Errorf("error retrieving key %s from kvstore. error: %w", QuitOnPluginStartupFailureKey, err) - } - return exists, nil -} - -func SetPluginStartupErrorFatal(ctx context.Context, kvstore *kvstore.NamespacedKVStore, isFatal bool) error { - if !isFatal { - return kvstore.Del(ctx, QuitOnPluginStartupFailureKey) - } - return kvstore.Set(ctx, QuitOnPluginStartupFailureKey, "true") -} - -func EvaluateRemoteSecretsPlugin(ctx context.Context, mg plugins.SecretsPluginManager, cfg *setting.Cfg) error { - usePlugin := cfg.SectionWithEnvOverrides("secrets").Key("use_plugin").MustBool() - if !usePlugin { - return errPluginDisabledByConfig - } - pluginInstalled := mg.SecretsManager(ctx) != nil - if !pluginInstalled { - return errPluginNotInstalled - } - return nil -} - -func HasPluginStarted(ctx context.Context, mg plugins.SecretsPluginManager) bool { - return mg.SecretsManager(ctx) != nil && mg.SecretsManager(ctx).SecretsManager != nil -} - -func StartAndReturnPlugin(mg plugins.SecretsPluginManager, ctx context.Context) (smp.SecretsManagerPlugin, error) { - var err error - startupOnce.Do(func() { - err = mg.SecretsManager(ctx).Start(ctx) - }) - if err != nil { - return nil, err - } - return mg.SecretsManager(ctx).SecretsManager, nil -} - -func ResetPlugin() { - fatalFlagOnce = sync.Once{} - startupOnce = sync.Once{} -} diff --git a/pkg/services/secrets/kvstore/plugin_test.go b/pkg/services/secrets/kvstore/plugin_test.go deleted file mode 100644 index 8cd70129df6..00000000000 --- a/pkg/services/secrets/kvstore/plugin_test.go +++ /dev/null @@ -1,82 +0,0 @@ -package kvstore - -import ( - "context" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/grafana/grafana/pkg/plugins/backendplugin/secretsmanagerplugin" - "github.com/grafana/grafana/pkg/tests/testsuite" -) - -func TestMain(m *testing.M) { - testsuite.Run(m) -} - -// Set fatal flag to true, then simulate a plugin start failure -// Should result in an error from the secret store provider -func TestFatalPluginErr_PluginFailsToStartWithFatalFlagSet(t *testing.T) { - p, err := SetupFatalCrashTest(t, true, true, false) - assert.Error(t, err) - assert.Equal(t, "mocked failed to start", err.Error()) - assert.Nil(t, p.SecretsKVStore) -} - -// Set fatal flag to false, then simulate a plugin start failure -// Should result in the secret store provider returning the sql impl -func TestFatalPluginErr_PluginFailsToStartWithFatalFlagNotSet(t *testing.T) { - p, err := SetupFatalCrashTest(t, true, false, false) - assert.NoError(t, err) - require.IsType(t, &CachedKVStore{}, p.SecretsKVStore) - - cachedKv, _ := p.SecretsKVStore.(*CachedKVStore) - store, err := GetUnwrappedStoreFromCache(cachedKv) - require.NoError(t, err) - assert.IsType(t, &SecretsKVStoreSQL{}, store) -} - -// With fatal flag not set, store a secret in the plugin while backwards compatibility is disabled -// Should result in the fatal flag going from unset -> set to true -func TestFatalPluginErr_FatalFlagGetsSetWithBackwardsCompatDisabled(t *testing.T) { - p, err := SetupFatalCrashTest(t, false, false, true) - assert.NoError(t, err) - require.NotNil(t, p.SecretsKVStore) - - err = p.SecretsKVStore.Set(context.Background(), 0, "datasource", "postgres", "my secret") - assert.NoError(t, err) - - isFatal, err := IsPluginStartupErrorFatal(context.Background(), GetNamespacedKVStore(p.KVStore)) - assert.NoError(t, err) - assert.True(t, isFatal) -} - -// With fatal flag set, retrieve a secret from the plugin while backwards compatibility is enabled -// Should result in the fatal flag going from set to true -> unset -func TestFatalPluginErr_FatalFlagGetsUnSetWithBackwardsCompatEnabled(t *testing.T) { - p, err := SetupFatalCrashTest(t, false, true, false) - assert.NoError(t, err) - require.NotNil(t, p.SecretsKVStore) - - // setup - store secret and manually bypassing the remote plugin impl - _, err = p.PluginManager.SecretsManager(context.Background()).SecretsManager.SetSecret(context.Background(), &secretsmanagerplugin.SetSecretRequest{ - KeyDescriptor: &secretsmanagerplugin.Key{ - OrgId: 0, - Namespace: "postgres", - Type: "datasource", - }, - Value: "bogus", - }) - assert.NoError(t, err) - - // retrieve the secret and check values - val, exists, err := p.SecretsKVStore.Get(context.Background(), 0, "postgres", "datasource") - assert.NoError(t, err) - assert.NotNil(t, val) - assert.True(t, exists) - - isFatal, err := IsPluginStartupErrorFatal(context.Background(), GetNamespacedKVStore(p.KVStore)) - assert.NoError(t, err) - assert.False(t, isFatal) -} diff --git a/pkg/services/secrets/kvstore/sql_test.go b/pkg/services/secrets/kvstore/sql_test.go index bb73e7d80bf..eeee727b9f0 100644 --- a/pkg/services/secrets/kvstore/sql_test.go +++ b/pkg/services/secrets/kvstore/sql_test.go @@ -12,6 +12,7 @@ import ( "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/services/secrets/fakes" "github.com/grafana/grafana/pkg/services/secrets/manager" + "github.com/grafana/grafana/pkg/tests/testsuite" ) type TestCase struct { @@ -25,6 +26,10 @@ func (t *TestCase) Value() string { return fmt.Sprintf("%d:%s:%s:%d", t.OrgId, t.Namespace, t.Type, t.Revision) } +func TestMain(m *testing.M) { + testsuite.Run(m) +} + func TestSecretsKVStoreSQL(t *testing.T) { sqlStore := db.InitTestDB(t) secretsService := manager.SetupTestService(t, fakes.NewFakeSecretsStore()) diff --git a/pkg/services/secrets/kvstore/test_helpers.go b/pkg/services/secrets/kvstore/test_helpers.go index 7c19def969c..479bb5d9172 100644 --- a/pkg/services/secrets/kvstore/test_helpers.go +++ b/pkg/services/secrets/kvstore/test_helpers.go @@ -3,25 +3,13 @@ package kvstore import ( "context" "errors" - "sync" "testing" - "github.com/stretchr/testify/require" - "google.golang.org/grpc" - "gopkg.in/ini.v1" - - "github.com/grafana/grafana/pkg/infra/db" - "github.com/grafana/grafana/pkg/infra/kvstore" "github.com/grafana/grafana/pkg/infra/log" - "github.com/grafana/grafana/pkg/plugins" - "github.com/grafana/grafana/pkg/plugins/backendplugin" - "github.com/grafana/grafana/pkg/plugins/backendplugin/secretsmanagerplugin" - pluginsLogger "github.com/grafana/grafana/pkg/plugins/log" "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/secrets/fakes" secretsmng "github.com/grafana/grafana/pkg/services/secrets/manager" "github.com/grafana/grafana/pkg/services/sqlstore" - "github.com/grafana/grafana/pkg/setting" ) func NewFakeSQLSecretsKVStore(t *testing.T, sqlStore *sqlstore.SQLStore) *SecretsKVStoreSQL { @@ -30,17 +18,6 @@ func NewFakeSQLSecretsKVStore(t *testing.T, sqlStore *sqlstore.SQLStore) *Secret return NewSQLSecretsKVStore(sqlStore, secretsService, log.New("test.logger")) } -func NewFakePluginSecretsKVStore(t *testing.T, features featuremgmt.FeatureToggles, fallback SecretsKVStore) *SecretsKVStorePlugin { - t.Helper() - sqlStore := db.InitTestDB(t) - secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore()) - store := kvstore.ProvideService(sqlStore) - namespacedKVStore := GetNamespacedKVStore(store) - manager := NewFakeSecretsPluginManager(t, false) - plugin := manager.SecretsManager(context.Background()).SecretsManager - return NewPluginSecretsKVStore(plugin, secretsService, namespacedKVStore, features, fallback, log.New("test.logger")) -} - // In memory kv store used for testing type FakeSecretsKVStore struct { store map[Key]string @@ -127,14 +104,6 @@ func buildKey(orgId int64, namespace string, typ string) Key { } } -func internalToProtoKey(k Key) *secretsmanagerplugin.Key { - return &secretsmanagerplugin.Key{ - OrgId: k.OrgId, - Namespace: k.Namespace, - Type: k.Type, - } -} - // Fake feature toggle - only need to check the backwards compatibility disabled flag type fakeFeatureToggles struct { returnValue bool @@ -158,172 +127,3 @@ func (f fakeFeatureToggles) IsEnabled(ctx context.Context, feature string) bool func (f fakeFeatureToggles) GetEnabled(ctx context.Context) map[string]bool { return map[string]bool{} } - -// Fake grpc secrets plugin impl -type fakeGRPCSecretsPlugin struct { - kv map[Key]string -} - -func (c *fakeGRPCSecretsPlugin) GetSecret(ctx context.Context, in *secretsmanagerplugin.GetSecretRequest, opts ...grpc.CallOption) (*secretsmanagerplugin.GetSecretResponse, error) { - val, ok := c.kv[buildKey(in.KeyDescriptor.OrgId, in.KeyDescriptor.Namespace, in.KeyDescriptor.Type)] - return &secretsmanagerplugin.GetSecretResponse{ - DecryptedValue: val, - Exists: ok, - }, nil -} - -func (c *fakeGRPCSecretsPlugin) SetSecret(ctx context.Context, in *secretsmanagerplugin.SetSecretRequest, opts ...grpc.CallOption) (*secretsmanagerplugin.SetSecretResponse, error) { - c.kv[buildKey(in.KeyDescriptor.OrgId, in.KeyDescriptor.Namespace, in.KeyDescriptor.Type)] = in.Value - return &secretsmanagerplugin.SetSecretResponse{}, nil -} - -func (c *fakeGRPCSecretsPlugin) DeleteSecret(ctx context.Context, in *secretsmanagerplugin.DeleteSecretRequest, opts ...grpc.CallOption) (*secretsmanagerplugin.DeleteSecretResponse, error) { - delete(c.kv, buildKey(in.KeyDescriptor.OrgId, in.KeyDescriptor.Namespace, in.KeyDescriptor.Type)) - return &secretsmanagerplugin.DeleteSecretResponse{}, nil -} - -func (c *fakeGRPCSecretsPlugin) ListSecrets(ctx context.Context, in *secretsmanagerplugin.ListSecretsRequest, opts ...grpc.CallOption) (*secretsmanagerplugin.ListSecretsResponse, error) { - res := make([]*secretsmanagerplugin.Key, 0) - for k := range c.kv { - if in.KeyDescriptor.OrgId == AllOrganizations && in.KeyDescriptor.Namespace == "" && in.KeyDescriptor.Type == "" { - res = append(res, internalToProtoKey(k)) - } else if k.OrgId == in.KeyDescriptor.OrgId && k.Namespace == in.KeyDescriptor.Namespace && k.Type == in.KeyDescriptor.Type { - res = append(res, internalToProtoKey(k)) - } - } - return &secretsmanagerplugin.ListSecretsResponse{ - Keys: res, - }, nil -} - -func (c *fakeGRPCSecretsPlugin) RenameSecret(ctx context.Context, in *secretsmanagerplugin.RenameSecretRequest, opts ...grpc.CallOption) (*secretsmanagerplugin.RenameSecretResponse, error) { - oldKey := buildKey(in.KeyDescriptor.OrgId, in.KeyDescriptor.Namespace, in.KeyDescriptor.Type) - val := c.kv[oldKey] - delete(c.kv, oldKey) - c.kv[buildKey(in.KeyDescriptor.OrgId, in.NewNamespace, in.KeyDescriptor.Type)] = val - return &secretsmanagerplugin.RenameSecretResponse{}, nil -} - -func (c *fakeGRPCSecretsPlugin) GetAllSecrets(ctx context.Context, in *secretsmanagerplugin.GetAllSecretsRequest, opts ...grpc.CallOption) (*secretsmanagerplugin.GetAllSecretsResponse, error) { - items := make([]*secretsmanagerplugin.Item, 0) - for k, v := range c.kv { - items = append(items, &secretsmanagerplugin.Item{ - Key: internalToProtoKey(k), - Value: v, - }) - } - return &secretsmanagerplugin.GetAllSecretsResponse{ - Items: items, - }, nil -} - -var _ SecretsKVStore = &FakeSecretsKVStore{} -var _ secretsmanagerplugin.SecretsManagerPlugin = &fakeGRPCSecretsPlugin{} - -// Fake plugin manager -type fakePluginManager struct { - shouldFailOnStart bool - plugin *plugins.Plugin -} - -func (mg *fakePluginManager) SecretsManager(_ context.Context) *plugins.Plugin { - if mg.plugin != nil { - return mg.plugin - } - p := &plugins.Plugin{ - SecretsManager: &fakeGRPCSecretsPlugin{ - kv: make(map[Key]string), - }, - } - p.RegisterClient(&fakePluginClient{ - shouldFailOnStart: mg.shouldFailOnStart, - }) - mg.plugin = p - return p -} - -func NewFakeSecretsPluginManager(t *testing.T, shouldFailOnStart bool) plugins.SecretsPluginManager { - t.Helper() - return &fakePluginManager{ - shouldFailOnStart: shouldFailOnStart, - } -} - -// Fake plugin client -type fakePluginClient struct { - shouldFailOnStart bool - backendplugin.Plugin -} - -func (pc *fakePluginClient) Start(_ context.Context) error { - if pc.shouldFailOnStart { - return errors.New("mocked failed to start") - } - return nil -} - -func (pc *fakePluginClient) Stop(_ context.Context) error { - return nil -} - -func (pc *fakePluginClient) Logger() pluginsLogger.Logger { - return pluginsLogger.NewTestLogger() -} - -func SetupFatalCrashTest( - t *testing.T, - shouldFailOnStart bool, - isPluginErrorFatal bool, - isBackwardsCompatDisabled bool, -) (fatalCrashTestFields, error) { - t.Helper() - fatalFlagOnce = sync.Once{} - startupOnce = sync.Once{} - cfg := SetupTestConfig(t) - sqlStore := db.InitTestDB(t) - secretService := fakes.FakeSecretsService{} - kvstore := kvstore.ProvideService(sqlStore) - if isPluginErrorFatal { - _ = SetPluginStartupErrorFatal(context.Background(), GetNamespacedKVStore(kvstore), true) - } - features := NewFakeFeatureToggles(t, isBackwardsCompatDisabled) - manager := NewFakeSecretsPluginManager(t, shouldFailOnStart) - svc, err := ProvideService(sqlStore, secretService, manager, kvstore, features, cfg) - t.Cleanup(ResetPlugin) - return fatalCrashTestFields{ - SecretsKVStore: svc, - PluginManager: manager, - KVStore: kvstore, - SqlStore: sqlStore, - }, err -} - -type fatalCrashTestFields struct { - SecretsKVStore SecretsKVStore - PluginManager plugins.SecretsPluginManager - KVStore kvstore.KVStore - SqlStore db.DB -} - -func SetupTestConfig(t *testing.T) *setting.Cfg { - t.Helper() - rawCfg := ` - [secrets] - use_plugin = true - ` - raw, err := ini.Load([]byte(rawCfg)) - require.NoError(t, err) - return &setting.Cfg{Raw: raw} -} - -func ReplaceFallback(t *testing.T, kv SecretsKVStore, fb SecretsKVStore) error { - t.Helper() - if store, ok := kv.(*CachedKVStore); ok { - kv = store.store - } - if store, ok := kv.(*SecretsKVStorePlugin); ok { - store.fallbackStore = fb - return nil - } - return errors.New("not a plugin store") -} diff --git a/pkg/services/store/testdata/public_testdata.golden.jsonc b/pkg/services/store/testdata/public_testdata.golden.jsonc index 03f9a7f16cc..ea77fd3d487 100644 --- a/pkg/services/store/testdata/public_testdata.golden.jsonc +++ b/pkg/services/store/testdata/public_testdata.golden.jsonc @@ -87,5 +87,4 @@ } } ] -} - +} \ No newline at end of file diff --git a/pkg/storage/legacysql/dualwrite/dualwriter.go b/pkg/storage/legacysql/dualwrite/dualwriter.go index b2389d3d8b6..c34609cc9f5 100644 --- a/pkg/storage/legacysql/dualwrite/dualwriter.go +++ b/pkg/storage/legacysql/dualwrite/dualwriter.go @@ -3,6 +3,7 @@ package dualwrite import ( "context" "fmt" + "time" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" @@ -12,6 +13,7 @@ import ( "k8s.io/apiserver/pkg/registry/rest" "github.com/grafana/grafana-app-sdk/logging" + grafanarest "github.com/grafana/grafana/pkg/apiserver/rest" ) @@ -19,6 +21,8 @@ var ( _ grafanarest.Storage = (*dualWriter)(nil) ) +const backgroundReqTimeout = 5 * time.Second + // dualWriter will write first to legacy, then to unified keeping the same internal ID type dualWriter struct { legacy grafanarest.Storage @@ -29,35 +33,62 @@ type dualWriter struct { } func (d *dualWriter) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) { - // Call get (send read traffic in cloud) - unifiedGet, unifiedErr := d.unified.Get(ctx, name, options) + // If we read from unified, we can just do that and return. if d.readUnified { - return unifiedGet, unifiedErr + return d.unified.Get(ctx, name, options) } - + // If legacy is still our main store, lets first read from it. legacyGet, err := d.legacy.Get(ctx, name, options) if err != nil { return nil, err } - - if unifiedErr != nil && !apierrors.IsNotFound(unifiedErr) && !d.errorIsOK { - return nil, unifiedErr // the unified error + // Once we have successfully read from legacy, we can check if we want to fail on a unified read. + // If we allow the unified read to fail, we can do it in the background. + if d.errorIsOK { + go func() { + ctxBg, cancel := context.WithTimeout(context.Background(), backgroundReqTimeout) + defer cancel() + if _, err := d.unified.Get(ctxBg, name, options); err != nil { + d.log.Error("failed background GET to unified", "err", err) + } + }() + return legacyGet, nil + } + // If it's not okay to fail, we have to check it in the foreground. + _, unifiedErr := d.unified.Get(ctx, name, options) + if unifiedErr != nil && !apierrors.IsNotFound(unifiedErr) { + return nil, unifiedErr } return legacyGet, nil } func (d *dualWriter) List(ctx context.Context, options *metainternalversion.ListOptions) (runtime.Object, error) { - // Call list (send read traffic in cloud) - unifiedList, err := d.unified.List(ctx, options) + // If we read from unified, we can just do that and return. if d.readUnified { - return unifiedList, err + return d.unified.List(ctx, options) } - - if err != nil && !d.errorIsOK { + // If legacy is still the main store, lets first read from it. + legacyList, err := d.legacy.List(ctx, options) + if err != nil { return nil, err } - - return d.legacy.List(ctx, options) + // Once we have successfully listed from legacy, we can check if we want to fail on a unified list. + // If we allow the unified list to fail, we can do it in the background and return. + if d.errorIsOK { + go func() { + ctxBg, cancel := context.WithTimeout(context.Background(), backgroundReqTimeout) + defer cancel() + if _, err := d.unified.List(ctxBg, options); err != nil { + d.log.Error("failed background LIST to unified", "err", err) + } + }() + return legacyList, nil + } + // If it's not okay to fail, we have to check it in the foreground. + if _, err := d.unified.List(ctx, options); err != nil { + return nil, err + } + return legacyList, nil } // Create overrides the behavior of the generic DualWriter and writes to LegacyStorage and Storage. @@ -93,23 +124,43 @@ func (d *dualWriter) Create(ctx context.Context, in runtime.Object, createValida accCreated.SetResourceVersion("") accCreated.SetUID("") - storageObj, errObjectSt := d.unified.Create(ctx, createdCopy, createValidation, options) - if errObjectSt != nil { - log.Error("unable to create object in unified storage", "err", errObjectSt) - if d.errorIsOK { - return createdFromLegacy, nil - } - - // if we cannot create in unistore, attempt to clean up legacy - _, _, err = d.legacy.Delete(ctx, accCreated.GetName(), nil, &metav1.DeleteOptions{}) - if err != nil { - log.Error("unable to cleanup object in legacy storage", "err", err) - } - return nil, errObjectSt - } - + // If unified storage is the primary storage, let's just create it in the foreground and return it. if d.readUnified { + storageObj, errObjectSt := d.unified.Create(ctx, createdCopy, createValidation, options) + if errObjectSt != nil { + log.Error("unable to create object in unified storage", "err", errObjectSt) + // If we cannot create in unified storage, attempt to clean up legacy. + _, _, err = d.legacy.Delete(ctx, accCreated.GetName(), nil, &metav1.DeleteOptions{}) + if err != nil { + log.Error("unable to cleanup object in legacy storage", "err", err) + } + return nil, errObjectSt + } return storageObj, nil + } else if d.errorIsOK { + // If we don't use unified as the primary store and errors are okay, let's create it in the background. + go func() { + ctxBg, cancel := context.WithTimeout(context.Background(), backgroundReqTimeout) + defer cancel() + if _, err := d.unified.Create(ctxBg, createdCopy, createValidation, options); err != nil { + log.Error("unable to create object in unified storage", "err", err) + } + }() + } else { + // Otherwise let's create it in the foreground and return any error. + if _, err := d.unified.Create(ctx, createdCopy, createValidation, options); err != nil { + log.Error("unable to create object in unified storage", "err", err) + if d.errorIsOK { + return createdFromLegacy, nil + } + + // If we cannot create in unified storage, attempt to clean up legacy. + _, _, errLegacy := d.legacy.Delete(ctx, accCreated.GetName(), nil, &metav1.DeleteOptions{}) + if errLegacy != nil { + log.Error("unable to cleanup object in legacy storage", "err", errLegacy) + } + return nil, err + } } return createdFromLegacy, nil } @@ -125,16 +176,29 @@ func (d *dualWriter) Delete(ctx context.Context, name string, deleteValidation r if err != nil && (!d.readUnified || !d.errorIsOK && !apierrors.IsNotFound(err)) { return nil, false, err } - - objFromStorage, asyncStorage, err := d.unified.Delete(ctx, name, deleteValidation, options) + // If unified storage is our primary store, just delete it and return + if d.readUnified { + objFromStorage, asyncStorage, err := d.unified.Delete(ctx, name, deleteValidation, options) + if err != nil && !apierrors.IsNotFound(err) && !d.errorIsOK { + return nil, false, err + } + return objFromStorage, asyncStorage, nil + } else if d.errorIsOK { + // If errors are okay and unified is not primary, we can just run it as background operation. + go func() { + ctxBg, cancel := context.WithTimeout(context.Background(), backgroundReqTimeout) + defer cancel() + _, _, err := d.unified.Delete(ctxBg, name, deleteValidation, options) + if err != nil && !apierrors.IsNotFound(err) && !d.errorIsOK { + d.log.Error("failed background DELETE in unified storage", "err", err) + } + }() + } + // Otherwise we just run it in the foreground and return an error if any might happen. + _, _, err = d.unified.Delete(ctx, name, deleteValidation, options) if err != nil && !apierrors.IsNotFound(err) && !d.errorIsOK { return nil, false, err } - - if d.readUnified { - return objFromStorage, asyncStorage, nil - } - return objFromLegacy, asyncLegacy, nil } @@ -157,20 +221,24 @@ func (d *dualWriter) Update(ctx context.Context, name string, objInfo rest.Updat log.With("object", objFromLegacy).Error("could not update in legacy storage", "err", err) return nil, false, err } - - objFromStorage, created, err := d.unified.Update(ctx, name, objInfo, createValidation, updateValidation, forceAllowCreate, options) - if err != nil { - log.With("object", objFromStorage).Error("could not update in storage", "err", err) - if d.errorIsOK { - return objFromLegacy, createdLegacy, nil - } + // If unified storage is our primary store, just update it there and return. + if d.readUnified { + return d.unified.Update(ctx, name, objInfo, createValidation, updateValidation, forceAllowCreate, options) + } else if d.errorIsOK { + // If unified is not primary, but errors are okay, we can just run in the background. + go func() { + ctxBg, cancel := context.WithTimeout(context.Background(), backgroundReqTimeout) + defer cancel() + if _, _, err := d.unified.Update(ctxBg, name, objInfo, createValidation, updateValidation, forceAllowCreate, options); err != nil { + log.Error("failed background UPDATE to unified storage", "err", err) + } + }() + return objFromLegacy, createdLegacy, nil + } + // If we want to check unified errors just run it in foreground. + if _, _, err := d.unified.Update(ctx, name, objInfo, createValidation, updateValidation, forceAllowCreate, options); err != nil { return nil, false, err } - - if d.readUnified { - return objFromStorage, created, nil - } - return objFromLegacy, createdLegacy, nil } @@ -190,19 +258,25 @@ func (d *dualWriter) DeleteCollection(ctx context.Context, deleteValidation rest return nil, err } - deletedStorage, err := d.unified.DeleteCollection(ctx, deleteValidation, options, listOptions) - if err != nil { + // If unified is the primary store, we can just delete it there and return. + if d.readUnified { + return d.unified.DeleteCollection(ctx, deleteValidation, options, listOptions) + } else if d.errorIsOK { + // If unified storage is not the primary store and errors are okay, we can just run it in the background. + go func() { + ctxBg, cancel := context.WithTimeout(context.Background(), backgroundReqTimeout) + defer cancel() + if _, err := d.unified.DeleteCollection(ctxBg, deleteValidation, options, listOptions); err != nil { + log.Error("failed background DELETE collection to unified storage", "err", err) + } + }() + return deletedLegacy, nil + } + // Otherwise we have to check the error and run it in the foreground. + if deletedStorage, err := d.unified.DeleteCollection(ctx, deleteValidation, options, listOptions); err != nil { log.With("deleted", deletedStorage).Error("failed to delete collection successfully from Storage", "err", err) - if d.errorIsOK { - return deletedLegacy, nil - } return nil, err } - - if d.readUnified { - return deletedStorage, nil - } - return deletedLegacy, nil } diff --git a/pkg/storage/legacysql/dualwrite/dualwriter_mode1_test.go b/pkg/storage/legacysql/dualwrite/dualwriter_mode1_test.go index 74a73a573c2..0de214c4596 100644 --- a/pkg/storage/legacysql/dualwrite/dualwriter_mode1_test.go +++ b/pkg/storage/legacysql/dualwrite/dualwriter_mode1_test.go @@ -129,6 +129,15 @@ func TestMode1_Get(t *testing.T) { m.On("Get", mock.Anything, name, mock.Anything).Return(nil, errors.New("error")) }, }, + { + name: "should not block for unified storage", + setupLegacyFn: func(m *mock.Mock, name string) { + m.On("Get", mock.Anything, name, mock.Anything).Return(exampleObj, nil) + }, + setupStorageFn: func(m *mock.Mock, name string) { + m.On("Get", mock.Anything, name, mock.Anything).WaitUntil(time.After(time.Hour)).Return(anotherObj, nil) + }, + }, } name := "foo" diff --git a/pkg/storage/unified/apistore/go.mod b/pkg/storage/unified/apistore/go.mod index 27ec63b47c5..14dc81193b1 100644 --- a/pkg/storage/unified/apistore/go.mod +++ b/pkg/storage/unified/apistore/go.mod @@ -65,7 +65,7 @@ require ( github.com/Microsoft/go-winio v0.6.2 // indirect github.com/NYTimes/gziphandler v1.1.1 // indirect github.com/ProtonMail/go-crypto v1.1.6 // indirect - github.com/RoaringBitmap/roaring v1.9.3 // indirect + github.com/RoaringBitmap/roaring/v2 v2.4.5 // indirect github.com/VividCortex/mysqlerr v0.0.0-20170204212430-6c6b55f8796f // indirect github.com/Yiling-J/theine-go v0.6.0 // indirect github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b // indirect @@ -101,24 +101,24 @@ require ( github.com/beorn7/perks v1.0.1 // indirect github.com/bits-and-blooms/bitset v1.12.0 // indirect github.com/blang/semver/v4 v4.0.0 // indirect - github.com/blevesearch/bleve/v2 v2.4.3 // indirect - github.com/blevesearch/bleve_index_api v1.1.12 // indirect + github.com/blevesearch/bleve/v2 v2.4.4-0.20250319135056-b82baf10b205 // indirect + github.com/blevesearch/bleve_index_api v1.2.3 // indirect github.com/blevesearch/geo v0.1.20 // indirect - github.com/blevesearch/go-faiss v1.0.23 // indirect + github.com/blevesearch/go-faiss v1.0.24 // indirect github.com/blevesearch/go-porterstemmer v1.0.3 // indirect github.com/blevesearch/gtreap v0.1.1 // indirect github.com/blevesearch/mmap-go v1.0.4 // indirect - github.com/blevesearch/scorch_segment_api/v2 v2.2.16 // indirect + github.com/blevesearch/scorch_segment_api/v2 v2.3.5 // indirect github.com/blevesearch/segment v0.9.1 // indirect github.com/blevesearch/snowballstem v0.9.0 // indirect github.com/blevesearch/upsidedown_store_api v1.0.2 // indirect - github.com/blevesearch/vellum v1.0.10 // indirect - github.com/blevesearch/zapx/v11 v11.3.10 // indirect - github.com/blevesearch/zapx/v12 v12.3.10 // indirect - github.com/blevesearch/zapx/v13 v13.3.10 // indirect - github.com/blevesearch/zapx/v14 v14.3.10 // indirect - github.com/blevesearch/zapx/v15 v15.3.16 // indirect - github.com/blevesearch/zapx/v16 v16.1.8 // indirect + github.com/blevesearch/vellum v1.1.0 // indirect + github.com/blevesearch/zapx/v11 v11.4.1 // indirect + github.com/blevesearch/zapx/v12 v12.4.1 // indirect + github.com/blevesearch/zapx/v13 v13.4.1 // indirect + github.com/blevesearch/zapx/v14 v14.4.1 // indirect + github.com/blevesearch/zapx/v15 v15.4.1 // indirect + github.com/blevesearch/zapx/v16 v16.2.2-0.20250305220028-89edb0ef9aa9 // indirect github.com/bluele/gcache v0.0.2 // indirect github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 // indirect github.com/bufbuild/protocompile v0.4.0 // indirect diff --git a/pkg/storage/unified/apistore/go.sum b/pkg/storage/unified/apistore/go.sum index 1466cc1def3..a74e2346a1f 100644 --- a/pkg/storage/unified/apistore/go.sum +++ b/pkg/storage/unified/apistore/go.sum @@ -688,8 +688,8 @@ github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8 github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/ProtonMail/go-crypto v1.1.6 h1:ZcV+Ropw6Qn0AX9brlQLAUXfqLBc7Bl+f/DmNxpLfdw= github.com/ProtonMail/go-crypto v1.1.6/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= -github.com/RoaringBitmap/roaring v1.9.3 h1:t4EbC5qQwnisr5PrP9nt0IRhRTb9gMUgQF4t4S2OByM= -github.com/RoaringBitmap/roaring v1.9.3/go.mod h1:6AXUsoIEzDTFFQCe1RbGA6uFONMhvejWj5rqITANK90= +github.com/RoaringBitmap/roaring/v2 v2.4.5 h1:uGrrMreGjvAtTBobc0g5IrW1D5ldxDQYe2JW2gggRdg= +github.com/RoaringBitmap/roaring/v2 v2.4.5/go.mod h1:FiJcsfkGje/nZBZgCu0ZxCPOKD/hVXDS2dXi7/eUFE0= github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= github.com/VividCortex/mysqlerr v0.0.0-20170204212430-6c6b55f8796f h1:HR5nRmUQgXrwqZOwZ2DAc/aCi3Bu3xENpspW935vxu0= @@ -790,42 +790,42 @@ github.com/bits-and-blooms/bitset v1.12.0 h1:U/q1fAF7xXRhFCrhROzIfffYnu+dlS38vCZ github.com/bits-and-blooms/bitset v1.12.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= 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/blevesearch/bleve/v2 v2.4.3 h1:XDYj+1prgX84L2Cf+V3ojrOPqXxy0qxyd2uLMmeuD+4= -github.com/blevesearch/bleve/v2 v2.4.3/go.mod h1:hEPDPrbYw3vyrm5VOa36GyS4bHWuIf4Fflp7460QQXY= -github.com/blevesearch/bleve_index_api v1.1.12 h1:P4bw9/G/5rulOF7SJ9l4FsDoo7UFJ+5kexNy1RXfegY= -github.com/blevesearch/bleve_index_api v1.1.12/go.mod h1:PbcwjIcRmjhGbkS/lJCpfgVSMROV6TRubGGAODaK1W8= +github.com/blevesearch/bleve/v2 v2.4.4-0.20250319135056-b82baf10b205 h1:u6DQJ1k4FKwRNtsrVhIRQenNdtz31way7/LgWCluFzA= +github.com/blevesearch/bleve/v2 v2.4.4-0.20250319135056-b82baf10b205/go.mod h1:nSmFOQ7M264rKoM3jf63Gl2G+ylCgZGovPgL6ZEQYzU= +github.com/blevesearch/bleve_index_api v1.2.3 h1:aJJPhTb30zc68qg2HbvCZLAF1Eh5Hhm296mDAUFaUeg= +github.com/blevesearch/bleve_index_api v1.2.3/go.mod h1:rKQDl4u51uwafZxFrPD1R7xFOwKnzZW7s/LSeK4lgo0= github.com/blevesearch/geo v0.1.20 h1:paaSpu2Ewh/tn5DKn/FB5SzvH0EWupxHEIwbCk/QPqM= github.com/blevesearch/geo v0.1.20/go.mod h1:DVG2QjwHNMFmjo+ZgzrIq2sfCh6rIHzy9d9d0B59I6w= -github.com/blevesearch/go-faiss v1.0.23 h1:Wmc5AFwDLKGl2L6mjLX1Da3vCL0EKa2uHHSorcIS1Uc= -github.com/blevesearch/go-faiss v1.0.23/go.mod h1:OMGQwOaRRYxrmeNdMrXJPvVx8gBnvE5RYrr0BahNnkk= +github.com/blevesearch/go-faiss v1.0.24 h1:K79IvKjoKHdi7FdiXEsAhxpMuns0x4fM0BO93bW5jLI= +github.com/blevesearch/go-faiss v1.0.24/go.mod h1:OMGQwOaRRYxrmeNdMrXJPvVx8gBnvE5RYrr0BahNnkk= github.com/blevesearch/go-porterstemmer v1.0.3 h1:GtmsqID0aZdCSNiY8SkuPJ12pD4jI+DdXTAn4YRcHCo= github.com/blevesearch/go-porterstemmer v1.0.3/go.mod h1:angGc5Ht+k2xhJdZi511LtmxuEf0OVpvUUNrwmM1P7M= github.com/blevesearch/gtreap v0.1.1 h1:2JWigFrzDMR+42WGIN/V2p0cUvn4UP3C4Q5nmaZGW8Y= github.com/blevesearch/gtreap v0.1.1/go.mod h1:QaQyDRAT51sotthUWAH4Sj08awFSSWzgYICSZ3w0tYk= github.com/blevesearch/mmap-go v1.0.4 h1:OVhDhT5B/M1HNPpYPBKIEJaD0F3Si+CrEKULGCDPWmc= github.com/blevesearch/mmap-go v1.0.4/go.mod h1:EWmEAOmdAS9z/pi/+Toxu99DnsbhG1TIxUoRmJw/pSs= -github.com/blevesearch/scorch_segment_api/v2 v2.2.16 h1:uGvKVvG7zvSxCwcm4/ehBa9cCEuZVE+/zvrSl57QUVY= -github.com/blevesearch/scorch_segment_api/v2 v2.2.16/go.mod h1:VF5oHVbIFTu+znY1v30GjSpT5+9YFs9dV2hjvuh34F0= +github.com/blevesearch/scorch_segment_api/v2 v2.3.5 h1:t6NVTdS8xp1xHMJoe5WjxdZWLKhsIIb6kB0U49rhAno= +github.com/blevesearch/scorch_segment_api/v2 v2.3.5/go.mod h1:7gG1vsL3lu2L+7RuEtZ7PJhxGYE+AV9zjxTSHuXFMdM= github.com/blevesearch/segment v0.9.1 h1:+dThDy+Lvgj5JMxhmOVlgFfkUtZV2kw49xax4+jTfSU= github.com/blevesearch/segment v0.9.1/go.mod h1:zN21iLm7+GnBHWTao9I+Au/7MBiL8pPFtJBJTsk6kQw= github.com/blevesearch/snowballstem v0.9.0 h1:lMQ189YspGP6sXvZQ4WZ+MLawfV8wOmPoD/iWeNXm8s= github.com/blevesearch/snowballstem v0.9.0/go.mod h1:PivSj3JMc8WuaFkTSRDW2SlrulNWPl4ABg1tC/hlgLs= github.com/blevesearch/upsidedown_store_api v1.0.2 h1:U53Q6YoWEARVLd1OYNc9kvhBMGZzVrdmaozG2MfoB+A= github.com/blevesearch/upsidedown_store_api v1.0.2/go.mod h1:M01mh3Gpfy56Ps/UXHjEO/knbqyQ1Oamg8If49gRwrQ= -github.com/blevesearch/vellum v1.0.10 h1:HGPJDT2bTva12hrHepVT3rOyIKFFF4t7Gf6yMxyMIPI= -github.com/blevesearch/vellum v1.0.10/go.mod h1:ul1oT0FhSMDIExNjIxHqJoGpVrBpKCdgDQNxfqgJt7k= -github.com/blevesearch/zapx/v11 v11.3.10 h1:hvjgj9tZ9DeIqBCxKhi70TtSZYMdcFn7gDb71Xo/fvk= -github.com/blevesearch/zapx/v11 v11.3.10/go.mod h1:0+gW+FaE48fNxoVtMY5ugtNHHof/PxCqh7CnhYdnMzQ= -github.com/blevesearch/zapx/v12 v12.3.10 h1:yHfj3vXLSYmmsBleJFROXuO08mS3L1qDCdDK81jDl8s= -github.com/blevesearch/zapx/v12 v12.3.10/go.mod h1:0yeZg6JhaGxITlsS5co73aqPtM04+ycnI6D1v0mhbCs= -github.com/blevesearch/zapx/v13 v13.3.10 h1:0KY9tuxg06rXxOZHg3DwPJBjniSlqEgVpxIqMGahDE8= -github.com/blevesearch/zapx/v13 v13.3.10/go.mod h1:w2wjSDQ/WBVeEIvP0fvMJZAzDwqwIEzVPnCPrz93yAk= -github.com/blevesearch/zapx/v14 v14.3.10 h1:SG6xlsL+W6YjhX5N3aEiL/2tcWh3DO75Bnz77pSwwKU= -github.com/blevesearch/zapx/v14 v14.3.10/go.mod h1:qqyuR0u230jN1yMmE4FIAuCxmahRQEOehF78m6oTgns= -github.com/blevesearch/zapx/v15 v15.3.16 h1:Ct3rv7FUJPfPk99TI/OofdC+Kpb4IdyfdMH48sb+FmE= -github.com/blevesearch/zapx/v15 v15.3.16/go.mod h1:Turk/TNRKj9es7ZpKK95PS7f6D44Y7fAFy8F4LXQtGg= -github.com/blevesearch/zapx/v16 v16.1.8 h1:Bxzpw6YQpFs7UjoCV1+RvDw6fmAT2GZxldwX8b3wVBM= -github.com/blevesearch/zapx/v16 v16.1.8/go.mod h1:JqQlOqlRVaYDkpLIl3JnKql8u4zKTNlVEa3nLsi0Gn8= +github.com/blevesearch/vellum v1.1.0 h1:CinkGyIsgVlYf8Y2LUQHvdelgXr6PYuvoDIajq6yR9w= +github.com/blevesearch/vellum v1.1.0/go.mod h1:QgwWryE8ThtNPxtgWJof5ndPfx0/YMBh+W2weHKPw8Y= +github.com/blevesearch/zapx/v11 v11.4.1 h1:qFCPlFbsEdwbbckJkysptSQOsHn4s6ZOHL5GMAIAVHA= +github.com/blevesearch/zapx/v11 v11.4.1/go.mod h1:qNOGxIqdPC1MXauJCD9HBG487PxviTUUbmChFOAosGs= +github.com/blevesearch/zapx/v12 v12.4.1 h1:K77bhypII60a4v8mwvav7r4IxWA8qxhNjgF9xGdb9eQ= +github.com/blevesearch/zapx/v12 v12.4.1/go.mod h1:QRPrlPOzAxBNMI0MkgdD+xsTqx65zbuPr3Ko4Re49II= +github.com/blevesearch/zapx/v13 v13.4.1 h1:EnkEMZFUK0lsW/jOJJF2xOcp+W8TjEsyeN5BeAZEYYE= +github.com/blevesearch/zapx/v13 v13.4.1/go.mod h1:e6duBMlCvgbH9rkzNMnUa9hRI9F7ri2BRcHfphcmGn8= +github.com/blevesearch/zapx/v14 v14.4.1 h1:G47kGCshknBZzZAtjcnIAMn3oNx8XBLxp8DMq18ogyE= +github.com/blevesearch/zapx/v14 v14.4.1/go.mod h1:O7sDxiaL2r2PnCXbhh1Bvm7b4sP+jp4unE9DDPWGoms= +github.com/blevesearch/zapx/v15 v15.4.1 h1:B5IoTMUCEzFdc9FSQbhVOxAY+BO17c05866fNruiI7g= +github.com/blevesearch/zapx/v15 v15.4.1/go.mod h1:b/MreHjYeQoLjyY2+UaM0hGZZUajEbE0xhnr1A2/Q6Y= +github.com/blevesearch/zapx/v16 v16.2.2-0.20250305220028-89edb0ef9aa9 h1:a5GLOQkJqCnpD/lTfNiEVQnJ1e2CVOR8JFO6u1D7bLE= +github.com/blevesearch/zapx/v16 v16.2.2-0.20250305220028-89edb0ef9aa9/go.mod h1:v8B9VjBtELr2qRO0f3RoRG8RAZg3Jml/5SYuBT0hvGw= github.com/bluele/gcache v0.0.2 h1:WcbfdXICg7G/DGBh1PFfcirkWOQV+v077yF1pSy3DGw= github.com/bluele/gcache v0.0.2/go.mod h1:m15KV+ECjptwSPxKhOhQoAFQVtUFjTVkc3H8o0t/fp0= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= @@ -2660,6 +2660,7 @@ gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= diff --git a/pkg/storage/unified/apistore/util.go b/pkg/storage/unified/apistore/util.go index e41a50b5efa..a716bd77024 100644 --- a/pkg/storage/unified/apistore/util.go +++ b/pkg/storage/unified/apistore/util.go @@ -31,6 +31,25 @@ func toListRequest(k *resource.ResourceKey, opts storage.ListOptions) (*resource NextPageToken: predicate.Continue, } + if opts.ResourceVersion != "" { + rv, err := strconv.ParseInt(opts.ResourceVersion, 10, 64) + if err != nil { + return nil, predicate, apierrors.NewBadRequest(fmt.Sprintf("invalid resource version: %s", opts.ResourceVersion)) + } + req.ResourceVersion = rv + } + + switch opts.ResourceVersionMatch { + case "", metav1.ResourceVersionMatchNotOlderThan: + req.VersionMatch = resource.ResourceVersionMatch_NotOlderThan + case metav1.ResourceVersionMatchExact: + req.VersionMatch = resource.ResourceVersionMatch_Exact + default: + return nil, predicate, apierrors.NewBadRequest( + fmt.Sprintf("unsupported version match: %v", opts.ResourceVersionMatch), + ) + } + if opts.Predicate.Label != nil && !opts.Predicate.Label.Empty() { requirements, selectable := opts.Predicate.Label.Requirements() if !selectable { @@ -91,25 +110,6 @@ func toListRequest(k *resource.ResourceKey, opts storage.ListOptions) (*resource } } - if opts.ResourceVersion != "" { - rv, err := strconv.ParseInt(opts.ResourceVersion, 10, 64) - if err != nil { - return nil, predicate, apierrors.NewBadRequest(fmt.Sprintf("invalid resource version: %s", opts.ResourceVersion)) - } - req.ResourceVersion = rv - } - - switch opts.ResourceVersionMatch { - case "", metav1.ResourceVersionMatchNotOlderThan: - req.VersionMatch = resource.ResourceVersionMatch_NotOlderThan - case metav1.ResourceVersionMatchExact: - req.VersionMatch = resource.ResourceVersionMatch_Exact - default: - return nil, predicate, apierrors.NewBadRequest( - fmt.Sprintf("unsupported version match: %v", opts.ResourceVersionMatch), - ) - } - return req, predicate, nil } diff --git a/pkg/storage/unified/e2e/grafana.go b/pkg/storage/unified/e2e/grafana.go deleted file mode 100644 index 3d1971d5a19..00000000000 --- a/pkg/storage/unified/e2e/grafana.go +++ /dev/null @@ -1,87 +0,0 @@ -package e2e - -import ( - "fmt" - "net/url" - "os" - - "github.com/grafana/e2e" - gapi "github.com/grafana/grafana-api-golang-client" -) - -const ( - grafanaINI = ` -app_mode = development -target = all - -[grafana-apiserver] -address = 0.0.0.0:10000 -storage_type = unified-grpc - -[unified_storage.dashboards.dashboard.grafana.app] -dualWriterMode = 5 - -[unified_storage.folders.folder.grafana.app] -dualWriterMode = 5 -` - defaultGrafanaImage = "grafana/grafana:main" - grafanaBinary = "/run.sh" - grafanaHTTPPort = 3000 - grafanaGRPCPort = 10000 -) - -type GrafanaService struct { - *e2e.HTTPService -} - -func NewGrafanaService(name string, flags, envVars map[string]string) *GrafanaService { - svc := &GrafanaService{ - HTTPService: e2e.NewHTTPService( - name, - GetGrafanaImage(), - e2e.NewCommandWithoutEntrypoint(grafanaBinary, e2e.BuildArgs(flags)...), - e2e.NewHTTPReadinessProbe(grafanaHTTPPort, "/ready", 200, 299), - grafanaHTTPPort, - grafanaGRPCPort), - } - - svc.ConcreteService.SetEnvVars(envVars) - - return svc -} - -func (g *GrafanaService) GRPCEndpoint() string { - return g.HTTPService.NetworkEndpoint(grafanaGRPCPort) -} - -type GrafanaClient struct { - *gapi.Client -} - -func NewGrafanaClient(host string, orgID int64) (*GrafanaClient, error) { - cfg := gapi.Config{ - BasicAuth: url.UserPassword("admin", "admin"), - OrgID: orgID, - HTTPHeaders: map[string]string{ - "X-Disable-Provenance": "true", - }, - } - - client, err := gapi.New(fmt.Sprintf("http://%s/", host), cfg) - if err != nil { - return nil, err - } - - return &GrafanaClient{ - Client: client, - }, nil -} - -// GetDefaultImage returns the Docker image to use to run the Grafana.. -func GetGrafanaImage() string { - if img := os.Getenv("GRAFANA_IMAGE"); img != "" { - return img - } - - return defaultGrafanaImage -} diff --git a/pkg/storage/unified/e2e/postgres.go b/pkg/storage/unified/e2e/postgres.go deleted file mode 100644 index 68eea764b58..00000000000 --- a/pkg/storage/unified/e2e/postgres.go +++ /dev/null @@ -1,41 +0,0 @@ -package e2e - -import ( - "os" - - "github.com/grafana/e2e" -) - -const ( - defaultPostgresImage = "postgres:16.4" - postgresHTTPPort = 5432 -) - -// GetDefaultImage returns the Docker image to use to run the Postgres.. -func GetPostgresImage() string { - if img := os.Getenv("POSTGRES_IMAGE"); img != "" { - return img - } - - return defaultPostgresImage -} - -type PostgresService struct { - *e2e.HTTPService -} - -func NewPostgresService(name string, envVars map[string]string) *PostgresService { - svc := &PostgresService{ - HTTPService: e2e.NewHTTPService( - name, - GetPostgresImage(), - nil, - nil, - postgresHTTPPort, - ), - } - - svc.SetEnvVars(envVars) - - return svc -} diff --git a/pkg/storage/unified/e2e/storage.go b/pkg/storage/unified/e2e/storage.go deleted file mode 100644 index 96e7c9a206b..00000000000 --- a/pkg/storage/unified/e2e/storage.go +++ /dev/null @@ -1,57 +0,0 @@ -package e2e - -import ( - "github.com/grafana/e2e" -) - -const ( - storageINI = ` -app_mode = development -target = storage-server - -[grafana-apiserver] -storage_type = unified - -[database] -type = postgres -host = postgres:5432 -name = grafana -user = admin -password = admin -ssl_mode = disable -` -) - -type StorageService struct { - *e2e.ConcreteService -} - -func NewStorageService(name string, flags, envVars map[string]string) *StorageService { - args := append([]string{"target"}, e2e.BuildArgs(flags)...) - svc := &StorageService{ - ConcreteService: e2e.NewConcreteService( - name, - GetGrafanaImage(), - e2e.NewCommandWithoutEntrypoint(grafanaBinary, args...), - e2e.NewTCPReadinessProbe(10000), - 10000, - ), - } - - svc.SetEnvVars(envVars) - svc.Endpoint(10000) - - return svc -} - -// TODO: remove this method when the storage-server module has a graceful shutdown on docker stop -// the following method is implemented because the storage-server module is not shutting down gracefully -// https://github.com/grafana/search-and-storage-team/issues/228 -func (s *StorageService) Stop() error { - _ = s.ConcreteService.Stop() - return nil -} - -func (s *StorageService) GRPCEndpoint() string { - return s.NetworkEndpoint(grafanaGRPCPort) -} diff --git a/pkg/storage/unified/e2e/unified_scenario.go b/pkg/storage/unified/e2e/unified_scenario.go deleted file mode 100644 index 8125d8da65c..00000000000 --- a/pkg/storage/unified/e2e/unified_scenario.go +++ /dev/null @@ -1,141 +0,0 @@ -package e2e - -import ( - "fmt" - "os" - "path/filepath" - "strings" - - "github.com/grafana/e2e" -) - -type UnifiedScenario struct { - *e2e.Scenario - - Grafana *GrafanaService - UnifiedStorage *StorageService - Postgres *PostgresService -} - -func NewUnifiedScenario() (*UnifiedScenario, error) { - s, err := e2e.NewScenario(GetNetworkName()) - if err != nil { - return nil, err - } - - return &UnifiedScenario{ - Scenario: s, - }, nil -} - -func (s *UnifiedScenario) NewGrafanaService(name string, grpcEndpoint string) (*GrafanaService, error) { - flags := map[string]string{ - "--config": fmt.Sprintf("/shared/%s.ini", name), - } - fts := []string{ - "grafanaAPIServerWithExperimentalAPIs", - "kubernetesClientDashboardsFolders", - "grafanaAPIServerEnsureKubectlAccess", - "unifiedStorageSearch", - "unifiedStorageSearchUI", - "kubernetesCliDashboards", - "kubernetesDashboardsAPI", - "kubernetesFolders", - "unified_storage", - } - envVars := map[string]string{ - "GF_PATHS_CONFIG": fmt.Sprintf("/shared/%s.ini", name), - "GF_FEATURE_TOGGLES_ENABLE": strings.Join(fts, ","), - "GF_GRAFANA_APISERVER_ADDRESS": grpcEndpoint, - "GF_GRAFANA_APISERVER_STORAGE_TYPE": "unified-grpc", - "GF_DATABASE_TYPE": "postgres", - "GF_DATABASE_HOST": "postgres:5432", - "GF_DATABASE_NAME": "grafana", - "GF_DATABASE_USER": "admin", - "GF_DATABASE_PASSWORD": "admin", - "GF_DATABASE_SSL_MODE": "disable", - } - - if err := s.loadCfg(name, grafanaINI); err != nil { - return nil, err - } - g := NewGrafanaService(name, flags, envVars) - s.Grafana = g - return g, nil -} - -func (s *UnifiedScenario) NewGrafanaClient(grafanaName string, orgID int64) (*GrafanaClient, error) { - g := s.Grafana - return NewGrafanaClient(g.HTTPEndpoint(), orgID) -} - -func (s *UnifiedScenario) NewStorageService(name string) (*StorageService, error) { - flags := map[string]string{ - "--config": "/shared/storage.ini", - } - envVars := map[string]string{ - "GF_PATHS_CONFIG": "/shared/storage.ini", - "GF_DEFAULT_TARGET": "storage-server", - "GF_GRPC_SERVER_ADDRESS": "0.0.0.0:10000", - "GF_FEATURE_TOGGLES_ENABLE": "unifiedStorage,unifiedStorageSearch", - "GF_RESOURCE_API_DB_TYPE": "postgres", - "GF_RESOURCE_API_DB_HOST": "postgres:5432", - "GF_RESOURCE_API_DB_NAME": "grafana", - "GF_RESOURCE_API_DB_USER": "admin", - "GF_RESOURCE_API_DB_PASS": "admin", - "GF_DATABASE_TYPE": "postgres", - "GF_DATABASE_HOST": "postgres:5432", - "GF_DATABASE_NAME": "grafana", - "GF_DATABASE_USER": "admin", - "GF_DATABASE_PASSWORD": "admin", - "GF_DATABASE_SSL_MODE": "disable", - } - - if err := s.loadCfg(name, storageINI); err != nil { - return nil, err - } - us := NewStorageService(name, flags, envVars) - s.UnifiedStorage = us - return us, nil -} - -func (s *UnifiedScenario) NewPostgresService(name string) *PostgresService { - ps := NewPostgresService(name, map[string]string{ - "POSTGRES_USER": "admin", - "POSTGRES_PASSWORD": "admin", - "POSTGRES_DB": "grafana", - }) - s.Postgres = ps - - return ps -} - -func (s *UnifiedScenario) loadCfg(svc, content string) error { - dst := fmt.Sprintf("%s/%s.ini", s.SharedDir(), svc) - destination, err := os.Create(filepath.Clean(dst)) - if err != nil { - return err - } - - _, err = destination.Write([]byte(content)) - if err != nil { - return err - } - - return nil -} - -const ( - defaultNetworkName = "e2e-grafana-unified" -) - -// GetNetworkName returns the docker network name to run tests within. -func GetNetworkName() string { - // If the E2E_NETWORK_NAME is set, use that for the network name. - // Otherwise, return the default network name. - if os.Getenv("E2E_NETWORK_NAME") != "" { - return os.Getenv("E2E_NETWORK_NAME") - } - - return defaultNetworkName -} diff --git a/pkg/storage/unified/e2e/unified_test.go b/pkg/storage/unified/e2e/unified_test.go deleted file mode 100644 index 8393471ddb0..00000000000 --- a/pkg/storage/unified/e2e/unified_test.go +++ /dev/null @@ -1,61 +0,0 @@ -package e2e - -import ( - "testing" - - gapi "github.com/grafana/grafana-api-golang-client" - "github.com/stretchr/testify/require" -) - -func TestUnifiedStorage(t *testing.T) { - // TODO: remove this line when ci pipeline is ready - t.Skip("skipping e2e test") - - t.Run("Create folder and dashboard", func(t *testing.T) { - s, err := NewUnifiedScenario() - require.NoError(t, err) - - postgres := s.NewPostgresService("postgres") - require.NoError(t, s.Scenario.StartAndWaitReady(postgres)) - - storage, err := s.NewStorageService("storage") - require.NoError(t, err) - require.NoError(t, s.Scenario.StartAndWaitReady(storage)) - - grafana, err := s.NewGrafanaService("grafana", storage.GRPCEndpoint()) - require.NoError(t, err) - require.NoError(t, s.Scenario.StartAndWaitReady(grafana)) - - const org1 = 1 - gorg1, err := s.NewGrafanaClient("grafana", org1) - require.NoError(t, err) - - folderOrg1, err := gorg1.NewFolder("test-folder-1") - require.NoError(t, err) - - const uid = "test-dashboard-1" - dashboard := gapi.Dashboard{ - Model: map[string]interface{}{ - "uid": uid, - "title": "Test Dashboard 1", - }, - FolderID: folderOrg1.ID, - FolderUID: folderOrg1.UID, - Overwrite: false, - Message: "Test Dashboard 1", - } - - dashboardOrg1, err := gorg1.NewDashboard(dashboard) - require.NoError(t, err) - - newDashboard, err := gorg1.DashboardByUID(uid) - require.NoError(t, err) - require.Equal(t, dashboardOrg1.UID, newDashboard.Model["uid"]) - - newFolder, err := gorg1.Folder(folderOrg1.ID) - require.NoError(t, err) - require.Equal(t, folderOrg1.UID, newFolder.UID) - - require.NoError(t, s.Scenario.Stop(grafana, storage, postgres)) - }) -} diff --git a/pkg/storage/unified/sql/backend.go b/pkg/storage/unified/sql/backend.go index f8deb95596f..c4f2a82e725 100644 --- a/pkg/storage/unified/sql/backend.go +++ b/pkg/storage/unified/sql/backend.go @@ -798,6 +798,19 @@ func (b *backend) getHistory(ctx context.Context, req *resource.ListRequest, cb listReq.StartRV = continueToken.ResourceVersion } + // Set ExactRV when using Exact matching + if req.VersionMatch == resource.ResourceVersionMatch_Exact { + if req.ResourceVersion <= 0 { + return 0, fmt.Errorf("expecting an explicit resource version query when using Exact matching") + } + listReq.ExactRV = req.ResourceVersion + } + + // Set MinRV when using NotOlderThan matching to filter at the database level + if req.ResourceVersion > 0 && req.VersionMatch == resource.ResourceVersionMatch_NotOlderThan { + listReq.MinRV = req.ResourceVersion + } + err := b.db.WithTx(ctx, ReadCommittedRO, func(ctx context.Context, tx db.Tx) error { var err error iter.listRV, err = fetchLatestRV(ctx, tx, b.dialect, req.Options.Key.Group, req.Options.Key.Resource) diff --git a/pkg/storage/unified/sql/backend_test.go b/pkg/storage/unified/sql/backend_test.go index d2ef981e91b..24b9317c568 100644 --- a/pkg/storage/unified/sql/backend_test.go +++ b/pkg/storage/unified/sql/backend_test.go @@ -4,6 +4,7 @@ import ( "context" "database/sql/driver" "errors" + "fmt" "testing" sqlmock "github.com/DATA-DOG/go-sqlmock" @@ -456,3 +457,131 @@ func TestBackend_restore(t *testing.T) { require.ErrorContains(t, err, "update history uid") }) } + +func TestBackend_getHistory(t *testing.T) { + t.Parallel() + + // Common setup + key := &resource.ResourceKey{ + Namespace: "ns", + Group: "gr", + Resource: "rs", + Name: "nm", + } + rv1, rv2, rv3 := int64(100), int64(200), int64(300) + cols := []string{"resource_version", "namespace", "name", "folder", "value"} + + tests := []struct { + name string + versionMatch resource.ResourceVersionMatch + resourceVersion int64 + expectedVersions []int64 + expectedListRv int64 + expectedRowsCount int + expectedErr string + }{ + { + name: "with ResourceVersionMatch_NotOlderThan", + versionMatch: resource.ResourceVersionMatch_NotOlderThan, + resourceVersion: rv2, + expectedVersions: []int64{rv3, rv2}, + expectedListRv: rv3, + expectedRowsCount: 2, + }, + { + name: "with ResourceVersionMatch_Exact", + versionMatch: resource.ResourceVersionMatch_Exact, + resourceVersion: rv2, + expectedVersions: []int64{rv2}, + expectedListRv: rv3, + expectedRowsCount: 1, + }, + { + name: "without version matcher", + versionMatch: resource.ResourceVersionMatch_NotOlderThan, + expectedVersions: []int64{rv3, rv2, rv1}, + expectedListRv: rv3, + expectedRowsCount: 3, + }, + { + name: "error with ResourceVersionMatch_Exact and ResourceVersion <= 0", + versionMatch: resource.ResourceVersionMatch_Exact, + resourceVersion: 0, + expectedErr: "expecting an explicit resource version query when using Exact matching", + }, + } + + for _, tc := range tests { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + b, ctx := setupBackendTest(t) + + // Build request with appropriate matcher + req := &resource.ListRequest{ + Options: &resource.ListOptions{Key: key}, + ResourceVersion: tc.resourceVersion, + VersionMatch: tc.versionMatch, + Source: resource.ListRequest_HISTORY, + } + + // Set up mock expectations only if we don't expect an error + if tc.expectedErr == "" { + // Build expected values map + expectedValues := make(map[int64]string) + for _, rv := range tc.expectedVersions { + expectedValues[rv] = fmt.Sprintf("rv-%d", rv) + } + + // Callback that tracks returned items + callback := func(iter resource.ListIterator) error { + count := 0 + for iter.Next() { + count++ + currentRV := iter.ResourceVersion() + expectedValue, ok := expectedValues[currentRV] + require.True(t, ok, "Got unexpected RV: %d", currentRV) + require.Equal(t, expectedValue, string(iter.Value())) + } + require.Equal(t, tc.expectedRowsCount, count) + return nil + } + + b.SQLMock.ExpectBegin() + + // Expect fetch latest RV call + latestRVRows := sqlmock.NewRows([]string{"resource_version", "unix_timestamp"}). + AddRow(rv3, 0) + b.SQLMock.ExpectQuery("SELECT .* FROM resource_version").WillReturnRows(latestRVRows) + + // Expect history query + historyRows := sqlmock.NewRows(cols) + for _, rv := range tc.expectedVersions { + historyRows.AddRow( + rv, // resource_version + "ns", // namespace + "nm", // name + "folder", // folder + []byte(fmt.Sprintf("rv-%d", rv)), // value + ) + } + b.SQLMock.ExpectQuery("SELECT .* FROM resource_history").WillReturnRows(historyRows) + b.SQLMock.ExpectCommit() + + // Execute the test + listRv, err := b.getHistory(ctx, req, callback) + require.NoError(t, err) + require.Equal(t, tc.expectedListRv, listRv) + } else { + // For error cases, we use a simple empty callback + callback := func(iter resource.ListIterator) error { return nil } + + // Execute the test expecting an error + listRv, err := b.getHistory(ctx, req, callback) + require.Zero(t, listRv) + require.Error(t, err) + require.ErrorContains(t, err, tc.expectedErr) + } + }) + } +} diff --git a/pkg/storage/unified/sql/data/resource_history_get.sql b/pkg/storage/unified/sql/data/resource_history_get.sql index 5ab25eb2e0c..066f0498810 100644 --- a/pkg/storage/unified/sql/data/resource_history_get.sql +++ b/pkg/storage/unified/sql/data/resource_history_get.sql @@ -18,4 +18,10 @@ WHERE 1 = 1 {{ if (gt .StartRV 0) }} AND {{ .Ident "resource_version" }} < {{ .Arg .StartRV }} {{ end }} + {{ if (gt .MinRV 0) }} + AND {{ .Ident "resource_version" }} >= {{ .Arg .MinRV }} + {{ end }} + {{ if (gt .ExactRV 0) }} + AND {{ .Ident "resource_version" }} = {{ .Arg .ExactRV }} + {{ end }} ORDER BY resource_version DESC diff --git a/pkg/storage/unified/sql/queries.go b/pkg/storage/unified/sql/queries.go index 3ea34472024..022c33f4775 100644 --- a/pkg/storage/unified/sql/queries.go +++ b/pkg/storage/unified/sql/queries.go @@ -247,6 +247,8 @@ type sqlGetHistoryRequest struct { Key *resource.ResourceKey Trash bool // only deleted items StartRV int64 // from NextPageToken + MinRV int64 // minimum resource version for NotOlderThan + ExactRV int64 // exact resource version for Exact } func (r sqlGetHistoryRequest) Validate() error { diff --git a/pkg/tests/api/plugins/data/expectedListResp.json b/pkg/tests/api/plugins/data/expectedListResp.json index 7fab5a77342..00fd4953f41 100644 --- a/pkg/tests/api/plugins/data/expectedListResp.json +++ b/pkg/tests/api/plugins/data/expectedListResp.json @@ -178,8 +178,8 @@ "url": "https://grafana.com/docs/grafana/latest/datasources/azuremonitor/" }, { - "name": "Apache License", - "url": "https://github.com/grafana/azure-monitor-datasource/blob/master/LICENSE" + "name": "License", + "url": "https://github.com/grafana/grafana/blob/HEAD/LICENSE" }, { "name": "Raise issue", @@ -458,6 +458,10 @@ { "name": "Raise issue", "url": "https://github.com/grafana/grafana/issues/new" + }, + { + "name": "Documentation", + "url": "https://grafana.com/docs/grafana/latest/datasources/aws-cloudwatch/" } ], "logos": { @@ -864,6 +868,10 @@ { "name": "Raise issue", "url": "https://github.com/grafana/grafana/issues/new" + }, + { + "name": "Documentation", + "url": "https://grafana.com/docs/grafana/latest/datasources/google-cloud-monitoring/" } ], "logos": { @@ -1397,6 +1405,10 @@ { "name": "Raise issue", "url": "https://github.com/grafana/grafana/issues/new" + }, + { + "name": "Documentation", + "url": "https://grafana.com/docs/grafana/latest/datasources/mssql/" } ], "logos": { @@ -2105,7 +2117,7 @@ }, { "name": "Raise issue", - "url": "https://github.com/grafana/tempo/issues/new" + "url": "https://github.com/grafana/grafana/issues/new" }, { "name": "Documentation", @@ -2256,6 +2268,10 @@ { "name": "Raise issue", "url": "https://github.com/grafana/grafana/issues/new" + }, + { + "name": "Documentation", + "url": "https://grafana.com/docs/grafana/latest/panels-visualizations/visualizations/time-series/" } ], "logos": { diff --git a/public/app/features/dashboard-scene/edit-pane/VizPanelEditableElement.tsx b/public/app/features/dashboard-scene/edit-pane/VizPanelEditableElement.tsx index 7bc07b3eeb9..ef73f73598f 100644 --- a/public/app/features/dashboard-scene/edit-pane/VizPanelEditableElement.tsx +++ b/public/app/features/dashboard-scene/edit-pane/VizPanelEditableElement.tsx @@ -71,19 +71,12 @@ export class VizPanelEditableElement implements EditableDashboardElement, BulkAc ); }, [panel]); - const layoutCategory = useMemo(() => { - if (isDashboardLayoutItem(layoutElement) && layoutElement.getOptions) { - return layoutElement.getOptions(); - } - return undefined; - }, [layoutElement]); + const layoutCategories = useMemo( + () => (isDashboardLayoutItem(layoutElement) && layoutElement.getOptions ? layoutElement.getOptions() : []), + [layoutElement] + ); - const categories = [panelOptions]; - if (layoutCategory) { - categories.push(layoutCategory); - } - - return categories; + return [panelOptions, ...layoutCategories]; } public onDelete() { diff --git a/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataQueriesTab.test.tsx b/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataQueriesTab.test.tsx index c0131b6847d..f3c58622b27 100644 --- a/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataQueriesTab.test.tsx +++ b/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataQueriesTab.test.tsx @@ -12,6 +12,7 @@ import { FieldType, LoadingState, PanelData, + PluginType, TimeRange, toDataFrame, } from '@grafana/data'; @@ -249,6 +250,7 @@ jest.mock('@grafana/runtime', () => ({ config: { ...jest.requireActual('@grafana/runtime').config, defaultDatasource: 'gdev-testdata', + expressionsEnabled: true, }, })); @@ -353,6 +355,50 @@ describe('PanelDataQueriesTab', () => { expect(modelMock.onQueriesChange).toHaveBeenCalledWith([]); }); + + it('renders add expression button when datasource meta.backend is true', async () => { + // arrange + const modelMock = await createModelMock(); + const dsSettingsMock: DataSourceInstanceSettings = { + id: 1, + uid: 'gdev-testdata', + name: 'testDs1', + type: 'grafana-testdata-datasource', + meta: { + id: 'grafana-testdata-datasource', + info: { + logos: { + small: 'test-logo.png', + large: 'test-logo.png', + }, + author: { + name: '', + url: undefined, + }, + description: '', + links: [], + screenshots: [], + updated: '', + version: '', + }, + backend: true, + name: '', + type: PluginType.datasource, + module: '', + baseUrl: '', + }, + readOnly: false, + jsonData: {}, + access: 'proxy', + }; + modelMock.setState({ datasource: ds1Mock, dsSettings: dsSettingsMock }); + + // act + render(); + + // assert + await screen.findByTestId(selectors.components.QueryTab.addExpression); + }); }); describe('query options', () => { diff --git a/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataQueriesTab.tsx b/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataQueriesTab.tsx index 31dbfc108e5..867fd35a06a 100644 --- a/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataQueriesTab.tsx +++ b/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataQueriesTab.tsx @@ -283,7 +283,7 @@ export class PanelDataQueriesTab extends SceneObjectBase { diff --git a/public/app/features/dashboard-scene/panel-edit/getPanelFrameOptions.tsx b/public/app/features/dashboard-scene/panel-edit/getPanelFrameOptions.tsx index d0c57d18d2b..26def57f9d7 100644 --- a/public/app/features/dashboard-scene/panel-edit/getPanelFrameOptions.tsx +++ b/public/app/features/dashboard-scene/panel-edit/getPanelFrameOptions.tsx @@ -84,8 +84,8 @@ export function getPanelFrameOptions(panel: VizPanel): OptionsPaneCategoryDescri ) ); - if (isDashboardLayoutItem(layoutElement) && layoutElement.getOptions) { - descriptor.addCategory(layoutElement.getOptions()); + if (isDashboardLayoutItem(layoutElement)) { + layoutElement.getOptions?.().forEach((category) => descriptor.addCategory(category)); } return descriptor; diff --git a/public/app/features/dashboard-scene/scene/convertScopesToAdHocFilters.test.ts b/public/app/features/dashboard-scene/scene/convertScopesToAdHocFilters.test.ts deleted file mode 100644 index c6b84a4d39f..00000000000 --- a/public/app/features/dashboard-scene/scene/convertScopesToAdHocFilters.test.ts +++ /dev/null @@ -1,288 +0,0 @@ -import { Scope, ScopeSpecFilter } from '@grafana/data'; -import { FilterOrigin } from '@grafana/scenes'; - -import { convertScopesToAdHocFilters } from './convertScopesToAdHocFilters'; - -describe('convertScopesToAdHocFilters', () => { - it('should return empty filters when no scopes are provided', () => { - let scopes = generateScopes([]); - - expect(scopes).toEqual([]); - expect(convertScopesToAdHocFilters(scopes)).toEqual([]); - - scopes = generateScopes([[], []]); - - expect(convertScopesToAdHocFilters(scopes)).toEqual([]); - }); - - it('should return filters formatted for adHoc from a single scope', () => { - let scopes = generateScopes([ - [ - { key: 'key1', value: 'value1', operator: 'equals' }, - { key: 'key2', value: 'value2', operator: 'not-equals' }, - { key: 'key3', value: 'value3', operator: 'regex-not-match' }, - ], - ]); - - expect(convertScopesToAdHocFilters(scopes)).toEqual([ - { key: 'key1', value: 'value1', operator: '=', origin: FilterOrigin.Scopes, values: ['value1'] }, - { key: 'key2', value: 'value2', operator: '!=', origin: FilterOrigin.Scopes, values: ['value2'] }, - { key: 'key3', value: 'value3', operator: '!~', origin: FilterOrigin.Scopes, values: ['value3'] }, - ]); - - scopes = generateScopes([[{ key: 'key3', value: 'value3', operator: 'regex-match' }]]); - - expect(convertScopesToAdHocFilters(scopes)).toEqual([ - { key: 'key3', value: 'value3', operator: '=~', origin: FilterOrigin.Scopes, values: ['value3'] }, - ]); - }); - - it('should return filters formatted for adHoc from multiple scopes with single values', () => { - let scopes = generateScopes([ - [{ key: 'key1', value: 'value1', operator: 'equals' }], - [{ key: 'key2', value: 'value2', operator: 'regex-match' }], - ]); - - expect(convertScopesToAdHocFilters(scopes)).toEqual([ - { key: 'key1', value: 'value1', operator: '=', origin: FilterOrigin.Scopes, values: ['value1'] }, - { key: 'key2', value: 'value2', operator: '=~', origin: FilterOrigin.Scopes, values: ['value2'] }, - ]); - }); - - it('should return filters formatted for adHoc from multiple scopes with multiple values', () => { - let scopes = generateScopes([ - [ - { key: 'key1', value: 'value1', operator: 'equals' }, - { key: 'key2', value: 'value2', operator: 'not-equals' }, - ], - [ - { key: 'key3', value: 'value3', operator: 'regex-match' }, - { key: 'key4', value: 'value4', operator: 'regex-match' }, - ], - ]); - - expect(convertScopesToAdHocFilters(scopes)).toEqual([ - { key: 'key1', value: 'value1', operator: '=', origin: FilterOrigin.Scopes, values: ['value1'] }, - { key: 'key2', value: 'value2', operator: '!=', origin: FilterOrigin.Scopes, values: ['value2'] }, - { key: 'key3', value: 'value3', operator: '=~', origin: FilterOrigin.Scopes, values: ['value3'] }, - { key: 'key4', value: 'value4', operator: '=~', origin: FilterOrigin.Scopes, values: ['value4'] }, - ]); - }); - - it('should return formatted filters and concat values of the same key, coming from different scopes, if operator supports multi-value', () => { - let scopes = generateScopes([ - [ - { key: 'key1', value: 'value1', operator: 'equals' }, - { key: 'key2', value: 'value2', operator: 'not-equals' }, - ], - [ - { key: 'key1', value: 'value3', operator: 'equals' }, - { key: 'key2', value: 'value4', operator: 'not-equals' }, - ], - [{ key: 'key1', value: 'value5', operator: 'equals' }], - ]); - - expect(convertScopesToAdHocFilters(scopes)).toEqual([ - { - key: 'key1', - value: 'value1', - operator: '=|', - origin: FilterOrigin.Scopes, - values: ['value1', 'value3', 'value5'], - }, - { key: 'key2', value: 'value2', operator: '!=|', origin: FilterOrigin.Scopes, values: ['value2', 'value4'] }, - ]); - }); - - it('should ignore the rest of the duplicate filters, if they are a combination of equals and not-equals', () => { - let scopes = generateScopes([ - [{ key: 'key1', value: 'value1', operator: 'equals' }], - [{ key: 'key1', value: 'value2', operator: 'not-equals' }], - [{ key: 'key1', value: 'value3', operator: 'equals' }], - ]); - - expect(convertScopesToAdHocFilters(scopes)).toEqual([ - { - key: 'key1', - value: 'value1', - operator: '=|', - origin: FilterOrigin.Scopes, - values: ['value1', 'value3'], - }, - { - key: 'key1', - value: 'value2', - operator: '!=', - origin: FilterOrigin.Scopes, - values: ['value2'], - }, - ]); - }); - - it('should return formatted filters and keep only the first filter of the same key if operator is not multi-value', () => { - let scopes = generateScopes([ - [ - { key: 'key1', value: 'value1', operator: 'regex-match' }, - { key: 'key2', value: 'value2', operator: 'not-equals' }, - ], - [ - { key: 'key1', value: 'value3', operator: 'regex-match' }, - { key: 'key2', value: 'value4', operator: 'not-equals' }, - ], - [{ key: 'key1', value: 'value5', operator: 'equals' }], - ]); - - expect(convertScopesToAdHocFilters(scopes)).toEqual([ - { - key: 'key1', - value: 'value1', - operator: '=~', - origin: FilterOrigin.Scopes, - values: ['value1'], - }, - { key: 'key2', value: 'value2', operator: '!=|', origin: FilterOrigin.Scopes, values: ['value2', 'value4'] }, - { - key: 'key1', - value: 'value3', - operator: '=~', - origin: FilterOrigin.Scopes, - values: ['value3'], - }, - { - key: 'key1', - value: 'value5', - operator: '=', - origin: FilterOrigin.Scopes, - values: ['value5'], - }, - ]); - - scopes = generateScopes([ - [{ key: 'key1', value: 'value1', operator: 'regex-match' }], - [{ key: 'key1', value: 'value5', operator: 'equals' }], - [{ key: 'key1', value: 'value3', operator: 'regex-match' }], - ]); - - expect(convertScopesToAdHocFilters(scopes)).toEqual([ - { - key: 'key1', - value: 'value1', - operator: '=~', - origin: FilterOrigin.Scopes, - values: ['value1'], - }, - { - key: 'key1', - value: 'value5', - operator: '=', - origin: FilterOrigin.Scopes, - values: ['value5'], - }, - { - key: 'key1', - value: 'value3', - operator: '=~', - origin: FilterOrigin.Scopes, - values: ['value3'], - }, - ]); - }); - - it('should return formatted filters and concat values that are multi-value and drop duplicates with non multi-value operator', () => { - let scopes = generateScopes([ - [{ key: 'key1', value: 'value1', operator: 'equals' }], - [{ key: 'key1', value: 'value2', operator: 'regex-match' }], - [{ key: 'key1', value: 'value3', operator: 'equals' }], - ]); - - expect(convertScopesToAdHocFilters(scopes)).toEqual([ - { - key: 'key1', - value: 'value1', - operator: '=|', - origin: FilterOrigin.Scopes, - values: ['value1', 'value3'], - }, - { - key: 'key1', - value: 'value2', - operator: '=~', - origin: FilterOrigin.Scopes, - values: ['value2'], - }, - ]); - - scopes = generateScopes([ - [ - { key: 'key1', value: 'value1', operator: 'equals' }, - { key: 'key2', value: 'value2', operator: 'equals' }, - ], - [ - { key: 'key1', value: 'value3', operator: 'equals' }, - { key: 'key2', value: 'value4', operator: 'equals' }, - ], - [ - { key: 'key1', value: 'value5', operator: 'regex-match' }, - { key: 'key2', value: 'value6', operator: 'equals' }, - ], - [ - { key: 'key1', value: 'value7', operator: 'equals' }, - { key: 'key2', value: 'value8', operator: 'regex-match' }, - ], - [ - { key: 'key1', value: 'value9', operator: 'equals' }, - { key: 'key2', value: 'value10', operator: 'equals' }, - ], - ]); - - expect(convertScopesToAdHocFilters(scopes)).toEqual([ - { - key: 'key1', - value: 'value1', - operator: '=|', - origin: FilterOrigin.Scopes, - values: ['value1', 'value3', 'value7', 'value9'], - }, - { - key: 'key2', - value: 'value2', - operator: '=|', - origin: FilterOrigin.Scopes, - values: ['value2', 'value4', 'value6', 'value10'], - }, - { - key: 'key1', - value: 'value5', - operator: '=~', - origin: FilterOrigin.Scopes, - values: ['value5'], - }, - { - key: 'key2', - value: 'value8', - operator: '=~', - origin: FilterOrigin.Scopes, - values: ['value8'], - }, - ]); - }); -}); - -function generateScopes(filtersSpec: ScopeSpecFilter[][]) { - const scopes: Scope[] = []; - - for (let i = 0; i < filtersSpec.length; i++) { - scopes.push({ - metadata: { name: `name-${i}` }, - spec: { - title: `scope-${i}`, - type: '', - description: 'desc', - category: '', - filters: filtersSpec[i], - }, - }); - } - - return scopes; -} diff --git a/public/app/features/dashboard-scene/scene/convertScopesToAdHocFilters.ts b/public/app/features/dashboard-scene/scene/convertScopesToAdHocFilters.ts deleted file mode 100644 index 1066a8f7b15..00000000000 --- a/public/app/features/dashboard-scene/scene/convertScopesToAdHocFilters.ts +++ /dev/null @@ -1,92 +0,0 @@ -import { - Scope, - ScopeSpecFilter, - isEqualityOrMultiOperator, - reverseScopeFilterOperatorMap, - scopeFilterOperatorMap, -} from '@grafana/data'; -import { AdHocFilterWithLabels, FilterOrigin } from '@grafana/scenes'; - -export function convertScopesToAdHocFilters(scopes: Scope[]): AdHocFilterWithLabels[] { - const formattedFilters: Map = new Map(); - // duplicated filters that could not be processed in any way are just appended to the list - const duplicatedFilters: AdHocFilterWithLabels[] = []; - const allFilters = scopes.flatMap((scope) => scope.spec.filters); - - for (const filter of allFilters) { - processFilter(formattedFilters, duplicatedFilters, filter); - } - - return [...formattedFilters.values(), ...duplicatedFilters]; -} - -function processFilter( - formattedFilters: Map, - duplicatedFilters: AdHocFilterWithLabels[], - filter: ScopeSpecFilter -) { - const existingFilter = formattedFilters.get(filter.key); - - if (existingFilter && canValueBeMerged(existingFilter.operator, filter.operator)) { - mergeFilterValues(existingFilter, filter); - } else if (!existingFilter) { - // Add filter to map either only if it is new. - // Otherwise it is an existing filter that cannot be converted to multi-value - // and thus will be moved to the duplicatedFilters list - formattedFilters.set(filter.key, { - key: filter.key, - operator: reverseScopeFilterOperatorMap[filter.operator], - value: filter.value, - values: filter.values ?? [filter.value], - origin: FilterOrigin.Scopes, - }); - } else { - duplicatedFilters.push({ - key: filter.key, - operator: reverseScopeFilterOperatorMap[filter.operator], - value: filter.value, - values: filter.values ?? [filter.value], - origin: FilterOrigin.Scopes, - }); - } -} - -function mergeFilterValues(adHocFilter: AdHocFilterWithLabels, filter: ScopeSpecFilter) { - const values = filter.values ?? [filter.value]; - - for (const value of values) { - if (!adHocFilter.values?.includes(value)) { - adHocFilter.values?.push(value); - } - } - - // If there's only one value, there's no need to update the - // operator to its multi-value equivalent - if (adHocFilter.values?.length === 1) { - return; - } - - // Otherwise update it to the equivalent multi-value operator - if (filter.operator === 'equals' && adHocFilter.operator === reverseScopeFilterOperatorMap['equals']) { - adHocFilter.operator = reverseScopeFilterOperatorMap['one-of']; - } else if (filter.operator === 'not-equals' && adHocFilter.operator === reverseScopeFilterOperatorMap['not-equals']) { - adHocFilter.operator = reverseScopeFilterOperatorMap['not-one-of']; - } -} - -function canValueBeMerged(adHocFilterOperator: string, filterOperator: string) { - const scopeConvertedOperator = scopeFilterOperatorMap[adHocFilterOperator]; - - if (!isEqualityOrMultiOperator(scopeConvertedOperator) || !isEqualityOrMultiOperator(filterOperator)) { - return false; - } - - if ( - (scopeConvertedOperator.includes('not') && !filterOperator.includes('not')) || - (!scopeConvertedOperator.includes('not') && filterOperator.includes('not')) - ) { - return false; - } - - return true; -} diff --git a/public/app/features/dashboard-scene/scene/layout-default/DashboardGridItem.tsx b/public/app/features/dashboard-scene/scene/layout-default/DashboardGridItem.tsx index cb609b8e485..1f944a71aa5 100644 --- a/public/app/features/dashboard-scene/scene/layout-default/DashboardGridItem.tsx +++ b/public/app/features/dashboard-scene/scene/layout-default/DashboardGridItem.tsx @@ -86,7 +86,7 @@ export class DashboardGridItem return this.state.variableName ? 'panel-repeater-grid-item' : ''; } - public getOptions(): OptionsPaneCategoryDescriptor { + public getOptions(): OptionsPaneCategoryDescriptor[] { return getDashboardGridItemOptions(this); } diff --git a/public/app/features/dashboard-scene/scene/layout-default/DashboardGridItemEditor.tsx b/public/app/features/dashboard-scene/scene/layout-default/DashboardGridItemEditor.tsx index 4a80df34331..2bc1ce3f37f 100644 --- a/public/app/features/dashboard-scene/scene/layout-default/DashboardGridItemEditor.tsx +++ b/public/app/features/dashboard-scene/scene/layout-default/DashboardGridItemEditor.tsx @@ -8,47 +8,44 @@ import { RepeatRowSelect2 } from 'app/features/dashboard/components/RepeatRowSel import { DashboardGridItem } from './DashboardGridItem'; -export function getDashboardGridItemOptions(gridItem: DashboardGridItem): OptionsPaneCategoryDescriptor { - const category = new OptionsPaneCategoryDescriptor({ +export function getDashboardGridItemOptions(gridItem: DashboardGridItem): OptionsPaneCategoryDescriptor[] { + const repeatCategory = new OptionsPaneCategoryDescriptor({ title: t('dashboard.default-layout.item-options.repeat.title', 'Repeat options'), id: 'Repeat options', isOpenDefault: false, - }); + }) + .addItem( + new OptionsPaneItemDescriptor({ + title: t('dashboard.default-layout.item-options.repeat.variable.title', 'Repeat by variable'), + description: t( + 'dashboard.default-layout.item-options.repeat.variable.description', + 'Repeat this panel for each value in the selected variable. This is not visible while in edit mode. You need to go back to dashboard and then update the variable or reload the dashboard.' + ), + render: () => , + }) + ) + .addItem( + new OptionsPaneItemDescriptor({ + title: t('dashboard.default-layout.item-options.repeat.direction.title', 'Repeat direction'), + useShowIf: () => { + const { variableName } = gridItem.useState(); + return Boolean(variableName); + }, + render: () => , + }) + ) + .addItem( + new OptionsPaneItemDescriptor({ + title: t('dashboard.default-layout.item-options.repeat.max', 'Max per row'), + useShowIf: () => { + const { variableName, repeatDirection } = gridItem.useState(); + return Boolean(variableName) && repeatDirection === 'h'; + }, + render: () => , + }) + ); - category.addItem( - new OptionsPaneItemDescriptor({ - title: t('dashboard.default-layout.item-options.repeat.variable.title', 'Repeat by variable'), - description: t( - 'dashboard.default-layout.item-options.repeat.variable.description', - 'Repeat this panel for each value in the selected variable. This is not visible while in edit mode. You need to go back to dashboard and then update the variable or reload the dashboard.' - ), - render: () => , - }) - ); - - category.addItem( - new OptionsPaneItemDescriptor({ - title: t('dashboard.default-layout.item-options.repeat.direction.title', 'Repeat direction'), - useShowIf: () => { - const { variableName } = gridItem.useState(); - return Boolean(variableName); - }, - render: () => , - }) - ); - - category.addItem( - new OptionsPaneItemDescriptor({ - title: t('dashboard.default-layout.item-options.repeat.max', 'Max per row'), - useShowIf: () => { - const { variableName, repeatDirection } = gridItem.useState(); - return Boolean(variableName) && repeatDirection === 'h'; - }, - render: () => , - }) - ); - - return category; + return [repeatCategory]; } interface OptionComponentProps { diff --git a/public/app/features/dashboard-scene/scene/layout-default/DefaultGridLayoutManager.tsx b/public/app/features/dashboard-scene/scene/layout-default/DefaultGridLayoutManager.tsx index 1f3216dea99..f8cb979f064 100644 --- a/public/app/features/dashboard-scene/scene/layout-default/DefaultGridLayoutManager.tsx +++ b/public/app/features/dashboard-scene/scene/layout-default/DefaultGridLayoutManager.tsx @@ -186,6 +186,29 @@ export class DefaultGridLayoutManager this.publishEvent(new NewObjectAddedToCanvasEvent(newPanel), true); } + public duplicate(): DashboardLayoutManager { + const clone = this.clone({ + key: undefined, + grid: this.state.grid.clone({ + key: undefined, + children: this.state.grid.state.children.map((child) => { + if (child instanceof DashboardGridItem) { + return child.clone({ + key: undefined, + body: child.state.body.clone({ + key: getVizPanelKeyForPanelId(dashboardSceneGraph.getNextPanelId(child.state.body)), + }), + }); + } + + return child.clone({ key: undefined }); + }), + }), + }); + + return clone; + } + public getVizPanels(): VizPanel[] { const panels: VizPanel[] = []; diff --git a/public/app/features/dashboard-scene/scene/layout-responsive-grid/ResponsiveGridItem.tsx b/public/app/features/dashboard-scene/scene/layout-responsive-grid/ResponsiveGridItem.tsx index 61f92a6ea69..158e4395e20 100644 --- a/public/app/features/dashboard-scene/scene/layout-responsive-grid/ResponsiveGridItem.tsx +++ b/public/app/features/dashboard-scene/scene/layout-responsive-grid/ResponsiveGridItem.tsx @@ -65,7 +65,7 @@ export class ResponsiveGridItem extends SceneObjectBase }; } - public getOptions(): OptionsPaneCategoryDescriptor { + public getOptions(): OptionsPaneCategoryDescriptor[] { return getOptions(this); } diff --git a/public/app/features/dashboard-scene/scene/layout-responsive-grid/ResponsiveGridItemEditor.tsx b/public/app/features/dashboard-scene/scene/layout-responsive-grid/ResponsiveGridItemEditor.tsx index 4e7b4f80dc9..733a92b9cb9 100644 --- a/public/app/features/dashboard-scene/scene/layout-responsive-grid/ResponsiveGridItemEditor.tsx +++ b/public/app/features/dashboard-scene/scene/layout-responsive-grid/ResponsiveGridItemEditor.tsx @@ -1,9 +1,42 @@ +import { t } from 'app/core/internationalization'; import { OptionsPaneCategoryDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneCategoryDescriptor'; +import { OptionsPaneItemDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneItemDescriptor'; +import { RepeatRowSelect2 } from 'app/features/dashboard/components/RepeatRowSelect/RepeatRowSelect'; import { useConditionalRenderingEditor } from '../../conditional-rendering/ConditionalRenderingEditor'; import { ResponsiveGridItem } from './ResponsiveGridItem'; -export function getOptions(model: ResponsiveGridItem): OptionsPaneCategoryDescriptor { - return useConditionalRenderingEditor(model.state.conditionalRendering)!; +export function getOptions(model: ResponsiveGridItem): OptionsPaneCategoryDescriptor[] { + const repeatCategory = new OptionsPaneCategoryDescriptor({ + title: t('dashboard.responsive-layout.item-options.repeat.title', 'Repeat options'), + id: 'repeat-options', + isOpenDefault: false, + }).addItem( + new OptionsPaneItemDescriptor({ + title: t('dashboard.responsive-layout.item-options.repeat.variable.title', 'Repeat by variable'), + description: t( + 'dashboard.responsive-layout.item-options.repeat.variable.description', + 'Repeat this panel for each value in the selected variable. This is not visible while in edit mode. You need to go back to dashboard and then update the variable or reload the dashboard.' + ), + render: () => , + }) + ); + + const conditionalRenderingCategory = useConditionalRenderingEditor(model.state.conditionalRendering)!; + + return [repeatCategory, conditionalRenderingCategory]; +} + +function RepeatByOption({ item }: { item: ResponsiveGridItem }) { + const { variableName } = item.useState(); + + return ( + item.setRepeatByVariable(value)} + /> + ); } diff --git a/public/app/features/dashboard-scene/scene/layout-responsive-grid/ResponsiveGridLayoutManager.tsx b/public/app/features/dashboard-scene/scene/layout-responsive-grid/ResponsiveGridLayoutManager.tsx index 8130cecec00..fe17f5ca802 100644 --- a/public/app/features/dashboard-scene/scene/layout-responsive-grid/ResponsiveGridLayoutManager.tsx +++ b/public/app/features/dashboard-scene/scene/layout-responsive-grid/ResponsiveGridLayoutManager.tsx @@ -72,6 +72,27 @@ export class ResponsiveGridLayoutManager this.publishEvent(new ObjectRemovedFromCanvasEvent(panel), true); } + public duplicate(): DashboardLayoutManager { + return this.clone({ + key: undefined, + layout: this.state.layout.clone({ + key: undefined, + children: this.state.layout.state.children.map((child) => { + if (child instanceof ResponsiveGridItem) { + return child.clone({ + key: undefined, + body: child.state.body.clone({ + key: getVizPanelKeyForPanelId(dashboardSceneGraph.getNextPanelId(child.state.body)), + }), + }); + } + + return child.clone({ key: undefined }); + }), + }), + }); + } + public duplicatePanel(panel: VizPanel) { const gridItem = panel.parent; if (!(gridItem instanceof ResponsiveGridItem)) { diff --git a/public/app/features/dashboard-scene/scene/layout-rows/RowItem.tsx b/public/app/features/dashboard-scene/scene/layout-rows/RowItem.tsx index d85eb832757..8d3b8f91a75 100644 --- a/public/app/features/dashboard-scene/scene/layout-rows/RowItem.tsx +++ b/public/app/features/dashboard-scene/scene/layout-rows/RowItem.tsx @@ -88,6 +88,14 @@ export class RowItem return new RowItems(items.filter((item) => item instanceof RowItem)); } + public onDuplicate() { + this._getParentLayout().duplicateRow(this); + } + + public duplicate(): RowItem { + return this.clone({ key: undefined, layout: this.getLayout().duplicate() }); + } + public onAddPanel(panel = getDefaultVizPanel()) { this.getLayout().addPanel(panel); } diff --git a/public/app/features/dashboard-scene/scene/layout-rows/RowItemEditor.tsx b/public/app/features/dashboard-scene/scene/layout-rows/RowItemEditor.tsx index 96630c78419..4bfc3b77544 100644 --- a/public/app/features/dashboard-scene/scene/layout-rows/RowItemEditor.tsx +++ b/public/app/features/dashboard-scene/scene/layout-rows/RowItemEditor.tsx @@ -19,48 +19,61 @@ import { RowItem } from './RowItem'; export function getEditOptions(model: RowItem): OptionsPaneCategoryDescriptor[] { const { layout } = model.useState(); - const rowOptions = useMemo(() => { - const editPaneHeaderOptions = new OptionsPaneCategoryDescriptor({ title: '', id: 'row-options' }) - .addItem( - new OptionsPaneItemDescriptor({ - title: t('dashboard.rows-layout.option.title', 'Title'), - render: () => , - }) - ) - .addItem( - new OptionsPaneItemDescriptor({ - title: t('dashboard.rows-layout.option.height', 'Height'), - render: () => , - }) - ); - editPaneHeaderOptions - .addItem( + const rowCategory = useMemo( + () => + new OptionsPaneCategoryDescriptor({ title: '', id: 'row-options' }) + .addItem( + new OptionsPaneItemDescriptor({ + title: t('dashboard.rows-layout.row-options.row.title', 'Title'), + render: () => , + }) + ) + .addItem( + new OptionsPaneItemDescriptor({ + title: t('dashboard.rows-layout.row-options.row.height', 'Height'), + render: () => , + }) + ) + .addItem( + new OptionsPaneItemDescriptor({ + title: t('dashboard.rows-layout.row-options.row.hide-header', 'Hide row header'), + render: () => , + }) + ), + [model] + ); + + const repeatCategory = useMemo( + () => + new OptionsPaneCategoryDescriptor({ + title: t('dashboard.rows-layout.row-options.repeat.title', 'Repeat options'), + id: 'repeat-options', + isOpenDefault: false, + }).addItem( new OptionsPaneItemDescriptor({ - title: t('dashboard.rows-layout.option.repeat', 'Repeat for'), + title: t('dashboard.rows-layout.row-options.repeat.variable.title', 'Repeat by variable'), + description: t( + 'dashboard.rows-layout.row-options.repeat.variable.description', + 'Repeat this row for each value in the selected variable.' + ), render: () => , }) - ) - .addItem( - new OptionsPaneItemDescriptor({ - title: t('dashboard.rows-layout.option.hide-header', 'Hide row header'), - render: () => , - }) - ); - - return editPaneHeaderOptions; - }, [model]); + ), + [model] + ); const layoutCategory = useLayoutCategory(layout); - const conditionalRenderingOptions = useMemo(() => { - return useConditionalRenderingEditor(model.state.conditionalRendering); - }, [model]); + const editOptions = [rowCategory, layoutCategory, repeatCategory]; - const editOptions = [rowOptions, layoutCategory]; + const conditionalRenderingCategory = useMemo( + () => useConditionalRenderingEditor(model.state.conditionalRendering), + [model] + ); - if (conditionalRenderingOptions) { - editOptions.push(conditionalRenderingOptions); + if (conditionalRenderingCategory) { + editOptions.push(conditionalRenderingCategory); } return editOptions; diff --git a/public/app/features/dashboard-scene/scene/layout-rows/RowsLayoutManager.tsx b/public/app/features/dashboard-scene/scene/layout-rows/RowsLayoutManager.tsx index 28b1960c285..7ad15344c45 100644 --- a/public/app/features/dashboard-scene/scene/layout-rows/RowsLayoutManager.tsx +++ b/public/app/features/dashboard-scene/scene/layout-rows/RowsLayoutManager.tsx @@ -75,6 +75,17 @@ export class RowsLayoutManager extends SceneObjectBase i throw new Error('Method not implemented.'); } + public duplicate(): DashboardLayoutManager { + const newRows = this.state.rows.map((row) => row.duplicate()); + return this.clone({ rows: newRows, key: undefined }); + } + + public duplicateRow(row: RowItem) { + const newRow = row.duplicate(); + this.setState({ rows: [...this.state.rows, newRow] }); + this.publishEvent(new NewObjectAddedToCanvasEvent(newRow), true); + } + public addNewRow(): RowItem { const row = new RowItem(); this.setState({ rows: [...this.state.rows, row] }); diff --git a/public/app/features/dashboard-scene/scene/layout-tabs/TabItem.tsx b/public/app/features/dashboard-scene/scene/layout-tabs/TabItem.tsx index e32383f9eaa..df09763b00b 100644 --- a/public/app/features/dashboard-scene/scene/layout-tabs/TabItem.tsx +++ b/public/app/features/dashboard-scene/scene/layout-tabs/TabItem.tsx @@ -70,6 +70,14 @@ export class TabItem return new TabItems(items.filter((item) => item instanceof TabItem)); } + public onDuplicate(): void { + this._getParentLayout().duplicateTab(this); + } + + public duplicate(): TabItem { + return this.clone({ key: undefined, layout: this.getLayout().duplicate() }); + } + public onAddPanel(panel = getDefaultVizPanel()) { this.getLayout().addPanel(panel); } diff --git a/public/app/features/dashboard-scene/scene/layout-tabs/TabItemEditor.tsx b/public/app/features/dashboard-scene/scene/layout-tabs/TabItemEditor.tsx index 432fe6ef497..13ae006ab43 100644 --- a/public/app/features/dashboard-scene/scene/layout-tabs/TabItemEditor.tsx +++ b/public/app/features/dashboard-scene/scene/layout-tabs/TabItemEditor.tsx @@ -11,19 +11,22 @@ import { useEditPaneInputAutoFocus } from '../layouts-shared/utils'; import { TabItem } from './TabItem'; export function getEditOptions(model: TabItem): OptionsPaneCategoryDescriptor[] { - const tabOptions = useMemo(() => { - return new OptionsPaneCategoryDescriptor({ title: '', id: 'tab-item-options' }).addItem( - new OptionsPaneItemDescriptor({ - title: t('dashboard.tabs-layout.tab-options.title-option', 'Title'), - render: () => , - }) - ); - }, [model]); - const { layout } = model.useState(); - const layoutOptions = useLayoutCategory(layout); - return [tabOptions, layoutOptions]; + const tabCategory = useMemo( + () => + new OptionsPaneCategoryDescriptor({ title: '', id: 'tab-item-options' }).addItem( + new OptionsPaneItemDescriptor({ + title: t('dashboard.tabs-layout.tab-options.title-option', 'Title'), + render: () => , + }) + ), + [model] + ); + + const layoutCategory = useLayoutCategory(layout); + + return [tabCategory, layoutCategory]; } function TabTitleInput({ tab }: { tab: TabItem }) { diff --git a/public/app/features/dashboard-scene/scene/layout-tabs/TabsLayoutManager.tsx b/public/app/features/dashboard-scene/scene/layout-tabs/TabsLayoutManager.tsx index a0296b4c2b5..6478ff19c54 100644 --- a/public/app/features/dashboard-scene/scene/layout-tabs/TabsLayoutManager.tsx +++ b/public/app/features/dashboard-scene/scene/layout-tabs/TabsLayoutManager.tsx @@ -54,6 +54,16 @@ export class TabsLayoutManager extends SceneObjectBase i }); } + public duplicate(): DashboardLayoutManager { + // Maybe not needed, depending on if we want nested tabs or tabs within rows + throw new Error('Method not implemented.'); + } + + public duplicateTab(tab: TabItem) { + const newTab = tab.duplicate(); + this.setState({ tabs: [...this.state.tabs, newTab] }); + } + public getUrlState() { return { tab: this.state.currentTabIndex.toString() }; } diff --git a/public/app/features/dashboard-scene/scene/types/DashboardLayoutItem.ts b/public/app/features/dashboard-scene/scene/types/DashboardLayoutItem.ts index 48301007b5d..15253215329 100644 --- a/public/app/features/dashboard-scene/scene/types/DashboardLayoutItem.ts +++ b/public/app/features/dashboard-scene/scene/types/DashboardLayoutItem.ts @@ -30,7 +30,7 @@ export interface DashboardLayoutItem extends SceneObject { /** * Return layout item options (like repeat, repeat direction, etc. for the default DashboardGridItem) */ - getOptions?(): OptionsPaneCategoryDescriptor; + getOptions?(): OptionsPaneCategoryDescriptor[]; /** * When going into panel edit diff --git a/public/app/features/dashboard-scene/scene/types/DashboardLayoutManager.ts b/public/app/features/dashboard-scene/scene/types/DashboardLayoutManager.ts index 190a6806467..1065b592f67 100644 --- a/public/app/features/dashboard-scene/scene/types/DashboardLayoutManager.ts +++ b/public/app/features/dashboard-scene/scene/types/DashboardLayoutManager.ts @@ -66,6 +66,11 @@ export interface DashboardLayoutManager extends SceneObject { * @param isSource */ cloneLayout(ancestorKey: string, isSource: boolean): DashboardLayoutManager; + + /** + * Duplicate, like clone but with new keys + */ + duplicate(): DashboardLayoutManager; } export interface LayoutManagerSerializer { diff --git a/public/app/features/dimensions/editors/ValueMappingsEditor/ValueMappingEditRow.tsx b/public/app/features/dimensions/editors/ValueMappingsEditor/ValueMappingEditRow.tsx index c26e5a26781..bb8215ffbe6 100644 --- a/public/app/features/dimensions/editors/ValueMappingsEditor/ValueMappingEditRow.tsx +++ b/public/app/features/dimensions/editors/ValueMappingsEditor/ValueMappingEditRow.tsx @@ -11,8 +11,8 @@ import { ResourcePicker } from '../ResourcePicker'; export interface ValueMappingEditRowModel { type: MappingType; - from?: number; - to?: number; + from?: number | null; + to?: number | null; pattern?: string; key?: string; isNew?: boolean; @@ -149,20 +149,8 @@ export function ValueMappingEditRow({ mapping, index, onChange, onRemove, onDupl )} {mapping.type === MappingType.RangeToText && (
- - + +
)} {mapping.type === MappingType.RegexToText && ( diff --git a/public/app/features/dimensions/editors/ValueMappingsEditor/ValueMappingsEditor.tsx b/public/app/features/dimensions/editors/ValueMappingsEditor/ValueMappingsEditor.tsx index 9bdb3ec96c8..3bbeff8882a 100644 --- a/public/app/features/dimensions/editors/ValueMappingsEditor/ValueMappingsEditor.tsx +++ b/public/app/features/dimensions/editors/ValueMappingsEditor/ValueMappingsEditor.tsx @@ -51,7 +51,7 @@ export const ValueMappingsEditor = memo((props: Props) => { {row.type === MappingType.ValueToText && row.key} {row.type === MappingType.RangeToText && ( - [{row.from} - {row.to}] + [{row.from ?? '-∞'} - {row.to ?? '∞'}] )} {row.type === MappingType.RegexToText && row.pattern} diff --git a/public/app/features/dimensions/editors/ValueMappingsEditor/ValueMappingsEditorModal.test.tsx b/public/app/features/dimensions/editors/ValueMappingsEditor/ValueMappingsEditorModal.test.tsx index 1090c942122..065f9d304fe 100644 --- a/public/app/features/dimensions/editors/ValueMappingsEditor/ValueMappingsEditorModal.test.tsx +++ b/public/app/features/dimensions/editors/ValueMappingsEditor/ValueMappingsEditorModal.test.tsx @@ -134,11 +134,11 @@ describe('ValueMappingsEditorModal', () => { ); await selectOptionInTest(selectComponent, 'Range'); - await userEvent.clear(screen.getByPlaceholderText('Range start')); - await userEvent.type(screen.getByPlaceholderText('Range start'), '10'); + await userEvent.clear(screen.getByPlaceholderText('From')); + await userEvent.type(screen.getByPlaceholderText('From'), '10'); - await userEvent.clear(screen.getByPlaceholderText('Range end')); - await userEvent.type(screen.getByPlaceholderText('Range end'), '20'); + await userEvent.clear(screen.getByPlaceholderText('To')); + await userEvent.type(screen.getByPlaceholderText('To'), '20'); await userEvent.clear(screen.getByPlaceholderText('Optional display text')); await userEvent.type(screen.getByPlaceholderText('Optional display text'), 'display'); diff --git a/public/app/features/dimensions/editors/ValueMappingsEditor/ValueMappingsEditorModal.tsx b/public/app/features/dimensions/editors/ValueMappingsEditor/ValueMappingsEditorModal.tsx index 9b970427143..199563a65fc 100644 --- a/public/app/features/dimensions/editors/ValueMappingsEditor/ValueMappingsEditorModal.tsx +++ b/public/app/features/dimensions/editors/ValueMappingsEditor/ValueMappingsEditorModal.tsx @@ -218,12 +218,12 @@ export function editModelToSaveModel(rows: ValueMappingEditRowModel[]) { } break; case MappingType.RangeToText: - if (item.from != null && item.to != null) { + if (item.from != null || item.to != null) { mappings.push({ type: item.type, options: { - from: item.from, - to: item.to, + from: item.from ?? null, + to: item.to ?? null, result, }, }); @@ -279,8 +279,8 @@ export function buildEditRowModels(value: ValueMapping[]) { createRow({ type: mapping.type, result: mapping.options.result, - from: mapping.options.from ?? 0, - to: mapping.options.to ?? 0, + from: mapping.options.from, + to: mapping.options.to, }) ); break; diff --git a/public/app/features/explore/Logs/Logs.tsx b/public/app/features/explore/Logs/Logs.tsx index 1ae6bcd7515..5bf10d2dac2 100644 --- a/public/app/features/explore/Logs/Logs.tsx +++ b/public/app/features/explore/Logs/Logs.tsx @@ -82,7 +82,7 @@ import { LogsMetaRow } from './LogsMetaRow'; import LogsNavigation from './LogsNavigation'; import { LogsTableWrap, getLogsTableHeight } from './LogsTableWrap'; import { LogsVolumePanelList } from './LogsVolumePanelList'; -import { canKeepDisplayedFields, SETTINGS_KEYS, visualisationTypeKey } from './utils/logs'; +import { SETTINGS_KEYS, visualisationTypeKey } from './utils/logs'; interface Props extends Themeable2 { width: number; @@ -225,7 +225,6 @@ const UnthemedLogs: React.FunctionComponent = (props: Props) => { const cancelFlippingTimer = useRef(undefined); const toggleLegendRef = useRef<(name: string, mode: SeriesVisibilityChangeMode) => void>(() => {}); const topLogsRef = useRef(null); - const prevLogsQueries = usePrevious(logsQueries); const tableHeight = getLogsTableHeight(); const styles = getStyles(theme, wrapLogMessage, tableHeight); @@ -409,20 +408,6 @@ const UnthemedLogs: React.FunctionComponent = (props: Props) => { ] ); - useEffect(() => { - if (!prevLogsQueries) { - // Initial load, ignore - return; - } - if (!canKeepDisplayedFields(logsQueries, prevLogsQueries)) { - setDisplayedFields([]); - updatePanelState({ - ...panelState?.logs, - displayedFields: [], - }); - } - }, [logsQueries, panelState?.logs, prevLogsQueries, updatePanelState]); - // actions const onLogRowHover = useCallback( (row?: LogRowModel) => { diff --git a/public/app/features/explore/Logs/utils/logs.test.ts b/public/app/features/explore/Logs/utils/logs.test.ts deleted file mode 100644 index 42883ff1ad4..00000000000 --- a/public/app/features/explore/Logs/utils/logs.test.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { DataQuery } from '@grafana/data'; - -import { canKeepDisplayedFields } from './logs'; - -describe('canKeepDisplayedFields', () => { - test('Returns false when passing no queries', () => { - expect(canKeepDisplayedFields(undefined, [])).toBe(false); - }); - - test('Returns false when some prev queries are undefined', () => { - const logQueries: DataQuery[] = [{ refId: 'A' }, { refId: 'B' }]; - const prevLogQueries = [{ refId: 'C' }]; - expect(canKeepDisplayedFields(logQueries, prevLogQueries)).toBe(false); - }); - - test('Returns false when some new queries are undefined', () => { - const logQueries: DataQuery[] = [{ refId: 'A' }]; - const prevLogQueries = [{ refId: 'C' }, { refId: 'B' }]; - expect(canKeepDisplayedFields(logQueries, prevLogQueries)).toBe(false); - }); - - test('Returns true when the queries exactly match', () => { - const logQueries: DataQuery[] = [{ refId: 'C' }, { refId: 'B' }]; - const prevLogQueries = [{ refId: 'C' }, { refId: 'B' }]; - expect(canKeepDisplayedFields(logQueries, prevLogQueries)).toBe(true); - }); -}); diff --git a/public/app/features/explore/Logs/utils/logs.ts b/public/app/features/explore/Logs/utils/logs.ts index edcaf0fc2c4..2aec91da980 100644 --- a/public/app/features/explore/Logs/utils/logs.ts +++ b/public/app/features/explore/Logs/utils/logs.ts @@ -1,6 +1,3 @@ -import { shallowCompare } from '@grafana/data'; -import { DataQuery } from '@grafana/schema'; - export const SETTINGS_KEYS = { showLabels: 'grafana.explore.logs.showLabels', showTime: 'grafana.explore.logs.showTime', @@ -11,15 +8,3 @@ export const SETTINGS_KEYS = { }; export const visualisationTypeKey = 'grafana.explore.logs.visualisationType'; - -export const canKeepDisplayedFields = (logsQueries: DataQuery[] | undefined, prevLogsQueries: DataQuery[]): boolean => { - if (!logsQueries) { - return false; - } - for (let i = 0; i < logsQueries.length; i++) { - if (!logsQueries[i] || !prevLogsQueries[i] || !shallowCompare(logsQueries[i], prevLogsQueries[i])) { - return false; - } - } - return true; -}; diff --git a/public/app/features/plugins/admin/components/InstallControls/InstallControlsWarning.tsx b/public/app/features/plugins/admin/components/InstallControls/InstallControlsWarning.tsx index 0cbf52ca151..8690a4f4a5f 100644 --- a/public/app/features/plugins/admin/components/InstallControls/InstallControlsWarning.tsx +++ b/public/app/features/plugins/admin/components/InstallControls/InstallControlsWarning.tsx @@ -33,16 +33,6 @@ export const InstallControlsWarning = ({ plugin, pluginStatus, latestCompatibleV ); } - if (plugin.type === PluginType.secretsmanager) { - return ( - - ); - } - if (plugin.isEnterprise && !featureEnabled('enterprise.plugins')) { return ( diff --git a/public/app/features/plugins/admin/components/PluginDetailsBody.test.tsx b/public/app/features/plugins/admin/components/PluginDetailsBody.test.tsx index cceb1058832..a24592c7d19 100644 --- a/public/app/features/plugins/admin/components/PluginDetailsBody.test.tsx +++ b/public/app/features/plugins/admin/components/PluginDetailsBody.test.tsx @@ -24,12 +24,6 @@ describe('PluginDetailsBody', () => { type: PluginType.renderer, }, }, - { - name: 'secrets manager type plugin', - plugin: { - type: PluginType.secretsmanager, - }, - }, { name: 'enterprise plugin type without enterprise license', plugin: { diff --git a/public/app/features/plugins/admin/helpers.ts b/public/app/features/plugins/admin/helpers.ts index 6ef0a7b2e0c..24d26a54c0e 100644 --- a/public/app/features/plugins/admin/helpers.ts +++ b/public/app/features/plugins/admin/helpers.ts @@ -131,7 +131,7 @@ export function mapRemoteToCatalog(plugin: RemotePlugin, error?: PluginError): C url, } = plugin; - const isDisabled = !!error || isDisabledSecretsPlugin(typeCode); + const isDisabled = !!error; return { description, downloads, @@ -185,7 +185,7 @@ export function mapLocalToCatalog(plugin: LocalPlugin, error?: PluginError): Cat angularDetected, } = plugin; - const isDisabled = !!error || isDisabledSecretsPlugin(type); + const isDisabled = !!error; return { description, downloads: 0, @@ -225,7 +225,7 @@ export function mapToCatalogPlugin(local?: LocalPlugin, remote?: RemotePlugin, e const installedVersion = local?.info.version; const id = remote?.slug || local?.id || ''; const type = local?.type || remote?.typeCode; - const isDisabled = !!error || isDisabledSecretsPlugin(type); + const isDisabled = !!error; const keywords = remote?.keywords || local?.info.keywords || []; let logos = { @@ -377,7 +377,6 @@ export const hasInstallControlWarning = ( const isCompatible = Boolean(latestCompatibleVersion); return ( plugin.type === PluginType.renderer || - plugin.type === PluginType.secretsmanager || (plugin.isEnterprise && !featureEnabled('enterprise.plugins')) || plugin.isDev || (!hasPermission && !isExternallyManaged) || @@ -426,10 +425,6 @@ export function isPreinstalledPlugin(id: string): { found: boolean; withVersion: return { found: !!plugin?.id, withVersion: !!plugin?.version }; } -function isDisabledSecretsPlugin(type?: PluginType): boolean { - return type === PluginType.secretsmanager && !config.secretsManagerPluginEnabled; -} - export function isLocalCorePlugin(local?: LocalPlugin): boolean { return Boolean(local?.signature === 'internal'); } @@ -492,7 +487,6 @@ export function isPluginUpdatable(plugin: CatalogPlugin) { export function shouldDisablePluginInstall(plugin: CatalogPlugin) { if ( !isPluginModifiable(plugin) || - plugin.type === PluginType.secretsmanager || (plugin.isEnterprise && !featureEnabled('enterprise.plugins')) || !plugin.isPublished || plugin.isDisabled || diff --git a/public/app/features/plugins/admin/types.ts b/public/app/features/plugins/admin/types.ts index 9497bae545a..191612f9df2 100644 --- a/public/app/features/plugins/admin/types.ts +++ b/public/app/features/plugins/admin/types.ts @@ -25,7 +25,6 @@ export enum PluginIconName { datasource = 'database', panel = 'credit-card', renderer = 'capture', - secretsmanager = 'key-skeleton-alt', } export interface CatalogPlugin extends WithAccessControlMetadata { diff --git a/public/app/features/query/components/QueryGroup.tsx b/public/app/features/query/components/QueryGroup.tsx index d7604d11670..278b05ffb8f 100644 --- a/public/app/features/query/components/QueryGroup.tsx +++ b/public/app/features/query/components/QueryGroup.tsx @@ -276,7 +276,7 @@ export class QueryGroup extends PureComponent { } isExpressionsSupported(dsSettings: DataSourceInstanceSettings): boolean { - return (dsSettings.meta.alerting || dsSettings.meta.mixed) === true; + return (dsSettings.meta.backend || dsSettings.meta.alerting || dsSettings.meta.mixed) === true; } renderExtraActions() { diff --git a/public/app/plugins/datasource/azuremonitor/plugin.json b/public/app/plugins/datasource/azuremonitor/plugin.json index dd185db82cb..fb67906f3f6 100644 --- a/public/app/plugins/datasource/azuremonitor/plugin.json +++ b/public/app/plugins/datasource/azuremonitor/plugin.json @@ -111,7 +111,7 @@ }, "links": [ { "name": "Learn more", "url": "https://grafana.com/docs/grafana/latest/datasources/azuremonitor/" }, - { "name": "Apache License", "url": "https://github.com/grafana/azure-monitor-datasource/blob/master/LICENSE" }, + { "name": "License", "url": "https://github.com/grafana/grafana/blob/HEAD/LICENSE" }, { "name": "Raise issue", "url": "https://github.com/grafana/grafana/issues/new" }, { "name": "Documentation", "url": "https://grafana.com/docs/grafana/latest/datasources/azure-monitor/" } ], diff --git a/public/app/plugins/datasource/cloud-monitoring/plugin.json b/public/app/plugins/datasource/cloud-monitoring/plugin.json index c2e13b16bb3..02506599181 100644 --- a/public/app/plugins/datasource/cloud-monitoring/plugin.json +++ b/public/app/plugins/datasource/cloud-monitoring/plugin.json @@ -92,6 +92,9 @@ "name": "Grafana Labs", "url": "https://grafana.com" }, - "links": [{ "name": "Raise issue", "url": "https://github.com/grafana/grafana/issues/new" }] + "links": [ + { "name": "Raise issue", "url": "https://github.com/grafana/grafana/issues/new" }, + { "name": "Documentation", "url": "https://grafana.com/docs/grafana/latest/datasources/google-cloud-monitoring/" } + ] } } diff --git a/public/app/plugins/datasource/cloudwatch/plugin.json b/public/app/plugins/datasource/cloudwatch/plugin.json index 8866a6ec6d2..4338e09e0ce 100644 --- a/public/app/plugins/datasource/cloudwatch/plugin.json +++ b/public/app/plugins/datasource/cloudwatch/plugin.json @@ -31,6 +31,9 @@ "small": "img/amazon-web-services.png", "large": "img/amazon-web-services.png" }, - "links": [{ "name": "Raise issue", "url": "https://github.com/grafana/grafana/issues/new" }] + "links": [ + { "name": "Raise issue", "url": "https://github.com/grafana/grafana/issues/new" }, + { "name": "Documentation", "url": "https://grafana.com/docs/grafana/latest/datasources/aws-cloudwatch/" } + ] } } diff --git a/public/app/plugins/datasource/mixed/plugin.json b/public/app/plugins/datasource/mixed/plugin.json index cb13ff30a0d..8f7c1b994fc 100644 --- a/public/app/plugins/datasource/mixed/plugin.json +++ b/public/app/plugins/datasource/mixed/plugin.json @@ -14,7 +14,10 @@ "small": "img/icn-mixeddatasources.svg", "large": "img/icn-mixeddatasources.svg" }, - "links": [{ "name": "Raise issue", "url": "https://github.com/grafana/grafana/issues/new" }] + "links": [ + { "name": "Raise issue", "url": "https://github.com/grafana/grafana/issues/new" }, + { "name": "Documentation", "url": "https://grafana.com/docs/grafana/latest/datasources/#special-data-sources" } + ] }, "mixed": true, diff --git a/public/app/plugins/datasource/mssql/plugin.json b/public/app/plugins/datasource/mssql/plugin.json index 760fdc16a92..0d704a8354b 100644 --- a/public/app/plugins/datasource/mssql/plugin.json +++ b/public/app/plugins/datasource/mssql/plugin.json @@ -16,7 +16,13 @@ "large": "img/sql_server_logo.svg" }, "version": "%VERSION%", - "links": [{ "name": "Raise issue", "url": "https://github.com/grafana/grafana/issues/new" }] + "links": [ + { "name": "Raise issue", "url": "https://github.com/grafana/grafana/issues/new" }, + { + "name": "Documentation", + "url": "https://grafana.com/docs/grafana/latest/datasources/mssql/" + } + ] }, "dependencies": { "grafanaDependency": ">=10.4.0" diff --git a/public/app/plugins/datasource/tempo/plugin.json b/public/app/plugins/datasource/tempo/plugin.json index 7feeaec7206..e1ab2453392 100644 --- a/public/app/plugins/datasource/tempo/plugin.json +++ b/public/app/plugins/datasource/tempo/plugin.json @@ -30,7 +30,7 @@ }, { "name": "Raise issue", - "url": "https://github.com/grafana/tempo/issues/new" + "url": "https://github.com/grafana/grafana/issues/new" }, { "name": "Documentation", diff --git a/public/app/plugins/panel/timeseries/plugin.json b/public/app/plugins/panel/timeseries/plugin.json index 6b1e21c62b0..88733015e1e 100644 --- a/public/app/plugins/panel/timeseries/plugin.json +++ b/public/app/plugins/panel/timeseries/plugin.json @@ -13,6 +13,12 @@ "small": "img/icn-timeseries-panel.svg", "large": "img/icn-timeseries-panel.svg" }, - "links": [{ "name": "Raise issue", "url": "https://github.com/grafana/grafana/issues/new" }] + "links": [ + { "name": "Raise issue", "url": "https://github.com/grafana/grafana/issues/new" }, + { + "name": "Documentation", + "url": "https://grafana.com/docs/grafana/latest/panels-visualizations/visualizations/time-series/" + } + ] } } diff --git a/public/locales/cs-CZ/grafana.json b/public/locales/cs-CZ/grafana.json index 5a982333efe..01f18df8845 100644 --- a/public/locales/cs-CZ/grafana.json +++ b/public/locales/cs-CZ/grafana.json @@ -35,15 +35,57 @@ } }, "admin": { + "add-to-org-modal": { + "label-organization": "", + "label-role": "", + "title-add-to-an-organization": "" + }, + "admin-edit-org-page": { + "label-name": "", + "render-missing-permission-message": { + "title-access-denied": "" + } + }, + "admin-feature-toggles-table": { + "get-stage-cell": { + "beta": "", + "deprecated": "", + "ga": "" + }, + "title-apply-feature-toggle-changes": "" + }, + "admin-orgs-table": { + "aria-label-delete-org": "", + "title-delete": "" + }, "anon-users": { "not-found": "" }, + "base-user-sessions": { + "title-force-logout-from-all-devices": "" + }, "edit-org": { "access-denied": "", "heading": "", "update-button": "", "users-heading": "" }, + "feature-listing": { + "title-auditing": "", + "title-dashboard-presence-indicators": "", + "title-dashboard-usage-stats-drawer": "", + "title-data-source-permissions": "", + "title-enhanced-ldap-integration": "", + "title-enterprise-plugins": "", + "title-find-unused-dashboards": "", + "title-grafana-usage-insights": "", + "title-reporting": "", + "title-saml-authentication": "", + "title-settings-updates-at-runtime": "", + "title-sort-dashboards-by-popularity-in-search": "", + "title-team-sync": "", + "title-white-labeling": "" + }, "feature-toggles": { "sub-title": "" }, @@ -61,11 +103,23 @@ "test-mapping-heading": "", "test-mapping-run-button": "" }, + "ldap-error-box": { + "title-connection-error": "" + }, + "ldap-page": { + "label-username": "" + }, "ldap-permissions": { "active": "", "admin": "", "inactive": "" }, + "ldap-settings-page": { + "label-reset-to-default-values": "", + "title-invalid-configuration": "", + "title-more-actions": "", + "tooltip-more-actions": "" + }, "ldap-status": { "title": "" }, @@ -87,12 +141,21 @@ "ldap-user-info": { "no-team": "" }, + "org-role-picker": { + "placeholder-choose-role": "" + }, "org-uers": { "last-seen-never": "" }, "org-users": { "not-editable": "" }, + "org-users-table": { + "columns": { + "aria-label-role": "" + }, + "title-delete": "" + }, "orgs": { "delete-body": "", "id-header": "", @@ -107,12 +170,38 @@ "title": "", "users-button": "" }, + "service-info": { + "title-critical-sla-hours": "", + "title-enterprise-plugins": "", + "title-unlimited-expert-support": "" + }, "settings": { "info-description": "" }, "upgrade-info": { "title": "" }, + "user-create-page": { + "label-email": "", + "label-name": "", + "label-password": "", + "label-username": "" + }, + "user-ldap-sync-info": { + "label-ldap": "" + }, + "user-list-admin-page-un-connected": { + "placeholder-search-login-email": "" + }, + "user-list-anonymous-devices-page-un-connected": { + "placeholder-search-devices-by-ip-address": "" + }, + "user-list-page": { + "label-all-users": "", + "label-anonymous-devices": "", + "label-organization-users": "", + "label-users": "" + }, "user-orgs": { "add-button": "", "change-role-button": "", @@ -137,7 +226,14 @@ "disable-button": "", "edit-button": "", "enable-button": "", - "title": "" + "label-email": "", + "label-name": "", + "label-numerical-identifier": "", + "label-password": "", + "label-username": "", + "title": "", + "title-delete-user": "", + "title-disable-user": "" }, "user-sessions": { "browser-column": "", @@ -155,6 +251,9 @@ "create-button": "" }, "users-table": { + "columns": { + "title-edit-user": "" + }, "last-seen-never": "", "no-licensed-roles": "" } @@ -357,6 +456,12 @@ "missing-reference": "", "self-reference": "" }, + "draggable-rules-table": { + "evals-to-start-alerting": "", + "pending-period": "", + "recording": "", + "rule-name": "" + }, "export": { "subtitle": { "formats": "", @@ -380,6 +485,45 @@ "export": "", "reorder": "" }, + "group-details": { + "ds-features-error": "", + "edit": "", + "evaluations-to-fire": "", + "export": "", + "folder": "", + "group-loading-error": "", + "interval": "", + "namespace": "", + "pending-period": "", + "recording": "", + "rule-name": "" + }, + "group-edit": { + "ds-error": "", + "form": { + "delete": "", + "delete-body": "", + "delete-confirm": "", + "delete-title": "", + "folder-label": "", + "group-name-label": "", + "group-name-required": "", + "interval-description": "", + "interval-label": "", + "namespace-label": "", + "namespace-required": "", + "rules-description": "", + "rules-label": "", + "save": "", + "update-error": "", + "update-success": "" + }, + "group-not-editable": "", + "group-not-editable-description": "", + "page-title": "", + "rule-group-error": "", + "title": "" + }, "irm-integration": { "connection-method": "", "disabled-description": "", @@ -608,6 +752,13 @@ } } }, + "rule-group-action": { + "details": "", + "edit": "", + "export-rules-folder": "", + "go-to-folder": "", + "manage-permissions": "" + }, "rule-groups": { "delete": { "success": "" @@ -775,6 +926,9 @@ "actions": { "button-to-recently-deleted": "" }, + "browse-view": { + "this-folder-is-empty": "" + }, "counts": { "alertRule_one": "", "alertRule_few": "", @@ -805,6 +959,9 @@ "select-checkbox": "", "tags-column": "" }, + "descendant-count": { + "title-unable-to-retrieve-descendant-information": "" + }, "empty-state": { "button-title": "", "pro-tip": "", @@ -835,6 +992,9 @@ "dashboards": "", "panels": "" }, + "name-cell": { + "no-items": "" + }, "new-folder-form": { "cancel-label": "", "create-label": "", @@ -1155,6 +1315,28 @@ } }, "edit-pane": { + "add": { + "lib-panel": { + "heading": "", + "title": "" + }, + "panel": { + "heading": "", + "title": "" + }, + "paste-panel": { + "heading": "", + "title": "" + }, + "row": { + "heading": "", + "title": "" + }, + "tab": { + "heading": "", + "title": "" + } + }, "elements": { "dashboard": "", "objects": "", @@ -1345,11 +1527,6 @@ }, "toolbar": { "add": "", - "add-panel": "", - "add-panel-description": "", - "add-panel-lib": "", - "add-row": "", - "add-tab": "", "alert-rules": "", "back-to-dashboard": "", "dashboard-settings": { @@ -1372,7 +1549,6 @@ "label": "", "tooltip": "" }, - "libray-panel-description": "", "mark-favorite": "", "more-save-options": "", "open-original": "", @@ -1381,7 +1557,6 @@ "playlist-stop": "", "public-dashboard": "", "refresh": "", - "row-description": "", "save": "", "save-dashboard": { "label": "", @@ -1401,7 +1576,6 @@ "share-button": "", "show-hidden-elements": "", "switch-old-dashboard": "", - "tabs-description": "", "unlink-library-panel": "", "unmark-favorite": "" }, @@ -1456,6 +1630,17 @@ "it-cannot-contain-or": "", "least-valid-character": "" }, + "dashboard-preview-banner": { + "not-saved": "", + "not-yet-saved": "", + "open-pull-request-in-git-hub": "", + "title-dashboard-loaded-branch-git-hub": "", + "title-dashboard-loaded-external-repository": "", + "title-dashboard-loaded-request-git-hub": "", + "title-error-loading-dashboard": "", + "value-not-saved": "", + "view-pull-request-in-git-hub": "" + }, "provisioned-resource-delete-modal": { "file-path": "", "managed-by-version-control": "", @@ -2369,9 +2554,32 @@ } }, "library-panels": { + "add-library-panel-modal": { + "title-create-library-panel": "" + }, + "confirm": { + "delete-panel": "" + }, + "delete-library-panel-modal": { + "cancel": "", + "delete": "", + "title-delete-library-panel": "" + }, "empty-state": { "message": "" }, + "folder-link": { + "dashboards": "" + }, + "has-connected-dashboards": { + "dashboard-name": "" + }, + "library-panels-search": { + "placeholder-search-by-name-or-description": "" + }, + "loading-indicator": { + "loading-library-panel": "" + }, "loading-panel-text": "", "modal": { "button-cancel": "", @@ -2386,9 +2594,20 @@ "body_many": "", "body_other": "" }, + "panel-library-options-group": { + "create-new-library-panel": "" + }, "save": { "error": "", "success": "" + }, + "save-library-panel-modal": { + "cancel": "", + "dashboard-name": "", + "discard": "", + "loading-connected-dashboards": "", + "placeholder-search-affected-dashboards": "", + "update-all": "" } }, "link": { @@ -2644,12 +2863,6 @@ "link-title": "", "title": "" }, - "public-preview": { - "button-text": "", - "message": "", - "message-cloud": "", - "title": "" - }, "resource-details": { "dismiss-button": "", "error-messages": { @@ -3136,6 +3349,27 @@ "error": "" } }, + "org": { + "new-org-page": { + "create": "", + "label-organization-name": "", + "placeholder-org-name": "" + }, + "org-profile": { + "label-organization-name": "", + "label-organization-profile": "", + "update-organization-name": "" + }, + "user-invite-form": { + "back": "", + "label-email-or-username": "", + "label-name": "", + "label-send-invite-email": "", + "placeholder-optional": "", + "role": "", + "submit": "" + } + }, "panel": { "header-menu": { "copy": "", @@ -3166,6 +3400,19 @@ "panel-type-filter": { "clear-button": "" }, + "playlist": { + "start-modal": { + "description-customize-dashboard-elements-visibility": "", + "description-panel-heights-adjusted-screen": "", + "label-autofit": "", + "label-dashboard-links": "", + "label-display-dashboard-controls": "", + "label-mode": "", + "label-time-and-refresh": "", + "label-variables": "", + "title-start-playlist": "" + } + }, "playlist-edit": { "error-prefix": "", "form": { @@ -3995,11 +4242,37 @@ "placeholder": "" }, "teams": { + "create-team": { + "create": "", + "label-name": "", + "label-role": "" + }, "empty-state": { "button-title": "", "message": "", "pro-tip": "", "title": "" + }, + "team-group-sync": { + "add-group": "", + "external-group-id": "", + "external-group-sync": "", + "title-there-external-groups": "" + }, + "team-list": { + "columns": { + "title-edit-team": "" + }, + "new-team": "", + "placeholder-search-teams": "" + }, + "team-settings": { + "label-email": "", + "label-name": "", + "label-numerical-identifier": "", + "label-role": "", + "label-team-details": "", + "save": "" } }, "theme-preview": { diff --git a/public/locales/de-DE/grafana.json b/public/locales/de-DE/grafana.json index 78af301b2f2..1a568cdeb88 100644 --- a/public/locales/de-DE/grafana.json +++ b/public/locales/de-DE/grafana.json @@ -35,15 +35,57 @@ } }, "admin": { + "add-to-org-modal": { + "label-organization": "", + "label-role": "", + "title-add-to-an-organization": "" + }, + "admin-edit-org-page": { + "label-name": "", + "render-missing-permission-message": { + "title-access-denied": "" + } + }, + "admin-feature-toggles-table": { + "get-stage-cell": { + "beta": "", + "deprecated": "", + "ga": "" + }, + "title-apply-feature-toggle-changes": "" + }, + "admin-orgs-table": { + "aria-label-delete-org": "", + "title-delete": "" + }, "anon-users": { "not-found": "" }, + "base-user-sessions": { + "title-force-logout-from-all-devices": "" + }, "edit-org": { "access-denied": "", "heading": "", "update-button": "", "users-heading": "" }, + "feature-listing": { + "title-auditing": "", + "title-dashboard-presence-indicators": "", + "title-dashboard-usage-stats-drawer": "", + "title-data-source-permissions": "", + "title-enhanced-ldap-integration": "", + "title-enterprise-plugins": "", + "title-find-unused-dashboards": "", + "title-grafana-usage-insights": "", + "title-reporting": "", + "title-saml-authentication": "", + "title-settings-updates-at-runtime": "", + "title-sort-dashboards-by-popularity-in-search": "", + "title-team-sync": "", + "title-white-labeling": "" + }, "feature-toggles": { "sub-title": "" }, @@ -61,11 +103,23 @@ "test-mapping-heading": "", "test-mapping-run-button": "" }, + "ldap-error-box": { + "title-connection-error": "" + }, + "ldap-page": { + "label-username": "" + }, "ldap-permissions": { "active": "", "admin": "", "inactive": "" }, + "ldap-settings-page": { + "label-reset-to-default-values": "", + "title-invalid-configuration": "", + "title-more-actions": "", + "tooltip-more-actions": "" + }, "ldap-status": { "title": "" }, @@ -87,12 +141,21 @@ "ldap-user-info": { "no-team": "" }, + "org-role-picker": { + "placeholder-choose-role": "" + }, "org-uers": { "last-seen-never": "" }, "org-users": { "not-editable": "" }, + "org-users-table": { + "columns": { + "aria-label-role": "" + }, + "title-delete": "" + }, "orgs": { "delete-body": "", "id-header": "", @@ -107,12 +170,38 @@ "title": "", "users-button": "" }, + "service-info": { + "title-critical-sla-hours": "", + "title-enterprise-plugins": "", + "title-unlimited-expert-support": "" + }, "settings": { "info-description": "" }, "upgrade-info": { "title": "" }, + "user-create-page": { + "label-email": "", + "label-name": "", + "label-password": "", + "label-username": "" + }, + "user-ldap-sync-info": { + "label-ldap": "" + }, + "user-list-admin-page-un-connected": { + "placeholder-search-login-email": "" + }, + "user-list-anonymous-devices-page-un-connected": { + "placeholder-search-devices-by-ip-address": "" + }, + "user-list-page": { + "label-all-users": "", + "label-anonymous-devices": "", + "label-organization-users": "", + "label-users": "" + }, "user-orgs": { "add-button": "", "change-role-button": "", @@ -137,7 +226,14 @@ "disable-button": "", "edit-button": "", "enable-button": "", - "title": "" + "label-email": "", + "label-name": "", + "label-numerical-identifier": "", + "label-password": "", + "label-username": "", + "title": "", + "title-delete-user": "", + "title-disable-user": "" }, "user-sessions": { "browser-column": "", @@ -155,6 +251,9 @@ "create-button": "" }, "users-table": { + "columns": { + "title-edit-user": "" + }, "last-seen-never": "", "no-licensed-roles": "" } @@ -353,6 +452,12 @@ "missing-reference": "", "self-reference": "" }, + "draggable-rules-table": { + "evals-to-start-alerting": "", + "pending-period": "", + "recording": "", + "rule-name": "" + }, "export": { "subtitle": { "formats": "", @@ -376,6 +481,45 @@ "export": "", "reorder": "" }, + "group-details": { + "ds-features-error": "", + "edit": "", + "evaluations-to-fire": "", + "export": "", + "folder": "", + "group-loading-error": "", + "interval": "", + "namespace": "", + "pending-period": "", + "recording": "", + "rule-name": "" + }, + "group-edit": { + "ds-error": "", + "form": { + "delete": "", + "delete-body": "", + "delete-confirm": "", + "delete-title": "", + "folder-label": "", + "group-name-label": "", + "group-name-required": "", + "interval-description": "", + "interval-label": "", + "namespace-label": "", + "namespace-required": "", + "rules-description": "", + "rules-label": "", + "save": "", + "update-error": "", + "update-success": "" + }, + "group-not-editable": "", + "group-not-editable-description": "", + "page-title": "", + "rule-group-error": "", + "title": "" + }, "irm-integration": { "connection-method": "", "disabled-description": "", @@ -600,6 +744,13 @@ } } }, + "rule-group-action": { + "details": "", + "edit": "", + "export-rules-folder": "", + "go-to-folder": "", + "manage-permissions": "" + }, "rule-groups": { "delete": { "success": "" @@ -767,6 +918,9 @@ "actions": { "button-to-recently-deleted": "" }, + "browse-view": { + "this-folder-is-empty": "" + }, "counts": { "alertRule_one": "{{count}} Alarmierungsregel", "alertRule_other": "{{count}} Alarmierungsregeln", @@ -787,6 +941,9 @@ "select-checkbox": "Auswählen", "tags-column": "Tags" }, + "descendant-count": { + "title-unable-to-retrieve-descendant-information": "" + }, "empty-state": { "button-title": "", "pro-tip": "", @@ -817,6 +974,9 @@ "dashboards": "Dashboards", "panels": "Fenster" }, + "name-cell": { + "no-items": "" + }, "new-folder-form": { "cancel-label": "Abbrechen", "create-label": "Erstellen", @@ -1137,6 +1297,28 @@ } }, "edit-pane": { + "add": { + "lib-panel": { + "heading": "", + "title": "" + }, + "panel": { + "heading": "", + "title": "" + }, + "paste-panel": { + "heading": "", + "title": "" + }, + "row": { + "heading": "", + "title": "" + }, + "tab": { + "heading": "", + "title": "" + } + }, "elements": { "dashboard": "", "objects": "", @@ -1327,11 +1509,6 @@ }, "toolbar": { "add": "Hinzufügen", - "add-panel": "", - "add-panel-description": "", - "add-panel-lib": "", - "add-row": "", - "add-tab": "", "alert-rules": "Warnregeln", "back-to-dashboard": "", "dashboard-settings": { @@ -1354,7 +1531,6 @@ "label": "", "tooltip": "" }, - "libray-panel-description": "", "mark-favorite": "Als Favorit markieren", "more-save-options": "", "open-original": "Original-Dashboard öffnen", @@ -1363,7 +1539,6 @@ "playlist-stop": "Wiedergabeliste stoppen", "public-dashboard": "", "refresh": "Dashboard aktualisieren", - "row-description": "", "save": "Dashboard speichern", "save-dashboard": { "label": "", @@ -1383,7 +1558,6 @@ "share-button": "Teilen", "show-hidden-elements": "", "switch-old-dashboard": "", - "tabs-description": "", "unlink-library-panel": "", "unmark-favorite": "Markierung als Favorit entfernen" }, @@ -1438,6 +1612,17 @@ "it-cannot-contain-or": "", "least-valid-character": "" }, + "dashboard-preview-banner": { + "not-saved": "", + "not-yet-saved": "", + "open-pull-request-in-git-hub": "", + "title-dashboard-loaded-branch-git-hub": "", + "title-dashboard-loaded-external-repository": "", + "title-dashboard-loaded-request-git-hub": "", + "title-error-loading-dashboard": "", + "value-not-saved": "", + "view-pull-request-in-git-hub": "" + }, "provisioned-resource-delete-modal": { "file-path": "", "managed-by-version-control": "", @@ -2351,9 +2536,32 @@ } }, "library-panels": { + "add-library-panel-modal": { + "title-create-library-panel": "" + }, + "confirm": { + "delete-panel": "" + }, + "delete-library-panel-modal": { + "cancel": "", + "delete": "", + "title-delete-library-panel": "" + }, "empty-state": { "message": "" }, + "folder-link": { + "dashboards": "" + }, + "has-connected-dashboards": { + "dashboard-name": "" + }, + "library-panels-search": { + "placeholder-search-by-name-or-description": "" + }, + "loading-indicator": { + "loading-library-panel": "" + }, "loading-panel-text": "", "modal": { "button-cancel": "Abbrechen", @@ -2366,9 +2574,20 @@ "body_one": "Dieses Fenster wird in {{count}} Dashboard verwendet. Bitte wählen Sie aus, in welchem Dashboard Sie das Fenster sehen möchten:", "body_other": "Dieses Fenster wird in {{count}} Dashboards verwendet. Bitte wählen Sie aus, in welchem Dashboard Sie das Fenster sehen möchten:" }, + "panel-library-options-group": { + "create-new-library-panel": "" + }, "save": { "error": "Fehler beim Speichern des Bibliotheks-Panels: „{{errorMsg}}“", "success": "Bibliotheks-Panel wurde gespeichert" + }, + "save-library-panel-modal": { + "cancel": "", + "dashboard-name": "", + "discard": "", + "loading-connected-dashboards": "", + "placeholder-search-affected-dashboards": "", + "update-all": "" } }, "link": { @@ -2624,12 +2843,6 @@ "link-title": "Grafana Cloud Preisgestaltung", "title": "Was kostet es?" }, - "public-preview": { - "button-text": "", - "message": "", - "message-cloud": "", - "title": "" - }, "resource-details": { "dismiss-button": "", "error-messages": { @@ -3116,6 +3329,27 @@ "error": "" } }, + "org": { + "new-org-page": { + "create": "", + "label-organization-name": "", + "placeholder-org-name": "" + }, + "org-profile": { + "label-organization-name": "", + "label-organization-profile": "", + "update-organization-name": "" + }, + "user-invite-form": { + "back": "", + "label-email-or-username": "", + "label-name": "", + "label-send-invite-email": "", + "placeholder-optional": "", + "role": "", + "submit": "" + } + }, "panel": { "header-menu": { "copy": "Kopieren", @@ -3146,6 +3380,19 @@ "panel-type-filter": { "clear-button": "" }, + "playlist": { + "start-modal": { + "description-customize-dashboard-elements-visibility": "", + "description-panel-heights-adjusted-screen": "", + "label-autofit": "", + "label-dashboard-links": "", + "label-display-dashboard-controls": "", + "label-mode": "", + "label-time-and-refresh": "", + "label-variables": "", + "title-start-playlist": "" + } + }, "playlist-edit": { "error-prefix": "Fehler beim Laden der Wiedergabeliste:", "form": { @@ -3967,11 +4214,37 @@ "placeholder": "Nach Tag filtern" }, "teams": { + "create-team": { + "create": "", + "label-name": "", + "label-role": "" + }, "empty-state": { "button-title": "", "message": "", "pro-tip": "", "title": "" + }, + "team-group-sync": { + "add-group": "", + "external-group-id": "", + "external-group-sync": "", + "title-there-external-groups": "" + }, + "team-list": { + "columns": { + "title-edit-team": "" + }, + "new-team": "", + "placeholder-search-teams": "" + }, + "team-settings": { + "label-email": "", + "label-name": "", + "label-numerical-identifier": "", + "label-role": "", + "label-team-details": "", + "save": "" } }, "theme-preview": { diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index 912f333f1ed..a81d84e77bf 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -1439,6 +1439,15 @@ }, "responsive-layout": { "description": "Panels resize to fit and form uniform grids", + "item-options": { + "repeat": { + "title": "Repeat options", + "variable": { + "description": "Repeat this panel for each value in the selected variable. This is not visible while in edit mode. You need to go back to dashboard and then update the variable or reload the dashboard.", + "title": "Repeat by variable" + } + } + }, "name": "Auto grid", "options": { "columns": "Columns", @@ -1453,12 +1462,6 @@ "rows-layout": { "description": "Collapsable panel groups with headings", "name": "Rows", - "option": { - "height": "Height", - "hide-header": "Hide row header", - "repeat": "Repeat for", - "title": "Title" - }, "options": { "height-expand": "Expand", "height-min": "Min" @@ -1482,6 +1485,18 @@ } }, "row-options": { + "repeat": { + "title": "Repeat options", + "variable": { + "description": "Repeat this row for each value in the selected variable.", + "title": "Repeat by variable" + } + }, + "row": { + "height": "Height", + "hide-header": "Hide row header", + "title": "Title" + }, "title-option": "Title" } }, diff --git a/public/locales/es-ES/grafana.json b/public/locales/es-ES/grafana.json index 20e3f23dbf5..b3a38cb6018 100644 --- a/public/locales/es-ES/grafana.json +++ b/public/locales/es-ES/grafana.json @@ -35,15 +35,57 @@ } }, "admin": { + "add-to-org-modal": { + "label-organization": "", + "label-role": "", + "title-add-to-an-organization": "" + }, + "admin-edit-org-page": { + "label-name": "", + "render-missing-permission-message": { + "title-access-denied": "" + } + }, + "admin-feature-toggles-table": { + "get-stage-cell": { + "beta": "", + "deprecated": "", + "ga": "" + }, + "title-apply-feature-toggle-changes": "" + }, + "admin-orgs-table": { + "aria-label-delete-org": "", + "title-delete": "" + }, "anon-users": { "not-found": "" }, + "base-user-sessions": { + "title-force-logout-from-all-devices": "" + }, "edit-org": { "access-denied": "", "heading": "", "update-button": "", "users-heading": "" }, + "feature-listing": { + "title-auditing": "", + "title-dashboard-presence-indicators": "", + "title-dashboard-usage-stats-drawer": "", + "title-data-source-permissions": "", + "title-enhanced-ldap-integration": "", + "title-enterprise-plugins": "", + "title-find-unused-dashboards": "", + "title-grafana-usage-insights": "", + "title-reporting": "", + "title-saml-authentication": "", + "title-settings-updates-at-runtime": "", + "title-sort-dashboards-by-popularity-in-search": "", + "title-team-sync": "", + "title-white-labeling": "" + }, "feature-toggles": { "sub-title": "" }, @@ -61,11 +103,23 @@ "test-mapping-heading": "", "test-mapping-run-button": "" }, + "ldap-error-box": { + "title-connection-error": "" + }, + "ldap-page": { + "label-username": "" + }, "ldap-permissions": { "active": "", "admin": "", "inactive": "" }, + "ldap-settings-page": { + "label-reset-to-default-values": "", + "title-invalid-configuration": "", + "title-more-actions": "", + "tooltip-more-actions": "" + }, "ldap-status": { "title": "" }, @@ -87,12 +141,21 @@ "ldap-user-info": { "no-team": "" }, + "org-role-picker": { + "placeholder-choose-role": "" + }, "org-uers": { "last-seen-never": "" }, "org-users": { "not-editable": "" }, + "org-users-table": { + "columns": { + "aria-label-role": "" + }, + "title-delete": "" + }, "orgs": { "delete-body": "", "id-header": "", @@ -107,12 +170,38 @@ "title": "", "users-button": "" }, + "service-info": { + "title-critical-sla-hours": "", + "title-enterprise-plugins": "", + "title-unlimited-expert-support": "" + }, "settings": { "info-description": "" }, "upgrade-info": { "title": "" }, + "user-create-page": { + "label-email": "", + "label-name": "", + "label-password": "", + "label-username": "" + }, + "user-ldap-sync-info": { + "label-ldap": "" + }, + "user-list-admin-page-un-connected": { + "placeholder-search-login-email": "" + }, + "user-list-anonymous-devices-page-un-connected": { + "placeholder-search-devices-by-ip-address": "" + }, + "user-list-page": { + "label-all-users": "", + "label-anonymous-devices": "", + "label-organization-users": "", + "label-users": "" + }, "user-orgs": { "add-button": "", "change-role-button": "", @@ -137,7 +226,14 @@ "disable-button": "", "edit-button": "", "enable-button": "", - "title": "" + "label-email": "", + "label-name": "", + "label-numerical-identifier": "", + "label-password": "", + "label-username": "", + "title": "", + "title-delete-user": "", + "title-disable-user": "" }, "user-sessions": { "browser-column": "", @@ -155,6 +251,9 @@ "create-button": "" }, "users-table": { + "columns": { + "title-edit-user": "" + }, "last-seen-never": "", "no-licensed-roles": "" } @@ -353,6 +452,12 @@ "missing-reference": "", "self-reference": "" }, + "draggable-rules-table": { + "evals-to-start-alerting": "", + "pending-period": "", + "recording": "", + "rule-name": "" + }, "export": { "subtitle": { "formats": "", @@ -376,6 +481,45 @@ "export": "", "reorder": "" }, + "group-details": { + "ds-features-error": "", + "edit": "", + "evaluations-to-fire": "", + "export": "", + "folder": "", + "group-loading-error": "", + "interval": "", + "namespace": "", + "pending-period": "", + "recording": "", + "rule-name": "" + }, + "group-edit": { + "ds-error": "", + "form": { + "delete": "", + "delete-body": "", + "delete-confirm": "", + "delete-title": "", + "folder-label": "", + "group-name-label": "", + "group-name-required": "", + "interval-description": "", + "interval-label": "", + "namespace-label": "", + "namespace-required": "", + "rules-description": "", + "rules-label": "", + "save": "", + "update-error": "", + "update-success": "" + }, + "group-not-editable": "", + "group-not-editable-description": "", + "page-title": "", + "rule-group-error": "", + "title": "" + }, "irm-integration": { "connection-method": "", "disabled-description": "", @@ -600,6 +744,13 @@ } } }, + "rule-group-action": { + "details": "", + "edit": "", + "export-rules-folder": "", + "go-to-folder": "", + "manage-permissions": "" + }, "rule-groups": { "delete": { "success": "" @@ -767,6 +918,9 @@ "actions": { "button-to-recently-deleted": "" }, + "browse-view": { + "this-folder-is-empty": "" + }, "counts": { "alertRule_one": "{{count}} regla de alerta", "alertRule_other": "{{count}} reglas de alerta", @@ -787,6 +941,9 @@ "select-checkbox": "Seleccionar", "tags-column": "Etiquetas" }, + "descendant-count": { + "title-unable-to-retrieve-descendant-information": "" + }, "empty-state": { "button-title": "", "pro-tip": "", @@ -817,6 +974,9 @@ "dashboards": "Paneles de control", "panels": "Paneles" }, + "name-cell": { + "no-items": "" + }, "new-folder-form": { "cancel-label": "Cancelar", "create-label": "Crear", @@ -1137,6 +1297,28 @@ } }, "edit-pane": { + "add": { + "lib-panel": { + "heading": "", + "title": "" + }, + "panel": { + "heading": "", + "title": "" + }, + "paste-panel": { + "heading": "", + "title": "" + }, + "row": { + "heading": "", + "title": "" + }, + "tab": { + "heading": "", + "title": "" + } + }, "elements": { "dashboard": "", "objects": "", @@ -1327,11 +1509,6 @@ }, "toolbar": { "add": "Añadir", - "add-panel": "", - "add-panel-description": "", - "add-panel-lib": "", - "add-row": "", - "add-tab": "", "alert-rules": "Reglas de alerta", "back-to-dashboard": "", "dashboard-settings": { @@ -1354,7 +1531,6 @@ "label": "", "tooltip": "" }, - "libray-panel-description": "", "mark-favorite": "Marcar como favorito", "more-save-options": "", "open-original": "Abrir el panel de control original", @@ -1363,7 +1539,6 @@ "playlist-stop": "Detener la lista de reproducción", "public-dashboard": "", "refresh": "Actualizar panel de control", - "row-description": "", "save": "Guardar panel de control", "save-dashboard": { "label": "", @@ -1383,7 +1558,6 @@ "share-button": "Compartir", "show-hidden-elements": "", "switch-old-dashboard": "", - "tabs-description": "", "unlink-library-panel": "", "unmark-favorite": "Deshacer marca como favorito" }, @@ -1438,6 +1612,17 @@ "it-cannot-contain-or": "", "least-valid-character": "" }, + "dashboard-preview-banner": { + "not-saved": "", + "not-yet-saved": "", + "open-pull-request-in-git-hub": "", + "title-dashboard-loaded-branch-git-hub": "", + "title-dashboard-loaded-external-repository": "", + "title-dashboard-loaded-request-git-hub": "", + "title-error-loading-dashboard": "", + "value-not-saved": "", + "view-pull-request-in-git-hub": "" + }, "provisioned-resource-delete-modal": { "file-path": "", "managed-by-version-control": "", @@ -2351,9 +2536,32 @@ } }, "library-panels": { + "add-library-panel-modal": { + "title-create-library-panel": "" + }, + "confirm": { + "delete-panel": "" + }, + "delete-library-panel-modal": { + "cancel": "", + "delete": "", + "title-delete-library-panel": "" + }, "empty-state": { "message": "" }, + "folder-link": { + "dashboards": "" + }, + "has-connected-dashboards": { + "dashboard-name": "" + }, + "library-panels-search": { + "placeholder-search-by-name-or-description": "" + }, + "loading-indicator": { + "loading-library-panel": "" + }, "loading-panel-text": "", "modal": { "button-cancel": "", @@ -2366,9 +2574,20 @@ "body_one": "Este panel se está utilizando en {{count}} tablero. Por favor, elija qué tablero quiere ver en el panel:", "body_other": "Este panel se está utilizando en {{count}} paneles de control. Elija qué panel quiere ver en el panel de control:" }, + "panel-library-options-group": { + "create-new-library-panel": "" + }, "save": { "error": "Error al guardar el panel de la librería: «{{errorMsg}}»", "success": "Panel de librería guardado" + }, + "save-library-panel-modal": { + "cancel": "", + "dashboard-name": "", + "discard": "", + "loading-connected-dashboards": "", + "placeholder-search-affected-dashboards": "", + "update-all": "" } }, "link": { @@ -2624,12 +2843,6 @@ "link-title": "Precios de Grafana Cloud", "title": "¿Cuánto cuesta?" }, - "public-preview": { - "button-text": "", - "message": "", - "message-cloud": "", - "title": "" - }, "resource-details": { "dismiss-button": "", "error-messages": { @@ -3116,6 +3329,27 @@ "error": "" } }, + "org": { + "new-org-page": { + "create": "", + "label-organization-name": "", + "placeholder-org-name": "" + }, + "org-profile": { + "label-organization-name": "", + "label-organization-profile": "", + "update-organization-name": "" + }, + "user-invite-form": { + "back": "", + "label-email-or-username": "", + "label-name": "", + "label-send-invite-email": "", + "placeholder-optional": "", + "role": "", + "submit": "" + } + }, "panel": { "header-menu": { "copy": "Copiar", @@ -3146,6 +3380,19 @@ "panel-type-filter": { "clear-button": "" }, + "playlist": { + "start-modal": { + "description-customize-dashboard-elements-visibility": "", + "description-panel-heights-adjusted-screen": "", + "label-autofit": "", + "label-dashboard-links": "", + "label-display-dashboard-controls": "", + "label-mode": "", + "label-time-and-refresh": "", + "label-variables": "", + "title-start-playlist": "" + } + }, "playlist-edit": { "error-prefix": "Error al cargar la lista de reproducción:", "form": { @@ -3967,11 +4214,37 @@ "placeholder": "Filtrar por etiqueta" }, "teams": { + "create-team": { + "create": "", + "label-name": "", + "label-role": "" + }, "empty-state": { "button-title": "", "message": "", "pro-tip": "", "title": "" + }, + "team-group-sync": { + "add-group": "", + "external-group-id": "", + "external-group-sync": "", + "title-there-external-groups": "" + }, + "team-list": { + "columns": { + "title-edit-team": "" + }, + "new-team": "", + "placeholder-search-teams": "" + }, + "team-settings": { + "label-email": "", + "label-name": "", + "label-numerical-identifier": "", + "label-role": "", + "label-team-details": "", + "save": "" } }, "theme-preview": { diff --git a/public/locales/fr-FR/grafana.json b/public/locales/fr-FR/grafana.json index 1d42e07b9fe..4ea1a67cf89 100644 --- a/public/locales/fr-FR/grafana.json +++ b/public/locales/fr-FR/grafana.json @@ -35,15 +35,57 @@ } }, "admin": { + "add-to-org-modal": { + "label-organization": "", + "label-role": "", + "title-add-to-an-organization": "" + }, + "admin-edit-org-page": { + "label-name": "", + "render-missing-permission-message": { + "title-access-denied": "" + } + }, + "admin-feature-toggles-table": { + "get-stage-cell": { + "beta": "", + "deprecated": "", + "ga": "" + }, + "title-apply-feature-toggle-changes": "" + }, + "admin-orgs-table": { + "aria-label-delete-org": "", + "title-delete": "" + }, "anon-users": { "not-found": "" }, + "base-user-sessions": { + "title-force-logout-from-all-devices": "" + }, "edit-org": { "access-denied": "", "heading": "", "update-button": "", "users-heading": "" }, + "feature-listing": { + "title-auditing": "", + "title-dashboard-presence-indicators": "", + "title-dashboard-usage-stats-drawer": "", + "title-data-source-permissions": "", + "title-enhanced-ldap-integration": "", + "title-enterprise-plugins": "", + "title-find-unused-dashboards": "", + "title-grafana-usage-insights": "", + "title-reporting": "", + "title-saml-authentication": "", + "title-settings-updates-at-runtime": "", + "title-sort-dashboards-by-popularity-in-search": "", + "title-team-sync": "", + "title-white-labeling": "" + }, "feature-toggles": { "sub-title": "" }, @@ -61,11 +103,23 @@ "test-mapping-heading": "", "test-mapping-run-button": "" }, + "ldap-error-box": { + "title-connection-error": "" + }, + "ldap-page": { + "label-username": "" + }, "ldap-permissions": { "active": "", "admin": "", "inactive": "" }, + "ldap-settings-page": { + "label-reset-to-default-values": "", + "title-invalid-configuration": "", + "title-more-actions": "", + "tooltip-more-actions": "" + }, "ldap-status": { "title": "" }, @@ -87,12 +141,21 @@ "ldap-user-info": { "no-team": "" }, + "org-role-picker": { + "placeholder-choose-role": "" + }, "org-uers": { "last-seen-never": "" }, "org-users": { "not-editable": "" }, + "org-users-table": { + "columns": { + "aria-label-role": "" + }, + "title-delete": "" + }, "orgs": { "delete-body": "", "id-header": "", @@ -107,12 +170,38 @@ "title": "", "users-button": "" }, + "service-info": { + "title-critical-sla-hours": "", + "title-enterprise-plugins": "", + "title-unlimited-expert-support": "" + }, "settings": { "info-description": "" }, "upgrade-info": { "title": "" }, + "user-create-page": { + "label-email": "", + "label-name": "", + "label-password": "", + "label-username": "" + }, + "user-ldap-sync-info": { + "label-ldap": "" + }, + "user-list-admin-page-un-connected": { + "placeholder-search-login-email": "" + }, + "user-list-anonymous-devices-page-un-connected": { + "placeholder-search-devices-by-ip-address": "" + }, + "user-list-page": { + "label-all-users": "", + "label-anonymous-devices": "", + "label-organization-users": "", + "label-users": "" + }, "user-orgs": { "add-button": "", "change-role-button": "", @@ -137,7 +226,14 @@ "disable-button": "", "edit-button": "", "enable-button": "", - "title": "" + "label-email": "", + "label-name": "", + "label-numerical-identifier": "", + "label-password": "", + "label-username": "", + "title": "", + "title-delete-user": "", + "title-disable-user": "" }, "user-sessions": { "browser-column": "", @@ -155,6 +251,9 @@ "create-button": "" }, "users-table": { + "columns": { + "title-edit-user": "" + }, "last-seen-never": "", "no-licensed-roles": "" } @@ -353,6 +452,12 @@ "missing-reference": "", "self-reference": "" }, + "draggable-rules-table": { + "evals-to-start-alerting": "", + "pending-period": "", + "recording": "", + "rule-name": "" + }, "export": { "subtitle": { "formats": "", @@ -376,6 +481,45 @@ "export": "", "reorder": "" }, + "group-details": { + "ds-features-error": "", + "edit": "", + "evaluations-to-fire": "", + "export": "", + "folder": "", + "group-loading-error": "", + "interval": "", + "namespace": "", + "pending-period": "", + "recording": "", + "rule-name": "" + }, + "group-edit": { + "ds-error": "", + "form": { + "delete": "", + "delete-body": "", + "delete-confirm": "", + "delete-title": "", + "folder-label": "", + "group-name-label": "", + "group-name-required": "", + "interval-description": "", + "interval-label": "", + "namespace-label": "", + "namespace-required": "", + "rules-description": "", + "rules-label": "", + "save": "", + "update-error": "", + "update-success": "" + }, + "group-not-editable": "", + "group-not-editable-description": "", + "page-title": "", + "rule-group-error": "", + "title": "" + }, "irm-integration": { "connection-method": "", "disabled-description": "", @@ -600,6 +744,13 @@ } } }, + "rule-group-action": { + "details": "", + "edit": "", + "export-rules-folder": "", + "go-to-folder": "", + "manage-permissions": "" + }, "rule-groups": { "delete": { "success": "" @@ -767,6 +918,9 @@ "actions": { "button-to-recently-deleted": "" }, + "browse-view": { + "this-folder-is-empty": "" + }, "counts": { "alertRule_one": "{{count}} règle d'alerte", "alertRule_other": "{{count}} règles d'alerte", @@ -787,6 +941,9 @@ "select-checkbox": "Sélectionner", "tags-column": "Étiquettes" }, + "descendant-count": { + "title-unable-to-retrieve-descendant-information": "" + }, "empty-state": { "button-title": "", "pro-tip": "", @@ -817,6 +974,9 @@ "dashboards": "Tableaux de bord", "panels": "Panneaux" }, + "name-cell": { + "no-items": "" + }, "new-folder-form": { "cancel-label": "Annuler", "create-label": "Créer", @@ -1137,6 +1297,28 @@ } }, "edit-pane": { + "add": { + "lib-panel": { + "heading": "", + "title": "" + }, + "panel": { + "heading": "", + "title": "" + }, + "paste-panel": { + "heading": "", + "title": "" + }, + "row": { + "heading": "", + "title": "" + }, + "tab": { + "heading": "", + "title": "" + } + }, "elements": { "dashboard": "", "objects": "", @@ -1327,11 +1509,6 @@ }, "toolbar": { "add": "Ajouter", - "add-panel": "", - "add-panel-description": "", - "add-panel-lib": "", - "add-row": "", - "add-tab": "", "alert-rules": "Règles d'alerte", "back-to-dashboard": "", "dashboard-settings": { @@ -1354,7 +1531,6 @@ "label": "", "tooltip": "" }, - "libray-panel-description": "", "mark-favorite": "Marquer comme favori", "more-save-options": "", "open-original": "Ouvrir le tableau de bord d'origine", @@ -1363,7 +1539,6 @@ "playlist-stop": "Arrêter la liste de lecture", "public-dashboard": "", "refresh": "Actualiser le tableau de bord", - "row-description": "", "save": "Enregistrer le tableau de bord", "save-dashboard": { "label": "", @@ -1383,7 +1558,6 @@ "share-button": "Partager", "show-hidden-elements": "", "switch-old-dashboard": "", - "tabs-description": "", "unlink-library-panel": "", "unmark-favorite": "Supprimer des favoris" }, @@ -1438,6 +1612,17 @@ "it-cannot-contain-or": "", "least-valid-character": "" }, + "dashboard-preview-banner": { + "not-saved": "", + "not-yet-saved": "", + "open-pull-request-in-git-hub": "", + "title-dashboard-loaded-branch-git-hub": "", + "title-dashboard-loaded-external-repository": "", + "title-dashboard-loaded-request-git-hub": "", + "title-error-loading-dashboard": "", + "value-not-saved": "", + "view-pull-request-in-git-hub": "" + }, "provisioned-resource-delete-modal": { "file-path": "", "managed-by-version-control": "", @@ -2351,9 +2536,32 @@ } }, "library-panels": { + "add-library-panel-modal": { + "title-create-library-panel": "" + }, + "confirm": { + "delete-panel": "" + }, + "delete-library-panel-modal": { + "cancel": "", + "delete": "", + "title-delete-library-panel": "" + }, "empty-state": { "message": "" }, + "folder-link": { + "dashboards": "" + }, + "has-connected-dashboards": { + "dashboard-name": "" + }, + "library-panels-search": { + "placeholder-search-by-name-or-description": "" + }, + "loading-indicator": { + "loading-library-panel": "" + }, "loading-panel-text": "", "modal": { "button-cancel": "", @@ -2366,9 +2574,20 @@ "body_one": "Ce panneau est utilisé dans {{count}} tableau de bord. Choisissez dans quel tableau de bord le panneau sera affiché :", "body_other": "Ce panneau est utilisé dans {{count}} tableaux de bord. Choisissez dans quel tableau de bord le panneau sera affiché :" }, + "panel-library-options-group": { + "create-new-library-panel": "" + }, "save": { "error": "Erreur lors de l'enregistrement du panneau de bibliothèque : \"{{errorMsg}}\"", "success": "Panneau de bibliothèque enregistré" + }, + "save-library-panel-modal": { + "cancel": "", + "dashboard-name": "", + "discard": "", + "loading-connected-dashboards": "", + "placeholder-search-affected-dashboards": "", + "update-all": "" } }, "link": { @@ -2624,12 +2843,6 @@ "link-title": "Tarifs de Grafana Cloud", "title": "Combien ça coûte ?" }, - "public-preview": { - "button-text": "", - "message": "", - "message-cloud": "", - "title": "" - }, "resource-details": { "dismiss-button": "", "error-messages": { @@ -3116,6 +3329,27 @@ "error": "" } }, + "org": { + "new-org-page": { + "create": "", + "label-organization-name": "", + "placeholder-org-name": "" + }, + "org-profile": { + "label-organization-name": "", + "label-organization-profile": "", + "update-organization-name": "" + }, + "user-invite-form": { + "back": "", + "label-email-or-username": "", + "label-name": "", + "label-send-invite-email": "", + "placeholder-optional": "", + "role": "", + "submit": "" + } + }, "panel": { "header-menu": { "copy": "Copier", @@ -3146,6 +3380,19 @@ "panel-type-filter": { "clear-button": "" }, + "playlist": { + "start-modal": { + "description-customize-dashboard-elements-visibility": "", + "description-panel-heights-adjusted-screen": "", + "label-autofit": "", + "label-dashboard-links": "", + "label-display-dashboard-controls": "", + "label-mode": "", + "label-time-and-refresh": "", + "label-variables": "", + "title-start-playlist": "" + } + }, "playlist-edit": { "error-prefix": "Erreur lors du chargement de la playlist :", "form": { @@ -3967,11 +4214,37 @@ "placeholder": "Filtrer par étiquette" }, "teams": { + "create-team": { + "create": "", + "label-name": "", + "label-role": "" + }, "empty-state": { "button-title": "", "message": "", "pro-tip": "", "title": "" + }, + "team-group-sync": { + "add-group": "", + "external-group-id": "", + "external-group-sync": "", + "title-there-external-groups": "" + }, + "team-list": { + "columns": { + "title-edit-team": "" + }, + "new-team": "", + "placeholder-search-teams": "" + }, + "team-settings": { + "label-email": "", + "label-name": "", + "label-numerical-identifier": "", + "label-role": "", + "label-team-details": "", + "save": "" } }, "theme-preview": { diff --git a/public/locales/hu-HU/grafana.json b/public/locales/hu-HU/grafana.json index 0b79be81e58..f7dea7cfce7 100644 --- a/public/locales/hu-HU/grafana.json +++ b/public/locales/hu-HU/grafana.json @@ -35,15 +35,57 @@ } }, "admin": { + "add-to-org-modal": { + "label-organization": "", + "label-role": "", + "title-add-to-an-organization": "" + }, + "admin-edit-org-page": { + "label-name": "", + "render-missing-permission-message": { + "title-access-denied": "" + } + }, + "admin-feature-toggles-table": { + "get-stage-cell": { + "beta": "", + "deprecated": "", + "ga": "" + }, + "title-apply-feature-toggle-changes": "" + }, + "admin-orgs-table": { + "aria-label-delete-org": "", + "title-delete": "" + }, "anon-users": { "not-found": "" }, + "base-user-sessions": { + "title-force-logout-from-all-devices": "" + }, "edit-org": { "access-denied": "", "heading": "", "update-button": "", "users-heading": "" }, + "feature-listing": { + "title-auditing": "", + "title-dashboard-presence-indicators": "", + "title-dashboard-usage-stats-drawer": "", + "title-data-source-permissions": "", + "title-enhanced-ldap-integration": "", + "title-enterprise-plugins": "", + "title-find-unused-dashboards": "", + "title-grafana-usage-insights": "", + "title-reporting": "", + "title-saml-authentication": "", + "title-settings-updates-at-runtime": "", + "title-sort-dashboards-by-popularity-in-search": "", + "title-team-sync": "", + "title-white-labeling": "" + }, "feature-toggles": { "sub-title": "" }, @@ -61,11 +103,23 @@ "test-mapping-heading": "", "test-mapping-run-button": "" }, + "ldap-error-box": { + "title-connection-error": "" + }, + "ldap-page": { + "label-username": "" + }, "ldap-permissions": { "active": "", "admin": "", "inactive": "" }, + "ldap-settings-page": { + "label-reset-to-default-values": "", + "title-invalid-configuration": "", + "title-more-actions": "", + "tooltip-more-actions": "" + }, "ldap-status": { "title": "" }, @@ -87,12 +141,21 @@ "ldap-user-info": { "no-team": "" }, + "org-role-picker": { + "placeholder-choose-role": "" + }, "org-uers": { "last-seen-never": "" }, "org-users": { "not-editable": "" }, + "org-users-table": { + "columns": { + "aria-label-role": "" + }, + "title-delete": "" + }, "orgs": { "delete-body": "", "id-header": "", @@ -107,12 +170,38 @@ "title": "", "users-button": "" }, + "service-info": { + "title-critical-sla-hours": "", + "title-enterprise-plugins": "", + "title-unlimited-expert-support": "" + }, "settings": { "info-description": "" }, "upgrade-info": { "title": "" }, + "user-create-page": { + "label-email": "", + "label-name": "", + "label-password": "", + "label-username": "" + }, + "user-ldap-sync-info": { + "label-ldap": "" + }, + "user-list-admin-page-un-connected": { + "placeholder-search-login-email": "" + }, + "user-list-anonymous-devices-page-un-connected": { + "placeholder-search-devices-by-ip-address": "" + }, + "user-list-page": { + "label-all-users": "", + "label-anonymous-devices": "", + "label-organization-users": "", + "label-users": "" + }, "user-orgs": { "add-button": "", "change-role-button": "", @@ -137,7 +226,14 @@ "disable-button": "", "edit-button": "", "enable-button": "", - "title": "" + "label-email": "", + "label-name": "", + "label-numerical-identifier": "", + "label-password": "", + "label-username": "", + "title": "", + "title-delete-user": "", + "title-disable-user": "" }, "user-sessions": { "browser-column": "", @@ -155,6 +251,9 @@ "create-button": "" }, "users-table": { + "columns": { + "title-edit-user": "" + }, "last-seen-never": "", "no-licensed-roles": "" } @@ -353,6 +452,12 @@ "missing-reference": "", "self-reference": "" }, + "draggable-rules-table": { + "evals-to-start-alerting": "", + "pending-period": "", + "recording": "", + "rule-name": "" + }, "export": { "subtitle": { "formats": "", @@ -376,6 +481,45 @@ "export": "", "reorder": "" }, + "group-details": { + "ds-features-error": "", + "edit": "", + "evaluations-to-fire": "", + "export": "", + "folder": "", + "group-loading-error": "", + "interval": "", + "namespace": "", + "pending-period": "", + "recording": "", + "rule-name": "" + }, + "group-edit": { + "ds-error": "", + "form": { + "delete": "", + "delete-body": "", + "delete-confirm": "", + "delete-title": "", + "folder-label": "", + "group-name-label": "", + "group-name-required": "", + "interval-description": "", + "interval-label": "", + "namespace-label": "", + "namespace-required": "", + "rules-description": "", + "rules-label": "", + "save": "", + "update-error": "", + "update-success": "" + }, + "group-not-editable": "", + "group-not-editable-description": "", + "page-title": "", + "rule-group-error": "", + "title": "" + }, "irm-integration": { "connection-method": "", "disabled-description": "", @@ -600,6 +744,13 @@ } } }, + "rule-group-action": { + "details": "", + "edit": "", + "export-rules-folder": "", + "go-to-folder": "", + "manage-permissions": "" + }, "rule-groups": { "delete": { "success": "" @@ -767,6 +918,9 @@ "actions": { "button-to-recently-deleted": "" }, + "browse-view": { + "this-folder-is-empty": "" + }, "counts": { "alertRule_one": "", "alertRule_other": "", @@ -787,6 +941,9 @@ "select-checkbox": "", "tags-column": "" }, + "descendant-count": { + "title-unable-to-retrieve-descendant-information": "" + }, "empty-state": { "button-title": "", "pro-tip": "", @@ -817,6 +974,9 @@ "dashboards": "", "panels": "" }, + "name-cell": { + "no-items": "" + }, "new-folder-form": { "cancel-label": "", "create-label": "", @@ -1137,6 +1297,28 @@ } }, "edit-pane": { + "add": { + "lib-panel": { + "heading": "", + "title": "" + }, + "panel": { + "heading": "", + "title": "" + }, + "paste-panel": { + "heading": "", + "title": "" + }, + "row": { + "heading": "", + "title": "" + }, + "tab": { + "heading": "", + "title": "" + } + }, "elements": { "dashboard": "", "objects": "", @@ -1327,11 +1509,6 @@ }, "toolbar": { "add": "", - "add-panel": "", - "add-panel-description": "", - "add-panel-lib": "", - "add-row": "", - "add-tab": "", "alert-rules": "", "back-to-dashboard": "", "dashboard-settings": { @@ -1354,7 +1531,6 @@ "label": "", "tooltip": "" }, - "libray-panel-description": "", "mark-favorite": "", "more-save-options": "", "open-original": "", @@ -1363,7 +1539,6 @@ "playlist-stop": "", "public-dashboard": "", "refresh": "", - "row-description": "", "save": "", "save-dashboard": { "label": "", @@ -1383,7 +1558,6 @@ "share-button": "", "show-hidden-elements": "", "switch-old-dashboard": "", - "tabs-description": "", "unlink-library-panel": "", "unmark-favorite": "" }, @@ -1438,6 +1612,17 @@ "it-cannot-contain-or": "", "least-valid-character": "" }, + "dashboard-preview-banner": { + "not-saved": "", + "not-yet-saved": "", + "open-pull-request-in-git-hub": "", + "title-dashboard-loaded-branch-git-hub": "", + "title-dashboard-loaded-external-repository": "", + "title-dashboard-loaded-request-git-hub": "", + "title-error-loading-dashboard": "", + "value-not-saved": "", + "view-pull-request-in-git-hub": "" + }, "provisioned-resource-delete-modal": { "file-path": "", "managed-by-version-control": "", @@ -2351,9 +2536,32 @@ } }, "library-panels": { + "add-library-panel-modal": { + "title-create-library-panel": "" + }, + "confirm": { + "delete-panel": "" + }, + "delete-library-panel-modal": { + "cancel": "", + "delete": "", + "title-delete-library-panel": "" + }, "empty-state": { "message": "" }, + "folder-link": { + "dashboards": "" + }, + "has-connected-dashboards": { + "dashboard-name": "" + }, + "library-panels-search": { + "placeholder-search-by-name-or-description": "" + }, + "loading-indicator": { + "loading-library-panel": "" + }, "loading-panel-text": "", "modal": { "button-cancel": "", @@ -2366,9 +2574,20 @@ "body_one": "", "body_other": "" }, + "panel-library-options-group": { + "create-new-library-panel": "" + }, "save": { "error": "", "success": "" + }, + "save-library-panel-modal": { + "cancel": "", + "dashboard-name": "", + "discard": "", + "loading-connected-dashboards": "", + "placeholder-search-affected-dashboards": "", + "update-all": "" } }, "link": { @@ -2624,12 +2843,6 @@ "link-title": "", "title": "" }, - "public-preview": { - "button-text": "", - "message": "", - "message-cloud": "", - "title": "" - }, "resource-details": { "dismiss-button": "", "error-messages": { @@ -3116,6 +3329,27 @@ "error": "" } }, + "org": { + "new-org-page": { + "create": "", + "label-organization-name": "", + "placeholder-org-name": "" + }, + "org-profile": { + "label-organization-name": "", + "label-organization-profile": "", + "update-organization-name": "" + }, + "user-invite-form": { + "back": "", + "label-email-or-username": "", + "label-name": "", + "label-send-invite-email": "", + "placeholder-optional": "", + "role": "", + "submit": "" + } + }, "panel": { "header-menu": { "copy": "", @@ -3146,6 +3380,19 @@ "panel-type-filter": { "clear-button": "" }, + "playlist": { + "start-modal": { + "description-customize-dashboard-elements-visibility": "", + "description-panel-heights-adjusted-screen": "", + "label-autofit": "", + "label-dashboard-links": "", + "label-display-dashboard-controls": "", + "label-mode": "", + "label-time-and-refresh": "", + "label-variables": "", + "title-start-playlist": "" + } + }, "playlist-edit": { "error-prefix": "", "form": { @@ -3967,11 +4214,37 @@ "placeholder": "" }, "teams": { + "create-team": { + "create": "", + "label-name": "", + "label-role": "" + }, "empty-state": { "button-title": "", "message": "", "pro-tip": "", "title": "" + }, + "team-group-sync": { + "add-group": "", + "external-group-id": "", + "external-group-sync": "", + "title-there-external-groups": "" + }, + "team-list": { + "columns": { + "title-edit-team": "" + }, + "new-team": "", + "placeholder-search-teams": "" + }, + "team-settings": { + "label-email": "", + "label-name": "", + "label-numerical-identifier": "", + "label-role": "", + "label-team-details": "", + "save": "" } }, "theme-preview": { diff --git a/public/locales/id-ID/grafana.json b/public/locales/id-ID/grafana.json index 553b8a59659..6afeeafec1f 100644 --- a/public/locales/id-ID/grafana.json +++ b/public/locales/id-ID/grafana.json @@ -35,15 +35,57 @@ } }, "admin": { + "add-to-org-modal": { + "label-organization": "", + "label-role": "", + "title-add-to-an-organization": "" + }, + "admin-edit-org-page": { + "label-name": "", + "render-missing-permission-message": { + "title-access-denied": "" + } + }, + "admin-feature-toggles-table": { + "get-stage-cell": { + "beta": "", + "deprecated": "", + "ga": "" + }, + "title-apply-feature-toggle-changes": "" + }, + "admin-orgs-table": { + "aria-label-delete-org": "", + "title-delete": "" + }, "anon-users": { "not-found": "" }, + "base-user-sessions": { + "title-force-logout-from-all-devices": "" + }, "edit-org": { "access-denied": "", "heading": "", "update-button": "", "users-heading": "" }, + "feature-listing": { + "title-auditing": "", + "title-dashboard-presence-indicators": "", + "title-dashboard-usage-stats-drawer": "", + "title-data-source-permissions": "", + "title-enhanced-ldap-integration": "", + "title-enterprise-plugins": "", + "title-find-unused-dashboards": "", + "title-grafana-usage-insights": "", + "title-reporting": "", + "title-saml-authentication": "", + "title-settings-updates-at-runtime": "", + "title-sort-dashboards-by-popularity-in-search": "", + "title-team-sync": "", + "title-white-labeling": "" + }, "feature-toggles": { "sub-title": "" }, @@ -61,11 +103,23 @@ "test-mapping-heading": "", "test-mapping-run-button": "" }, + "ldap-error-box": { + "title-connection-error": "" + }, + "ldap-page": { + "label-username": "" + }, "ldap-permissions": { "active": "", "admin": "", "inactive": "" }, + "ldap-settings-page": { + "label-reset-to-default-values": "", + "title-invalid-configuration": "", + "title-more-actions": "", + "tooltip-more-actions": "" + }, "ldap-status": { "title": "" }, @@ -87,12 +141,21 @@ "ldap-user-info": { "no-team": "" }, + "org-role-picker": { + "placeholder-choose-role": "" + }, "org-uers": { "last-seen-never": "" }, "org-users": { "not-editable": "" }, + "org-users-table": { + "columns": { + "aria-label-role": "" + }, + "title-delete": "" + }, "orgs": { "delete-body": "", "id-header": "", @@ -107,12 +170,38 @@ "title": "", "users-button": "" }, + "service-info": { + "title-critical-sla-hours": "", + "title-enterprise-plugins": "", + "title-unlimited-expert-support": "" + }, "settings": { "info-description": "" }, "upgrade-info": { "title": "" }, + "user-create-page": { + "label-email": "", + "label-name": "", + "label-password": "", + "label-username": "" + }, + "user-ldap-sync-info": { + "label-ldap": "" + }, + "user-list-admin-page-un-connected": { + "placeholder-search-login-email": "" + }, + "user-list-anonymous-devices-page-un-connected": { + "placeholder-search-devices-by-ip-address": "" + }, + "user-list-page": { + "label-all-users": "", + "label-anonymous-devices": "", + "label-organization-users": "", + "label-users": "" + }, "user-orgs": { "add-button": "", "change-role-button": "", @@ -137,7 +226,14 @@ "disable-button": "", "edit-button": "", "enable-button": "", - "title": "" + "label-email": "", + "label-name": "", + "label-numerical-identifier": "", + "label-password": "", + "label-username": "", + "title": "", + "title-delete-user": "", + "title-disable-user": "" }, "user-sessions": { "browser-column": "", @@ -155,6 +251,9 @@ "create-button": "" }, "users-table": { + "columns": { + "title-edit-user": "" + }, "last-seen-never": "", "no-licensed-roles": "" } @@ -351,6 +450,12 @@ "missing-reference": "", "self-reference": "" }, + "draggable-rules-table": { + "evals-to-start-alerting": "", + "pending-period": "", + "recording": "", + "rule-name": "" + }, "export": { "subtitle": { "formats": "", @@ -374,6 +479,45 @@ "export": "", "reorder": "" }, + "group-details": { + "ds-features-error": "", + "edit": "", + "evaluations-to-fire": "", + "export": "", + "folder": "", + "group-loading-error": "", + "interval": "", + "namespace": "", + "pending-period": "", + "recording": "", + "rule-name": "" + }, + "group-edit": { + "ds-error": "", + "form": { + "delete": "", + "delete-body": "", + "delete-confirm": "", + "delete-title": "", + "folder-label": "", + "group-name-label": "", + "group-name-required": "", + "interval-description": "", + "interval-label": "", + "namespace-label": "", + "namespace-required": "", + "rules-description": "", + "rules-label": "", + "save": "", + "update-error": "", + "update-success": "" + }, + "group-not-editable": "", + "group-not-editable-description": "", + "page-title": "", + "rule-group-error": "", + "title": "" + }, "irm-integration": { "connection-method": "", "disabled-description": "", @@ -596,6 +740,13 @@ } } }, + "rule-group-action": { + "details": "", + "edit": "", + "export-rules-folder": "", + "go-to-folder": "", + "manage-permissions": "" + }, "rule-groups": { "delete": { "success": "" @@ -763,6 +914,9 @@ "actions": { "button-to-recently-deleted": "" }, + "browse-view": { + "this-folder-is-empty": "" + }, "counts": { "alertRule_other": "", "dashboard_other": "", @@ -778,6 +932,9 @@ "select-checkbox": "", "tags-column": "" }, + "descendant-count": { + "title-unable-to-retrieve-descendant-information": "" + }, "empty-state": { "button-title": "", "pro-tip": "", @@ -808,6 +965,9 @@ "dashboards": "", "panels": "" }, + "name-cell": { + "no-items": "" + }, "new-folder-form": { "cancel-label": "", "create-label": "", @@ -1128,6 +1288,28 @@ } }, "edit-pane": { + "add": { + "lib-panel": { + "heading": "", + "title": "" + }, + "panel": { + "heading": "", + "title": "" + }, + "paste-panel": { + "heading": "", + "title": "" + }, + "row": { + "heading": "", + "title": "" + }, + "tab": { + "heading": "", + "title": "" + } + }, "elements": { "dashboard": "", "objects": "", @@ -1318,11 +1500,6 @@ }, "toolbar": { "add": "", - "add-panel": "", - "add-panel-description": "", - "add-panel-lib": "", - "add-row": "", - "add-tab": "", "alert-rules": "", "back-to-dashboard": "", "dashboard-settings": { @@ -1345,7 +1522,6 @@ "label": "", "tooltip": "" }, - "libray-panel-description": "", "mark-favorite": "", "more-save-options": "", "open-original": "", @@ -1354,7 +1530,6 @@ "playlist-stop": "", "public-dashboard": "", "refresh": "", - "row-description": "", "save": "", "save-dashboard": { "label": "", @@ -1374,7 +1549,6 @@ "share-button": "", "show-hidden-elements": "", "switch-old-dashboard": "", - "tabs-description": "", "unlink-library-panel": "", "unmark-favorite": "" }, @@ -1429,6 +1603,17 @@ "it-cannot-contain-or": "", "least-valid-character": "" }, + "dashboard-preview-banner": { + "not-saved": "", + "not-yet-saved": "", + "open-pull-request-in-git-hub": "", + "title-dashboard-loaded-branch-git-hub": "", + "title-dashboard-loaded-external-repository": "", + "title-dashboard-loaded-request-git-hub": "", + "title-error-loading-dashboard": "", + "value-not-saved": "", + "view-pull-request-in-git-hub": "" + }, "provisioned-resource-delete-modal": { "file-path": "", "managed-by-version-control": "", @@ -2342,9 +2527,32 @@ } }, "library-panels": { + "add-library-panel-modal": { + "title-create-library-panel": "" + }, + "confirm": { + "delete-panel": "" + }, + "delete-library-panel-modal": { + "cancel": "", + "delete": "", + "title-delete-library-panel": "" + }, "empty-state": { "message": "" }, + "folder-link": { + "dashboards": "" + }, + "has-connected-dashboards": { + "dashboard-name": "" + }, + "library-panels-search": { + "placeholder-search-by-name-or-description": "" + }, + "loading-indicator": { + "loading-library-panel": "" + }, "loading-panel-text": "", "modal": { "button-cancel": "", @@ -2356,9 +2564,20 @@ "title": "", "body_other": "" }, + "panel-library-options-group": { + "create-new-library-panel": "" + }, "save": { "error": "", "success": "" + }, + "save-library-panel-modal": { + "cancel": "", + "dashboard-name": "", + "discard": "", + "loading-connected-dashboards": "", + "placeholder-search-affected-dashboards": "", + "update-all": "" } }, "link": { @@ -2614,12 +2833,6 @@ "link-title": "", "title": "" }, - "public-preview": { - "button-text": "", - "message": "", - "message-cloud": "", - "title": "" - }, "resource-details": { "dismiss-button": "", "error-messages": { @@ -3106,6 +3319,27 @@ "error": "" } }, + "org": { + "new-org-page": { + "create": "", + "label-organization-name": "", + "placeholder-org-name": "" + }, + "org-profile": { + "label-organization-name": "", + "label-organization-profile": "", + "update-organization-name": "" + }, + "user-invite-form": { + "back": "", + "label-email-or-username": "", + "label-name": "", + "label-send-invite-email": "", + "placeholder-optional": "", + "role": "", + "submit": "" + } + }, "panel": { "header-menu": { "copy": "", @@ -3136,6 +3370,19 @@ "panel-type-filter": { "clear-button": "" }, + "playlist": { + "start-modal": { + "description-customize-dashboard-elements-visibility": "", + "description-panel-heights-adjusted-screen": "", + "label-autofit": "", + "label-dashboard-links": "", + "label-display-dashboard-controls": "", + "label-mode": "", + "label-time-and-refresh": "", + "label-variables": "", + "title-start-playlist": "" + } + }, "playlist-edit": { "error-prefix": "", "form": { @@ -3953,11 +4200,37 @@ "placeholder": "" }, "teams": { + "create-team": { + "create": "", + "label-name": "", + "label-role": "" + }, "empty-state": { "button-title": "", "message": "", "pro-tip": "", "title": "" + }, + "team-group-sync": { + "add-group": "", + "external-group-id": "", + "external-group-sync": "", + "title-there-external-groups": "" + }, + "team-list": { + "columns": { + "title-edit-team": "" + }, + "new-team": "", + "placeholder-search-teams": "" + }, + "team-settings": { + "label-email": "", + "label-name": "", + "label-numerical-identifier": "", + "label-role": "", + "label-team-details": "", + "save": "" } }, "theme-preview": { diff --git a/public/locales/it-IT/grafana.json b/public/locales/it-IT/grafana.json index 0b79be81e58..f7dea7cfce7 100644 --- a/public/locales/it-IT/grafana.json +++ b/public/locales/it-IT/grafana.json @@ -35,15 +35,57 @@ } }, "admin": { + "add-to-org-modal": { + "label-organization": "", + "label-role": "", + "title-add-to-an-organization": "" + }, + "admin-edit-org-page": { + "label-name": "", + "render-missing-permission-message": { + "title-access-denied": "" + } + }, + "admin-feature-toggles-table": { + "get-stage-cell": { + "beta": "", + "deprecated": "", + "ga": "" + }, + "title-apply-feature-toggle-changes": "" + }, + "admin-orgs-table": { + "aria-label-delete-org": "", + "title-delete": "" + }, "anon-users": { "not-found": "" }, + "base-user-sessions": { + "title-force-logout-from-all-devices": "" + }, "edit-org": { "access-denied": "", "heading": "", "update-button": "", "users-heading": "" }, + "feature-listing": { + "title-auditing": "", + "title-dashboard-presence-indicators": "", + "title-dashboard-usage-stats-drawer": "", + "title-data-source-permissions": "", + "title-enhanced-ldap-integration": "", + "title-enterprise-plugins": "", + "title-find-unused-dashboards": "", + "title-grafana-usage-insights": "", + "title-reporting": "", + "title-saml-authentication": "", + "title-settings-updates-at-runtime": "", + "title-sort-dashboards-by-popularity-in-search": "", + "title-team-sync": "", + "title-white-labeling": "" + }, "feature-toggles": { "sub-title": "" }, @@ -61,11 +103,23 @@ "test-mapping-heading": "", "test-mapping-run-button": "" }, + "ldap-error-box": { + "title-connection-error": "" + }, + "ldap-page": { + "label-username": "" + }, "ldap-permissions": { "active": "", "admin": "", "inactive": "" }, + "ldap-settings-page": { + "label-reset-to-default-values": "", + "title-invalid-configuration": "", + "title-more-actions": "", + "tooltip-more-actions": "" + }, "ldap-status": { "title": "" }, @@ -87,12 +141,21 @@ "ldap-user-info": { "no-team": "" }, + "org-role-picker": { + "placeholder-choose-role": "" + }, "org-uers": { "last-seen-never": "" }, "org-users": { "not-editable": "" }, + "org-users-table": { + "columns": { + "aria-label-role": "" + }, + "title-delete": "" + }, "orgs": { "delete-body": "", "id-header": "", @@ -107,12 +170,38 @@ "title": "", "users-button": "" }, + "service-info": { + "title-critical-sla-hours": "", + "title-enterprise-plugins": "", + "title-unlimited-expert-support": "" + }, "settings": { "info-description": "" }, "upgrade-info": { "title": "" }, + "user-create-page": { + "label-email": "", + "label-name": "", + "label-password": "", + "label-username": "" + }, + "user-ldap-sync-info": { + "label-ldap": "" + }, + "user-list-admin-page-un-connected": { + "placeholder-search-login-email": "" + }, + "user-list-anonymous-devices-page-un-connected": { + "placeholder-search-devices-by-ip-address": "" + }, + "user-list-page": { + "label-all-users": "", + "label-anonymous-devices": "", + "label-organization-users": "", + "label-users": "" + }, "user-orgs": { "add-button": "", "change-role-button": "", @@ -137,7 +226,14 @@ "disable-button": "", "edit-button": "", "enable-button": "", - "title": "" + "label-email": "", + "label-name": "", + "label-numerical-identifier": "", + "label-password": "", + "label-username": "", + "title": "", + "title-delete-user": "", + "title-disable-user": "" }, "user-sessions": { "browser-column": "", @@ -155,6 +251,9 @@ "create-button": "" }, "users-table": { + "columns": { + "title-edit-user": "" + }, "last-seen-never": "", "no-licensed-roles": "" } @@ -353,6 +452,12 @@ "missing-reference": "", "self-reference": "" }, + "draggable-rules-table": { + "evals-to-start-alerting": "", + "pending-period": "", + "recording": "", + "rule-name": "" + }, "export": { "subtitle": { "formats": "", @@ -376,6 +481,45 @@ "export": "", "reorder": "" }, + "group-details": { + "ds-features-error": "", + "edit": "", + "evaluations-to-fire": "", + "export": "", + "folder": "", + "group-loading-error": "", + "interval": "", + "namespace": "", + "pending-period": "", + "recording": "", + "rule-name": "" + }, + "group-edit": { + "ds-error": "", + "form": { + "delete": "", + "delete-body": "", + "delete-confirm": "", + "delete-title": "", + "folder-label": "", + "group-name-label": "", + "group-name-required": "", + "interval-description": "", + "interval-label": "", + "namespace-label": "", + "namespace-required": "", + "rules-description": "", + "rules-label": "", + "save": "", + "update-error": "", + "update-success": "" + }, + "group-not-editable": "", + "group-not-editable-description": "", + "page-title": "", + "rule-group-error": "", + "title": "" + }, "irm-integration": { "connection-method": "", "disabled-description": "", @@ -600,6 +744,13 @@ } } }, + "rule-group-action": { + "details": "", + "edit": "", + "export-rules-folder": "", + "go-to-folder": "", + "manage-permissions": "" + }, "rule-groups": { "delete": { "success": "" @@ -767,6 +918,9 @@ "actions": { "button-to-recently-deleted": "" }, + "browse-view": { + "this-folder-is-empty": "" + }, "counts": { "alertRule_one": "", "alertRule_other": "", @@ -787,6 +941,9 @@ "select-checkbox": "", "tags-column": "" }, + "descendant-count": { + "title-unable-to-retrieve-descendant-information": "" + }, "empty-state": { "button-title": "", "pro-tip": "", @@ -817,6 +974,9 @@ "dashboards": "", "panels": "" }, + "name-cell": { + "no-items": "" + }, "new-folder-form": { "cancel-label": "", "create-label": "", @@ -1137,6 +1297,28 @@ } }, "edit-pane": { + "add": { + "lib-panel": { + "heading": "", + "title": "" + }, + "panel": { + "heading": "", + "title": "" + }, + "paste-panel": { + "heading": "", + "title": "" + }, + "row": { + "heading": "", + "title": "" + }, + "tab": { + "heading": "", + "title": "" + } + }, "elements": { "dashboard": "", "objects": "", @@ -1327,11 +1509,6 @@ }, "toolbar": { "add": "", - "add-panel": "", - "add-panel-description": "", - "add-panel-lib": "", - "add-row": "", - "add-tab": "", "alert-rules": "", "back-to-dashboard": "", "dashboard-settings": { @@ -1354,7 +1531,6 @@ "label": "", "tooltip": "" }, - "libray-panel-description": "", "mark-favorite": "", "more-save-options": "", "open-original": "", @@ -1363,7 +1539,6 @@ "playlist-stop": "", "public-dashboard": "", "refresh": "", - "row-description": "", "save": "", "save-dashboard": { "label": "", @@ -1383,7 +1558,6 @@ "share-button": "", "show-hidden-elements": "", "switch-old-dashboard": "", - "tabs-description": "", "unlink-library-panel": "", "unmark-favorite": "" }, @@ -1438,6 +1612,17 @@ "it-cannot-contain-or": "", "least-valid-character": "" }, + "dashboard-preview-banner": { + "not-saved": "", + "not-yet-saved": "", + "open-pull-request-in-git-hub": "", + "title-dashboard-loaded-branch-git-hub": "", + "title-dashboard-loaded-external-repository": "", + "title-dashboard-loaded-request-git-hub": "", + "title-error-loading-dashboard": "", + "value-not-saved": "", + "view-pull-request-in-git-hub": "" + }, "provisioned-resource-delete-modal": { "file-path": "", "managed-by-version-control": "", @@ -2351,9 +2536,32 @@ } }, "library-panels": { + "add-library-panel-modal": { + "title-create-library-panel": "" + }, + "confirm": { + "delete-panel": "" + }, + "delete-library-panel-modal": { + "cancel": "", + "delete": "", + "title-delete-library-panel": "" + }, "empty-state": { "message": "" }, + "folder-link": { + "dashboards": "" + }, + "has-connected-dashboards": { + "dashboard-name": "" + }, + "library-panels-search": { + "placeholder-search-by-name-or-description": "" + }, + "loading-indicator": { + "loading-library-panel": "" + }, "loading-panel-text": "", "modal": { "button-cancel": "", @@ -2366,9 +2574,20 @@ "body_one": "", "body_other": "" }, + "panel-library-options-group": { + "create-new-library-panel": "" + }, "save": { "error": "", "success": "" + }, + "save-library-panel-modal": { + "cancel": "", + "dashboard-name": "", + "discard": "", + "loading-connected-dashboards": "", + "placeholder-search-affected-dashboards": "", + "update-all": "" } }, "link": { @@ -2624,12 +2843,6 @@ "link-title": "", "title": "" }, - "public-preview": { - "button-text": "", - "message": "", - "message-cloud": "", - "title": "" - }, "resource-details": { "dismiss-button": "", "error-messages": { @@ -3116,6 +3329,27 @@ "error": "" } }, + "org": { + "new-org-page": { + "create": "", + "label-organization-name": "", + "placeholder-org-name": "" + }, + "org-profile": { + "label-organization-name": "", + "label-organization-profile": "", + "update-organization-name": "" + }, + "user-invite-form": { + "back": "", + "label-email-or-username": "", + "label-name": "", + "label-send-invite-email": "", + "placeholder-optional": "", + "role": "", + "submit": "" + } + }, "panel": { "header-menu": { "copy": "", @@ -3146,6 +3380,19 @@ "panel-type-filter": { "clear-button": "" }, + "playlist": { + "start-modal": { + "description-customize-dashboard-elements-visibility": "", + "description-panel-heights-adjusted-screen": "", + "label-autofit": "", + "label-dashboard-links": "", + "label-display-dashboard-controls": "", + "label-mode": "", + "label-time-and-refresh": "", + "label-variables": "", + "title-start-playlist": "" + } + }, "playlist-edit": { "error-prefix": "", "form": { @@ -3967,11 +4214,37 @@ "placeholder": "" }, "teams": { + "create-team": { + "create": "", + "label-name": "", + "label-role": "" + }, "empty-state": { "button-title": "", "message": "", "pro-tip": "", "title": "" + }, + "team-group-sync": { + "add-group": "", + "external-group-id": "", + "external-group-sync": "", + "title-there-external-groups": "" + }, + "team-list": { + "columns": { + "title-edit-team": "" + }, + "new-team": "", + "placeholder-search-teams": "" + }, + "team-settings": { + "label-email": "", + "label-name": "", + "label-numerical-identifier": "", + "label-role": "", + "label-team-details": "", + "save": "" } }, "theme-preview": { diff --git a/public/locales/ja-JP/grafana.json b/public/locales/ja-JP/grafana.json index 553b8a59659..6afeeafec1f 100644 --- a/public/locales/ja-JP/grafana.json +++ b/public/locales/ja-JP/grafana.json @@ -35,15 +35,57 @@ } }, "admin": { + "add-to-org-modal": { + "label-organization": "", + "label-role": "", + "title-add-to-an-organization": "" + }, + "admin-edit-org-page": { + "label-name": "", + "render-missing-permission-message": { + "title-access-denied": "" + } + }, + "admin-feature-toggles-table": { + "get-stage-cell": { + "beta": "", + "deprecated": "", + "ga": "" + }, + "title-apply-feature-toggle-changes": "" + }, + "admin-orgs-table": { + "aria-label-delete-org": "", + "title-delete": "" + }, "anon-users": { "not-found": "" }, + "base-user-sessions": { + "title-force-logout-from-all-devices": "" + }, "edit-org": { "access-denied": "", "heading": "", "update-button": "", "users-heading": "" }, + "feature-listing": { + "title-auditing": "", + "title-dashboard-presence-indicators": "", + "title-dashboard-usage-stats-drawer": "", + "title-data-source-permissions": "", + "title-enhanced-ldap-integration": "", + "title-enterprise-plugins": "", + "title-find-unused-dashboards": "", + "title-grafana-usage-insights": "", + "title-reporting": "", + "title-saml-authentication": "", + "title-settings-updates-at-runtime": "", + "title-sort-dashboards-by-popularity-in-search": "", + "title-team-sync": "", + "title-white-labeling": "" + }, "feature-toggles": { "sub-title": "" }, @@ -61,11 +103,23 @@ "test-mapping-heading": "", "test-mapping-run-button": "" }, + "ldap-error-box": { + "title-connection-error": "" + }, + "ldap-page": { + "label-username": "" + }, "ldap-permissions": { "active": "", "admin": "", "inactive": "" }, + "ldap-settings-page": { + "label-reset-to-default-values": "", + "title-invalid-configuration": "", + "title-more-actions": "", + "tooltip-more-actions": "" + }, "ldap-status": { "title": "" }, @@ -87,12 +141,21 @@ "ldap-user-info": { "no-team": "" }, + "org-role-picker": { + "placeholder-choose-role": "" + }, "org-uers": { "last-seen-never": "" }, "org-users": { "not-editable": "" }, + "org-users-table": { + "columns": { + "aria-label-role": "" + }, + "title-delete": "" + }, "orgs": { "delete-body": "", "id-header": "", @@ -107,12 +170,38 @@ "title": "", "users-button": "" }, + "service-info": { + "title-critical-sla-hours": "", + "title-enterprise-plugins": "", + "title-unlimited-expert-support": "" + }, "settings": { "info-description": "" }, "upgrade-info": { "title": "" }, + "user-create-page": { + "label-email": "", + "label-name": "", + "label-password": "", + "label-username": "" + }, + "user-ldap-sync-info": { + "label-ldap": "" + }, + "user-list-admin-page-un-connected": { + "placeholder-search-login-email": "" + }, + "user-list-anonymous-devices-page-un-connected": { + "placeholder-search-devices-by-ip-address": "" + }, + "user-list-page": { + "label-all-users": "", + "label-anonymous-devices": "", + "label-organization-users": "", + "label-users": "" + }, "user-orgs": { "add-button": "", "change-role-button": "", @@ -137,7 +226,14 @@ "disable-button": "", "edit-button": "", "enable-button": "", - "title": "" + "label-email": "", + "label-name": "", + "label-numerical-identifier": "", + "label-password": "", + "label-username": "", + "title": "", + "title-delete-user": "", + "title-disable-user": "" }, "user-sessions": { "browser-column": "", @@ -155,6 +251,9 @@ "create-button": "" }, "users-table": { + "columns": { + "title-edit-user": "" + }, "last-seen-never": "", "no-licensed-roles": "" } @@ -351,6 +450,12 @@ "missing-reference": "", "self-reference": "" }, + "draggable-rules-table": { + "evals-to-start-alerting": "", + "pending-period": "", + "recording": "", + "rule-name": "" + }, "export": { "subtitle": { "formats": "", @@ -374,6 +479,45 @@ "export": "", "reorder": "" }, + "group-details": { + "ds-features-error": "", + "edit": "", + "evaluations-to-fire": "", + "export": "", + "folder": "", + "group-loading-error": "", + "interval": "", + "namespace": "", + "pending-period": "", + "recording": "", + "rule-name": "" + }, + "group-edit": { + "ds-error": "", + "form": { + "delete": "", + "delete-body": "", + "delete-confirm": "", + "delete-title": "", + "folder-label": "", + "group-name-label": "", + "group-name-required": "", + "interval-description": "", + "interval-label": "", + "namespace-label": "", + "namespace-required": "", + "rules-description": "", + "rules-label": "", + "save": "", + "update-error": "", + "update-success": "" + }, + "group-not-editable": "", + "group-not-editable-description": "", + "page-title": "", + "rule-group-error": "", + "title": "" + }, "irm-integration": { "connection-method": "", "disabled-description": "", @@ -596,6 +740,13 @@ } } }, + "rule-group-action": { + "details": "", + "edit": "", + "export-rules-folder": "", + "go-to-folder": "", + "manage-permissions": "" + }, "rule-groups": { "delete": { "success": "" @@ -763,6 +914,9 @@ "actions": { "button-to-recently-deleted": "" }, + "browse-view": { + "this-folder-is-empty": "" + }, "counts": { "alertRule_other": "", "dashboard_other": "", @@ -778,6 +932,9 @@ "select-checkbox": "", "tags-column": "" }, + "descendant-count": { + "title-unable-to-retrieve-descendant-information": "" + }, "empty-state": { "button-title": "", "pro-tip": "", @@ -808,6 +965,9 @@ "dashboards": "", "panels": "" }, + "name-cell": { + "no-items": "" + }, "new-folder-form": { "cancel-label": "", "create-label": "", @@ -1128,6 +1288,28 @@ } }, "edit-pane": { + "add": { + "lib-panel": { + "heading": "", + "title": "" + }, + "panel": { + "heading": "", + "title": "" + }, + "paste-panel": { + "heading": "", + "title": "" + }, + "row": { + "heading": "", + "title": "" + }, + "tab": { + "heading": "", + "title": "" + } + }, "elements": { "dashboard": "", "objects": "", @@ -1318,11 +1500,6 @@ }, "toolbar": { "add": "", - "add-panel": "", - "add-panel-description": "", - "add-panel-lib": "", - "add-row": "", - "add-tab": "", "alert-rules": "", "back-to-dashboard": "", "dashboard-settings": { @@ -1345,7 +1522,6 @@ "label": "", "tooltip": "" }, - "libray-panel-description": "", "mark-favorite": "", "more-save-options": "", "open-original": "", @@ -1354,7 +1530,6 @@ "playlist-stop": "", "public-dashboard": "", "refresh": "", - "row-description": "", "save": "", "save-dashboard": { "label": "", @@ -1374,7 +1549,6 @@ "share-button": "", "show-hidden-elements": "", "switch-old-dashboard": "", - "tabs-description": "", "unlink-library-panel": "", "unmark-favorite": "" }, @@ -1429,6 +1603,17 @@ "it-cannot-contain-or": "", "least-valid-character": "" }, + "dashboard-preview-banner": { + "not-saved": "", + "not-yet-saved": "", + "open-pull-request-in-git-hub": "", + "title-dashboard-loaded-branch-git-hub": "", + "title-dashboard-loaded-external-repository": "", + "title-dashboard-loaded-request-git-hub": "", + "title-error-loading-dashboard": "", + "value-not-saved": "", + "view-pull-request-in-git-hub": "" + }, "provisioned-resource-delete-modal": { "file-path": "", "managed-by-version-control": "", @@ -2342,9 +2527,32 @@ } }, "library-panels": { + "add-library-panel-modal": { + "title-create-library-panel": "" + }, + "confirm": { + "delete-panel": "" + }, + "delete-library-panel-modal": { + "cancel": "", + "delete": "", + "title-delete-library-panel": "" + }, "empty-state": { "message": "" }, + "folder-link": { + "dashboards": "" + }, + "has-connected-dashboards": { + "dashboard-name": "" + }, + "library-panels-search": { + "placeholder-search-by-name-or-description": "" + }, + "loading-indicator": { + "loading-library-panel": "" + }, "loading-panel-text": "", "modal": { "button-cancel": "", @@ -2356,9 +2564,20 @@ "title": "", "body_other": "" }, + "panel-library-options-group": { + "create-new-library-panel": "" + }, "save": { "error": "", "success": "" + }, + "save-library-panel-modal": { + "cancel": "", + "dashboard-name": "", + "discard": "", + "loading-connected-dashboards": "", + "placeholder-search-affected-dashboards": "", + "update-all": "" } }, "link": { @@ -2614,12 +2833,6 @@ "link-title": "", "title": "" }, - "public-preview": { - "button-text": "", - "message": "", - "message-cloud": "", - "title": "" - }, "resource-details": { "dismiss-button": "", "error-messages": { @@ -3106,6 +3319,27 @@ "error": "" } }, + "org": { + "new-org-page": { + "create": "", + "label-organization-name": "", + "placeholder-org-name": "" + }, + "org-profile": { + "label-organization-name": "", + "label-organization-profile": "", + "update-organization-name": "" + }, + "user-invite-form": { + "back": "", + "label-email-or-username": "", + "label-name": "", + "label-send-invite-email": "", + "placeholder-optional": "", + "role": "", + "submit": "" + } + }, "panel": { "header-menu": { "copy": "", @@ -3136,6 +3370,19 @@ "panel-type-filter": { "clear-button": "" }, + "playlist": { + "start-modal": { + "description-customize-dashboard-elements-visibility": "", + "description-panel-heights-adjusted-screen": "", + "label-autofit": "", + "label-dashboard-links": "", + "label-display-dashboard-controls": "", + "label-mode": "", + "label-time-and-refresh": "", + "label-variables": "", + "title-start-playlist": "" + } + }, "playlist-edit": { "error-prefix": "", "form": { @@ -3953,11 +4200,37 @@ "placeholder": "" }, "teams": { + "create-team": { + "create": "", + "label-name": "", + "label-role": "" + }, "empty-state": { "button-title": "", "message": "", "pro-tip": "", "title": "" + }, + "team-group-sync": { + "add-group": "", + "external-group-id": "", + "external-group-sync": "", + "title-there-external-groups": "" + }, + "team-list": { + "columns": { + "title-edit-team": "" + }, + "new-team": "", + "placeholder-search-teams": "" + }, + "team-settings": { + "label-email": "", + "label-name": "", + "label-numerical-identifier": "", + "label-role": "", + "label-team-details": "", + "save": "" } }, "theme-preview": { diff --git a/public/locales/ko-KR/grafana.json b/public/locales/ko-KR/grafana.json index 553b8a59659..6afeeafec1f 100644 --- a/public/locales/ko-KR/grafana.json +++ b/public/locales/ko-KR/grafana.json @@ -35,15 +35,57 @@ } }, "admin": { + "add-to-org-modal": { + "label-organization": "", + "label-role": "", + "title-add-to-an-organization": "" + }, + "admin-edit-org-page": { + "label-name": "", + "render-missing-permission-message": { + "title-access-denied": "" + } + }, + "admin-feature-toggles-table": { + "get-stage-cell": { + "beta": "", + "deprecated": "", + "ga": "" + }, + "title-apply-feature-toggle-changes": "" + }, + "admin-orgs-table": { + "aria-label-delete-org": "", + "title-delete": "" + }, "anon-users": { "not-found": "" }, + "base-user-sessions": { + "title-force-logout-from-all-devices": "" + }, "edit-org": { "access-denied": "", "heading": "", "update-button": "", "users-heading": "" }, + "feature-listing": { + "title-auditing": "", + "title-dashboard-presence-indicators": "", + "title-dashboard-usage-stats-drawer": "", + "title-data-source-permissions": "", + "title-enhanced-ldap-integration": "", + "title-enterprise-plugins": "", + "title-find-unused-dashboards": "", + "title-grafana-usage-insights": "", + "title-reporting": "", + "title-saml-authentication": "", + "title-settings-updates-at-runtime": "", + "title-sort-dashboards-by-popularity-in-search": "", + "title-team-sync": "", + "title-white-labeling": "" + }, "feature-toggles": { "sub-title": "" }, @@ -61,11 +103,23 @@ "test-mapping-heading": "", "test-mapping-run-button": "" }, + "ldap-error-box": { + "title-connection-error": "" + }, + "ldap-page": { + "label-username": "" + }, "ldap-permissions": { "active": "", "admin": "", "inactive": "" }, + "ldap-settings-page": { + "label-reset-to-default-values": "", + "title-invalid-configuration": "", + "title-more-actions": "", + "tooltip-more-actions": "" + }, "ldap-status": { "title": "" }, @@ -87,12 +141,21 @@ "ldap-user-info": { "no-team": "" }, + "org-role-picker": { + "placeholder-choose-role": "" + }, "org-uers": { "last-seen-never": "" }, "org-users": { "not-editable": "" }, + "org-users-table": { + "columns": { + "aria-label-role": "" + }, + "title-delete": "" + }, "orgs": { "delete-body": "", "id-header": "", @@ -107,12 +170,38 @@ "title": "", "users-button": "" }, + "service-info": { + "title-critical-sla-hours": "", + "title-enterprise-plugins": "", + "title-unlimited-expert-support": "" + }, "settings": { "info-description": "" }, "upgrade-info": { "title": "" }, + "user-create-page": { + "label-email": "", + "label-name": "", + "label-password": "", + "label-username": "" + }, + "user-ldap-sync-info": { + "label-ldap": "" + }, + "user-list-admin-page-un-connected": { + "placeholder-search-login-email": "" + }, + "user-list-anonymous-devices-page-un-connected": { + "placeholder-search-devices-by-ip-address": "" + }, + "user-list-page": { + "label-all-users": "", + "label-anonymous-devices": "", + "label-organization-users": "", + "label-users": "" + }, "user-orgs": { "add-button": "", "change-role-button": "", @@ -137,7 +226,14 @@ "disable-button": "", "edit-button": "", "enable-button": "", - "title": "" + "label-email": "", + "label-name": "", + "label-numerical-identifier": "", + "label-password": "", + "label-username": "", + "title": "", + "title-delete-user": "", + "title-disable-user": "" }, "user-sessions": { "browser-column": "", @@ -155,6 +251,9 @@ "create-button": "" }, "users-table": { + "columns": { + "title-edit-user": "" + }, "last-seen-never": "", "no-licensed-roles": "" } @@ -351,6 +450,12 @@ "missing-reference": "", "self-reference": "" }, + "draggable-rules-table": { + "evals-to-start-alerting": "", + "pending-period": "", + "recording": "", + "rule-name": "" + }, "export": { "subtitle": { "formats": "", @@ -374,6 +479,45 @@ "export": "", "reorder": "" }, + "group-details": { + "ds-features-error": "", + "edit": "", + "evaluations-to-fire": "", + "export": "", + "folder": "", + "group-loading-error": "", + "interval": "", + "namespace": "", + "pending-period": "", + "recording": "", + "rule-name": "" + }, + "group-edit": { + "ds-error": "", + "form": { + "delete": "", + "delete-body": "", + "delete-confirm": "", + "delete-title": "", + "folder-label": "", + "group-name-label": "", + "group-name-required": "", + "interval-description": "", + "interval-label": "", + "namespace-label": "", + "namespace-required": "", + "rules-description": "", + "rules-label": "", + "save": "", + "update-error": "", + "update-success": "" + }, + "group-not-editable": "", + "group-not-editable-description": "", + "page-title": "", + "rule-group-error": "", + "title": "" + }, "irm-integration": { "connection-method": "", "disabled-description": "", @@ -596,6 +740,13 @@ } } }, + "rule-group-action": { + "details": "", + "edit": "", + "export-rules-folder": "", + "go-to-folder": "", + "manage-permissions": "" + }, "rule-groups": { "delete": { "success": "" @@ -763,6 +914,9 @@ "actions": { "button-to-recently-deleted": "" }, + "browse-view": { + "this-folder-is-empty": "" + }, "counts": { "alertRule_other": "", "dashboard_other": "", @@ -778,6 +932,9 @@ "select-checkbox": "", "tags-column": "" }, + "descendant-count": { + "title-unable-to-retrieve-descendant-information": "" + }, "empty-state": { "button-title": "", "pro-tip": "", @@ -808,6 +965,9 @@ "dashboards": "", "panels": "" }, + "name-cell": { + "no-items": "" + }, "new-folder-form": { "cancel-label": "", "create-label": "", @@ -1128,6 +1288,28 @@ } }, "edit-pane": { + "add": { + "lib-panel": { + "heading": "", + "title": "" + }, + "panel": { + "heading": "", + "title": "" + }, + "paste-panel": { + "heading": "", + "title": "" + }, + "row": { + "heading": "", + "title": "" + }, + "tab": { + "heading": "", + "title": "" + } + }, "elements": { "dashboard": "", "objects": "", @@ -1318,11 +1500,6 @@ }, "toolbar": { "add": "", - "add-panel": "", - "add-panel-description": "", - "add-panel-lib": "", - "add-row": "", - "add-tab": "", "alert-rules": "", "back-to-dashboard": "", "dashboard-settings": { @@ -1345,7 +1522,6 @@ "label": "", "tooltip": "" }, - "libray-panel-description": "", "mark-favorite": "", "more-save-options": "", "open-original": "", @@ -1354,7 +1530,6 @@ "playlist-stop": "", "public-dashboard": "", "refresh": "", - "row-description": "", "save": "", "save-dashboard": { "label": "", @@ -1374,7 +1549,6 @@ "share-button": "", "show-hidden-elements": "", "switch-old-dashboard": "", - "tabs-description": "", "unlink-library-panel": "", "unmark-favorite": "" }, @@ -1429,6 +1603,17 @@ "it-cannot-contain-or": "", "least-valid-character": "" }, + "dashboard-preview-banner": { + "not-saved": "", + "not-yet-saved": "", + "open-pull-request-in-git-hub": "", + "title-dashboard-loaded-branch-git-hub": "", + "title-dashboard-loaded-external-repository": "", + "title-dashboard-loaded-request-git-hub": "", + "title-error-loading-dashboard": "", + "value-not-saved": "", + "view-pull-request-in-git-hub": "" + }, "provisioned-resource-delete-modal": { "file-path": "", "managed-by-version-control": "", @@ -2342,9 +2527,32 @@ } }, "library-panels": { + "add-library-panel-modal": { + "title-create-library-panel": "" + }, + "confirm": { + "delete-panel": "" + }, + "delete-library-panel-modal": { + "cancel": "", + "delete": "", + "title-delete-library-panel": "" + }, "empty-state": { "message": "" }, + "folder-link": { + "dashboards": "" + }, + "has-connected-dashboards": { + "dashboard-name": "" + }, + "library-panels-search": { + "placeholder-search-by-name-or-description": "" + }, + "loading-indicator": { + "loading-library-panel": "" + }, "loading-panel-text": "", "modal": { "button-cancel": "", @@ -2356,9 +2564,20 @@ "title": "", "body_other": "" }, + "panel-library-options-group": { + "create-new-library-panel": "" + }, "save": { "error": "", "success": "" + }, + "save-library-panel-modal": { + "cancel": "", + "dashboard-name": "", + "discard": "", + "loading-connected-dashboards": "", + "placeholder-search-affected-dashboards": "", + "update-all": "" } }, "link": { @@ -2614,12 +2833,6 @@ "link-title": "", "title": "" }, - "public-preview": { - "button-text": "", - "message": "", - "message-cloud": "", - "title": "" - }, "resource-details": { "dismiss-button": "", "error-messages": { @@ -3106,6 +3319,27 @@ "error": "" } }, + "org": { + "new-org-page": { + "create": "", + "label-organization-name": "", + "placeholder-org-name": "" + }, + "org-profile": { + "label-organization-name": "", + "label-organization-profile": "", + "update-organization-name": "" + }, + "user-invite-form": { + "back": "", + "label-email-or-username": "", + "label-name": "", + "label-send-invite-email": "", + "placeholder-optional": "", + "role": "", + "submit": "" + } + }, "panel": { "header-menu": { "copy": "", @@ -3136,6 +3370,19 @@ "panel-type-filter": { "clear-button": "" }, + "playlist": { + "start-modal": { + "description-customize-dashboard-elements-visibility": "", + "description-panel-heights-adjusted-screen": "", + "label-autofit": "", + "label-dashboard-links": "", + "label-display-dashboard-controls": "", + "label-mode": "", + "label-time-and-refresh": "", + "label-variables": "", + "title-start-playlist": "" + } + }, "playlist-edit": { "error-prefix": "", "form": { @@ -3953,11 +4200,37 @@ "placeholder": "" }, "teams": { + "create-team": { + "create": "", + "label-name": "", + "label-role": "" + }, "empty-state": { "button-title": "", "message": "", "pro-tip": "", "title": "" + }, + "team-group-sync": { + "add-group": "", + "external-group-id": "", + "external-group-sync": "", + "title-there-external-groups": "" + }, + "team-list": { + "columns": { + "title-edit-team": "" + }, + "new-team": "", + "placeholder-search-teams": "" + }, + "team-settings": { + "label-email": "", + "label-name": "", + "label-numerical-identifier": "", + "label-role": "", + "label-team-details": "", + "save": "" } }, "theme-preview": { diff --git a/public/locales/nl-NL/grafana.json b/public/locales/nl-NL/grafana.json index 0b79be81e58..f7dea7cfce7 100644 --- a/public/locales/nl-NL/grafana.json +++ b/public/locales/nl-NL/grafana.json @@ -35,15 +35,57 @@ } }, "admin": { + "add-to-org-modal": { + "label-organization": "", + "label-role": "", + "title-add-to-an-organization": "" + }, + "admin-edit-org-page": { + "label-name": "", + "render-missing-permission-message": { + "title-access-denied": "" + } + }, + "admin-feature-toggles-table": { + "get-stage-cell": { + "beta": "", + "deprecated": "", + "ga": "" + }, + "title-apply-feature-toggle-changes": "" + }, + "admin-orgs-table": { + "aria-label-delete-org": "", + "title-delete": "" + }, "anon-users": { "not-found": "" }, + "base-user-sessions": { + "title-force-logout-from-all-devices": "" + }, "edit-org": { "access-denied": "", "heading": "", "update-button": "", "users-heading": "" }, + "feature-listing": { + "title-auditing": "", + "title-dashboard-presence-indicators": "", + "title-dashboard-usage-stats-drawer": "", + "title-data-source-permissions": "", + "title-enhanced-ldap-integration": "", + "title-enterprise-plugins": "", + "title-find-unused-dashboards": "", + "title-grafana-usage-insights": "", + "title-reporting": "", + "title-saml-authentication": "", + "title-settings-updates-at-runtime": "", + "title-sort-dashboards-by-popularity-in-search": "", + "title-team-sync": "", + "title-white-labeling": "" + }, "feature-toggles": { "sub-title": "" }, @@ -61,11 +103,23 @@ "test-mapping-heading": "", "test-mapping-run-button": "" }, + "ldap-error-box": { + "title-connection-error": "" + }, + "ldap-page": { + "label-username": "" + }, "ldap-permissions": { "active": "", "admin": "", "inactive": "" }, + "ldap-settings-page": { + "label-reset-to-default-values": "", + "title-invalid-configuration": "", + "title-more-actions": "", + "tooltip-more-actions": "" + }, "ldap-status": { "title": "" }, @@ -87,12 +141,21 @@ "ldap-user-info": { "no-team": "" }, + "org-role-picker": { + "placeholder-choose-role": "" + }, "org-uers": { "last-seen-never": "" }, "org-users": { "not-editable": "" }, + "org-users-table": { + "columns": { + "aria-label-role": "" + }, + "title-delete": "" + }, "orgs": { "delete-body": "", "id-header": "", @@ -107,12 +170,38 @@ "title": "", "users-button": "" }, + "service-info": { + "title-critical-sla-hours": "", + "title-enterprise-plugins": "", + "title-unlimited-expert-support": "" + }, "settings": { "info-description": "" }, "upgrade-info": { "title": "" }, + "user-create-page": { + "label-email": "", + "label-name": "", + "label-password": "", + "label-username": "" + }, + "user-ldap-sync-info": { + "label-ldap": "" + }, + "user-list-admin-page-un-connected": { + "placeholder-search-login-email": "" + }, + "user-list-anonymous-devices-page-un-connected": { + "placeholder-search-devices-by-ip-address": "" + }, + "user-list-page": { + "label-all-users": "", + "label-anonymous-devices": "", + "label-organization-users": "", + "label-users": "" + }, "user-orgs": { "add-button": "", "change-role-button": "", @@ -137,7 +226,14 @@ "disable-button": "", "edit-button": "", "enable-button": "", - "title": "" + "label-email": "", + "label-name": "", + "label-numerical-identifier": "", + "label-password": "", + "label-username": "", + "title": "", + "title-delete-user": "", + "title-disable-user": "" }, "user-sessions": { "browser-column": "", @@ -155,6 +251,9 @@ "create-button": "" }, "users-table": { + "columns": { + "title-edit-user": "" + }, "last-seen-never": "", "no-licensed-roles": "" } @@ -353,6 +452,12 @@ "missing-reference": "", "self-reference": "" }, + "draggable-rules-table": { + "evals-to-start-alerting": "", + "pending-period": "", + "recording": "", + "rule-name": "" + }, "export": { "subtitle": { "formats": "", @@ -376,6 +481,45 @@ "export": "", "reorder": "" }, + "group-details": { + "ds-features-error": "", + "edit": "", + "evaluations-to-fire": "", + "export": "", + "folder": "", + "group-loading-error": "", + "interval": "", + "namespace": "", + "pending-period": "", + "recording": "", + "rule-name": "" + }, + "group-edit": { + "ds-error": "", + "form": { + "delete": "", + "delete-body": "", + "delete-confirm": "", + "delete-title": "", + "folder-label": "", + "group-name-label": "", + "group-name-required": "", + "interval-description": "", + "interval-label": "", + "namespace-label": "", + "namespace-required": "", + "rules-description": "", + "rules-label": "", + "save": "", + "update-error": "", + "update-success": "" + }, + "group-not-editable": "", + "group-not-editable-description": "", + "page-title": "", + "rule-group-error": "", + "title": "" + }, "irm-integration": { "connection-method": "", "disabled-description": "", @@ -600,6 +744,13 @@ } } }, + "rule-group-action": { + "details": "", + "edit": "", + "export-rules-folder": "", + "go-to-folder": "", + "manage-permissions": "" + }, "rule-groups": { "delete": { "success": "" @@ -767,6 +918,9 @@ "actions": { "button-to-recently-deleted": "" }, + "browse-view": { + "this-folder-is-empty": "" + }, "counts": { "alertRule_one": "", "alertRule_other": "", @@ -787,6 +941,9 @@ "select-checkbox": "", "tags-column": "" }, + "descendant-count": { + "title-unable-to-retrieve-descendant-information": "" + }, "empty-state": { "button-title": "", "pro-tip": "", @@ -817,6 +974,9 @@ "dashboards": "", "panels": "" }, + "name-cell": { + "no-items": "" + }, "new-folder-form": { "cancel-label": "", "create-label": "", @@ -1137,6 +1297,28 @@ } }, "edit-pane": { + "add": { + "lib-panel": { + "heading": "", + "title": "" + }, + "panel": { + "heading": "", + "title": "" + }, + "paste-panel": { + "heading": "", + "title": "" + }, + "row": { + "heading": "", + "title": "" + }, + "tab": { + "heading": "", + "title": "" + } + }, "elements": { "dashboard": "", "objects": "", @@ -1327,11 +1509,6 @@ }, "toolbar": { "add": "", - "add-panel": "", - "add-panel-description": "", - "add-panel-lib": "", - "add-row": "", - "add-tab": "", "alert-rules": "", "back-to-dashboard": "", "dashboard-settings": { @@ -1354,7 +1531,6 @@ "label": "", "tooltip": "" }, - "libray-panel-description": "", "mark-favorite": "", "more-save-options": "", "open-original": "", @@ -1363,7 +1539,6 @@ "playlist-stop": "", "public-dashboard": "", "refresh": "", - "row-description": "", "save": "", "save-dashboard": { "label": "", @@ -1383,7 +1558,6 @@ "share-button": "", "show-hidden-elements": "", "switch-old-dashboard": "", - "tabs-description": "", "unlink-library-panel": "", "unmark-favorite": "" }, @@ -1438,6 +1612,17 @@ "it-cannot-contain-or": "", "least-valid-character": "" }, + "dashboard-preview-banner": { + "not-saved": "", + "not-yet-saved": "", + "open-pull-request-in-git-hub": "", + "title-dashboard-loaded-branch-git-hub": "", + "title-dashboard-loaded-external-repository": "", + "title-dashboard-loaded-request-git-hub": "", + "title-error-loading-dashboard": "", + "value-not-saved": "", + "view-pull-request-in-git-hub": "" + }, "provisioned-resource-delete-modal": { "file-path": "", "managed-by-version-control": "", @@ -2351,9 +2536,32 @@ } }, "library-panels": { + "add-library-panel-modal": { + "title-create-library-panel": "" + }, + "confirm": { + "delete-panel": "" + }, + "delete-library-panel-modal": { + "cancel": "", + "delete": "", + "title-delete-library-panel": "" + }, "empty-state": { "message": "" }, + "folder-link": { + "dashboards": "" + }, + "has-connected-dashboards": { + "dashboard-name": "" + }, + "library-panels-search": { + "placeholder-search-by-name-or-description": "" + }, + "loading-indicator": { + "loading-library-panel": "" + }, "loading-panel-text": "", "modal": { "button-cancel": "", @@ -2366,9 +2574,20 @@ "body_one": "", "body_other": "" }, + "panel-library-options-group": { + "create-new-library-panel": "" + }, "save": { "error": "", "success": "" + }, + "save-library-panel-modal": { + "cancel": "", + "dashboard-name": "", + "discard": "", + "loading-connected-dashboards": "", + "placeholder-search-affected-dashboards": "", + "update-all": "" } }, "link": { @@ -2624,12 +2843,6 @@ "link-title": "", "title": "" }, - "public-preview": { - "button-text": "", - "message": "", - "message-cloud": "", - "title": "" - }, "resource-details": { "dismiss-button": "", "error-messages": { @@ -3116,6 +3329,27 @@ "error": "" } }, + "org": { + "new-org-page": { + "create": "", + "label-organization-name": "", + "placeholder-org-name": "" + }, + "org-profile": { + "label-organization-name": "", + "label-organization-profile": "", + "update-organization-name": "" + }, + "user-invite-form": { + "back": "", + "label-email-or-username": "", + "label-name": "", + "label-send-invite-email": "", + "placeholder-optional": "", + "role": "", + "submit": "" + } + }, "panel": { "header-menu": { "copy": "", @@ -3146,6 +3380,19 @@ "panel-type-filter": { "clear-button": "" }, + "playlist": { + "start-modal": { + "description-customize-dashboard-elements-visibility": "", + "description-panel-heights-adjusted-screen": "", + "label-autofit": "", + "label-dashboard-links": "", + "label-display-dashboard-controls": "", + "label-mode": "", + "label-time-and-refresh": "", + "label-variables": "", + "title-start-playlist": "" + } + }, "playlist-edit": { "error-prefix": "", "form": { @@ -3967,11 +4214,37 @@ "placeholder": "" }, "teams": { + "create-team": { + "create": "", + "label-name": "", + "label-role": "" + }, "empty-state": { "button-title": "", "message": "", "pro-tip": "", "title": "" + }, + "team-group-sync": { + "add-group": "", + "external-group-id": "", + "external-group-sync": "", + "title-there-external-groups": "" + }, + "team-list": { + "columns": { + "title-edit-team": "" + }, + "new-team": "", + "placeholder-search-teams": "" + }, + "team-settings": { + "label-email": "", + "label-name": "", + "label-numerical-identifier": "", + "label-role": "", + "label-team-details": "", + "save": "" } }, "theme-preview": { diff --git a/public/locales/pl-PL/grafana.json b/public/locales/pl-PL/grafana.json index 5a982333efe..01f18df8845 100644 --- a/public/locales/pl-PL/grafana.json +++ b/public/locales/pl-PL/grafana.json @@ -35,15 +35,57 @@ } }, "admin": { + "add-to-org-modal": { + "label-organization": "", + "label-role": "", + "title-add-to-an-organization": "" + }, + "admin-edit-org-page": { + "label-name": "", + "render-missing-permission-message": { + "title-access-denied": "" + } + }, + "admin-feature-toggles-table": { + "get-stage-cell": { + "beta": "", + "deprecated": "", + "ga": "" + }, + "title-apply-feature-toggle-changes": "" + }, + "admin-orgs-table": { + "aria-label-delete-org": "", + "title-delete": "" + }, "anon-users": { "not-found": "" }, + "base-user-sessions": { + "title-force-logout-from-all-devices": "" + }, "edit-org": { "access-denied": "", "heading": "", "update-button": "", "users-heading": "" }, + "feature-listing": { + "title-auditing": "", + "title-dashboard-presence-indicators": "", + "title-dashboard-usage-stats-drawer": "", + "title-data-source-permissions": "", + "title-enhanced-ldap-integration": "", + "title-enterprise-plugins": "", + "title-find-unused-dashboards": "", + "title-grafana-usage-insights": "", + "title-reporting": "", + "title-saml-authentication": "", + "title-settings-updates-at-runtime": "", + "title-sort-dashboards-by-popularity-in-search": "", + "title-team-sync": "", + "title-white-labeling": "" + }, "feature-toggles": { "sub-title": "" }, @@ -61,11 +103,23 @@ "test-mapping-heading": "", "test-mapping-run-button": "" }, + "ldap-error-box": { + "title-connection-error": "" + }, + "ldap-page": { + "label-username": "" + }, "ldap-permissions": { "active": "", "admin": "", "inactive": "" }, + "ldap-settings-page": { + "label-reset-to-default-values": "", + "title-invalid-configuration": "", + "title-more-actions": "", + "tooltip-more-actions": "" + }, "ldap-status": { "title": "" }, @@ -87,12 +141,21 @@ "ldap-user-info": { "no-team": "" }, + "org-role-picker": { + "placeholder-choose-role": "" + }, "org-uers": { "last-seen-never": "" }, "org-users": { "not-editable": "" }, + "org-users-table": { + "columns": { + "aria-label-role": "" + }, + "title-delete": "" + }, "orgs": { "delete-body": "", "id-header": "", @@ -107,12 +170,38 @@ "title": "", "users-button": "" }, + "service-info": { + "title-critical-sla-hours": "", + "title-enterprise-plugins": "", + "title-unlimited-expert-support": "" + }, "settings": { "info-description": "" }, "upgrade-info": { "title": "" }, + "user-create-page": { + "label-email": "", + "label-name": "", + "label-password": "", + "label-username": "" + }, + "user-ldap-sync-info": { + "label-ldap": "" + }, + "user-list-admin-page-un-connected": { + "placeholder-search-login-email": "" + }, + "user-list-anonymous-devices-page-un-connected": { + "placeholder-search-devices-by-ip-address": "" + }, + "user-list-page": { + "label-all-users": "", + "label-anonymous-devices": "", + "label-organization-users": "", + "label-users": "" + }, "user-orgs": { "add-button": "", "change-role-button": "", @@ -137,7 +226,14 @@ "disable-button": "", "edit-button": "", "enable-button": "", - "title": "" + "label-email": "", + "label-name": "", + "label-numerical-identifier": "", + "label-password": "", + "label-username": "", + "title": "", + "title-delete-user": "", + "title-disable-user": "" }, "user-sessions": { "browser-column": "", @@ -155,6 +251,9 @@ "create-button": "" }, "users-table": { + "columns": { + "title-edit-user": "" + }, "last-seen-never": "", "no-licensed-roles": "" } @@ -357,6 +456,12 @@ "missing-reference": "", "self-reference": "" }, + "draggable-rules-table": { + "evals-to-start-alerting": "", + "pending-period": "", + "recording": "", + "rule-name": "" + }, "export": { "subtitle": { "formats": "", @@ -380,6 +485,45 @@ "export": "", "reorder": "" }, + "group-details": { + "ds-features-error": "", + "edit": "", + "evaluations-to-fire": "", + "export": "", + "folder": "", + "group-loading-error": "", + "interval": "", + "namespace": "", + "pending-period": "", + "recording": "", + "rule-name": "" + }, + "group-edit": { + "ds-error": "", + "form": { + "delete": "", + "delete-body": "", + "delete-confirm": "", + "delete-title": "", + "folder-label": "", + "group-name-label": "", + "group-name-required": "", + "interval-description": "", + "interval-label": "", + "namespace-label": "", + "namespace-required": "", + "rules-description": "", + "rules-label": "", + "save": "", + "update-error": "", + "update-success": "" + }, + "group-not-editable": "", + "group-not-editable-description": "", + "page-title": "", + "rule-group-error": "", + "title": "" + }, "irm-integration": { "connection-method": "", "disabled-description": "", @@ -608,6 +752,13 @@ } } }, + "rule-group-action": { + "details": "", + "edit": "", + "export-rules-folder": "", + "go-to-folder": "", + "manage-permissions": "" + }, "rule-groups": { "delete": { "success": "" @@ -775,6 +926,9 @@ "actions": { "button-to-recently-deleted": "" }, + "browse-view": { + "this-folder-is-empty": "" + }, "counts": { "alertRule_one": "", "alertRule_few": "", @@ -805,6 +959,9 @@ "select-checkbox": "", "tags-column": "" }, + "descendant-count": { + "title-unable-to-retrieve-descendant-information": "" + }, "empty-state": { "button-title": "", "pro-tip": "", @@ -835,6 +992,9 @@ "dashboards": "", "panels": "" }, + "name-cell": { + "no-items": "" + }, "new-folder-form": { "cancel-label": "", "create-label": "", @@ -1155,6 +1315,28 @@ } }, "edit-pane": { + "add": { + "lib-panel": { + "heading": "", + "title": "" + }, + "panel": { + "heading": "", + "title": "" + }, + "paste-panel": { + "heading": "", + "title": "" + }, + "row": { + "heading": "", + "title": "" + }, + "tab": { + "heading": "", + "title": "" + } + }, "elements": { "dashboard": "", "objects": "", @@ -1345,11 +1527,6 @@ }, "toolbar": { "add": "", - "add-panel": "", - "add-panel-description": "", - "add-panel-lib": "", - "add-row": "", - "add-tab": "", "alert-rules": "", "back-to-dashboard": "", "dashboard-settings": { @@ -1372,7 +1549,6 @@ "label": "", "tooltip": "" }, - "libray-panel-description": "", "mark-favorite": "", "more-save-options": "", "open-original": "", @@ -1381,7 +1557,6 @@ "playlist-stop": "", "public-dashboard": "", "refresh": "", - "row-description": "", "save": "", "save-dashboard": { "label": "", @@ -1401,7 +1576,6 @@ "share-button": "", "show-hidden-elements": "", "switch-old-dashboard": "", - "tabs-description": "", "unlink-library-panel": "", "unmark-favorite": "" }, @@ -1456,6 +1630,17 @@ "it-cannot-contain-or": "", "least-valid-character": "" }, + "dashboard-preview-banner": { + "not-saved": "", + "not-yet-saved": "", + "open-pull-request-in-git-hub": "", + "title-dashboard-loaded-branch-git-hub": "", + "title-dashboard-loaded-external-repository": "", + "title-dashboard-loaded-request-git-hub": "", + "title-error-loading-dashboard": "", + "value-not-saved": "", + "view-pull-request-in-git-hub": "" + }, "provisioned-resource-delete-modal": { "file-path": "", "managed-by-version-control": "", @@ -2369,9 +2554,32 @@ } }, "library-panels": { + "add-library-panel-modal": { + "title-create-library-panel": "" + }, + "confirm": { + "delete-panel": "" + }, + "delete-library-panel-modal": { + "cancel": "", + "delete": "", + "title-delete-library-panel": "" + }, "empty-state": { "message": "" }, + "folder-link": { + "dashboards": "" + }, + "has-connected-dashboards": { + "dashboard-name": "" + }, + "library-panels-search": { + "placeholder-search-by-name-or-description": "" + }, + "loading-indicator": { + "loading-library-panel": "" + }, "loading-panel-text": "", "modal": { "button-cancel": "", @@ -2386,9 +2594,20 @@ "body_many": "", "body_other": "" }, + "panel-library-options-group": { + "create-new-library-panel": "" + }, "save": { "error": "", "success": "" + }, + "save-library-panel-modal": { + "cancel": "", + "dashboard-name": "", + "discard": "", + "loading-connected-dashboards": "", + "placeholder-search-affected-dashboards": "", + "update-all": "" } }, "link": { @@ -2644,12 +2863,6 @@ "link-title": "", "title": "" }, - "public-preview": { - "button-text": "", - "message": "", - "message-cloud": "", - "title": "" - }, "resource-details": { "dismiss-button": "", "error-messages": { @@ -3136,6 +3349,27 @@ "error": "" } }, + "org": { + "new-org-page": { + "create": "", + "label-organization-name": "", + "placeholder-org-name": "" + }, + "org-profile": { + "label-organization-name": "", + "label-organization-profile": "", + "update-organization-name": "" + }, + "user-invite-form": { + "back": "", + "label-email-or-username": "", + "label-name": "", + "label-send-invite-email": "", + "placeholder-optional": "", + "role": "", + "submit": "" + } + }, "panel": { "header-menu": { "copy": "", @@ -3166,6 +3400,19 @@ "panel-type-filter": { "clear-button": "" }, + "playlist": { + "start-modal": { + "description-customize-dashboard-elements-visibility": "", + "description-panel-heights-adjusted-screen": "", + "label-autofit": "", + "label-dashboard-links": "", + "label-display-dashboard-controls": "", + "label-mode": "", + "label-time-and-refresh": "", + "label-variables": "", + "title-start-playlist": "" + } + }, "playlist-edit": { "error-prefix": "", "form": { @@ -3995,11 +4242,37 @@ "placeholder": "" }, "teams": { + "create-team": { + "create": "", + "label-name": "", + "label-role": "" + }, "empty-state": { "button-title": "", "message": "", "pro-tip": "", "title": "" + }, + "team-group-sync": { + "add-group": "", + "external-group-id": "", + "external-group-sync": "", + "title-there-external-groups": "" + }, + "team-list": { + "columns": { + "title-edit-team": "" + }, + "new-team": "", + "placeholder-search-teams": "" + }, + "team-settings": { + "label-email": "", + "label-name": "", + "label-numerical-identifier": "", + "label-role": "", + "label-team-details": "", + "save": "" } }, "theme-preview": { diff --git a/public/locales/pt-BR/grafana.json b/public/locales/pt-BR/grafana.json index f4126d2775a..da9afdb99be 100644 --- a/public/locales/pt-BR/grafana.json +++ b/public/locales/pt-BR/grafana.json @@ -35,15 +35,57 @@ } }, "admin": { + "add-to-org-modal": { + "label-organization": "", + "label-role": "", + "title-add-to-an-organization": "" + }, + "admin-edit-org-page": { + "label-name": "", + "render-missing-permission-message": { + "title-access-denied": "" + } + }, + "admin-feature-toggles-table": { + "get-stage-cell": { + "beta": "", + "deprecated": "", + "ga": "" + }, + "title-apply-feature-toggle-changes": "" + }, + "admin-orgs-table": { + "aria-label-delete-org": "", + "title-delete": "" + }, "anon-users": { "not-found": "Nenhum usuário anônimo encontrado." }, + "base-user-sessions": { + "title-force-logout-from-all-devices": "" + }, "edit-org": { "access-denied": "Você não tem permissão para ver usuários nesta organização. Para atualizar esta organização, entre em contato com o administrador do servidor.", "heading": "Editar Organização", "update-button": "Atualizar", "users-heading": "Usuários da organização" }, + "feature-listing": { + "title-auditing": "", + "title-dashboard-presence-indicators": "", + "title-dashboard-usage-stats-drawer": "", + "title-data-source-permissions": "", + "title-enhanced-ldap-integration": "", + "title-enterprise-plugins": "", + "title-find-unused-dashboards": "", + "title-grafana-usage-insights": "", + "title-reporting": "", + "title-saml-authentication": "", + "title-settings-updates-at-runtime": "", + "title-sort-dashboards-by-popularity-in-search": "", + "title-team-sync": "", + "title-white-labeling": "" + }, "feature-toggles": { "sub-title": "Visualize e edite feature toggles. Leia mais sobre feature toggles em <2>grafana.com." }, @@ -61,11 +103,23 @@ "test-mapping-heading": "", "test-mapping-run-button": "" }, + "ldap-error-box": { + "title-connection-error": "" + }, + "ldap-page": { + "label-username": "" + }, "ldap-permissions": { "active": "", "admin": "", "inactive": "" }, + "ldap-settings-page": { + "label-reset-to-default-values": "", + "title-invalid-configuration": "", + "title-more-actions": "", + "tooltip-more-actions": "" + }, "ldap-status": { "title": "" }, @@ -87,12 +141,21 @@ "ldap-user-info": { "no-team": "" }, + "org-role-picker": { + "placeholder-choose-role": "" + }, "org-uers": { "last-seen-never": "" }, "org-users": { "not-editable": "" }, + "org-users-table": { + "columns": { + "aria-label-role": "" + }, + "title-delete": "" + }, "orgs": { "delete-body": "", "id-header": "ID", @@ -107,12 +170,38 @@ "title": "", "users-button": "" }, + "service-info": { + "title-critical-sla-hours": "", + "title-enterprise-plugins": "", + "title-unlimited-expert-support": "" + }, "settings": { "info-description": "" }, "upgrade-info": { "title": "Licença Enterprise" }, + "user-create-page": { + "label-email": "", + "label-name": "", + "label-password": "", + "label-username": "" + }, + "user-ldap-sync-info": { + "label-ldap": "" + }, + "user-list-admin-page-un-connected": { + "placeholder-search-login-email": "" + }, + "user-list-anonymous-devices-page-un-connected": { + "placeholder-search-devices-by-ip-address": "" + }, + "user-list-page": { + "label-all-users": "", + "label-anonymous-devices": "", + "label-organization-users": "", + "label-users": "" + }, "user-orgs": { "add-button": "Adicionar usuário à organização", "change-role-button": "", @@ -137,7 +226,14 @@ "disable-button": "", "edit-button": "", "enable-button": "", - "title": "" + "label-email": "", + "label-name": "", + "label-numerical-identifier": "", + "label-password": "", + "label-username": "", + "title": "", + "title-delete-user": "", + "title-disable-user": "" }, "user-sessions": { "browser-column": "", @@ -155,6 +251,9 @@ "create-button": "" }, "users-table": { + "columns": { + "title-edit-user": "" + }, "last-seen-never": "", "no-licensed-roles": "" } @@ -353,6 +452,12 @@ "missing-reference": "", "self-reference": "" }, + "draggable-rules-table": { + "evals-to-start-alerting": "", + "pending-period": "", + "recording": "", + "rule-name": "" + }, "export": { "subtitle": { "formats": "", @@ -376,6 +481,45 @@ "export": "", "reorder": "" }, + "group-details": { + "ds-features-error": "", + "edit": "", + "evaluations-to-fire": "", + "export": "", + "folder": "", + "group-loading-error": "", + "interval": "", + "namespace": "", + "pending-period": "", + "recording": "", + "rule-name": "" + }, + "group-edit": { + "ds-error": "", + "form": { + "delete": "", + "delete-body": "", + "delete-confirm": "", + "delete-title": "", + "folder-label": "", + "group-name-label": "", + "group-name-required": "", + "interval-description": "", + "interval-label": "", + "namespace-label": "", + "namespace-required": "", + "rules-description": "", + "rules-label": "", + "save": "", + "update-error": "", + "update-success": "" + }, + "group-not-editable": "", + "group-not-editable-description": "", + "page-title": "", + "rule-group-error": "", + "title": "" + }, "irm-integration": { "connection-method": "", "disabled-description": "", @@ -600,6 +744,13 @@ } } }, + "rule-group-action": { + "details": "", + "edit": "", + "export-rules-folder": "", + "go-to-folder": "", + "manage-permissions": "" + }, "rule-groups": { "delete": { "success": "" @@ -767,6 +918,9 @@ "actions": { "button-to-recently-deleted": "" }, + "browse-view": { + "this-folder-is-empty": "" + }, "counts": { "alertRule_one": "{{count}} regra de alerta", "alertRule_other": "{{count}} regras de alerta", @@ -787,6 +941,9 @@ "select-checkbox": "Selecionar", "tags-column": "Tags" }, + "descendant-count": { + "title-unable-to-retrieve-descendant-information": "" + }, "empty-state": { "button-title": "", "pro-tip": "", @@ -817,6 +974,9 @@ "dashboards": "Painéis de controle", "panels": "Painéis" }, + "name-cell": { + "no-items": "" + }, "new-folder-form": { "cancel-label": "Cancelar", "create-label": "Criar", @@ -1137,6 +1297,28 @@ } }, "edit-pane": { + "add": { + "lib-panel": { + "heading": "", + "title": "" + }, + "panel": { + "heading": "", + "title": "" + }, + "paste-panel": { + "heading": "", + "title": "" + }, + "row": { + "heading": "", + "title": "" + }, + "tab": { + "heading": "", + "title": "" + } + }, "elements": { "dashboard": "", "objects": "", @@ -1327,11 +1509,6 @@ }, "toolbar": { "add": "Adicionar", - "add-panel": "", - "add-panel-description": "", - "add-panel-lib": "", - "add-row": "", - "add-tab": "", "alert-rules": "Regras de alerta", "back-to-dashboard": "", "dashboard-settings": { @@ -1354,7 +1531,6 @@ "label": "", "tooltip": "" }, - "libray-panel-description": "", "mark-favorite": "Marcar como favorito", "more-save-options": "", "open-original": "Abrir painel de controle original", @@ -1363,7 +1539,6 @@ "playlist-stop": "Parar lista de reprodução", "public-dashboard": "", "refresh": "Atualizar painel de controle", - "row-description": "", "save": "Salvar painel de controle", "save-dashboard": { "label": "", @@ -1383,7 +1558,6 @@ "share-button": "Compartilhar", "show-hidden-elements": "", "switch-old-dashboard": "", - "tabs-description": "", "unlink-library-panel": "", "unmark-favorite": "Desmarcar como favorito" }, @@ -1438,6 +1612,17 @@ "it-cannot-contain-or": "", "least-valid-character": "" }, + "dashboard-preview-banner": { + "not-saved": "", + "not-yet-saved": "", + "open-pull-request-in-git-hub": "", + "title-dashboard-loaded-branch-git-hub": "", + "title-dashboard-loaded-external-repository": "", + "title-dashboard-loaded-request-git-hub": "", + "title-error-loading-dashboard": "", + "value-not-saved": "", + "view-pull-request-in-git-hub": "" + }, "provisioned-resource-delete-modal": { "file-path": "", "managed-by-version-control": "", @@ -2351,9 +2536,32 @@ } }, "library-panels": { + "add-library-panel-modal": { + "title-create-library-panel": "" + }, + "confirm": { + "delete-panel": "" + }, + "delete-library-panel-modal": { + "cancel": "", + "delete": "", + "title-delete-library-panel": "" + }, "empty-state": { "message": "" }, + "folder-link": { + "dashboards": "" + }, + "has-connected-dashboards": { + "dashboard-name": "" + }, + "library-panels-search": { + "placeholder-search-by-name-or-description": "" + }, + "loading-indicator": { + "loading-library-panel": "" + }, "loading-panel-text": "", "modal": { "button-cancel": "", @@ -2366,9 +2574,20 @@ "body_one": "Este painel está sendo usado em {{count}} painel de controle. Escolha em qual painel de controle você poderá visualizar:", "body_other": "Este painel está sendo usado em {{count}} painéis de controle. Escolha em qual painel de controle você poderá visualizar:" }, + "panel-library-options-group": { + "create-new-library-panel": "" + }, "save": { "error": "Erro ao salvar painel de biblioteca: \"{{errorMsg}}\"", "success": "Painel de biblioteca salvo" + }, + "save-library-panel-modal": { + "cancel": "", + "dashboard-name": "", + "discard": "", + "loading-connected-dashboards": "", + "placeholder-search-affected-dashboards": "", + "update-all": "" } }, "link": { @@ -2624,12 +2843,6 @@ "link-title": "Preços do Grafana Cloud", "title": "Quanto isto custa?" }, - "public-preview": { - "button-text": "", - "message": "", - "message-cloud": "", - "title": "" - }, "resource-details": { "dismiss-button": "", "error-messages": { @@ -3116,6 +3329,27 @@ "error": "" } }, + "org": { + "new-org-page": { + "create": "", + "label-organization-name": "", + "placeholder-org-name": "" + }, + "org-profile": { + "label-organization-name": "", + "label-organization-profile": "", + "update-organization-name": "" + }, + "user-invite-form": { + "back": "", + "label-email-or-username": "", + "label-name": "", + "label-send-invite-email": "", + "placeholder-optional": "", + "role": "", + "submit": "" + } + }, "panel": { "header-menu": { "copy": "Copiar", @@ -3146,6 +3380,19 @@ "panel-type-filter": { "clear-button": "" }, + "playlist": { + "start-modal": { + "description-customize-dashboard-elements-visibility": "", + "description-panel-heights-adjusted-screen": "", + "label-autofit": "", + "label-dashboard-links": "", + "label-display-dashboard-controls": "", + "label-mode": "", + "label-time-and-refresh": "", + "label-variables": "", + "title-start-playlist": "" + } + }, "playlist-edit": { "error-prefix": "Erro ao carregar lista de reprodução:", "form": { @@ -3967,11 +4214,37 @@ "placeholder": "Filtrar por tag" }, "teams": { + "create-team": { + "create": "", + "label-name": "", + "label-role": "" + }, "empty-state": { "button-title": "", "message": "", "pro-tip": "", "title": "" + }, + "team-group-sync": { + "add-group": "", + "external-group-id": "", + "external-group-sync": "", + "title-there-external-groups": "" + }, + "team-list": { + "columns": { + "title-edit-team": "" + }, + "new-team": "", + "placeholder-search-teams": "" + }, + "team-settings": { + "label-email": "", + "label-name": "", + "label-numerical-identifier": "", + "label-role": "", + "label-team-details": "", + "save": "" } }, "theme-preview": { diff --git a/public/locales/pt-PT/grafana.json b/public/locales/pt-PT/grafana.json index 0b79be81e58..f7dea7cfce7 100644 --- a/public/locales/pt-PT/grafana.json +++ b/public/locales/pt-PT/grafana.json @@ -35,15 +35,57 @@ } }, "admin": { + "add-to-org-modal": { + "label-organization": "", + "label-role": "", + "title-add-to-an-organization": "" + }, + "admin-edit-org-page": { + "label-name": "", + "render-missing-permission-message": { + "title-access-denied": "" + } + }, + "admin-feature-toggles-table": { + "get-stage-cell": { + "beta": "", + "deprecated": "", + "ga": "" + }, + "title-apply-feature-toggle-changes": "" + }, + "admin-orgs-table": { + "aria-label-delete-org": "", + "title-delete": "" + }, "anon-users": { "not-found": "" }, + "base-user-sessions": { + "title-force-logout-from-all-devices": "" + }, "edit-org": { "access-denied": "", "heading": "", "update-button": "", "users-heading": "" }, + "feature-listing": { + "title-auditing": "", + "title-dashboard-presence-indicators": "", + "title-dashboard-usage-stats-drawer": "", + "title-data-source-permissions": "", + "title-enhanced-ldap-integration": "", + "title-enterprise-plugins": "", + "title-find-unused-dashboards": "", + "title-grafana-usage-insights": "", + "title-reporting": "", + "title-saml-authentication": "", + "title-settings-updates-at-runtime": "", + "title-sort-dashboards-by-popularity-in-search": "", + "title-team-sync": "", + "title-white-labeling": "" + }, "feature-toggles": { "sub-title": "" }, @@ -61,11 +103,23 @@ "test-mapping-heading": "", "test-mapping-run-button": "" }, + "ldap-error-box": { + "title-connection-error": "" + }, + "ldap-page": { + "label-username": "" + }, "ldap-permissions": { "active": "", "admin": "", "inactive": "" }, + "ldap-settings-page": { + "label-reset-to-default-values": "", + "title-invalid-configuration": "", + "title-more-actions": "", + "tooltip-more-actions": "" + }, "ldap-status": { "title": "" }, @@ -87,12 +141,21 @@ "ldap-user-info": { "no-team": "" }, + "org-role-picker": { + "placeholder-choose-role": "" + }, "org-uers": { "last-seen-never": "" }, "org-users": { "not-editable": "" }, + "org-users-table": { + "columns": { + "aria-label-role": "" + }, + "title-delete": "" + }, "orgs": { "delete-body": "", "id-header": "", @@ -107,12 +170,38 @@ "title": "", "users-button": "" }, + "service-info": { + "title-critical-sla-hours": "", + "title-enterprise-plugins": "", + "title-unlimited-expert-support": "" + }, "settings": { "info-description": "" }, "upgrade-info": { "title": "" }, + "user-create-page": { + "label-email": "", + "label-name": "", + "label-password": "", + "label-username": "" + }, + "user-ldap-sync-info": { + "label-ldap": "" + }, + "user-list-admin-page-un-connected": { + "placeholder-search-login-email": "" + }, + "user-list-anonymous-devices-page-un-connected": { + "placeholder-search-devices-by-ip-address": "" + }, + "user-list-page": { + "label-all-users": "", + "label-anonymous-devices": "", + "label-organization-users": "", + "label-users": "" + }, "user-orgs": { "add-button": "", "change-role-button": "", @@ -137,7 +226,14 @@ "disable-button": "", "edit-button": "", "enable-button": "", - "title": "" + "label-email": "", + "label-name": "", + "label-numerical-identifier": "", + "label-password": "", + "label-username": "", + "title": "", + "title-delete-user": "", + "title-disable-user": "" }, "user-sessions": { "browser-column": "", @@ -155,6 +251,9 @@ "create-button": "" }, "users-table": { + "columns": { + "title-edit-user": "" + }, "last-seen-never": "", "no-licensed-roles": "" } @@ -353,6 +452,12 @@ "missing-reference": "", "self-reference": "" }, + "draggable-rules-table": { + "evals-to-start-alerting": "", + "pending-period": "", + "recording": "", + "rule-name": "" + }, "export": { "subtitle": { "formats": "", @@ -376,6 +481,45 @@ "export": "", "reorder": "" }, + "group-details": { + "ds-features-error": "", + "edit": "", + "evaluations-to-fire": "", + "export": "", + "folder": "", + "group-loading-error": "", + "interval": "", + "namespace": "", + "pending-period": "", + "recording": "", + "rule-name": "" + }, + "group-edit": { + "ds-error": "", + "form": { + "delete": "", + "delete-body": "", + "delete-confirm": "", + "delete-title": "", + "folder-label": "", + "group-name-label": "", + "group-name-required": "", + "interval-description": "", + "interval-label": "", + "namespace-label": "", + "namespace-required": "", + "rules-description": "", + "rules-label": "", + "save": "", + "update-error": "", + "update-success": "" + }, + "group-not-editable": "", + "group-not-editable-description": "", + "page-title": "", + "rule-group-error": "", + "title": "" + }, "irm-integration": { "connection-method": "", "disabled-description": "", @@ -600,6 +744,13 @@ } } }, + "rule-group-action": { + "details": "", + "edit": "", + "export-rules-folder": "", + "go-to-folder": "", + "manage-permissions": "" + }, "rule-groups": { "delete": { "success": "" @@ -767,6 +918,9 @@ "actions": { "button-to-recently-deleted": "" }, + "browse-view": { + "this-folder-is-empty": "" + }, "counts": { "alertRule_one": "", "alertRule_other": "", @@ -787,6 +941,9 @@ "select-checkbox": "", "tags-column": "" }, + "descendant-count": { + "title-unable-to-retrieve-descendant-information": "" + }, "empty-state": { "button-title": "", "pro-tip": "", @@ -817,6 +974,9 @@ "dashboards": "", "panels": "" }, + "name-cell": { + "no-items": "" + }, "new-folder-form": { "cancel-label": "", "create-label": "", @@ -1137,6 +1297,28 @@ } }, "edit-pane": { + "add": { + "lib-panel": { + "heading": "", + "title": "" + }, + "panel": { + "heading": "", + "title": "" + }, + "paste-panel": { + "heading": "", + "title": "" + }, + "row": { + "heading": "", + "title": "" + }, + "tab": { + "heading": "", + "title": "" + } + }, "elements": { "dashboard": "", "objects": "", @@ -1327,11 +1509,6 @@ }, "toolbar": { "add": "", - "add-panel": "", - "add-panel-description": "", - "add-panel-lib": "", - "add-row": "", - "add-tab": "", "alert-rules": "", "back-to-dashboard": "", "dashboard-settings": { @@ -1354,7 +1531,6 @@ "label": "", "tooltip": "" }, - "libray-panel-description": "", "mark-favorite": "", "more-save-options": "", "open-original": "", @@ -1363,7 +1539,6 @@ "playlist-stop": "", "public-dashboard": "", "refresh": "", - "row-description": "", "save": "", "save-dashboard": { "label": "", @@ -1383,7 +1558,6 @@ "share-button": "", "show-hidden-elements": "", "switch-old-dashboard": "", - "tabs-description": "", "unlink-library-panel": "", "unmark-favorite": "" }, @@ -1438,6 +1612,17 @@ "it-cannot-contain-or": "", "least-valid-character": "" }, + "dashboard-preview-banner": { + "not-saved": "", + "not-yet-saved": "", + "open-pull-request-in-git-hub": "", + "title-dashboard-loaded-branch-git-hub": "", + "title-dashboard-loaded-external-repository": "", + "title-dashboard-loaded-request-git-hub": "", + "title-error-loading-dashboard": "", + "value-not-saved": "", + "view-pull-request-in-git-hub": "" + }, "provisioned-resource-delete-modal": { "file-path": "", "managed-by-version-control": "", @@ -2351,9 +2536,32 @@ } }, "library-panels": { + "add-library-panel-modal": { + "title-create-library-panel": "" + }, + "confirm": { + "delete-panel": "" + }, + "delete-library-panel-modal": { + "cancel": "", + "delete": "", + "title-delete-library-panel": "" + }, "empty-state": { "message": "" }, + "folder-link": { + "dashboards": "" + }, + "has-connected-dashboards": { + "dashboard-name": "" + }, + "library-panels-search": { + "placeholder-search-by-name-or-description": "" + }, + "loading-indicator": { + "loading-library-panel": "" + }, "loading-panel-text": "", "modal": { "button-cancel": "", @@ -2366,9 +2574,20 @@ "body_one": "", "body_other": "" }, + "panel-library-options-group": { + "create-new-library-panel": "" + }, "save": { "error": "", "success": "" + }, + "save-library-panel-modal": { + "cancel": "", + "dashboard-name": "", + "discard": "", + "loading-connected-dashboards": "", + "placeholder-search-affected-dashboards": "", + "update-all": "" } }, "link": { @@ -2624,12 +2843,6 @@ "link-title": "", "title": "" }, - "public-preview": { - "button-text": "", - "message": "", - "message-cloud": "", - "title": "" - }, "resource-details": { "dismiss-button": "", "error-messages": { @@ -3116,6 +3329,27 @@ "error": "" } }, + "org": { + "new-org-page": { + "create": "", + "label-organization-name": "", + "placeholder-org-name": "" + }, + "org-profile": { + "label-organization-name": "", + "label-organization-profile": "", + "update-organization-name": "" + }, + "user-invite-form": { + "back": "", + "label-email-or-username": "", + "label-name": "", + "label-send-invite-email": "", + "placeholder-optional": "", + "role": "", + "submit": "" + } + }, "panel": { "header-menu": { "copy": "", @@ -3146,6 +3380,19 @@ "panel-type-filter": { "clear-button": "" }, + "playlist": { + "start-modal": { + "description-customize-dashboard-elements-visibility": "", + "description-panel-heights-adjusted-screen": "", + "label-autofit": "", + "label-dashboard-links": "", + "label-display-dashboard-controls": "", + "label-mode": "", + "label-time-and-refresh": "", + "label-variables": "", + "title-start-playlist": "" + } + }, "playlist-edit": { "error-prefix": "", "form": { @@ -3967,11 +4214,37 @@ "placeholder": "" }, "teams": { + "create-team": { + "create": "", + "label-name": "", + "label-role": "" + }, "empty-state": { "button-title": "", "message": "", "pro-tip": "", "title": "" + }, + "team-group-sync": { + "add-group": "", + "external-group-id": "", + "external-group-sync": "", + "title-there-external-groups": "" + }, + "team-list": { + "columns": { + "title-edit-team": "" + }, + "new-team": "", + "placeholder-search-teams": "" + }, + "team-settings": { + "label-email": "", + "label-name": "", + "label-numerical-identifier": "", + "label-role": "", + "label-team-details": "", + "save": "" } }, "theme-preview": { diff --git a/public/locales/ru-RU/grafana.json b/public/locales/ru-RU/grafana.json index 5a982333efe..01f18df8845 100644 --- a/public/locales/ru-RU/grafana.json +++ b/public/locales/ru-RU/grafana.json @@ -35,15 +35,57 @@ } }, "admin": { + "add-to-org-modal": { + "label-organization": "", + "label-role": "", + "title-add-to-an-organization": "" + }, + "admin-edit-org-page": { + "label-name": "", + "render-missing-permission-message": { + "title-access-denied": "" + } + }, + "admin-feature-toggles-table": { + "get-stage-cell": { + "beta": "", + "deprecated": "", + "ga": "" + }, + "title-apply-feature-toggle-changes": "" + }, + "admin-orgs-table": { + "aria-label-delete-org": "", + "title-delete": "" + }, "anon-users": { "not-found": "" }, + "base-user-sessions": { + "title-force-logout-from-all-devices": "" + }, "edit-org": { "access-denied": "", "heading": "", "update-button": "", "users-heading": "" }, + "feature-listing": { + "title-auditing": "", + "title-dashboard-presence-indicators": "", + "title-dashboard-usage-stats-drawer": "", + "title-data-source-permissions": "", + "title-enhanced-ldap-integration": "", + "title-enterprise-plugins": "", + "title-find-unused-dashboards": "", + "title-grafana-usage-insights": "", + "title-reporting": "", + "title-saml-authentication": "", + "title-settings-updates-at-runtime": "", + "title-sort-dashboards-by-popularity-in-search": "", + "title-team-sync": "", + "title-white-labeling": "" + }, "feature-toggles": { "sub-title": "" }, @@ -61,11 +103,23 @@ "test-mapping-heading": "", "test-mapping-run-button": "" }, + "ldap-error-box": { + "title-connection-error": "" + }, + "ldap-page": { + "label-username": "" + }, "ldap-permissions": { "active": "", "admin": "", "inactive": "" }, + "ldap-settings-page": { + "label-reset-to-default-values": "", + "title-invalid-configuration": "", + "title-more-actions": "", + "tooltip-more-actions": "" + }, "ldap-status": { "title": "" }, @@ -87,12 +141,21 @@ "ldap-user-info": { "no-team": "" }, + "org-role-picker": { + "placeholder-choose-role": "" + }, "org-uers": { "last-seen-never": "" }, "org-users": { "not-editable": "" }, + "org-users-table": { + "columns": { + "aria-label-role": "" + }, + "title-delete": "" + }, "orgs": { "delete-body": "", "id-header": "", @@ -107,12 +170,38 @@ "title": "", "users-button": "" }, + "service-info": { + "title-critical-sla-hours": "", + "title-enterprise-plugins": "", + "title-unlimited-expert-support": "" + }, "settings": { "info-description": "" }, "upgrade-info": { "title": "" }, + "user-create-page": { + "label-email": "", + "label-name": "", + "label-password": "", + "label-username": "" + }, + "user-ldap-sync-info": { + "label-ldap": "" + }, + "user-list-admin-page-un-connected": { + "placeholder-search-login-email": "" + }, + "user-list-anonymous-devices-page-un-connected": { + "placeholder-search-devices-by-ip-address": "" + }, + "user-list-page": { + "label-all-users": "", + "label-anonymous-devices": "", + "label-organization-users": "", + "label-users": "" + }, "user-orgs": { "add-button": "", "change-role-button": "", @@ -137,7 +226,14 @@ "disable-button": "", "edit-button": "", "enable-button": "", - "title": "" + "label-email": "", + "label-name": "", + "label-numerical-identifier": "", + "label-password": "", + "label-username": "", + "title": "", + "title-delete-user": "", + "title-disable-user": "" }, "user-sessions": { "browser-column": "", @@ -155,6 +251,9 @@ "create-button": "" }, "users-table": { + "columns": { + "title-edit-user": "" + }, "last-seen-never": "", "no-licensed-roles": "" } @@ -357,6 +456,12 @@ "missing-reference": "", "self-reference": "" }, + "draggable-rules-table": { + "evals-to-start-alerting": "", + "pending-period": "", + "recording": "", + "rule-name": "" + }, "export": { "subtitle": { "formats": "", @@ -380,6 +485,45 @@ "export": "", "reorder": "" }, + "group-details": { + "ds-features-error": "", + "edit": "", + "evaluations-to-fire": "", + "export": "", + "folder": "", + "group-loading-error": "", + "interval": "", + "namespace": "", + "pending-period": "", + "recording": "", + "rule-name": "" + }, + "group-edit": { + "ds-error": "", + "form": { + "delete": "", + "delete-body": "", + "delete-confirm": "", + "delete-title": "", + "folder-label": "", + "group-name-label": "", + "group-name-required": "", + "interval-description": "", + "interval-label": "", + "namespace-label": "", + "namespace-required": "", + "rules-description": "", + "rules-label": "", + "save": "", + "update-error": "", + "update-success": "" + }, + "group-not-editable": "", + "group-not-editable-description": "", + "page-title": "", + "rule-group-error": "", + "title": "" + }, "irm-integration": { "connection-method": "", "disabled-description": "", @@ -608,6 +752,13 @@ } } }, + "rule-group-action": { + "details": "", + "edit": "", + "export-rules-folder": "", + "go-to-folder": "", + "manage-permissions": "" + }, "rule-groups": { "delete": { "success": "" @@ -775,6 +926,9 @@ "actions": { "button-to-recently-deleted": "" }, + "browse-view": { + "this-folder-is-empty": "" + }, "counts": { "alertRule_one": "", "alertRule_few": "", @@ -805,6 +959,9 @@ "select-checkbox": "", "tags-column": "" }, + "descendant-count": { + "title-unable-to-retrieve-descendant-information": "" + }, "empty-state": { "button-title": "", "pro-tip": "", @@ -835,6 +992,9 @@ "dashboards": "", "panels": "" }, + "name-cell": { + "no-items": "" + }, "new-folder-form": { "cancel-label": "", "create-label": "", @@ -1155,6 +1315,28 @@ } }, "edit-pane": { + "add": { + "lib-panel": { + "heading": "", + "title": "" + }, + "panel": { + "heading": "", + "title": "" + }, + "paste-panel": { + "heading": "", + "title": "" + }, + "row": { + "heading": "", + "title": "" + }, + "tab": { + "heading": "", + "title": "" + } + }, "elements": { "dashboard": "", "objects": "", @@ -1345,11 +1527,6 @@ }, "toolbar": { "add": "", - "add-panel": "", - "add-panel-description": "", - "add-panel-lib": "", - "add-row": "", - "add-tab": "", "alert-rules": "", "back-to-dashboard": "", "dashboard-settings": { @@ -1372,7 +1549,6 @@ "label": "", "tooltip": "" }, - "libray-panel-description": "", "mark-favorite": "", "more-save-options": "", "open-original": "", @@ -1381,7 +1557,6 @@ "playlist-stop": "", "public-dashboard": "", "refresh": "", - "row-description": "", "save": "", "save-dashboard": { "label": "", @@ -1401,7 +1576,6 @@ "share-button": "", "show-hidden-elements": "", "switch-old-dashboard": "", - "tabs-description": "", "unlink-library-panel": "", "unmark-favorite": "" }, @@ -1456,6 +1630,17 @@ "it-cannot-contain-or": "", "least-valid-character": "" }, + "dashboard-preview-banner": { + "not-saved": "", + "not-yet-saved": "", + "open-pull-request-in-git-hub": "", + "title-dashboard-loaded-branch-git-hub": "", + "title-dashboard-loaded-external-repository": "", + "title-dashboard-loaded-request-git-hub": "", + "title-error-loading-dashboard": "", + "value-not-saved": "", + "view-pull-request-in-git-hub": "" + }, "provisioned-resource-delete-modal": { "file-path": "", "managed-by-version-control": "", @@ -2369,9 +2554,32 @@ } }, "library-panels": { + "add-library-panel-modal": { + "title-create-library-panel": "" + }, + "confirm": { + "delete-panel": "" + }, + "delete-library-panel-modal": { + "cancel": "", + "delete": "", + "title-delete-library-panel": "" + }, "empty-state": { "message": "" }, + "folder-link": { + "dashboards": "" + }, + "has-connected-dashboards": { + "dashboard-name": "" + }, + "library-panels-search": { + "placeholder-search-by-name-or-description": "" + }, + "loading-indicator": { + "loading-library-panel": "" + }, "loading-panel-text": "", "modal": { "button-cancel": "", @@ -2386,9 +2594,20 @@ "body_many": "", "body_other": "" }, + "panel-library-options-group": { + "create-new-library-panel": "" + }, "save": { "error": "", "success": "" + }, + "save-library-panel-modal": { + "cancel": "", + "dashboard-name": "", + "discard": "", + "loading-connected-dashboards": "", + "placeholder-search-affected-dashboards": "", + "update-all": "" } }, "link": { @@ -2644,12 +2863,6 @@ "link-title": "", "title": "" }, - "public-preview": { - "button-text": "", - "message": "", - "message-cloud": "", - "title": "" - }, "resource-details": { "dismiss-button": "", "error-messages": { @@ -3136,6 +3349,27 @@ "error": "" } }, + "org": { + "new-org-page": { + "create": "", + "label-organization-name": "", + "placeholder-org-name": "" + }, + "org-profile": { + "label-organization-name": "", + "label-organization-profile": "", + "update-organization-name": "" + }, + "user-invite-form": { + "back": "", + "label-email-or-username": "", + "label-name": "", + "label-send-invite-email": "", + "placeholder-optional": "", + "role": "", + "submit": "" + } + }, "panel": { "header-menu": { "copy": "", @@ -3166,6 +3400,19 @@ "panel-type-filter": { "clear-button": "" }, + "playlist": { + "start-modal": { + "description-customize-dashboard-elements-visibility": "", + "description-panel-heights-adjusted-screen": "", + "label-autofit": "", + "label-dashboard-links": "", + "label-display-dashboard-controls": "", + "label-mode": "", + "label-time-and-refresh": "", + "label-variables": "", + "title-start-playlist": "" + } + }, "playlist-edit": { "error-prefix": "", "form": { @@ -3995,11 +4242,37 @@ "placeholder": "" }, "teams": { + "create-team": { + "create": "", + "label-name": "", + "label-role": "" + }, "empty-state": { "button-title": "", "message": "", "pro-tip": "", "title": "" + }, + "team-group-sync": { + "add-group": "", + "external-group-id": "", + "external-group-sync": "", + "title-there-external-groups": "" + }, + "team-list": { + "columns": { + "title-edit-team": "" + }, + "new-team": "", + "placeholder-search-teams": "" + }, + "team-settings": { + "label-email": "", + "label-name": "", + "label-numerical-identifier": "", + "label-role": "", + "label-team-details": "", + "save": "" } }, "theme-preview": { diff --git a/public/locales/sv-SE/grafana.json b/public/locales/sv-SE/grafana.json index 0b79be81e58..f7dea7cfce7 100644 --- a/public/locales/sv-SE/grafana.json +++ b/public/locales/sv-SE/grafana.json @@ -35,15 +35,57 @@ } }, "admin": { + "add-to-org-modal": { + "label-organization": "", + "label-role": "", + "title-add-to-an-organization": "" + }, + "admin-edit-org-page": { + "label-name": "", + "render-missing-permission-message": { + "title-access-denied": "" + } + }, + "admin-feature-toggles-table": { + "get-stage-cell": { + "beta": "", + "deprecated": "", + "ga": "" + }, + "title-apply-feature-toggle-changes": "" + }, + "admin-orgs-table": { + "aria-label-delete-org": "", + "title-delete": "" + }, "anon-users": { "not-found": "" }, + "base-user-sessions": { + "title-force-logout-from-all-devices": "" + }, "edit-org": { "access-denied": "", "heading": "", "update-button": "", "users-heading": "" }, + "feature-listing": { + "title-auditing": "", + "title-dashboard-presence-indicators": "", + "title-dashboard-usage-stats-drawer": "", + "title-data-source-permissions": "", + "title-enhanced-ldap-integration": "", + "title-enterprise-plugins": "", + "title-find-unused-dashboards": "", + "title-grafana-usage-insights": "", + "title-reporting": "", + "title-saml-authentication": "", + "title-settings-updates-at-runtime": "", + "title-sort-dashboards-by-popularity-in-search": "", + "title-team-sync": "", + "title-white-labeling": "" + }, "feature-toggles": { "sub-title": "" }, @@ -61,11 +103,23 @@ "test-mapping-heading": "", "test-mapping-run-button": "" }, + "ldap-error-box": { + "title-connection-error": "" + }, + "ldap-page": { + "label-username": "" + }, "ldap-permissions": { "active": "", "admin": "", "inactive": "" }, + "ldap-settings-page": { + "label-reset-to-default-values": "", + "title-invalid-configuration": "", + "title-more-actions": "", + "tooltip-more-actions": "" + }, "ldap-status": { "title": "" }, @@ -87,12 +141,21 @@ "ldap-user-info": { "no-team": "" }, + "org-role-picker": { + "placeholder-choose-role": "" + }, "org-uers": { "last-seen-never": "" }, "org-users": { "not-editable": "" }, + "org-users-table": { + "columns": { + "aria-label-role": "" + }, + "title-delete": "" + }, "orgs": { "delete-body": "", "id-header": "", @@ -107,12 +170,38 @@ "title": "", "users-button": "" }, + "service-info": { + "title-critical-sla-hours": "", + "title-enterprise-plugins": "", + "title-unlimited-expert-support": "" + }, "settings": { "info-description": "" }, "upgrade-info": { "title": "" }, + "user-create-page": { + "label-email": "", + "label-name": "", + "label-password": "", + "label-username": "" + }, + "user-ldap-sync-info": { + "label-ldap": "" + }, + "user-list-admin-page-un-connected": { + "placeholder-search-login-email": "" + }, + "user-list-anonymous-devices-page-un-connected": { + "placeholder-search-devices-by-ip-address": "" + }, + "user-list-page": { + "label-all-users": "", + "label-anonymous-devices": "", + "label-organization-users": "", + "label-users": "" + }, "user-orgs": { "add-button": "", "change-role-button": "", @@ -137,7 +226,14 @@ "disable-button": "", "edit-button": "", "enable-button": "", - "title": "" + "label-email": "", + "label-name": "", + "label-numerical-identifier": "", + "label-password": "", + "label-username": "", + "title": "", + "title-delete-user": "", + "title-disable-user": "" }, "user-sessions": { "browser-column": "", @@ -155,6 +251,9 @@ "create-button": "" }, "users-table": { + "columns": { + "title-edit-user": "" + }, "last-seen-never": "", "no-licensed-roles": "" } @@ -353,6 +452,12 @@ "missing-reference": "", "self-reference": "" }, + "draggable-rules-table": { + "evals-to-start-alerting": "", + "pending-period": "", + "recording": "", + "rule-name": "" + }, "export": { "subtitle": { "formats": "", @@ -376,6 +481,45 @@ "export": "", "reorder": "" }, + "group-details": { + "ds-features-error": "", + "edit": "", + "evaluations-to-fire": "", + "export": "", + "folder": "", + "group-loading-error": "", + "interval": "", + "namespace": "", + "pending-period": "", + "recording": "", + "rule-name": "" + }, + "group-edit": { + "ds-error": "", + "form": { + "delete": "", + "delete-body": "", + "delete-confirm": "", + "delete-title": "", + "folder-label": "", + "group-name-label": "", + "group-name-required": "", + "interval-description": "", + "interval-label": "", + "namespace-label": "", + "namespace-required": "", + "rules-description": "", + "rules-label": "", + "save": "", + "update-error": "", + "update-success": "" + }, + "group-not-editable": "", + "group-not-editable-description": "", + "page-title": "", + "rule-group-error": "", + "title": "" + }, "irm-integration": { "connection-method": "", "disabled-description": "", @@ -600,6 +744,13 @@ } } }, + "rule-group-action": { + "details": "", + "edit": "", + "export-rules-folder": "", + "go-to-folder": "", + "manage-permissions": "" + }, "rule-groups": { "delete": { "success": "" @@ -767,6 +918,9 @@ "actions": { "button-to-recently-deleted": "" }, + "browse-view": { + "this-folder-is-empty": "" + }, "counts": { "alertRule_one": "", "alertRule_other": "", @@ -787,6 +941,9 @@ "select-checkbox": "", "tags-column": "" }, + "descendant-count": { + "title-unable-to-retrieve-descendant-information": "" + }, "empty-state": { "button-title": "", "pro-tip": "", @@ -817,6 +974,9 @@ "dashboards": "", "panels": "" }, + "name-cell": { + "no-items": "" + }, "new-folder-form": { "cancel-label": "", "create-label": "", @@ -1137,6 +1297,28 @@ } }, "edit-pane": { + "add": { + "lib-panel": { + "heading": "", + "title": "" + }, + "panel": { + "heading": "", + "title": "" + }, + "paste-panel": { + "heading": "", + "title": "" + }, + "row": { + "heading": "", + "title": "" + }, + "tab": { + "heading": "", + "title": "" + } + }, "elements": { "dashboard": "", "objects": "", @@ -1327,11 +1509,6 @@ }, "toolbar": { "add": "", - "add-panel": "", - "add-panel-description": "", - "add-panel-lib": "", - "add-row": "", - "add-tab": "", "alert-rules": "", "back-to-dashboard": "", "dashboard-settings": { @@ -1354,7 +1531,6 @@ "label": "", "tooltip": "" }, - "libray-panel-description": "", "mark-favorite": "", "more-save-options": "", "open-original": "", @@ -1363,7 +1539,6 @@ "playlist-stop": "", "public-dashboard": "", "refresh": "", - "row-description": "", "save": "", "save-dashboard": { "label": "", @@ -1383,7 +1558,6 @@ "share-button": "", "show-hidden-elements": "", "switch-old-dashboard": "", - "tabs-description": "", "unlink-library-panel": "", "unmark-favorite": "" }, @@ -1438,6 +1612,17 @@ "it-cannot-contain-or": "", "least-valid-character": "" }, + "dashboard-preview-banner": { + "not-saved": "", + "not-yet-saved": "", + "open-pull-request-in-git-hub": "", + "title-dashboard-loaded-branch-git-hub": "", + "title-dashboard-loaded-external-repository": "", + "title-dashboard-loaded-request-git-hub": "", + "title-error-loading-dashboard": "", + "value-not-saved": "", + "view-pull-request-in-git-hub": "" + }, "provisioned-resource-delete-modal": { "file-path": "", "managed-by-version-control": "", @@ -2351,9 +2536,32 @@ } }, "library-panels": { + "add-library-panel-modal": { + "title-create-library-panel": "" + }, + "confirm": { + "delete-panel": "" + }, + "delete-library-panel-modal": { + "cancel": "", + "delete": "", + "title-delete-library-panel": "" + }, "empty-state": { "message": "" }, + "folder-link": { + "dashboards": "" + }, + "has-connected-dashboards": { + "dashboard-name": "" + }, + "library-panels-search": { + "placeholder-search-by-name-or-description": "" + }, + "loading-indicator": { + "loading-library-panel": "" + }, "loading-panel-text": "", "modal": { "button-cancel": "", @@ -2366,9 +2574,20 @@ "body_one": "", "body_other": "" }, + "panel-library-options-group": { + "create-new-library-panel": "" + }, "save": { "error": "", "success": "" + }, + "save-library-panel-modal": { + "cancel": "", + "dashboard-name": "", + "discard": "", + "loading-connected-dashboards": "", + "placeholder-search-affected-dashboards": "", + "update-all": "" } }, "link": { @@ -2624,12 +2843,6 @@ "link-title": "", "title": "" }, - "public-preview": { - "button-text": "", - "message": "", - "message-cloud": "", - "title": "" - }, "resource-details": { "dismiss-button": "", "error-messages": { @@ -3116,6 +3329,27 @@ "error": "" } }, + "org": { + "new-org-page": { + "create": "", + "label-organization-name": "", + "placeholder-org-name": "" + }, + "org-profile": { + "label-organization-name": "", + "label-organization-profile": "", + "update-organization-name": "" + }, + "user-invite-form": { + "back": "", + "label-email-or-username": "", + "label-name": "", + "label-send-invite-email": "", + "placeholder-optional": "", + "role": "", + "submit": "" + } + }, "panel": { "header-menu": { "copy": "", @@ -3146,6 +3380,19 @@ "panel-type-filter": { "clear-button": "" }, + "playlist": { + "start-modal": { + "description-customize-dashboard-elements-visibility": "", + "description-panel-heights-adjusted-screen": "", + "label-autofit": "", + "label-dashboard-links": "", + "label-display-dashboard-controls": "", + "label-mode": "", + "label-time-and-refresh": "", + "label-variables": "", + "title-start-playlist": "" + } + }, "playlist-edit": { "error-prefix": "", "form": { @@ -3967,11 +4214,37 @@ "placeholder": "" }, "teams": { + "create-team": { + "create": "", + "label-name": "", + "label-role": "" + }, "empty-state": { "button-title": "", "message": "", "pro-tip": "", "title": "" + }, + "team-group-sync": { + "add-group": "", + "external-group-id": "", + "external-group-sync": "", + "title-there-external-groups": "" + }, + "team-list": { + "columns": { + "title-edit-team": "" + }, + "new-team": "", + "placeholder-search-teams": "" + }, + "team-settings": { + "label-email": "", + "label-name": "", + "label-numerical-identifier": "", + "label-role": "", + "label-team-details": "", + "save": "" } }, "theme-preview": { diff --git a/public/locales/tr-TR/grafana.json b/public/locales/tr-TR/grafana.json index 0b79be81e58..f7dea7cfce7 100644 --- a/public/locales/tr-TR/grafana.json +++ b/public/locales/tr-TR/grafana.json @@ -35,15 +35,57 @@ } }, "admin": { + "add-to-org-modal": { + "label-organization": "", + "label-role": "", + "title-add-to-an-organization": "" + }, + "admin-edit-org-page": { + "label-name": "", + "render-missing-permission-message": { + "title-access-denied": "" + } + }, + "admin-feature-toggles-table": { + "get-stage-cell": { + "beta": "", + "deprecated": "", + "ga": "" + }, + "title-apply-feature-toggle-changes": "" + }, + "admin-orgs-table": { + "aria-label-delete-org": "", + "title-delete": "" + }, "anon-users": { "not-found": "" }, + "base-user-sessions": { + "title-force-logout-from-all-devices": "" + }, "edit-org": { "access-denied": "", "heading": "", "update-button": "", "users-heading": "" }, + "feature-listing": { + "title-auditing": "", + "title-dashboard-presence-indicators": "", + "title-dashboard-usage-stats-drawer": "", + "title-data-source-permissions": "", + "title-enhanced-ldap-integration": "", + "title-enterprise-plugins": "", + "title-find-unused-dashboards": "", + "title-grafana-usage-insights": "", + "title-reporting": "", + "title-saml-authentication": "", + "title-settings-updates-at-runtime": "", + "title-sort-dashboards-by-popularity-in-search": "", + "title-team-sync": "", + "title-white-labeling": "" + }, "feature-toggles": { "sub-title": "" }, @@ -61,11 +103,23 @@ "test-mapping-heading": "", "test-mapping-run-button": "" }, + "ldap-error-box": { + "title-connection-error": "" + }, + "ldap-page": { + "label-username": "" + }, "ldap-permissions": { "active": "", "admin": "", "inactive": "" }, + "ldap-settings-page": { + "label-reset-to-default-values": "", + "title-invalid-configuration": "", + "title-more-actions": "", + "tooltip-more-actions": "" + }, "ldap-status": { "title": "" }, @@ -87,12 +141,21 @@ "ldap-user-info": { "no-team": "" }, + "org-role-picker": { + "placeholder-choose-role": "" + }, "org-uers": { "last-seen-never": "" }, "org-users": { "not-editable": "" }, + "org-users-table": { + "columns": { + "aria-label-role": "" + }, + "title-delete": "" + }, "orgs": { "delete-body": "", "id-header": "", @@ -107,12 +170,38 @@ "title": "", "users-button": "" }, + "service-info": { + "title-critical-sla-hours": "", + "title-enterprise-plugins": "", + "title-unlimited-expert-support": "" + }, "settings": { "info-description": "" }, "upgrade-info": { "title": "" }, + "user-create-page": { + "label-email": "", + "label-name": "", + "label-password": "", + "label-username": "" + }, + "user-ldap-sync-info": { + "label-ldap": "" + }, + "user-list-admin-page-un-connected": { + "placeholder-search-login-email": "" + }, + "user-list-anonymous-devices-page-un-connected": { + "placeholder-search-devices-by-ip-address": "" + }, + "user-list-page": { + "label-all-users": "", + "label-anonymous-devices": "", + "label-organization-users": "", + "label-users": "" + }, "user-orgs": { "add-button": "", "change-role-button": "", @@ -137,7 +226,14 @@ "disable-button": "", "edit-button": "", "enable-button": "", - "title": "" + "label-email": "", + "label-name": "", + "label-numerical-identifier": "", + "label-password": "", + "label-username": "", + "title": "", + "title-delete-user": "", + "title-disable-user": "" }, "user-sessions": { "browser-column": "", @@ -155,6 +251,9 @@ "create-button": "" }, "users-table": { + "columns": { + "title-edit-user": "" + }, "last-seen-never": "", "no-licensed-roles": "" } @@ -353,6 +452,12 @@ "missing-reference": "", "self-reference": "" }, + "draggable-rules-table": { + "evals-to-start-alerting": "", + "pending-period": "", + "recording": "", + "rule-name": "" + }, "export": { "subtitle": { "formats": "", @@ -376,6 +481,45 @@ "export": "", "reorder": "" }, + "group-details": { + "ds-features-error": "", + "edit": "", + "evaluations-to-fire": "", + "export": "", + "folder": "", + "group-loading-error": "", + "interval": "", + "namespace": "", + "pending-period": "", + "recording": "", + "rule-name": "" + }, + "group-edit": { + "ds-error": "", + "form": { + "delete": "", + "delete-body": "", + "delete-confirm": "", + "delete-title": "", + "folder-label": "", + "group-name-label": "", + "group-name-required": "", + "interval-description": "", + "interval-label": "", + "namespace-label": "", + "namespace-required": "", + "rules-description": "", + "rules-label": "", + "save": "", + "update-error": "", + "update-success": "" + }, + "group-not-editable": "", + "group-not-editable-description": "", + "page-title": "", + "rule-group-error": "", + "title": "" + }, "irm-integration": { "connection-method": "", "disabled-description": "", @@ -600,6 +744,13 @@ } } }, + "rule-group-action": { + "details": "", + "edit": "", + "export-rules-folder": "", + "go-to-folder": "", + "manage-permissions": "" + }, "rule-groups": { "delete": { "success": "" @@ -767,6 +918,9 @@ "actions": { "button-to-recently-deleted": "" }, + "browse-view": { + "this-folder-is-empty": "" + }, "counts": { "alertRule_one": "", "alertRule_other": "", @@ -787,6 +941,9 @@ "select-checkbox": "", "tags-column": "" }, + "descendant-count": { + "title-unable-to-retrieve-descendant-information": "" + }, "empty-state": { "button-title": "", "pro-tip": "", @@ -817,6 +974,9 @@ "dashboards": "", "panels": "" }, + "name-cell": { + "no-items": "" + }, "new-folder-form": { "cancel-label": "", "create-label": "", @@ -1137,6 +1297,28 @@ } }, "edit-pane": { + "add": { + "lib-panel": { + "heading": "", + "title": "" + }, + "panel": { + "heading": "", + "title": "" + }, + "paste-panel": { + "heading": "", + "title": "" + }, + "row": { + "heading": "", + "title": "" + }, + "tab": { + "heading": "", + "title": "" + } + }, "elements": { "dashboard": "", "objects": "", @@ -1327,11 +1509,6 @@ }, "toolbar": { "add": "", - "add-panel": "", - "add-panel-description": "", - "add-panel-lib": "", - "add-row": "", - "add-tab": "", "alert-rules": "", "back-to-dashboard": "", "dashboard-settings": { @@ -1354,7 +1531,6 @@ "label": "", "tooltip": "" }, - "libray-panel-description": "", "mark-favorite": "", "more-save-options": "", "open-original": "", @@ -1363,7 +1539,6 @@ "playlist-stop": "", "public-dashboard": "", "refresh": "", - "row-description": "", "save": "", "save-dashboard": { "label": "", @@ -1383,7 +1558,6 @@ "share-button": "", "show-hidden-elements": "", "switch-old-dashboard": "", - "tabs-description": "", "unlink-library-panel": "", "unmark-favorite": "" }, @@ -1438,6 +1612,17 @@ "it-cannot-contain-or": "", "least-valid-character": "" }, + "dashboard-preview-banner": { + "not-saved": "", + "not-yet-saved": "", + "open-pull-request-in-git-hub": "", + "title-dashboard-loaded-branch-git-hub": "", + "title-dashboard-loaded-external-repository": "", + "title-dashboard-loaded-request-git-hub": "", + "title-error-loading-dashboard": "", + "value-not-saved": "", + "view-pull-request-in-git-hub": "" + }, "provisioned-resource-delete-modal": { "file-path": "", "managed-by-version-control": "", @@ -2351,9 +2536,32 @@ } }, "library-panels": { + "add-library-panel-modal": { + "title-create-library-panel": "" + }, + "confirm": { + "delete-panel": "" + }, + "delete-library-panel-modal": { + "cancel": "", + "delete": "", + "title-delete-library-panel": "" + }, "empty-state": { "message": "" }, + "folder-link": { + "dashboards": "" + }, + "has-connected-dashboards": { + "dashboard-name": "" + }, + "library-panels-search": { + "placeholder-search-by-name-or-description": "" + }, + "loading-indicator": { + "loading-library-panel": "" + }, "loading-panel-text": "", "modal": { "button-cancel": "", @@ -2366,9 +2574,20 @@ "body_one": "", "body_other": "" }, + "panel-library-options-group": { + "create-new-library-panel": "" + }, "save": { "error": "", "success": "" + }, + "save-library-panel-modal": { + "cancel": "", + "dashboard-name": "", + "discard": "", + "loading-connected-dashboards": "", + "placeholder-search-affected-dashboards": "", + "update-all": "" } }, "link": { @@ -2624,12 +2843,6 @@ "link-title": "", "title": "" }, - "public-preview": { - "button-text": "", - "message": "", - "message-cloud": "", - "title": "" - }, "resource-details": { "dismiss-button": "", "error-messages": { @@ -3116,6 +3329,27 @@ "error": "" } }, + "org": { + "new-org-page": { + "create": "", + "label-organization-name": "", + "placeholder-org-name": "" + }, + "org-profile": { + "label-organization-name": "", + "label-organization-profile": "", + "update-organization-name": "" + }, + "user-invite-form": { + "back": "", + "label-email-or-username": "", + "label-name": "", + "label-send-invite-email": "", + "placeholder-optional": "", + "role": "", + "submit": "" + } + }, "panel": { "header-menu": { "copy": "", @@ -3146,6 +3380,19 @@ "panel-type-filter": { "clear-button": "" }, + "playlist": { + "start-modal": { + "description-customize-dashboard-elements-visibility": "", + "description-panel-heights-adjusted-screen": "", + "label-autofit": "", + "label-dashboard-links": "", + "label-display-dashboard-controls": "", + "label-mode": "", + "label-time-and-refresh": "", + "label-variables": "", + "title-start-playlist": "" + } + }, "playlist-edit": { "error-prefix": "", "form": { @@ -3967,11 +4214,37 @@ "placeholder": "" }, "teams": { + "create-team": { + "create": "", + "label-name": "", + "label-role": "" + }, "empty-state": { "button-title": "", "message": "", "pro-tip": "", "title": "" + }, + "team-group-sync": { + "add-group": "", + "external-group-id": "", + "external-group-sync": "", + "title-there-external-groups": "" + }, + "team-list": { + "columns": { + "title-edit-team": "" + }, + "new-team": "", + "placeholder-search-teams": "" + }, + "team-settings": { + "label-email": "", + "label-name": "", + "label-numerical-identifier": "", + "label-role": "", + "label-team-details": "", + "save": "" } }, "theme-preview": { diff --git a/public/locales/zh-Hans/grafana.json b/public/locales/zh-Hans/grafana.json index 7b159d90793..1137c90e5ff 100644 --- a/public/locales/zh-Hans/grafana.json +++ b/public/locales/zh-Hans/grafana.json @@ -35,15 +35,57 @@ } }, "admin": { + "add-to-org-modal": { + "label-organization": "", + "label-role": "", + "title-add-to-an-organization": "" + }, + "admin-edit-org-page": { + "label-name": "", + "render-missing-permission-message": { + "title-access-denied": "" + } + }, + "admin-feature-toggles-table": { + "get-stage-cell": { + "beta": "", + "deprecated": "", + "ga": "" + }, + "title-apply-feature-toggle-changes": "" + }, + "admin-orgs-table": { + "aria-label-delete-org": "", + "title-delete": "" + }, "anon-users": { "not-found": "" }, + "base-user-sessions": { + "title-force-logout-from-all-devices": "" + }, "edit-org": { "access-denied": "", "heading": "", "update-button": "", "users-heading": "" }, + "feature-listing": { + "title-auditing": "", + "title-dashboard-presence-indicators": "", + "title-dashboard-usage-stats-drawer": "", + "title-data-source-permissions": "", + "title-enhanced-ldap-integration": "", + "title-enterprise-plugins": "", + "title-find-unused-dashboards": "", + "title-grafana-usage-insights": "", + "title-reporting": "", + "title-saml-authentication": "", + "title-settings-updates-at-runtime": "", + "title-sort-dashboards-by-popularity-in-search": "", + "title-team-sync": "", + "title-white-labeling": "" + }, "feature-toggles": { "sub-title": "" }, @@ -61,11 +103,23 @@ "test-mapping-heading": "", "test-mapping-run-button": "" }, + "ldap-error-box": { + "title-connection-error": "" + }, + "ldap-page": { + "label-username": "" + }, "ldap-permissions": { "active": "", "admin": "", "inactive": "" }, + "ldap-settings-page": { + "label-reset-to-default-values": "", + "title-invalid-configuration": "", + "title-more-actions": "", + "tooltip-more-actions": "" + }, "ldap-status": { "title": "" }, @@ -87,12 +141,21 @@ "ldap-user-info": { "no-team": "" }, + "org-role-picker": { + "placeholder-choose-role": "" + }, "org-uers": { "last-seen-never": "" }, "org-users": { "not-editable": "" }, + "org-users-table": { + "columns": { + "aria-label-role": "" + }, + "title-delete": "" + }, "orgs": { "delete-body": "", "id-header": "", @@ -107,12 +170,38 @@ "title": "", "users-button": "" }, + "service-info": { + "title-critical-sla-hours": "", + "title-enterprise-plugins": "", + "title-unlimited-expert-support": "" + }, "settings": { "info-description": "" }, "upgrade-info": { "title": "" }, + "user-create-page": { + "label-email": "", + "label-name": "", + "label-password": "", + "label-username": "" + }, + "user-ldap-sync-info": { + "label-ldap": "" + }, + "user-list-admin-page-un-connected": { + "placeholder-search-login-email": "" + }, + "user-list-anonymous-devices-page-un-connected": { + "placeholder-search-devices-by-ip-address": "" + }, + "user-list-page": { + "label-all-users": "", + "label-anonymous-devices": "", + "label-organization-users": "", + "label-users": "" + }, "user-orgs": { "add-button": "", "change-role-button": "", @@ -137,7 +226,14 @@ "disable-button": "", "edit-button": "", "enable-button": "", - "title": "" + "label-email": "", + "label-name": "", + "label-numerical-identifier": "", + "label-password": "", + "label-username": "", + "title": "", + "title-delete-user": "", + "title-disable-user": "" }, "user-sessions": { "browser-column": "", @@ -155,6 +251,9 @@ "create-button": "" }, "users-table": { + "columns": { + "title-edit-user": "" + }, "last-seen-never": "", "no-licensed-roles": "" } @@ -351,6 +450,12 @@ "missing-reference": "", "self-reference": "" }, + "draggable-rules-table": { + "evals-to-start-alerting": "", + "pending-period": "", + "recording": "", + "rule-name": "" + }, "export": { "subtitle": { "formats": "", @@ -374,6 +479,45 @@ "export": "", "reorder": "" }, + "group-details": { + "ds-features-error": "", + "edit": "", + "evaluations-to-fire": "", + "export": "", + "folder": "", + "group-loading-error": "", + "interval": "", + "namespace": "", + "pending-period": "", + "recording": "", + "rule-name": "" + }, + "group-edit": { + "ds-error": "", + "form": { + "delete": "", + "delete-body": "", + "delete-confirm": "", + "delete-title": "", + "folder-label": "", + "group-name-label": "", + "group-name-required": "", + "interval-description": "", + "interval-label": "", + "namespace-label": "", + "namespace-required": "", + "rules-description": "", + "rules-label": "", + "save": "", + "update-error": "", + "update-success": "" + }, + "group-not-editable": "", + "group-not-editable-description": "", + "page-title": "", + "rule-group-error": "", + "title": "" + }, "irm-integration": { "connection-method": "", "disabled-description": "", @@ -596,6 +740,13 @@ } } }, + "rule-group-action": { + "details": "", + "edit": "", + "export-rules-folder": "", + "go-to-folder": "", + "manage-permissions": "" + }, "rule-groups": { "delete": { "success": "" @@ -763,6 +914,9 @@ "actions": { "button-to-recently-deleted": "" }, + "browse-view": { + "this-folder-is-empty": "" + }, "counts": { "alertRule_other": "{{count}} 条警告规则", "dashboard_other": "{{count}} 个仪表板", @@ -778,6 +932,9 @@ "select-checkbox": "选择", "tags-column": "标签" }, + "descendant-count": { + "title-unable-to-retrieve-descendant-information": "" + }, "empty-state": { "button-title": "", "pro-tip": "", @@ -808,6 +965,9 @@ "dashboards": "仪表板", "panels": "面板" }, + "name-cell": { + "no-items": "" + }, "new-folder-form": { "cancel-label": "取消", "create-label": "创建", @@ -1128,6 +1288,28 @@ } }, "edit-pane": { + "add": { + "lib-panel": { + "heading": "", + "title": "" + }, + "panel": { + "heading": "", + "title": "" + }, + "paste-panel": { + "heading": "", + "title": "" + }, + "row": { + "heading": "", + "title": "" + }, + "tab": { + "heading": "", + "title": "" + } + }, "elements": { "dashboard": "", "objects": "", @@ -1318,11 +1500,6 @@ }, "toolbar": { "add": "添加", - "add-panel": "", - "add-panel-description": "", - "add-panel-lib": "", - "add-row": "", - "add-tab": "", "alert-rules": "警报规则", "back-to-dashboard": "", "dashboard-settings": { @@ -1345,7 +1522,6 @@ "label": "", "tooltip": "" }, - "libray-panel-description": "", "mark-favorite": "标记为收藏", "more-save-options": "", "open-original": "打开原始仪表板", @@ -1354,7 +1530,6 @@ "playlist-stop": "停止播放列表", "public-dashboard": "", "refresh": "刷新仪表板", - "row-description": "", "save": "保存仪表板", "save-dashboard": { "label": "", @@ -1374,7 +1549,6 @@ "share-button": "分享", "show-hidden-elements": "", "switch-old-dashboard": "", - "tabs-description": "", "unlink-library-panel": "", "unmark-favorite": "取消标记为收藏" }, @@ -1429,6 +1603,17 @@ "it-cannot-contain-or": "", "least-valid-character": "" }, + "dashboard-preview-banner": { + "not-saved": "", + "not-yet-saved": "", + "open-pull-request-in-git-hub": "", + "title-dashboard-loaded-branch-git-hub": "", + "title-dashboard-loaded-external-repository": "", + "title-dashboard-loaded-request-git-hub": "", + "title-error-loading-dashboard": "", + "value-not-saved": "", + "view-pull-request-in-git-hub": "" + }, "provisioned-resource-delete-modal": { "file-path": "", "managed-by-version-control": "", @@ -2342,9 +2527,32 @@ } }, "library-panels": { + "add-library-panel-modal": { + "title-create-library-panel": "" + }, + "confirm": { + "delete-panel": "" + }, + "delete-library-panel-modal": { + "cancel": "", + "delete": "", + "title-delete-library-panel": "" + }, "empty-state": { "message": "" }, + "folder-link": { + "dashboards": "" + }, + "has-connected-dashboards": { + "dashboard-name": "" + }, + "library-panels-search": { + "placeholder-search-by-name-or-description": "" + }, + "loading-indicator": { + "loading-library-panel": "" + }, "loading-panel-text": "", "modal": { "button-cancel": "", @@ -2356,9 +2564,20 @@ "title": "在仪表板中查看面板", "body_other": "此面板在 {{count}} 个仪表板中使用。请选择要在哪个仪表板中查看此面板:" }, + "panel-library-options-group": { + "create-new-library-panel": "" + }, "save": { "error": "保存库面板时出错:\"{{errorMsg}}\"", "success": "库面板已保存" + }, + "save-library-panel-modal": { + "cancel": "", + "dashboard-name": "", + "discard": "", + "loading-connected-dashboards": "", + "placeholder-search-affected-dashboards": "", + "update-all": "" } }, "link": { @@ -2614,12 +2833,6 @@ "link-title": "Grafana Cloud 价格", "title": "费用如何?" }, - "public-preview": { - "button-text": "", - "message": "", - "message-cloud": "", - "title": "" - }, "resource-details": { "dismiss-button": "", "error-messages": { @@ -3106,6 +3319,27 @@ "error": "" } }, + "org": { + "new-org-page": { + "create": "", + "label-organization-name": "", + "placeholder-org-name": "" + }, + "org-profile": { + "label-organization-name": "", + "label-organization-profile": "", + "update-organization-name": "" + }, + "user-invite-form": { + "back": "", + "label-email-or-username": "", + "label-name": "", + "label-send-invite-email": "", + "placeholder-optional": "", + "role": "", + "submit": "" + } + }, "panel": { "header-menu": { "copy": "复制", @@ -3136,6 +3370,19 @@ "panel-type-filter": { "clear-button": "" }, + "playlist": { + "start-modal": { + "description-customize-dashboard-elements-visibility": "", + "description-panel-heights-adjusted-screen": "", + "label-autofit": "", + "label-dashboard-links": "", + "label-display-dashboard-controls": "", + "label-mode": "", + "label-time-and-refresh": "", + "label-variables": "", + "title-start-playlist": "" + } + }, "playlist-edit": { "error-prefix": "加载播放列表时发生错误:", "form": { @@ -3953,11 +4200,37 @@ "placeholder": "按标签筛选" }, "teams": { + "create-team": { + "create": "", + "label-name": "", + "label-role": "" + }, "empty-state": { "button-title": "", "message": "", "pro-tip": "", "title": "" + }, + "team-group-sync": { + "add-group": "", + "external-group-id": "", + "external-group-sync": "", + "title-there-external-groups": "" + }, + "team-list": { + "columns": { + "title-edit-team": "" + }, + "new-team": "", + "placeholder-search-teams": "" + }, + "team-settings": { + "label-email": "", + "label-name": "", + "label-numerical-identifier": "", + "label-role": "", + "label-team-details": "", + "save": "" } }, "theme-preview": { diff --git a/public/locales/zh-Hant/grafana.json b/public/locales/zh-Hant/grafana.json index 553b8a59659..6afeeafec1f 100644 --- a/public/locales/zh-Hant/grafana.json +++ b/public/locales/zh-Hant/grafana.json @@ -35,15 +35,57 @@ } }, "admin": { + "add-to-org-modal": { + "label-organization": "", + "label-role": "", + "title-add-to-an-organization": "" + }, + "admin-edit-org-page": { + "label-name": "", + "render-missing-permission-message": { + "title-access-denied": "" + } + }, + "admin-feature-toggles-table": { + "get-stage-cell": { + "beta": "", + "deprecated": "", + "ga": "" + }, + "title-apply-feature-toggle-changes": "" + }, + "admin-orgs-table": { + "aria-label-delete-org": "", + "title-delete": "" + }, "anon-users": { "not-found": "" }, + "base-user-sessions": { + "title-force-logout-from-all-devices": "" + }, "edit-org": { "access-denied": "", "heading": "", "update-button": "", "users-heading": "" }, + "feature-listing": { + "title-auditing": "", + "title-dashboard-presence-indicators": "", + "title-dashboard-usage-stats-drawer": "", + "title-data-source-permissions": "", + "title-enhanced-ldap-integration": "", + "title-enterprise-plugins": "", + "title-find-unused-dashboards": "", + "title-grafana-usage-insights": "", + "title-reporting": "", + "title-saml-authentication": "", + "title-settings-updates-at-runtime": "", + "title-sort-dashboards-by-popularity-in-search": "", + "title-team-sync": "", + "title-white-labeling": "" + }, "feature-toggles": { "sub-title": "" }, @@ -61,11 +103,23 @@ "test-mapping-heading": "", "test-mapping-run-button": "" }, + "ldap-error-box": { + "title-connection-error": "" + }, + "ldap-page": { + "label-username": "" + }, "ldap-permissions": { "active": "", "admin": "", "inactive": "" }, + "ldap-settings-page": { + "label-reset-to-default-values": "", + "title-invalid-configuration": "", + "title-more-actions": "", + "tooltip-more-actions": "" + }, "ldap-status": { "title": "" }, @@ -87,12 +141,21 @@ "ldap-user-info": { "no-team": "" }, + "org-role-picker": { + "placeholder-choose-role": "" + }, "org-uers": { "last-seen-never": "" }, "org-users": { "not-editable": "" }, + "org-users-table": { + "columns": { + "aria-label-role": "" + }, + "title-delete": "" + }, "orgs": { "delete-body": "", "id-header": "", @@ -107,12 +170,38 @@ "title": "", "users-button": "" }, + "service-info": { + "title-critical-sla-hours": "", + "title-enterprise-plugins": "", + "title-unlimited-expert-support": "" + }, "settings": { "info-description": "" }, "upgrade-info": { "title": "" }, + "user-create-page": { + "label-email": "", + "label-name": "", + "label-password": "", + "label-username": "" + }, + "user-ldap-sync-info": { + "label-ldap": "" + }, + "user-list-admin-page-un-connected": { + "placeholder-search-login-email": "" + }, + "user-list-anonymous-devices-page-un-connected": { + "placeholder-search-devices-by-ip-address": "" + }, + "user-list-page": { + "label-all-users": "", + "label-anonymous-devices": "", + "label-organization-users": "", + "label-users": "" + }, "user-orgs": { "add-button": "", "change-role-button": "", @@ -137,7 +226,14 @@ "disable-button": "", "edit-button": "", "enable-button": "", - "title": "" + "label-email": "", + "label-name": "", + "label-numerical-identifier": "", + "label-password": "", + "label-username": "", + "title": "", + "title-delete-user": "", + "title-disable-user": "" }, "user-sessions": { "browser-column": "", @@ -155,6 +251,9 @@ "create-button": "" }, "users-table": { + "columns": { + "title-edit-user": "" + }, "last-seen-never": "", "no-licensed-roles": "" } @@ -351,6 +450,12 @@ "missing-reference": "", "self-reference": "" }, + "draggable-rules-table": { + "evals-to-start-alerting": "", + "pending-period": "", + "recording": "", + "rule-name": "" + }, "export": { "subtitle": { "formats": "", @@ -374,6 +479,45 @@ "export": "", "reorder": "" }, + "group-details": { + "ds-features-error": "", + "edit": "", + "evaluations-to-fire": "", + "export": "", + "folder": "", + "group-loading-error": "", + "interval": "", + "namespace": "", + "pending-period": "", + "recording": "", + "rule-name": "" + }, + "group-edit": { + "ds-error": "", + "form": { + "delete": "", + "delete-body": "", + "delete-confirm": "", + "delete-title": "", + "folder-label": "", + "group-name-label": "", + "group-name-required": "", + "interval-description": "", + "interval-label": "", + "namespace-label": "", + "namespace-required": "", + "rules-description": "", + "rules-label": "", + "save": "", + "update-error": "", + "update-success": "" + }, + "group-not-editable": "", + "group-not-editable-description": "", + "page-title": "", + "rule-group-error": "", + "title": "" + }, "irm-integration": { "connection-method": "", "disabled-description": "", @@ -596,6 +740,13 @@ } } }, + "rule-group-action": { + "details": "", + "edit": "", + "export-rules-folder": "", + "go-to-folder": "", + "manage-permissions": "" + }, "rule-groups": { "delete": { "success": "" @@ -763,6 +914,9 @@ "actions": { "button-to-recently-deleted": "" }, + "browse-view": { + "this-folder-is-empty": "" + }, "counts": { "alertRule_other": "", "dashboard_other": "", @@ -778,6 +932,9 @@ "select-checkbox": "", "tags-column": "" }, + "descendant-count": { + "title-unable-to-retrieve-descendant-information": "" + }, "empty-state": { "button-title": "", "pro-tip": "", @@ -808,6 +965,9 @@ "dashboards": "", "panels": "" }, + "name-cell": { + "no-items": "" + }, "new-folder-form": { "cancel-label": "", "create-label": "", @@ -1128,6 +1288,28 @@ } }, "edit-pane": { + "add": { + "lib-panel": { + "heading": "", + "title": "" + }, + "panel": { + "heading": "", + "title": "" + }, + "paste-panel": { + "heading": "", + "title": "" + }, + "row": { + "heading": "", + "title": "" + }, + "tab": { + "heading": "", + "title": "" + } + }, "elements": { "dashboard": "", "objects": "", @@ -1318,11 +1500,6 @@ }, "toolbar": { "add": "", - "add-panel": "", - "add-panel-description": "", - "add-panel-lib": "", - "add-row": "", - "add-tab": "", "alert-rules": "", "back-to-dashboard": "", "dashboard-settings": { @@ -1345,7 +1522,6 @@ "label": "", "tooltip": "" }, - "libray-panel-description": "", "mark-favorite": "", "more-save-options": "", "open-original": "", @@ -1354,7 +1530,6 @@ "playlist-stop": "", "public-dashboard": "", "refresh": "", - "row-description": "", "save": "", "save-dashboard": { "label": "", @@ -1374,7 +1549,6 @@ "share-button": "", "show-hidden-elements": "", "switch-old-dashboard": "", - "tabs-description": "", "unlink-library-panel": "", "unmark-favorite": "" }, @@ -1429,6 +1603,17 @@ "it-cannot-contain-or": "", "least-valid-character": "" }, + "dashboard-preview-banner": { + "not-saved": "", + "not-yet-saved": "", + "open-pull-request-in-git-hub": "", + "title-dashboard-loaded-branch-git-hub": "", + "title-dashboard-loaded-external-repository": "", + "title-dashboard-loaded-request-git-hub": "", + "title-error-loading-dashboard": "", + "value-not-saved": "", + "view-pull-request-in-git-hub": "" + }, "provisioned-resource-delete-modal": { "file-path": "", "managed-by-version-control": "", @@ -2342,9 +2527,32 @@ } }, "library-panels": { + "add-library-panel-modal": { + "title-create-library-panel": "" + }, + "confirm": { + "delete-panel": "" + }, + "delete-library-panel-modal": { + "cancel": "", + "delete": "", + "title-delete-library-panel": "" + }, "empty-state": { "message": "" }, + "folder-link": { + "dashboards": "" + }, + "has-connected-dashboards": { + "dashboard-name": "" + }, + "library-panels-search": { + "placeholder-search-by-name-or-description": "" + }, + "loading-indicator": { + "loading-library-panel": "" + }, "loading-panel-text": "", "modal": { "button-cancel": "", @@ -2356,9 +2564,20 @@ "title": "", "body_other": "" }, + "panel-library-options-group": { + "create-new-library-panel": "" + }, "save": { "error": "", "success": "" + }, + "save-library-panel-modal": { + "cancel": "", + "dashboard-name": "", + "discard": "", + "loading-connected-dashboards": "", + "placeholder-search-affected-dashboards": "", + "update-all": "" } }, "link": { @@ -2614,12 +2833,6 @@ "link-title": "", "title": "" }, - "public-preview": { - "button-text": "", - "message": "", - "message-cloud": "", - "title": "" - }, "resource-details": { "dismiss-button": "", "error-messages": { @@ -3106,6 +3319,27 @@ "error": "" } }, + "org": { + "new-org-page": { + "create": "", + "label-organization-name": "", + "placeholder-org-name": "" + }, + "org-profile": { + "label-organization-name": "", + "label-organization-profile": "", + "update-organization-name": "" + }, + "user-invite-form": { + "back": "", + "label-email-or-username": "", + "label-name": "", + "label-send-invite-email": "", + "placeholder-optional": "", + "role": "", + "submit": "" + } + }, "panel": { "header-menu": { "copy": "", @@ -3136,6 +3370,19 @@ "panel-type-filter": { "clear-button": "" }, + "playlist": { + "start-modal": { + "description-customize-dashboard-elements-visibility": "", + "description-panel-heights-adjusted-screen": "", + "label-autofit": "", + "label-dashboard-links": "", + "label-display-dashboard-controls": "", + "label-mode": "", + "label-time-and-refresh": "", + "label-variables": "", + "title-start-playlist": "" + } + }, "playlist-edit": { "error-prefix": "", "form": { @@ -3953,11 +4200,37 @@ "placeholder": "" }, "teams": { + "create-team": { + "create": "", + "label-name": "", + "label-role": "" + }, "empty-state": { "button-title": "", "message": "", "pro-tip": "", "title": "" + }, + "team-group-sync": { + "add-group": "", + "external-group-id": "", + "external-group-sync": "", + "title-there-external-groups": "" + }, + "team-list": { + "columns": { + "title-edit-team": "" + }, + "new-team": "", + "placeholder-search-teams": "" + }, + "team-settings": { + "label-email": "", + "label-name": "", + "label-numerical-identifier": "", + "label-role": "", + "label-team-details": "", + "save": "" } }, "theme-preview": { diff --git a/scripts/releasefinder.sh b/scripts/releasefinder.sh new file mode 100755 index 00000000000..06b287f691e --- /dev/null +++ b/scripts/releasefinder.sh @@ -0,0 +1,150 @@ +#!/bin/bash + +# This script finds which Grafana releases include a specific commit. +# It checks both release branches and tags to determine: +# 1. Which release branches contain the commit +# 2. The first release tag that included the commit +# 3. Which release tags include the commit +# 4. The first release that included the commit +# +# Usage: ./scripts/releasefinder.sh +# The commit hash can be either: +# - Full hash (e.g., 1a2b3c4d5e6f7g8h9i0j1k2l3m4n5o6p7q8r9s0t) +# - Short hash (e.g., 1a2b3c4d) +# +# Example: ./scripts/releasefinder.sh a1b2c3d4e5f6 +# +# If you get a "Permission denied" error, make the script executable with: +# chmod +x scripts/releasefinder.sh + +# Check if script is executable +if [ ! -x "$0" ]; then + echo "Error: This script is not executable." + echo "To fix this, run: chmod +x $0" + echo "Then try running the script again." + exit 1 +fi + +# Check if a commit hash was provided +if [ $# -ne 1 ]; then + echo "Usage: $0 " + echo "The commit hash can be either:" + echo " - Full hash (e.g., 1a2b3c4d5e6f7g8h9i0j1k2l3m4n5o6p7q8r9s0t)" + echo " - Short hash (e.g., 1a2b3c4d)" + echo "Example: $0 a1b2c3d4e5f6" + exit 1 +fi + +COMMIT_HASH=$1 + +# Validate that the commit exists +if ! git cat-file -t "$COMMIT_HASH" >/dev/null 2>&1; then + echo "Error: Commit $COMMIT_HASH not found in repository" + echo "Make sure you've provided a valid commit hash (full or short)" + exit 1 +fi + +echo "Fetching latest remote information..." +git fetch --all --tags --prune 2>/dev/null + +echo "Finding release branches containing the commit..." +echo "Finding tags associated with the commit..." +echo + +echo "Results for commit: $COMMIT_HASH" +echo "=============================================" +echo + +# Get commit details +echo "Commit details:" +echo " Author: $(git log -1 --format="%an <%ae>" "$COMMIT_HASH")" +echo " Date: $(git log -1 --format="%ad" --date=iso "$COMMIT_HASH")" +echo + +# Check for backport information +echo "Backport information:" +if git log -1 --pretty=format:"%B" "$COMMIT_HASH" | grep -q "cherry picked from commit"; then + ORIGINAL_COMMIT=$(git log -1 --pretty=format:"%B" "$COMMIT_HASH" | grep "cherry picked from commit" | sed 's/.*cherry picked from commit \([a-f0-9]*\).*/\1/') + echo " This is a backport from commit: $ORIGINAL_COMMIT" + echo " Original commit details:" + echo " Author: $(git log -1 --format="%an <%ae>" "$ORIGINAL_COMMIT")" + echo " Date: $(git log -1 --format="%ad" --date=iso "$ORIGINAL_COMMIT")" +else + echo " Not a backport" +fi +echo + +# Arrays to store results +declare -a release_branches=() +declare -a direct_tags=() +declare -a included_tags=() + +# First check all release branches (including security releases) +for branch in $(git branch -r | grep -E 'origin/release-[0-9]+\.[0-9]+\.[0-9]+(\+security-[0-9]{2})?$' | sed 's/origin\///'); do + # Check if the commit is in this branch's history + if git merge-base --is-ancestor "$COMMIT_HASH" "origin/$branch" 2>/dev/null; then + release_branches+=("$branch") + fi +done + +# Then check all version tags (including security releases) +for tag in $(git tag | sort -V); do + # Skip non-version tags + if ! [[ $tag =~ ^v[0-9]+\.[0-9]+\.[0-9]+(\+security-[0-9]{2})?$ ]]; then + continue + fi + + # Check if the commit is in this tag + if git merge-base --is-ancestor "$COMMIT_HASH" "$tag" 2>/dev/null; then + # If this is the first tag containing the commit, it's the initial release tag + if [ ${#direct_tags[@]} -eq 0 ]; then + direct_tags+=("$tag") + else + included_tags+=("$tag") + fi + fi +done + +# Print release branches +echo "Included in release branches:" +if [ ${#release_branches[@]} -eq 0 ]; then + echo " None" +else + for branch in "${release_branches[@]}"; do + # Convert branch name to tag format (e.g., release-11.5.0 -> v11.5.0) + tag_version="v${branch#release-}" + # Check if tag exists + if git tag | grep -q "^$tag_version$"; then + echo " - $branch" + else + echo " - $branch (release for this branch upcoming)" + fi + done | sort -V +fi +echo + +# Print initial release tag +echo "Initial release tag (the first release in which this commit was included):" +if [ ${#direct_tags[@]} -eq 0 ]; then + echo " None" +else + printf " - %s\n" "${direct_tags[@]}" | sort -V +fi +echo + +# Print included tags +echo "Included in these release tags (the subsequent releases that included this commit):" +if [ ${#included_tags[@]} -eq 0 ]; then + echo " None" +else + printf " - %s\n" "${included_tags[@]}" | sort -V +fi +echo + +# Find first release +if [ ${#direct_tags[@]} -gt 0 ]; then + first_release=$(printf "%s\n" "${direct_tags[@]}" | sort -V | head -n1) + echo "First included in release: $first_release" +else + echo "Not included in any releases" +fi diff --git a/yarn.lock b/yarn.lock index 6e95804750d..78a70c82618 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1416,7 +1416,7 @@ __metadata: languageName: node linkType: hard -"@babel/runtime@npm:7.26.10": +"@babel/runtime@npm:7.26.10, @babel/runtime@npm:^7.0.0, @babel/runtime@npm:^7.1.2, @babel/runtime@npm:^7.10.1, @babel/runtime@npm:^7.11.1, @babel/runtime@npm:^7.11.2, @babel/runtime@npm:^7.12.0, @babel/runtime@npm:^7.12.1, @babel/runtime@npm:^7.12.13, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.13.10, @babel/runtime@npm:^7.15.4, @babel/runtime@npm:^7.16.3, @babel/runtime@npm:^7.17.8, @babel/runtime@npm:^7.18.0, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.20.0, @babel/runtime@npm:^7.20.7, @babel/runtime@npm:^7.23.2, @babel/runtime@npm:^7.23.9, @babel/runtime@npm:^7.24.5, @babel/runtime@npm:^7.24.7, @babel/runtime@npm:^7.25.0, @babel/runtime@npm:^7.25.6, @babel/runtime@npm:^7.25.7, @babel/runtime@npm:^7.3.1, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.7.6, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.8.7": version: 7.26.10 resolution: "@babel/runtime@npm:7.26.10" dependencies: @@ -1425,15 +1425,6 @@ __metadata: languageName: node linkType: hard -"@babel/runtime@npm:^7.0.0, @babel/runtime@npm:^7.1.2, @babel/runtime@npm:^7.10.1, @babel/runtime@npm:^7.11.1, @babel/runtime@npm:^7.11.2, @babel/runtime@npm:^7.12.0, @babel/runtime@npm:^7.12.1, @babel/runtime@npm:^7.12.13, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.13.10, @babel/runtime@npm:^7.15.4, @babel/runtime@npm:^7.16.3, @babel/runtime@npm:^7.17.8, @babel/runtime@npm:^7.18.0, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.20.0, @babel/runtime@npm:^7.20.7, @babel/runtime@npm:^7.23.2, @babel/runtime@npm:^7.23.9, @babel/runtime@npm:^7.24.5, @babel/runtime@npm:^7.24.7, @babel/runtime@npm:^7.25.0, @babel/runtime@npm:^7.25.6, @babel/runtime@npm:^7.25.7, @babel/runtime@npm:^7.3.1, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.7.6, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.8.7": - version: 7.26.9 - resolution: "@babel/runtime@npm:7.26.9" - dependencies: - regenerator-runtime: "npm:^0.14.0" - checksum: 10/08edd07d774eafbf157fdc8450ed6ddd22416fdd8e2a53e4a00349daba1b502c03ab7f7ad3ad3a7c46b9a24d99b5697591d0f852ee2f84642082ef7dda90b83d - languageName: node - linkType: hard - "@babel/template@npm:^7.22.5, @babel/template@npm:^7.24.7, @babel/template@npm:^7.25.9, @babel/template@npm:^7.26.9, @babel/template@npm:^7.3.3": version: 7.26.9 resolution: "@babel/template@npm:7.26.9" @@ -3480,11 +3471,11 @@ __metadata: languageName: unknown linkType: soft -"@grafana/scenes-react@npm:6.3.1": - version: 6.3.1 - resolution: "@grafana/scenes-react@npm:6.3.1" +"@grafana/scenes-react@npm:6.5.2": + version: 6.5.2 + resolution: "@grafana/scenes-react@npm:6.5.2" dependencies: - "@grafana/scenes": "npm:6.3.1" + "@grafana/scenes": "npm:6.5.2" lru-cache: "npm:^10.2.2" react-use: "npm:^17.4.0" peerDependencies: @@ -3496,13 +3487,13 @@ __metadata: react: ^18.0.0 react-dom: ^18.0.0 react-router-dom: ^6.28.0 - checksum: 10/77dd6f7bbe3699ece25435623a78f2ef5d831ab83b59612baa362581f7c3f6c02cb420b5acfff4e3d266872a694e08b98340370468bb246d102b20e9fcc79438 + checksum: 10/c922fea8ca86db49651a818640f32a7a35eeabcf886b52309ccd515a17bd071d19814e578158540299a4d01824ff3b9a05667bb86c91d87885c8ba7d0810c353 languageName: node linkType: hard -"@grafana/scenes@npm:6.3.1": - version: 6.3.1 - resolution: "@grafana/scenes@npm:6.3.1" +"@grafana/scenes@npm:6.5.2": + version: 6.5.2 + resolution: "@grafana/scenes@npm:6.5.2" dependencies: "@floating-ui/react": "npm:^0.26.16" "@leeoniya/ufuzzy": "npm:^1.0.16" @@ -3520,7 +3511,7 @@ __metadata: react: ^18.0.0 react-dom: ^18.0.0 react-router-dom: ^6.28.0 - checksum: 10/98e3e96b9ce12ae67aa458819dc9eed1eabd1b7421e768074bd97a0fa2a0b1e080d4121b1ddd357ce41ccaf9252439163aae7e612f12cba878ee6dca34f73831 + checksum: 10/267af84b2cc86acc547afbacb661aeffdd67388146bbf0f682ddd6577992973766daf7f8b35fb79ef5170ed10808f73294bfc8565b90fc4dd044dd738a34eba8 languageName: node linkType: hard @@ -17692,8 +17683,8 @@ __metadata: "@grafana/prometheus": "workspace:*" "@grafana/runtime": "workspace:*" "@grafana/saga-icons": "workspace:*" - "@grafana/scenes": "npm:6.3.1" - "@grafana/scenes-react": "npm:6.3.1" + "@grafana/scenes": "npm:6.5.2" + "@grafana/scenes-react": "npm:6.5.2" "@grafana/schema": "workspace:*" "@grafana/sql": "workspace:*" "@grafana/tsconfig": "npm:^2.0.0" @@ -30533,7 +30524,7 @@ __metadata: languageName: node linkType: hard -"uuid@npm:11.0.5, uuid@npm:^11.0.0, uuid@npm:^11.0.2": +"uuid@npm:11.0.5": version: 11.0.5 resolution: "uuid@npm:11.0.5" bin: @@ -30551,7 +30542,7 @@ __metadata: languageName: node linkType: hard -"uuid@npm:^11.0.5": +"uuid@npm:^11.0.0, uuid@npm:^11.0.2, uuid@npm:^11.0.5": version: 11.1.0 resolution: "uuid@npm:11.1.0" bin: