diff --git a/Makefile b/Makefile index 7ad46d9..5fef658 100644 --- a/Makefile +++ b/Makefile @@ -118,6 +118,10 @@ $(ENVTEST): test: ./hack/run-tests.sh +.PHONY: test-e2e +test-e2e: $(ENVTEST) $(KCP) + ./hack/run-e2e-tests.sh + .PHONY: codegen codegen: $(YQ) hack/update-codegen-crds.sh diff --git a/deploy/crd/kcp.io/syncagent.kcp.io_publishedresources.yaml b/deploy/crd/kcp.io/syncagent.kcp.io_publishedresources.yaml index 88c01bf..4048990 100644 --- a/deploy/crd/kcp.io/syncagent.kcp.io_publishedresources.yaml +++ b/deploy/crd/kcp.io/syncagent.kcp.io_publishedresources.yaml @@ -4,7 +4,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.16.5 + controller-gen.kubebuilder.io/version: v0.18.0 name: publishedresources.syncagent.kcp.io spec: group: syncagent.kcp.io diff --git a/docs/content/getting-started.md b/docs/content/getting-started.md index 2e78883..447a3b8 100644 --- a/docs/content/getting-started.md +++ b/docs/content/getting-started.md @@ -180,7 +180,8 @@ The Sync Agent needs to * access the workspace of its `APIExport`, * get the `LogicalCluster`, * manage its `APIExport`, -* manage `APIResourceSchemas` and +* manage `APIResourceSchemas`, +* create `events` for `APIExports` and * access the virtual workspace for its `APIExport`. This can be achieved by applying RBAC like this _in the workspace where the `APIExport` resides_: @@ -213,6 +214,16 @@ rules: - watch - patch - update + # issue events for APIExports + - apiGroups: + - "" + resources: + - events + verbs: + - get + - create + - patch + - update # manage APIResourceSchemas - apiGroups: - apis.kcp.io diff --git a/go.mod b/go.mod index 6dc49d4..f189b24 100644 --- a/go.mod +++ b/go.mod @@ -5,10 +5,8 @@ go 1.24.0 replace github.com/kcp-dev/api-syncagent/sdk => ./sdk replace ( - k8s.io/api => github.com/kcp-dev/kubernetes/staging/src/k8s.io/api v0.0.0-20250425143807-ddbe171670d8 - k8s.io/apiextensions-apiserver => github.com/kcp-dev/kubernetes/staging/src/k8s.io/apiextensions-apiserver v0.0.0-20250425143807-ddbe171670d8 - k8s.io/apiserver => github.com/kcp-dev/kubernetes/staging/src/k8s.io/apiserver v0.0.0-20250425143807-ddbe171670d8 - k8s.io/kms => github.com/kcp-dev/kubernetes/staging/src/k8s.io/kms v0.0.0-20250425143807-ddbe171670d8 + k8s.io/apiextensions-apiserver => github.com/kcp-dev/kubernetes/staging/src/k8s.io/apiextensions-apiserver v0.0.0-20250821202322-e42d885de52b + k8s.io/apiserver => github.com/kcp-dev/kubernetes/staging/src/k8s.io/apiserver v0.0.0-20250821202322-e42d885de52b ) require ( @@ -19,10 +17,10 @@ require ( github.com/google/go-cmp v0.7.0 github.com/kcp-dev/api-syncagent/sdk v0.0.0-00010101000000-000000000000 github.com/kcp-dev/code-generator/v2 v2.3.1 - github.com/kcp-dev/kcp v0.28.0 - github.com/kcp-dev/kcp/sdk v0.28.0 + github.com/kcp-dev/kcp v0.28.1 + github.com/kcp-dev/kcp/sdk v0.28.1 github.com/kcp-dev/logicalcluster/v3 v3.0.5 - github.com/kcp-dev/multicluster-provider v0.1.0 + github.com/kcp-dev/multicluster-provider v0.2.1-0.20250901093233-9ef571c8bd47 github.com/openshift-eng/openshift-goimports v0.0.0-20230304234052-c70783e636f2 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 github.com/spf13/cobra v1.9.1 @@ -31,18 +29,22 @@ require ( github.com/tidwall/sjson v1.2.5 go.uber.org/zap v1.27.0 k8c.io/reconciler v0.5.0 - k8s.io/api v0.32.3 - k8s.io/apiextensions-apiserver v0.32.3 - k8s.io/apimachinery v0.32.3 - k8s.io/apiserver v0.32.3 - k8s.io/client-go v0.32.3 - k8s.io/code-generator v0.32.3 - k8s.io/component-base v0.32.3 - k8s.io/kube-openapi v0.0.0-20241212222426-2c72e554b1e7 - k8s.io/utils v0.0.0-20241210054802-24370beab758 - sigs.k8s.io/controller-runtime v0.20.4 - sigs.k8s.io/controller-tools v0.16.5 - sigs.k8s.io/multicluster-runtime v0.20.4-alpha.7 + // Deviating from kcp's kube version (0.32) because more recent multicluster-runtime + // versions are built ontop of controller-runtime 0.21 with Kube 0.33. Ideally in + // the future we should go back to having kcp, controller-runtime and multicluster-runtime + // be in-sync again. + k8s.io/api v0.33.3 + k8s.io/apiextensions-apiserver v0.33.3 + k8s.io/apimachinery v0.33.3 + k8s.io/apiserver v0.33.3 + k8s.io/client-go v0.33.3 + k8s.io/code-generator v0.33.3 + k8s.io/component-base v0.33.3 + k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff + k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 + sigs.k8s.io/controller-runtime v0.21.0 + sigs.k8s.io/controller-tools v0.18.0 + sigs.k8s.io/multicluster-runtime v0.21.0-alpha.9 sigs.k8s.io/yaml v1.4.0 ) @@ -53,7 +55,6 @@ require ( github.com/Masterminds/semver/v3 v3.3.1 // indirect github.com/NYTimes/gziphandler v1.1.1 // indirect github.com/antlr4-go/antlr/v4 v4.13.1 // indirect - github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/blang/semver/v4 v4.0.0 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect @@ -76,9 +77,8 @@ require ( github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/google/btree v1.1.3 // indirect - github.com/google/cel-go v0.22.1 // indirect + github.com/google/cel-go v0.23.2 // indirect github.com/google/gnostic-models v0.6.9 // indirect - github.com/google/gofuzz v1.2.1-0.20210504230335-f78f29fc09ea // indirect github.com/google/uuid v1.6.0 // indirect github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect @@ -87,9 +87,8 @@ require ( github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/kcp-dev/apimachinery/v2 v2.0.1-0.20250512171935-ebb573a40077 // indirect - github.com/kcp-dev/client-go v0.0.0-20250707095244-decc4df45adb // indirect - github.com/klauspost/compress v1.17.11 // indirect + github.com/kcp-dev/apimachinery/v2 v2.0.1-0.20250728122101-adbf20db3e51 // indirect + github.com/kcp-dev/client-go v0.0.0-20250728134101-0355faa9361b // indirect github.com/kylelemons/godebug v1.1.0 // indirect github.com/mailru/easyjson v0.9.0 // indirect github.com/mattn/go-colorable v0.1.14 // indirect @@ -100,11 +99,11 @@ require ( github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - github.com/onsi/gomega v1.36.3 // indirect + github.com/onsi/gomega v1.37.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/pelletier/go-toml/v2 v2.2.3 // indirect github.com/pkg/errors v0.9.1 // indirect - github.com/prometheus/client_golang v1.21.1 // indirect + github.com/prometheus/client_golang v1.22.0 // indirect github.com/prometheus/client_model v0.6.1 // indirect github.com/prometheus/common v0.63.0 // indirect github.com/prometheus/procfs v0.16.0 // indirect @@ -133,17 +132,17 @@ require ( go.opentelemetry.io/otel/trace v1.35.0 // indirect go.opentelemetry.io/proto/otlp v1.5.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/crypto v0.37.0 // indirect + golang.org/x/crypto v0.38.0 // indirect golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 // indirect golang.org/x/mod v0.24.0 // indirect - golang.org/x/net v0.39.0 // indirect + golang.org/x/net v0.40.0 // indirect golang.org/x/oauth2 v0.29.0 // indirect - golang.org/x/sync v0.13.0 // indirect - golang.org/x/sys v0.32.0 // indirect - golang.org/x/term v0.31.0 // indirect - golang.org/x/text v0.24.0 // indirect + golang.org/x/sync v0.14.0 // indirect + golang.org/x/sys v0.33.0 // indirect + golang.org/x/term v0.32.0 // indirect + golang.org/x/text v0.25.0 // indirect golang.org/x/time v0.11.0 // indirect - golang.org/x/tools v0.32.0 // indirect + golang.org/x/tools v0.33.0 // indirect gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250324211829-b45e905df463 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463 // indirect @@ -155,10 +154,11 @@ require ( gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/component-helpers v0.32.3 // indirect k8s.io/controller-manager v0.32.3 // indirect - k8s.io/gengo/v2 v2.0.0-20240911193312-2b36238f13e9 // indirect + k8s.io/gengo/v2 v2.0.0-20250513215321-e3bc6f1e78b4 // indirect k8s.io/klog/v2 v2.130.1 // indirect - k8s.io/kubernetes v1.32.3 // indirect - sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.1 // indirect + k8s.io/kubernetes v1.33.3 // indirect + sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 // indirect sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect + sigs.k8s.io/randfill v1.0.0 // indirect sigs.k8s.io/structured-merge-diff/v4 v4.6.0 // indirect ) diff --git a/go.sum b/go.sum index 8b24684..bf442a3 100644 --- a/go.sum +++ b/go.sum @@ -12,8 +12,6 @@ github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cq github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= -github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= -github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= @@ -79,8 +77,8 @@ github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= -github.com/google/cel-go v0.22.1 h1:AfVXx3chM2qwoSbM7Da8g8hX8OVSkBFwX+rz2+PcK40= -github.com/google/cel-go v0.22.1/go.mod h1:BuznPXXfQDpXKWQ9sPW3TzlAJN5zzFe+i9tIs0yC4s8= +github.com/google/cel-go v0.23.2 h1:UdEe3CvQh3Nv+E/j9r1Y//WO0K0cSyD7/y0bzyLIMI4= +github.com/google/cel-go v0.23.2/go.mod h1:52Pb6QsDbC5kvgxvZhiL9QX1oZEkcUF/ZqaPx1J5Wwo= github.com/google/gnostic-models v0.6.9 h1:MU/8wDLif2qCXZmzncUQ/BOfxWfthHi63KqpoNbWqVw= github.com/google/gnostic-models v0.6.9/go.mod h1:CiWsm0s6BSQd1hRn8/QmxqB6BesYcbSZxsz9b0KuDBw= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= @@ -93,8 +91,8 @@ github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad h1:a6HEuzUHeKH6hwfN/Z github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= 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.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= -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/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw= github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= @@ -115,32 +113,28 @@ github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8Hm github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= -github.com/kcp-dev/apimachinery/v2 v2.0.1-0.20250512171935-ebb573a40077 h1:lDi9nZ75ypmRJwDFXUN70Cdu8+HxAjPU1kcnn+l4MvI= -github.com/kcp-dev/apimachinery/v2 v2.0.1-0.20250512171935-ebb573a40077/go.mod h1:jnMZxVnCuKlkIXc4J1Qtmy1Lyo171CDF/RQhNAo0tvA= -github.com/kcp-dev/client-go v0.0.0-20250707095244-decc4df45adb h1:PTfc4FGjz1Dx+6epz92G4RJj3BYrqg0+XYTiGJQR1cc= -github.com/kcp-dev/client-go v0.0.0-20250707095244-decc4df45adb/go.mod h1:iv3cC1ShwBGzFfNjB+6KWvZviWPe6+MbRlQ7SZoZPFc= +github.com/kcp-dev/apimachinery/v2 v2.0.1-0.20250728122101-adbf20db3e51 h1:l38RDS+VUMx9etvyaCgJIZa4nM7FaNevNubWN0kDZY4= +github.com/kcp-dev/apimachinery/v2 v2.0.1-0.20250728122101-adbf20db3e51/go.mod h1:rF1jfvUfPjFXs+HV/LN1BtPzAz1bfjJOwVa+hAVfroQ= +github.com/kcp-dev/client-go v0.0.0-20250728134101-0355faa9361b h1:2LGrXvY9sc4l5yjKIbMZ86GEou7NyrHhA4qBPaeFfxs= +github.com/kcp-dev/client-go v0.0.0-20250728134101-0355faa9361b/go.mod h1:QdO8AaGAZPr/rIZ1iVanCM3tUOiiuX897GWv7WTByLE= github.com/kcp-dev/code-generator/v2 v2.3.1 h1:FnGGaDeO033d6wg1gBndhZzO/PZAmU0NKVCretEpQbQ= github.com/kcp-dev/code-generator/v2 v2.3.1/go.mod h1:uvIHtZzfv8qPzW9Hym+kL4aNpZaiTBONvPJkTWVVCBk= -github.com/kcp-dev/kcp v0.28.0 h1:J3oaOPqc4A2Q+wZveL0iVElAuOLivFmKTCpaKVx8iXA= -github.com/kcp-dev/kcp v0.28.0/go.mod h1:q28Fx8sU/KA8kz8HGwtaqA7Iom8oR90ydoPK39jMaxo= -github.com/kcp-dev/kcp/sdk v0.28.0 h1:AOgGrgpqhrplbXMSbcvjFwCqwg4UlysTwIFZ0LvFxlk= -github.com/kcp-dev/kcp/sdk v0.28.0/go.mod h1:8oZpWxkoMu2TDpx5DgdIGDigByKHKkeqVMA4GiWneoI= -github.com/kcp-dev/kubernetes/staging/src/k8s.io/api v0.0.0-20250425143807-ddbe171670d8 h1:oPP9XnpYpTv0dvqTGUozYbgvdB16kzX9oZ2r5QfoQxE= -github.com/kcp-dev/kubernetes/staging/src/k8s.io/api v0.0.0-20250425143807-ddbe171670d8/go.mod h1:7sL6AnFDKD/ke3g56SKzA+hLRWWuhujrxUBvRYlGwD0= -github.com/kcp-dev/kubernetes/staging/src/k8s.io/apiextensions-apiserver v0.0.0-20250425143807-ddbe171670d8 h1:lhZjzj5K9bVp33MfkM6KbTH2etE9Wi5e/90jTFmD8NI= -github.com/kcp-dev/kubernetes/staging/src/k8s.io/apiextensions-apiserver v0.0.0-20250425143807-ddbe171670d8/go.mod h1:eOZI4fqbsFue7NxS/nkKW7fmblSsm2N9u3WT6CqL4Sg= -github.com/kcp-dev/kubernetes/staging/src/k8s.io/apiserver v0.0.0-20250425143807-ddbe171670d8 h1:om/ndI5xQPQ4ho8z6A5uckh94QfgLSWt+WHfsRvH6n8= -github.com/kcp-dev/kubernetes/staging/src/k8s.io/apiserver v0.0.0-20250425143807-ddbe171670d8/go.mod h1:wQatO8gBJa1b4KK9fGISKorQLQUleYdQiRoPHHYZXAw= -github.com/kcp-dev/kubernetes/staging/src/k8s.io/kms v0.0.0-20250425143807-ddbe171670d8 h1:z3FDRYca6rhsyHmF8HbwrQ5JTUOygdwbzvGlFTi/MzI= -github.com/kcp-dev/kubernetes/staging/src/k8s.io/kms v0.0.0-20250425143807-ddbe171670d8/go.mod h1:6c0fX+GV12dynYuruueesB5Emf9PwK+Jo/0UijPRhFk= +github.com/kcp-dev/kcp v0.28.1 h1:T7Ky7u9hvprkGrBnKuw0QZoP8O6TCbXqJz2Kwt6Tx+o= +github.com/kcp-dev/kcp v0.28.1/go.mod h1:q28Fx8sU/KA8kz8HGwtaqA7Iom8oR90ydoPK39jMaxo= +github.com/kcp-dev/kcp/sdk v0.28.1 h1:bTtuHVjFRjbwFEqXTPxc1J1JP2Hc3mTYqQ2xfJsi16M= +github.com/kcp-dev/kcp/sdk v0.28.1/go.mod h1:8oZpWxkoMu2TDpx5DgdIGDigByKHKkeqVMA4GiWneoI= +github.com/kcp-dev/kubernetes/staging/src/k8s.io/apiextensions-apiserver v0.0.0-20250821202322-e42d885de52b h1:PsIDMrXs6N6TqdEhjoEQC2xnj6CUV9yOdc5jmlt1PPg= +github.com/kcp-dev/kubernetes/staging/src/k8s.io/apiextensions-apiserver v0.0.0-20250821202322-e42d885de52b/go.mod h1:YS4BozVhJubIHAN+HRLFrXAAPd+vctFRS0u1d6DRUIM= +github.com/kcp-dev/kubernetes/staging/src/k8s.io/apiserver v0.0.0-20250821202322-e42d885de52b h1:G8dAytU6qgg9jTqeYJcsTiJkndOeOsS4/fub7JOu2A0= +github.com/kcp-dev/kubernetes/staging/src/k8s.io/apiserver v0.0.0-20250821202322-e42d885de52b/go.mod h1:STCgTiD+xCCHsfLOPHn5sNVsyktakX/ctW3dMv3erh0= github.com/kcp-dev/logicalcluster/v3 v3.0.5 h1:JbYakokb+5Uinz09oTXomSUJVQsqfxEvU4RyHUYxHOU= github.com/kcp-dev/logicalcluster/v3 v3.0.5/go.mod h1:EWBUBxdr49fUB1cLMO4nOdBWmYifLbP1LfoL20KkXYY= -github.com/kcp-dev/multicluster-provider v0.1.0 h1:LS4z4d6AbsYg7Lj9Hlmkbv1M+ZIyw4laNpSsUgF3tRI= -github.com/kcp-dev/multicluster-provider v0.1.0/go.mod h1:8a53s17AhgsEq5mL7VDHZ30eflhu7sFS0isHG1zRz0Y= +github.com/kcp-dev/multicluster-provider v0.2.1-0.20250901093233-9ef571c8bd47 h1:CPrrjZevZqUHrW/BBVvSjzSFrFnASm7srHznhTBhJ0Y= +github.com/kcp-dev/multicluster-provider v0.2.1-0.20250901093233-9ef571c8bd47/go.mod h1:RhSDOKAn/ROpSIFt140Tk2LsM7PXa3a3rov4qbqFERc= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= -github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= +github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= +github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -172,8 +166,8 @@ github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= github.com/onsi/ginkgo/v2 v2.23.3 h1:edHxnszytJ4lD9D5Jjc4tiDkPBZ3siDeJJkUZJJVkp0= github.com/onsi/ginkgo/v2 v2.23.3/go.mod h1:zXTP6xIp3U8aVuXN8ENK9IXRaTjFnpVB9mGmaSRvxnM= -github.com/onsi/gomega v1.36.3 h1:hID7cr8t3Wp26+cYnfcjR6HpJ00fdogN6dqZ1t6IylU= -github.com/onsi/gomega v1.36.3/go.mod h1:8D9+Txp43QWKhM24yyOBEdpkzN8FvJyAwecBgsU4KU0= +github.com/onsi/gomega v1.37.0 h1:CdEG8g0S133B4OswTDC/5XPSzE1OeP29QOioj2PID2Y= +github.com/onsi/gomega v1.37.0/go.mod h1:8D9+Txp43QWKhM24yyOBEdpkzN8FvJyAwecBgsU4KU0= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/openshift-eng/openshift-goimports v0.0.0-20230304234052-c70783e636f2 h1:Zq1BYSO2UmZuu/O1tpYIaC/7ir7ljFqdEY90TwqlseI= @@ -185,8 +179,8 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v1.21.1 h1:DOvXXTqVzvkIewV/CDPFdejpMCGeMcbGCQ8YOmu+Ibk= -github.com/prometheus/client_golang v1.21.1/go.mod h1:U9NM32ykUErtVBxdvD3zfi+EuFkkaBvMb09mIfe0Zgg= +github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q= +github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0= github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= github.com/prometheus/common v0.63.0 h1:YR/EIY1o3mEFP/kZCD7iDMnLPlGyuU2Gb3HIcXnA98k= @@ -221,6 +215,8 @@ github.com/stoewer/go-strcase v1.3.0/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8w github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= @@ -253,16 +249,16 @@ go.etcd.io/etcd/api/v3 v3.5.21 h1:A6O2/JDb3tvHhiIz3xf9nJ7REHvtEFJJ3veW3FbCnS8= go.etcd.io/etcd/api/v3 v3.5.21/go.mod h1:c3aH5wcvXv/9dqIw2Y810LDXJfhSYdHQ0vxmP3CCHVY= go.etcd.io/etcd/client/pkg/v3 v3.5.21 h1:lPBu71Y7osQmzlflM9OfeIV2JlmpBjqBNlLtcoBqUTc= go.etcd.io/etcd/client/pkg/v3 v3.5.21/go.mod h1:BgqT/IXPjK9NkeSDjbzwsHySX3yIle2+ndz28nVsjUs= -go.etcd.io/etcd/client/v2 v2.305.16 h1:kQrn9o5czVNaukf2A2At43cE9ZtWauOtf9vRZuiKXow= -go.etcd.io/etcd/client/v2 v2.305.16/go.mod h1:h9YxWCzcdvZENbfzBTFCnoNumr2ax3F19sKMqHFmXHE= +go.etcd.io/etcd/client/v2 v2.305.21 h1:eLiFfexc2mE+pTLz9WwnoEsX5JTTpLCYVivKkmVXIRA= +go.etcd.io/etcd/client/v2 v2.305.21/go.mod h1:OKkn4hlYNf43hpjEM3Ke3aRdUkhSl8xjKjSf8eCq2J8= go.etcd.io/etcd/client/v3 v3.5.21 h1:T6b1Ow6fNjOLOtM0xSoKNQt1ASPCLWrF9XMHcH9pEyY= go.etcd.io/etcd/client/v3 v3.5.21/go.mod h1:mFYy67IOqmbRf/kRUvsHixzo3iG+1OF2W2+jVIQRAnU= -go.etcd.io/etcd/pkg/v3 v3.5.16 h1:cnavs5WSPWeK4TYwPYfmcr3Joz9BH+TZ6qoUtz6/+mc= -go.etcd.io/etcd/pkg/v3 v3.5.16/go.mod h1:+lutCZHG5MBBFI/U4eYT5yL7sJfnexsoM20Y0t2uNuY= -go.etcd.io/etcd/raft/v3 v3.5.16 h1:zBXA3ZUpYs1AwiLGPafYAKKl/CORn/uaxYDwlNwndAk= -go.etcd.io/etcd/raft/v3 v3.5.16/go.mod h1:P4UP14AxofMJ/54boWilabqqWoW9eLodl6I5GdGzazI= -go.etcd.io/etcd/server/v3 v3.5.16 h1:d0/SAdJ3vVsZvF8IFVb1k8zqMZ+heGcNfft71ul9GWE= -go.etcd.io/etcd/server/v3 v3.5.16/go.mod h1:ynhyZZpdDp1Gq49jkUg5mfkDWZwXnn3eIqCqtJnrD/s= +go.etcd.io/etcd/pkg/v3 v3.5.21 h1:jUItxeKyrDuVuWhdh0HtjUANwyuzcb7/FAeUfABmQsk= +go.etcd.io/etcd/pkg/v3 v3.5.21/go.mod h1:wpZx8Egv1g4y+N7JAsqi2zoUiBIUWznLjqJbylDjWgU= +go.etcd.io/etcd/raft/v3 v3.5.21 h1:dOmE0mT55dIUsX77TKBLq+RgyumsQuYeiRQnW/ylugk= +go.etcd.io/etcd/raft/v3 v3.5.21/go.mod h1:fmcuY5R2SNkklU4+fKVBQi2biVp5vafMrWUEj4TJ4Cs= +go.etcd.io/etcd/server/v3 v3.5.21 h1:9w0/k12majtgarGmlMVuhwXRI2ob3/d1Ik3X5TKo0yU= +go.etcd.io/etcd/server/v3 v3.5.21/go.mod h1:G1mOzdwuzKT1VRL7SqRchli/qcFrtLBTAQ4lV20sXXo= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 h1:x7wzEgXfnzJcHDwStJT+mxOz4etr2EcexjqhBvmoakw= @@ -285,8 +281,8 @@ go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4= go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4= -go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= -go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/goleak v1.3.1-0.20241121203838-4ff5fa6529ee h1:uOMbcH1Dmxv45VkkpZQYoerZFeDncWpjbN7ATiQOO7c= +go.uber.org/goleak v1.3.1-0.20241121203838-4ff5fa6529ee/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= @@ -294,8 +290,8 @@ go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= -golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= +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-20250408133849-7e4ce0ab07d0 h1:R84qjqJb5nVJMxqWYb3np9L5ZsaDtB+a39EqjV0JSUM= golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:S9Xr4PYopiDyqSyp5NjCrhFrqg6A5zA2E/iPHPhqnS8= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= @@ -306,35 +302,35 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY= -golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E= +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.29.0 h1:WdYw2tdTK1S8olAzWHdgeqfy+Mtm9XNhv/xJsY65d98= golang.org/x/oauth2 v0.29.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610= -golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ= +golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= -golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o= -golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw= +golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= +golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg= +golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= -golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= +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.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.32.0 h1:Q7N1vhpkQv7ybVzLFtTjvQya2ewbwNDZzUgfXGqtMWU= -golang.org/x/tools v0.32.0/go.mod h1:ZxrU41P/wAbZD8EDa6dDCa6XfpkhJ7HFMjHJXfBDu8s= +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-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -369,40 +365,45 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= k8c.io/reconciler v0.5.0 h1:BHpelg1UfI/7oBFctqOq8sX6qzflXpl3SlvHe7e8wak= k8c.io/reconciler v0.5.0/go.mod h1:pT1+SVcVXJQeBJhpJBXQ5XW64QnKKeYTnVlQf0dGE0k= -k8s.io/apimachinery v0.32.3 h1:JmDuDarhDmA/Li7j3aPrwhpNBA94Nvk5zLeOge9HH1U= -k8s.io/apimachinery v0.32.3/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE= -k8s.io/client-go v0.32.3 h1:RKPVltzopkSgHS7aS98QdscAgtgah/+zmpAogooIqVU= -k8s.io/client-go v0.32.3/go.mod h1:3v0+3k4IcT9bXTc4V2rt+d2ZPPG700Xy6Oi0Gdl2PaY= -k8s.io/code-generator v0.32.3 h1:31p2TVzC9+hVdSkAFruAk3JY+iSfzrJ83Qij1yZutyw= -k8s.io/code-generator v0.32.3/go.mod h1:+mbiYID5NLsBuqxjQTygKM/DAdKpAjvBzrJd64NU1G8= -k8s.io/component-base v0.32.3 h1:98WJvvMs3QZ2LYHBzvltFSeJjEx7t5+8s71P7M74u8k= -k8s.io/component-base v0.32.3/go.mod h1:LWi9cR+yPAv7cu2X9rZanTiFKB2kHA+JjmhkKjCZRpI= +k8s.io/api v0.33.3 h1:SRd5t//hhkI1buzxb288fy2xvjubstenEKL9K51KBI8= +k8s.io/api v0.33.3/go.mod h1:01Y/iLUjNBM3TAvypct7DIj0M0NIZc+PzAHCIo0CYGE= +k8s.io/apimachinery v0.33.3 h1:4ZSrmNa0c/ZpZJhAgRdcsFcZOw1PQU1bALVQ0B3I5LA= +k8s.io/apimachinery v0.33.3/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM= +k8s.io/client-go v0.33.3 h1:M5AfDnKfYmVJif92ngN532gFqakcGi6RvaOF16efrpA= +k8s.io/client-go v0.33.3/go.mod h1:luqKBQggEf3shbxHY4uVENAxrDISLOarxpTKMiUuujg= +k8s.io/code-generator v0.33.3 h1:6+34LhYkIuQ/yn/E3qlpVqjQaP8smzCu4NE1A8b0LWs= +k8s.io/code-generator v0.33.3/go.mod h1:6Y02+HQJYgNphv9z3wJB5w+sjYDIEBQW7sh62PkufvA= +k8s.io/component-base v0.33.3 h1:mlAuyJqyPlKZM7FyaoM/LcunZaaY353RXiOd2+B5tGA= +k8s.io/component-base v0.33.3/go.mod h1:ktBVsBzkI3imDuxYXmVxZ2zxJnYTZ4HAsVj9iF09qp4= k8s.io/component-helpers v0.32.3 h1:9veHpOGTPLluqU4hAu5IPOwkOIZiGAJUhHndfVc5FT4= k8s.io/component-helpers v0.32.3/go.mod h1:utTBXk8lhkJewBKNuNf32Xl3KT/0VV19DmiXU/SV4Ao= k8s.io/controller-manager v0.32.3 h1:jBxZnQ24k6IMeWLyxWZmpa3QVS7ww+osAIzaUY/jqyc= k8s.io/controller-manager v0.32.3/go.mod h1:out1L3DZjE/p7JG0MoMMIaQGWIkt3c+pKaswqSHgKsI= -k8s.io/gengo/v2 v2.0.0-20240911193312-2b36238f13e9 h1:si3PfKm8dDYxgfbeA6orqrtLkvvIeH8UqffFJDl0bz4= -k8s.io/gengo/v2 v2.0.0-20240911193312-2b36238f13e9/go.mod h1:EJykeLsmFC60UQbYJezXkEsG2FLrt0GPNkU5iK5GWxU= +k8s.io/gengo/v2 v2.0.0-20250513215321-e3bc6f1e78b4 h1:iicENHE63xPBlGQeany8LqrH40Wh/48QhMRI/mGVsqA= +k8s.io/gengo/v2 v2.0.0-20250513215321-e3bc6f1e78b4/go.mod h1:EJykeLsmFC60UQbYJezXkEsG2FLrt0GPNkU5iK5GWxU= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/kube-openapi v0.0.0-20241212222426-2c72e554b1e7 h1:hcha5B1kVACrLujCKLbr8XWMxCxzQx42DY8QKYJrDLg= -k8s.io/kube-openapi v0.0.0-20241212222426-2c72e554b1e7/go.mod h1:GewRfANuJ70iYzvn+i4lezLDAFzvjxZYK1gn1lWcfas= -k8s.io/kubernetes v1.32.3 h1:2A58BlNME8NwsMawmnM6InYo3Jf35Nw5G79q46kXwoA= -k8s.io/kubernetes v1.32.3/go.mod h1:GvhiBeolvSRzBpFlgM0z/Bbu3Oxs9w3P6XfEgYaMi8k= -k8s.io/utils v0.0.0-20241210054802-24370beab758 h1:sdbE21q2nlQtFh65saZY+rRM6x6aJJI8IUa1AmH/qa0= -k8s.io/utils v0.0.0-20241210054802-24370beab758/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.1 h1:uOuSLOMBWkJH0TWa9X6l+mj5nZdm6Ay6Bli8HL8rNfk= -sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.1/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw= -sigs.k8s.io/controller-runtime v0.20.4 h1:X3c+Odnxz+iPTRobG4tp092+CvBU9UK0t/bRf+n0DGU= -sigs.k8s.io/controller-runtime v0.20.4/go.mod h1:xg2XB0K5ShQzAgsoujxuKN4LNXR2LfwwHsPj7Iaw+XY= -sigs.k8s.io/controller-tools v0.16.5 h1:5k9FNRqziBPwqr17AMEPPV/En39ZBplLAdOwwQHruP4= -sigs.k8s.io/controller-tools v0.16.5/go.mod h1:8vztuRVzs8IuuJqKqbXCSlXcw+lkAv/M2sTpg55qjMY= +k8s.io/kms v0.32.3 h1:HhHw5+pRCzEJp3oFFJ1q5W2N6gAI7YkUg4ay4Z0dgwM= +k8s.io/kms v0.32.3/go.mod h1:Bk2evz/Yvk0oVrvm4MvZbgq8BD34Ksxs2SRHn4/UiOM= +k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff h1:/usPimJzUKKu+m+TE36gUyGcf03XZEP0ZIKgKj35LS4= +k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff/go.mod h1:5jIi+8yX4RIb8wk3XwBo5Pq2ccx4FP10ohkbSKCZoK8= +k8s.io/kubernetes v1.33.3 h1:dBx5Z2ZhR8kNzAwCoCz4j1niUbUrNUDVxeSj4/Ienu0= +k8s.io/kubernetes v1.33.3/go.mod h1:nrt8sldmckKz2fCZhgRX3SKfS2e+CzXATPv6ITNkU00= +k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 h1:hwvWFiBzdWw1FhfY1FooPn3kzWuJ8tmbZBHi4zVsl1Y= +k8s.io/utils v0.0.0-20250604170112-4c0f3b243397/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 h1:jpcvIRr3GLoUoEKRkHKSmGjxb6lWwrBlJsXc+eUYQHM= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw= +sigs.k8s.io/controller-runtime v0.21.0 h1:CYfjpEuicjUecRk+KAeyYh+ouUBn4llGyDYytIGcJS8= +sigs.k8s.io/controller-runtime v0.21.0/go.mod h1:OSg14+F65eWqIu4DceX7k/+QRAbTTvxeQSNSOQpukWM= +sigs.k8s.io/controller-tools v0.18.0 h1:rGxGZCZTV2wJreeRgqVoWab/mfcumTMmSwKzoM9xrsE= +sigs.k8s.io/controller-tools v0.18.0/go.mod h1:gLKoiGBriyNh+x1rWtUQnakUYEujErjXs9pf+x/8n1U= sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE= sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= -sigs.k8s.io/multicluster-runtime v0.20.4-alpha.7 h1:AFlM/TFQaESxtCRX6scodEKensLhcbfGwXfjJIvoaT8= -sigs.k8s.io/multicluster-runtime v0.20.4-alpha.7/go.mod h1:2N2/c3p08bYC9eDaRs0dllTxgAm5xiLDSkmGZpWKyw4= -sigs.k8s.io/randfill v0.0.0-20250304075658-069ef1bbf016 h1:kXv6kKdoEtedwuqMmkqhbkgvYKeycVbC8+iPCP9j5kQ= +sigs.k8s.io/multicluster-runtime v0.21.0-alpha.9 h1:baonM4f081WWct3U7O4EfqrxcUGtmCrFDbsT1FQ8xlo= +sigs.k8s.io/multicluster-runtime v0.21.0-alpha.9/go.mod h1:CpBzLMLQKdm+UCchd2FiGPiDdCxM5dgCCPKuaQ6Fsv0= sigs.k8s.io/randfill v0.0.0-20250304075658-069ef1bbf016/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= +sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= +sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= sigs.k8s.io/structured-merge-diff/v4 v4.6.0 h1:IUA9nvMmnKWcj5jl84xn+T5MnlZKThmUW1TdblaLVAc= sigs.k8s.io/structured-merge-diff/v4 v4.6.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps= sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= diff --git a/hack/ci/run-e2e-tests.sh b/hack/ci/run-e2e-tests.sh index b185304..07ade96 100755 --- a/hack/ci/run-e2e-tests.sh +++ b/hack/ci/run-e2e-tests.sh @@ -73,7 +73,7 @@ if ! retry_linear 3 20 kubectl --kubeconfig "$KCP_KUBECONFIG" get workspaces; th exit 1 fi -# makes it easier to reference thesefiles from various _test.go files. +# makes it easier to reference these files from various _test.go files. export ROOT_DIRECTORY="$(realpath .)" export KCP_KUBECONFIG="$(realpath "$KCP_KUBECONFIG")" export AGENT_BINARY="$(realpath _build/api-syncagent)" diff --git a/hack/run-e2e-tests.sh b/hack/run-e2e-tests.sh new file mode 100755 index 0000000..1f98d01 --- /dev/null +++ b/hack/run-e2e-tests.sh @@ -0,0 +1,85 @@ +#!/usr/bin/env bash + +# Copyright 2025 The KCP Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set -euo pipefail + +cd "$(dirname $0)/.." + +source hack/lib.sh + +# get kube envtest binaries +echodate "Setting up Kube binaries…" +export KUBEBUILDER_ASSETS="$(_tools/setup-envtest use 1.31.0 --bin-dir _tools -p path)" +KUBEBUILDER_ASSETS="$(realpath "$KUBEBUILDER_ASSETS")" + +export ARTIFACTS=.e2e + +rm -rf "$ARTIFACTS" +mkdir -p "$ARTIFACTS" + +KCP_ROOT_DIRECTORY="$ARTIFACTS/kcp" +KCP_LOGFILE="$ARTIFACTS/kcp.log" +KCP_TOKENFILE=hack/ci/testdata/e2e-kcp.tokens +KCP_PID=0 + +echodate "Starting kcp…" +rm -rf "$KCP_ROOT_DIRECTORY" "$KCP_LOGFILE" +_tools/kcp start \ + -v4 \ + --token-auth-file "$KCP_TOKENFILE" \ + --root-directory "$KCP_ROOT_DIRECTORY" 1>"$KCP_LOGFILE" 2>&1 & +KCP_PID=$! + +stop_kcp() { + echodate "Stopping kcp (set \$KEEP_KCP=true to not do this)…" + kill -TERM $KCP_PID + wait $KCP_PID +} + +if [[ -v KEEP_KCP ]] && $KEEP_KCP; then + echodate "\$KEEP_KCP is set, will not stop kcp once the script is finished." +else + append_trap stop_kcp EXIT +fi + +# make the token available to the Go tests +export KCP_AGENT_TOKEN="$(grep e2e "$KCP_TOKENFILE" | cut -f1 -d,)" + +# Wait for kcp to be ready; this env name is also hardcoded in the Go tests. +export KCP_KUBECONFIG="$KCP_ROOT_DIRECTORY/admin.kubeconfig" + +# the tenancy API becomes available pretty late during startup, so it's a good readiness check +if ! retry_linear 3 20 kubectl --kubeconfig "$KCP_KUBECONFIG" get workspaces; then + echodate "kcp never became ready." + exit 1 +fi + +# makes it easier to reference these files from various _test.go files. +export ROOT_DIRECTORY="$(realpath .)" +export KCP_KUBECONFIG="$(realpath "$KCP_KUBECONFIG")" +export AGENT_BINARY="$(realpath _build/api-syncagent)" + +# The tests require ARTIFACTS to be absolute. +ARTIFACTS="$(realpath "$ARTIFACTS")" + +# time to run the tests +echodate "Running e2e tests…" + +WHAT="${WHAT:-./test/e2e/...}" +TEST_ARGS="${TEST_ARGS:--timeout 30m -v}" +E2E_PARALLELISM=${E2E_PARALLELISM:-2} + +(set -x; go test -tags e2e -parallel $E2E_PARALLELISM $TEST_ARGS "$WHAT") diff --git a/internal/controller/apiexport/controller.go b/internal/controller/apiexport/controller.go index c6c9ed2..d51ef23 100644 --- a/internal/controller/apiexport/controller.go +++ b/internal/controller/apiexport/controller.go @@ -75,7 +75,7 @@ func Add( kcpClient: kcpCluster.GetClient(), lcName: lcName, log: log.Named(ControllerName), - recorder: mgr.GetEventRecorderFor(ControllerName), + recorder: kcpCluster.GetEventRecorderFor(ControllerName), apiExportName: apiExportName, agentName: agentName, prFilter: prFilter, @@ -103,6 +103,7 @@ func Add( // Watch for changes to PublishedResources on the local service cluster Watches(&syncagentv1alpha1.PublishedResource{}, controllerutil.EnqueueConst[ctrlruntimeclient.Object]("dummy"), builder.WithPredicates(predicateutil.ByLabels(prFilter), hasARS)). Build(reconciler) + return err } @@ -159,6 +160,9 @@ func (r *Reconciler) reconcile(ctx context.Context) error { claimedResources.Insert("namespaces") } + // We always want to create events. + claimedResources.Insert("events") + if arsList.Len() == 0 { r.log.Debug("No ready PublishedResources available.") return nil @@ -166,7 +170,7 @@ func (r *Reconciler) reconcile(ctx context.Context) error { // reconcile an APIExport in kcp factories := []reconciling.NamedAPIExportReconcilerFactory{ - r.createAPIExportReconciler(arsList, claimedResources, r.agentName, r.apiExportName), + r.createAPIExportReconciler(arsList, claimedResources, r.agentName, r.apiExportName, r.recorder), } if err := reconciling.ReconcileAPIExports(ctx, factories, "", r.kcpClient); err != nil { diff --git a/internal/controller/apiexport/reconciler.go b/internal/controller/apiexport/reconciler.go index bcd3ab5..cc83272 100644 --- a/internal/controller/apiexport/reconciler.go +++ b/internal/controller/apiexport/reconciler.go @@ -25,13 +25,22 @@ import ( kcpdevv1alpha1 "github.com/kcp-dev/kcp/sdk/apis/apis/v1alpha1" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/client-go/tools/record" ) // createAPIExportReconciler creates the reconciler for the APIExport. // WARNING: The APIExport in this is NOT created by the Sync Agent, it's created // by a controller in kcp. Make sure you don't create a reconciling conflict! -func (r *Reconciler) createAPIExportReconciler(availableResourceSchemas sets.Set[string], claimedResourceKinds sets.Set[string], agentName string, apiExportName string) reconciling.NamedAPIExportReconcilerFactory { +func (r *Reconciler) createAPIExportReconciler( + availableResourceSchemas sets.Set[string], + claimedResourceKinds sets.Set[string], + agentName string, + apiExportName string, + recorder record.EventRecorder, +) reconciling.NamedAPIExportReconcilerFactory { return func() (string, reconciling.APIExportReconciler) { return apiExportName, func(existing *kcpdevv1alpha1.APIExport) (*kcpdevv1alpha1.APIExport, error) { if existing.Annotations == nil { @@ -40,7 +49,10 @@ func (r *Reconciler) createAPIExportReconciler(availableResourceSchemas sets.Set existing.Annotations[syncagentv1alpha1.AgentNameAnnotation] = agentName // combine existing schemas with new ones - existing.Spec.LatestResourceSchemas = mergeResourceSchemas(existing.Spec.LatestResourceSchemas, availableResourceSchemas) + newSchemas := mergeResourceSchemas(existing.Spec.LatestResourceSchemas, availableResourceSchemas) + createSchemaEvents(existing, existing.Spec.LatestResourceSchemas, newSchemas, recorder) + + existing.Spec.LatestResourceSchemas = newSchemas // To allow admins to configure additional permission claims, sometimes // useful for debugging, we do not override the permission claims, but @@ -67,6 +79,10 @@ func (r *Reconciler) createAPIExportReconciler(availableResourceSchemas sets.Set }) } + if missingClaims.Len() > 0 { + recorder.Eventf(existing, corev1.EventTypeNormal, "AddingPermissionClaims", "Added new permission claim(s) for all %s.", strings.Join(sets.List(missingClaims), ", ")) + } + // prevent reconcile loops by ensuring a stable order slices.SortFunc(existing.Spec.PermissionClaims, func(a, b kcpdevv1alpha1.PermissionClaim) int { if a.Group != b.Group { @@ -113,6 +129,19 @@ func mergeResourceSchemas(existing []string, configured sets.Set[string]) []stri return result } +func createSchemaEvents(obj runtime.Object, oldSchemas, newSchemas []string, recorder record.EventRecorder) { + oldSet := sets.New(oldSchemas...) + newSet := sets.New(newSchemas...) + + if change := sets.List(newSet.Difference(oldSet)); len(change) > 0 { + recorder.Eventf(obj, corev1.EventTypeNormal, "AddingResourceSchemas", "Added new resource schema(s) %s.", strings.Join(change, ", ")) + } + + if change := sets.List(oldSet.Difference(newSet)); len(change) > 0 { + recorder.Eventf(obj, corev1.EventTypeWarning, "RemovingResourceSchemas", "Removed resource schema(s) %s.", strings.Join(change, ", ")) + } +} + func parseResourceGroup(schema string) string { // .. parts := strings.SplitN(schema, ".", 2) diff --git a/internal/controller/sync/controller.go b/internal/controller/sync/controller.go index 53744b1..e0e2bce 100644 --- a/internal/controller/sync/controller.go +++ b/internal/controller/sync/controller.go @@ -165,7 +165,7 @@ func Create( } func (r *Reconciler) Reconcile(ctx context.Context, request mcreconcile.Request) (reconcile.Result, error) { - log := r.log.With("request", request, "cluster", request.ClusterName) + log := r.log.With("cluster", request.ClusterName, "request", request.NamespacedName) log.Debug("Processing") cl, err := r.remoteManager.GetCluster(ctx, request.ClusterName) @@ -205,28 +205,34 @@ func (r *Reconciler) Reconcile(ctx context.Context, request mcreconcile.Request) return reconcile.Result{}, nil } - cInfo := sync.NewClusterInfo(logicalcluster.Name(request.ClusterName)) + recorder := cl.GetEventRecorderFor(ControllerName) - // if desired, fetch the cluster path as well (some downstream service providers might make use of it, + ctx = sync.WithClusterName(ctx, logicalcluster.Name(request.ClusterName)) + ctx = sync.WithEventRecorder(ctx, recorder) + + // if desired, fetch the workspace path as well (some downstream service providers might make use of it, // but since it requires an additional permission claim, it's optional) if r.pubRes.Spec.EnableWorkspacePaths { lc := &kcpdevcorev1alpha1.LogicalCluster{} if err := vwClient.Get(ctx, types.NamespacedName{Name: kcpdevcorev1alpha1.LogicalClusterName}, lc); err != nil { + recorder.Event(remoteObj, corev1.EventTypeWarning, "ReconcilingError", "Failed to retrieve workspace path, cannot process object.") return reconcile.Result{}, fmt.Errorf("failed to retrieve remote logicalcluster: %w", err) } path := lc.Annotations[kcpcore.LogicalClusterPathAnnotationKey] - cInfo = cInfo.WithWorkspacePath(logicalcluster.NewPath(path)) + ctx = sync.WithWorkspacePath(ctx, logicalcluster.NewPath(path)) } // sync main object syncer, err := sync.NewResourceSyncer(log, r.localClient, vwClient, r.pubRes, r.localCRD, mutation.NewMutator, r.stateNamespace, r.agentName) if err != nil { + recorder.Event(remoteObj, corev1.EventTypeWarning, "ReconcilingError", "Failed to process object: a provider-side issue has occurred.") return reconcile.Result{}, fmt.Errorf("failed to create syncer: %w", err) } - requeue, err := syncer.Process(ctx, cInfo, remoteObj) + requeue, err := syncer.Process(ctx, remoteObj) if err != nil { + recorder.Event(remoteObj, corev1.EventTypeWarning, "ReconcilingError", "Failed to process object: a provider-side issue has occurred.") return reconcile.Result{}, err } diff --git a/internal/controller/syncmanager/controller.go b/internal/controller/syncmanager/controller.go index aa5d3f1..cecfb84 100644 --- a/internal/controller/syncmanager/controller.go +++ b/internal/controller/syncmanager/controller.go @@ -295,8 +295,6 @@ type controllerShim struct { } func (s *controllerShim) Engage(ctx context.Context, clusterName string, cl cluster.Cluster) error { - s.reconciler.log.Infof("Engage(%q)\n", clusterName) - for _, worker := range s.reconciler.syncWorkers { if err := worker.controller.Engage(ctx, clusterName, cl); err != nil { return err diff --git a/internal/sync/context.go b/internal/sync/context.go index 8f10dcd..89cbee1 100644 --- a/internal/sync/context.go +++ b/internal/sync/context.go @@ -17,23 +17,58 @@ limitations under the License. package sync import ( + "context" + "github.com/kcp-dev/logicalcluster/v3" + + "k8s.io/client-go/tools/record" +) + +type contextKey int + +const ( + clusterContextKey contextKey = iota + workspaceContextKey + recorderContextKey ) -type clusterInfo struct { - clusterName logicalcluster.Name - workspacePath logicalcluster.Path +func WithClusterName(ctx context.Context, cluster logicalcluster.Name) context.Context { + return context.WithValue(ctx, clusterContextKey, cluster) } -func NewClusterInfo(clusterName logicalcluster.Name) clusterInfo { - return clusterInfo{ - clusterName: clusterName, +func clusterFromContext(ctx context.Context) logicalcluster.Name { + cluster, ok := ctx.Value(clusterContextKey).(logicalcluster.Name) + if !ok { + return "" } + + return cluster +} + +func WithWorkspacePath(ctx context.Context, path logicalcluster.Path) context.Context { + return context.WithValue(ctx, workspaceContextKey, path) } -func (c *clusterInfo) WithWorkspacePath(path logicalcluster.Path) clusterInfo { - return clusterInfo{ - clusterName: c.clusterName, - workspacePath: path, +func workspacePathFromContext(ctx context.Context) logicalcluster.Path { + path, ok := ctx.Value(workspaceContextKey).(logicalcluster.Path) + if !ok { + return logicalcluster.None } + + return path +} + +// WithEventRecorder adds a event recorder to the context. The recorder must be configured +// for the kcp side of the synchronization as the agent never issues events on the local cluster. +func WithEventRecorder(ctx context.Context, recorder record.EventRecorder) context.Context { + return context.WithValue(ctx, recorderContextKey, recorder) +} + +func recorderFromContext(ctx context.Context) record.EventRecorder { + cluster, ok := ctx.Value(recorderContextKey).(record.EventRecorder) + if !ok { + return nil + } + + return cluster } diff --git a/internal/sync/crd/dummy.example.com_namespacedthings.yaml b/internal/sync/crd/dummy.example.com_namespacedthings.yaml index 5a8455e..42a82d3 100644 --- a/internal/sync/crd/dummy.example.com_namespacedthings.yaml +++ b/internal/sync/crd/dummy.example.com_namespacedthings.yaml @@ -4,7 +4,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.16.5 + controller-gen.kubebuilder.io/version: v0.18.0 name: namespacedthings.dummy.example.com spec: group: dummy.example.com diff --git a/internal/sync/crd/dummy.example.com_things.yaml b/internal/sync/crd/dummy.example.com_things.yaml index d490de3..6ff56e9 100644 --- a/internal/sync/crd/dummy.example.com_things.yaml +++ b/internal/sync/crd/dummy.example.com_things.yaml @@ -4,7 +4,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.16.5 + controller-gen.kubebuilder.io/version: v0.18.0 name: things.dummy.example.com spec: group: dummy.example.com diff --git a/internal/sync/crd/dummy.example.com_thingwithstatuses.yaml b/internal/sync/crd/dummy.example.com_thingwithstatuses.yaml index c6d078e..06ae55a 100644 --- a/internal/sync/crd/dummy.example.com_thingwithstatuses.yaml +++ b/internal/sync/crd/dummy.example.com_thingwithstatuses.yaml @@ -4,7 +4,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.16.5 + controller-gen.kubebuilder.io/version: v0.18.0 name: thingwithstatuses.dummy.example.com spec: group: dummy.example.com diff --git a/internal/sync/crd/dummy.example.com_thingwithstatussubresources.yaml b/internal/sync/crd/dummy.example.com_thingwithstatussubresources.yaml index a486454..042da9f 100644 --- a/internal/sync/crd/dummy.example.com_thingwithstatussubresources.yaml +++ b/internal/sync/crd/dummy.example.com_thingwithstatussubresources.yaml @@ -4,7 +4,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.16.5 + controller-gen.kubebuilder.io/version: v0.18.0 name: thingwithstatussubresources.dummy.example.com spec: group: dummy.example.com diff --git a/internal/sync/object_syncer.go b/internal/sync/object_syncer.go index aa9c67e..eb4db73 100644 --- a/internal/sync/object_syncer.go +++ b/internal/sync/object_syncer.go @@ -31,6 +31,7 @@ import ( corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" ctrlruntimeclient "sigs.k8s.io/controller-runtime/pkg/client" ) @@ -57,8 +58,19 @@ type objectSyncer struct { mutator mutation.Mutator // stateStore is capable of remembering the state of a Kubernetes object stateStore ObjectStateStore + // eventObjSide is configuring whether the source or destination object will + // receive events. Since these objects might be created during the sync, + // they cannot be specified here directly. + eventObjSide syncSideType } +type syncSideType int + +const ( + syncSideSource syncSideType = iota + syncSideDestination +) + type syncSide struct { clusterName logicalcluster.Name workspacePath logicalcluster.Path @@ -66,6 +78,19 @@ type syncSide struct { object *unstructured.Unstructured } +func (s *objectSyncer) recordEvent(ctx context.Context, source, dest syncSide, eventtype, reason, msg string, args ...any) { + recorder := recorderFromContext(ctx) + + var obj runtime.Object + if s.eventObjSide == syncSideDestination { + obj = dest.object + } else { + obj = source.object + } + + recorder.Eventf(obj, eventtype, reason, msg, args...) +} + func (s *objectSyncer) Sync(ctx context.Context, log *zap.SugaredLogger, source, dest syncSide) (requeue bool, err error) { // handle deletion: if source object is in deletion, delete the destination object (the clone) if source.object.GetDeletionTimestamp() != nil { @@ -81,6 +106,7 @@ func (s *objectSyncer) Sync(ctx context.Context, log *zap.SugaredLogger, source, // the patch above would trigger a new reconciliation anyway if updated { + s.recordEvent(ctx, source, dest, corev1.EventTypeNormal, "ObjectAccepted", "Object has been seen by the service provider.") return true, nil } } @@ -254,6 +280,8 @@ func (s *objectSyncer) syncObjectSpec(ctx context.Context, log *zap.SugaredLogge } if requeue { + s.recordEvent(ctx, source, dest, corev1.EventTypeNormal, "ObjectSynced", "The current desired state of the object has been synchronized.") + // remember this object state for the next reconciliation (this will strip any syncer-related // metadata the 3-way diff may have added above) if err := s.stateStore.Put(ctx, sourceObjCopy, source.clusterName, s.subresources); err != nil { @@ -282,6 +310,8 @@ func (s *objectSyncer) syncObjectStatus(ctx context.Context, log *zap.SugaredLog if err := source.client.Status().Update(ctx, source.object); err != nil { return false, fmt.Errorf("failed to update source object status: %w", err) } + + s.recordEvent(ctx, source, dest, corev1.EventTypeNormal, "ObjectStatusSynced", "The current object status has been updated.") } // always return false; there is no need to requeue the source object when we changed its status @@ -330,6 +360,9 @@ func (s *objectSyncer) ensureDestinationObject(ctx context.Context, log *zap.Sug } } + dest.object = destObj + s.recordEvent(ctx, source, dest, corev1.EventTypeNormal, "ObjectPlaced", "Object has been placed.") + // remember the state of the object that we just created if err := s.stateStore.Put(ctx, source.object, source.clusterName, s.subresources); err != nil { return fmt.Errorf("failed to update sync state: %w", err) @@ -406,6 +439,7 @@ func (s *objectSyncer) handleDeletion(ctx context.Context, log *zap.SugaredLogge if dest.object != nil { if dest.object.GetDeletionTimestamp() == nil { log.Debugw("Deleting destination object…", "dest-object", newObjectKey(dest.object, dest.clusterName, logicalcluster.None)) + s.recordEvent(ctx, source, dest, corev1.EventTypeNormal, "ObjectCleanup", "Object deletion has been started and will progress in the background.") if err := dest.client.Delete(ctx, dest.object); err != nil { return false, fmt.Errorf("failed to delete destination object: %w", err) } @@ -422,6 +456,7 @@ func (s *objectSyncer) handleDeletion(ctx context.Context, log *zap.SugaredLogge // if we just removed the finalizer, we can requeue the source object if updated { + s.recordEvent(ctx, source, dest, corev1.EventTypeNormal, "ObjectDeleted", "Object deletion has been completed, finalizer has been removed.") return true, nil } diff --git a/internal/sync/syncer.go b/internal/sync/syncer.go index b989115..a741ed5 100644 --- a/internal/sync/syncer.go +++ b/internal/sync/syncer.go @@ -20,6 +20,7 @@ import ( "context" "fmt" + "github.com/kcp-dev/logicalcluster/v3" "go.uber.org/zap" "github.com/kcp-dev/api-syncagent/internal/mutation" @@ -136,11 +137,16 @@ func NewResourceSyncer( // Each of these steps can potentially end the current processing and return (true, nil). In this // case, the caller should re-fetch the remote object and call Process() again (most likely in the // next reconciliation). Only when (false, nil) is returned is the entire process finished. -func (s *ResourceSyncer) Process(ctx context.Context, info clusterInfo, remoteObj *unstructured.Unstructured) (requeue bool, err error) { - log := s.log.With("source-object", newObjectKey(remoteObj, info.clusterName, info.workspacePath)) +// The context must contain a cluster name and event recorder, optionally a workspace path. +func (s *ResourceSyncer) Process(ctx context.Context, remoteObj *unstructured.Unstructured) (requeue bool, err error) { + clusterName := clusterFromContext(ctx) + workspacePath := workspacePathFromContext(ctx) + objectKey := newObjectKey(remoteObj, clusterName, workspacePath) + + log := s.log.With("source-object", objectKey) // find the local equivalent object in the local service cluster - localObj, err := s.findLocalObject(ctx, info, remoteObj) + localObj, err := s.findLocalObject(ctx, objectKey) if err != nil { return false, fmt.Errorf("failed to find local equivalent: %w", err) } @@ -151,8 +157,8 @@ func (s *ResourceSyncer) Process(ctx context.Context, info clusterInfo, remoteOb // Prepare object sync sides. sourceSide := syncSide{ - clusterName: info.clusterName, - workspacePath: info.workspacePath, + clusterName: clusterName, + workspacePath: workspacePath, client: s.remoteClient, object: remoteObj, } @@ -172,7 +178,7 @@ func (s *ResourceSyncer) Process(ctx context.Context, info clusterInfo, remoteOb agentName: s.agentName, subresources: s.subresources, // use the projection and renaming rules configured in the PublishedResource - destCreator: s.newLocalObjectCreator(info), + destCreator: s.newLocalObjectCreator(clusterName, workspacePath), // for the main resource, status subresource handling is enabled (this // means _allowing_ status back-syncing, it still depends on whether the // status subresource even exists whether an update happens) @@ -188,6 +194,7 @@ func (s *ResourceSyncer) Process(ctx context.Context, info clusterInfo, remoteOb // (i.e. on the service cluster), so that the original and copy are linked // together and can be found. metadataOnDestination: true, + eventObjSide: syncSideSource, } requeue, err = syncer.Sync(ctx, log, sourceSide, destSide) @@ -210,8 +217,8 @@ func (s *ResourceSyncer) Process(ctx context.Context, info clusterInfo, remoteOb return s.processRelatedResources(ctx, log, stateStore, sourceSide, destSide) } -func (s *ResourceSyncer) findLocalObject(ctx context.Context, info clusterInfo, remoteObj *unstructured.Unstructured) (*unstructured.Unstructured, error) { - localSelector := labels.SelectorFromSet(newObjectKey(remoteObj, info.clusterName, info.workspacePath).Labels()) +func (s *ResourceSyncer) findLocalObject(ctx context.Context, objectKey objectKey) (*unstructured.Unstructured, error) { + localSelector := labels.SelectorFromSet(objectKey.Labels()) localObjects := &unstructured.UnstructuredList{} localObjects.SetAPIVersion(s.destDummy.GetAPIVersion()) @@ -234,7 +241,7 @@ func (s *ResourceSyncer) findLocalObject(ctx context.Context, info clusterInfo, } } -func (s *ResourceSyncer) newLocalObjectCreator(info clusterInfo) objectCreatorFunc { +func (s *ResourceSyncer) newLocalObjectCreator(clusterName logicalcluster.Name, workspacePath logicalcluster.Path) objectCreatorFunc { return func(remoteObj *unstructured.Unstructured) (*unstructured.Unstructured, error) { // map from the remote API into the actual, local API group destObj := remoteObj.DeepCopy() @@ -244,7 +251,7 @@ func (s *ResourceSyncer) newLocalObjectCreator(info clusterInfo) objectCreatorFu destScope := syncagentv1alpha1.ResourceScope(s.localCRD.Spec.Scope) // map namespace/name - mappedName, err := templating.GenerateLocalObjectName(s.pubRes, remoteObj, info.clusterName, info.workspacePath) + mappedName, err := templating.GenerateLocalObjectName(s.pubRes, remoteObj, clusterName, workspacePath) if err != nil { return nil, fmt.Errorf("failed to generate local object name: %w", err) } diff --git a/internal/sync/syncer_related.go b/internal/sync/syncer_related.go index e453051..49b8835 100644 --- a/internal/sync/syncer_related.go +++ b/internal/sync/syncer_related.go @@ -64,16 +64,19 @@ type relatedObjectAnnotation struct { func (s *ResourceSyncer) processRelatedResource(ctx context.Context, log *zap.SugaredLogger, stateStore ObjectStateStore, remote, local syncSide, relRes syncagentv1alpha1.RelatedResourceSpec) (requeue bool, err error) { // decide what direction to sync (local->remote vs. remote->local) var ( - origin syncSide - dest syncSide + origin syncSide + dest syncSide + eventObjSide syncSideType ) if relRes.Origin == syncagentv1alpha1.RelatedResourceOriginService { origin = local dest = remote + eventObjSide = syncSideDestination } else { origin = remote dest = local + eventObjSide = syncSideSource } // find the all objects on the origin side that match the given criteria @@ -143,6 +146,8 @@ func (s *ResourceSyncer) processRelatedResource(ctx context.Context, log *zap.Su mutator: s.relatedMutators[relRes.Identifier], // we never want to store sync-related metadata inside kcp metadataOnDestination: false, + // events are always created on the kcp side + eventObjSide: eventObjSide, } req, err := syncer.Sync(ctx, log, sourceSide, destSide) diff --git a/internal/sync/syncer_test.go b/internal/sync/syncer_test.go index 7410b0f..ff91756 100644 --- a/internal/sync/syncer_test.go +++ b/internal/sync/syncer_test.go @@ -37,6 +37,7 @@ import ( "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" yamlutil "k8s.io/apimachinery/pkg/util/yaml" + "k8s.io/client-go/tools/record" ctrlruntimeclient "sigs.k8s.io/controller-runtime/pkg/client" fakectrlruntimeclient "sigs.k8s.io/controller-runtime/pkg/client/fake" ) @@ -910,7 +911,8 @@ func TestSyncerProcessingSingleResourceWithoutStatus(t *testing.T) { } ctx := t.Context() - cInfo := NewClusterInfo(clusterName) + ctx = WithClusterName(ctx, clusterName) + ctx = WithEventRecorder(ctx, record.NewFakeRecorder(99)) // setup a custom state backend that we can prime var backend *kubernetesBackend @@ -940,7 +942,7 @@ func TestSyncerProcessingSingleResourceWithoutStatus(t *testing.T) { t.Fatalf("Detected potential infinite loop, stopping after %d requeues.", i) } - requeue, err = syncer.Process(ctx, cInfo, target) + requeue, err = syncer.Process(ctx, target) if err != nil { break } @@ -960,7 +962,7 @@ func TestSyncerProcessingSingleResourceWithoutStatus(t *testing.T) { } } } else { - requeue, err = syncer.Process(ctx, cInfo, testcase.remoteObject) + requeue, err = syncer.Process(ctx, testcase.remoteObject) } finalRemoteObject, getErr := getFinalObjectVersion(ctx, remoteClient, testcase.remoteObject, testcase.expectedRemoteObject) @@ -1217,7 +1219,8 @@ func TestSyncerProcessingSingleResourceWithStatus(t *testing.T) { } ctx := t.Context() - cInfo := NewClusterInfo(clusterName) + ctx = WithClusterName(ctx, clusterName) + ctx = WithEventRecorder(ctx, record.NewFakeRecorder(99)) // setup a custom state backend that we can prime var backend *kubernetesBackend @@ -1247,7 +1250,7 @@ func TestSyncerProcessingSingleResourceWithStatus(t *testing.T) { t.Fatalf("Detected potential infinite loop, stopping after %d requeues.", i) } - requeue, err = syncer.Process(ctx, cInfo, target) + requeue, err = syncer.Process(ctx, target) if err != nil { break } @@ -1267,7 +1270,7 @@ func TestSyncerProcessingSingleResourceWithStatus(t *testing.T) { } } } else { - requeue, err = syncer.Process(ctx, cInfo, testcase.remoteObject) + requeue, err = syncer.Process(ctx, testcase.remoteObject) } finalRemoteObject, getErr := getFinalObjectVersion(ctx, remoteClient, testcase.remoteObject, testcase.expectedRemoteObject) diff --git a/internal/sync/templating/naming.go b/internal/sync/templating/naming.go index 4877039..46024f6 100644 --- a/internal/sync/templating/naming.go +++ b/internal/sync/templating/naming.go @@ -40,11 +40,11 @@ type localObjectNamingContext struct { ClusterPath logicalcluster.Path } -func newLocalObjectNamingContext(object *unstructured.Unstructured, clusterName logicalcluster.Name, clusterPath logicalcluster.Path) localObjectNamingContext { +func newLocalObjectNamingContext(object *unstructured.Unstructured, clusterName logicalcluster.Name, workspacePath logicalcluster.Path) localObjectNamingContext { return localObjectNamingContext{ Object: object.Object, ClusterName: clusterName, - ClusterPath: clusterPath, + ClusterPath: workspacePath, } } @@ -53,7 +53,7 @@ var defaultNamingScheme = syncagentv1alpha1.ResourceNaming{ Name: "{{ .Object.metadata.namespace | sha3short }}-{{ .Object.metadata.name | sha3short }}", } -func GenerateLocalObjectName(pr *syncagentv1alpha1.PublishedResource, object *unstructured.Unstructured, clusterName logicalcluster.Name, clusterPath logicalcluster.Path) (types.NamespacedName, error) { +func GenerateLocalObjectName(pr *syncagentv1alpha1.PublishedResource, object *unstructured.Unstructured, clusterName logicalcluster.Name, workspacePath logicalcluster.Path) (types.NamespacedName, error) { naming := pr.Spec.Naming if naming == nil { naming = &syncagentv1alpha1.ResourceNaming{} @@ -65,7 +65,7 @@ func GenerateLocalObjectName(pr *syncagentv1alpha1.PublishedResource, object *un if pattern == "" { pattern = defaultNamingScheme.Namespace } - rendered, err := generateLocalObjectIdentifier(pattern, object, clusterName, clusterPath) + rendered, err := generateLocalObjectIdentifier(pattern, object, clusterName, workspacePath) if err != nil { return result, fmt.Errorf("invalid namespace naming: %w", err) } @@ -76,7 +76,7 @@ func GenerateLocalObjectName(pr *syncagentv1alpha1.PublishedResource, object *un if pattern == "" { pattern = defaultNamingScheme.Name } - rendered, err = generateLocalObjectIdentifier(pattern, object, clusterName, clusterPath) + rendered, err = generateLocalObjectIdentifier(pattern, object, clusterName, workspacePath) if err != nil { return result, fmt.Errorf("invalid name naming: %w", err) } @@ -86,10 +86,10 @@ func GenerateLocalObjectName(pr *syncagentv1alpha1.PublishedResource, object *un return result, nil } -func generateLocalObjectIdentifier(pattern string, object *unstructured.Unstructured, clusterName logicalcluster.Name, clusterPath logicalcluster.Path) (string, error) { +func generateLocalObjectIdentifier(pattern string, object *unstructured.Unstructured, clusterName logicalcluster.Name, workspacePath logicalcluster.Path) (string, error) { // modern Go template style if strings.Contains(pattern, "{{") { - return Render(pattern, newLocalObjectNamingContext(object, clusterName, clusterPath)) + return Render(pattern, newLocalObjectNamingContext(object, clusterName, workspacePath)) } // Legacy $variable style, does also not support clusterPath; diff --git a/sdk/clientset/versioned/fake/clientset_generated.go b/sdk/clientset/versioned/fake/clientset_generated.go index 5d0d44a..bc47987 100644 --- a/sdk/clientset/versioned/fake/clientset_generated.go +++ b/sdk/clientset/versioned/fake/clientset_generated.go @@ -19,6 +19,7 @@ limitations under the License. package fake import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/watch" "k8s.io/client-go/discovery" @@ -50,9 +51,13 @@ func NewSimpleClientset(objects ...runtime.Object) *Clientset { cs.discovery = &fakediscovery.FakeDiscovery{Fake: &cs.Fake} cs.AddReactor("*", "*", testing.ObjectReaction(o)) cs.AddWatchReactor("*", func(action testing.Action) (handled bool, ret watch.Interface, err error) { + var opts metav1.ListOptions + if watchActcion, ok := action.(testing.WatchActionImpl); ok { + opts = watchActcion.ListOptions + } gvr := action.GetResource() ns := action.GetNamespace() - watch, err := o.Watch(gvr, ns) + watch, err := o.Watch(gvr, ns, opts) if err != nil { return false, nil, err } diff --git a/sdk/clientset/versioned/typed/syncagent/v1alpha1/syncagent_client.go b/sdk/clientset/versioned/typed/syncagent/v1alpha1/syncagent_client.go index c016500..6608d23 100644 --- a/sdk/clientset/versioned/typed/syncagent/v1alpha1/syncagent_client.go +++ b/sdk/clientset/versioned/typed/syncagent/v1alpha1/syncagent_client.go @@ -46,9 +46,7 @@ func (c *SyncagentV1alpha1Client) PublishedResources() PublishedResourceInterfac // where httpClient was generated with rest.HTTPClientFor(c). func NewForConfig(c *rest.Config) (*SyncagentV1alpha1Client, error) { config := *c - if err := setConfigDefaults(&config); err != nil { - return nil, err - } + setConfigDefaults(&config) httpClient, err := rest.HTTPClientFor(&config) if err != nil { return nil, err @@ -60,9 +58,7 @@ func NewForConfig(c *rest.Config) (*SyncagentV1alpha1Client, error) { // Note the http client provided takes precedence over the configured transport values. func NewForConfigAndClient(c *rest.Config, h *http.Client) (*SyncagentV1alpha1Client, error) { config := *c - if err := setConfigDefaults(&config); err != nil { - return nil, err - } + setConfigDefaults(&config) client, err := rest.RESTClientForConfigAndClient(&config, h) if err != nil { return nil, err @@ -85,7 +81,7 @@ func New(c rest.Interface) *SyncagentV1alpha1Client { return &SyncagentV1alpha1Client{c} } -func setConfigDefaults(config *rest.Config) error { +func setConfigDefaults(config *rest.Config) { gv := syncagentv1alpha1.SchemeGroupVersion config.GroupVersion = &gv config.APIPath = "/apis" @@ -94,8 +90,6 @@ func setConfigDefaults(config *rest.Config) error { if config.UserAgent == "" { config.UserAgent = rest.DefaultKubernetesUserAgent() } - - return nil } // RESTClient returns a RESTClient that is used to communicate diff --git a/test/e2e/apiexport/apiexport_test.go b/test/e2e/apiexport/apiexport_test.go index cac539b..5631c83 100644 --- a/test/e2e/apiexport/apiexport_test.go +++ b/test/e2e/apiexport/apiexport_test.go @@ -114,8 +114,8 @@ func TestPermissionsClaims(t *testing.T) { t.Fatalf("Failed to wait for APIExport to be updated: %v", err) } - if claims := apiExport.Spec.PermissionClaims; len(claims) > 0 { - t.Fatalf("APIExport should have no permissions claims, but has %v", claims) + if claims := apiExport.Spec.PermissionClaims; len(claims) > 1 { + t.Fatalf("APIExport should have only events permissions claims, but has %v", claims) } // let's configure some related resources @@ -206,7 +206,8 @@ func TestPermissionsClaims(t *testing.T) { return false, err } - return len(apiExport.Spec.PermissionClaims) == 3, nil + // 4 = events (always) + configmaps + secrets + namespaces (because of configmaps and secrets) + return len(apiExport.Spec.PermissionClaims) == 4, nil }) if err != nil { t.Fatalf("Failed to wait for APIExport to be updated: %v", err) @@ -220,6 +221,13 @@ func TestPermissionsClaims(t *testing.T) { }, All: true, }, + { + GroupResource: kcpapisv1alpha1.GroupResource{ + Group: "", + Resource: "events", + }, + All: true, + }, { GroupResource: kcpapisv1alpha1.GroupResource{ Group: "", @@ -331,6 +339,13 @@ func TestExistingPermissionsClaimsAreKept(t *testing.T) { }, All: true, }, + { + GroupResource: kcpapisv1alpha1.GroupResource{ + Group: "", + Resource: "events", + }, + All: true, + }, { GroupResource: kcpapisv1alpha1.GroupResource{ Group: "", diff --git a/test/utils/fixtures.go b/test/utils/fixtures.go index 9f264b5..fa12d32 100644 --- a/test/utils/fixtures.go +++ b/test/utils/fixtures.go @@ -155,6 +155,11 @@ func CreateAPIExport(t *testing.T, ctx context.Context, client ctrlruntimeclient ResourceNames: []string{name}, Verbs: []string{"get", "list", "watch", "patch", "update"}, }, + { + APIGroups: []string{""}, + Resources: []string{"events"}, + Verbs: []string{"get", "create", "update", "patch"}, + }, { APIGroups: []string{"apis.kcp.io"}, Resources: []string{"apiresourceschemas"}, @@ -258,6 +263,16 @@ func BindToAPIExport(t *testing.T, ctx context.Context, client ctrlruntimeclient }, State: kcpapisv1alpha1.ClaimAccepted, }, + { + PermissionClaim: kcpapisv1alpha1.PermissionClaim{ + GroupResource: kcpapisv1alpha1.GroupResource{ + Group: "", + Resource: "events", + }, + All: true, + }, + State: kcpapisv1alpha1.ClaimAccepted, + }, // for related resources, the agent can also sync ConfigMaps and Secrets { PermissionClaim: kcpapisv1alpha1.PermissionClaim{