diff --git a/go.mod b/go.mod index 2092178d565..81dd22b8e23 100644 --- a/go.mod +++ b/go.mod @@ -79,7 +79,7 @@ require ( github.com/gorilla/mux v1.8.1 // @grafana/grafana-backend-group github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // @grafana/grafana-app-platform-squad github.com/grafana/alerting v0.0.0-20250508220812-83b6de6b0a35 // @grafana/alerting-backend - github.com/grafana/authlib v0.0.0-20250422131730-e8482efe6b8a // @grafana/identity-access-team + github.com/grafana/authlib v0.0.0-20250430134519-13c42d09f9f5 // @grafana/identity-access-team github.com/grafana/authlib/types v0.0.0-20250325095148-d6da9c164a7d // @grafana/identity-access-team github.com/grafana/dataplane/examples v0.0.1 // @grafana/observability-metrics github.com/grafana/dataplane/sdata v0.0.9 // @grafana/observability-metrics @@ -193,7 +193,7 @@ require ( gopkg.in/mail.v2 v2.3.1 // @grafana/grafana-backend-group gopkg.in/yaml.v2 v2.4.0 // @grafana/alerting-backend gopkg.in/yaml.v3 v3.0.1 // @grafana/alerting-backend - k8s.io/api v0.32.3 // @grafana/grafana-app-platform-squad + k8s.io/api v0.32.3 // indirect; @grafana/grafana-app-platform-squad k8s.io/apimachinery v0.32.3 // @grafana/grafana-app-platform-squad k8s.io/apiserver v0.32.3 // @grafana/grafana-app-platform-squad k8s.io/client-go v0.32.3 // @grafana/grafana-app-platform-squad @@ -452,12 +452,10 @@ require ( github.com/mithrandie/go-file/v2 v2.1.0 // indirect github.com/mithrandie/go-text v1.6.0 // indirect github.com/mithrandie/ternary v1.1.1 // indirect - github.com/moby/spdystream v0.5.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect github.com/mschoch/smat v0.2.0 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect github.com/natefinch/wrap v0.2.0 // indirect github.com/ncruces/go-strftime v0.1.9 // indirect github.com/oapi-codegen/runtime v1.0.0 // indirect diff --git a/go.sum b/go.sum index e4d92797fcf..8d1bf535d33 100644 --- a/go.sum +++ b/go.sum @@ -1571,8 +1571,8 @@ github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5T github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA= github.com/grafana/alerting v0.0.0-20250508220812-83b6de6b0a35 h1:/FvrKg5ZtJ09oWOt91lIPpeWtWGcNqXv/9UOQaRDyQE= github.com/grafana/alerting v0.0.0-20250508220812-83b6de6b0a35/go.mod h1:pMfhRxL2LZ3Pm8iy7VcVsb9CLYuBtjFYbf1oxgx7yFA= -github.com/grafana/authlib v0.0.0-20250422131730-e8482efe6b8a h1:irEH0Abl6mKbkPx/xtmB5Wai4ipzEB6hGPNsLya/p9Y= -github.com/grafana/authlib v0.0.0-20250422131730-e8482efe6b8a/go.mod h1:PBtQaXwkFu4BAt2aXsR7w8p8NVpdjV5aJYhqRDei9Us= +github.com/grafana/authlib v0.0.0-20250430134519-13c42d09f9f5 h1:qu6+b9tY6E/CgKrLh8srjtvKOMCxdwk46jRqURjJ61s= +github.com/grafana/authlib v0.0.0-20250430134519-13c42d09f9f5/go.mod h1:PBtQaXwkFu4BAt2aXsR7w8p8NVpdjV5aJYhqRDei9Us= github.com/grafana/authlib/types v0.0.0-20250325095148-d6da9c164a7d h1:34E6btDAhdDOiSEyrMaYaHwnJpM8w9QKzVQZIBzLNmM= github.com/grafana/authlib/types v0.0.0-20250325095148-d6da9c164a7d/go.mod h1:qeWYbnWzaYGl88JlL9+DsP1GT2Cudm58rLtx13fKZdw= github.com/grafana/dataplane/examples v0.0.1 h1:K9M5glueWyLoL4//H+EtTQq16lXuHLmOhb6DjSCahzA= @@ -2029,8 +2029,6 @@ github.com/mithrandie/ternary v1.1.1 h1:k/joD6UGVYxHixYmSR8EGgDFNONBMqyD373xT4QR github.com/mithrandie/ternary v1.1.1/go.mod h1:0D9Ba3+09K2TdSZO7/bFCC0GjSXetCvYuYq0u8FY/1g= github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= -github.com/moby/spdystream v0.5.0 h1:7r0J1Si3QO/kjRitvSLVVFUjxMEb/YLj6S9FF62JBCU= -github.com/moby/spdystream v0.5.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI= github.com/mocktools/go-smtp-mock/v2 v2.3.1 h1:wq75NDSsOy5oHo/gEQQT0fRRaYKRqr1IdkjhIPXxagM= github.com/mocktools/go-smtp-mock/v2 v2.3.1/go.mod h1:h9AOf/IXLSU2m/1u4zsjtOM/WddPwdOUBz56dV9f81M= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -2053,7 +2051,6 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8m github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= github.com/nakagami/firebirdsql v0.0.0-20190310045651-3c02a58cfed8/go.mod h1:86wM1zFnC6/uDBfZGNwB65O+pR2OFi5q/YQaEUid1qA= github.com/natefinch/wrap v0.2.0 h1:IXzc/pw5KqxJv55gV0lSOcKHYuEZPGbQrOOXr/bamRk= diff --git a/go.work.sum b/go.work.sum index 0cc7fc74429..0045e12a04d 100644 --- a/go.work.sum +++ b/go.work.sum @@ -1123,8 +1123,6 @@ github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB7 github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo= -github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA= github.com/grafana/alerting v0.0.0-20250129195454-3e5b80036b7a/go.mod h1:QsnoKX/iYZxA4Cv+H+wC7uxutBD8qi8ZW5UJvD2TYmU= github.com/grafana/alerting v0.0.0-20250310104713-16b885f1c79e/go.mod h1:HfvjmU3UqCIpoy9Z2wgKGrZ4A5vz+yQlP9ZXvCfEkiA= github.com/grafana/alerting v0.0.0-20250403153742-418bc7118d05 h1:hMzOzI/S0nkZt0nUqpfAa4Rdb+YL8z8oG3pl4Jb31h8= @@ -1378,6 +1376,8 @@ github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f h1:2+myh5ml7lgEU/5 github.com/mithrandie/readline-csvq v1.3.0 h1:VTJEOGouJ8j27jJCD4kBBbNTxM0OdBvE1aY1tMhlqE8= github.com/mithrandie/readline-csvq v1.3.0/go.mod h1:FKyYqDgf/G4SNov7SMFXRWO6LQLXIOeTog/NB97FZl0= github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg= +github.com/moby/spdystream v0.5.0 h1:7r0J1Si3QO/kjRitvSLVVFUjxMEb/YLj6S9FF62JBCU= +github.com/moby/spdystream v0.5.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI= github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw= github.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs= github.com/moby/sys/mountinfo v0.5.0 h1:2Ks8/r6lopsxWi9m58nlwjaeSzUX9iiL1vj5qB/9ObI= @@ -1397,6 +1397,7 @@ github.com/mozilla/tls-observatory v0.0.0-20210609171429-7bc42856d2e5/go.mod h1: github.com/mrunalp/fileutils v0.5.0 h1:NKzVxiH7eSk+OQ4M+ZYW1K6h27RUV3MI6NUTsHhU6Z4= github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= +github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus= github.com/nakagami/firebirdsql v0.0.0-20190310045651-3c02a58cfed8 h1:P48LjvUQpTReR3TQRbxSeSBsMXzfK0uol7eRcr7VBYQ= github.com/natessilva/dag v0.0.0-20180124060714-7194b8dcc5c4 h1:dnMxwus89s86tI8rcGVp2HwZzlz7c5o92VOy7dSckBQ= github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= @@ -1923,8 +1924,6 @@ golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ug golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ= golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= -golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8= -golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw= golang.org/x/exp v0.0.0-20230315142452-642cacee5cc0/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w= golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w= @@ -1969,8 +1968,6 @@ golang.org/x/net v0.32.0/go.mod h1:CwU0IoeOlnQQWJ6ioyFrfRuomB8GKF6KbYXZVyeXNfs= golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= -golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= -golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= golang.org/x/oauth2 v0.15.0/go.mod h1:q48ptWNTY5XWf+JNten23lcvHpLJ0ZSxF5ttTHKVCAM= golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8= golang.org/x/oauth2 v0.19.0/go.mod h1:vYi7skDa1x015PmRRYZ7+s1cWyPgrPiSYRe4rnsexc8= @@ -2004,22 +2001,16 @@ golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= -golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457 h1:zf5N6UOrA487eEFacMePxjXAJctxKmyjKUsjA11Uzuk= golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457/go.mod h1:pRgIJT+bRLFKnoM1ldnzKoxTIn14Yxz928LQRYYgIN0= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= -golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg= -golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ= golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= -golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= -golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= @@ -2043,8 +2034,6 @@ golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0 golang.org/x/tools v0.28.0/go.mod h1:dcIOrVd3mfQKTgrDVQHqCPMWy6lnhfhtX3hLXYVLfRw= golang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY= golang.org/x/tools v0.31.0/go.mod h1:naFTU+Cev749tSJRXJlna0T3WxKvb1kWEx15xA4SdmQ= -golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc= -golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI= golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0 h1:OE9mWmgKkjJyEmDAAtGMPjXu+YNeGvK9VTSHY6+Qihc= diff --git a/packages/grafana-data/src/types/featureToggles.gen.ts b/packages/grafana-data/src/types/featureToggles.gen.ts index 7f5f09f291d..15a3e6ba701 100644 --- a/packages/grafana-data/src/types/featureToggles.gen.ts +++ b/packages/grafana-data/src/types/featureToggles.gen.ts @@ -520,6 +520,10 @@ export interface FeatureToggles { */ kubernetesAggregator?: boolean; /** + * Enable CAP token based authentication in grafana's embedded kube-aggregator + */ + kubernetesAggregatorCapTokenAuth?: boolean; + /** * Enable new expression parser */ expressionParser?: boolean; diff --git a/pkg/apimachinery/go.mod b/pkg/apimachinery/go.mod index b4f18da2a7a..331b1b13068 100644 --- a/pkg/apimachinery/go.mod +++ b/pkg/apimachinery/go.mod @@ -3,7 +3,7 @@ module github.com/grafana/grafana/pkg/apimachinery go 1.24.3 require ( - github.com/grafana/authlib v0.0.0-20250422131730-e8482efe6b8a // @grafana/identity-access-team + github.com/grafana/authlib v0.0.0-20250430134519-13c42d09f9f5 // @grafana/identity-access-team github.com/grafana/authlib/types v0.0.0-20250325095148-d6da9c164a7d // @grafana/identity-access-team github.com/stretchr/testify v1.10.0 k8s.io/apimachinery v0.32.3 diff --git a/pkg/apimachinery/go.sum b/pkg/apimachinery/go.sum index 11c110e5347..f592b41e72a 100644 --- a/pkg/apimachinery/go.sum +++ b/pkg/apimachinery/go.sum @@ -32,8 +32,8 @@ github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/grafana/authlib v0.0.0-20250422131730-e8482efe6b8a h1:irEH0Abl6mKbkPx/xtmB5Wai4ipzEB6hGPNsLya/p9Y= -github.com/grafana/authlib v0.0.0-20250422131730-e8482efe6b8a/go.mod h1:PBtQaXwkFu4BAt2aXsR7w8p8NVpdjV5aJYhqRDei9Us= +github.com/grafana/authlib v0.0.0-20250430134519-13c42d09f9f5 h1:qu6+b9tY6E/CgKrLh8srjtvKOMCxdwk46jRqURjJ61s= +github.com/grafana/authlib v0.0.0-20250430134519-13c42d09f9f5/go.mod h1:PBtQaXwkFu4BAt2aXsR7w8p8NVpdjV5aJYhqRDei9Us= github.com/grafana/authlib/types v0.0.0-20250325095148-d6da9c164a7d h1:34E6btDAhdDOiSEyrMaYaHwnJpM8w9QKzVQZIBzLNmM= github.com/grafana/authlib/types v0.0.0-20250325095148-d6da9c164a7d/go.mod h1:qeWYbnWzaYGl88JlL9+DsP1GT2Cudm58rLtx13fKZdw= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= diff --git a/pkg/apiserver/go.mod b/pkg/apiserver/go.mod index 8cefe3ae0b1..073d09f5575 100644 --- a/pkg/apiserver/go.mod +++ b/pkg/apiserver/go.mod @@ -46,7 +46,7 @@ require ( github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e // indirect github.com/google/uuid v1.6.0 // indirect github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect - github.com/grafana/authlib v0.0.0-20250422131730-e8482efe6b8a // indirect + github.com/grafana/authlib v0.0.0-20250430134519-13c42d09f9f5 // indirect github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect github.com/grpc-ecosystem/go-grpc-prometheus v1.2.1-0.20191002090509-6af20e3a5340 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect diff --git a/pkg/apiserver/go.sum b/pkg/apiserver/go.sum index 6a3d38979be..969b11a7420 100644 --- a/pkg/apiserver/go.sum +++ b/pkg/apiserver/go.sum @@ -81,8 +81,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo= github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA= -github.com/grafana/authlib v0.0.0-20250422131730-e8482efe6b8a h1:irEH0Abl6mKbkPx/xtmB5Wai4ipzEB6hGPNsLya/p9Y= -github.com/grafana/authlib v0.0.0-20250422131730-e8482efe6b8a/go.mod h1:PBtQaXwkFu4BAt2aXsR7w8p8NVpdjV5aJYhqRDei9Us= +github.com/grafana/authlib v0.0.0-20250430134519-13c42d09f9f5 h1:qu6+b9tY6E/CgKrLh8srjtvKOMCxdwk46jRqURjJ61s= +github.com/grafana/authlib v0.0.0-20250430134519-13c42d09f9f5/go.mod h1:PBtQaXwkFu4BAt2aXsR7w8p8NVpdjV5aJYhqRDei9Us= github.com/grafana/authlib/types v0.0.0-20250325095148-d6da9c164a7d h1:34E6btDAhdDOiSEyrMaYaHwnJpM8w9QKzVQZIBzLNmM= github.com/grafana/authlib/types v0.0.0-20250325095148-d6da9c164a7d/go.mod h1:qeWYbnWzaYGl88JlL9+DsP1GT2Cudm58rLtx13fKZdw= github.com/grafana/grafana-app-sdk/logging v0.35.1 h1:taVpl+RoixTYl0JBJGhH+fPVmwA9wvdwdzJTZsv9buM= diff --git a/pkg/extensions/enterprise_imports.go b/pkg/extensions/enterprise_imports.go index 7e96faa86ac..f54f5d134c5 100644 --- a/pkg/extensions/enterprise_imports.go +++ b/pkg/extensions/enterprise_imports.go @@ -32,9 +32,18 @@ import ( _ "golang.org/x/time/rate" _ "xorm.io/builder" + _ "github.com/grafana/authlib/authn" + _ "github.com/grafana/authlib/authz" + _ "github.com/grafana/authlib/cache" + _ "github.com/grafana/authlib/grpcutils" + _ "github.com/grafana/authlib/types" _ "github.com/grafana/dskit/backoff" _ "github.com/grafana/dskit/flagext" _ "github.com/grafana/e2e" _ "github.com/grafana/gofpdf" _ "github.com/grafana/gomemcache/memcache" + + _ "k8s.io/kube-aggregator/pkg/apiserver/scheme" + _ "k8s.io/kube-aggregator/pkg/generated/openapi" + _ "k8s.io/kube-aggregator/pkg/registry/apiservice/rest" ) diff --git a/pkg/server/wireexts_oss.go b/pkg/server/wireexts_oss.go index 77ff00b960c..28f116c2f9c 100644 --- a/pkg/server/wireexts_oss.go +++ b/pkg/server/wireexts_oss.go @@ -19,6 +19,7 @@ import ( "github.com/grafana/grafana/pkg/services/anonymous" "github.com/grafana/grafana/pkg/services/anonymous/anonimpl" "github.com/grafana/grafana/pkg/services/anonymous/validator" + "github.com/grafana/grafana/pkg/services/apiserver/aggregatorrunner" builder "github.com/grafana/grafana/pkg/services/apiserver/builder" "github.com/grafana/grafana/pkg/services/apiserver/standalone" "github.com/grafana/grafana/pkg/services/auth" @@ -121,6 +122,7 @@ var wireExtsBasicSet = wire.NewSet( wire.Struct(new(unified.Options), "*"), unified.ProvideUnifiedStorageClient, builder.ProvideDefaultBuildHandlerChainFuncFromBuilders, + aggregatorrunner.ProvideNoopAggregatorConfigurator, ) var wireExtsSet = wire.NewSet( diff --git a/pkg/services/apiserver/aggregator/README.md b/pkg/services/apiserver/aggregator/README.md deleted file mode 100644 index 5216b23873c..00000000000 --- a/pkg/services/apiserver/aggregator/README.md +++ /dev/null @@ -1,127 +0,0 @@ -# aggregator - -This is a package that is intended to power the aggregation of microservices within Grafana. The concept -as well as implementation is largely borrowed from [kube-aggregator](https://github.com/kubernetes/kube-aggregator). - -## Why aggregate services? - -Grafana's future architecture will entail the same API Server design as that of Kubernetes API Servers. API Servers -provide a standard way of stitching together API Groups through discovery and shared routing patterns that allows -them to aggregate to a parent API Server in a seamless manner. Since we desire to break Grafana monolith up into -more functionally divided microservices, aggregation does the job of still being able to provide these services -under a single address. Other benefits of aggregation include free health checks and being able to independently -roll out features for each service without downtime. - -To read more about the concept, see -[here](https://kubernetes.io/docs/tasks/extend-kubernetes/setup-extension-api-server/). - -Note that this aggregation will be a totally internal detail to Grafana. External fully functional API Servers that -may themselves act as parent API Servers to Grafana will never be made aware of internal Grafana API Servers. -Thus, any `APIService` objects corresponding to Grafana's API groups will take the address of -Grafana's main API Server (the one that bundles grafana-aggregator). - -Also, note that the single binary OSS offering of Grafana doesn't make use of the aggregator component at all, instead -opting for local installation of all the Grafana API groups. - -### kube-aggregator versus grafana-aggregator - -The `grafana-aggregator` component will work similarly to how `kube-aggregator` works for `kube-apiserver`, the major -difference being that it doesn't require core V1 APIs such as `Service`. Early on, we decided to not have core V1 -APIs in the root Grafana API Server. In order to still be able to implement aggregation, we do the following in this Go -package: - -1. We do not start the core shared informer factories as well as any default controllers that utilize them. -This is achieved using `DisabledPostStartHooks` facility under the GenericAPIServer's RecommendedConfig. -2. We provide an `externalname` Kind API implementation under `service.grafana.app` group which works functionally -equivalent to the idea with the same name under `core/v1/Service`. -3. Lastly, we swap the default available condition controller with the custom one written by us. This one is based on -our `externalname` (`service.grafana.app`) implementation. We register separate `PostStartHooks` -using `AddPostStartHookOrDie` on the GenericAPIServer to start the corresponding custom controller as well as -requisite informer factories for our own `externalname` Kind. -4. For now, we bundle apiextensions-apiserver under our aggregator component. This is slightly different from K8s -where kube-apiserver is called the top-level component and controlplane, aggregator and apiextensions-apiserver -live under that instead. - -### Gotchas (Pay Attention) - -1. `grafana-aggregator` uses file storage under `data/grafana-apiserver` (`apiregistration.k8s.io`, -`service.grafana.app`). Thus, any restarts will still have any prior configured aggregation in effect. -2. During local development, ensure you start the aggregated service after launching the aggregator. This is -so you have TLS and kubeconfig available for use with example aggregated api servers. -3. Ensure you have `grafanaAPIServerWithExperimentalAPIs = false` in your custom.ini. Otherwise, the example -service the following guide uses for the aggregation test is bundled as a `Local` `APIService` and will cause -configuration overwrites on startup. - -## Testing aggregation locally - -1. Generate the PKI using `openssl` (for development purposes, we will use the CN of `system:masters`): - ```shell - ./hack/make-aggregator-pki.sh - ``` -2. Configure the aggregator: - ```ini - [feature_toggles] - grafanaAPIServerEnsureKubectlAccess = true - ; disable the experimental APIs flag to disable bundling of the example service locally - grafanaAPIServerWithExperimentalAPIs = false - kubernetesAggregator = true - - [grafana-apiserver] - proxy_client_cert_file = ./data/grafana-aggregator/client.crt - proxy_client_key_file = ./data/grafana-aggregator/client.key - ``` -3. Start the server - ```shell - make run - ``` -4. In another tab, apply the manifests: - ```shell - export KUBECONFIG=$PWD/data/grafana-apiserver/grafana.kubeconfig - kubectl apply -f ./pkg/services/apiserver/aggregator/examples/manual-test/ - # SAMPLE OUTPUT - # apiservice.apiregistration.k8s.io/v0alpha1.example.grafana.app created - # externalname.service.grafana.app/example-apiserver created - - kubectl get apiservice - # SAMPLE OUTPUT - # NAME SERVICE AVAILABLE AGE - # v0alpha1.example.grafana.app grafana/example-apiserver False (FailedDiscoveryCheck) 29m - ``` -5. In another tab, start the example microservice that will be aggregated by the parent apiserver: - ```shell - go run ./pkg/cmd/grafana apiserver \ - --runtime-config=example.grafana.app/v0alpha1=true \ - --secure-port 7443 \ - --tls-cert-file $PWD/data/grafana-aggregator/server.crt \ - --tls-private-key-file $PWD/data/grafana-aggregator/server.key \ - --requestheader-client-ca-file=$PWD/data/grafana-aggregator/ca.crt \ - --requestheader-extra-headers-prefix=X-Remote-Extra- \ - --requestheader-group-headers=X-Remote-Group \ - --requestheader-username-headers=X-Remote-User \ - -v 10 - ``` -6. After 10 seconds, check `APIService` again. It should report as available. - ```shell - export KUBECONFIG=$PWD/data/grafana-apiserver/grafana.kubeconfig - kubectl get apiservice - # SAMPLE OUTPUT - # NAME SERVICE AVAILABLE AGE - # v0alpha1.example.grafana.app grafana/example-apiserver True 30m - ``` -7. For tear down of the above test: - ```shell - kubectl delete -f ./pkg/services/apiserver/aggregator/examples/ - ``` - -## Testing auto-registration of remote services locally - -A sample aggregation config for remote services is provided under [conf](../../../../conf/aggregation/apiservices.yaml). Provided, you have the following setup in your custom.ini, the apiserver will -register your remotely running services on startup. - -```ini -; in custom.ini -; the bundle is only used when not in dev mode -apiservice_ca_bundle_file = ./data/grafana-aggregator/ca.crt - -remote_services_file = ./pkg/services/apiserver/aggregator/examples/autoregister/apiservices.yaml -``` diff --git a/pkg/services/apiserver/aggregator/aggregator.go b/pkg/services/apiserver/aggregator/aggregator.go deleted file mode 100644 index 7338cbab7bb..00000000000 --- a/pkg/services/apiserver/aggregator/aggregator.go +++ /dev/null @@ -1,509 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -// Provenance-includes-location: https://github.com/kubernetes/kubernetes/blob/master/cmd/kube-apiserver/app/aggregator.go -// Provenance-includes-license: Apache-2.0 -// Provenance-includes-copyright: The Kubernetes Authors. -// Provenance-includes-location: https://github.com/kubernetes/kubernetes/blob/master/cmd/kube-apiserver/app/server.go -// Provenance-includes-license: Apache-2.0 -// Provenance-includes-copyright: The Kubernetes Authors. -// Provenance-includes-location: https://github.com/kubernetes/kubernetes/blob/master/pkg/controlplane/apiserver/apiextensions.go -// Provenance-includes-license: Apache-2.0 -// Provenance-includes-copyright: The Kubernetes Authors. - -package aggregator - -import ( - "crypto/tls" - "fmt" - "io" - "net/http" - "os" - "strings" - "sync" - "time" - - "github.com/prometheus/client_golang/prometheus" - "gopkg.in/yaml.v3" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime/schema" - utilnet "k8s.io/apimachinery/pkg/util/net" - "k8s.io/apimachinery/pkg/util/sets" - genericapiserver "k8s.io/apiserver/pkg/server" - "k8s.io/apiserver/pkg/server/dynamiccertificates" - "k8s.io/apiserver/pkg/server/healthz" - "k8s.io/client-go/informers" - "k8s.io/client-go/kubernetes/fake" - "k8s.io/client-go/tools/cache" - "k8s.io/component-base/metrics" - "k8s.io/component-base/metrics/legacyregistry" - "k8s.io/klog/v2" - v1 "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1" - v1helper "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1/helper" - aggregatorapiserver "k8s.io/kube-aggregator/pkg/apiserver" - aggregatorscheme "k8s.io/kube-aggregator/pkg/apiserver/scheme" - apiregistrationclientset "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset" - apiregistrationclient "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/typed/apiregistration/v1" - apiregistrationInformers "k8s.io/kube-aggregator/pkg/client/informers/externalversions/apiregistration/v1" - "k8s.io/kube-aggregator/pkg/controllers" - "k8s.io/kube-aggregator/pkg/controllers/autoregister" - - servicev0alpha1 "github.com/grafana/grafana/pkg/apis/service/v0alpha1" - servicev0alpha1applyconfiguration "github.com/grafana/grafana/pkg/generated/applyconfiguration/service/v0alpha1" - serviceclientset "github.com/grafana/grafana/pkg/generated/clientset/versioned" - informersv0alpha1 "github.com/grafana/grafana/pkg/generated/informers/externalversions" - "github.com/grafana/grafana/pkg/registry/apis/service" - "github.com/grafana/grafana/pkg/services/apiserver/builder" - "github.com/grafana/grafana/pkg/services/apiserver/options" -) - -// making sure we only register metrics once into legacy registry -var registerIntoLegacyRegistryOnce sync.Once - -//nolint:unused -func _readCABundlePEM(path string, devMode bool) ([]byte, error) { - if devMode { - return nil, nil - } - - // We can ignore the gosec G304 warning on this one because `path` comes - // from Grafana configuration (commandOptions.AggregatorOptions.APIServiceCABundleFile) - //nolint:gosec - f, err := os.Open(path) - if err != nil { - return nil, err - } - defer func() { - if err := f.Close(); err != nil { - klog.Errorf("error closing remote services file: %s", err) - } - }() - - return io.ReadAll(f) -} - -func ReadRemoteServices(path string) ([]RemoteService, error) { - // We can ignore the gosec G304 warning on this one because `path` comes - // from Grafana configuration (commandOptions.AggregatorOptions.RemoteServicesFile) - //nolint:gosec - f, err := os.Open(path) - if err != nil { - return nil, err - } - defer func() { - if err := f.Close(); err != nil { - klog.Errorf("error closing remote services file: %s", err) - } - }() - - rawRemoteServices, err := io.ReadAll(f) - if err != nil { - return nil, err - } - - remoteServices := make([]RemoteService, 0) - if err := yaml.Unmarshal(rawRemoteServices, &remoteServices); err != nil { - return nil, err - } - - return remoteServices, nil -} - -func CreateAggregatorConfig(commandOptions *options.Options, sharedConfig genericapiserver.RecommendedConfig, externalNamesNamespace string) (*Config, error) { - // Create a fake clientset and informers for the k8s v1 API group. - // These are not used in grafana's aggregator because v1 APIs are not available. - fakev1Informers := informers.NewSharedInformerFactory(fake.NewSimpleClientset(), 10*time.Minute) - - serviceClient, err := serviceclientset.NewForConfig(sharedConfig.LoopbackClientConfig) - if err != nil { - return nil, err - } - sharedInformerFactory := informersv0alpha1.NewSharedInformerFactory( - serviceClient, - 5*time.Minute, // this is effectively used as a refresh interval right now. Might want to do something nicer later on. - ) - serviceResolver := NewExternalNameResolver(sharedInformerFactory.Service().V0alpha1().ExternalNames().Lister()) - - aggregatorConfig := &aggregatorapiserver.Config{ - GenericConfig: &genericapiserver.RecommendedConfig{ - Config: sharedConfig.Config, - SharedInformerFactory: fakev1Informers, - ClientConfig: sharedConfig.LoopbackClientConfig, - }, - ExtraConfig: aggregatorapiserver.ExtraConfig{ - DisableRemoteAvailableConditionController: true, - // NOTE: while ProxyTransport can be skipped in the configuration, it allows honoring - // DISABLE_HTTP2, HTTPS_PROXY and NO_PROXY env vars as needed - ProxyTransport: createProxyTransport(), - ServiceResolver: serviceResolver, - }, - } - - if commandOptions.KubeAggregatorOptions.LegacyClientCertAuth { - // NOTE: the availability controller below is a bit different and uses the cert/key pair regardless - // of the legacy bool, this is because we are still using that for discovery requests - aggregatorConfig.ExtraConfig.ProxyClientCertFile = commandOptions.KubeAggregatorOptions.ProxyClientCertFile - aggregatorConfig.ExtraConfig.ProxyClientKeyFile = commandOptions.KubeAggregatorOptions.ProxyClientKeyFile - } - - customExtraConfig := &CustomExtraConfig{ - DiscoveryOnlyProxyClientCertFile: commandOptions.KubeAggregatorOptions.ProxyClientCertFile, - DiscoveryOnlyProxyClientKeyFile: commandOptions.KubeAggregatorOptions.ProxyClientKeyFile, - } - - if err := commandOptions.KubeAggregatorOptions.ApplyTo(aggregatorConfig, commandOptions.RecommendedOptions.Etcd); err != nil { - return nil, err - } - - serviceAPIBuilder := service.NewServiceAPIBuilder() - if err := serviceAPIBuilder.InstallSchema(aggregatorscheme.Scheme); err != nil { - return nil, err - } - APIVersionPriorities[serviceAPIBuilder.GetGroupVersion()] = Priority{Group: 15000, Version: int32(1)} - - // Exit early, if no remote services file is configured - if commandOptions.KubeAggregatorOptions.RemoteServicesFile == "" { - return NewConfig(aggregatorConfig, customExtraConfig, sharedInformerFactory, []builder.APIGroupBuilder{serviceAPIBuilder}, nil), nil - } - - remoteServices, err := ReadRemoteServices(commandOptions.KubeAggregatorOptions.RemoteServicesFile) - if err != nil { - return nil, err - } - - remoteServicesConfig := &RemoteServicesConfig{ - // TODO: in practice, we should only use the insecure flag when commandOptions.ExtraOptions.DevMode == true - // But given the bug in K8s, we are forced to set it to true until the below PR is merged and available - // https://github.com/kubernetes/kubernetes/pull/123808 - InsecureSkipTLSVerify: true, - ExternalNamesNamespace: externalNamesNamespace, - // TODO: CABundle can't be set when insecure is true - // CABundle: caBundlePEM, - Services: remoteServices, - serviceClientSet: serviceClient, - } - - return NewConfig(aggregatorConfig, customExtraConfig, sharedInformerFactory, []builder.APIGroupBuilder{serviceAPIBuilder}, remoteServicesConfig), nil -} - -// CreateAggregatorServer creates an aggregated server to layer into the existing apiserver -// TODO: passing options temporarily as that allows us to pass in cert/key for client into AvailableController but skip it in the aggregator lib -func CreateAggregatorServer(config *Config, delegateAPIServer genericapiserver.DelegationTarget, reg prometheus.Registerer) (*aggregatorapiserver.APIAggregator, error) { - aggregatorConfig := config.KubeAggregatorConfig - sharedInformerFactory := config.Informers - remoteServicesConfig := config.RemoteServicesConfig - externalNamesInformer := sharedInformerFactory.Service().V0alpha1().ExternalNames() - completedConfig := aggregatorConfig.Complete() - - aggregatorServer, err := completedConfig.NewWithDelegate(delegateAPIServer) - if err != nil { - return nil, err - } - - // create controllers for auto-registration - apiRegistrationClient, err := apiregistrationclient.NewForConfig(completedConfig.GenericConfig.LoopbackClientConfig) - if err != nil { - return nil, err - } - - autoRegistrationController := autoregister.NewAutoRegisterController(aggregatorServer.APIRegistrationInformers.Apiregistration().V1().APIServices(), apiRegistrationClient) - - apiServices := apiServicesToRegister(delegateAPIServer, autoRegistrationController) - - // Imbue all builtin group-priorities onto the aggregated discovery - if completedConfig.GenericConfig.AggregatedDiscoveryGroupManager != nil { - for gv, entry := range APIVersionPriorities { - completedConfig.GenericConfig.AggregatedDiscoveryGroupManager.SetGroupVersionPriority(metav1.GroupVersion(gv), int(entry.Group), int(entry.Version)) - } - } - - err = aggregatorServer.GenericAPIServer.AddPostStartHook("grafana-apiserver-autoregistration", func(context genericapiserver.PostStartHookContext) error { - go autoRegistrationController.Run(5, context.Done()) - return nil - }) - if err != nil { - return nil, err - } - - if remoteServicesConfig != nil { - addRemoteAPIServicesToRegister(remoteServicesConfig, autoRegistrationController) - externalNames := getRemoteExternalNamesToRegister(remoteServicesConfig) - err = aggregatorServer.GenericAPIServer.AddPostStartHook("grafana-apiserver-remote-autoregistration", func(ctx genericapiserver.PostStartHookContext) error { - controllers.WaitForCacheSync("grafana-apiserver-remote-autoregistration", ctx.Done(), externalNamesInformer.Informer().HasSynced) - namespacedClient := remoteServicesConfig.serviceClientSet.ServiceV0alpha1().ExternalNames(remoteServicesConfig.ExternalNamesNamespace) - for _, externalName := range externalNames { - _, err := namespacedClient.Apply(ctx, externalName, metav1.ApplyOptions{ - FieldManager: "grafana-aggregator", - Force: true, - }) - if err != nil { - return err - } - } - return nil - }) - if err != nil { - return nil, err - } - } - - err = aggregatorServer.GenericAPIServer.AddBootSequenceHealthChecks( - makeAPIServiceAvailableHealthCheck( - "autoregister-completion", - apiServices, - aggregatorServer.APIRegistrationInformers.Apiregistration().V1().APIServices(), - ), - ) - if err != nil { - return nil, err - } - - apiregistrationClient, err := apiregistrationclientset.NewForConfig(completedConfig.GenericConfig.LoopbackClientConfig) - if err != nil { - return nil, err - } - - proxyCurrentCertKeyContentFunc := func() ([]byte, []byte) { - return nil, nil - } - if len(config.CustomExtraConfig.DiscoveryOnlyProxyClientCertFile) > 0 && len(config.CustomExtraConfig.DiscoveryOnlyProxyClientKeyFile) > 0 { - aggregatorProxyCerts, err := dynamiccertificates.NewDynamicServingContentFromFiles("aggregator-proxy-cert", config.CustomExtraConfig.DiscoveryOnlyProxyClientCertFile, config.CustomExtraConfig.DiscoveryOnlyProxyClientKeyFile) - if err != nil { - return nil, err - } - proxyCurrentCertKeyContentFunc = func() ([]byte, []byte) { - return aggregatorProxyCerts.CurrentCertKeyContent() - } - } - - registry := legacyregistry.DefaultGatherer.(metrics.KubeRegistry) - availibilityMetrics := newAvailabilityMetrics() - // create shared (remote and local) availability metrics - // TODO: decouple from legacyregistry - registerIntoLegacyRegistryOnce.Do(func() { err = availibilityMetrics.Register(registry.Register, registry.CustomRegister) }) - if err != nil { - return nil, err - } - - availableController, err := NewAvailableConditionController( - aggregatorServer.APIRegistrationInformers.Apiregistration().V1().APIServices(), - externalNamesInformer, - apiregistrationClient.ApiregistrationV1(), - nil, - proxyCurrentCertKeyContentFunc, - completedConfig.ExtraConfig.ServiceResolver, - availibilityMetrics, - ) - if err != nil { - return nil, err - } - - aggregatorServer.GenericAPIServer.AddPostStartHookOrDie("apiservice-status-override-available-controller", func(context genericapiserver.PostStartHookContext) error { - // if we end up blocking for long periods of time, we may need to increase workers. - go availableController.Run(5, context.Done()) - return nil - }) - - aggregatorServer.GenericAPIServer.AddPostStartHookOrDie("start-grafana-aggregator-informers", func(context genericapiserver.PostStartHookContext) error { - sharedInformerFactory.Start(context.Done()) - aggregatorServer.APIRegistrationInformers.Start(context.Done()) - return nil - }) - - serviceAPIGroupInfo := genericapiserver.NewDefaultAPIGroupInfo(servicev0alpha1.GROUP, aggregatorscheme.Scheme, metav1.ParameterCodec, aggregatorscheme.Codecs) - for _, b := range config.Builders { - err := b.UpdateAPIGroupInfo( - &serviceAPIGroupInfo, - builder.APIGroupOptions{ - Scheme: aggregatorscheme.Scheme, - OptsGetter: aggregatorConfig.GenericConfig.RESTOptionsGetter, - DualWriteBuilder: nil, // no dual writer - MetricsRegister: reg, - }, - ) - if err != nil { - return nil, err - } - } - - if err := aggregatorServer.GenericAPIServer.InstallAPIGroup(&serviceAPIGroupInfo); err != nil { - return nil, err - } - - return aggregatorServer, nil -} - -func makeAPIService(gv schema.GroupVersion) *v1.APIService { - apiServicePriority, ok := APIVersionPriorities[gv] - if !ok { - // if we aren't found, then we shouldn't register ourselves because it could result in a CRD group version - // being permanently stuck in the APIServices list. - klog.Infof("Skipping APIService creation for %v", gv) - return nil - } - return &v1.APIService{ - ObjectMeta: metav1.ObjectMeta{Name: gv.Version + "." + gv.Group}, - Spec: v1.APIServiceSpec{ - Group: gv.Group, - Version: gv.Version, - GroupPriorityMinimum: apiServicePriority.Group, - VersionPriority: apiServicePriority.Version, - }, - } -} - -// makeAPIServiceAvailableHealthCheck returns a healthz check that returns healthy -// once all of the specified services have been observed to be available at least once. -func makeAPIServiceAvailableHealthCheck(name string, apiServices []*v1.APIService, apiServiceInformer apiregistrationInformers.APIServiceInformer) healthz.HealthChecker { - // Track the auto-registered API services that have not been observed to be available yet - pendingServiceNamesLock := &sync.RWMutex{} - pendingServiceNames := sets.NewString() - for _, service := range apiServices { - pendingServiceNames.Insert(service.Name) - } - - // When an APIService in the list is seen as available, remove it from the pending list - handleAPIServiceChange := func(service *v1.APIService) { - pendingServiceNamesLock.Lock() - defer pendingServiceNamesLock.Unlock() - if !pendingServiceNames.Has(service.Name) { - return - } - if v1helper.IsAPIServiceConditionTrue(service, v1.Available) { - pendingServiceNames.Delete(service.Name) - } - } - - // Watch add/update events for APIServices - _, err := apiServiceInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ - AddFunc: func(obj interface{}) { handleAPIServiceChange(obj.(*v1.APIService)) }, - UpdateFunc: func(old, new interface{}) { handleAPIServiceChange(new.(*v1.APIService)) }, - }) - if err != nil { - klog.Errorf("Failed to watch APIServices for health check: %v", err) - } - - // Don't return healthy until the pending list is empty - return healthz.NamedCheck(name, func(r *http.Request) error { - pendingServiceNamesLock.RLock() - defer pendingServiceNamesLock.RUnlock() - if pendingServiceNames.Len() > 0 { - klog.Error("APIServices not yet available", "services", pendingServiceNames.List()) - return fmt.Errorf("missing APIService: %v", pendingServiceNames.List()) - } - return nil - }) -} - -// Priority defines group Priority that is used in discovery. This controls -// group position in the kubectl output. -type Priority struct { - // Group indicates the order of the Group relative to other groups. - Group int32 - // Version indicates the relative order of the Version inside of its group. - Version int32 -} - -// APIVersionPriorities are the proper way to resolve this letting the aggregator know the desired group and version-within-group order of the underlying servers -// is to refactor the genericapiserver.DelegationTarget to include a list of priorities based on which APIs were installed. -// This requires the APIGroupInfo struct to evolve and include the concept of priorities and to avoid mistakes, the core storage map there needs to be updated. -// That ripples out every bit as far as you'd expect, so for 1.7 we'll include the list here instead of being built up during storage. -var APIVersionPriorities = map[schema.GroupVersion]Priority{ - {Group: "", Version: "v1"}: {Group: 18000, Version: 1}, - // to my knowledge, nothing below here collides - {Group: "admissionregistration.k8s.io", Version: "v1"}: {Group: 16700, Version: 15}, - {Group: "admissionregistration.k8s.io", Version: "v1beta1"}: {Group: 16700, Version: 12}, - {Group: "admissionregistration.k8s.io", Version: "v1alpha1"}: {Group: 16700, Version: 9}, - // Append a new group to the end of the list if unsure. - // You can use min(existing group)-100 as the initial value for a group. - // Version can be set to 9 (to have space around) for a new group. -} - -func addRemoteAPIServicesToRegister(config *RemoteServicesConfig, registration autoregister.AutoAPIServiceRegistration) { - for i, service := range config.Services { - port := service.Port - apiService := &v1.APIService{ - ObjectMeta: metav1.ObjectMeta{Name: service.Version + "." + service.Group}, - Spec: v1.APIServiceSpec{ - Group: service.Group, - Version: service.Version, - InsecureSkipTLSVerify: config.InsecureSkipTLSVerify, - CABundle: config.CABundle, - // TODO: Group priority minimum of 1000 more than for local services, figure out a better story - // when we have multiple versions, potentially running in heterogeneous ways (local and remote) - GroupPriorityMinimum: 16000, - VersionPriority: 1 + int32(i), - Service: &v1.ServiceReference{ - Name: service.Version + "." + service.Group, - Namespace: config.ExternalNamesNamespace, - Port: &port, - }, - }, - } - - registration.AddAPIServiceToSyncOnStart(apiService) - } -} - -func getRemoteExternalNamesToRegister(config *RemoteServicesConfig) []*servicev0alpha1applyconfiguration.ExternalNameApplyConfiguration { - externalNames := make([]*servicev0alpha1applyconfiguration.ExternalNameApplyConfiguration, 0) - - for _, service := range config.Services { - host := service.Host - name := service.Version + "." + service.Group - externalName := &servicev0alpha1applyconfiguration.ExternalNameApplyConfiguration{} - externalName.WithAPIVersion(servicev0alpha1.SchemeGroupVersion.String()) - externalName.WithKind("ExternalName") - externalName.WithName(name) - externalName.WithSpec(&servicev0alpha1applyconfiguration.ExternalNameSpecApplyConfiguration{ - Host: &host, - }) - externalNames = append(externalNames, externalName) - } - - return externalNames -} - -func apiServicesToRegister(delegateAPIServer genericapiserver.DelegationTarget, registration autoregister.AutoAPIServiceRegistration) []*v1.APIService { - apiServices := []*v1.APIService{} - - for _, curr := range delegateAPIServer.ListedPaths() { - if curr == "/api/v1" { - apiService := makeAPIService(schema.GroupVersion{Group: "", Version: "v1"}) - registration.AddAPIServiceToSyncOnStart(apiService) - apiServices = append(apiServices, apiService) - continue - } - - if !strings.HasPrefix(curr, "/apis/") { - continue - } - // this comes back in a list that looks like /apis/rbac.authorization.k8s.io/v1alpha1 - tokens := strings.Split(curr, "/") - if len(tokens) != 4 { - continue - } - - apiService := makeAPIService(schema.GroupVersion{Group: tokens[2], Version: tokens[3]}) - if apiService == nil { - continue - } - registration.AddAPIServiceToSyncOnStart(apiService) - apiServices = append(apiServices, apiService) - } - - return apiServices -} - -// NOTE: below function imported from https://github.com/kubernetes/kubernetes/blob/master/cmd/kube-apiserver/app/server.go#L197 -// createProxyTransport creates the dialer infrastructure to connect to the api servers. -func createProxyTransport() *http.Transport { - // NOTE: We don't set proxyDialerFn but the below SetTransportDefaults will - // See https://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/apimachinery/pkg/util/net/http.go#L109 - var proxyDialerFn utilnet.DialFunc - // Proxying to services is IP-based... don't expect to be able to verify the hostname - proxyTLSClientConfig := &tls.Config{InsecureSkipVerify: true} - proxyTransport := utilnet.SetTransportDefaults(&http.Transport{ - DialContext: proxyDialerFn, - TLSClientConfig: proxyTLSClientConfig, - }) - return proxyTransport -} diff --git a/pkg/services/apiserver/aggregator/aggregator_test.go b/pkg/services/apiserver/aggregator/aggregator_test.go deleted file mode 100644 index 15a8fa33f50..00000000000 --- a/pkg/services/apiserver/aggregator/aggregator_test.go +++ /dev/null @@ -1,71 +0,0 @@ -package aggregator_test - -import ( - "sort" - "testing" - "time" - - "github.com/stretchr/testify/require" - openapinamer "k8s.io/apiserver/pkg/endpoints/openapi" - genericapiserver "k8s.io/apiserver/pkg/server" - "k8s.io/apiserver/pkg/storage/storagebackend" - "k8s.io/client-go/informers" - "k8s.io/client-go/kubernetes/fake" - clientrest "k8s.io/client-go/rest" - utilversion "k8s.io/component-base/version" - "k8s.io/kube-aggregator/pkg/apiserver" - aggregatorscheme "k8s.io/kube-aggregator/pkg/apiserver/scheme" - aggregatoropenapi "k8s.io/kube-aggregator/pkg/generated/openapi" - - "github.com/grafana/grafana/pkg/storage/unified/apistore" -) - -// TestAggregatorPostStartHooks tests that the kube-aggregator server has the expected default post start hooks enabled. -func TestAggregatorPostStartHooks(t *testing.T) { - cfg := apiserver.Config{ - GenericConfig: genericapiserver.NewRecommendedConfig(aggregatorscheme.Codecs), - ExtraConfig: apiserver.ExtraConfig{}, - } - - cfg.GenericConfig.ExternalAddress = "127.0.0.1:6443" - cfg.GenericConfig.EffectiveVersion = utilversion.DefaultBuildEffectiveVersion() - cfg.GenericConfig.LoopbackClientConfig = &clientrest.Config{} - cfg.GenericConfig.MergedResourceConfig = apiserver.DefaultAPIResourceConfigSource() - - // Add OpenAPI config, which depends on builders - namer := openapinamer.NewDefinitionNamer(aggregatorscheme.Scheme) - cfg.GenericConfig.OpenAPIV3Config = genericapiserver.DefaultOpenAPIV3Config(aggregatoropenapi.GetOpenAPIDefinitions, namer) - cfg.GenericConfig.OpenAPIV3Config.Info.Title = "Kubernetes" - cfg.GenericConfig.OpenAPIConfig = genericapiserver.DefaultOpenAPIConfig(aggregatoropenapi.GetOpenAPIDefinitions, namer) - cfg.GenericConfig.OpenAPIConfig.Info.Title = "Kubernetes" - cfg.GenericConfig.SkipOpenAPIInstallation = true - cfg.GenericConfig.SharedInformerFactory = informers.NewSharedInformerFactory(fake.NewSimpleClientset(), 10*time.Minute) - - // override the RESTOptionsGetter to use the in memory storage options - restOptionsGetter, err := apistore.NewRESTOptionsGetterMemory(*storagebackend.NewDefaultConfig("memory", nil)) - require.NoError(t, err) - cfg.GenericConfig.RESTOptionsGetter = restOptionsGetter - - complete := cfg.Complete() - - server, err := complete.NewWithDelegate(genericapiserver.NewEmptyDelegate()) - require.NoError(t, err) - - actual := make([]string, 0, len(server.GenericAPIServer.PostStartHooks())) - for k := range server.GenericAPIServer.PostStartHooks() { - actual = append(actual, k) - } - sort.Strings(actual) - expected := []string{ - "apiservice-discovery-controller", - "generic-apiserver-start-informers", - "max-in-flight-filter", - "storage-object-count-tracker-hook", - "start-kube-aggregator-informers", - "apiservice-status-local-available-controller", - "apiservice-status-remote-available-controller", - "apiservice-registration-controller", - } - sort.Strings(expected) - require.Equal(t, expected, actual) -} diff --git a/pkg/services/apiserver/aggregator/availableController.go b/pkg/services/apiserver/aggregator/availableController.go deleted file mode 100644 index fb285715f7d..00000000000 --- a/pkg/services/apiserver/aggregator/availableController.go +++ /dev/null @@ -1,490 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -// Provenance-includes-location: https://github.com/kubernetes/kube-aggregator/blob/master/pkg/controllers/status/available_controller.go -// Provenance-includes-license: Apache-2.0 -// Provenance-includes-copyright: The Kubernetes Authors. - -package aggregator - -import ( - "context" - "fmt" - "net/http" - "net/url" - "reflect" - "sync" - "time" - - "k8s.io/apimachinery/pkg/api/equality" - apierrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/api/meta" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/labels" - "k8s.io/apimachinery/pkg/runtime" - utilruntime "k8s.io/apimachinery/pkg/util/runtime" - "k8s.io/apimachinery/pkg/util/wait" - "k8s.io/client-go/tools/cache" - "k8s.io/client-go/transport" - "k8s.io/client-go/util/workqueue" - "k8s.io/klog/v2" - apiregistrationv1 "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1" - apiregistrationv1apihelper "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1/helper" - apiregistrationclient "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/typed/apiregistration/v1" - informers "k8s.io/kube-aggregator/pkg/client/informers/externalversions/apiregistration/v1" - listers "k8s.io/kube-aggregator/pkg/client/listers/apiregistration/v1" - "k8s.io/kube-aggregator/pkg/controllers" - - "github.com/grafana/grafana/pkg/apis/service/v0alpha1" - informersservicev0alpha1 "github.com/grafana/grafana/pkg/generated/informers/externalversions/service/v0alpha1" - listersservicev0alpha1 "github.com/grafana/grafana/pkg/generated/listers/service/v0alpha1" -) - -type certKeyFunc func() ([]byte, []byte) - -// ServiceResolver knows how to convert a service reference into an actual location. -type ServiceResolver interface { - ResolveEndpoint(namespace, name string, port int32) (*url.URL, error) -} - -// AvailableConditionController handles checking the availability of registered API services. -type AvailableConditionController struct { - apiServiceClient apiregistrationclient.APIServicesGetter - - apiServiceLister listers.APIServiceLister - apiServiceSynced cache.InformerSynced - - // externalNameLister is used to get the IP to create the transport for - externalNameLister listersservicev0alpha1.ExternalNameLister - servicesSynced cache.InformerSynced - - // proxyTransportDial specifies the dial function for creating unencrypted TCP connections. - proxyTransportDial *transport.DialHolder - proxyCurrentCertKeyContent certKeyFunc - serviceResolver ServiceResolver - - // To allow injection for testing. - syncFn func(key string) error - - queue workqueue.TypedRateLimitingInterface[string] - // map from service-namespace -> service-name -> apiservice names - cache map[string]map[string][]string - // this lock protects operations on the above cache - cacheLock sync.RWMutex - metrics *Metrics -} - -// NewAvailableConditionController returns a new AvailableConditionController. -func NewAvailableConditionController( - apiServiceInformer informers.APIServiceInformer, - externalNameInformer informersservicev0alpha1.ExternalNameInformer, - apiServiceClient apiregistrationclient.APIServicesGetter, - proxyTransportDial *transport.DialHolder, - proxyCurrentCertKeyContent certKeyFunc, - serviceResolver ServiceResolver, - metrics *Metrics, -) (*AvailableConditionController, error) { - c := &AvailableConditionController{ - apiServiceClient: apiServiceClient, - apiServiceLister: apiServiceInformer.Lister(), - externalNameLister: externalNameInformer.Lister(), - serviceResolver: serviceResolver, - queue: workqueue.NewTypedRateLimitingQueueWithConfig( - // We want a fairly tight requeue time. The controller listens to the API, but because it relies on the routability of the - // service network, it is possible for an external, non-watchable factor to affect availability. This keeps - // the maximum disruption time to a minimum, but it does prevent hot loops. - workqueue.NewTypedItemExponentialFailureRateLimiter[string](5*time.Millisecond, 30*time.Second), - workqueue.TypedRateLimitingQueueConfig[string]{Name: "AvailableConditionController"}, - ), - proxyTransportDial: proxyTransportDial, - proxyCurrentCertKeyContent: proxyCurrentCertKeyContent, - metrics: metrics, - } - - // resync on this one because it is low cardinality and rechecking the actual discovery - // allows us to detect health in a more timely fashion when network connectivity to - // nodes is snipped, but the network still attempts to route there. See - // https://github.com/openshift/origin/issues/17159#issuecomment-341798063 - apiServiceHandler, _ := apiServiceInformer.Informer().AddEventHandlerWithResyncPeriod( - cache.ResourceEventHandlerFuncs{ - AddFunc: c.addAPIService, - UpdateFunc: c.updateAPIService, - DeleteFunc: c.deleteAPIService, - }, - 30*time.Second) - c.apiServiceSynced = apiServiceHandler.HasSynced - - serviceHandler, _ := externalNameInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ - AddFunc: c.addService, - UpdateFunc: c.updateService, - DeleteFunc: c.deleteService, - }) - c.servicesSynced = serviceHandler.HasSynced - - c.syncFn = c.sync - - return c, nil -} - -func (c *AvailableConditionController) sync(key string) error { - originalAPIService, err := c.apiServiceLister.Get(key) - if apierrors.IsNotFound(err) { - if originalAPIService.Spec.Service != nil { - // Only reset state, if the service was a remote service - c.metrics.ForgetAPIService(key) - } - return nil - } - if err != nil { - return err - } - - // if a particular transport was specified, use that otherwise build one - // construct an http client that will ignore TLS verification (if someone owns the network and messes with your status - // that's not so bad) and sets a very short timeout. This is a best effort GET that provides no additional information - transportConfig := &transport.Config{ - TLS: transport.TLSConfig{ - Insecure: true, - }, - DialHolder: c.proxyTransportDial, - } - - if c.proxyCurrentCertKeyContent != nil { - proxyClientCert, proxyClientKey := c.proxyCurrentCertKeyContent() - - transportConfig.TLS.CertData = proxyClientCert - transportConfig.TLS.KeyData = proxyClientKey - } - restTransport, err := transport.New(transportConfig) - if err != nil { - return err - } - discoveryClient := &http.Client{ - Transport: restTransport, - // the request should happen quickly. - Timeout: 5 * time.Second, - CheckRedirect: func(req *http.Request, via []*http.Request) error { - return http.ErrUseLastResponse - }, - } - - apiService := originalAPIService.DeepCopy() - - availableCondition := apiregistrationv1.APIServiceCondition{ - Type: apiregistrationv1.Available, - Status: apiregistrationv1.ConditionTrue, - LastTransitionTime: metav1.Now(), - } - - // local API services are always considered available - if apiService.Spec.Service == nil { - apiregistrationv1apihelper.SetAPIServiceCondition(apiService, apiregistrationv1apihelper.NewLocalAvailableAPIServiceCondition()) - _, err := c.updateAPIServiceStatus(originalAPIService, apiService) - return err - } - - _, err = c.externalNameLister.ExternalNames(apiService.Spec.Service.Namespace).Get(apiService.Spec.Service.Name) - if apierrors.IsNotFound(err) { - availableCondition.Status = apiregistrationv1.ConditionFalse - availableCondition.Reason = "ServiceNotFound" - availableCondition.Message = fmt.Sprintf("service/%s in %q is not present", apiService.Spec.Service.Name, apiService.Spec.Service.Namespace) - apiregistrationv1apihelper.SetAPIServiceCondition(apiService, availableCondition) - _, err := c.updateAPIServiceStatus(originalAPIService, apiService) - return err - } else if err != nil { - availableCondition.Status = apiregistrationv1.ConditionUnknown - availableCondition.Reason = "ServiceAccessError" - availableCondition.Message = fmt.Sprintf("service/%s in %q cannot be checked due to: %v", apiService.Spec.Service.Name, apiService.Spec.Service.Namespace, err) - apiregistrationv1apihelper.SetAPIServiceCondition(apiService, availableCondition) - _, err := c.updateAPIServiceStatus(originalAPIService, apiService) - return err - } - - // actually try to hit the discovery endpoint when it isn't local and when we're routing as a service. - if apiService.Spec.Service != nil && c.serviceResolver != nil { - attempts := 5 - results := make(chan error, attempts) - for i := 0; i < attempts; i++ { - go func() { - // stagger these requests to reduce pressure on aggregated services - waitDuration := time.Second * time.Duration(int32(i)) - time.Sleep(waitDuration) - - discoveryURL, err := c.serviceResolver.ResolveEndpoint(apiService.Spec.Service.Namespace, apiService.Spec.Service.Name, *apiService.Spec.Service.Port) - if err != nil { - results <- err - return - } - // render legacyAPIService health check path when it is delegated to a service - if apiService.Name == "v1." { - discoveryURL.Path = "/api/" + apiService.Spec.Version - } else { - discoveryURL.Path = "/apis/" + apiService.Spec.Group + "/" + apiService.Spec.Version - } - - errCh := make(chan error, 1) - go func() { - // be sure to check a URL that the aggregated API server is required to serve - newReq, err := http.NewRequest("GET", discoveryURL.String(), nil) - if err != nil { - errCh <- err - return - } - - // setting the system-masters identity ensures that we will always have access rights - uid := "" - var extra map[string][]string - transport.SetAuthProxyHeaders(newReq, "system:kube-aggregator", uid, []string{"system:masters"}, extra) - resp, err := discoveryClient.Do(newReq) - if resp != nil { - _ = resp.Body.Close() - // we should always been in the 200s or 300s - if resp.StatusCode < http.StatusOK || resp.StatusCode >= http.StatusMultipleChoices { - errCh <- fmt.Errorf("bad status from %v: %d", discoveryURL, resp.StatusCode) - return - } - } - - errCh <- err - }() - - select { - case err = <-errCh: - if err != nil { - results <- fmt.Errorf("failing or missing response from %v: %v", discoveryURL, err) - return - } - - // we had trouble with slow dial and DNS responses causing us to wait too long. - // we added this as insurance - case <-time.After(6 * time.Second): - results <- fmt.Errorf("timed out waiting for %v", discoveryURL) - return - } - - results <- nil - }() - } - - var lastError error - for i := 0; i < attempts; i++ { - lastError = <-results - // if we had at least one success, we are successful overall and we can return now - if lastError == nil { - break - } - } - - if lastError != nil { - availableCondition.Status = apiregistrationv1.ConditionFalse - availableCondition.Reason = "FailedDiscoveryCheck" - availableCondition.Message = lastError.Error() - apiregistrationv1apihelper.SetAPIServiceCondition(apiService, availableCondition) - _, updateErr := c.updateAPIServiceStatus(originalAPIService, apiService) - if updateErr != nil { - return updateErr - } - // force a requeue to make it very obvious that this will be retried at some point in the future - // along with other requeues done via service change, endpoint change, and resync - return lastError - } - } - - availableCondition.Reason = "Passed" - availableCondition.Message = "all checks passed" - apiregistrationv1apihelper.SetAPIServiceCondition(apiService, availableCondition) - _, err = c.updateAPIServiceStatus(originalAPIService, apiService) - return err -} - -// updateAPIServiceStatus only issues an update if a change is detected. We have a tight resync loop to quickly detect dead -// apiservices. Doing that means we don't want to quickly issue no-op updates. -func (c *AvailableConditionController) updateAPIServiceStatus(originalAPIService, newAPIService *apiregistrationv1.APIService) (*apiregistrationv1.APIService, error) { - // update this metric on every sync operation to reflect the actual state - if newAPIService.Spec.Service != nil { - // Only expose the metric for remote services, trusts the type on the new object - c.metrics.SetUnavailableGauge(newAPIService) - } - - if equality.Semantic.DeepEqual(originalAPIService.Status, newAPIService.Status) { - return newAPIService, nil - } - - orig := apiregistrationv1apihelper.GetAPIServiceConditionByType(originalAPIService, apiregistrationv1.Available) - now := apiregistrationv1apihelper.GetAPIServiceConditionByType(newAPIService, apiregistrationv1.Available) - unknown := apiregistrationv1.APIServiceCondition{ - Type: apiregistrationv1.Available, - Status: apiregistrationv1.ConditionUnknown, - } - if orig == nil { - orig = &unknown - } - if now == nil { - now = &unknown - } - if *orig != *now { - klog.V(2).InfoS("changing APIService availability", "name", newAPIService.Name, "oldStatus", orig.Status, "newStatus", now.Status, "message", now.Message, "reason", now.Reason) - } - - newAPIService, err := c.apiServiceClient.APIServices().UpdateStatus(context.TODO(), newAPIService, metav1.UpdateOptions{}) - if err != nil { - return nil, err - } - - if newAPIService.Spec.Service != nil { - // Only expose the metric for remote services, trusts the type on the new object - c.metrics.SetUnavailableCounter(originalAPIService, newAPIService) - } - return newAPIService, nil -} - -// Run starts the AvailableConditionController loop which manages the availability condition of API services. -func (c *AvailableConditionController) Run(workers int, stopCh <-chan struct{}) { - defer utilruntime.HandleCrash() - defer c.queue.ShutDown() - - klog.Info("Starting AvailableConditionController") - defer klog.Info("Shutting down AvailableConditionController") - - // This waits not just for the informers to sync, but for our handlers - // to be called; since the handlers are three different ways of - // enqueueing the same thing, waiting for this permits the queue to - // maximally de-duplicate the entries. - if !controllers.WaitForCacheSync("AvailableConditionCOverrideController", stopCh, c.apiServiceSynced, c.servicesSynced) { - return - } - - for i := 0; i < workers; i++ { - go wait.Until(c.runWorker, time.Second, stopCh) - } - - <-stopCh -} - -func (c *AvailableConditionController) runWorker() { - for c.processNextWorkItem() { - } -} - -// processNextWorkItem deals with one key off the queue. It returns false when it's time to quit. -func (c *AvailableConditionController) processNextWorkItem() bool { - key, quit := c.queue.Get() - if quit { - return false - } - defer c.queue.Done(key) - - err := c.syncFn(key) - if err == nil { - c.queue.Forget(key) - return true - } - - utilruntime.HandleError(fmt.Errorf("%v failed with: %v", key, err)) - c.queue.AddRateLimited(key) - - return true -} - -func (c *AvailableConditionController) addAPIService(obj interface{}) { - castObj := obj.(*apiregistrationv1.APIService) - klog.V(4).Infof("Adding %s", castObj.Name) - if castObj.Spec.Service != nil { - c.rebuildAPIServiceCache() - } - c.queue.Add(castObj.Name) -} - -func (c *AvailableConditionController) updateAPIService(oldObj, newObj interface{}) { - castObj := newObj.(*apiregistrationv1.APIService) - oldCastObj := oldObj.(*apiregistrationv1.APIService) - klog.V(4).Infof("Updating %s", oldCastObj.Name) - if !reflect.DeepEqual(castObj.Spec.Service, oldCastObj.Spec.Service) { - c.rebuildAPIServiceCache() - } - c.queue.Add(oldCastObj.Name) -} - -func (c *AvailableConditionController) deleteAPIService(obj interface{}) { - castObj, ok := obj.(*apiregistrationv1.APIService) - if !ok { - tombstone, ok := obj.(cache.DeletedFinalStateUnknown) - if !ok { - klog.Errorf("Couldn't get object from tombstone %#v", obj) - return - } - castObj, ok = tombstone.Obj.(*apiregistrationv1.APIService) - if !ok { - klog.Errorf("Tombstone contained object that is not expected %#v", obj) - return - } - } - klog.V(4).Infof("Deleting %q", castObj.Name) - if castObj.Spec.Service != nil { - c.rebuildAPIServiceCache() - } - c.queue.Add(castObj.Name) -} - -func (c *AvailableConditionController) getAPIServicesFor(obj runtime.Object) []string { - metadata, err := meta.Accessor(obj) - if err != nil { - utilruntime.HandleError(err) - return nil - } - c.cacheLock.RLock() - defer c.cacheLock.RUnlock() - return c.cache[metadata.GetNamespace()][metadata.GetName()] -} - -// if the service/endpoint handler wins the race against the cache rebuilding, it may queue a no-longer-relevant apiservice -// (which will get processed an extra time - this doesn't matter), -// and miss a newly relevant apiservice (which will get queued by the apiservice handler) -func (c *AvailableConditionController) rebuildAPIServiceCache() { - apiServiceList, _ := c.apiServiceLister.List(labels.Everything()) - newCache := map[string]map[string][]string{} - for _, apiService := range apiServiceList { - if apiService.Spec.Service == nil { - continue - } - if newCache[apiService.Spec.Service.Namespace] == nil { - newCache[apiService.Spec.Service.Namespace] = map[string][]string{} - } - newCache[apiService.Spec.Service.Namespace][apiService.Spec.Service.Name] = append(newCache[apiService.Spec.Service.Namespace][apiService.Spec.Service.Name], apiService.Name) - } - - c.cacheLock.Lock() - defer c.cacheLock.Unlock() - c.cache = newCache -} - -// TODO, think of a way to avoid checking on every service manipulation - -func (c *AvailableConditionController) addService(obj interface{}) { - for _, apiService := range c.getAPIServicesFor(obj.(*v0alpha1.ExternalName)) { - c.queue.Add(apiService) - } -} - -func (c *AvailableConditionController) updateService(obj, _ interface{}) { - for _, apiService := range c.getAPIServicesFor(obj.(*v0alpha1.ExternalName)) { - c.queue.Add(apiService) - } -} - -func (c *AvailableConditionController) deleteService(obj interface{}) { - castObj, ok := obj.(*v0alpha1.ExternalName) - if !ok { - tombstone, ok := obj.(cache.DeletedFinalStateUnknown) - if !ok { - klog.Errorf("Couldn't get object from tombstone %#v", obj) - return - } - castObj, ok = tombstone.Obj.(*v0alpha1.ExternalName) - if !ok { - klog.Errorf("Tombstone contained object that is not expected %#v", obj) - return - } - } - for _, apiService := range c.getAPIServicesFor(castObj) { - c.queue.Add(apiService) - } -} diff --git a/pkg/services/apiserver/aggregator/config.go b/pkg/services/apiserver/aggregator/config.go deleted file mode 100644 index 19017f5ad1a..00000000000 --- a/pkg/services/apiserver/aggregator/config.go +++ /dev/null @@ -1,75 +0,0 @@ -package aggregator - -import ( - openapinamer "k8s.io/apiserver/pkg/endpoints/openapi" - genericapiserver "k8s.io/apiserver/pkg/server" - aggregatorapiserver "k8s.io/kube-aggregator/pkg/apiserver" - aggregatorscheme "k8s.io/kube-aggregator/pkg/apiserver/scheme" - aggregatoropenapi "k8s.io/kube-aggregator/pkg/generated/openapi" - "k8s.io/kube-openapi/pkg/common" - - serviceclientset "github.com/grafana/grafana/pkg/generated/clientset/versioned" - informersv0alpha1 "github.com/grafana/grafana/pkg/generated/informers/externalversions" - "github.com/grafana/grafana/pkg/services/apiserver/builder" -) - -type RemoteService struct { - Group string `yaml:"group"` - Version string `yaml:"version"` - Host string `yaml:"host"` - Port int32 `yaml:"port"` -} - -type RemoteServicesConfig struct { - ExternalNamesNamespace string - InsecureSkipTLSVerify bool - CABundle []byte - Services []RemoteService - serviceClientSet *serviceclientset.Clientset -} - -type CustomExtraConfig struct { - DiscoveryOnlyProxyClientCertFile string - DiscoveryOnlyProxyClientKeyFile string -} - -type Config struct { - KubeAggregatorConfig *aggregatorapiserver.Config - CustomExtraConfig *CustomExtraConfig // this is temporary and will be removed once we have moved across newer auth rollout in cloud - Informers informersv0alpha1.SharedInformerFactory - RemoteServicesConfig *RemoteServicesConfig - // Builders contain prerequisite api groups for aggregator to function correctly e.g. ExternalName - // Since the main APIServer delegate supports storage implementations that intend to be multi-tenant - // Aggregator builders that we don't intend to use multi-tenant storage are kept in aggregator's - // Delegate, one which is configured explicitly to use file storage only - Builders []builder.APIGroupBuilder -} - -// remoteServices may be nil when not using aggregation -func NewConfig(aggregator *aggregatorapiserver.Config, customExtraConfig *CustomExtraConfig, informers informersv0alpha1.SharedInformerFactory, builders []builder.APIGroupBuilder, remoteServices *RemoteServicesConfig) *Config { - getMergedOpenAPIDefinitions := func(ref common.ReferenceCallback) map[string]common.OpenAPIDefinition { - aggregatorAPIs := aggregatoropenapi.GetOpenAPIDefinitions(ref) - builderAPIs := builder.GetOpenAPIDefinitions(builders)(ref) - - for k, v := range builderAPIs { - aggregatorAPIs[k] = v - } - - return aggregatorAPIs - } - - // Add OpenAPI config, which depends on builders - namer := openapinamer.NewDefinitionNamer(aggregatorscheme.Scheme) - aggregator.GenericConfig.OpenAPIV3Config = genericapiserver.DefaultOpenAPIV3Config(getMergedOpenAPIDefinitions, namer) - aggregator.GenericConfig.OpenAPIV3Config.Info.Title = "Kubernetes" - aggregator.GenericConfig.OpenAPIConfig = genericapiserver.DefaultOpenAPIConfig(getMergedOpenAPIDefinitions, namer) - aggregator.GenericConfig.OpenAPIConfig.Info.Title = "Kubernetes" - - return &Config{ - aggregator, - customExtraConfig, - informers, - remoteServices, - builders, - } -} diff --git a/pkg/services/apiserver/aggregator/examples/autoregister/apiservices.yaml b/pkg/services/apiserver/aggregator/examples/autoregister/apiservices.yaml deleted file mode 100644 index 183afb542b2..00000000000 --- a/pkg/services/apiserver/aggregator/examples/autoregister/apiservices.yaml +++ /dev/null @@ -1,14 +0,0 @@ -# NOTE: dev-mode only and governed by presence of non-empty value for cfg["grafana-apiserver"]["remote_services_file"] -# List of sample multi-tenant services to aggregate on startup -- group: example.grafana.app - version: v0alpha1 - host: localhost - port: 7443 -- group: query.grafana.app - version: v0alpha1 - host: localhost - port: 7444 -- group: testdata.datasource.grafana.app - version: v0alpha1 - host: localhost - port: 7445 diff --git a/pkg/services/apiserver/aggregator/examples/manual-test/apiservice.yaml b/pkg/services/apiserver/aggregator/examples/manual-test/apiservice.yaml deleted file mode 100644 index 65cc2a5884a..00000000000 --- a/pkg/services/apiserver/aggregator/examples/manual-test/apiservice.yaml +++ /dev/null @@ -1,15 +0,0 @@ ---- -apiVersion: apiregistration.k8s.io/v1 -kind: APIService -metadata: - name: v0alpha1.example.grafana.app -spec: - version: v0alpha1 - insecureSkipTLSVerify: true - group: example.grafana.app - groupPriorityMinimum: 1000 - versionPriority: 15 - service: - name: example-apiserver - namespace: grafana - port: 7443 diff --git a/pkg/services/apiserver/aggregator/examples/manual-test/externalname.yaml b/pkg/services/apiserver/aggregator/examples/manual-test/externalname.yaml deleted file mode 100644 index 731782611d5..00000000000 --- a/pkg/services/apiserver/aggregator/examples/manual-test/externalname.yaml +++ /dev/null @@ -1,8 +0,0 @@ ---- -apiVersion: service.grafana.app/v0alpha1 -kind: ExternalName -metadata: - name: example-apiserver - namespace: grafana -spec: - host: localhost diff --git a/pkg/services/apiserver/aggregator/metrics.go b/pkg/services/apiserver/aggregator/metrics.go deleted file mode 100644 index 64798dad63c..00000000000 --- a/pkg/services/apiserver/aggregator/metrics.go +++ /dev/null @@ -1,170 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -// Provenance-includes-location: https://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/kube-aggregator/pkg/controllers/status/metrics/metrics.go -// Provenance-includes-license: Apache-2.0 -// Provenance-includes-copyright: The Kubernetes Authors. - -package aggregator - -import ( - "sync" - - "k8s.io/component-base/metrics" - apiregistrationv1 "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1" - apiregistrationv1apihelper "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1/helper" -) - -/* - * By default, all the following metrics are defined as falling under - * ALPHA stability level https://github.com/kubernetes/enhancements/blob/master/keps/sig-instrumentation/1209-metrics-stability/kubernetes-control-plane-metrics-stability.md#stability-classes) - * - * Promoting the stability level of the metric is a responsibility of the component owner, since it - * involves explicitly acknowledging support for the metric across multiple releases, in accordance with - * the metric stability policy. - */ -var ( - unavailableGaugeDesc = metrics.NewDesc( - "st_aggregator_unavailable_apiservice", - "Gauge of Grafana APIServices which are marked as unavailable broken down by APIService name.", - []string{"name"}, - nil, - metrics.ALPHA, - "", - ) -) - -type Metrics struct { - unavailableCounter *metrics.CounterVec - - *availabilityCollector -} - -func newAvailabilityMetrics() *Metrics { - return &Metrics{ - unavailableCounter: metrics.NewCounterVec( - &metrics.CounterOpts{ - // These metrics are registered in the main kube-aggregator package as well, prefixing with single-tenant (ST) to avoid - // "duplicate metrics collector registration attempted" in https://github.com/prometheus/client_golang - // a more descriptive prefix is already added for apiserver metrics during scraping in cloud and didn't want - // to double a word by using a word such as "grafana" here - Name: "st_aggregator_unavailable_apiservice_total", - Help: "Counter of Grafana APIServices which are marked as unavailable broken down by APIService name and reason.", - StabilityLevel: metrics.ALPHA, - }, - []string{"name", "reason"}, - ), - availabilityCollector: newAvailabilityCollector(), - } -} - -// Register registers apiservice availability metrics. -func (m *Metrics) Register( - registrationFunc func(metrics.Registerable) error, - customRegistrationFunc func(metrics.StableCollector) error, -) error { - err := registrationFunc(m.unavailableCounter) - if err != nil { - return err - } - - err = customRegistrationFunc(m.availabilityCollector) - if err != nil { - return err - } - - return nil -} - -// UnavailableCounter returns a counter to track apiservices marked as unavailable. -func (m *Metrics) UnavailableCounter(apiServiceName, reason string) metrics.CounterMetric { - return m.unavailableCounter.WithLabelValues(apiServiceName, reason) -} - -type availabilityCollector struct { - metrics.BaseStableCollector - - mtx sync.RWMutex - availabilities map[string]bool -} - -// SetUnavailableGauge set the metrics so that it reflect the current state based on availability of the given service -func (m *Metrics) SetUnavailableGauge(newAPIService *apiregistrationv1.APIService) { - if apiregistrationv1apihelper.IsAPIServiceConditionTrue(newAPIService, apiregistrationv1.Available) { - m.SetAPIServiceAvailable(newAPIService.Name) - return - } - - m.SetAPIServiceUnavailable(newAPIService.Name) -} - -// SetUnavailableCounter increases the metrics only if the given service is unavailable and its APIServiceCondition has changed -func (m *Metrics) SetUnavailableCounter(originalAPIService, newAPIService *apiregistrationv1.APIService) { - wasAvailable := apiregistrationv1apihelper.IsAPIServiceConditionTrue(originalAPIService, apiregistrationv1.Available) - isAvailable := apiregistrationv1apihelper.IsAPIServiceConditionTrue(newAPIService, apiregistrationv1.Available) - statusChanged := isAvailable != wasAvailable - - if statusChanged && !isAvailable { - reason := "UnknownReason" - if newCondition := apiregistrationv1apihelper.GetAPIServiceConditionByType(newAPIService, apiregistrationv1.Available); newCondition != nil { - reason = newCondition.Reason - } - m.UnavailableCounter(newAPIService.Name, reason).Inc() - } -} - -// Check if apiServiceStatusCollector implements necessary interface. -var _ metrics.StableCollector = &availabilityCollector{} - -func newAvailabilityCollector() *availabilityCollector { - return &availabilityCollector{ - availabilities: make(map[string]bool), - } -} - -// DescribeWithStability implements the metrics.StableCollector interface. -func (c *availabilityCollector) DescribeWithStability(ch chan<- *metrics.Desc) { - ch <- unavailableGaugeDesc -} - -// CollectWithStability implements the metrics.StableCollector interface. -func (c *availabilityCollector) CollectWithStability(ch chan<- metrics.Metric) { - c.mtx.RLock() - defer c.mtx.RUnlock() - - for apiServiceName, isAvailable := range c.availabilities { - gaugeValue := 1.0 - if isAvailable { - gaugeValue = 0.0 - } - ch <- metrics.NewLazyConstMetric( - unavailableGaugeDesc, - metrics.GaugeValue, - gaugeValue, - apiServiceName, - ) - } -} - -// SetAPIServiceAvailable sets the given apiservice availability gauge to available. -func (c *availabilityCollector) SetAPIServiceAvailable(apiServiceKey string) { - c.setAPIServiceAvailability(apiServiceKey, true) -} - -// SetAPIServiceUnavailable sets the given apiservice availability gauge to unavailable. -func (c *availabilityCollector) SetAPIServiceUnavailable(apiServiceKey string) { - c.setAPIServiceAvailability(apiServiceKey, false) -} - -func (c *availabilityCollector) setAPIServiceAvailability(apiServiceKey string, availability bool) { - c.mtx.Lock() - defer c.mtx.Unlock() - - c.availabilities[apiServiceKey] = availability -} - -// ForgetAPIService removes the availability gauge of the given apiservice. -func (c *availabilityCollector) ForgetAPIService(apiServiceKey string) { - c.mtx.Lock() - defer c.mtx.Unlock() - - delete(c.availabilities, apiServiceKey) -} diff --git a/pkg/services/apiserver/aggregator/resolver.go b/pkg/services/apiserver/aggregator/resolver.go deleted file mode 100644 index c0970ab726c..00000000000 --- a/pkg/services/apiserver/aggregator/resolver.go +++ /dev/null @@ -1,32 +0,0 @@ -package aggregator - -import ( - "fmt" - "net" - "net/url" - - "k8s.io/kube-aggregator/pkg/apiserver" - - servicelistersv0alpha1 "github.com/grafana/grafana/pkg/generated/listers/service/v0alpha1" -) - -func NewExternalNameResolver(externalNames servicelistersv0alpha1.ExternalNameLister) apiserver.ServiceResolver { - return &externalNameResolver{ - externalNames: externalNames, - } -} - -type externalNameResolver struct { - externalNames servicelistersv0alpha1.ExternalNameLister -} - -func (r *externalNameResolver) ResolveEndpoint(namespace, name string, port int32) (*url.URL, error) { - extName, err := r.externalNames.ExternalNames(namespace).Get(name) - if err != nil { - return nil, err - } - return &url.URL{ - Scheme: "https", - Host: net.JoinHostPort(extName.Spec.Host, fmt.Sprintf("%d", port)), - }, nil -} diff --git a/pkg/services/apiserver/aggregatorrunner/noopaggregator.go b/pkg/services/apiserver/aggregatorrunner/noopaggregator.go new file mode 100644 index 00000000000..a399955f04e --- /dev/null +++ b/pkg/services/apiserver/aggregatorrunner/noopaggregator.go @@ -0,0 +1,26 @@ +package aggregatorrunner + +import ( + "context" + + "k8s.io/apimachinery/pkg/runtime" + genericapiserver "k8s.io/apiserver/pkg/server" + + "github.com/grafana/grafana/pkg/services/apiserver/builder" + "github.com/grafana/grafana/pkg/services/apiserver/options" +) + +type NoopAggregatorConfigurator struct { +} + +func (n NoopAggregatorConfigurator) Configure(opts *options.Options, config *genericapiserver.RecommendedConfig, delegateAPIServer genericapiserver.DelegationTarget, scheme *runtime.Scheme, builders []builder.APIGroupBuilder) (*genericapiserver.GenericAPIServer, error) { + return nil, nil +} + +func (n NoopAggregatorConfigurator) Run(ctx context.Context, transport *options.RoundTripperFunc, stoppedCh chan error) (*genericapiserver.GenericAPIServer, error) { + return nil, nil +} + +func ProvideNoopAggregatorConfigurator() AggregatorRunner { + return &NoopAggregatorConfigurator{} +} diff --git a/pkg/services/apiserver/aggregatorrunner/runner.go b/pkg/services/apiserver/aggregatorrunner/runner.go new file mode 100644 index 00000000000..8cfb04622dc --- /dev/null +++ b/pkg/services/apiserver/aggregatorrunner/runner.go @@ -0,0 +1,24 @@ +package aggregatorrunner + +import ( + "context" + + "k8s.io/apimachinery/pkg/runtime" + genericapiserver "k8s.io/apiserver/pkg/server" + + "github.com/grafana/grafana/pkg/services/apiserver/builder" + "github.com/grafana/grafana/pkg/services/apiserver/options" +) + +// AggregatorRunner is an interface for running an aggregator inside the same generic apiserver delegate chain +type AggregatorRunner interface { + // Configure is called to configure the component and returns the delegate for further chaining. + Configure(opts *options.Options, + config *genericapiserver.RecommendedConfig, + delegateAPIServer genericapiserver.DelegationTarget, + scheme *runtime.Scheme, + builders []builder.APIGroupBuilder) (*genericapiserver.GenericAPIServer, error) + + // Run starts the complete apiserver chain, expects it executes any logic inside a goroutine and doesn't block. Returns the running server. + Run(ctx context.Context, transport *options.RoundTripperFunc, stoppedCh chan error) (*genericapiserver.GenericAPIServer, error) +} diff --git a/pkg/services/apiserver/config.go b/pkg/services/apiserver/config.go index e995ce14596..8cf5789c777 100644 --- a/pkg/services/apiserver/config.go +++ b/pkg/services/apiserver/config.go @@ -46,13 +46,6 @@ func applyGrafanaConfig(cfg *setting.Cfg, features featuremgmt.FeatureToggles, o o.RecommendedOptions.Authentication.RemoteKubeConfigFileOptional = true o.RecommendedOptions.Authorization.RemoteKubeConfigFileOptional = true - o.KubeAggregatorOptions.ProxyClientCertFile = apiserverCfg.Key("proxy_client_cert_file").MustString("") - o.KubeAggregatorOptions.ProxyClientKeyFile = apiserverCfg.Key("proxy_client_key_file").MustString("") - o.KubeAggregatorOptions.LegacyClientCertAuth = apiserverCfg.Key("legacy_client_cert_auth").MustBool(true) - - o.KubeAggregatorOptions.APIServiceCABundleFile = apiserverCfg.Key("apiservice_ca_bundle_file").MustString("") - o.KubeAggregatorOptions.RemoteServicesFile = apiserverCfg.Key("remote_services_file").MustString("") - o.RecommendedOptions.Admission = nil o.RecommendedOptions.CoreAPI = nil diff --git a/pkg/services/apiserver/options/grafana-aggregator.go b/pkg/services/apiserver/options/grafana-aggregator.go index 693f27ddd34..367b4940cc7 100644 --- a/pkg/services/apiserver/options/grafana-aggregator.go +++ b/pkg/services/apiserver/options/grafana-aggregator.go @@ -3,7 +3,6 @@ package options import ( "maps" - "github.com/spf13/pflag" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apiserver/pkg/endpoints/openapi" @@ -12,6 +11,8 @@ import ( "k8s.io/apiserver/pkg/server/resourceconfig" "k8s.io/kube-openapi/pkg/common" + "github.com/spf13/pflag" + "github.com/grafana/grafana/pkg/aggregator/apis/aggregation/v0alpha1" aggregatorapiserver "github.com/grafana/grafana/pkg/aggregator/apiserver" aggregatorscheme "github.com/grafana/grafana/pkg/aggregator/apiserver/scheme" @@ -88,7 +89,5 @@ func (o *GrafanaAggregatorOptions) ApplyTo(aggregatorConfig *aggregatorapiserver } genericConfig.MergedResourceConfig = mergedResourceConfig - genericConfig.PostStartHooks = map[string]genericapiserver.PostStartHookConfigEntry{} - return nil } diff --git a/pkg/services/apiserver/options/kube-aggregator.go b/pkg/services/apiserver/options/kube-aggregator.go deleted file mode 100644 index abab98e175d..00000000000 --- a/pkg/services/apiserver/options/kube-aggregator.go +++ /dev/null @@ -1,114 +0,0 @@ -package options - -import ( - "github.com/spf13/pflag" - v1 "k8s.io/api/apps/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" - genericfeatures "k8s.io/apiserver/pkg/features" - genericapiserver "k8s.io/apiserver/pkg/server" - "k8s.io/apiserver/pkg/server/options" - "k8s.io/apiserver/pkg/server/resourceconfig" - utilfeature "k8s.io/apiserver/pkg/util/feature" - apiregistrationv1beta1 "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1beta1" - aggregatorapiserver "k8s.io/kube-aggregator/pkg/apiserver" - aggregatorscheme "k8s.io/kube-aggregator/pkg/apiserver/scheme" - - servicev0alpha1 "github.com/grafana/grafana/pkg/apis/service/v0alpha1" - "github.com/grafana/grafana/pkg/storage/unified/apistore" -) - -// KubeAggregatorOptions contains the state for the aggregator apiserver -type KubeAggregatorOptions struct { - AlternateDNS []string - ProxyClientCertFile string - ProxyClientKeyFile string - LegacyClientCertAuth bool - RemoteServicesFile string - APIServiceCABundleFile string -} - -func NewAggregatorServerOptions() *KubeAggregatorOptions { - return &KubeAggregatorOptions{} -} - -func (o *KubeAggregatorOptions) AddFlags(fs *pflag.FlagSet) { - if o == nil { - return - } - - // the following two config variables are slated to be faded out in cloud deployments after which - // their scope is restricted to local development and non Grafana Cloud use-cases only - // leaving them unspecified leads to graceful behavior in grafana-aggregator - // and would work for configurations where the aggregated servers and aggregator are auth-less and trusting - // of each other - fs.StringVar(&o.ProxyClientCertFile, "proxy-client-cert-file", o.ProxyClientCertFile, - "path to proxy client cert file") - - fs.StringVar(&o.ProxyClientKeyFile, "proxy-client-key-file", o.ProxyClientKeyFile, - "path to proxy client key file") - - fs.BoolVar(&o.LegacyClientCertAuth, "legacy-client-cert-auth", true, - "whether to use legacy client cert auth") -} - -func (o *KubeAggregatorOptions) Validate() []error { - if o == nil { - return nil - } - - // TODO: do we need to validate anything here? - return nil -} - -func (o *KubeAggregatorOptions) ApplyTo(aggregatorConfig *aggregatorapiserver.Config, etcdOpts *options.EtcdOptions) error { - genericConfig := aggregatorConfig.GenericConfig - - genericConfig.PostStartHooks = map[string]genericapiserver.PostStartHookConfigEntry{} - genericConfig.RESTOptionsGetter = nil - - if utilfeature.DefaultFeatureGate.Enabled(genericfeatures.StorageVersionAPI) && - utilfeature.DefaultFeatureGate.Enabled(genericfeatures.APIServerIdentity) { - // Add StorageVersionPrecondition handler to aggregator-apiserver. - // The handler will block write requests to built-in resources until the - // target resources' storage versions are up-to-date. - genericConfig.BuildHandlerChainFunc = genericapiserver.BuildHandlerChainWithStorageVersionPrecondition - } - - // copy the etcd options so we don't mutate originals. - // we assume that the etcd options have been completed already. avoid messing with anything outside - // of changes to StorageConfig as that may lead to unexpected behavior when the options are applied. - etcdOptions := *etcdOpts - etcdOptions.StorageConfig.Codec = aggregatorscheme.Codecs.LegacyCodec(v1.SchemeGroupVersion, - apiregistrationv1beta1.SchemeGroupVersion, - servicev0alpha1.SchemeGroupVersion) - etcdOptions.StorageConfig.EncodeVersioner = runtime.NewMultiGroupVersioner(v1.SchemeGroupVersion, - schema.GroupKind{Group: apiregistrationv1beta1.GroupName}, - schema.GroupKind{Group: servicev0alpha1.GROUP}) - etcdOptions.SkipHealthEndpoints = true // avoid double wiring of health checks - if err := etcdOptions.ApplyTo(&genericConfig.Config); err != nil { - return err - } - // override the RESTOptionsGetter to use the in memory storage options - restOptionsGetter, err := apistore.NewRESTOptionsGetterMemory(etcdOptions.StorageConfig) - if err != nil { - return err - } - aggregatorConfig.GenericConfig.RESTOptionsGetter = restOptionsGetter - - // prevent generic API server from installing the OpenAPI handler. Aggregator server has its own customized OpenAPI handler. - genericConfig.SkipOpenAPIInstallation = true - mergedResourceConfig, err := resourceconfig.MergeAPIResourceConfigs(aggregatorapiserver.DefaultAPIResourceConfigSource(), nil, aggregatorscheme.Scheme) - if err != nil { - return err - } - genericConfig.MergedResourceConfig = mergedResourceConfig - - genericConfig.PostStartHooks = map[string]genericapiserver.PostStartHookConfigEntry{} - - // These hooks use v1 informers, which are not available in the grafana aggregator. - genericConfig.DisabledPostStartHooks = genericConfig.DisabledPostStartHooks.Insert("start-kube-aggregator-informers") - genericConfig.DisabledPostStartHooks = genericConfig.DisabledPostStartHooks.Insert("apiservice-status-local-available-controller") - - return nil -} diff --git a/pkg/services/apiserver/options/options.go b/pkg/services/apiserver/options/options.go index 7fd29359e6f..a9209cdbc00 100644 --- a/pkg/services/apiserver/options/options.go +++ b/pkg/services/apiserver/options/options.go @@ -3,11 +3,12 @@ package options import ( "net" - "github.com/spf13/pflag" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apiserver/pkg/endpoints/discovery/aggregated" genericapiserver "k8s.io/apiserver/pkg/server" genericoptions "k8s.io/apiserver/pkg/server/options" + + "github.com/spf13/pflag" ) type OptionsProvider interface { @@ -21,7 +22,6 @@ const defaultEtcdPathPrefix = "/registry/grafana.app" type Options struct { RecommendedOptions *genericoptions.RecommendedOptions GrafanaAggregatorOptions *GrafanaAggregatorOptions - KubeAggregatorOptions *KubeAggregatorOptions StorageOptions *StorageOptions ExtraOptions *ExtraOptions APIOptions []OptionsProvider @@ -31,7 +31,6 @@ func NewOptions(codec runtime.Codec) *Options { return &Options{ RecommendedOptions: NewRecommendedOptions(codec), GrafanaAggregatorOptions: NewGrafanaAggregatorOptions(), - KubeAggregatorOptions: NewAggregatorServerOptions(), StorageOptions: NewStorageOptions(), ExtraOptions: NewExtraOptions(), } @@ -40,7 +39,6 @@ func NewOptions(codec runtime.Codec) *Options { func (o *Options) AddFlags(fs *pflag.FlagSet) { o.RecommendedOptions.AddFlags(fs) o.GrafanaAggregatorOptions.AddFlags(fs) - o.KubeAggregatorOptions.AddFlags(fs) o.StorageOptions.AddFlags(fs) o.ExtraOptions.AddFlags(fs) @@ -62,10 +60,6 @@ func (o *Options) Validate() []error { return errs } - if errs := o.KubeAggregatorOptions.Validate(); len(errs) != 0 { - return errs - } - if errs := o.RecommendedOptions.SecureServing.Validate(); len(errs) != 0 { return errs } diff --git a/pkg/services/apiserver/options/ready-roundtripper.go b/pkg/services/apiserver/options/ready-roundtripper.go new file mode 100644 index 00000000000..01ed990046c --- /dev/null +++ b/pkg/services/apiserver/options/ready-roundtripper.go @@ -0,0 +1,21 @@ +package options + +import ( + "net/http" +) + +// NOTE: both dataplane aggregator and kubernetes aggregator (when enterprise is linked) have logic around +// setting this RoundTripper as ready, however, kubernetes aggregator part is skipped naturally, +// given it is invoked as part of the delegate chain headed by the dataplane aggregator, and not through +// its own Run method. +type RoundTripperFunc struct { + Ready chan struct{} + Fn func(req *http.Request) (*http.Response, error) +} + +func (f *RoundTripperFunc) RoundTrip(req *http.Request) (*http.Response, error) { + if f.Fn == nil { + <-f.Ready + } + return f.Fn(req) +} diff --git a/pkg/services/apiserver/service.go b/pkg/services/apiserver/service.go index 56a33f6f98f..2ac8391ebe5 100644 --- a/pkg/services/apiserver/service.go +++ b/pkg/services/apiserver/service.go @@ -6,7 +6,6 @@ import ( "net/http" "path" - "github.com/prometheus/client_golang/prometheus" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/serializer" @@ -16,7 +15,6 @@ import ( "k8s.io/apiserver/pkg/util/notfoundhandler" clientrest "k8s.io/client-go/rest" "k8s.io/client-go/tools/clientcmd" - aggregatorapiserver "k8s.io/kube-aggregator/pkg/apiserver" "github.com/grafana/dskit/services" "github.com/grafana/grafana-plugin-sdk-go/backend" @@ -35,7 +33,7 @@ import ( "github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/registry" "github.com/grafana/grafana/pkg/registry/apis/datasource" - kubeaggregator "github.com/grafana/grafana/pkg/services/apiserver/aggregator" + "github.com/grafana/grafana/pkg/services/apiserver/aggregatorrunner" "github.com/grafana/grafana/pkg/services/apiserver/auth/authenticator" "github.com/grafana/grafana/pkg/services/apiserver/auth/authorizer" "github.com/grafana/grafana/pkg/services/apiserver/builder" @@ -49,6 +47,7 @@ import ( "github.com/grafana/grafana/pkg/storage/legacysql/dualwrite" "github.com/grafana/grafana/pkg/storage/unified/apistore" "github.com/grafana/grafana/pkg/storage/unified/resource" + "github.com/prometheus/client_golang/prometheus" ) var ( @@ -102,6 +101,7 @@ type service struct { restConfigProvider RestConfigProvider buildHandlerChainFuncFromBuilders builder.BuildHandlerChainFuncFromBuilders + aggregatorRunner aggregatorrunner.AggregatorRunner } func ProvideService( @@ -122,6 +122,7 @@ func ProvideService( buildHandlerChainFuncFromBuilders builder.BuildHandlerChainFuncFromBuilders, eventualRestConfigProvider *eventualRestConfigProvider, reg prometheus.Registerer, + aggregatorRunner aggregatorrunner.AggregatorRunner, ) (*service, error) { scheme := builder.ProvideScheme() codecs := builder.ProvideCodecFactory(scheme) @@ -148,6 +149,7 @@ func ProvideService( unified: unified, restConfigProvider: restConfigProvider, buildHandlerChainFuncFromBuilders: buildHandlerChainFuncFromBuilders, + aggregatorRunner: aggregatorRunner, } // This will be used when running as a dskit service service := services.NewBasicService(s.start, s.running, nil).WithName(modules.GrafanaAPIServer) @@ -234,7 +236,7 @@ func (s *service) start(ctx context.Context) error { groupVersions := make([]schema.GroupVersion, 0, len(builders)) // Install schemas - for i, b := range builders { + for _, b := range builders { gvs := builder.GetGroupVersions(b) groupVersions = append(groupVersions, gvs...) if len(gvs) == 0 { @@ -245,12 +247,7 @@ func (s *service) start(ctx context.Context) error { } pvs := s.scheme.PrioritizedVersionsForGroup(gvs[0].Group) - for j, gv := range pvs { - if s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesAggregator) { - // set the priority for the group+version - kubeaggregator.APIVersionPriorities[gv] = kubeaggregator.Priority{Group: int32(15000 + i), Version: int32(len(pvs) - j)} - } - + for _, gv := range pvs { if a, ok := b.(builder.APIGroupAuthorizer); ok { auth := a.GetAuthorizer() if auth != nil { @@ -280,7 +277,7 @@ func (s *service) start(ctx context.Context) error { serverConfig.TracerProvider = s.tracing.GetTracerProvider() // setup loopback transport for the aggregator server - transport := &roundTripperFunc{ready: make(chan struct{})} + transport := &grafanaapiserveroptions.RoundTripperFunc{Ready: make(chan struct{})} serverConfig.LoopbackClientConfig.Transport = transport serverConfig.LoopbackClientConfig.TLSClientConfig = clientrest.TLSClientConfig{} serverConfig.MaxRequestBodyBytes = MaxRequestBodyBytes @@ -341,27 +338,41 @@ func (s *service) start(ctx context.Context) error { s.options = o delegate := server - var aggregatorServer *aggregatorapiserver.APIAggregator - if s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesAggregator) { - aggregatorServer, err = s.createKubeAggregator(serverConfig, server, s.metrics) + + var runningServer *genericapiserver.GenericAPIServer + isKubernetesAggregatorEnabled := s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesAggregator) + isDataplaneAggregatorEnabled := s.features.IsEnabledGlobally(featuremgmt.FlagDataplaneAggregator) + + if isKubernetesAggregatorEnabled { + aggregatorServer, err := s.aggregatorRunner.Configure(s.options, serverConfig, delegate, s.scheme, builders) if err != nil { return err } - delegate = aggregatorServer.GenericAPIServer + // we are running with KubernetesAggregator FT set to true but with enterprise unlinked, handle this gracefully + if aggregatorServer != nil { + if !isDataplaneAggregatorEnabled { + runningServer, err = s.aggregatorRunner.Run(ctx, transport, s.stoppedCh) + if err != nil { + s.log.Error("aggregator runner failed to run", "error", err) + return err + } + } else { + delegate = aggregatorServer + } + } else { + // even though the FT is set to true, enterprise isn't linked + isKubernetesAggregatorEnabled = false + } } - var runningServer *genericapiserver.GenericAPIServer - if s.features.IsEnabledGlobally(featuremgmt.FlagDataplaneAggregator) { + if isDataplaneAggregatorEnabled { runningServer, err = s.startDataplaneAggregator(ctx, transport, serverConfig, delegate) if err != nil { return err } - } else if s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesAggregator) { - runningServer, err = s.startKubeAggregator(ctx, transport, aggregatorServer) - if err != nil { - return err - } - } else { + } + + if !isDataplaneAggregatorEnabled && !isKubernetesAggregatorEnabled { runningServer, err = s.startCoreServer(ctx, transport, server) if err != nil { return err @@ -385,15 +396,15 @@ func (s *service) start(ctx context.Context) error { func (s *service) startCoreServer( ctx context.Context, - transport *roundTripperFunc, + transport *grafanaapiserveroptions.RoundTripperFunc, server *genericapiserver.GenericAPIServer, ) (*genericapiserver.GenericAPIServer, error) { // setup the loopback transport and signal that it's ready. // ignore the lint error because the response is passed directly to the client, // so the client will be responsible for closing the response body. // nolint:bodyclose - transport.fn = grafanaresponsewriter.WrapHandler(server.Handler) - close(transport.ready) + transport.Fn = grafanaresponsewriter.WrapHandler(server.Handler) + close(transport.Ready) prepared := server.PrepareRun() go func() { @@ -405,7 +416,7 @@ func (s *service) startCoreServer( func (s *service) startDataplaneAggregator( ctx context.Context, - transport *roundTripperFunc, + transport *grafanaapiserveroptions.RoundTripperFunc, serverConfig *genericapiserver.RecommendedConfig, delegate *genericapiserver.GenericAPIServer, ) (*genericapiserver.GenericAPIServer, error) { @@ -436,8 +447,8 @@ func (s *service) startDataplaneAggregator( // ignore the lint error because the response is passed directly to the client, // so the client will be responsible for closing the response body. // nolint:bodyclose - transport.fn = grafanaresponsewriter.WrapHandler(aggregatorServer.GenericAPIServer.Handler) - close(transport.ready) + transport.Fn = grafanaresponsewriter.WrapHandler(aggregatorServer.GenericAPIServer.Handler) + close(transport.Ready) prepared, err := aggregatorServer.PrepareRun() if err != nil { @@ -451,49 +462,10 @@ func (s *service) startDataplaneAggregator( return aggregatorServer.GenericAPIServer, nil } -func (s *service) createKubeAggregator( - serverConfig *genericapiserver.RecommendedConfig, - server *genericapiserver.GenericAPIServer, - reg prometheus.Registerer, -) (*aggregatorapiserver.APIAggregator, error) { - namespaceMapper := request.GetNamespaceMapper(s.cfg) - - aggregatorConfig, err := kubeaggregator.CreateAggregatorConfig(s.options, *serverConfig, namespaceMapper(1)) - if err != nil { - return nil, err - } - - return kubeaggregator.CreateAggregatorServer(aggregatorConfig, server, reg) -} - -func (s *service) startKubeAggregator( - ctx context.Context, - transport *roundTripperFunc, - aggregatorServer *aggregatorapiserver.APIAggregator, -) (*genericapiserver.GenericAPIServer, error) { - // setup the loopback transport for the aggregator server and signal that it's ready - // ignore the lint error because the response is passed directly to the client, - // so the client will be responsible for closing the response body. - // nolint:bodyclose - transport.fn = grafanaresponsewriter.WrapHandler(aggregatorServer.GenericAPIServer.Handler) - close(transport.ready) - - prepared, err := aggregatorServer.PrepareRun() - if err != nil { - return nil, err - } - - go func() { - s.stoppedCh <- prepared.Run(ctx) - }() - - return aggregatorServer.GenericAPIServer, nil -} - func (s *service) GetDirectRestConfig(c *contextmodel.ReqContext) *clientrest.Config { return &clientrest.Config{ - Transport: &roundTripperFunc{ - fn: func(req *http.Request) (*http.Response, error) { + Transport: &grafanaapiserveroptions.RoundTripperFunc{ + Fn: func(req *http.Request) (*http.Response, error) { if err := s.AwaitRunning(req.Context()); err != nil { return nil, err } @@ -531,18 +503,6 @@ func ensureKubeConfig(restConfig *clientrest.Config, dir string) error { ) } -type roundTripperFunc struct { - ready chan struct{} - fn func(req *http.Request) (*http.Response, error) -} - -func (f *roundTripperFunc) RoundTrip(req *http.Request) (*http.Response, error) { - if f.fn == nil { - <-f.ready - } - return f.fn(req) -} - type pluginContextProvider struct { pluginStore pluginstore.Store datasources datasource.ScopedPluginDatasourceProvider diff --git a/pkg/services/featuremgmt/registry.go b/pkg/services/featuremgmt/registry.go index 44b647592e0..45fc3f955e6 100644 --- a/pkg/services/featuremgmt/registry.go +++ b/pkg/services/featuremgmt/registry.go @@ -875,6 +875,13 @@ var ( Owner: grafanaAppPlatformSquad, RequiresRestart: true, }, + { + Name: "kubernetesAggregatorCapTokenAuth", + Description: "Enable CAP token based authentication in grafana's embedded kube-aggregator", + Stage: FeatureStageExperimental, + Owner: grafanaAppPlatformSquad, + RequiresRestart: true, + }, { Name: "expressionParser", Description: "Enable new expression parser", diff --git a/pkg/services/featuremgmt/toggles_gen.csv b/pkg/services/featuremgmt/toggles_gen.csv index 5f997138bbe..ffbae23fc96 100644 --- a/pkg/services/featuremgmt/toggles_gen.csv +++ b/pkg/services/featuremgmt/toggles_gen.csv @@ -114,6 +114,7 @@ groupToNestedTableTransformation,GA,@grafana/dataviz-squad,false,false,true newPDFRendering,GA,@grafana/sharing-squad,false,false,false tlsMemcached,GA,@grafana/grafana-operator-experience-squad,false,false,false kubernetesAggregator,experimental,@grafana/grafana-app-platform-squad,false,true,false +kubernetesAggregatorCapTokenAuth,experimental,@grafana/grafana-app-platform-squad,false,true,false expressionParser,experimental,@grafana/grafana-app-platform-squad,false,true,false groupByVariable,experimental,@grafana/dashboards-squad,false,false,false scopeFilters,experimental,@grafana/dashboards-squad,false,false,false diff --git a/pkg/services/featuremgmt/toggles_gen.go b/pkg/services/featuremgmt/toggles_gen.go index c528219c193..05a693b67e7 100644 --- a/pkg/services/featuremgmt/toggles_gen.go +++ b/pkg/services/featuremgmt/toggles_gen.go @@ -467,6 +467,10 @@ const ( // Enable grafana's embedded kube-aggregator FlagKubernetesAggregator = "kubernetesAggregator" + // FlagKubernetesAggregatorCapTokenAuth + // Enable CAP token based authentication in grafana's embedded kube-aggregator + FlagKubernetesAggregatorCapTokenAuth = "kubernetesAggregatorCapTokenAuth" + // FlagExpressionParser // Enable new expression parser FlagExpressionParser = "expressionParser" diff --git a/pkg/services/featuremgmt/toggles_gen.json b/pkg/services/featuremgmt/toggles_gen.json index 43db84470b4..a755f0a79aa 100644 --- a/pkg/services/featuremgmt/toggles_gen.json +++ b/pkg/services/featuremgmt/toggles_gen.json @@ -1695,6 +1695,22 @@ "requiresRestart": true } }, + { + "metadata": { + "name": "kubernetesAggregatorCapTokenAuth", + "resourceVersion": "1746996924159", + "creationTimestamp": "2025-05-11T20:48:13Z", + "annotations": { + "grafana.app/updatedTimestamp": "2025-05-11 20:55:24.159856 +0000 UTC" + } + }, + "spec": { + "description": "Enable CAP token based authentication in grafana's embedded kube-aggregator", + "stage": "experimental", + "codeowner": "@grafana/grafana-app-platform-squad", + "requiresRestart": true + } + }, { "metadata": { "name": "kubernetesClientDashboardsFolders", diff --git a/pkg/storage/unified/apistore/go.mod b/pkg/storage/unified/apistore/go.mod index bb85fa46f0a..6ac8dbdd091 100644 --- a/pkg/storage/unified/apistore/go.mod +++ b/pkg/storage/unified/apistore/go.mod @@ -120,7 +120,7 @@ require ( github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect github.com/googleapis/gax-go/v2 v2.14.1 // indirect github.com/gorilla/mux v1.8.1 // indirect - github.com/grafana/authlib v0.0.0-20250422131730-e8482efe6b8a // indirect + github.com/grafana/authlib v0.0.0-20250430134519-13c42d09f9f5 // indirect github.com/grafana/dskit v0.0.0-20241105154643-a6b453a88040 // indirect github.com/grafana/grafana-app-sdk v0.35.1 // indirect github.com/grafana/grafana-app-sdk/logging v0.35.1 // indirect diff --git a/pkg/storage/unified/apistore/go.sum b/pkg/storage/unified/apistore/go.sum index d5a8bf92159..1b17e3df2ed 100644 --- a/pkg/storage/unified/apistore/go.sum +++ b/pkg/storage/unified/apistore/go.sum @@ -308,8 +308,7 @@ github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORR github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo= -github.com/grafana/authlib v0.0.0-20250422131730-e8482efe6b8a h1:irEH0Abl6mKbkPx/xtmB5Wai4ipzEB6hGPNsLya/p9Y= -github.com/grafana/authlib v0.0.0-20250422131730-e8482efe6b8a/go.mod h1:PBtQaXwkFu4BAt2aXsR7w8p8NVpdjV5aJYhqRDei9Us= +github.com/grafana/authlib v0.0.0-20250430134519-13c42d09f9f5 h1:qu6+b9tY6E/CgKrLh8srjtvKOMCxdwk46jRqURjJ61s= github.com/grafana/authlib/types v0.0.0-20250325095148-d6da9c164a7d h1:34E6btDAhdDOiSEyrMaYaHwnJpM8w9QKzVQZIBzLNmM= github.com/grafana/authlib/types v0.0.0-20250325095148-d6da9c164a7d/go.mod h1:qeWYbnWzaYGl88JlL9+DsP1GT2Cudm58rLtx13fKZdw= github.com/grafana/dskit v0.0.0-20241105154643-a6b453a88040 h1:IR+UNYHqaU31t8/TArJk8K/GlDwOyxMpGNkWCXeZ28g= diff --git a/pkg/storage/unified/client.go b/pkg/storage/unified/client.go index 2df1f962f3c..585cbfa3bd2 100644 --- a/pkg/storage/unified/client.go +++ b/pkg/storage/unified/client.go @@ -6,15 +6,16 @@ import ( "path/filepath" "time" - otgrpc "github.com/opentracing-contrib/go-grpc" - "github.com/opentracing/opentracing-go" - "github.com/prometheus/client_golang/prometheus" - "github.com/prometheus/client_golang/prometheus/promauto" "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" "gocloud.dev/blob/fileblob" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" + otgrpc "github.com/opentracing-contrib/go-grpc" + "github.com/opentracing/opentracing-go" + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promauto" + "github.com/grafana/authlib/types" "github.com/grafana/dskit/flagext" "github.com/grafana/dskit/grpcclient" @@ -52,7 +53,7 @@ type clientMetrics struct { // This adds a UnifiedStorage client into the wire dependency tree func ProvideUnifiedStorageClient(opts *Options, storageMetrics *resource.StorageMetrics, indexMetrics *resource.BleveIndexMetrics) (resource.ResourceClient, error) { - // See: apiserver.ApplyGrafanaConfig(cfg, features, o) + // See: apiserver.applyAPIServerConfig(cfg, features, o) apiserverCfg := opts.Cfg.SectionWithEnvOverrides("grafana-apiserver") client, err := newClient(options.StorageOptions{ StorageType: options.StorageType(apiserverCfg.Key("storage_type").MustString(string(options.StorageTypeUnified))), diff --git a/pkg/storage/unified/resource/go.mod b/pkg/storage/unified/resource/go.mod index 50565afedbb..8513363100b 100644 --- a/pkg/storage/unified/resource/go.mod +++ b/pkg/storage/unified/resource/go.mod @@ -13,7 +13,7 @@ require ( github.com/fullstorydev/grpchan v1.1.1 github.com/go-jose/go-jose/v3 v3.0.4 github.com/google/uuid v1.6.0 - github.com/grafana/authlib v0.0.0-20250422131730-e8482efe6b8a + github.com/grafana/authlib v0.0.0-20250430134519-13c42d09f9f5 github.com/grafana/authlib/types v0.0.0-20250325095148-d6da9c164a7d github.com/grafana/dskit v0.0.0-20241105154643-a6b453a88040 github.com/grafana/grafana-app-sdk/logging v0.35.1 diff --git a/pkg/storage/unified/resource/go.sum b/pkg/storage/unified/resource/go.sum index fed7e82c270..e83af3671ad 100644 --- a/pkg/storage/unified/resource/go.sum +++ b/pkg/storage/unified/resource/go.sum @@ -307,8 +307,8 @@ github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e h1:JKmoR8x90Iww1 github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= -github.com/grafana/authlib v0.0.0-20250422131730-e8482efe6b8a h1:irEH0Abl6mKbkPx/xtmB5Wai4ipzEB6hGPNsLya/p9Y= -github.com/grafana/authlib v0.0.0-20250422131730-e8482efe6b8a/go.mod h1:PBtQaXwkFu4BAt2aXsR7w8p8NVpdjV5aJYhqRDei9Us= +github.com/grafana/authlib v0.0.0-20250430134519-13c42d09f9f5 h1:qu6+b9tY6E/CgKrLh8srjtvKOMCxdwk46jRqURjJ61s= +github.com/grafana/authlib v0.0.0-20250430134519-13c42d09f9f5/go.mod h1:PBtQaXwkFu4BAt2aXsR7w8p8NVpdjV5aJYhqRDei9Us= github.com/grafana/authlib/types v0.0.0-20250325095148-d6da9c164a7d h1:34E6btDAhdDOiSEyrMaYaHwnJpM8w9QKzVQZIBzLNmM= github.com/grafana/authlib/types v0.0.0-20250325095148-d6da9c164a7d/go.mod h1:qeWYbnWzaYGl88JlL9+DsP1GT2Cudm58rLtx13fKZdw= github.com/grafana/dskit v0.0.0-20241105154643-a6b453a88040 h1:IR+UNYHqaU31t8/TArJk8K/GlDwOyxMpGNkWCXeZ28g=