diff --git a/.gitignore b/.gitignore index 294685952b..bb310c7065 100644 --- a/.gitignore +++ b/.gitignore @@ -24,4 +24,6 @@ hack/tools/bin junit-report.xml -/artifacts \ No newline at end of file +/artifacts + +examples/kcp/.gitignore diff --git a/.prow.yaml b/.prow.yaml new file mode 100644 index 0000000000..1f9f775c1c --- /dev/null +++ b/.prow.yaml @@ -0,0 +1,34 @@ +presubmits: + - name: pull-controller-runtime-everything + always_run: true + decorate: true + clone_uri: "ssh://git@github.com/kcp-dev/controller-runtime.git" + labels: + preset-goproxy: "true" + spec: + containers: + - image: ghcr.io/kcp-dev/infra/build:1.22.2-1 + command: + - make + - test + + - name: pull-controller-runtime-example-e2e + decorate: true + # only run e2e tests if code changed. + run_if_changed: "(pkg|examples|go.mod|go.sum|Makefile|.prow.yaml)" + clone_uri: "https://github.com/kcp-dev/controller-runtime" + labels: + preset-goproxy: "true" + spec: + containers: + - image: ghcr.io/kcp-dev/infra/build:1.22.2-1 + env: + - name: KUBECONFIG + value: /home/prow/go/src/github.com/kcp-dev/controller-runtime/examples/kcp/.test/kcp.kubeconfig + command: + - make + - test-kcp-e2e + resources: + requests: + memory: 6Gi + cpu: 4 diff --git a/DOWNSTREAM_OWNERS b/DOWNSTREAM_OWNERS new file mode 100644 index 0000000000..3f5abef6e3 --- /dev/null +++ b/DOWNSTREAM_OWNERS @@ -0,0 +1,5 @@ +approvers: + - sttts + - xrstf + - mjudeikis + - embik diff --git a/DOWNSTREAM_OWNERS_ALIASES b/DOWNSTREAM_OWNERS_ALIASES new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Makefile b/Makefile index 438613b3eb..02a4a75461 100644 --- a/Makefile +++ b/Makefile @@ -142,4 +142,6 @@ APIDIFF_OLD_COMMIT ?= $(shell git rev-parse origin/main) verify-apidiff: $(GO_APIDIFF) ## Check for API differences $(GO_APIDIFF) $(APIDIFF_OLD_COMMIT) --print-compatible - +.PHONY: test-kcp-e2e +test-kcp-e2e: + cd examples/kcp && make kcp-server kcp-controller test diff --git a/examples/builtins/main.go b/examples/builtins/main.go index 5a6e313f7b..cd2ca0b4d8 100644 --- a/examples/builtins/main.go +++ b/examples/builtins/main.go @@ -42,6 +42,7 @@ func main() { // Setup a Manager entryLog.Info("setting up manager") + mgr, err := manager.New(config.GetConfigOrDie(), manager.Options{}) if err != nil { entryLog.Error(err, "unable to set up overall controller manager") diff --git a/examples/kcp/Makefile b/examples/kcp/Makefile new file mode 100644 index 0000000000..b0d167a9df --- /dev/null +++ b/examples/kcp/Makefile @@ -0,0 +1,91 @@ +SHELL := /bin/bash + +.PHONY: help +help: ## Display this help. + @awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST) + +GO_INSTALL = ./hack/go-install.sh + +LOCALBIN ?= $(shell pwd)/bin +TOOLS_DIR=hack/tools +TOOLS_BIN_DIR := $(abspath $(TOOLS_DIR)/bin) +ARTIFACT_DIR ?= .test + +KCP ?= $(LOCALBIN)/kcp +KUBECTL_KCP ?= $(LOCALBIN)/kubectl-kcp + +KCP_VERSION ?= 0.23.0 +CONTROLLER_GEN := $(TOOLS_BIN_DIR)/controller-gen +export CONTROLLER_GEN # so hack scripts can use it + +KCP_APIGEN_VER := v0.21.0 +KCP_APIGEN_BIN := apigen +KCP_APIGEN_GEN := $(TOOLS_BIN_DIR)/$(KCP_APIGEN_BIN)-$(KCP_APIGEN_VER) +export KCP_APIGEN_GEN # so hack scripts can use it + +OS ?= $(shell go env GOOS ) +ARCH ?= $(shell go env GOARCH ) + +$(KCP): ## Download kcp locally if necessary. + mkdir -p $(LOCALBIN) + curl -L -s -o - https://github.com/kcp-dev/kcp/releases/download/v$(KCP_VERSION)/kcp_$(KCP_VERSION)_$(OS)_$(ARCH).tar.gz | tar --directory $(LOCALBIN)/../ -xvzf - bin/kcp + touch $(KCP) # we download an "old" file, so make will re-download to refresh it unless we make it newer than the owning dir + +$(KUBECTL_KCP): ## Download kcp kubectl plugins locally if necessary. + curl -L -s -o - https://github.com/kcp-dev/kcp/releases/download/v$(KCP_VERSION)/kubectl-kcp-plugin_$(KCP_VERSION)_$(OS)_$(ARCH).tar.gz | tar --directory $(LOCALBIN)/../ -xvzf - bin + touch $(KUBECTL_KCP) # we download an "old" file, so make will re-download to refresh it unless we make it newer than the owning dir + +$(KCP_APIGEN_GEN): + GOBIN=$(TOOLS_BIN_DIR) $(GO_INSTALL) github.com/kcp-dev/kcp/sdk/cmd/apigen $(KCP_APIGEN_BIN) $(KCP_APIGEN_VER) + +$(CONTROLLER_GEN): $(TOOLS_DIR)/go.mod # Build controller-gen from tools folder. + cd $(TOOLS_DIR) && go build -tags=tools -o bin/controller-gen sigs.k8s.io/controller-tools/cmd/controller-gen + +build: $(KCP) $(KUBECTL_KCP) build-controller + +ifeq (,$(shell go env GOBIN)) +GOBIN=$(shell go env GOPATH)/bin +else +GOBIN=$(shell go env GOBIN) +endif + +build-controller: ## Build the controller binary. + go build -o $(LOCALBIN)/kcp-controller ./main.go + +.PHONY: kcp-server +kcp-server: $(KCP) $(ARTIFACT_DIR)/kcp ## Run the kcp server. + @if [[ ! -s $(ARTIFACT_DIR)/kcp.log ]]; then ( $(KCP) start -v 5 --root-directory $(ARTIFACT_DIR)/kcp --kubeconfig-path $(ARTIFACT_DIR)/kcp.kubeconfig --audit-log-maxsize 1024 --audit-log-mode=batch --audit-log-batch-max-wait=1s --audit-log-batch-max-size=1000 --audit-log-batch-buffer-size=10000 --audit-log-batch-throttle-burst=15 --audit-log-batch-throttle-enable=true --audit-log-batch-throttle-qps=10 --audit-policy-file ./test/e2e/audit-policy.yaml --audit-log-path $(ARTIFACT_DIR)/audit.log >$(ARTIFACT_DIR)/kcp.log 2>&1 & ); fi + @echo "Waiting for kcp server to generate kubeconfig..." + @while true; do if [[ ! -s $(ARTIFACT_DIR)/kcp.kubeconfig ]]; then sleep 0.2; else break; fi; done + @echo "Waiting for kcp server to be ready..." + @while true; do if ! kubectl --kubeconfig $(ARTIFACT_DIR)/kcp.kubeconfig get --raw /readyz >$(ARTIFACT_DIR)/kcp.probe.log 2>&1; then sleep 0.2; else break; fi; done + @echo "kcp server is ready and running in the background. To stop run 'make test-cleanup'" + +.PHONY: kcp-bootstrap +kcp-bootstrap: ## Bootstrap the kcp server. + @go run ./config/main.go + +.PHONY: kcp-controller +kcp-controller: build kcp-bootstrap ## Run the kcp-controller. + @echo "Starting kcp-controller in the background. To stop run 'make test-cleanup'" + @if [[ ! -s $(ARTIFACT_DIR)/controller.log ]]; then ( ./bin/kcp-controller >$(ARTIFACT_DIR)/controller.log 2>&1 & ); fi + +.PHONY: test-e2e-cleanup +test-cleanup: ## Clean up processes and directories from an end-to-end test run. + rm -rf $(ARTIFACT_DIR) || true + pkill -sigterm kcp || true + pkill -sigterm kubectl || true + pkill -sigterm kcp-controller || true + +$(ARTIFACT_DIR)/kcp: ## Create a directory for the kcp server data. + mkdir -p $(ARTIFACT_DIR)/kcp + +generate: build $(CONTROLLER_GEN) $(KCP_APIGEN_GEN) # Generate code + ./hack/update-codegen-crds.sh + +run-local: build-controller kcp-bootstrap + ./bin/kcp-controller + +.PHONY: test # Run tests +test: + go test ./... --workspace=root --kubeconfig=$(CURDIR)/$(ARTIFACT_DIR)/kcp.kubeconfig diff --git a/examples/kcp/README.md b/examples/kcp/README.md new file mode 100644 index 0000000000..760f141055 --- /dev/null +++ b/examples/kcp/README.md @@ -0,0 +1,85 @@ +# controller-runtime-example +An example project that is multi-cluster aware and works with [kcp](https://github.com/kcp-dev/kcp) + +## Description + +In this example, we intentionally not using advanced kubebuilder patterns to keep the example simple and easy to understand. +In the future, we will add more advanced examples. Example covers 3 parts: +1. KCP bootstrapping - creating APIExport & consumer workspaces + 1. Creating WorkspaceType for particular exports +2. Running controller with APIExport aware configuration and reconciling multiple consumer workspaces + + +This example contains an example project that works with APIExports and multiple kcp workspaces. It demonstrates +two reconcilers: + +1. ConfigMap + 1. Get a ConfigMap for the key from the queue, from the correct logical cluster + 2. If the ConfigMap has labels["name"], set labels["response"] = "hello-$name" and save the changes + 3. List all ConfigMaps in the logical cluster and log each one's namespace and name + 4. If the ConfigMap from step 1 has data["namespace"] set, create a namespace whose name is the data value. + 5. If the ConfigMap from step 1 has data["secretData"] set, create a secret in the same namespace as the ConfigMap, + with an owner reference to the ConfigMap, and data["dataFromCM"] set to the data value. + +2. Widget + 1. Show how to list all Widget instances across all logical clusters + 2. Get a Widget for the key from the queue, from the correct logical cluster + 3. List all Widgets in the same logical cluster + 4. Count the number of Widgets (list length) + 5. Make sure `.status.total` matches the current count (via a `patch`) + +## Getting Started + +### Running on kcp + +1. Run KCP with the following command: + +```sh +make kcp-server +``` + +From this point onwards you can inspect kcp configuration using kubeconfig: + +```sh +export KUBECONFIG=.test/kcp.kubeconfig +``` + +1. Bootstrap the KCP server with the following command: + +```sh +export KUBECONFIG=./.test/kcp.kubeconfig +make kcp-bootstrap +``` + +1. Run controller: + +```sh +export KUBECONFIG=./.test/kcp.kubeconfig +make run-local +``` + +1. In separate shell you can run tests to exercise the controller: + +```sh +export KUBECONFIG=./.test/kcp.kubeconfig +make test +``` + +### Uninstall resources +To delete the resources from kcp: + +```sh +make test-clean +``` + + + +### How it works +This project aims to follow the Kubernetes [Operator pattern](https://kubernetes.io/docs/concepts/extend-kubernetes/operator/) + +It uses [Controllers](https://kubernetes.io/docs/concepts/architecture/controller/) +which provides a reconcile function responsible for synchronizing resources until the desired state is reached. + + + + diff --git a/examples/kcp/apis/v1alpha1/groupversion_info.go b/examples/kcp/apis/v1alpha1/groupversion_info.go new file mode 100644 index 0000000000..5e15b53ba7 --- /dev/null +++ b/examples/kcp/apis/v1alpha1/groupversion_info.go @@ -0,0 +1,36 @@ +/* +Copyright 2024. + +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. +*/ + +// Package v1alpha1 contains API Schema definitions for the data v1alpha1 API group +// +kubebuilder:object:generate=true +// +groupName=data.my.domain +package v1alpha1 + +import ( + "k8s.io/apimachinery/pkg/runtime/schema" + "sigs.k8s.io/controller-runtime/pkg/scheme" +) + +var ( + // GroupVersion is group version used to register these objects + GroupVersion = schema.GroupVersion{Group: "data.my.domain", Version: "v1alpha1"} + + // SchemeBuilder is used to add go types to the GroupVersionKind scheme + SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} + + // AddToScheme adds the types in this group-version to the given scheme. + AddToScheme = SchemeBuilder.AddToScheme +) diff --git a/examples/kcp/apis/v1alpha1/widget_types.go b/examples/kcp/apis/v1alpha1/widget_types.go new file mode 100644 index 0000000000..038a1cee41 --- /dev/null +++ b/examples/kcp/apis/v1alpha1/widget_types.go @@ -0,0 +1,56 @@ +/* +Copyright 2024. + +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. +*/ + +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// WidgetSpec defines the desired state of Widget +type WidgetSpec struct { + Foo string `json:"foo,omitempty"` +} + +// WidgetStatus defines the observed state of Widget +type WidgetStatus struct { + Total int `json:"total,omitempty"` +} + +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status + +// Widget is the Schema for the widgets API +type Widget struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec WidgetSpec `json:"spec,omitempty"` + Status WidgetStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true + +// WidgetList contains a list of Widget +type WidgetList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []Widget `json:"items"` +} + +func init() { + SchemeBuilder.Register(&Widget{}, &WidgetList{}) +} diff --git a/examples/kcp/apis/v1alpha1/zz_generated.deepcopy.go b/examples/kcp/apis/v1alpha1/zz_generated.deepcopy.go new file mode 100644 index 0000000000..e4892949c4 --- /dev/null +++ b/examples/kcp/apis/v1alpha1/zz_generated.deepcopy.go @@ -0,0 +1,98 @@ +//go:build !ignore_autogenerated + +// Code generated by controller-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + runtime "k8s.io/apimachinery/pkg/runtime" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Widget) DeepCopyInto(out *Widget) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + out.Spec = in.Spec + out.Status = in.Status +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Widget. +func (in *Widget) DeepCopy() *Widget { + if in == nil { + return nil + } + out := new(Widget) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Widget) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *WidgetList) DeepCopyInto(out *WidgetList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Widget, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WidgetList. +func (in *WidgetList) DeepCopy() *WidgetList { + if in == nil { + return nil + } + out := new(WidgetList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *WidgetList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *WidgetSpec) DeepCopyInto(out *WidgetSpec) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WidgetSpec. +func (in *WidgetSpec) DeepCopy() *WidgetSpec { + if in == nil { + return nil + } + out := new(WidgetSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *WidgetStatus) DeepCopyInto(out *WidgetStatus) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WidgetStatus. +func (in *WidgetStatus) DeepCopy() *WidgetStatus { + if in == nil { + return nil + } + out := new(WidgetStatus) + in.DeepCopyInto(out) + return out +} diff --git a/examples/kcp/config/consumers/bootstrap.go b/examples/kcp/config/consumers/bootstrap.go new file mode 100644 index 0000000000..ed68b8bebc --- /dev/null +++ b/examples/kcp/config/consumers/bootstrap.go @@ -0,0 +1,43 @@ +/* +Copyright 2024 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. +*/ + +package consumers + +import ( + "context" + "embed" + + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/log" + + confighelpers "github.com/kcp-dev/controller-runtime/examples/kcp/config/helpers" +) + +//go:embed *.yaml +var fs embed.FS + +// Bootstrap creates resources in this package by continuously retrying the list. +// This is blocking, i.e. it only returns (with error) when the context is closed or with nil when +// the bootstrapping is successfully completed. +func Bootstrap( + ctx context.Context, + client client.Client, +) error { + log := log.FromContext(ctx) + + log.Info("Bootstrapping consumers workspaces") + return confighelpers.Bootstrap(ctx, client, fs) +} diff --git a/examples/kcp/config/consumers/consumer1-workspace.yaml b/examples/kcp/config/consumers/consumer1-workspace.yaml new file mode 100644 index 0000000000..3161d713a9 --- /dev/null +++ b/examples/kcp/config/consumers/consumer1-workspace.yaml @@ -0,0 +1,14 @@ +apiVersion: tenancy.kcp.io/v1alpha1 +kind: Workspace +metadata: + name: consumer1 + annotations: + bootstrap.kcp.io/create-only: "true" +spec: + type: + name: widgets + path: root + location: + selector: + matchLabels: + name: root diff --git a/examples/kcp/config/consumers/consumer2-workspace.yaml b/examples/kcp/config/consumers/consumer2-workspace.yaml new file mode 100644 index 0000000000..e75e3121a8 --- /dev/null +++ b/examples/kcp/config/consumers/consumer2-workspace.yaml @@ -0,0 +1,14 @@ +apiVersion: tenancy.kcp.io/v1alpha1 +kind: Workspace +metadata: + name: consumer2 + annotations: + bootstrap.kcp.io/create-only: "true" +spec: + type: + name: widgets + path: root + location: + selector: + matchLabels: + name: root diff --git a/examples/kcp/config/crds/data.my.domain_widgets.yaml b/examples/kcp/config/crds/data.my.domain_widgets.yaml new file mode 100644 index 0000000000..02c5feaa38 --- /dev/null +++ b/examples/kcp/config/crds/data.my.domain_widgets.yaml @@ -0,0 +1,55 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.14.0 + name: widgets.data.my.domain +spec: + group: data.my.domain + names: + kind: Widget + listKind: WidgetList + plural: widgets + singular: widget + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: Widget is the Schema for the widgets API + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: WidgetSpec defines the desired state of Widget + properties: + foo: + type: string + type: object + status: + description: WidgetStatus defines the observed state of Widget + properties: + total: + type: integer + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/examples/kcp/config/helpers/bootstrap.go b/examples/kcp/config/helpers/bootstrap.go new file mode 100644 index 0000000000..93dd23058c --- /dev/null +++ b/examples/kcp/config/helpers/bootstrap.go @@ -0,0 +1,143 @@ +package helpers + +import ( + "bufio" + "bytes" + "context" + "embed" + "errors" + "fmt" + "io" + "text/template" + "time" + + extensionsapiserver "k8s.io/apiextensions-apiserver/pkg/apiserver" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/types" + apimachineryerrors "k8s.io/apimachinery/pkg/util/errors" + "k8s.io/apimachinery/pkg/util/wait" + kubeyaml "k8s.io/apimachinery/pkg/util/yaml" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/log" +) + +// Bootstrap creates resources in a package's fs by +// continuously retrying the list. This is blocking, i.e. it only returns (with error) +// when the context is closed or with nil when the bootstrapping is successfully completed. +func Bootstrap(ctx context.Context, client client.Client, fs embed.FS) error { + // bootstrap non-crd resources + return wait.PollUntilContextCancel(ctx, time.Second, true, func(ctx context.Context) (bool, error) { + if err := CreateResourcesFromFS(ctx, client, fs); err != nil { + log.FromContext(ctx).WithValues("err", err).Info("failed to bootstrap resources, retrying") + return false, nil + } + return true, nil + }) +} + +// CreateResourcesFromFS creates all resources from a filesystem. +func CreateResourcesFromFS(ctx context.Context, client client.Client, fs embed.FS) error { + files, err := fs.ReadDir(".") + if err != nil { + return err + } + + var errs []error + for _, f := range files { + if f.IsDir() { + continue + } + if err := CreateResourceFromFS(ctx, client, f.Name(), fs); err != nil { + errs = append(errs, err) + } + } + return apimachineryerrors.NewAggregate(errs) +} + +// CreateResourceFromFS creates given resource file. +func CreateResourceFromFS(ctx context.Context, client client.Client, filename string, fs embed.FS) error { + raw, err := fs.ReadFile(filename) + if err != nil { + return fmt.Errorf("could not read %s: %w", filename, err) + } + + if len(raw) == 0 { + return nil // ignore empty files + } + + d := kubeyaml.NewYAMLReader(bufio.NewReader(bytes.NewReader(raw))) + var errs []error + for i := 1; ; i++ { + doc, err := d.Read() + if errors.Is(err, io.EOF) { + break + } else if err != nil { + return err + } + if len(bytes.TrimSpace(doc)) == 0 { + continue + } + + if err := createResourceFromFS(ctx, client, doc); err != nil { + errs = append(errs, fmt.Errorf("failed to create resource %s doc %d: %w", filename, i, err)) + } + } + return apimachineryerrors.NewAggregate(errs) +} + +func createResourceFromFS(ctx context.Context, client client.Client, raw []byte) error { + log := log.FromContext(ctx) + + type Input struct { + Batteries map[string]bool + } + input := Input{ + Batteries: map[string]bool{}, + } + tmpl, err := template.New("manifest").Parse(string(raw)) + if err != nil { + return fmt.Errorf("failed to parse manifest: %w", err) + } + var buf bytes.Buffer + if err := tmpl.Execute(&buf, input); err != nil { + return fmt.Errorf("failed to execute manifest: %w", err) + } + + obj, gvk, err := extensionsapiserver.Codecs.UniversalDeserializer().Decode(buf.Bytes(), nil, &unstructured.Unstructured{}) + if err != nil { + return fmt.Errorf("could not decode raw: %w", err) + } + u, ok := obj.(*unstructured.Unstructured) + if !ok { + return fmt.Errorf("decoded into incorrect type, got %T, wanted %T", obj, &unstructured.Unstructured{}) + } + + key := types.NamespacedName{ + Namespace: u.GetNamespace(), + Name: u.GetName(), + } + err = client.Create(ctx, u) + if err != nil { + if apierrors.IsAlreadyExists(err) { + err = client.Get(ctx, key, u) + if err != nil { + return err + } + + u.SetResourceVersion(u.GetResourceVersion()) + err = client.Update(ctx, u) + if err != nil { + return fmt.Errorf("could not update %s %s: %w", gvk.Kind, key.String(), err) + } else { + log.WithValues("resource", u.GetName(), "kind", gvk.Kind).Info("updated object") + return nil + } + } + return err + } + + log.WithValues("resource", u.GetName(), "kind", gvk.Kind).Info("created object") + + return nil +} diff --git a/examples/kcp/config/main.go b/examples/kcp/config/main.go new file mode 100644 index 0000000000..f41a49313b --- /dev/null +++ b/examples/kcp/config/main.go @@ -0,0 +1,113 @@ +/* +Copyright 2024 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. +*/ + +package main + +import ( + "os" + + kcpclienthelper "github.com/kcp-dev/apimachinery/v2/pkg/client" + apisv1alpha1 "github.com/kcp-dev/kcp/sdk/apis/apis/v1alpha1" + "github.com/kcp-dev/kcp/sdk/apis/core" + corev1alpha1 "github.com/kcp-dev/kcp/sdk/apis/core/v1alpha1" + tenancyv1alpha1 "github.com/kcp-dev/kcp/sdk/apis/tenancy/v1alpha1" + "github.com/kcp-dev/logicalcluster/v3" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + clientgoscheme "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/rest" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/config" + "sigs.k8s.io/controller-runtime/pkg/log/zap" + + "github.com/kcp-dev/controller-runtime/examples/kcp/config/consumers" + "github.com/kcp-dev/controller-runtime/examples/kcp/config/widgets" + widgetresources "github.com/kcp-dev/controller-runtime/examples/kcp/config/widgets/resources" + ctrllog "sigs.k8s.io/controller-runtime/pkg/log" +) + +// config is bootstrap set of assets for the controller-runtime examples. +// It includes the following assets: +// - crds/* for the Widget type - autogenerated from the Widget type definition +// - widgets/resources/* - a set of Widget resources for KCP to manage. Automatically generated by kcp apigen +// see Makefile & hack/update-codegen-crds.sh for more details + +// It is intended to be running with higher privileges than the examples themselves +// to ensure system (kcp) is bootstrapped. In real world scenarios, this would be +// done by the platform operator to enable service providers to deploy their +// controllers. + +func init() { + utilruntime.Must(tenancyv1alpha1.AddToScheme(clientgoscheme.Scheme)) + utilruntime.Must(clientgoscheme.AddToScheme(clientgoscheme.Scheme)) + utilruntime.Must(corev1alpha1.AddToScheme(clientgoscheme.Scheme)) + utilruntime.Must(apisv1alpha1.AddToScheme(clientgoscheme.Scheme)) +} + +var ( + // clusterName is the workspace to host common APIs. + clusterName = logicalcluster.NewPath("root:widgets") +) + +func main() { + opts := zap.Options{ + Development: true, + } + ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts))) + + ctx := ctrl.SetupSignalHandler() + log := ctrllog.FromContext(ctx) + + restConfig, err := config.GetConfigWithContext("base") + if err != nil { + log.Error(err, "unable to get config") + os.Exit(1) + } + + restCopy := rest.CopyConfig(restConfig) + restRoot := rest.AddUserAgent(kcpclienthelper.SetCluster(restCopy, core.RootCluster.Path()), "bootstrap-root") + rootClient, err := client.New(restRoot, client.Options{}) + if err != nil { + log.Error(err, "unable to create client") + os.Exit(1) + } + + restCopy = rest.CopyConfig(restConfig) + restWidgets := rest.AddUserAgent(kcpclienthelper.SetCluster(restCopy, clusterName), "bootstrap-widgets") + widgetsClient, err := client.New(restWidgets, client.Options{}) + if err != nil { + log.Error(err, "unable to create client") + os.Exit(1) + } + + err = widgets.Bootstrap(ctx, rootClient) + if err != nil { + log.Error(err, "failed to bootstrap widgets") + os.Exit(1) + } + + err = widgetresources.Bootstrap(ctx, widgetsClient) + if err != nil { + log.Error(err, "failed to bootstrap resources") + os.Exit(1) + } + + err = consumers.Bootstrap(ctx, rootClient) + if err != nil { + log.Error(err, "failed to bootstrap consumers") + os.Exit(1) + } +} diff --git a/examples/kcp/config/widgets/bootstrap.go b/examples/kcp/config/widgets/bootstrap.go new file mode 100644 index 0000000000..f68414eac5 --- /dev/null +++ b/examples/kcp/config/widgets/bootstrap.go @@ -0,0 +1,43 @@ +/* +Copyright 2024 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. +*/ + +package widgets + +import ( + "context" + "embed" + + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/log" + + confighelpers "github.com/kcp-dev/controller-runtime/examples/kcp/config/helpers" +) + +//go:embed *.yaml +var fs embed.FS + +// Bootstrap creates resources in this package by continuously retrying the list. +// This is blocking, i.e. it only returns (with error) when the context is closed or with nil when +// the bootstrapping is successfully completed. +func Bootstrap( + ctx context.Context, + client client.Client, +) error { + log := log.FromContext(ctx) + + log.Info("Bootstrapping widgets workspace") + return confighelpers.Bootstrap(ctx, client, fs) +} diff --git a/examples/kcp/config/widgets/resources/apiexport-data.my.domain.yaml b/examples/kcp/config/widgets/resources/apiexport-data.my.domain.yaml new file mode 100644 index 0000000000..2b7bd64182 --- /dev/null +++ b/examples/kcp/config/widgets/resources/apiexport-data.my.domain.yaml @@ -0,0 +1,16 @@ +apiVersion: apis.kcp.io/v1alpha1 +kind: APIExport +metadata: + creationTimestamp: null + name: data.my.domain +spec: + latestResourceSchemas: + - v240406-90e42b7b.widgets.data.my.domain + permissionClaims: + - all: true + resource: configmaps + - all: true + resource: secrets + - all: true + resource: namespaces +status: {} diff --git a/examples/kcp/config/widgets/resources/apiresourceschema-widgets.data.my.domain.yaml b/examples/kcp/config/widgets/resources/apiresourceschema-widgets.data.my.domain.yaml new file mode 100644 index 0000000000..a3d5bfbbaa --- /dev/null +++ b/examples/kcp/config/widgets/resources/apiresourceschema-widgets.data.my.domain.yaml @@ -0,0 +1,52 @@ +apiVersion: apis.kcp.io/v1alpha1 +kind: APIResourceSchema +metadata: + creationTimestamp: null + name: v240406-90e42b7b.widgets.data.my.domain +spec: + group: data.my.domain + names: + kind: Widget + listKind: WidgetList + plural: widgets + singular: widget + scope: Namespaced + versions: + - name: v1alpha1 + schema: + description: Widget is the Schema for the widgets API + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: WidgetSpec defines the desired state of Widget + properties: + foo: + type: string + type: object + status: + description: WidgetStatus defines the observed state of Widget + properties: + total: + type: integer + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/examples/kcp/config/widgets/resources/bootstrap.go b/examples/kcp/config/widgets/resources/bootstrap.go new file mode 100644 index 0000000000..50daebb94b --- /dev/null +++ b/examples/kcp/config/widgets/resources/bootstrap.go @@ -0,0 +1,47 @@ +/* +Copyright 2024 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. +*/ + +package resources + +import ( + "context" + "embed" + + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/log" + + confighelpers "github.com/kcp-dev/controller-runtime/examples/kcp/config/helpers" +) + +//go:embed *.yaml +var fs embed.FS + +// Bootstrap creates resources in this package by continuously retrying the list. +// This is blocking, i.e. it only returns (with error) when the context is closed or with nil when +// the bootstrapping is successfully completed. +func Bootstrap( + ctx context.Context, + client client.Client, +) error { + log := log.FromContext(ctx) + + log.Info("Bootstrapping widgets resources") + if err := confighelpers.Bootstrap(ctx, client, fs); err != nil { + return err + } + + return nil +} diff --git a/examples/kcp/config/widgets/widgets-workspace.yaml b/examples/kcp/config/widgets/widgets-workspace.yaml new file mode 100644 index 0000000000..7b3c863c4e --- /dev/null +++ b/examples/kcp/config/widgets/widgets-workspace.yaml @@ -0,0 +1,14 @@ +apiVersion: tenancy.kcp.io/v1alpha1 +kind: Workspace +metadata: + name: widgets + annotations: + bootstrap.kcp.io/create-only: "true" +spec: + type: + name: universal + path: root + location: + selector: + matchLabels: + name: root diff --git a/examples/kcp/config/widgets/widgets-workspacetype.yaml b/examples/kcp/config/widgets/widgets-workspacetype.yaml new file mode 100644 index 0000000000..c9290aa36e --- /dev/null +++ b/examples/kcp/config/widgets/widgets-workspacetype.yaml @@ -0,0 +1,12 @@ +apiVersion: tenancy.kcp.io/v1alpha1 +kind: WorkspaceType +metadata: + name: widgets +spec: + extend: + with: + - name: universal + path: root + defaultAPIBindings: + - path: root:widgets + export: data.my.domain diff --git a/examples/kcp/controllers/configmap/reconciler.go b/examples/kcp/controllers/configmap/reconciler.go new file mode 100644 index 0000000000..b08c3279c0 --- /dev/null +++ b/examples/kcp/controllers/configmap/reconciler.go @@ -0,0 +1,145 @@ +/* +Copyright 2024 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. +*/ + +package configmap + +import ( + "context" + "fmt" + + corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/kcp" + + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + "sigs.k8s.io/controller-runtime/pkg/kontext" + "sigs.k8s.io/controller-runtime/pkg/log" +) + +type Reconciler struct { + Client client.Client +} + +func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + log := log.FromContext(ctx).WithValues("cluster", req.ClusterName) + + // Test get + var cm corev1.ConfigMap + if err := r.Client.Get(ctx, req.NamespacedName, &cm); err != nil { + log.Error(err, "unable to get configmap") + return ctrl.Result{}, nil + } + + log.Info("Get: retrieved configMap") + if cm.Labels["name"] != "" { + response := fmt.Sprintf("hello-%s", cm.Labels["name"]) + + if cm.Labels["response"] != response { + cm.Labels["response"] = response + + // Test Update + if err := r.Client.Update(ctx, &cm); err != nil { + return ctrl.Result{}, err + } + + log.Info("Update: updated configMap") + return ctrl.Result{}, nil + } + } + + // Test list + var cms corev1.ConfigMapList + if err := r.Client.List(ctx, &cms); err != nil { + log.Error(err, "unable to list configmaps") + return ctrl.Result{}, nil + } + log.Info("List: got", "itemCount", len(cms.Items)) + found := false + for _, other := range cms.Items { + cluster, ok := kontext.ClusterFrom(ctx) + if !ok { + log.Info("List: got", "clusterName", cluster.String(), "namespace", other.Namespace, "name", other.Name) + } else if other.Name == cm.Name && other.Namespace == cm.Namespace { + if found { + return ctrl.Result{}, fmt.Errorf("there should be listed only one configmap with the given name '%s' for the given namespace '%s' when the clusterName is not available", cm.Name, cm.Namespace) + } + found = true + log.Info("Found in listed configmaps", "namespace", cm.Namespace, "name", cm.Name) + } + } + + // If the configmap has a namespace field, create the corresponding namespace + nsName, exists := cm.Data["namespace"] + if exists { + var namespace corev1.Namespace + if err := r.Client.Get(ctx, types.NamespacedName{Name: nsName}, &namespace); err != nil { + if !apierrors.IsNotFound(err) { + log.Error(err, "unable to get namespace") + return ctrl.Result{}, err + } + + // Need to create ns + namespace.SetName(nsName) + if err = r.Client.Create(ctx, &namespace); err != nil { + log.Error(err, "unable to create namespace") + return ctrl.Result{}, err + } + log.Info("Create: created ", "namespace", nsName) + return ctrl.Result{Requeue: true}, nil + } + log.Info("Exists", "namespace", nsName) + } + + // If the configmap has a secretData field, create a secret in the same namespace + // If the secret already exists but is out of sync, it will be non-destructively patched + secretData, exists := cm.Data["secretData"] + if exists { + var secret corev1.Secret + secret.SetName(cm.GetName()) + secret.SetNamespace(cm.GetNamespace()) + secret.SetOwnerReferences([]metav1.OwnerReference{{ + Name: cm.GetName(), + UID: cm.GetUID(), + APIVersion: "v1", + Kind: "ConfigMap", + Controller: func() *bool { x := true; return &x }(), + }}) + secret.Data = map[string][]byte{"dataFromCM": []byte(secretData)} + + operationResult, err := controllerutil.CreateOrPatch(ctx, r.Client, &secret, func() error { + secret.Data["dataFromCM"] = []byte(secretData) + return nil + }) + if err != nil { + log.Error(err, "unable to create or patch secret") + return ctrl.Result{}, err + } + log.Info(string(operationResult), "secret", secret.GetName()) + } + + return ctrl.Result{}, nil +} + +func (r *Reconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&corev1.ConfigMap{}). + Owns(&corev1.Secret{}). + Complete(kcp.WithClusterInContext(r)) +} diff --git a/examples/kcp/controllers/widget/reconciler.go b/examples/kcp/controllers/widget/reconciler.go new file mode 100644 index 0000000000..0e33f2f889 --- /dev/null +++ b/examples/kcp/controllers/widget/reconciler.go @@ -0,0 +1,83 @@ +/* +Copyright 2024 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. +*/ + +package widget + +import ( + "context" + + "k8s.io/apimachinery/pkg/api/errors" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/kcp" + "sigs.k8s.io/controller-runtime/pkg/log" + + datav1alpha1 "github.com/kcp-dev/controller-runtime/examples/kcp/apis/v1alpha1" +) + +// Reconciler reconciles a Widget object +type Reconciler struct { + Client client.Client +} + +// Reconcile TODO +func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + log := log.FromContext(ctx) + + // Include the clusterName from req.ObjectKey in the logger, similar to the namespace and name keys that are already + // there. + log = log.WithValues("clusterName", req.ClusterName) + + // You probably wouldn't need to do this, but if you wanted to list all instances across all logical clusters: + var allWidgets datav1alpha1.WidgetList + if err := r.Client.List(ctx, &allWidgets); err != nil { + return ctrl.Result{}, err + } + + log.Info("Listed all widgets across all workspaces", "count", len(allWidgets.Items)) + + log.Info("Getting widget") + var w datav1alpha1.Widget + if err := r.Client.Get(ctx, req.NamespacedName, &w); err != nil { + if errors.IsNotFound(err) { + // Normal - was deleted + return ctrl.Result{}, nil + } + return ctrl.Result{}, err + } + + log.Info("Listing all widgets in the current logical cluster") + var list datav1alpha1.WidgetList + if err := r.Client.List(ctx, &list); err != nil { + return ctrl.Result{}, err + } + + log.Info("Patching widget status to store total widget count in the current logical cluster") + orig := w.DeepCopy() + w.Status.Total = len(list.Items) + if err := r.Client.Status().Patch(ctx, &w, client.MergeFromWithOptions(orig, client.MergeFromWithOptimisticLock{})); err != nil { + return ctrl.Result{}, err + } + + return ctrl.Result{}, nil +} + +// SetupWithManager sets up the controller with the Manager. +func (r *Reconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&datav1alpha1.Widget{}). + Complete(kcp.WithClusterInContext(r)) +} diff --git a/examples/kcp/go.mod b/examples/kcp/go.mod new file mode 100644 index 0000000000..db3f29bbaf --- /dev/null +++ b/examples/kcp/go.mod @@ -0,0 +1,117 @@ +module github.com/kcp-dev/controller-runtime/examples/kcp + +go 1.22.0 + +// IMPORTANT: This is only an example replace directive. This is so examples can be run with the latest version of controller-runtime. +// In your own projects, you should not use replace directives like this. Instead, you should replace, but with kcp-dev/controller-runtime instead of ../../ +replace sigs.k8s.io/controller-runtime => ../../ + +require ( + github.com/google/go-cmp v0.6.0 + github.com/kcp-dev/apimachinery/v2 v2.0.0-alpha.0.0.20230926071920-57d168bcbe34 + github.com/kcp-dev/kcp/sdk v0.24.0 + github.com/kcp-dev/logicalcluster/v3 v3.0.5 + k8s.io/api v0.30.1 + k8s.io/apiextensions-apiserver v0.30.1 + k8s.io/apimachinery v0.30.1 + k8s.io/client-go v0.30.1 + k8s.io/klog/v2 v2.120.1 + sigs.k8s.io/controller-runtime v0.12.3 +) + +require ( + github.com/NYTimes/gziphandler v1.1.1 // indirect + github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230305170008-8188dc5388df // 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.2.1 // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/coreos/go-semver v0.3.1 // indirect + github.com/coreos/go-systemd/v22 v22.5.0 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/emicklei/go-restful/v3 v3.11.0 // indirect + github.com/evanphx/json-patch v5.6.0+incompatible // indirect + github.com/evanphx/json-patch/v5 v5.9.0 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/fsnotify/fsnotify v1.7.0 // indirect + github.com/go-logr/logr v1.4.1 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-logr/zapr v1.3.0 // indirect + github.com/go-openapi/jsonpointer v0.19.6 // indirect + github.com/go-openapi/jsonreference v0.20.2 // indirect + github.com/go-openapi/swag v0.22.3 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/golang/protobuf v1.5.4 // indirect + github.com/google/cel-go v0.17.8 // indirect + github.com/google/gnostic-models v0.6.8 // indirect + github.com/google/gofuzz v1.2.0 // indirect + github.com/google/uuid v1.3.1 // indirect + github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 // indirect + github.com/imdario/mergo v0.3.13 // indirect + 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/mailru/easyjson v0.7.7 // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/onsi/gomega v1.32.0 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/prometheus/client_golang v1.19.0 // indirect + github.com/prometheus/client_model v0.6.0 // indirect + github.com/prometheus/common v0.48.0 // indirect + github.com/prometheus/procfs v0.12.0 // indirect + github.com/spf13/cobra v1.7.0 // indirect + github.com/spf13/pflag v1.0.6-0.20210604193023-d5e0c0615ace // indirect + github.com/stoewer/go-strcase v1.3.0 // indirect + go.etcd.io/etcd/api/v3 v3.5.10 // indirect + go.etcd.io/etcd/client/pkg/v3 v3.5.10 // indirect + go.etcd.io/etcd/client/v3 v3.5.10 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.0 // indirect + go.opentelemetry.io/otel v1.20.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.20.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.20.0 // indirect + go.opentelemetry.io/otel/metric v1.20.0 // indirect + go.opentelemetry.io/otel/sdk v1.20.0 // indirect + go.opentelemetry.io/otel/trace v1.20.0 // indirect + go.opentelemetry.io/proto/otlp v1.0.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + go.uber.org/zap v1.26.0 // indirect + golang.org/x/crypto v0.21.0 // indirect + golang.org/x/exp v0.0.0-20220827204233-334a2380cb91 // indirect + golang.org/x/net v0.23.0 // indirect + golang.org/x/oauth2 v0.16.0 // indirect + golang.org/x/sync v0.6.0 // indirect + golang.org/x/sys v0.18.0 // indirect + golang.org/x/term v0.18.0 // indirect + golang.org/x/text v0.14.0 // indirect + golang.org/x/time v0.3.0 // indirect + gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect + google.golang.org/appengine v1.6.7 // indirect + google.golang.org/genproto v0.0.0-20230822172742-b8732ec3820d // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20230822172742-b8732ec3820d // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d // indirect + google.golang.org/grpc v1.59.0 // indirect + google.golang.org/protobuf v1.33.0 // indirect + gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + k8s.io/apiserver v0.30.1 // indirect + k8s.io/component-base v0.30.1 // indirect + k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect + k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect + sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.29.0 // indirect + sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect + sigs.k8s.io/yaml v1.4.0 // indirect +) + +replace ( + github.com/prometheus/client_golang => github.com/prometheus/client_golang v1.16.0 + github.com/prometheus/common => github.com/prometheus/common v0.44.0 +) diff --git a/examples/scratch-env/go.sum b/examples/kcp/go.sum similarity index 53% rename from examples/scratch-env/go.sum rename to examples/kcp/go.sum index 12c31c9069..a85a6c0f70 100644 --- a/examples/scratch-env/go.sum +++ b/examples/kcp/go.sum @@ -1,21 +1,52 @@ +cloud.google.com/go v0.110.7 h1:rJyC7nWRg2jWGZ4wSJ5nY65GTdYJkg0cd/uXb+ACI6o= +cloud.google.com/go/compute v1.23.0 h1:tP41Zoavr8ptEqaW6j+LQOnyBBhO7OkOMAGrgLopTwY= +cloud.google.com/go/compute v1.23.0/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM= +cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= +cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= +github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I= +github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= +github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230305170008-8188dc5388df h1:7RFfzj4SSt6nnvCPbCqijJi1nWCd+TqAT3bYCStRC18= +github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230305170008-8188dc5388df/go.mod h1:pSwJ0fSY5KhvocuWSx4fz3BA8OrA1bQn+K1Eli3BRwM= +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= +github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= +github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= +github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4 h1:/inchEIKaYC1Akx+H+gqO04wryn5h75LSazbRlnya1k= +github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4= +github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec= +github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= +github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= -github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84= -github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/envoyproxy/protoc-gen-validate v1.0.2 h1:QkIBuU5k+x7/QXPvPPnWXWlCdaBFApVqftFV6k087DA= +github.com/envoyproxy/protoc-gen-validate v1.0.2/go.mod h1:GpiZQP3dDbg4JouG/NNS7QWXpgx6x8QiMKdmN72jogE= +github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U= +github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch/v5 v5.9.0 h1:kcBlZQbplgElYIlo/n1hJbls2z/1awpXxpRi0/FOJfg= github.com/evanphx/json-patch/v5 v5.9.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= @@ -26,13 +57,23 @@ github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/ github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= +github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/golang/glog v1.1.2 h1:DVjP2PbBOzHyzA+dn3WhHIq4NdVu3Q+pvivFICf/7fo= +github.com/golang/glog v1.1.2/go.mod h1:zR+okUeTbrL6EL3xHUDxZuEtGv04p5shwip1+mL/rLQ= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= +github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= +github.com/google/cel-go v0.17.8 h1:j9m730pMZt1Fc4oKhCLUHfjj6527LuhYcYw0Rl8gqto= +github.com/google/cel-go v0.17.8/go.mod h1:HXZKzB0LXqer5lHHgfWAnlYwJaQBDKMjxjulNQzhwhY= github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= @@ -43,14 +84,34 @@ 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/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= -github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28= -github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= +github.com/google/uuid v1.3.1/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/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= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg= +github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= +github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/jonboulle/clockwork v0.2.2 h1:UOGuzwb1PwsrDAObMuhUnj0p5ULPj8V/xJ7Kx9qUBdQ= +github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/kcp-dev/apimachinery/v2 v2.0.0-alpha.0.0.20230926071920-57d168bcbe34 h1:tom0JX5OmAeOOmkGv8LaYHDtA1xAKDiQL5U0vhYYgdM= +github.com/kcp-dev/apimachinery/v2 v2.0.0-alpha.0.0.20230926071920-57d168bcbe34/go.mod h1:cWoaYGHl1nlzdEM2xvMzIASkEZJZLSf5nhe17M7wDhw= +github.com/kcp-dev/kcp/sdk v0.24.0 h1:ZTfStDOQshVU2cnrqjgMo9xb0VNblkmrgMRtl0PCQEY= +github.com/kcp-dev/kcp/sdk v0.24.0/go.mod h1:Pd2xxw/qhgfF2xgHolVwheq9VOJwPtNrBmxgBlYmjfk= +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/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/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= @@ -62,6 +123,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= +github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -77,18 +140,28 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v1.19.0 h1:ygXvpU1AoN1MhdzckN+PyD9QJOSD4x7kmXYlnfbA6JU= -github.com/prometheus/client_golang v1.19.0/go.mod h1:ZRM9uEAypZakd+q/x7+gmsvXdURP+DABIEIjnmDdp+k= +github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8= +github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc= github.com/prometheus/client_model v0.6.0 h1:k1v3CzpSRUTrKMppY35TLwPvxHqBu0bYgxZzqGIgaos= github.com/prometheus/client_model v0.6.0/go.mod h1:NTQHnmxFpouOD0DpvP4XujX3CdOAGQPoaGhyTchlyt8= -github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSzKKE= -github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc= +github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdOOfY= +github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY= github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= +github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/soheilhy/cmux v0.1.5 h1:jjzc5WVemNEDTLwv9tlmemhC73tI08BNOIGwBOo10Js= +github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0= +github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= +github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/pflag v1.0.6-0.20210604193023-d5e0c0615ace h1:9PNP1jnUjRhfmGMlkXHjYPishpcw4jpSt/V/xYY3FMA= +github.com/spf13/pflag v1.0.6-0.20210604193023-d5e0c0615ace/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stoewer/go-strcase v1.3.0 h1:g0eASXYtp+yvN9fK8sH94oCIk0fau9uV1/ZdJ0AVEzs= +github.com/stoewer/go-strcase v1.3.0/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo= 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= @@ -98,8 +171,46 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75 h1:6fotK7otjonDflCTK0BCfls4SPy3NcCVb5dqqmbRknE= +github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75/go.mod h1:KO6IkyS8Y3j8OdNO85qEYBsRPuteD+YciPomcXdrMnk= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +go.etcd.io/bbolt v1.3.8 h1:xs88BrvEv273UsB79e0hcVrlUWmS0a8upikMFhSyAtA= +go.etcd.io/bbolt v1.3.8/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw= +go.etcd.io/etcd/api/v3 v3.5.10 h1:szRajuUUbLyppkhs9K6BRtjY37l66XQQmw7oZRANE4k= +go.etcd.io/etcd/api/v3 v3.5.10/go.mod h1:TidfmT4Uycad3NM/o25fG3J07odo4GBB9hoxaodFCtI= +go.etcd.io/etcd/client/pkg/v3 v3.5.10 h1:kfYIdQftBnbAq8pUWFXfpuuxFSKzlmM5cSn76JByiT0= +go.etcd.io/etcd/client/pkg/v3 v3.5.10/go.mod h1:DYivfIviIuQ8+/lCq4vcxuseg2P2XbHygkKwFo9fc8U= +go.etcd.io/etcd/client/v2 v2.305.10 h1:MrmRktzv/XF8CvtQt+P6wLUlURaNpSDJHFZhe//2QE4= +go.etcd.io/etcd/client/v2 v2.305.10/go.mod h1:m3CKZi69HzilhVqtPDcjhSGp+kA1OmbNn0qamH80xjA= +go.etcd.io/etcd/client/v3 v3.5.10 h1:W9TXNZ+oB3MCd/8UjxHTWK5J9Nquw9fQBLJd5ne5/Ao= +go.etcd.io/etcd/client/v3 v3.5.10/go.mod h1:RVeBnDz2PUEZqTpgqwAtUd8nAPf5kjyFyND7P1VkOKc= +go.etcd.io/etcd/pkg/v3 v3.5.10 h1:WPR8K0e9kWl1gAhB5A7gEa5ZBTNkT9NdNWrR8Qpo1CM= +go.etcd.io/etcd/pkg/v3 v3.5.10/go.mod h1:TKTuCKKcF1zxmfKWDkfz5qqYaE3JncKKZPFf8c1nFUs= +go.etcd.io/etcd/raft/v3 v3.5.10 h1:cgNAYe7xrsrn/5kXMSaH8kM/Ky8mAdMqGOxyYwpP0LA= +go.etcd.io/etcd/raft/v3 v3.5.10/go.mod h1:odD6kr8XQXTy9oQnyMPBOr0TVe+gT0neQhElQ6jbGRc= +go.etcd.io/etcd/server/v3 v3.5.10 h1:4NOGyOwD5sUZ22PiWYKmfxqoeh72z6EhYjNosKGLmZg= +go.etcd.io/etcd/server/v3 v3.5.10/go.mod h1:gBplPHfs6YI0L+RpGkTQO7buDbHv5HJGG/Bst0/zIPo= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.0 h1:PzIubN4/sjByhDRHLviCjJuweBXWFZWhghjg7cS28+M= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.0/go.mod h1:Ct6zzQEuGK3WpJs2n4dn+wfJYzd/+hNnxMRTWjGn30M= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.0 h1:1eHu3/pUSWaOgltNK3WJFaywKsTIr/PwvHyDmi0lQA0= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.0/go.mod h1:HyABWq60Uy1kjJSa2BVOxUVao8Cdick5AWSKPutqy6U= +go.opentelemetry.io/otel v1.20.0 h1:vsb/ggIY+hUjD/zCAQHpzTmndPqv/ml2ArbsbfBYTAc= +go.opentelemetry.io/otel v1.20.0/go.mod h1:oUIGj3D77RwJdM6PPZImDpSZGDvkD9fhesHny69JFrs= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.20.0 h1:DeFD0VgTZ+Cj6hxravYYZE2W4GlneVH81iAOPjZkzk8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.20.0/go.mod h1:GijYcYmNpX1KazD5JmWGsi4P7dDTTTnfv1UbGn84MnU= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.20.0 h1:gvmNvqrPYovvyRmCSygkUDyL8lC5Tl845MLEwqpxhEU= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.20.0/go.mod h1:vNUq47TGFioo+ffTSnKNdob241vePmtNZnAODKapKd0= +go.opentelemetry.io/otel/metric v1.20.0 h1:ZlrO8Hu9+GAhnepmRGhSU7/VkpjrNowxRN9GyKR4wzA= +go.opentelemetry.io/otel/metric v1.20.0/go.mod h1:90DRw3nfK4D7Sm/75yQ00gTJxtkBxX+wu6YaNymbpVM= +go.opentelemetry.io/otel/sdk v1.20.0 h1:5Jf6imeFZlZtKv9Qbo6qt2ZkmWtdWx/wzcCbNUlAWGM= +go.opentelemetry.io/otel/sdk v1.20.0/go.mod h1:rmkSx1cZCm/tn16iWDn1GQbLtsW/LvsdEEFzCSRM6V0= +go.opentelemetry.io/otel/trace v1.20.0 h1:+yxVAPZPbQhbC3OfAkeIVTky6iTFpcr4SiY9om7mXSQ= +go.opentelemetry.io/otel/trace v1.20.0/go.mod h1:HJSK7F/hA5RlzpZ0zKDCHCDHm556LCDtKaAo6JmBFUU= +go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I= +go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= @@ -109,6 +220,8 @@ go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= 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.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= +golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/exp v0.0.0-20220827204233-334a2380cb91 h1:tnebWN09GYg9OLPss1KXj8txwZc6X6uMr6VFdcGNbHw= golang.org/x/exp v0.0.0-20220827204233-334a2380cb91/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= @@ -122,14 +235,17 @@ golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/oauth2 v0.16.0 h1:aDkGMBSYxElaoP81NpoUoz2oo2R2wHdZpGToUxfyQrQ= golang.org/x/oauth2 v0.16.0/go.mod h1:hqZ+0LWXsiVoZpeld6jVt06P3adbS2Uu911W1SsJv2o= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-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.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= -golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -153,6 +269,14 @@ gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20230822172742-b8732ec3820d h1:VBu5YqKPv6XiJ199exd8Br+Aetz+o08F+PLMnwJQHAY= +google.golang.org/genproto v0.0.0-20230822172742-b8732ec3820d/go.mod h1:yZTlhN0tQnXo3h00fuXNCxJdLdIdnVFVBaRJ5LWBbw4= +google.golang.org/genproto/googleapis/api v0.0.0-20230822172742-b8732ec3820d h1:DoPTO70H+bcDXcd39vOqb2viZxgqeBeSGtZ55yZU4/Q= +google.golang.org/genproto/googleapis/api v0.0.0-20230822172742-b8732ec3820d/go.mod h1:KjSP20unUpOx5kyQUFa7k4OJg0qeJ7DEZflGDu2p6Bk= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d h1:uvYuEyMHKNt+lT4K3bN6fGswmK8qSvcreM3BwjDh+y4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d/go.mod h1:+Bk1OCOj40wS2hwAMA+aCW9ypzm63QTBBHp6lQ3p+9M= +google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk= +google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -160,10 +284,13 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntN gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= +gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= k8s.io/api v0.30.1 h1:kCm/6mADMdbAxmIh0LBjS54nQBE+U4KmbCfIkF5CpJY= @@ -172,14 +299,22 @@ k8s.io/apiextensions-apiserver v0.30.1 h1:4fAJZ9985BmpJG6PkoxVRpXv9vmPUOVzl614xa k8s.io/apiextensions-apiserver v0.30.1/go.mod h1:R4GuSrlhgq43oRY9sF2IToFh7PVlF1JjfWdoG3pixk4= k8s.io/apimachinery v0.30.1 h1:ZQStsEfo4n65yAdlGTfP/uSHMQSoYzU/oeEbkmF7P2U= k8s.io/apimachinery v0.30.1/go.mod h1:iexa2somDaxdnj7bha06bhb43Zpa6eWH8N8dbqVjTUc= +k8s.io/apiserver v0.30.1 h1:BEWEe8bzS12nMtDKXzCF5Q5ovp6LjjYkSp8qOPk8LZ8= +k8s.io/apiserver v0.30.1/go.mod h1:i87ZnQ+/PGAmSbD/iEKM68bm1D5reX8fO4Ito4B01mo= k8s.io/client-go v0.30.1 h1:uC/Ir6A3R46wdkgCV3vbLyNOYyCJ8oZnjtJGKfytl/Q= k8s.io/client-go v0.30.1/go.mod h1:wrAqLNs2trwiCH/wxxmT/x3hKVH9PuV0GGW0oDoHVqc= +k8s.io/component-base v0.30.1 h1:bvAtlPh1UrdaZL20D9+sWxsJljMi0QZ3Lmw+kmZAaxQ= +k8s.io/component-base v0.30.1/go.mod h1:e/X9kDiOebwlI41AvBHuWdqFriSRrX50CdwA9TFaHLI= k8s.io/klog/v2 v2.120.1 h1:QXU6cPEOIslTGvZaXvFWiP9VKyeet3sawzTOvdXb4Vw= k8s.io/klog/v2 v2.120.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= +k8s.io/kms v0.30.1 h1:gEIbEeCbFiaN2tNfp/EUhFdGr5/CSj8Eyq6Mkr7cCiY= +k8s.io/kms v0.30.1/go.mod h1:GrMurD0qk3G4yNgGcsCEmepqf9KyyIrTXYR2lyUOJC4= k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag= k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98= k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI= k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.29.0 h1:/U5vjBbQn3RChhv7P11uhYvCSm5G2GaIi5AIGBS6r4c= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.29.0/go.mod h1:z7+wmGM2dfIiLRfrC6jb5kV2Mq/sK1ZP303cxzkV5Y4= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= diff --git a/examples/kcp/hack/go-install.sh b/examples/kcp/hack/go-install.sh new file mode 100755 index 0000000000..dd323c5666 --- /dev/null +++ b/examples/kcp/hack/go-install.sh @@ -0,0 +1,62 @@ +#!/usr/bin/env bash + +# Copyright 2024 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. + +# Originally copied from +# https://github.com/kubernetes-sigs/cluster-api-provider-gcp/blob/c26a68b23e9317323d5d37660fe9d29b3d2ff40c/scripts/go_install.sh + +set -o errexit +set -o nounset +set -o pipefail + +if [[ -z "${1:-}" ]]; then + echo "must provide module as first parameter" + exit 1 +fi + +if [[ -z "${2:-}" ]]; then + echo "must provide binary name as second parameter" + exit 1 +fi + +if [[ -z "${3:-}" ]]; then + echo "must provide version as third parameter" + exit 1 +fi + +if [[ -z "${GOBIN:-}" ]]; then + echo "GOBIN is not set. Must set GOBIN to install the bin in a specified directory." + exit 1 +fi + +mkdir -p "${GOBIN}" + +tmp_dir=$(mktemp -d -t goinstall_XXXXXXXXXX) +function clean { + rm -rf "${tmp_dir}" +} +trap clean EXIT + +rm "${GOBIN}/${2}"* > /dev/null 2>&1 || true + +cd "${tmp_dir}" + +# create a new module in the tmp directory +go mod init fake/mod + +# install the golang module specified as the first argument +go install -tags kcptools "${1}@${3}" +mv "${GOBIN}/${2}" "${GOBIN}/${2}-${3}" +ln -sf "${GOBIN}/${2}-${3}" "${GOBIN}/${2}" diff --git a/examples/kcp/hack/tools/go.mod b/examples/kcp/hack/tools/go.mod new file mode 100644 index 0000000000..e2c3eac332 --- /dev/null +++ b/examples/kcp/hack/tools/go.mod @@ -0,0 +1,61 @@ +module sigs.k8s.io/controller-runtime/hack/tools + +go 1.21 + +toolchain go1.21.5 + +require ( + github.com/joelanford/go-apidiff v0.8.2 + sigs.k8s.io/controller-tools v0.14.0 +) + +require ( + dario.cat/mergo v1.0.0 // indirect + github.com/Microsoft/go-winio v0.6.1 // indirect + github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 // indirect + github.com/cloudflare/circl v1.3.7 // indirect + github.com/cyphar/filepath-securejoin v0.2.4 // indirect + github.com/emirpasic/gods v1.18.1 // indirect + github.com/fatih/color v1.16.0 // indirect + github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect + github.com/go-git/go-billy/v5 v5.5.0 // indirect + github.com/go-git/go-git/v5 v5.11.0 // indirect + github.com/go-logr/logr v1.3.0 // indirect + github.com/gobuffalo/flect v1.0.2 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/google/gofuzz v1.2.0 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/kevinburke/ssh_config v1.2.0 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/pjbgf/sha1cd v0.3.0 // indirect + github.com/sergi/go-diff v1.1.0 // indirect + github.com/skeema/knownhosts v1.2.1 // indirect + github.com/spf13/cobra v1.8.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/xanzy/ssh-agent v0.3.3 // indirect + golang.org/x/crypto v0.18.0 // indirect + golang.org/x/exp v0.0.0-20230811145653-3b0b5b66b5f1 // indirect + golang.org/x/mod v0.14.0 // indirect + golang.org/x/net v0.20.0 // indirect + golang.org/x/sys v0.16.0 // indirect + golang.org/x/text v0.14.0 // indirect + golang.org/x/tools v0.17.0 // indirect + gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/warnings.v0 v0.1.2 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + k8s.io/api v0.29.0 // indirect + k8s.io/apiextensions-apiserver v0.29.0 // indirect + k8s.io/apimachinery v0.29.0 // indirect + k8s.io/klog/v2 v2.110.1 // indirect + k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect + sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect + sigs.k8s.io/yaml v1.4.0 // indirect +) diff --git a/examples/kcp/hack/tools/go.sum b/examples/kcp/hack/tools/go.sum new file mode 100644 index 0000000000..151b377222 --- /dev/null +++ b/examples/kcp/hack/tools/go.sum @@ -0,0 +1,240 @@ +dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= +dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= +github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= +github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= +github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 h1:kkhsdkhsCvIsutKu5zLMgWtgh9YxGCNAw8Ad8hjwfYg= +github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= +github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= +github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= +github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= +github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= +github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg= +github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcejNsXKSkQ6lcIaNec2nyfOdlTBR2lU= +github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= +github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= +github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= +github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= +github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= +github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= +github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= +github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY= +github.com/gliderlabs/ssh v0.3.5/go.mod h1:8XB4KraRrX39qHhT6yxPsHedjA08I/uBVwj4xC+/+z4= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= +github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU= +github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow= +github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= +github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= +github.com/go-git/go-git/v5 v5.11.0 h1:XIZc1p+8YzypNr34itUfSvYJcv+eYdTnTvOZ2vD3cA4= +github.com/go-git/go-git/v5 v5.11.0/go.mod h1:6GFcX2P3NM7FPBfpePbpLd21XxsgdAt+lKqXmCUiUCY= +github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY= +github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/gobuffalo/flect v1.0.2 h1:eqjPGSo2WmjgY2XlpGwo2NXgL3RucAKo4k4qQMNA5sA= +github.com/gobuffalo/flect v1.0.2/go.mod h1:A5msMlrHtLqh9umBSnvabjsMrCcCpAyzglnDvkbYKHs= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= +github.com/joelanford/go-apidiff v0.8.2 h1:AvHPY3vYINr6I2xGMHqhDKoszpdsDmH4VHZtit6NJKk= +github.com/joelanford/go-apidiff v0.8.2/go.mod h1:3fPoVVLpPCaU8aOuR7X1xDABzcWbLGKeeMerR2Pxulk= +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/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= +github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= +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/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +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/gomega v1.30.0 h1:hvMK7xYz4D3HapigLTeGdId/NcfQx1VHMJc60ew99+8= +github.com/onsi/gomega v1.30.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ= +github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= +github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= +github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= +github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/skeema/knownhosts v1.2.1 h1:SHWdIUa82uGZz+F+47k8SY4QhhI291cXCpopT1lK2AQ= +github.com/skeema/knownhosts v1.2.1/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo= +github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= +github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= +github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +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.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= +golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= +golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= +golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= +golang.org/x/exp v0.0.0-20230811145653-3b0b5b66b5f1 h1:EFPukSCgigmk1W0azH8EMt97AoMjMOgtJ3Z3sGM9AGw= +golang.org/x/exp v0.0.0-20230811145653-3b0b5b66b5f1/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= +golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= +golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= +golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= +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.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/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-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= +golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE= +golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/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.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc= +golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +k8s.io/api v0.29.0 h1:NiCdQMY1QOp1H8lfRyeEf8eOwV6+0xA6XEE44ohDX2A= +k8s.io/api v0.29.0/go.mod h1:sdVmXoz2Bo/cb77Pxi71IPTSErEW32xa4aXwKH7gfBA= +k8s.io/apiextensions-apiserver v0.29.0 h1:0VuspFG7Hj+SxyF/Z/2T0uFbI5gb5LRgEyUVE3Q4lV0= +k8s.io/apiextensions-apiserver v0.29.0/go.mod h1:TKmpy3bTS0mr9pylH0nOt/QzQRrW7/h7yLdRForMZwc= +k8s.io/apimachinery v0.29.0 h1:+ACVktwyicPz0oc6MTMLwa2Pw3ouLAfAon1wPLtG48o= +k8s.io/apimachinery v0.29.0/go.mod h1:eVBxQ/cwiJxH58eK/jd/vAk4mrxmVlnpBH5J2GbMeis= +k8s.io/klog/v2 v2.110.1 h1:U/Af64HJf7FcwMcXyKm2RPM22WZzyR7OSpYj5tg3cL0= +k8s.io/klog/v2 v2.110.1/go.mod h1:YGtd1984u+GgbuZ7e08/yBuAfKLSO0+uR1Fhi6ExXjo= +k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI= +k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/controller-tools v0.14.0 h1:rnNoCC5wSXlrNoBKKzL70LNJKIQKEzT6lloG6/LF73A= +sigs.k8s.io/controller-tools v0.14.0/go.mod h1:TV7uOtNNnnR72SpzhStvPkoS/U5ir0nMudrkrC4M9Sc= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= +sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= +sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= +sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= +sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= diff --git a/examples/kcp/hack/tools/tools.go b/examples/kcp/hack/tools/tools.go new file mode 100644 index 0000000000..481a7c6f01 --- /dev/null +++ b/examples/kcp/hack/tools/tools.go @@ -0,0 +1,25 @@ +// +build tools + +/* +Copyright 2019 The Kubernetes 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. +*/ + +// This package imports things required by build scripts, to force `go mod` to see them as dependencies +package tools + +import ( + _ "github.com/joelanford/go-apidiff" + _ "sigs.k8s.io/controller-tools/cmd/controller-gen" +) diff --git a/examples/kcp/hack/update-codegen-crds.sh b/examples/kcp/hack/update-codegen-crds.sh new file mode 100755 index 0000000000..03524149f0 --- /dev/null +++ b/examples/kcp/hack/update-codegen-crds.sh @@ -0,0 +1,50 @@ +#!/usr/bin/env bash + +# Copyright 2024 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 -o errexit +set -o nounset +set -o pipefail +set -o xtrace + +if [[ -z "${CONTROLLER_GEN:-}" ]]; then + echo "You must either set CONTROLLER_GEN to the path to controller-gen or invoke via make" + exit 1 +fi + +REPO_ROOT=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd) + +# Update generated CRD YAML +( + cd "${REPO_ROOT}/apis" + "${CONTROLLER_GEN}" \ + crd \ + rbac:roleName=manager-role \ + webhook \ + paths="./..." \ + output:crd:artifacts:config="${REPO_ROOT}"/config/crds +) + +for CRD in "${REPO_ROOT}"/config/crds/*.yaml; do + if [ -f "${CRD}-patch" ]; then + echo "Applying ${CRD}" + ${YAML_PATCH} -o "${CRD}-patch" < "${CRD}" > "${CRD}.patched" + mv "${CRD}.patched" "${CRD}" + fi +done + +( + ${KCP_APIGEN_GEN} --input-dir "${REPO_ROOT}"/config/crds --output-dir "${REPO_ROOT}"/config/widgets/resources +) diff --git a/examples/kcp/main.go b/examples/kcp/main.go new file mode 100644 index 0000000000..6480643410 --- /dev/null +++ b/examples/kcp/main.go @@ -0,0 +1,199 @@ +/* +Copyright 2024 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. +*/ + +package main + +import ( + "context" + "flag" + "fmt" + "os" + + kcpclienthelper "github.com/kcp-dev/apimachinery/v2/pkg/client" + "github.com/kcp-dev/controller-runtime/examples/kcp/controllers/configmap" + "github.com/kcp-dev/controller-runtime/examples/kcp/controllers/widget" + "k8s.io/apimachinery/pkg/types" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + "k8s.io/client-go/discovery" + "k8s.io/client-go/kubernetes/scheme" + clientgoscheme "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/rest" + "k8s.io/klog/v2" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/config" + + datav1alpha1 "github.com/kcp-dev/controller-runtime/examples/kcp/apis/v1alpha1" + "github.com/kcp-dev/logicalcluster/v3" + + // Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.) + // to ensure that exec-entrypoint and run can make use of them. + _ "k8s.io/client-go/plugin/pkg/client/auth" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/healthz" + "sigs.k8s.io/controller-runtime/pkg/kcp" + + apisv1alpha1 "github.com/kcp-dev/kcp/sdk/apis/apis/v1alpha1" +) + +func init() { + utilruntime.Must(clientgoscheme.AddToScheme(scheme.Scheme)) + utilruntime.Must(datav1alpha1.AddToScheme(scheme.Scheme)) + utilruntime.Must(apisv1alpha1.AddToScheme(scheme.Scheme)) +} + +func main() { + var opts Options + opts.addFlags(flag.CommandLine) + flag.Parse() + flag.Lookup("v").Value.Set("6") + + ctx := ctrl.SetupSignalHandler() + if err := runController(ctx, opts); err != nil { + fmt.Fprintf(os.Stderr, "%v\n", err) + os.Exit(1) + } +} + +type Options struct { + MetricsAddr string + EnableLeaderElection bool + ProbeAddr string + APIExportName string + KubeconfigContext string +} + +func (o *Options) addFlags(fs *flag.FlagSet) { + fs.StringVar(&o.KubeconfigContext, "context", "", "kubeconfig context") + fs.StringVar(&o.APIExportName, "api-export-name", "data.my.domain", "The name of the APIExport.") + fs.StringVar(&o.MetricsAddr, "metrics-bind-address", ":8080", "The address the metric endpoint binds to.") + fs.StringVar(&o.ProbeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.") + fs.BoolVar(&o.EnableLeaderElection, "leader-elect", false, + "Enable leader election for controller manager. "+ + "Enabling this will ensure there is only one active controller manager.") + + klog.InitFlags(fs) +} + +func runController(ctx context.Context, opts Options) error { + log := ctrl.Log.WithName("setup").WithValues("api-export-name", opts.APIExportName) + + // Important: We use non-controller-runtime client loader so we can always + // be sure we have correct kubeconfig file. This ease the development and maintenance + // of the example. In production, you should use the controller-runtime client loader + // to load the kubeconfig file dedicated to workspace where APIExport is located. + // restConfig := ctrl.GetConfigOrDie() + widgetsCluster := logicalcluster.NewPath("root:widgets") + widgetsConfig, err := config.GetConfigWithContext("base") + if err != nil { + return fmt.Errorf("unable to get config: %w", err) + } + widgetsConfig = rest.AddUserAgent(kcpclienthelper.SetCluster(widgetsConfig, widgetsCluster), "kcp-controller-runtime-example") + + ctrlOpts := ctrl.Options{ + HealthProbeBindAddress: opts.ProbeAddr, + LeaderElection: opts.EnableLeaderElection, + LeaderElectionID: "68a0532d.my.domain", + LeaderElectionConfig: widgetsConfig, + } + + // create a manager, either with or without kcp support + var mgr ctrl.Manager + if isKcp, err := kcpAPIsGroupPresent(widgetsConfig); err != nil { + return fmt.Errorf("error checking for kcp APIs group: %w", err) + } else if isKcp { + log.Info("Looking up virtual workspace URL") + exportConfig, err := restConfigForAPIExport(ctx, widgetsConfig, opts.APIExportName) + if err != nil { + return fmt.Errorf("error looking up virtual workspace URL: %w", err) + } + log.Info("Using virtual workspace URL", "url", exportConfig.Host) + + mgr, err = kcp.NewClusterAwareManager(exportConfig, ctrlOpts) + if err != nil { + return fmt.Errorf("unable to create cluster aware manager: %w", err) + } + } else { + log.Info("The apis.kcp.dev group is not present - creating standard manager") + mgr, err = ctrl.NewManager(widgetsConfig, ctrlOpts) + if err != nil { + return fmt.Errorf("unable to create manager: %w", err) + } + } + + // create controllers + if err = (&configmap.Reconciler{Client: mgr.GetClient()}).SetupWithManager(mgr); err != nil { + return fmt.Errorf("unable to create configmap controller: %w", err) + } + if err = (&widget.Reconciler{Client: mgr.GetClient()}).SetupWithManager(mgr); err != nil { + return fmt.Errorf("unable to create widget controller: %w", err) + } + + if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { + return fmt.Errorf("unable to set up health check: %w", err) + } + if err := mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil { + return fmt.Errorf("unable to set up ready check: %w", err) + } + + log.Info("Starting manager") + return mgr.Start(ctx) +} + +// restConfigForAPIExport returns a *rest.Config properly configured to communicate with the endpoint for the +// APIExport's virtual workspace. +func restConfigForAPIExport(ctx context.Context, cfg *rest.Config, apiExportName string) (*rest.Config, error) { + apiExportClient, err := client.New(cfg, client.Options{}) + if err != nil { + return nil, fmt.Errorf("error creating APIExport client: %w", err) + } + + var apiExport apisv1alpha1.APIExport + if err := apiExportClient.Get(ctx, types.NamespacedName{Name: apiExportName}, &apiExport); err != nil { + return nil, fmt.Errorf("error getting APIExport %q: %w", apiExportName, err) + } + + if len(apiExport.Status.VirtualWorkspaces) < 1 { + return nil, fmt.Errorf("APIExport %q status.virtualWorkspaces is empty", apiExportName) + } + + // create a new rest.Config with the APIExport's virtual workspace URL + exportConfig := rest.CopyConfig(cfg) + exportConfig.Host = apiExport.Status.VirtualWorkspaces[0].URL // TODO(ncdc): sharding support + + return exportConfig, nil +} + +func kcpAPIsGroupPresent(cfg *rest.Config) (bool, error) { + discoveryClient, err := discovery.NewDiscoveryClientForConfig(cfg) + if err != nil { + return false, fmt.Errorf("failed to create discovery client: %w", err) + } + apiGroupList, err := discoveryClient.ServerGroups() + if err != nil { + return false, fmt.Errorf("failed to get server groups: %w", err) + } + + for _, group := range apiGroupList.Groups { + if group.Name == apisv1alpha1.SchemeGroupVersion.Group { + for _, version := range group.Versions { + if version.Version == apisv1alpha1.SchemeGroupVersion.Version { + return true, nil + } + } + } + } + return false, nil +} diff --git a/examples/kcp/test/e2e/audit-policy.yaml b/examples/kcp/test/e2e/audit-policy.yaml new file mode 100644 index 0000000000..9b1b0384de --- /dev/null +++ b/examples/kcp/test/e2e/audit-policy.yaml @@ -0,0 +1,30 @@ +apiVersion: audit.k8s.io/v1 +kind: Policy +omitStages: + - RequestReceived +omitManagedFields: true +rules: + - level: None + nonResourceURLs: + - "/api*" + - "/version" + + - level: Metadata + resources: + - group: "" + resources: ["secrets", "configmaps"] + - group: "authorization.k8s.io" + resources: ["subjectaccessreviews"] + + - level: Metadata + verbs: ["list", "watch"] + + - level: Metadata + verbs: ["get", "delete"] + omitStages: + - ResponseStarted + + - level: RequestResponse + verbs: ["create", "update", "patch"] + omitStages: + - ResponseStarted diff --git a/examples/kcp/test/e2e/controller_test.go b/examples/kcp/test/e2e/controller_test.go new file mode 100644 index 0000000000..8a28ddf0f6 --- /dev/null +++ b/examples/kcp/test/e2e/controller_test.go @@ -0,0 +1,336 @@ +package e2e + +import ( + "context" + "flag" + "fmt" + "math/rand" + "strings" + "testing" + "time" + + "github.com/google/go-cmp/cmp" + apierrors "k8s.io/apimachinery/pkg/api/errors" + + kcpclienthelper "github.com/kcp-dev/apimachinery/v2/pkg/client" + apisv1alpha1 "github.com/kcp-dev/kcp/sdk/apis/apis/v1alpha1" + corev1alpha1 "github.com/kcp-dev/kcp/sdk/apis/core/v1alpha1" + tenancyv1alpha1 "github.com/kcp-dev/kcp/sdk/apis/tenancy/v1alpha1" + "github.com/kcp-dev/kcp/sdk/apis/third_party/conditions/util/conditions" + + "github.com/kcp-dev/logicalcluster/v3" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/errors" + "k8s.io/apimachinery/pkg/util/wait" + clientgoscheme "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/rest" + + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/config" + + datav1alpha1 "github.com/kcp-dev/controller-runtime/examples/kcp/apis/v1alpha1" +) + +// The tests in this package expect to be called when: +// - kcp is running +// - the controller-manager from this repo is running +// +// We can then check that the controllers defined here are working as expected. + +var workspaceName string + +func init() { + rand.Seed(time.Now().Unix()) + flag.StringVar(&workspaceName, "workspace", "", "Workspace in which to run these tests.") +} + +func parentWorkspace(t *testing.T) logicalcluster.Path { + if workspaceName == "" { + t.Fatal("--workspace cannot be empty") + } + + return logicalcluster.NewPath(workspaceName) +} + +func loadClusterConfig(t *testing.T, clusterName logicalcluster.Path) *rest.Config { + t.Helper() + restConfig, err := config.GetConfigWithContext("base") + if err != nil { + t.Fatalf("failed to load *rest.Config: %v", err) + } + return rest.AddUserAgent(kcpclienthelper.SetCluster(restConfig, clusterName), t.Name()) +} + +func loadClient(t *testing.T, clusterName logicalcluster.Path) client.Client { + t.Helper() + scheme := runtime.NewScheme() + if err := clientgoscheme.AddToScheme(scheme); err != nil { + t.Fatalf("failed to add client go to scheme: %v", err) + } + if err := tenancyv1alpha1.AddToScheme(scheme); err != nil { + t.Fatalf("failed to add %s to scheme: %v", tenancyv1alpha1.SchemeGroupVersion, err) + } + if err := datav1alpha1.AddToScheme(scheme); err != nil { + t.Fatalf("failed to add %s to scheme: %v", datav1alpha1.GroupVersion, err) + } + if err := apisv1alpha1.AddToScheme(scheme); err != nil { + t.Fatalf("failed to add %s to scheme: %v", apisv1alpha1.SchemeGroupVersion, err) + } + tenancyClient, err := client.New(loadClusterConfig(t, clusterName), client.Options{Scheme: scheme}) + if err != nil { + t.Fatalf("failed to create a client: %v", err) + } + return tenancyClient +} + +func createWorkspace(t *testing.T, clusterName logicalcluster.Path) client.Client { + t.Helper() + parent, ok := clusterName.Parent() + if !ok { + t.Fatalf("cluster %s has no parent", clusterName) + } + c := loadClient(t, parent) + t.Logf("creating workspace %s", clusterName) + if err := c.Create(context.TODO(), &tenancyv1alpha1.Workspace{ + ObjectMeta: metav1.ObjectMeta{ + Name: clusterName.Base(), + }, + Spec: tenancyv1alpha1.WorkspaceSpec{ + Type: tenancyv1alpha1.WorkspaceTypeReference{ + Name: "widgets", + Path: "root", + }, + }, + }); err != nil { + t.Fatalf("failed to create workspace: %s: %v", clusterName, err) + } + + t.Logf("waiting for workspace %s to be ready", clusterName) + var workspace tenancyv1alpha1.Workspace + if err := wait.PollImmediate(100*time.Millisecond, wait.ForeverTestTimeout, func() (done bool, err error) { + fetchErr := c.Get(context.TODO(), client.ObjectKey{Name: clusterName.Base()}, &workspace) + if fetchErr != nil { + t.Logf("failed to get workspace %s: %v", clusterName, err) + return false, fetchErr + } + var reason string + if actual, expected := workspace.Status.Phase, corev1alpha1.LogicalClusterPhaseReady; actual != expected { + reason = fmt.Sprintf("phase is %s, not %s", actual, expected) + t.Logf("not done waiting for workspace %s to be ready: %s", clusterName, reason) + } + return reason == "", nil + }); err != nil { + t.Fatalf("workspace %s never ready: %v", clusterName, err) + } + + return waitingForAPIBinding(t, clusterName) +} + +func waitingForAPIBinding(t *testing.T, workspaceCluster logicalcluster.Path) client.Client { + c := loadClient(t, workspaceCluster) + ctx := context.TODO() + apiNamePrefix := "data.my.domain" // matches bootstrapped name + + list := &apisv1alpha1.APIBindingList{} + err := c.List(ctx, list) + if err != nil { + t.Fatalf("failed to list APIBindings: %v", err) + } + + apiName := "" + for _, apiBinding := range list.Items { + if strings.HasPrefix(apiBinding.Name, apiNamePrefix) { + apiName = apiBinding.Name + break + } + } + + t.Logf("waiting for APIBinding %s|%s to be bound", workspaceCluster, apiName) + var apiBinding apisv1alpha1.APIBinding + if err := wait.PollImmediate(100*time.Millisecond, wait.ForeverTestTimeout, func() (done bool, err error) { + fetchErr := c.Get(context.TODO(), client.ObjectKey{Name: apiName}, &apiBinding) + if fetchErr != nil { + t.Logf("failed to get APIBinding %s|%s: %v", workspaceCluster, apiName, err) + return false, fetchErr + } + var reason string + if !conditions.IsTrue(&apiBinding, apisv1alpha1.InitialBindingCompleted) { + condition := conditions.Get(&apiBinding, apisv1alpha1.InitialBindingCompleted) + if condition != nil { + reason = fmt.Sprintf("%s: %s", condition.Reason, condition.Message) + } else { + reason = "no condition present" + } + t.Logf("not done waiting for APIBinding %s|%s to be bound: %s", workspaceCluster, apiName, reason) + } + return conditions.IsTrue(&apiBinding, apisv1alpha1.InitialBindingCompleted), nil + }); err != nil { + t.Fatalf("APIBinding %s|%s never bound: %v", workspaceCluster, apiName, err) + } + + return c +} + +const characters = "abcdefghijklmnopqrstuvwxyz" + +func randomName() string { + b := make([]byte, 10) + for i := range b { + b[i] = characters[rand.Intn(len(characters))] + } + return string(b) +} + +// TestConfigMapController verifies that our ConfigMap behavior works. +func TestConfigMapController(t *testing.T) { + t.Parallel() + for i := 0; i < 3; i++ { + t.Run(fmt.Sprintf("attempt-%d", i), func(t *testing.T) { + t.Parallel() + workspaceCluster := parentWorkspace(t).Join(randomName()) + c := createWorkspace(t, workspaceCluster) + + namespaceName := randomName() + t.Logf("creating namespace %s|%s", workspaceCluster, namespaceName) + if err := c.Create(context.TODO(), &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{Name: namespaceName}, + }); err != nil { + t.Fatalf("failed to create a namespace: %v", err) + } + + otherNamespaceName := randomName() + data := randomName() + configmapName := randomName() + t.Logf("creating configmap %s|%s/%s", workspaceCluster, namespaceName, configmapName) + if err := c.Create(context.TODO(), &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: configmapName, + Namespace: namespaceName, + Labels: map[string]string{ + "name": "timothy", + }, + }, + Data: map[string]string{ + "namespace": otherNamespaceName, + "secretData": data, + }, + }); err != nil { + t.Fatalf("failed to create a configmap: %v", err) + } + + t.Logf("waiting for configmap %s|%s to have a response", workspaceCluster, configmapName) + var configmap corev1.ConfigMap + if err := wait.PollImmediate(100*time.Millisecond, wait.ForeverTestTimeout, func() (done bool, err error) { + fetchErr := c.Get(context.TODO(), client.ObjectKey{Namespace: namespaceName, Name: configmapName}, &configmap) + if fetchErr != nil { + t.Logf("failed to get configmap %s|%s/%s: %v", workspaceCluster, namespaceName, configmapName, err) + return false, fetchErr + } + response, ok := configmap.Labels["response"] + if !ok { + t.Logf("configmap %s|%s/%s has no response set", workspaceCluster, namespaceName, configmapName) + } + diff := cmp.Diff(response, "hello-timothy") + if ok && diff != "" { + t.Logf("configmap %s|%s/%s has an invalid response: %v", workspaceCluster, namespaceName, configmapName, diff) + } + return diff == "", nil + }); err != nil { + t.Fatalf("configmap %s|%s/%s never got a response: %v", workspaceCluster, namespaceName, configmapName, err) + } + + t.Logf("waiting for namespace %s|%s to exist", workspaceCluster, otherNamespaceName) + var otherNamespace corev1.Namespace + if err := wait.PollImmediate(100*time.Millisecond, wait.ForeverTestTimeout, func() (done bool, err error) { + fetchErr := c.Get(context.TODO(), client.ObjectKey{Name: otherNamespaceName}, &otherNamespace) + if fetchErr != nil && !apierrors.IsNotFound(fetchErr) { + t.Logf("failed to get namespace %s|%s: %v", workspaceCluster, otherNamespaceName, fetchErr) + return false, fetchErr + } + return fetchErr == nil, nil + }); err != nil { + t.Fatalf("namespace %s|%s never created: %v", workspaceCluster, otherNamespaceName, err) + } + + t.Logf("waiting for secret %s|%s/%s to exist and have correct data", workspaceCluster, namespaceName, configmapName) + var secret corev1.Secret + if err := wait.PollImmediate(100*time.Millisecond, wait.ForeverTestTimeout, func() (done bool, err error) { + fetchErr := c.Get(context.TODO(), client.ObjectKey{Namespace: namespaceName, Name: configmapName}, &secret) + if fetchErr != nil && !apierrors.IsNotFound(fetchErr) { + t.Logf("failed to get secret %s|%s/%s: %v", workspaceCluster, namespaceName, configmapName, fetchErr) + return false, fetchErr + } + response, ok := secret.Data["dataFromCM"] + if !ok { + t.Logf("secret %s|%s/%s has no data set", workspaceCluster, namespaceName, configmapName) + } + diff := cmp.Diff(string(response), data) + if ok && diff != "" { + t.Logf("secret %s|%s/%s has invalid data: %v", workspaceCluster, namespaceName, configmapName, diff) + } + return diff == "", nil + }); err != nil { + t.Fatalf("secret %s|%s/%s never created: %v", workspaceCluster, namespaceName, configmapName, err) + } + }) + } +} + +// TestWidgetController verifies that our ConfigMap behavior works. +func TestWidgetController(t *testing.T) { + t.Parallel() + for i := 0; i < 3; i++ { + t.Run(fmt.Sprintf("attempt-%d", i), func(t *testing.T) { + t.Parallel() + workspaceCluster := parentWorkspace(t).Join(randomName()) + c := createWorkspace(t, workspaceCluster) + + var totalWidgets int + for i := 0; i < 3; i++ { + namespaceName := randomName() + t.Logf("creating namespace %s|%s", workspaceCluster, namespaceName) + if err := c.Create(context.TODO(), &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{Name: namespaceName}, + }); err != nil { + t.Fatalf("failed to create a namespace: %v", err) + } + numWidgets := rand.Intn(10) + for i := 0; i < numWidgets; i++ { + if err := c.Create(context.TODO(), &datav1alpha1.Widget{ + ObjectMeta: metav1.ObjectMeta{Namespace: namespaceName, Name: fmt.Sprintf("widget-%d", i)}, + Spec: datav1alpha1.WidgetSpec{Foo: fmt.Sprintf("intended-%d", i)}, + }); err != nil { + t.Fatalf("failed to create widget: %v", err) + } + } + totalWidgets += numWidgets + } + + t.Logf("waiting for all widgets in cluster %s to have a correct status", workspaceCluster) + var allWidgets datav1alpha1.WidgetList + if err := wait.PollImmediate(100*time.Millisecond, wait.ForeverTestTimeout, func() (done bool, err error) { + fetchErr := c.List(context.TODO(), &allWidgets) + if fetchErr != nil { + t.Logf("failed to get widgets in cluster %s: %v", workspaceCluster, err) + return false, fetchErr + } + var errs []error + for _, widget := range allWidgets.Items { + if actual, expected := widget.Status.Total, totalWidgets; actual != expected { + errs = append(errs, fmt.Errorf("widget %s|%s .status.total incorrect: %d != %d", workspaceCluster, widget.Name, actual, expected)) + } + } + validationErr := errors.NewAggregate(errs) + if validationErr != nil { + t.Logf("widgets in cluster %s invalid: %v", workspaceCluster, validationErr) + } + return validationErr == nil, nil + }); err != nil { + t.Fatalf("widgets in cluster %s never got correct statuses: %v", workspaceCluster, err) + } + }) + } +} diff --git a/examples/scratch-env/go.mod b/examples/scratch-env/go.mod deleted file mode 100644 index 345940ee47..0000000000 --- a/examples/scratch-env/go.mod +++ /dev/null @@ -1,68 +0,0 @@ -module sigs.k8s.io/controller-runtime/examples/scratch-env - -go 1.22.0 - -require ( - github.com/spf13/pflag v1.0.5 - go.uber.org/zap v1.26.0 - sigs.k8s.io/controller-runtime v0.0.0-00010101000000-000000000000 -) - -require ( - github.com/beorn7/perks v1.0.1 // indirect - github.com/cespare/xxhash/v2 v2.2.0 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect - github.com/emicklei/go-restful/v3 v3.11.0 // indirect - github.com/evanphx/json-patch/v5 v5.9.0 // indirect - github.com/fsnotify/fsnotify v1.7.0 // indirect - github.com/go-logr/logr v1.4.1 // indirect - github.com/go-logr/zapr v1.3.0 // indirect - github.com/go-openapi/jsonpointer v0.19.6 // indirect - github.com/go-openapi/jsonreference v0.20.2 // indirect - github.com/go-openapi/swag v0.22.3 // indirect - github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect - github.com/golang/protobuf v1.5.4 // indirect - github.com/google/gnostic-models v0.6.8 // indirect - github.com/google/go-cmp v0.6.0 // indirect - github.com/google/gofuzz v1.2.0 // indirect - github.com/google/uuid v1.3.0 // indirect - github.com/imdario/mergo v0.3.6 // indirect - github.com/josharian/intern v1.0.0 // indirect - github.com/json-iterator/go v1.1.12 // indirect - github.com/mailru/easyjson v0.7.7 // indirect - github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect - github.com/modern-go/reflect2 v1.0.2 // indirect - github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - github.com/pkg/errors v0.9.1 // indirect - github.com/prometheus/client_golang v1.19.0 // indirect - github.com/prometheus/client_model v0.6.0 // indirect - github.com/prometheus/common v0.48.0 // indirect - github.com/prometheus/procfs v0.12.0 // indirect - go.uber.org/multierr v1.11.0 // indirect - golang.org/x/exp v0.0.0-20220827204233-334a2380cb91 // indirect - golang.org/x/net v0.23.0 // indirect - golang.org/x/oauth2 v0.16.0 // indirect - golang.org/x/sys v0.19.0 // indirect - golang.org/x/term v0.18.0 // indirect - golang.org/x/text v0.14.0 // indirect - golang.org/x/time v0.3.0 // indirect - gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect - google.golang.org/appengine v1.6.7 // indirect - google.golang.org/protobuf v1.33.0 // indirect - gopkg.in/inf.v0 v0.9.1 // indirect - gopkg.in/yaml.v2 v2.4.0 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/api v0.30.1 // indirect - k8s.io/apiextensions-apiserver v0.30.1 // indirect - k8s.io/apimachinery v0.30.1 // indirect - k8s.io/client-go v0.30.1 // indirect - k8s.io/klog/v2 v2.120.1 // indirect - k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect - k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect - sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect - sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect - sigs.k8s.io/yaml v1.4.0 // indirect -) - -replace sigs.k8s.io/controller-runtime => ../.. diff --git a/examples/scratch-env/main.go b/examples/scratch-env/main.go deleted file mode 100644 index b8305ffed3..0000000000 --- a/examples/scratch-env/main.go +++ /dev/null @@ -1,132 +0,0 @@ -/* -Copyright 2021 The Kubernetes 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. -*/ - -package main - -import ( - goflag "flag" - "os" - - flag "github.com/spf13/pflag" - "go.uber.org/zap" - - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/envtest" - logzap "sigs.k8s.io/controller-runtime/pkg/log/zap" -) - -var ( - crdPaths = flag.StringSlice("crd-paths", nil, "paths to files or directories containing CRDs to install on start") - webhookPaths = flag.StringSlice("webhook-paths", nil, "paths to files or directories containing webhook configurations to install on start") - attachControlPlaneOut = flag.Bool("debug-env", false, "attach to test env (apiserver & etcd) output -- just a convinience flag to force KUBEBUILDER_ATTACH_CONTROL_PLANE_OUTPUT=true") -) - -// have a separate function so we can return an exit code w/o skipping defers -func runMain() int { - loggerOpts := &logzap.Options{ - Development: true, // a sane default - ZapOpts: []zap.Option{zap.AddCaller()}, - } - { - var goFlagSet goflag.FlagSet - loggerOpts.BindFlags(&goFlagSet) - flag.CommandLine.AddGoFlagSet(&goFlagSet) - } - flag.Parse() - ctrl.SetLogger(logzap.New(logzap.UseFlagOptions(loggerOpts))) - ctrl.Log.Info("Starting...") - - log := ctrl.Log.WithName("main") - - env := &envtest.Environment{} - env.CRDInstallOptions.Paths = *crdPaths - env.WebhookInstallOptions.Paths = *webhookPaths - - if *attachControlPlaneOut { - os.Setenv("KUBEBUILDER_ATTACH_CONTROL_PLANE_OUTPUT", "true") - } - - log.Info("Starting apiserver & etcd") - cfg, err := env.Start() - if err != nil { - log.Error(err, "unable to start the test environment") - // shut down the environment in case we started it and failed while - // installing CRDs or provisioning users. - if err := env.Stop(); err != nil { - log.Error(err, "unable to stop the test environment after an error (this might be expected, but just though you should know)") - } - return 1 - } - - log.Info("apiserver running", "host", cfg.Host) - - // NB(directxman12): this group is unfortunately named, but various - // kubernetes versions require us to use it to get "admin" access. - user, err := env.ControlPlane.AddUser(envtest.User{ - Name: "envtest-admin", - Groups: []string{"system:masters"}, - }, nil) - if err != nil { - log.Error(err, "unable to provision admin user, continuing on without it") - return 1 - } - - // TODO(directxman12): add support for writing to a new context in an existing file - kubeconfigFile, err := os.CreateTemp("", "scratch-env-kubeconfig-") - if err != nil { - log.Error(err, "unable to create kubeconfig file, continuing on without it") - return 1 - } - defer os.Remove(kubeconfigFile.Name()) - - { - log := log.WithValues("path", kubeconfigFile.Name()) - log.V(1).Info("Writing kubeconfig") - - kubeConfig, err := user.KubeConfig() - if err != nil { - log.Error(err, "unable to create kubeconfig") - } - - if _, err := kubeconfigFile.Write(kubeConfig); err != nil { - log.Error(err, "unable to save kubeconfig") - return 1 - } - - log.Info("Wrote kubeconfig") - } - - if opts := env.WebhookInstallOptions; opts.LocalServingPort != 0 { - log.Info("webhooks configured for", "host", opts.LocalServingHost, "port", opts.LocalServingPort, "dir", opts.LocalServingCertDir) - } - - ctx := ctrl.SetupSignalHandler() - <-ctx.Done() - - log.Info("Shutting down apiserver & etcd") - err = env.Stop() - if err != nil { - log.Error(err, "unable to stop the test environment") - return 1 - } - - log.Info("Shutdown successful") - return 0 -} - -func main() { - os.Exit(runMain()) -} diff --git a/go.mod b/go.mod index db128c8ab7..03527ada6f 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module sigs.k8s.io/controller-runtime go 1.22.0 require ( - github.com/evanphx/json-patch v4.12.0+incompatible // Using v4 to match upstream + github.com/evanphx/json-patch v5.6.0+incompatible // Using v4 to match upstream github.com/evanphx/json-patch/v5 v5.9.0 github.com/fsnotify/fsnotify v1.7.0 github.com/go-logr/logr v1.4.1 @@ -12,11 +12,11 @@ require ( github.com/google/gofuzz v1.2.0 github.com/onsi/ginkgo/v2 v2.17.1 github.com/onsi/gomega v1.32.0 - github.com/prometheus/client_golang v1.16.0 - github.com/prometheus/client_model v0.4.0 + github.com/prometheus/client_golang v1.19.0 + github.com/prometheus/client_model v0.6.0 go.uber.org/goleak v1.3.0 go.uber.org/zap v1.26.0 - golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e + golang.org/x/exp v0.0.0-20220827204233-334a2380cb91 golang.org/x/sys v0.18.0 gomodules.xyz/jsonpatch/v2 v2.4.0 k8s.io/api v0.30.1 @@ -27,7 +27,12 @@ require ( k8s.io/component-base v0.30.1 // indirect k8s.io/klog/v2 v2.120.1 k8s.io/utils v0.0.0-20230726121419-3b25d923346b - sigs.k8s.io/yaml v1.3.0 + sigs.k8s.io/yaml v1.4.0 +) + +require ( + github.com/kcp-dev/apimachinery/v2 v2.0.0-alpha.0.0.20230926071920-57d168bcbe34 + github.com/kcp-dev/logicalcluster/v3 v3.0.5 ) require ( @@ -38,7 +43,7 @@ require ( github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/emicklei/go-restful/v3 v3.11.0 // indirect - github.com/felixge/httpsnoop v1.0.3 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/jsonpointer v0.19.6 // indirect github.com/go-openapi/jsonreference v0.20.2 // indirect @@ -50,41 +55,40 @@ require ( github.com/google/cel-go v0.17.8 // indirect github.com/google/gnostic-models v0.6.8 // indirect github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 // indirect - github.com/google/uuid v1.3.0 // indirect + github.com/google/uuid v1.3.1 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 // indirect - github.com/imdario/mergo v0.3.6 // indirect + github.com/imdario/mergo v0.3.13 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/mailru/easyjson v0.7.7 // indirect - github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/pkg/errors v0.9.1 // indirect - github.com/prometheus/common v0.44.0 // indirect + github.com/prometheus/common v0.48.0 // indirect github.com/prometheus/procfs v0.12.0 // indirect - github.com/spf13/pflag v1.0.5 // indirect - github.com/stoewer/go-strcase v1.2.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.44.0 // indirect - go.opentelemetry.io/otel v1.19.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.19.0 // indirect - go.opentelemetry.io/otel/metric v1.19.0 // indirect - go.opentelemetry.io/otel/sdk v1.19.0 // indirect - go.opentelemetry.io/otel/trace v1.19.0 // indirect + github.com/spf13/pflag v1.0.6-0.20210604193023-d5e0c0615ace // indirect + github.com/stoewer/go-strcase v1.3.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.0 // indirect + go.opentelemetry.io/otel v1.20.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.20.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.20.0 // indirect + go.opentelemetry.io/otel/metric v1.20.0 // indirect + go.opentelemetry.io/otel/sdk v1.20.0 // indirect + go.opentelemetry.io/otel/trace v1.20.0 // indirect go.opentelemetry.io/proto/otlp v1.0.0 // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/net v0.23.0 // indirect - golang.org/x/oauth2 v0.12.0 // indirect + golang.org/x/oauth2 v0.16.0 // indirect golang.org/x/sync v0.6.0 // indirect golang.org/x/term v0.18.0 // indirect golang.org/x/text v0.14.0 // indirect golang.org/x/time v0.3.0 // indirect golang.org/x/tools v0.18.0 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20230726155614-23370e0ffb3e // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20230822172742-b8732ec3820d // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d // indirect - google.golang.org/grpc v1.58.3 // indirect + google.golang.org/grpc v1.59.0 // indirect google.golang.org/protobuf v1.33.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect diff --git a/go.sum b/go.sum index 1e47c0243e..a5a2ef8c22 100644 --- a/go.sum +++ b/go.sum @@ -17,12 +17,12 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= -github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84= -github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U= +github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch/v5 v5.9.0 h1:kcBlZQbplgElYIlo/n1hJbls2z/1awpXxpRi0/FOJfg= github.com/evanphx/json-patch/v5 v5.9.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ= -github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk= -github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= @@ -42,11 +42,10 @@ github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEe github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang/glog v1.1.0 h1:/d3pCKDPWNnvIWe0vVUpNP32qc8U3PDVxySP/y360qE= -github.com/golang/glog v1.1.0/go.mod h1:pfYeQZ3JWZoXTV5sFc986z3HTpwQs9At6P4ImfuP3NQ= +github.com/golang/glog v1.1.2 h1:DVjP2PbBOzHyzA+dn3WhHIq4NdVu3Q+pvivFICf/7fo= +github.com/golang/glog v1.1.2/go.mod h1:zR+okUeTbrL6EL3xHUDxZuEtGv04p5shwip1+mL/rLQ= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= @@ -62,17 +61,21 @@ 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/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= -github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= +github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms= github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28= -github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= +github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/kcp-dev/apimachinery/v2 v2.0.0-alpha.0.0.20230926071920-57d168bcbe34 h1:tom0JX5OmAeOOmkGv8LaYHDtA1xAKDiQL5U0vhYYgdM= +github.com/kcp-dev/apimachinery/v2 v2.0.0-alpha.0.0.20230926071920-57d168bcbe34/go.mod h1:cWoaYGHl1nlzdEM2xvMzIASkEZJZLSf5nhe17M7wDhw= +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/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/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= @@ -84,8 +87,6 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= -github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= -github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -101,25 +102,24 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8= -github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc= -github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY= -github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU= -github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdOOfY= -github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY= +github.com/prometheus/client_golang v1.19.0 h1:ygXvpU1AoN1MhdzckN+PyD9QJOSD4x7kmXYlnfbA6JU= +github.com/prometheus/client_golang v1.19.0/go.mod h1:ZRM9uEAypZakd+q/x7+gmsvXdURP+DABIEIjnmDdp+k= +github.com/prometheus/client_model v0.6.0 h1:k1v3CzpSRUTrKMppY35TLwPvxHqBu0bYgxZzqGIgaos= +github.com/prometheus/client_model v0.6.0/go.mod h1:NTQHnmxFpouOD0DpvP4XujX3CdOAGQPoaGhyTchlyt8= +github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSzKKE= +github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc= github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/stoewer/go-strcase v1.2.0 h1:Z2iHWqGXH00XYgqDmNgQbIBxf3wrNq0F3feEy0ainaU= -github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= +github.com/spf13/pflag v1.0.6-0.20210604193023-d5e0c0615ace h1:9PNP1jnUjRhfmGMlkXHjYPishpcw4jpSt/V/xYY3FMA= +github.com/spf13/pflag v1.0.6-0.20210604193023-d5e0c0615ace/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stoewer/go-strcase v1.3.0 h1:g0eASXYtp+yvN9fK8sH94oCIk0fau9uV1/ZdJ0AVEzs= +github.com/stoewer/go-strcase v1.3.0/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= @@ -128,20 +128,20 @@ github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcU github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.44.0 h1:KfYpVmrjI7JuToy5k8XV3nkapjWx48k4E4JOtVstzQI= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.44.0/go.mod h1:SeQhzAEccGVZVEy7aH87Nh0km+utSpo1pTv6eMMop48= -go.opentelemetry.io/otel v1.19.0 h1:MuS/TNf4/j4IXsZuJegVzI1cwut7Qc00344rgH7p8bs= -go.opentelemetry.io/otel v1.19.0/go.mod h1:i0QyjOq3UPoTzff0PJB2N66fb4S0+rSbSB15/oyH9fY= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 h1:Mne5On7VWdx7omSrSSZvM4Kw7cS7NQkOOmLcgscI51U= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0/go.mod h1:IPtUMKL4O3tH5y+iXVyAXqpAwMuzC1IrxVS81rummfE= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.19.0 h1:3d+S281UTjM+AbF31XSOYn1qXn3BgIdWl8HNEpx08Jk= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.19.0/go.mod h1:0+KuTDyKL4gjKCF75pHOX4wuzYDUZYfAQdSu43o+Z2I= -go.opentelemetry.io/otel/metric v1.19.0 h1:aTzpGtV0ar9wlV4Sna9sdJyII5jTVJEvKETPiOKwvpE= -go.opentelemetry.io/otel/metric v1.19.0/go.mod h1:L5rUsV9kM1IxCj1MmSdS+JQAcVm319EUrDVLrt7jqt8= -go.opentelemetry.io/otel/sdk v1.19.0 h1:6USY6zH+L8uMH8L3t1enZPR3WFEmSTADlqldyHtJi3o= -go.opentelemetry.io/otel/sdk v1.19.0/go.mod h1:NedEbbS4w3C6zElbLdPJKOpJQOrGUJ+GfzpjUvI0v1A= -go.opentelemetry.io/otel/trace v1.19.0 h1:DFVQmlVbfVeOuBRrwdtaehRrWiL1JoVs9CPIQ1Dzxpg= -go.opentelemetry.io/otel/trace v1.19.0/go.mod h1:mfaSyvGyEJEI0nyV2I4qhNQnbBOUUmYZpYojqMnX2vo= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.0 h1:1eHu3/pUSWaOgltNK3WJFaywKsTIr/PwvHyDmi0lQA0= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.0/go.mod h1:HyABWq60Uy1kjJSa2BVOxUVao8Cdick5AWSKPutqy6U= +go.opentelemetry.io/otel v1.20.0 h1:vsb/ggIY+hUjD/zCAQHpzTmndPqv/ml2ArbsbfBYTAc= +go.opentelemetry.io/otel v1.20.0/go.mod h1:oUIGj3D77RwJdM6PPZImDpSZGDvkD9fhesHny69JFrs= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.20.0 h1:DeFD0VgTZ+Cj6hxravYYZE2W4GlneVH81iAOPjZkzk8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.20.0/go.mod h1:GijYcYmNpX1KazD5JmWGsi4P7dDTTTnfv1UbGn84MnU= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.20.0 h1:gvmNvqrPYovvyRmCSygkUDyL8lC5Tl845MLEwqpxhEU= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.20.0/go.mod h1:vNUq47TGFioo+ffTSnKNdob241vePmtNZnAODKapKd0= +go.opentelemetry.io/otel/metric v1.20.0 h1:ZlrO8Hu9+GAhnepmRGhSU7/VkpjrNowxRN9GyKR4wzA= +go.opentelemetry.io/otel/metric v1.20.0/go.mod h1:90DRw3nfK4D7Sm/75yQ00gTJxtkBxX+wu6YaNymbpVM= +go.opentelemetry.io/otel/sdk v1.20.0 h1:5Jf6imeFZlZtKv9Qbo6qt2ZkmWtdWx/wzcCbNUlAWGM= +go.opentelemetry.io/otel/sdk v1.20.0/go.mod h1:rmkSx1cZCm/tn16iWDn1GQbLtsW/LvsdEEFzCSRM6V0= +go.opentelemetry.io/otel/trace v1.20.0 h1:+yxVAPZPbQhbC3OfAkeIVTky6iTFpcr4SiY9om7mXSQ= +go.opentelemetry.io/otel/trace v1.20.0/go.mod h1:HJSK7F/hA5RlzpZ0zKDCHCDHm556LCDtKaAo6JmBFUU= go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I= go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= @@ -153,8 +153,8 @@ go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= 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/exp v0.0.0-20220722155223-a9213eeb770e h1:+WEEuIdZHnUeJJmEUjyYC2gfUMj69yZXw17EnHg/otA= -golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA= +golang.org/x/exp v0.0.0-20220827204233-334a2380cb91 h1:tnebWN09GYg9OLPss1KXj8txwZc6X6uMr6VFdcGNbHw= +golang.org/x/exp v0.0.0-20220827204233-334a2380cb91/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -164,9 +164,8 @@ golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= -golang.org/x/oauth2 v0.12.0 h1:smVPGxink+n1ZI5pkQa8y6fZT0RW0MgCO5bFpepy4B4= -golang.org/x/oauth2 v0.12.0/go.mod h1:A74bZ3aGXgCY0qaIC9Ahg6Lglin4AMAco8cIv9baba4= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/oauth2 v0.16.0 h1:aDkGMBSYxElaoP81NpoUoz2oo2R2wHdZpGToUxfyQrQ= +golang.org/x/oauth2 v0.16.0/go.mod h1:hqZ+0LWXsiVoZpeld6jVt06P3adbS2Uu911W1SsJv2o= 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= @@ -201,14 +200,14 @@ gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/genproto v0.0.0-20230803162519-f966b187b2e5 h1:L6iMMGrtzgHsWofoFcihmDEMYeDR9KN/ThbPWGrh++g= -google.golang.org/genproto v0.0.0-20230803162519-f966b187b2e5/go.mod h1:oH/ZOT02u4kWEp7oYBGYFFkCdKS/uYR9Z7+0/xuuFp8= -google.golang.org/genproto/googleapis/api v0.0.0-20230726155614-23370e0ffb3e h1:z3vDksarJxsAKM5dmEGv0GHwE2hKJ096wZra71Vs4sw= -google.golang.org/genproto/googleapis/api v0.0.0-20230726155614-23370e0ffb3e/go.mod h1:rsr7RhLuwsDKL7RmgDDCUc6yaGr1iqceVb5Wv6f6YvQ= +google.golang.org/genproto v0.0.0-20230822172742-b8732ec3820d h1:VBu5YqKPv6XiJ199exd8Br+Aetz+o08F+PLMnwJQHAY= +google.golang.org/genproto v0.0.0-20230822172742-b8732ec3820d/go.mod h1:yZTlhN0tQnXo3h00fuXNCxJdLdIdnVFVBaRJ5LWBbw4= +google.golang.org/genproto/googleapis/api v0.0.0-20230822172742-b8732ec3820d h1:DoPTO70H+bcDXcd39vOqb2viZxgqeBeSGtZ55yZU4/Q= +google.golang.org/genproto/googleapis/api v0.0.0-20230822172742-b8732ec3820d/go.mod h1:KjSP20unUpOx5kyQUFa7k4OJg0qeJ7DEZflGDu2p6Bk= google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d h1:uvYuEyMHKNt+lT4K3bN6fGswmK8qSvcreM3BwjDh+y4= google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d/go.mod h1:+Bk1OCOj40wS2hwAMA+aCW9ypzm63QTBBHp6lQ3p+9M= -google.golang.org/grpc v1.58.3 h1:BjnpXut1btbtgN/6sp+brB2Kbm2LjNXnidYujAVbSoQ= -google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= +google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk= +google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -216,11 +215,11 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntN gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= k8s.io/api v0.30.1 h1:kCm/6mADMdbAxmIh0LBjS54nQBE+U4KmbCfIkF5CpJY= @@ -247,5 +246,5 @@ sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMm sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= -sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= -sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= +sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= +sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= diff --git a/pkg/cache/cache.go b/pkg/cache/cache.go index 612dcca8b3..ef93a4bf7e 100644 --- a/pkg/cache/cache.go +++ b/pkg/cache/cache.go @@ -21,6 +21,7 @@ import ( "fmt" "net/http" "sort" + "strings" "time" "golang.org/x/exp/maps" @@ -169,6 +170,14 @@ type Options struct { // instead of `reconcile.Result{}`. SyncPeriod *time.Duration + // NewInformerFunc is a function that is used to create SharedIndexInformers. + // Defaults to cache.NewSharedIndexInformer from client-go + NewInformerFunc client.NewInformerFunc + + // Indexers is the indexers that the informers will be configured to use. + // Will always have the standard NamespaceIndex. + Indexers toolscache.Indexers + // ReaderFailOnMissingInformer configures the cache to return a ErrResourceNotCached error when a user // requests, using Get() and List(), a resource the cache does not already have an informer for. // @@ -225,9 +234,6 @@ type Options struct { // ByObject restricts the cache's ListWatch to the desired fields per GVK at the specified object. // If unset, this will fall through to the Default* settings. ByObject map[client.Object]ByObject - - // newInformer allows overriding of NewSharedIndexInformer for testing. - newInformer *func(toolscache.ListerWatcher, runtime.Object, time.Duration, toolscache.Indexers) toolscache.SharedIndexInformer } // ByObject offers more fine-grained control over the cache's ListWatch by object. @@ -398,9 +404,10 @@ func newCache(restConfig *rest.Config, opts Options) newCacheFunc { Transform: config.Transform, WatchErrorHandler: opts.DefaultWatchErrorHandler, UnsafeDisableDeepCopy: ptr.Deref(config.UnsafeDisableDeepCopy, false), - NewInformer: opts.newInformer, + NewInformer: opts.NewInformerFunc, }), readerFailOnMissingInformer: opts.ReaderFailOnMissingInformer, + clusterIndexes: strings.HasSuffix(restConfig.Host, "/clusters/*"), } } } @@ -507,6 +514,10 @@ func defaultOpts(config *rest.Config, opts Options) (Options, error) { if opts.SyncPeriod == nil { opts.SyncPeriod = &defaultSyncPeriod } + + if opts.NewInformerFunc == nil { + opts.NewInformerFunc = toolscache.NewSharedIndexInformer + } return opts, nil } diff --git a/pkg/cache/cache_test.go b/pkg/cache/cache_test.go index 7a21c87c37..1b37a2d94f 100644 --- a/pkg/cache/cache_test.go +++ b/pkg/cache/cache_test.go @@ -544,14 +544,9 @@ func NonBlockingGetTest(createCacheFunc func(config *rest.Config, opts cache.Opt Expect(err).NotTo(HaveOccurred()) By("creating the informer cache") - v := reflect.ValueOf(&opts).Elem() - newInformerField := v.FieldByName("newInformer") - newFakeInformer := func(_ kcache.ListerWatcher, _ runtime.Object, _ time.Duration, _ kcache.Indexers) kcache.SharedIndexInformer { + opts.NewInformerFunc = func(_ kcache.ListerWatcher, _ runtime.Object, _ time.Duration, _ kcache.Indexers) kcache.SharedIndexInformer { return &controllertest.FakeInformer{Synced: false} } - reflect.NewAt(newInformerField.Type(), newInformerField.Addr().UnsafePointer()). - Elem(). - Set(reflect.ValueOf(&newFakeInformer)) informerCache, err = createCacheFunc(cfg, opts) Expect(err).NotTo(HaveOccurred()) By("running the cache and waiting for it to sync") diff --git a/pkg/cache/defaulting_test.go b/pkg/cache/defaulting_test.go index 3c01bf8404..930a1535ba 100644 --- a/pkg/cache/defaulting_test.go +++ b/pkg/cache/defaulting_test.go @@ -401,6 +401,9 @@ func TestDefaultOpts(t *testing.T) { t.Fatal(err) } + // We cannot reference kcp.NewInformerWithClusterIndexes due to import cycle. + defaulted.NewInformerFunc = nil + if diff := tc.verification(defaulted); diff != "" { t.Errorf("expected config differs from actual: %s", diff) } diff --git a/pkg/cache/informer_cache.go b/pkg/cache/informer_cache.go index 091667b7fa..71b94da174 100644 --- a/pkg/cache/informer_cache.go +++ b/pkg/cache/informer_cache.go @@ -31,6 +31,8 @@ import ( "sigs.k8s.io/controller-runtime/pkg/cache/internal" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/apiutil" + + "github.com/kcp-dev/logicalcluster/v3" ) var ( @@ -68,6 +70,7 @@ type informerCache struct { scheme *runtime.Scheme *internal.Informers readerFailOnMissingInformer bool + clusterIndexes bool } // Get implements Reader. @@ -217,10 +220,10 @@ func (ic *informerCache) IndexField(ctx context.Context, obj client.Object, fiel if err != nil { return err } - return indexByField(informer, field, extractValue) + return indexByField(informer, field, extractValue, ic.clusterIndexes) } -func indexByField(informer Informer, field string, extractValue client.IndexerFunc) error { +func indexByField(informer Informer, field string, extractValue client.IndexerFunc, clusterIndexes bool) error { indexFunc := func(objRaw interface{}) ([]string, error) { // TODO(directxman12): check if this is the correct type? obj, isObj := objRaw.(client.Object) @@ -233,6 +236,13 @@ func indexByField(informer Informer, field string, extractValue client.IndexerFu } ns := meta.GetNamespace() + keyFunc := internal.KeyToNamespacedKey + if clusterName := logicalcluster.From(obj); clusterIndexes && !clusterName.Empty() { + keyFunc = func(ns, val string) string { + return internal.KeyToClusteredKey(clusterName.String(), ns, val) + } + } + rawVals := extractValue(obj) var vals []string if ns == "" { @@ -242,14 +252,15 @@ func indexByField(informer Informer, field string, extractValue client.IndexerFu // if we need to add non-namespaced versions too, double the length vals = make([]string, len(rawVals)*2) } + for i, rawVal := range rawVals { // save a namespaced variant, so that we can ask // "what are all the object matching a given index *in a given namespace*" - vals[i] = internal.KeyToNamespacedKey(ns, rawVal) + vals[i] = keyFunc(ns, rawVal) if ns != "" { // if we have a namespace, also inject a special index key for listing // regardless of the object namespace - vals[i+len(rawVals)] = internal.KeyToNamespacedKey("", rawVal) + vals[i+len(rawVals)] = keyFunc("", rawVal) } } diff --git a/pkg/cache/internal/cache_reader.go b/pkg/cache/internal/cache_reader.go index 81ee960b73..aaf0966f5a 100644 --- a/pkg/cache/internal/cache_reader.go +++ b/pkg/cache/internal/cache_reader.go @@ -21,6 +21,9 @@ import ( "fmt" "reflect" + kcpcache "github.com/kcp-dev/apimachinery/v2/pkg/cache" + "github.com/kcp-dev/logicalcluster/v3" + apierrors "k8s.io/apimachinery/pkg/api/errors" apimeta "k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/fields" @@ -31,6 +34,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/internal/field/selector" + "sigs.k8s.io/controller-runtime/pkg/kontext" ) // CacheReader is a client.Reader. @@ -54,12 +58,22 @@ type CacheReader struct { } // Get checks the indexer for the object and writes a copy of it if found. -func (c *CacheReader) Get(_ context.Context, key client.ObjectKey, out client.Object, _ ...client.GetOption) error { +func (c *CacheReader) Get(ctx context.Context, key client.ObjectKey, out client.Object, _ ...client.GetOption) error { if c.scopeName == apimeta.RESTScopeNameRoot { key.Namespace = "" } storeKey := objectKeyToStoreKey(key) + // create cluster-aware key for KCP + _, isClusterAware := c.indexer.GetIndexers()[kcpcache.ClusterAndNamespaceIndexName] + clusterName, _ := kontext.ClusterFrom(ctx) + if isClusterAware && clusterName.Empty() { + return fmt.Errorf("cluster-aware cache requires a cluster in context") + } + if isClusterAware { + storeKey = clusterName.String() + "|" + storeKey + } + // Lookup the object from the indexer cache obj, exists, err := c.indexer.GetByKey(storeKey) if err != nil { @@ -105,7 +119,7 @@ func (c *CacheReader) Get(_ context.Context, key client.ObjectKey, out client.Ob } // List lists items out of the indexer and writes them to out. -func (c *CacheReader) List(_ context.Context, out client.ObjectList, opts ...client.ListOption) error { +func (c *CacheReader) List(ctx context.Context, out client.ObjectList, opts ...client.ListOption) error { var objs []interface{} var err error @@ -116,6 +130,9 @@ func (c *CacheReader) List(_ context.Context, out client.ObjectList, opts ...cli return fmt.Errorf("continue list option is not supported by the cache") } + _, isClusterAware := c.indexer.GetIndexers()[kcpcache.ClusterAndNamespaceIndexName] + clusterName, _ := kontext.ClusterFrom(ctx) + switch { case listOpts.FieldSelector != nil: requiresExact := selector.RequiresExactMatch(listOpts.FieldSelector) @@ -125,11 +142,19 @@ func (c *CacheReader) List(_ context.Context, out client.ObjectList, opts ...cli // list all objects by the field selector. If this is namespaced and we have one, ask for the // namespaced index key. Otherwise, ask for the non-namespaced variant by using the fake "all namespaces" // namespace. - objs, err = byIndexes(c.indexer, listOpts.FieldSelector.Requirements(), listOpts.Namespace) + objs, err = byIndexes(c.indexer, listOpts.FieldSelector.Requirements(), clusterName, listOpts.Namespace) case listOpts.Namespace != "": - objs, err = c.indexer.ByIndex(cache.NamespaceIndex, listOpts.Namespace) + if isClusterAware && !clusterName.Empty() { + objs, err = c.indexer.ByIndex(kcpcache.ClusterAndNamespaceIndexName, kcpcache.ClusterAndNamespaceIndexKey(clusterName, listOpts.Namespace)) + } else { + objs, err = c.indexer.ByIndex(cache.NamespaceIndex, listOpts.Namespace) + } default: - objs = c.indexer.List() + if isClusterAware && !clusterName.Empty() { + objs, err = c.indexer.ByIndex(kcpcache.ClusterIndexName, kcpcache.ClusterIndexKey(clusterName)) + } else { + objs = c.indexer.List() + } } if err != nil { return err @@ -177,16 +202,22 @@ func (c *CacheReader) List(_ context.Context, out client.ObjectList, opts ...cli return apimeta.SetList(out, runtimeObjs) } -func byIndexes(indexer cache.Indexer, requires fields.Requirements, namespace string) ([]interface{}, error) { +func byIndexes(indexer cache.Indexer, requires fields.Requirements, clusterName logicalcluster.Name, namespace string) ([]interface{}, error) { var ( err error objs []interface{} vals []string ) indexers := indexer.GetIndexers() + _, isClusterAware := indexers[kcpcache.ClusterAndNamespaceIndexName] for idx, req := range requires { indexName := FieldIndexName(req.Field) - indexedValue := KeyToNamespacedKey(namespace, req.Value) + var indexedValue string + if isClusterAware { + indexedValue = KeyToClusteredKey(clusterName.String(), namespace, req.Value) + } else { + indexedValue = KeyToNamespacedKey(namespace, req.Value) + } if idx == 0 { // we use first require to get snapshot data // TODO(halfcrazy): use complicated index when client-go provides byIndexes @@ -253,3 +284,9 @@ func KeyToNamespacedKey(ns string, baseKey string) string { } return allNamespacesNamespace + "/" + baseKey } + +// KeyToClusteredKey prefixes the given index key with a cluster name +// for use in field selector indexes. +func KeyToClusteredKey(clusterName string, ns string, baseKey string) string { + return clusterName + "|" + KeyToNamespacedKey(ns, baseKey) +} diff --git a/pkg/cache/internal/informers.go b/pkg/cache/internal/informers.go index cd8c6774ca..ae10b2f600 100644 --- a/pkg/cache/internal/informers.go +++ b/pkg/cache/internal/informers.go @@ -47,7 +47,7 @@ type InformersOpts struct { Mapper meta.RESTMapper ResyncPeriod time.Duration Namespace string - NewInformer *func(cache.ListerWatcher, runtime.Object, time.Duration, cache.Indexers) cache.SharedIndexInformer + NewInformer func(cache.ListerWatcher, runtime.Object, time.Duration, cache.Indexers) cache.SharedIndexInformer Selector Selector Transform cache.TransformFunc UnsafeDisableDeepCopy bool @@ -58,7 +58,7 @@ type InformersOpts struct { func NewInformers(config *rest.Config, options *InformersOpts) *Informers { newInformer := cache.NewSharedIndexInformer if options.NewInformer != nil { - newInformer = *options.NewInformer + newInformer = options.NewInformer } return &Informers{ config: config, diff --git a/pkg/cache/kcp_test.go b/pkg/cache/kcp_test.go new file mode 100644 index 0000000000..11c48457e9 --- /dev/null +++ b/pkg/cache/kcp_test.go @@ -0,0 +1,138 @@ +/* +Copyright 2024 The Kubernetes 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. +*/ + +package cache_test + +import ( + "context" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/fields" + "sigs.k8s.io/controller-runtime/pkg/cache" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +var _ = Describe("informer cache against a kube cluster", func() { + BeforeEach(func() { + By("Annotating the default namespace with kcp.io/cluster") + cl, err := client.New(cfg, client.Options{}) + Expect(err).NotTo(HaveOccurred()) + ns := &corev1.Namespace{} + err = cl.Get(context.Background(), client.ObjectKey{Name: "default"}, ns) + Expect(err).NotTo(HaveOccurred()) + ns.Annotations = map[string]string{"kcp.io/cluster": "cluster1"} + err = cl.Update(context.Background(), ns) + Expect(err).NotTo(HaveOccurred()) + }) + + Describe("KCP cluster-unaware informer cache", func() { + // Test whether we can have a cluster-unaware informer cache against a single workspace. + // I.e. every object has a kcp.io/cluster annotation, but it should not be taken + // into consideration by the cache to compute the key. + It("should be able to get the default namespace despite kcp.io/cluster annotation", func() { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + c, err := cache.New(cfg, cache.Options{}) + Expect(err).NotTo(HaveOccurred()) + + go c.Start(ctx) //nolint:errcheck // Start is blocking, and error not relevant here. + c.WaitForCacheSync(ctx) + + By("By getting the default namespace with the informer") + ns := &corev1.Namespace{} + err = c.Get(ctx, client.ObjectKey{Name: "default"}, ns) + Expect(err).NotTo(HaveOccurred()) + }) + + It("should support indexes with cluster-less keys", func() { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + c, err := cache.New(cfg, cache.Options{}) + Expect(err).NotTo(HaveOccurred()) + + By("Indexing the default namespace by name") + err = c.IndexField(ctx, &corev1.Namespace{}, "name-clusterless", func(obj client.Object) []string { + return []string{"key-" + obj.GetName()} + }) + Expect(err).NotTo(HaveOccurred()) + + go c.Start(ctx) //nolint:errcheck // Start is blocking, and error not relevant here. + c.WaitForCacheSync(ctx) + + By("By getting the default namespace via the custom index") + nss := &corev1.NamespaceList{} + err = c.List(ctx, nss, client.MatchingFieldsSelector{ + Selector: fields.OneTermEqualSelector("name-clusterless", "key-default"), + }) + Expect(err).NotTo(HaveOccurred()) + Expect(nss.Items).To(HaveLen(1)) + }) + }) + + // TODO: get envtest in place with kcp + /* + Describe("KCP cluster-aware informer cache", func() { + It("should be able to get the default namespace with kcp.io/cluster annotation", func() { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + c, err := kcp.NewClusterAwareCache(cfg, cache.Options{}) + Expect(err).NotTo(HaveOccurred()) + + go c.Start(ctx) //nolint:errcheck // Start is blocking, and error not relevant here. + c.WaitForCacheSync(ctx) + + By("By getting the default namespace with the informer, but cluster-less key should fail") + ns := &corev1.Namespace{} + err = c.Get(ctx, client.ObjectKey{Name: "default"}, ns) + Expect(err).To(HaveOccurred()) + + By("By getting the default namespace with the informer, but cluster-aware key should succeed") + err = c.Get(kontext.WithCluster(ctx, "cluster1"), client.ObjectKey{Name: "default", Namespace: "cluster1"}, ns) + Expect(err).NotTo(HaveOccurred()) + }) + + It("should support indexes with cluster-aware keys", func() { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + c, err := kcp.NewClusterAwareCache(cfg, cache.Options{}) + Expect(err).NotTo(HaveOccurred()) + + By("Indexing the default namespace by name") + err = c.IndexField(ctx, &corev1.Namespace{}, "name-clusteraware", func(obj client.Object) []string { + return []string{"key-" + obj.GetName()} + }) + Expect(err).NotTo(HaveOccurred()) + + go c.Start(ctx) //nolint:errcheck // Start is blocking, and error not relevant here. + c.WaitForCacheSync(ctx) + + By("By getting the default namespace via the custom index") + nss := &corev1.NamespaceList{} + err = c.List(ctx, nss, client.MatchingFieldsSelector{ + Selector: fields.OneTermEqualSelector("name-clusteraware", "key-default"), + }) + Expect(err).NotTo(HaveOccurred()) + Expect(nss.Items).To(HaveLen(1)) + }) + }) + */ +}) diff --git a/pkg/client/client.go b/pkg/client/client.go index e6c075eb00..a3eb0ff5e2 100644 --- a/pkg/client/client.go +++ b/pkg/client/client.go @@ -89,6 +89,14 @@ type CacheOptions struct { // NewClientFunc allows a user to define how to create a client. type NewClientFunc func(config *rest.Config, options Options) (Client, error) +// NewAPIReaderFunc allows a user to define how to create an API server reader. +type NewAPIReaderFunc func(config *rest.Config, options Options) (Reader, error) + +// NewAPIReader creates a new API server reader. +func NewAPIReader(config *rest.Config, options Options) (Reader, error) { + return New(config, options) +} + // New returns a new Client using the provided config and Options. // // The client's read behavior is determined by Options.Cache. diff --git a/pkg/client/interfaces.go b/pkg/client/interfaces.go index 3cd745e4c0..6c47fc35b8 100644 --- a/pkg/client/interfaces.go +++ b/pkg/client/interfaces.go @@ -18,9 +18,11 @@ package client import ( "context" + "time" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/client-go/tools/cache" "k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/runtime" @@ -44,6 +46,10 @@ type Patch interface { Data(obj Object) ([]byte, error) } +// NewInformerFunc describes a function that creates SharedIndexInformers. +// Its signature matches cache.NewSharedIndexInformer from client-go. +type NewInformerFunc func(cache.ListerWatcher, runtime.Object, time.Duration, cache.Indexers) cache.SharedIndexInformer + // TODO(directxman12): is there a sane way to deal with get/delete options? // Reader knows how to read and list Kubernetes objects. diff --git a/pkg/cluster/cluster.go b/pkg/cluster/cluster.go index 248893ea31..8836658d2b 100644 --- a/pkg/cluster/cluster.go +++ b/pkg/cluster/cluster.go @@ -121,6 +121,11 @@ type Options struct { // By default, the client will use the cache for reads and direct calls for writes. Client client.Options + // NewAPIReaderFunc is the function that creates the APIReader client to be + // used by the manager. If not set this will use the default new APIReader + // function. + NewAPIReader client.NewAPIReaderFunc + // NewClient is the func that creates the client to be used by the manager. // If not set this will create a Client backed by a Cache for read operations // and a direct Client for write operations. @@ -230,7 +235,7 @@ func New(config *rest.Config, opts ...Option) (Cluster, error) { } // Create the API Reader, a client with no cache. - clientReader, err := client.New(config, client.Options{ + clientReader, err := options.NewAPIReader(config, client.Options{ HTTPClient: options.HTTPClient, Scheme: options.Scheme, Mapper: mapper, @@ -280,6 +285,10 @@ func setOptionsDefaults(options Options, config *rest.Config) (Options, error) { options.MapperProvider = apiutil.NewDynamicRESTMapper } + if options.NewAPIReader == nil { + options.NewAPIReader = client.NewAPIReader + } + // Allow users to define how to create a new client if options.NewClient == nil { options.NewClient = client.New diff --git a/pkg/handler/enqueue.go b/pkg/handler/enqueue.go index c9c7693854..a01cce967b 100644 --- a/pkg/handler/enqueue.go +++ b/pkg/handler/enqueue.go @@ -20,6 +20,8 @@ import ( "context" "reflect" + "github.com/kcp-dev/logicalcluster/v3" + "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/util/workqueue" "sigs.k8s.io/controller-runtime/pkg/client" @@ -52,25 +54,16 @@ func (e *TypedEnqueueRequestForObject[T]) Create(ctx context.Context, evt event. enqueueLog.Error(nil, "CreateEvent received with no metadata", "event", evt) return } - q.Add(reconcile.Request{NamespacedName: types.NamespacedName{ - Name: evt.Object.GetName(), - Namespace: evt.Object.GetNamespace(), - }}) + q.Add(request(evt.Object)) } // Update implements EventHandler. func (e *TypedEnqueueRequestForObject[T]) Update(ctx context.Context, evt event.TypedUpdateEvent[T], q workqueue.RateLimitingInterface) { switch { case !isNil(evt.ObjectNew): - q.Add(reconcile.Request{NamespacedName: types.NamespacedName{ - Name: evt.ObjectNew.GetName(), - Namespace: evt.ObjectNew.GetNamespace(), - }}) + q.Add(request(evt.ObjectNew)) case !isNil(evt.ObjectOld): - q.Add(reconcile.Request{NamespacedName: types.NamespacedName{ - Name: evt.ObjectOld.GetName(), - Namespace: evt.ObjectOld.GetNamespace(), - }}) + q.Add(request(evt.ObjectOld)) default: enqueueLog.Error(nil, "UpdateEvent received with no metadata", "event", evt) } @@ -82,10 +75,7 @@ func (e *TypedEnqueueRequestForObject[T]) Delete(ctx context.Context, evt event. enqueueLog.Error(nil, "DeleteEvent received with no metadata", "event", evt) return } - q.Add(reconcile.Request{NamespacedName: types.NamespacedName{ - Name: evt.Object.GetName(), - Namespace: evt.Object.GetNamespace(), - }}) + q.Add(request(evt.Object)) } // Generic implements EventHandler. @@ -94,10 +84,18 @@ func (e *TypedEnqueueRequestForObject[T]) Generic(ctx context.Context, evt event enqueueLog.Error(nil, "GenericEvent received with no metadata", "event", evt) return } - q.Add(reconcile.Request{NamespacedName: types.NamespacedName{ - Name: evt.Object.GetName(), - Namespace: evt.Object.GetNamespace(), - }}) + q.Add(request(evt.Object)) +} + +func request(obj client.Object) reconcile.Request { + return reconcile.Request{ + // TODO(kcp) Need to implement a non-kcp-specific way to support this + ClusterName: logicalcluster.From(obj).String(), + NamespacedName: types.NamespacedName{ + Namespace: obj.GetNamespace(), + Name: obj.GetName(), + }, + } } func isNil(arg any) bool { diff --git a/pkg/handler/enqueue_owner.go b/pkg/handler/enqueue_owner.go index 052a3140e1..52d676944f 100644 --- a/pkg/handler/enqueue_owner.go +++ b/pkg/handler/enqueue_owner.go @@ -20,6 +20,8 @@ import ( "context" "fmt" + "github.com/kcp-dev/logicalcluster/v3" + "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" @@ -181,9 +183,12 @@ func (e *enqueueRequestForOwner[T]) getOwnerReconcileRequest(object metav1.Objec // object in the event. if ref.Kind == e.groupKind.Kind && refGV.Group == e.groupKind.Group { // Match found - add a Request for the object referred to in the OwnerReference - request := reconcile.Request{NamespacedName: types.NamespacedName{ - Name: ref.Name, - }} + request := reconcile.Request{ + ClusterName: logicalcluster.From(object).String(), + NamespacedName: types.NamespacedName{ + Name: ref.Name, + }, + } // if owner is not namespaced then we should not set the namespace mapping, err := e.mapper.RESTMapping(e.groupKind, refGV.Version) diff --git a/pkg/kcp/helper.go b/pkg/kcp/helper.go new file mode 100644 index 0000000000..85d3900127 --- /dev/null +++ b/pkg/kcp/helper.go @@ -0,0 +1,34 @@ +/* +Copyright 2024 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. +*/ + +package kcp + +import ( + "context" + + "github.com/kcp-dev/logicalcluster/v3" + "sigs.k8s.io/controller-runtime/pkg/kontext" + "sigs.k8s.io/controller-runtime/pkg/reconcile" +) + +// WithClusterInContext injects a cluster name into a context such that +// cluster clients and cache work out of the box. +func WithClusterInContext(r reconcile.Reconciler) reconcile.Reconciler { + return reconcile.Func(func(ctx context.Context, req reconcile.Request) (reconcile.Result, error) { + ctx = kontext.WithCluster(ctx, logicalcluster.Name(req.ClusterName)) + return r.Reconcile(ctx, req) + }) +} diff --git a/pkg/kcp/wrappers.go b/pkg/kcp/wrappers.go new file mode 100644 index 0000000000..597be213c2 --- /dev/null +++ b/pkg/kcp/wrappers.go @@ -0,0 +1,250 @@ +/* +Copyright 2022 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. +*/ + +package kcp + +import ( + "fmt" + "net/http" + "regexp" + "strings" + "time" + + "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/rest" + k8scache "k8s.io/client-go/tools/cache" + + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/cache" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/apiutil" + "sigs.k8s.io/controller-runtime/pkg/kontext" + "sigs.k8s.io/controller-runtime/pkg/manager" + + kcpcache "github.com/kcp-dev/apimachinery/v2/pkg/cache" + "github.com/kcp-dev/apimachinery/v2/third_party/informers" + "github.com/kcp-dev/logicalcluster/v3" +) + +// NewClusterAwareManager returns a kcp-aware manager with appropriate defaults for cache and +// client creation. +func NewClusterAwareManager(cfg *rest.Config, options ctrl.Options) (manager.Manager, error) { + if options.NewCache == nil { + options.NewCache = NewClusterAwareCache + } + + if options.NewAPIReader == nil { + options.NewAPIReader = NewClusterAwareAPIReader + } + + if options.NewClient == nil { + options.NewClient = NewClusterAwareClient + } + + if options.MapperProvider == nil { + options.MapperProvider = NewClusterAwareMapperProvider + } + + cfg.Wrap(func(rt http.RoundTripper) http.RoundTripper { + return newClusterRoundTripper(rt) + }) + return ctrl.NewManager(cfg, options) +} + +// NewInformerWithClusterIndexes returns a SharedIndexInformer that is configured +// ClusterIndexName and ClusterAndNamespaceIndexName indexes. +func NewInformerWithClusterIndexes(lw k8scache.ListerWatcher, obj runtime.Object, syncPeriod time.Duration, indexers k8scache.Indexers) k8scache.SharedIndexInformer { + indexers[kcpcache.ClusterIndexName] = kcpcache.ClusterIndexFunc + indexers[kcpcache.ClusterAndNamespaceIndexName] = kcpcache.ClusterAndNamespaceIndexFunc + + return informers.NewSharedIndexInformer(lw, obj, syncPeriod, indexers) +} + +// NewClusterAwareCache returns a cache.Cache that handles multi-cluster watches. +func NewClusterAwareCache(config *rest.Config, opts cache.Options) (cache.Cache, error) { + c := rest.CopyConfig(config) + c.Host += "/clusters/*" + + opts.NewInformerFunc = NewInformerWithClusterIndexes + return cache.New(c, opts) +} + +// NewClusterAwareAPIReader returns a client.Reader that provides read-only access to the API server, +// and is configured to use the context to scope requests to the proper cluster. To scope requests, +// pass the request context with the cluster set. +// Example: +// +// import ( +// "context" +// kcpclient "github.com/kcp-dev/apimachinery/v2/pkg/client" +// ctrl "sigs.k8s.io/controller-runtime" +// ) +// func (r *reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { +// ctx = kcpclient.WithCluster(ctx, req.ObjectKey.Cluster) +// // from here on pass this context to all client calls +// ... +// } +func NewClusterAwareAPIReader(config *rest.Config, opts client.Options) (client.Reader, error) { + httpClient, err := ClusterAwareHTTPClient(config) + if err != nil { + return nil, err + } + opts.HTTPClient = httpClient + return client.NewAPIReader(config, opts) +} + +// NewClusterAwareClient returns a client.Client that is configured to use the context +// to scope requests to the proper cluster. To scope requests, pass the request context with the cluster set. +// Example: +// +// import ( +// "context" +// kcpclient "github.com/kcp-dev/apimachinery/v2/pkg/client" +// ctrl "sigs.k8s.io/controller-runtime" +// ) +// func (r *reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { +// ctx = kcpclient.WithCluster(ctx, req.ObjectKey.Cluster) +// // from here on pass this context to all client calls +// ... +// } +func NewClusterAwareClient(config *rest.Config, opts client.Options) (client.Client, error) { + httpClient, err := ClusterAwareHTTPClient(config) + if err != nil { + return nil, err + } + opts.HTTPClient = httpClient + return client.New(config, opts) +} + +// NewClusterAwareClientForConfig returns a client.Client that is configured to use the context to scope +// requests to the proper cluster. To scope requests, pass the request context with the cluster set. +// Example: +// +// import ( +// "context" +// kcpclient "github.com/kcp-dev/apimachinery/v2/pkg/client" +// ctrl "sigs.k8s.io/controller-runtime" +// ) +// func (r *reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { +// ctx = kcpclient.WithCluster(ctx, req.ObjectKey.Cluster) +// // from here on pass this context to all client calls +// ... +// } +func NewClusterAwareClientForConfig(config *rest.Config, httpClient *http.Client) (client.Client, error) { + restMapper, err := NewClusterAwareMapperProvider(config, httpClient) + if err != nil { + return nil, err + } + return client.New(config, client.Options{ + Mapper: restMapper, + HTTPClient: httpClient, + }) +} + +// ClusterAwareHTTPClient returns an http.Client with a cluster aware round tripper. +func ClusterAwareHTTPClient(config *rest.Config) (*http.Client, error) { + httpClient, err := rest.HTTPClientFor(config) + if err != nil { + return nil, err + } + + httpClient.Transport = newClusterRoundTripper(httpClient.Transport) + return httpClient, nil +} + +// NewClusterAwareMapperProvider is a MapperProvider that returns a logical cluster aware meta.RESTMapper. +func NewClusterAwareMapperProvider(c *rest.Config, httpClient *http.Client) (meta.RESTMapper, error) { + mapperCfg := rest.CopyConfig(c) + if !strings.HasSuffix(mapperCfg.Host, "/clusters/*") { + mapperCfg.Host += "/clusters/*" + } + + return apiutil.NewDynamicRESTMapper(mapperCfg, httpClient) +} + +// ClusterAwareBuilderWithOptions returns a cluster aware Cache constructor that will build +// a cache honoring the options argument, this is useful to specify options like +// SelectorsDefaultNamespaces +// WARNING: If SelectorsByObject is specified, filtered out resources are not +// returned. +// WARNING: If UnsafeDisableDeepCopy is enabled, you must DeepCopy any object +// returned from cache get/list before mutating it. +func ClusterAwareBuilderWithOptions(options cache.Options) cache.NewCacheFunc { + return func(config *rest.Config, opts cache.Options) (cache.Cache, error) { + if options.Scheme == nil { + options.Scheme = opts.Scheme + } + if options.Mapper == nil { + options.Mapper = opts.Mapper + } + if options.SyncPeriod == nil { + options.SyncPeriod = opts.SyncPeriod + } + if opts.DefaultNamespaces == nil { + opts.DefaultNamespaces = options.DefaultNamespaces + } + + return NewClusterAwareCache(config, options) + } +} + +// clusterRoundTripper is a cluster aware wrapper around http.RoundTripper. +type clusterRoundTripper struct { + delegate http.RoundTripper +} + +// newClusterRoundTripper creates a new cluster aware round tripper. +func newClusterRoundTripper(delegate http.RoundTripper) *clusterRoundTripper { + return &clusterRoundTripper{ + delegate: delegate, + } +} + +func (c *clusterRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { + cluster, ok := kontext.ClusterFrom(req.Context()) + if ok { + req = req.Clone(req.Context()) + req.URL.Path = generatePath(req.URL.Path, cluster.Path()) + req.URL.RawPath = generatePath(req.URL.RawPath, cluster.Path()) + } + return c.delegate.RoundTrip(req) +} + +// apiRegex matches any string that has /api/ or /apis/ in it. +var apiRegex = regexp.MustCompile(`(/api/|/apis/)`) + +// generatePath formats the request path to target the specified cluster. +func generatePath(originalPath string, clusterPath logicalcluster.Path) string { + // If the originalPath already has cluster.Path() then the path was already modifed and no change needed + if strings.Contains(originalPath, clusterPath.RequestPath()) { + return originalPath + } + // If the originalPath has /api/ or /apis/ in it, it might be anywhere in the path, so we use a regex to find and + // replaces /api/ or /apis/ with $cluster/api/ or $cluster/apis/ + if apiRegex.MatchString(originalPath) { + return apiRegex.ReplaceAllString(originalPath, fmt.Sprintf("%s$1", clusterPath.RequestPath())) + } + // Otherwise, we're just prepending /clusters/$name + path := clusterPath.RequestPath() + // if the original path is relative, add a / separator + if len(originalPath) > 0 && originalPath[0] != '/' { + path += "/" + } + // finally append the original path + path += originalPath + return path +} diff --git a/pkg/kontext/kontext.go b/pkg/kontext/kontext.go new file mode 100644 index 0000000000..4de151a742 --- /dev/null +++ b/pkg/kontext/kontext.go @@ -0,0 +1,24 @@ +package kontext + +import ( + "context" + + "github.com/kcp-dev/logicalcluster/v3" +) + +type key int + +const ( + keyCluster key = iota +) + +// WithCluster injects a cluster name into a context. +func WithCluster(ctx context.Context, cluster logicalcluster.Name) context.Context { + return context.WithValue(ctx, keyCluster, cluster) +} + +// ClusterFrom extracts a cluster name from the context. +func ClusterFrom(ctx context.Context) (logicalcluster.Name, bool) { + s, ok := ctx.Value(keyCluster).(logicalcluster.Name) + return s, ok +} diff --git a/pkg/manager/manager.go b/pkg/manager/manager.go index 3166f4818f..6abc0b0412 100644 --- a/pkg/manager/manager.go +++ b/pkg/manager/manager.go @@ -126,6 +126,11 @@ type Options struct { // Only use a custom NewCache if you know what you are doing. NewCache cache.NewCacheFunc + // NewAPIReaderFunc is the function that creates the APIReader client to be + // used by the manager. If not set this will use the default new APIReader + // function. + NewAPIReader client.NewAPIReaderFunc + // Client is the client.Options that will be used to create the default Client. // By default, the client will use the cache for reads and direct calls for writes. Client client.Options @@ -330,6 +335,7 @@ func New(config *rest.Config, options Options) (Manager, error) { clusterOptions.MapperProvider = options.MapperProvider clusterOptions.Logger = options.Logger clusterOptions.NewCache = options.NewCache + clusterOptions.NewAPIReader = options.NewAPIReader clusterOptions.NewClient = options.NewClient clusterOptions.Cache = options.Cache clusterOptions.Client = options.Client diff --git a/pkg/reconcile/reconcile.go b/pkg/reconcile/reconcile.go index f1cce87c85..9a545f2de6 100644 --- a/pkg/reconcile/reconcile.go +++ b/pkg/reconcile/reconcile.go @@ -50,6 +50,10 @@ func (r *Result) IsZero() bool { type Request struct { // NamespacedName is the name and namespace of the object to reconcile. types.NamespacedName + + // ClusterName can be used for reconciling requests across multiple clusters, + // to prevent objects with the same name and namespace from conflicting + ClusterName string } /*