From d2ded664837a1faa249cc73cb7d716aa6921c73b Mon Sep 17 00:00:00 2001 From: Huy Mai Date: Thu, 26 Sep 2024 11:18:57 +0300 Subject: [PATCH] Add Metal3 Fake API Server (FKAS) Signed-off-by: Huy Mai --- .github/dependabot.yml | 1 + .../workflows/build-fkas-images-action.yml | 25 + Makefile | 14 +- docs/releasing.md | 4 +- hack/fake-apiserver/Dockerfile | 65 ++ hack/fake-apiserver/README.md | 183 ++++++ .../cmd/metal3-fkas-reconciler/main.go | 161 +++++ hack/fake-apiserver/cmd/metal3-fkas/main.go | 616 ++++++++++++++++++ hack/fake-apiserver/cmd/metal3-fkas/utils.go | 179 +++++ hack/fake-apiserver/go.mod | 112 ++++ hack/fake-apiserver/go.sum | 326 +++++++++ .../k8s/metal3-fkas-system.yaml | 71 ++ hack/fake-apiserver/k8s/metal3-fkas.yaml | 61 ++ hack/verify-release.sh | 2 +- 14 files changed, 1816 insertions(+), 4 deletions(-) create mode 100644 .github/workflows/build-fkas-images-action.yml create mode 100644 hack/fake-apiserver/Dockerfile create mode 100644 hack/fake-apiserver/README.md create mode 100644 hack/fake-apiserver/cmd/metal3-fkas-reconciler/main.go create mode 100644 hack/fake-apiserver/cmd/metal3-fkas/main.go create mode 100644 hack/fake-apiserver/cmd/metal3-fkas/utils.go create mode 100644 hack/fake-apiserver/go.mod create mode 100644 hack/fake-apiserver/go.sum create mode 100644 hack/fake-apiserver/k8s/metal3-fkas-system.yaml create mode 100644 hack/fake-apiserver/k8s/metal3-fkas.yaml diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 5c9e9d8512..e4a30c882f 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -20,6 +20,7 @@ updates: - "/" - "/api" - "/hack/tools" + - "/hack/fake-apiserver" - "/test" schedule: interval: "weekly" diff --git a/.github/workflows/build-fkas-images-action.yml b/.github/workflows/build-fkas-images-action.yml new file mode 100644 index 0000000000..c69629662e --- /dev/null +++ b/.github/workflows/build-fkas-images-action.yml @@ -0,0 +1,25 @@ +name: build-fkas-images-action + +on: + push: + branches: + - 'main' + paths: + - 'hack/fake-apiserver/**' + +permissions: + contents: read + +jobs: + build_FKAS: + name: Build Metal3-FKAS image + if: github.repository == 'metal3-io/cluster-api-provider-metal3' + uses: metal3-io/project-infra/.github/workflows/container-image-build.yml@main + with: + image-name: "metal3-fkas" + pushImage: true + dockerfile-directory: hack/fake-apiserver + secrets: + QUAY_USERNAME: ${{ secrets.QUAY_USERNAME }} + QUAY_PASSWORD: ${{ secrets.QUAY_PASSWORD }} + SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }} diff --git a/Makefile b/Makefile index ce3fba8279..d498712e35 100644 --- a/Makefile +++ b/Makefile @@ -40,6 +40,7 @@ APIS_DIR := api TEST_DIR := test BIN_DIR := bin TOOLS_BIN_DIR := $(abspath $(TOOLS_DIR)/$(BIN_DIR)) +FAKE_APISERVER_DIR := hack/fake-apiserver # Set --output-base for conversion-gen if we are not within GOPATH ifneq ($(abspath $(ROOT_DIR)),$(shell $(GO) env GOPATH)/src/github.com/metal3-io/cluster-api-provider-metal3) @@ -349,6 +350,8 @@ modules: ## Runs go mod to ensure proper vendoring. cd $(APIS_DIR) && $(GO) mod verify cd $(TEST_DIR) && $(GO) mod tidy cd $(TEST_DIR) && $(GO) mod verify + cd $(FAKE_APISERVER_DIR) && $(GO) mod tidy + cd $(FAKE_APISERVER_DIR) && $(GO) mod verify .PHONY: generate generate: ## Generate code @@ -449,6 +452,14 @@ docker-build: ## Build the docker image for controller-manager docker-push: ## Push the docker image docker push $(CONTROLLER_IMG)-$(ARCH):$(TAG) +.PHONY: build-fkas +# Allow overriding this by setting CONTAINER_RUNTIME var +CONTAINER_RUNTIME := $(if $(CONTAINER_RUNTIME),$(CONTAINER_RUNTIME),docker) +export CONTAINER_RUNTIME + +build-fkas: + cd $(FAKE_APISERVER_DIR) && $(CONTAINER_RUNTIME) build --build-arg ARCH=$(ARCH) -t "quay.io/metal3-io/metal3-fkas:latest" . + ## -------------------------------------- ## Docker — All ARCH ## -------------------------------------- @@ -656,7 +667,8 @@ verify-boilerplate: .PHONY: verify-modules verify-modules: modules - @if !(git diff --quiet HEAD -- go.sum go.mod hack/tools/go.mod hack/tools/go.sum); then \ + @if !(git diff --quiet HEAD -- go.sum go.mod hack/tools/go.mod hack/tools/go.sum \ + hack/fake-apiserver/go.mod hack/fake-apiserver/go.sum); then \ echo "go module files are out of date"; exit 1; \ fi diff --git a/docs/releasing.md b/docs/releasing.md index 23b3c8cc5a..702308dfe7 100644 --- a/docs/releasing.md +++ b/docs/releasing.md @@ -98,8 +98,8 @@ This triggers two things: We also need to create one or more tags for the Go modules ecosystem: -- For any subdirectory with `go.mod` in it (excluding `hack/tools`), create - another Git tag with directory prefix, ie. +- For any subdirectory with `go.mod` in it (excluding `hack/tools` and + `hack/fake-apiserver`), create another Git tag with directory prefix, ie. `git tag api/v1.x.y` and `git tag test/v1.x.y`. This enables the tags to be used as a Go module version for any downstream users. diff --git a/hack/fake-apiserver/Dockerfile b/hack/fake-apiserver/Dockerfile new file mode 100644 index 0000000000..e925af8c12 --- /dev/null +++ b/hack/fake-apiserver/Dockerfile @@ -0,0 +1,65 @@ +# 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. + +# Support FROM override +ARG BUILD_IMAGE=docker.io/golang:1.22.7@sha256:192683db8982323952988c7b86c098ee7ecc6cbeb202bf7c113ff9be5358367c +ARG BASE_IMAGE=gcr.io/distroless/static:nonroot@sha256:9ecc53c269509f63c69a266168e4a687c7eb8c0cfd753bd8bfcaa4f58a90876f + +# Build the fkas binary on golang image +FROM $BUILD_IMAGE AS base +WORKDIR /workspace + +# Run this with docker build --build_arg $(go env GOPROXY) to override the goproxy +ARG goproxy=https://proxy.golang.org +ENV GOPROXY=$goproxy + +# Copy the Go Modules manifests +COPY go.mod go.sum ./ + +# Cache deps before building and copying source so that we don't need to re-download as much +# and so that source changes don't invalidate our downloaded layer +RUN go mod download + +# Build Fkas +FROM base AS build-fkas + +# Copy the sources +COPY cmd/metal3-fkas/*.go ./ + +# Build +ARG ARCH=amd64 +RUN CGO_ENABLED=0 GOOS=linux GOARCH=${ARCH} \ + go build -a -ldflags '-extldflags "-static"' \ + -o fkas . + +# Build fkas-reconciler +FROM base AS build-fkas-reconciler + +# Copy the sources +COPY cmd/metal3-fkas-reconciler/*.go ./ + +# Build +ARG ARCH=amd64 +RUN CGO_ENABLED=0 GOOS=linux GOARCH=${ARCH} \ + go build -a -ldflags '-extldflags "-static"' \ + -o reconciler . + +# Copy the controller-manager into a thin image +FROM $BASE_IMAGE +WORKDIR / +# Use uid of nonroot user (65532) because kubernetes expects numeric user when applying pod security policies +COPY --from=build-fkas /workspace/fkas . +COPY --from=build-fkas-reconciler /workspace/reconciler . +USER 65532 +ENTRYPOINT ["/fkas"] diff --git a/hack/fake-apiserver/README.md b/hack/fake-apiserver/README.md new file mode 100644 index 0000000000..03a533a073 --- /dev/null +++ b/hack/fake-apiserver/README.md @@ -0,0 +1,183 @@ +# Metal3 Fake Kubernetes API server system (Metal3-FKAS) + +## FKAS + +Metal3-FKAS is a tool for testing CAPI-related projects. +When being asked, it generates new fake kubernetes api endpoint, that responds +to all typical requests that CAPI sends to a newly provisioned cluster. + +Despite being developed for Metal3 ecosystem, FKAS is provider-free. It can be adopted +and used by any CAPI provider with intention to test the provider provisioning ability, +without using real nodes. + +### Purpose + +After a CAPI Infrastructure provisions a new cluster, CAPI will send queries +towards the newly launched cluster's API server to verify that the cluster is +fully up and running. + +In a simulated provisioning process, there are no real nodes, hence we cannot +have an actual API server running inside the node. Booting up a real kubelet +and etcd servers elsewhere is possible, but these processes are likely to consume +a lot of resources. + +FKAS is useful in this situation. When a request is sent towards `/register` endpoint, +it will spawn a new simulated kubernetes API server with *unique* a host and +port pair. +User can, then, inject the address into cluster template consumed by +CAPI with any infra provider. + +### How to use + +You can build the `metal3-fkas` image that is suitable for +your local environment with + +```shell +make build-fkas +``` + +The result is an image with label `quay.io/metal3-io/metal3-fkas:` + +Alternatively, you can also build a custom image with + +```shell +cd hack/fake-apiserver +docker build -t . +``` + +For local tests, it's normally needed to load the image into the cluster. +For e.g. with `minikube` + +```shell +minikube image load quay.io/metal3-io/metal3-fkas:latest +``` + +Now you can deploy this container to the cluster, for e.g. with the deployment +if k8s/metal3-fkas.yaml + +```shell +kubectl apply -f metal3-fkas.yaml +``` + +After building the container image and deploy it to the bootstrap kubernetes cluster, +you need to create a tunnel to send request to it and get response, by using +a LoadBalancer, or a simple port-forward + +```shell +fkas_pod_name=$(kubectl get pods -n default -l app=metal3-fkas-system -o jsonpath='{.items[0].metadata.name}') +kubectl port-forward pod/${fkas_pod_name} 3333:3333 2>/dev/null& +``` + +Now, you can generate a fake API server endpoint by sending +a POST request to the fake API server. + +```shell +namespace= +cluster_name= + +cluster_endpoint=$(curl -X POST "localhost:3333/register" \ +-H "Content-Type: application/json" -d '{ + "cluster": "'$cluster_name'", + "namespace": "'$namespace'" +}') +``` + +The fake API server will return a response with the ip and port of the newly +generated api server. For example: + +```json +{ + "Resource": "metal3/test1", + "Host": "10.244.0.83", + "Port": 20000 +} +``` + +A new cluster can be provisioned by injecting the host and port we +got from FKAS to the cluster template provided by a CAPI infrastructure +provider. For e.g., with CAPM3 that can be done as followed: + +```shell +host=$(echo ${cluster_endpoints} | jq -r ".Host") +port=$(echo ${cluster_endpoints} | jq -r ".Port") + +# Injecting the new api address into the cluster template by +# exporting these env vars +export CLUSTER_APIENDPOINT_HOST="${host}" +export CLUSTER_APIENDPOINT_PORT="${port}" + +clusterctl generate cluster "${cluster}" \ + --from "${CLUSTER_TEMPLATE}" \ + --target-namespace "${namespace}" > /tmp/${cluster}-cluster.yaml +kubectl apply -f /tmp/${cluster}-cluster.yaml +``` + +After the cluster is created, CAPI will expect that information like node name +and provider ID is registered in the API server. Since our API server doesn't +live inside the node, we will need to feed the info to it, by sending a +PUT request to `/updateNode` endpoint: + +```shell +curl -X PUT "localhost:3333/updateNode" -H "Content-Type: application/json" -d '{ + "cluster": "", + "namespace": "", + "nodeName": "", + "providerID": "", + "uuid": "", + "labels": "", + "k8sversion": "" +}' +``` + +### Acknowledgements + +This was developed thanks to the implementation of +[Cluster API Provider In Memory (CAPIM)](https://github.com/kubernetes-sigs/cluster-api/tree/main/test/infrastructure/inmemory). + +## Metal3 FKAS System + +### FKAS in Metal3 + +In metal3 ecosystem, currently we have two ways of simulating a workflow without +using any baremetal or virtual machines: + +- [FakeIPA container](https://github.com/metal3-io/utility-images/tree/main/fake-ipa) +- BMO simulation mode + +In both of these cases, the "nodes" are not able to boot up any kubernetes api server, +hence the needs of having mock API servers on-demands. + +Similar to the general case, after having BMHs provisioned to `available` state, +the user can send a request towards the Fake API server endpoint `/register`, +which will spawn a new API server, with an unique `Host` and `Port` pair. + +User can, then, use this IP address to feed the cluster template, by exporting +`CLUSTER_APIENDPOINT_HOST` and `CLUSTER_APIENDPOINT_PORT` variables. + +There is no need of manually check and send node info to `/updateNode`, as we have +another tool to automate that part. + +### Metal3-FKAS-Reconciler + +This tool runs as a side-car container alongside FKAS, and works specifically +for Metal3. It eliminates the needs of user to manually fetch the nodes information +and send to `/updateNode` (as described earlier), by constantly watch the changes +in BMH objects, notice if a BMH is being provisioned to a kubernetes node, and +send request to `/updateNode` with appropriate information. + +If you want to use *Metal3-FKAS* with another CAPI provider, you can also implement +your own reconciler, based on implementation of *metal3-fkas-reconciler*. + +### Deployment + +The `metal3-fkas-system` deployment (including `metal3-fkas` and `metal3-fkas-reconciler`) +can be deployed with the `k8s/metal3-fkas-system.yaml` file. + +```shell +kubectl apply -f k8s/metal3-fkas-system.yaml +``` + +## Disclaimer + +This is intended for development environments only. +Do **NOT** use it in production. diff --git a/hack/fake-apiserver/cmd/metal3-fkas-reconciler/main.go b/hack/fake-apiserver/cmd/metal3-fkas-reconciler/main.go new file mode 100644 index 0000000000..c2d544524f --- /dev/null +++ b/hack/fake-apiserver/cmd/metal3-fkas-reconciler/main.go @@ -0,0 +1,161 @@ +package main + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "net/http" + "os" + + bmov1alpha1 "github.com/metal3-io/baremetal-operator/apis/metal3.io/v1alpha1" + infrav1 "github.com/metal3-io/cluster-api-provider-metal3/api/v1beta1" + + "go.uber.org/zap/zapcore" + "k8s.io/apimachinery/pkg/runtime" + rest "k8s.io/client-go/rest" + clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/log/zap" + "sigs.k8s.io/controller-runtime/pkg/manager" + "sigs.k8s.io/controller-runtime/pkg/reconcile" +) + +func main() { + // Set up logger + debug := os.Getenv("DEBUG") + logLevel := zapcore.InfoLevel // Default log level + if debug == "true" { + logLevel = zapcore.DebugLevel // Set log level to Debug if DEBUG=true + } + log.SetLogger(zap.New(zap.UseDevMode(true), zap.Level(logLevel))) + + // Create a Kubernetes client + config, err := rest.InClusterConfig() + setupLog := ctrl.Log.WithName("setup") + if err != nil { + setupLog.Error(err, "Error getting context kubeconfig") + } + + setupLog.Info("Starting the Metal3 FKAS reconciler") + + // Add BareMetalHost to scheme + scheme := runtime.NewScheme() + if err := bmov1alpha1.AddToScheme(scheme); err != nil { + setupLog.Error(err, "Error adding BareMetalHost to scheme") + } + + if err := infrav1.AddToScheme(scheme); err != nil { + setupLog.Error(err, "Error adding Metal3Machine to scheme") + } + + if err := clusterv1.AddToScheme(scheme); err != nil { + setupLog.Error(err, "Error adding Machine to scheme") + } + + // Create a Kubernetes client + mgr, err := manager.New(config, manager.Options{Scheme: scheme}) + if err != nil { + setupLog.Error(err, "Error creating manager") + } + + // Set up the BareMetalHost controller + if err := ctrl.NewControllerManagedBy(mgr). + For(&bmov1alpha1.BareMetalHost{}). + Complete(reconcile.Func(func(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + setupLog.Info("Detected change in BMH", "namespace", req.Namespace, "name", req.Name) + bmh := &bmov1alpha1.BareMetalHost{} + if err := mgr.GetClient().Get(ctx, req.NamespacedName, bmh); err != nil { + setupLog.Error(err, "Error fetching BareMetalHost") + return reconcile.Result{}, err + } + + // Check if the state has changed to "provisioned" + if bmh.Status.Provisioning.State != "provisioned" { + setupLog.V(4).Info(fmt.Sprintf("BMH %s/%s state is not in 'provisioning' or 'provisioned' state.", req.Namespace, req.Name)) + return reconcile.Result{}, nil + } + uuid := bmh.ObjectMeta.UID + if bmh.Spec.ConsumerRef == nil { + return reconcile.Result{}, err + } + m3m := &infrav1.Metal3Machine{} + m3mKey := client.ObjectKey{ + Namespace: bmh.Spec.ConsumerRef.Namespace, + Name: bmh.Spec.ConsumerRef.Name, + } + if err := mgr.GetClient().Get(ctx, m3mKey, m3m); err != nil { + setupLog.Error(err, "Error fetching Metal3Machine", "namespace", bmh.Spec.ConsumerRef.Namespace, "name", bmh.Spec.ConsumerRef.Name) + return reconcile.Result{}, err + } + // Get the Machine object referenced by m3m.Metadata.OwnerReference.Name + if len(m3m.OwnerReferences) == 0 { + setupLog.Error(fmt.Errorf("no owner reference found"), "Metal3Machine has no owner reference") + return reconcile.Result{}, fmt.Errorf("no owner reference found") + } + + machineName := m3m.ObjectMeta.OwnerReferences[0].Name + namespace := m3m.Namespace + machine := &clusterv1.Machine{} + machineKey := client.ObjectKey{ + Namespace: namespace, + Name: machineName, + } + if err := mgr.GetClient().Get(ctx, machineKey, machine); err != nil { + setupLog.Error(err, "Error fetching Machine", "namespace", m3m.Namespace, "name", machineName) + return reconcile.Result{}, err + } + labels := machine.Labels + clusterName, ok := labels["cluster.x-k8s.io/cluster-name"] + if !ok { + return reconcile.Result{}, err + } + providerID := m3m.Spec.ProviderID + url := "http://localhost:3333/updateNode" + requestData := map[string]interface{}{ + "cluster": clusterName, + "nodeName": machineName, + "namespace": namespace, + "providerID": providerID, + "uuid": string(uuid), + "labels": labels, + "k8sversion": machine.Spec.Version, + } + jsonData, err := json.Marshal(requestData) + if err != nil { + setupLog.Error(err, "Error marshalling JSON") + return reconcile.Result{}, err + } + setupLog.Info("Making PUT request", "content", string(jsonData)) + putReq, err := http.NewRequest(http.MethodPut, url, bytes.NewBuffer(jsonData)) + if err != nil { + setupLog.Error(err, "Error creating PUT request") + return reconcile.Result{}, err + } + putReq.Header.Set("Content-Type", "application/json") + client := &http.Client{} + resp, err := client.Do(putReq) + if err != nil { + setupLog.Error(err, "Error making PUT request") + return reconcile.Result{}, err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + setupLog.Info(fmt.Sprintf("PUT request failed with status: %s", resp.Status)) + return reconcile.Result{}, fmt.Errorf("PUT request failed with status: %s", resp.Status) + } + + return reconcile.Result{}, nil + })); err != nil { + setupLog.Error(err, "Error setting up controller") + } + + // Start the manager + setupLog.Info("Starting controller...") + if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil { + setupLog.Error(err, "Error starting manager") + } +} diff --git a/hack/fake-apiserver/cmd/metal3-fkas/main.go b/hack/fake-apiserver/cmd/metal3-fkas/main.go new file mode 100644 index 0000000000..efb99d26ab --- /dev/null +++ b/hack/fake-apiserver/cmd/metal3-fkas/main.go @@ -0,0 +1,616 @@ +package main + +import ( + "context" + "crypto/rsa" + "encoding/json" + "fmt" + "math/rand/v2" + "net/http" + "os" + "time" + + "go.uber.org/zap/zapcore" + appsv1 "k8s.io/api/apps/v1" + coordinationv1 "k8s.io/api/coordination/v1" + corev1 "k8s.io/api/core/v1" + rbacv1 "k8s.io/api/rbac/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + rest "k8s.io/client-go/rest" + cmanager "sigs.k8s.io/cluster-api/test/infrastructure/inmemory/pkg/runtime/manager" + "sigs.k8s.io/cluster-api/test/infrastructure/inmemory/pkg/server" + "sigs.k8s.io/cluster-api/util/certs" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/log/zap" +) + +var ( + cloudScheme = runtime.NewScheme() + cloudMgr cmanager.Manager + setupLog = ctrl.Log.WithName("setup") + apiServerMux = &server.WorkloadClustersMux{} + ctx = context.Background() + bootstrapClient client.Client + etcdInfoMap = make(map[string]etcdInfo) + podIP string + workloadListenerActivations = make(map[string]bool) +) + +func init() { + // scheme used for operating on the cloud resource. + _ = corev1.AddToScheme(cloudScheme) + _ = appsv1.AddToScheme(cloudScheme) + _ = rbacv1.AddToScheme(cloudScheme) + _ = coordinationv1.AddToScheme(cloudScheme) + cloudMgr = cmanager.New(cloudScheme) +} + +// register receives a ClusterName and Namespace from a request +// and generates a fake k8s API server corresponding with the provided name. +func register(w http.ResponseWriter, r *http.Request) { + setupLog.Info("Received request to /register") + var requestData struct { + ClusterName string `json:"cluster"` + Namespace string `json:"namespace"` + } + + if err := json.NewDecoder(r.Body).Decode(&requestData); err != nil { + setupLog.Error(err, "Failed to decode request body") + http.Error(w, "Invalid JSON", http.StatusBadRequest) + setupLog.Error(err, "Invalid JSON in request body") + return + } + + resourceName := fmt.Sprintf("%s/%s", requestData.Namespace, requestData.ClusterName) + setupLog.Info("Registering new resource", "resource", resourceName) + resp := &ResourceData{} + resp.ResourceName = resourceName + setupLog.Info("Adding resource group", "resourceName", resourceName) + cloudMgr.AddResourceGroup(resourceName) + // NOTE: We are using resourceName as listener name for convenience + listenerName := resourceName + listener, err := apiServerMux.InitWorkloadClusterListener(listenerName) + if err != nil { + setupLog.Error(err, "Failed to initialize listener", "listenerName", listenerName) + http.Error(w, "Failed to initialize listener", http.StatusInternalServerError) + return + } + if err := apiServerMux.RegisterResourceGroup(listenerName, resourceName); err != nil { + setupLog.Error(err, "Failed to register resource group to listener", "resourceName", resourceName) + http.Error(w, "Failed to register resource group to listener", http.StatusInternalServerError) + setupLog.Error(err, "failed to Register resource group to listener") + return + } + + resp.Host = listener.Host() + resp.Port = listener.Port() + + workloadListenerActivations[listenerName] = false + + data, err := json.Marshal(resp) + if err != nil { + setupLog.Error(err, "Failed to marshal the response", "resourceName", resourceName) + http.Error(w, "", http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "application/json") + if _, err = w.Write(data); err != nil { + setupLog.Error(err, "failed to write the response data") + return + } +} + +// activateCluster instantiates the cluster by installing the cluster components that should +// be ready after the first control-plane is started. +func activateCluster(resourceName string, w http.ResponseWriter, k8sVersion string) error { + listener := cloudMgr.GetResourceGroup(resourceName) + c := listener.GetClient() + + role := &rbacv1.ClusterRole{ + ObjectMeta: metav1.ObjectMeta{ + Name: "kubeadm:get-nodes", + }, + Rules: []rbacv1.PolicyRule{ + { + Verbs: []string{"get"}, + APIGroups: []string{"core"}, + Resources: []string{"nodes"}, + }, + }, + } + if err := c.Create(ctx, role); err != nil { + setupLog.Error(err, "Failed to create cluster role", "resourceName", resourceName) + http.Error(w, "", http.StatusInternalServerError) + return err + } + roleBinding := &rbacv1.ClusterRoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: "kubeadm:get-nodes", + }, + RoleRef: rbacv1.RoleRef{ + APIGroup: rbacv1.GroupName, + Kind: "ClusterRole", + Name: "kubeadm:get-nodes", + }, + Subjects: []rbacv1.Subject{ + { + Kind: rbacv1.GroupKind, + Name: "system:bootstrappers:kubeadm:default-node-token", + }, + }, + } + if err := c.Create(ctx, roleBinding); err != nil { + setupLog.Error(err, "Failed to create cluster rolebinding", "resourceName", resourceName) + http.Error(w, "", http.StatusInternalServerError) + return err + } + // create kubeadm config map + cm := &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "kubeadm-config", + Namespace: metav1.NamespaceSystem, + }, + Data: map[string]string{ + "ClusterConfiguration": "", + }, + } + if err := c.Create(ctx, cm); err != nil { + setupLog.Error(err, "Failed to create kubeadm configmap", "resourceName", resourceName) + http.Error(w, "", http.StatusInternalServerError) + return err + } + // create kubelet config map + kubeletCm := &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "kubelet-config", + Namespace: metav1.NamespaceSystem, + }, + Data: map[string]string{ + "ClusterConfiguration": "", + }, + } + if err := c.Create(ctx, kubeletCm); err != nil { + setupLog.Error(err, "Failed to create kubelet configmap", "resourceName", resourceName) + http.Error(w, "", http.StatusInternalServerError) + return err + } + // Create kube-proxy DaemonSet + kubeProxyDaemonSet := &appsv1.DaemonSet{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: metav1.NamespaceSystem, + Name: "kube-proxy", + Labels: map[string]string{ + "component": "kube-proxy", + }, + }, + Spec: appsv1.DaemonSetSpec{ + Template: corev1.PodTemplateSpec{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "kube-proxy", + Image: fmt.Sprintf("registry.k8s.io/kube-proxy:%s", k8sVersion), + }, + }, + }, + }, + }, + } + + if err := c.Create(ctx, kubeProxyDaemonSet); err != nil { + setupLog.Error(err, "Failed to create kube-proxy DaemonSet", "resourceName", resourceName) + http.Error(w, "Failed to create kube-proxy DaemonSet", http.StatusInternalServerError) + return err + } + // Create the coredns configMap. + corednsConfigMap := &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: metav1.NamespaceSystem, + Name: "coredns", + }, + Data: map[string]string{ + "Corefile": "ANG", + }, + } + if err := c.Create(ctx, corednsConfigMap); err != nil { + setupLog.Error(err, "Failed to create corednsConfigMap", "resourceName", resourceName) + http.Error(w, "Failed to create corednsConfigMap", http.StatusInternalServerError) + return err + } + // Create the coredns deployment. + corednsDeployment := &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: metav1.NamespaceSystem, + Name: "coredns", + }, + Spec: appsv1.DeploymentSpec{ + Template: corev1.PodTemplateSpec{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "coredns", + Image: "registry.k8s.io/coredns/coredns:v1.10.1", + }, + }, + }, + }, + }, + } + if err := c.Create(ctx, corednsDeployment); err != nil { + setupLog.Error(err, "Failed to create corednsDeployment", "resourceName", resourceName) + http.Error(w, "Failed to create corednsDeployment", http.StatusInternalServerError) + return err + } + return nil +} + +// updateNode receives nodeName and providerID, which are provided by CAPI after +// node provisioning, from request, and update the Node object on the fake API server accordingly. +func updateNode(w http.ResponseWriter, r *http.Request) { + var requestData struct { + ClusterName string `json:"cluster"` + UUID string `json:"uuid"` + NodeName string `json:"nodeName"` + Namespace string `json:"namespace"` + ProviderID string `json:"providerID"` + Labels map[string]string `json:"labels"` + K8sVersion string `json:"k8sversion"` + } + + if err := json.NewDecoder(r.Body).Decode(&requestData); err != nil { + http.Error(w, "Invalid JSON", http.StatusBadRequest) + setupLog.Error(err, "Invalid JSON in request body") + return + } + + setupLog.Info("Decoded request data", "nodeName", requestData.NodeName, "providerID", requestData.ProviderID) + nodeLabels := requestData.Labels + nodeLabels["metal3.io/uuid"] = requestData.UUID + namespace := requestData.Namespace + clusterName := requestData.ClusterName + resourceName := fmt.Sprintf("%s/%s", namespace, clusterName) + nodeName := requestData.NodeName + _, isControlPlane := nodeLabels["cluster.x-k8s.io/control-plane"] + + workloadActivated, ok := workloadListenerActivations[resourceName] + if !ok { + http.Error(w, "Workload Cluster does not exist", http.StatusInternalServerError) + return + } + + listener := cloudMgr.GetResourceGroup(resourceName) + + if isControlPlane { + // Add node role control-plane label + nodeLabels["node-role.kubernetes.io/control-plane"] = "" + caSecretName := fmt.Sprintf("%s-ca", clusterName) + caCertRaw, caKeyRaw, err := getSecretKeyAndCert(ctx, bootstrapClient, requestData.Namespace, caSecretName) + if err != nil { + logLine := fmt.Sprintf("Error adding node %s", nodeName) + http.Error(w, logLine, http.StatusInternalServerError) + setupLog.Error(err, "Failed to get ca secrets for cluster", "cluster name", resourceName) + return + } + + caCert, err := certs.DecodeCertPEM(caCertRaw) + if err != nil { + http.Error(w, "Failed to add API server", http.StatusInternalServerError) + setupLog.Error(err, "failed to decode caCertPEM") + return + } + + caKey, err := certs.DecodePrivateKeyPEM(caKeyRaw) + if err != nil { + http.Error(w, "Failed to generate etcdKey", http.StatusInternalServerError) + setupLog.Error(err, "failed to decode caKeyPEM") + return + } + + apiServerPodName := nodeName + err = apiServerMux.AddAPIServer(resourceName, apiServerPodName, caCert, caKey.(*rsa.PrivateKey)) + if err != nil { + http.Error(w, "Failed to add API server", http.StatusInternalServerError) + setupLog.Error(err, "failed to add API server") + return + } + // For first CP, we need to install some cluster-wide resources + if !workloadActivated { + activateCluster(resourceName, w, requestData.K8sVersion) + } + workloadListenerActivations[resourceName] = true + } + + if activated := workloadListenerActivations[resourceName]; !activated { + http.Error(w, "Workload Cluster has not been activated", http.StatusInternalServerError) + return + } + + c := listener.GetClient() + timeOutput := metav1.Now() + + node := &corev1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: nodeName, + Labels: nodeLabels, + }, + Spec: corev1.NodeSpec{ + ProviderID: requestData.ProviderID, + }, + Status: corev1.NodeStatus{ + Conditions: []corev1.NodeCondition{ + { + LastHeartbeatTime: timeOutput, + LastTransitionTime: timeOutput, + Type: corev1.NodeReady, + Status: corev1.ConditionTrue, + }, + { + LastHeartbeatTime: timeOutput, + LastTransitionTime: timeOutput, + Type: corev1.NodeMemoryPressure, + Status: corev1.ConditionFalse, + Message: "kubelet has sufficient memory available", + Reason: "KubeletHasSufficientMemory", + }, + { + LastHeartbeatTime: timeOutput, + LastTransitionTime: timeOutput, + Message: "kubelet has no disk pressure", + Reason: "KubeletHasNoDiskPressure", + Status: corev1.ConditionFalse, + Type: corev1.NodeDiskPressure, + }, + { + LastHeartbeatTime: timeOutput, + LastTransitionTime: timeOutput, + Message: "kubelet has sufficient PID available", + Reason: "KubeletHasSufficientPID", + Status: corev1.ConditionFalse, + Type: corev1.NodePIDPressure, + }, + { + LastHeartbeatTime: timeOutput, + LastTransitionTime: timeOutput, + Message: "kubelet is posting ready status", + Reason: "KubeletReady", + Status: corev1.ConditionTrue, + Type: corev1.NodeReady, + }, + }, + }, + } + + err := c.Create(ctx, node) + if err != nil { + if apierrors.IsAlreadyExists(err) { + setupLog.Info("Node already exists", "nodeName", nodeName) + w.WriteHeader(http.StatusOK) + return + } + logLine := fmt.Sprintf("Error adding node %s: %s", nodeName, err) + fmt.Println(err, "Error adding node:", nodeName) + http.Error(w, logLine, http.StatusInternalServerError) + return + } + fmt.Printf("Created node object: %v\n", node) + + // Start a goroutine to update the LastHeartbeatTime every 10 seconds + go func(nodeName string) { + setupLog.Info("Starting heartbeat goroutine", "nodeName", nodeName) + ticker := time.NewTicker(10 * time.Second) + defer ticker.Stop() + + for range ticker.C { + node := &corev1.Node{} + if err := c.Get(ctx, client.ObjectKey{Name: nodeName}, node); err != nil { + setupLog.Error(err, "Failed to get node for heartbeat update", "nodeName", nodeName) + continue + } + + timeOutput := metav1.Now() + for i := range node.Status.Conditions { + if node.Status.Conditions[i].Type == corev1.NodeReady { + node.Status.Conditions[i].LastHeartbeatTime = timeOutput + } + } + + err := c.Update(ctx, node) + if err != nil { + setupLog.Error(err, "Failed to update node heartbeat", "nodeName", nodeName) + } else { + setupLog.Info("Updated node heartbeat", "nodeName", nodeName, "timestamp", timeOutput) + } + } + }(nodeName) + + if !isControlPlane { + return + } + + // create etcd member + // Wait for some time after the node is provisioned + waitForRandomSeconds() + etcdSecretName := fmt.Sprintf("%s-etcd", clusterName) + etcdCertRaw, etcdKeyRaw, err := getSecretKeyAndCert(ctx, bootstrapClient, requestData.Namespace, etcdSecretName) + if err != nil { + logLine := fmt.Sprintf("Error adding node %s", nodeName) + http.Error(w, logLine, http.StatusInternalServerError) + setupLog.Error(err, "Failed to get etcd secrets for cluster", "cluster name", resourceName) + return + } + + etcdCert, err := certs.DecodeCertPEM(etcdCertRaw) + if err != nil { + http.Error(w, "", http.StatusInternalServerError) + setupLog.Error(err, "failed to generate etcdCert") + return + } + etcdKey, err := certs.DecodePrivateKeyPEM(etcdKeyRaw) + if err != nil { + http.Error(w, "", http.StatusInternalServerError) + setupLog.Error(err, "failed to generate etcdKey") + return + } + if etcdKey == nil { + http.Error(w, "", http.StatusInternalServerError) + setupLog.Error(err, "failed to generate etcdKey") + return + } + + // Create the etcd pod + etcdPodMember := fmt.Sprintf("etcd-%s", nodeName) + etcdLabels := map[string]string{ + "component": "etcd", + "tier": "control-plane", + } + etcdPod := getFakePodObject(FakePod{ + PodName: etcdPodMember, + Namespace: metav1.NamespaceSystem, + NodeName: nodeName, + Labels: etcdLabels, + TransactionTime: timeOutput, + }) + + if err := c.Get(ctx, client.ObjectKeyFromObject(etcdPod), etcdPod); err != nil { + if !apierrors.IsNotFound(err) { + setupLog.Error(err, "failed to get etcd pod") + http.Error(w, "", http.StatusInternalServerError) + return + } + + // Gets info about the current etcd cluster, if any. + info, ok := etcdInfoMap[resourceName] + if !ok { + info = etcdInfo{} + for { + info.clusterID = fmt.Sprintf("%d", rand.Uint32()) //nolint:gosec // weak random number generator is good enough here + if info.clusterID != "0" { + break + } + } + } + + // Computes a unique memberID. + var memberID string + for { + memberID = fmt.Sprintf("%d", rand.Uint32()) //nolint:gosec // weak random number generator is good enough here + if !info.members.Has(memberID) && memberID != "0" { + break + } + } + + // Annotate the pod with the info about the etcd cluster. + etcdPod.Annotations = map[string]string{ + EtcdClusterIDAnnotationName: info.clusterID, + EtcdMemberIDAnnotationName: memberID, + } + + // If the etcd cluster is being created it doesn't have a leader yet, so set this member as a leader. + if info.leaderID == "" { + etcdPod.Annotations[EtcdLeaderFromAnnotationName] = time.Now().Format(time.RFC3339) + } + + etcdInfoMap[resourceName] = info + + if err := c.Create(ctx, etcdPod); err != nil && !apierrors.IsAlreadyExists(err) { + setupLog.Error(err, "failed to create etcd pod") + http.Error(w, "Failed to create etcd pod", http.StatusInternalServerError) + return + } + } + + err = apiServerMux.AddEtcdMember(resourceName, nodeName, etcdCert, etcdKey.(*rsa.PrivateKey)) + if err != nil { + setupLog.Error(err, "failed to add etcd member") + http.Error(w, "", http.StatusInternalServerError) + return + } + + // Create the kube-apiserver pod + if err := createControlPlanePod(ctx, c, "kube-apiserver", FakePod{ + PodName: fmt.Sprintf("kube-apiserver-%s", nodeName), + NodeName: nodeName, + TransactionTime: timeOutput, + }); err != nil { + setupLog.Error(err, "failed to create kube-apiserver pod") + http.Error(w, "", http.StatusInternalServerError) + return + } + + // Create the kube-controller-manager + if err := createControlPlanePod(ctx, c, "kube-controller-manager", FakePod{ + PodName: fmt.Sprintf("kube-controller-manager-%s", nodeName), + NodeName: nodeName, + TransactionTime: timeOutput, + }); err != nil { + setupLog.Error(err, "failed to create kube-controller-manager pod") + http.Error(w, "", http.StatusInternalServerError) + return + } + + // Create the kube-scheduler + if err := createControlPlanePod(ctx, c, "kube-scheduler", FakePod{ + PodName: fmt.Sprintf("kube-scheduler-%s", nodeName), + NodeName: nodeName, + TransactionTime: timeOutput, + }); err != nil { + setupLog.Error(err, "failed to create scheduler Pod") + http.Error(w, "", http.StatusInternalServerError) + return + } +} + +func main() { + debug := os.Getenv("DEBUG") + logLevel := zapcore.InfoLevel // Default log level + if debug == "true" { + logLevel = zapcore.DebugLevel // Set log level to Debug if DEBUG=true + } + log.SetLogger(zap.New(zap.UseDevMode(true), zap.Level(logLevel))) + podIP = os.Getenv("POD_IP") + apiServerMux, _ = server.NewWorkloadClustersMux(cloudMgr, podIP) + setupLog.Info("Starting the FKAS server") + config, err := rest.InClusterConfig() + if err != nil { + setupLog.Error(err, "Cannot create bootstrap client config") + os.Exit(1) + } + + bootstrapClient, err = client.New(config, client.Options{Scheme: cloudScheme}) + if err != nil { + setupLog.Error(err, "Failed to create controller-runtime client") + os.Exit(1) + } + + http.HandleFunc("/register", func(w http.ResponseWriter, r *http.Request) { + setupLog.Info("Received request to /register", "content", r.Body) + if r.Method != http.MethodPost { + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + return + } + register(w, r) + }) + http.HandleFunc("/updateNode", func(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPut { + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + return + } + updateNode(w, r) + }) + server := &http.Server{ + Addr: ":3333", + Handler: nil, + ReadTimeout: 10 * time.Second, + WriteTimeout: 10 * time.Second, + IdleTimeout: 15 * time.Second, + } + if err := server.ListenAndServe(); err != nil { + setupLog.Error(err, "Error starting server") + os.Exit(1) + } +} diff --git a/hack/fake-apiserver/cmd/metal3-fkas/utils.go b/hack/fake-apiserver/cmd/metal3-fkas/utils.go new file mode 100644 index 0000000000..b8aeb85764 --- /dev/null +++ b/hack/fake-apiserver/cmd/metal3-fkas/utils.go @@ -0,0 +1,179 @@ +package main + +import ( + "context" + "fmt" + "math/rand" + "time" + + 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/util/sets" + inmemoryclient "sigs.k8s.io/cluster-api/test/infrastructure/inmemory/pkg/runtime/client" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +type etcdInfo struct { + clusterID string + leaderID string + members sets.Set[string] +} + +type ResourceData struct { + ResourceName string + Host string + Port int +} + +const ( + // EtcdClusterIDAnnotationName defines the name of the annotation applied to in memory etcd + // pods to track the cluster ID of the etcd member each pod represent. + EtcdClusterIDAnnotationName = "etcd.inmemory.infrastructure.cluster.x-k8s.io/cluster-id" + + // EtcdMemberIDAnnotationName defines the name of the annotation applied to in memory etcd + // pods to track the member ID of the etcd member each pod represent. + EtcdMemberIDAnnotationName = "etcd.inmemory.infrastructure.cluster.x-k8s.io/member-id" + + // EtcdLeaderFromAnnotationName defines the name of the annotation applied to in memory etcd + // pods to track leadership status of the etcd member each pod represent. + // Note: We are tracking the time from an etcd member is leader; if more than one pod has this + // annotation, the last etcd member that became leader is the current leader. + // By using this mechanism leadership can be forwarded to another pod with an atomic operation + // (add/update of the annotation to the pod/etcd member we are forwarding leadership to). + EtcdLeaderFromAnnotationName = "etcd.inmemory.infrastructure.cluster.x-k8s.io/leader-from" + + // EtcdMemberRemoved is added to etcd pods which have been removed from the etcd cluster. + EtcdMemberRemoved = "etcd.inmemory.infrastructure.cluster.x-k8s.io/member-removed" +) + +type FakePod struct { + PodName string + Namespace string + NodeName string + TransactionTime metav1.Time + Labels map[string]string +} + +func getFakePodObject(f FakePod) *corev1.Pod { + podObj := &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: f.Namespace, + Name: f.PodName, + Labels: f.Labels, + }, + Spec: corev1.PodSpec{ + NodeName: f.NodeName, + }, + Status: corev1.PodStatus{ + Phase: corev1.PodRunning, + Conditions: []corev1.PodCondition{ + { + Type: corev1.PodReadyToStartContainers, + Status: corev1.ConditionTrue, + LastTransitionTime: f.TransactionTime, + }, + { + Type: corev1.PodInitialized, + Status: corev1.ConditionTrue, + LastTransitionTime: f.TransactionTime, + }, + { + Type: corev1.PodReady, + Status: corev1.ConditionTrue, + LastTransitionTime: f.TransactionTime, + }, + { + Type: corev1.ContainersReady, + Status: corev1.ConditionTrue, + LastTransitionTime: f.TransactionTime, + }, + { + Type: corev1.PodScheduled, + Status: corev1.ConditionTrue, + LastTransitionTime: f.TransactionTime, + }, + }, + }, + } + return podObj +} + +func createFakePod(ctx context.Context, c inmemoryclient.Client, f FakePod) error { + // Check if the namespace exists + ns := &corev1.Namespace{} + err := c.Get(ctx, client.ObjectKey{Name: f.Namespace}, ns) + if err != nil { + if !apierrors.IsNotFound(err) { + return fmt.Errorf("failed to get namespace: %w", err) + } + + // Namespace does not exist, create it + ns = &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: f.Namespace, + }, + } + if err := c.Create(ctx, ns); err != nil { + return fmt.Errorf("failed to create namespace: %w", err) + } + } + + podObj := getFakePodObject(f) + + if err := c.Create(ctx, podObj); err != nil && !apierrors.IsAlreadyExists(err) { + return err + } + return nil +} + +func createControlPlanePod(ctx context.Context, c inmemoryclient.Client, component string, f FakePod) error { + podLabels := map[string]string{ + "component": component, + "tier": "control-plane", + } + f.Labels = podLabels + f.Namespace = metav1.NamespaceSystem + return createFakePod(ctx, c, f) +} + +func getSecretKeyAndCert( + ctx context.Context, + k8sClient client.Client, + namespace, secretName string, +) ([]byte, []byte, error) { + // Get the secret + secret := &corev1.Secret{} + err := k8sClient.Get(ctx, client.ObjectKey{ + Namespace: namespace, + Name: secretName, + }, secret) + if err != nil { + return nil, nil, fmt.Errorf("failed to get secret: %v", err) + } + + // Extract tls.crt and tls.key + tlsCrt, ok := secret.Data["tls.crt"] + if !ok { + return nil, nil, fmt.Errorf("tls.crt not found in secret") + } + + tlsKey, ok := secret.Data["tls.key"] + if !ok { + return nil, nil, fmt.Errorf("tls.key not found in secret") + } + + return tlsCrt, tlsKey, nil +} + +func waitForRandomSeconds() { + // Generate a random number of seconds between 1 and 10 + randomSeconds := rand.Intn(10) + 1 + + fmt.Printf("Waiting for %d seconds...\n", randomSeconds) + + // Wait for the random number of seconds + time.Sleep(time.Duration(randomSeconds) * time.Second) + + fmt.Println("Done waiting!") +} diff --git a/hack/fake-apiserver/go.mod b/hack/fake-apiserver/go.mod new file mode 100644 index 0000000000..9cb18d2791 --- /dev/null +++ b/hack/fake-apiserver/go.mod @@ -0,0 +1,112 @@ +module github.com/metal3-io/cluster-api-provider-metal3/hack/fake-apiserver + +go 1.22.3 + +require ( + github.com/metal3-io/baremetal-operator/apis v0.8.0 + github.com/metal3-io/cluster-api-provider-metal3/api v1.8.1 + go.uber.org/zap v1.27.0 + k8s.io/api v0.31.1 + k8s.io/apimachinery v0.31.1 + k8s.io/client-go v0.31.1 + sigs.k8s.io/cluster-api v1.8.2 + sigs.k8s.io/cluster-api/test v1.7.1 + sigs.k8s.io/controller-runtime v0.19.0 +) + +require ( + github.com/NYTimes/gziphandler v1.1.1 // indirect + github.com/antlr4-go/antlr/v4 v4.13.0 // indirect + github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/blang/semver/v4 v4.0.0 // indirect + github.com/cenkalti/backoff/v4 v4.3.0 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/coreos/go-semver v0.3.1 // indirect + github.com/coreos/go-systemd/v22 v22.5.0 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/emicklei/go-restful/v3 v3.12.1 // 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/fxamacker/cbor/v2 v2.7.0 // indirect + github.com/go-logr/logr v1.4.2 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-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.4 // 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.20.1 // 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.6.0 // indirect + github.com/gorilla/websocket v1.5.0 // indirect + github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.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/metal3-io/baremetal-operator/pkg/hardwareutils v0.5.1 // indirect + github.com/metal3-io/ip-address-manager/api v1.8.0 // indirect + github.com/moby/spdystream v0.4.0 // 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/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/prometheus/client_golang v1.19.1 // indirect + github.com/prometheus/client_model v0.6.1 // indirect + github.com/prometheus/common v0.55.0 // indirect + github.com/prometheus/procfs v0.15.1 // indirect + github.com/spf13/cobra v1.8.1 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/stoewer/go-strcase v1.2.0 // indirect + github.com/x448/float16 v0.8.4 // indirect + go.etcd.io/etcd/api/v3 v3.5.16 // indirect + go.etcd.io/etcd/client/pkg/v3 v3.5.16 // indirect + go.etcd.io/etcd/client/v3 v3.5.16 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.53.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 // indirect + go.opentelemetry.io/otel v1.28.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0 // indirect + go.opentelemetry.io/otel/metric v1.28.0 // indirect + go.opentelemetry.io/otel/sdk v1.28.0 // indirect + go.opentelemetry.io/otel/trace v1.28.0 // indirect + go.opentelemetry.io/proto/otlp v1.3.1 // indirect + go.uber.org/multierr v1.11.0 // indirect + golang.org/x/crypto v0.27.0 // indirect + golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect + golang.org/x/net v0.29.0 // indirect + golang.org/x/oauth2 v0.23.0 // indirect + golang.org/x/sync v0.8.0 // indirect + golang.org/x/sys v0.25.0 // indirect + golang.org/x/term v0.24.0 // indirect + golang.org/x/text v0.18.0 // indirect + golang.org/x/time v0.5.0 // indirect + gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 // indirect + google.golang.org/grpc v1.65.0 // indirect + google.golang.org/protobuf v1.34.2 // indirect + gopkg.in/evanphx/json-patch.v4 v4.12.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/apiextensions-apiserver v0.31.1 // indirect + k8s.io/apiserver v0.31.1 // indirect + k8s.io/component-base v0.31.1 // indirect + k8s.io/klog/v2 v2.130.1 // indirect + k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect + k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 // indirect + sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.30.3 // 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/hack/fake-apiserver/go.sum b/hack/fake-apiserver/go.sum new file mode 100644 index 0000000000..6726181231 --- /dev/null +++ b/hack/fake-apiserver/go.sum @@ -0,0 +1,326 @@ +github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I= +github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= +github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI= +github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g= +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/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a h1:idn718Q4B6AGu/h5Sxe66HYVdqdGu2l9Iebqhi/AEoA= +github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= +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.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/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.4/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/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/emicklei/go-restful/v3 v3.12.1 h1:PJMDIM/ak7btuL8Ex0iYET9hxM3CI2sjZtzpL63nKAU= +github.com/emicklei/go-restful/v3 v3.12.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/evanphx/json-patch v5.7.0+incompatible h1:vgGkfT/9f8zE6tvSCe74nfpAVDQ2tG6yudJd8LBksgI= +github.com/evanphx/json-patch v5.7.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/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= +github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +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= +github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= +github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= +github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= +github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU= +github.com/go-openapi/swag v0.22.4/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/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= +github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= +github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +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.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.20.1 h1:nDx9r8S3L4pE61eDdt8igGj8rf5kjYR3ILxWIpWNi84= +github.com/google/cel-go v0.20.1/go.mod h1:kWcIzTsPX0zmQ+H3TirHstLLf9ep5QTsZBN9u4dOYLg= +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= +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/google/pprof v0.0.0-20240727154555-813a5fbdbec8 h1:FKHo8hFI3A+7w0aUQuYXQ+6EN5stWmeY/AZqtM8xk9k= +github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= +github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/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.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k= +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/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= +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/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/metal3-io/baremetal-operator/apis v0.8.0 h1:dy1Z6IRdlRi8tsEww8M3CmOayWZE0yb+xJdNx065FJA= +github.com/metal3-io/baremetal-operator/apis v0.8.0/go.mod h1:eeCH0K7XD17AbEp479XtmYTF0QwOqNkoTCQf3bdZSzk= +github.com/metal3-io/baremetal-operator/pkg/hardwareutils v0.5.1 h1:X0+MWsJ+Gj/TAkmhGybvesvxk6zQKu3NQXzvC6l0iJs= +github.com/metal3-io/baremetal-operator/pkg/hardwareutils v0.5.1/go.mod h1:399nvdaqoU9rTI25UdFw2EWcVjmJPpeZPIhfDAIx/XU= +github.com/metal3-io/cluster-api-provider-metal3/api v1.8.1 h1:c0HtsTWQcRY2h/LebRN1dcHyDLTe7dckISsOz5/J8nI= +github.com/metal3-io/cluster-api-provider-metal3/api v1.8.1/go.mod h1:KHcF/nWOAndGur5wXb+ZTo4aSvJGk5RsTJqslKn+x0E= +github.com/metal3-io/ip-address-manager/api v1.8.0 h1:qEemVSb45uDbbQonr2StMV21RfSKNSO/NrPnhApV90w= +github.com/metal3-io/ip-address-manager/api v1.8.0/go.mod h1:0PjnN4ke64Jh6A3U04mMvV2sBwePa7HKkiZXazktCTk= +github.com/moby/spdystream v0.4.0 h1:Vy79D6mHeJJjiPdFEL2yku1kl0chZpJfZcPpb16BRl8= +github.com/moby/spdystream v0.4.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus= +github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= +github.com/onsi/ginkgo/v2 v2.20.0 h1:PE84V2mHqoT1sglvHc8ZdQtPcwmvvt29WLEEO3xmdZw= +github.com/onsi/ginkgo/v2 v2.20.0/go.mod h1:lG9ey2Z29hR41WMVthyJBGUBcBhGOtoPF2VFMvBXFCI= +github.com/onsi/gomega v1.34.2 h1:pNCwDkzrsv7MS9kpaQvVb1aVLahQXyJ/Tv5oAZMI3i8= +github.com/onsi/gomega v1.34.2/go.mod h1:v1xfxRgk0KIsG+QOdm7p8UosrOzPYRo60fd3B/1Dukc= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE= +github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho= +github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= +github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= +github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc= +github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8= +github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= +github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/soheilhy/cmux v0.1.5 h1:jjzc5WVemNEDTLwv9tlmemhC73tI08BNOIGwBOo10Js= +github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0= +github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= +github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= +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/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.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.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75 h1:6fotK7otjonDflCTK0BCfls4SPy3NcCVb5dqqmbRknE= +github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75/go.mod h1:KO6IkyS8Y3j8OdNO85qEYBsRPuteD+YciPomcXdrMnk= +github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= +github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= +github.com/xiang90/probing v0.0.0-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.9 h1:8x7aARPEXiXbHmtUwAIv7eV2fQFHrLLavdiJ3uzJXoI= +go.etcd.io/bbolt v1.3.9/go.mod h1:zaO32+Ti0PK1ivdPtgMESzuzL2VPoIG1PCQNvOdo/dE= +go.etcd.io/etcd/api/v3 v3.5.16 h1:WvmyJVbjWqK4R1E+B12RRHz3bRGy9XVfh++MgbN+6n0= +go.etcd.io/etcd/api/v3 v3.5.16/go.mod h1:1P4SlIP/VwkDmGo3OlOD7faPeP8KDIFhqvciH5EfN28= +go.etcd.io/etcd/client/pkg/v3 v3.5.16 h1:ZgY48uH6UvB+/7R9Yf4x574uCO3jIx0TRDyetSfId3Q= +go.etcd.io/etcd/client/pkg/v3 v3.5.16/go.mod h1:V8acl8pcEK0Y2g19YlOV9m9ssUe6MgiDSobSoaBAM0E= +go.etcd.io/etcd/client/v2 v2.305.13 h1:RWfV1SX5jTU0lbCvpVQe3iPQeAHETWdOTb6pxhd77C8= +go.etcd.io/etcd/client/v2 v2.305.13/go.mod h1:iQnL7fepbiomdXMb3om1rHq96htNNGv2sJkEcZGDRRg= +go.etcd.io/etcd/client/v3 v3.5.16 h1:sSmVYOAHeC9doqi0gv7v86oY/BTld0SEFGaxsU9eRhE= +go.etcd.io/etcd/client/v3 v3.5.16/go.mod h1:X+rExSGkyqxvu276cr2OwPLBaeqFu1cIl4vmRjAD/50= +go.etcd.io/etcd/pkg/v3 v3.5.13 h1:st9bDWNsKkBNpP4PR1MvM/9NqUPfvYZx/YXegsYEH8M= +go.etcd.io/etcd/pkg/v3 v3.5.13/go.mod h1:N+4PLrp7agI/Viy+dUYpX7iRtSPvKq+w8Y14d1vX+m0= +go.etcd.io/etcd/raft/v3 v3.5.13 h1:7r/NKAOups1YnKcfro2RvGGo2PTuizF/xh26Z2CTAzA= +go.etcd.io/etcd/raft/v3 v3.5.13/go.mod h1:uUFibGLn2Ksm2URMxN1fICGhk8Wu96EfDQyuLhAcAmw= +go.etcd.io/etcd/server/v3 v3.5.13 h1:V6KG+yMfMSqWt+lGnhFpP5z5dRUj1BDRJ5k1fQ9DFok= +go.etcd.io/etcd/server/v3 v3.5.13/go.mod h1:K/8nbsGupHqmr5MkgaZpLlH1QdX1pcNQLAkODy44XcQ= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.53.0 h1:9G6E0TXzGFVfTnawRzrPl83iHOAV7L8NJiR8RSGYV1g= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.53.0/go.mod h1:azvtTADFQJA8mX80jIH/akaE7h+dbm/sVuaHqN13w74= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 h1:4K4tsIXefpVJtvA/8srF4V4y0akAoPHkIslgAkjixJA= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0/go.mod h1:jjdQuTGVsXV4vSs+CJ2qYDeDPf9yIJV23qlIzBm73Vg= +go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo= +go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 h1:3Q/xZUyC1BBkualc9ROb4G8qkH90LXEIICcs5zv1OYY= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0/go.mod h1:s75jGIWA9OfCMzF0xr+ZgfrB5FEbbV7UuYo32ahUiFI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0 h1:qFffATk0X+HD+f1Z8lswGiOQYKHRlzfmdJm0wEaVrFA= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0/go.mod h1:MOiCmryaYtc+V0Ei+Tx9o5S1ZjA7kzLucuVuyzBZloQ= +go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q= +go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s= +go.opentelemetry.io/otel/sdk v1.28.0 h1:b9d7hIry8yZsgtbmM0DKyPWMMUMlK9NEKuIG4aBqWyE= +go.opentelemetry.io/otel/sdk v1.28.0/go.mod h1:oYj7ClPUA7Iw3m+r7GeEjz0qckQRJK2B8zjcZEfu7Pg= +go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= +go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= +go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= +go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= +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= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= +golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= +golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= +golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= +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= +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.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= +golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= +golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= +golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +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.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.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.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= +golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM= +golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8= +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.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= +golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +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.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= +golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= +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= +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/genproto v0.0.0-20231106174013-bbf56f31fb17 h1:wpZ8pe2x1Q3f2KyT5f8oP/fa9rHAKgFPr/HZdNuS+PQ= +google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:J7XzRzVy1+IPwWHZUzoD0IccYZIrXILAQpc+Qy9CMhY= +google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157 h1:7whR9kGa5LUwFtpLm2ArCEejtnxlGeLbAyjFY8sGNFw= +google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157/go.mod h1:99sLkeliLXfdj2J75X3Ho+rrVCaJze0uwN7zDDkjPVU= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 h1:BwIjyKYGsK9dMCBOorzRri8MQwmi7mT9rGHsCEinZkA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= +google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc= +google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= +gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= +gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.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.31.1 h1:Xe1hX/fPW3PXYYv8BlozYqw63ytA92snr96zMW9gWTU= +k8s.io/api v0.31.1/go.mod h1:sbN1g6eY6XVLeqNsZGLnI5FwVseTrZX7Fv3O26rhAaI= +k8s.io/apiextensions-apiserver v0.31.1 h1:L+hwULvXx+nvTYX/MKM3kKMZyei+UiSXQWciX/N6E40= +k8s.io/apiextensions-apiserver v0.31.1/go.mod h1:tWMPR3sgW+jsl2xm9v7lAyRF1rYEK71i9G5dRtkknoQ= +k8s.io/apimachinery v0.31.1 h1:mhcUBbj7KUjaVhyXILglcVjuS4nYXiwC+KKFBgIVy7U= +k8s.io/apimachinery v0.31.1/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo= +k8s.io/apiserver v0.31.1 h1:Sars5ejQDCRBY5f7R3QFHdqN3s61nhkpaX8/k1iEw1c= +k8s.io/apiserver v0.31.1/go.mod h1:lzDhpeToamVZJmmFlaLwdYZwd7zB+WYRYIboqA1kGxM= +k8s.io/client-go v0.31.1 h1:f0ugtWSbWpxHR7sjVpQwuvw9a3ZKLXX0u0itkFXufb0= +k8s.io/client-go v0.31.1/go.mod h1:sKI8871MJN2OyeqRlmA4W4KM9KBdBUpDLu/43eGemCg= +k8s.io/component-base v0.31.1 h1:UpOepcrX3rQ3ab5NB6g5iP0tvsgJWzxTyAo20sgYSy8= +k8s.io/component-base v0.31.1/go.mod h1:WGeaw7t/kTsqpVTaCoVEtillbqAhF2/JgvO0LDOMa0w= +k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= +k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= +k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag= +k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98= +k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 h1:pUdcCO1Lk/tbT5ztQWOBi5HBgbBP1J8+AsQnQCKsi8A= +k8s.io/utils v0.0.0-20240711033017-18e509b52bc8/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.30.3 h1:2770sDpzrjjsAtVhSeUFseziht227YAWYHLGNM8QPwY= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.30.3/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw= +sigs.k8s.io/cluster-api v1.8.2 h1:upWtsmCIAZK82WvEX6T/jlXW7XC6YwCEQFo2FezXH0c= +sigs.k8s.io/cluster-api v1.8.2/go.mod h1:pXv5LqLxuIbhGIXykyNKiJh+KrLweSBajVHHitPLyoY= +sigs.k8s.io/cluster-api/test v1.7.1 h1:QDru2586ZjIFBTW1Z7VVXVtauzR/yANm4tglUNLm9iE= +sigs.k8s.io/cluster-api/test v1.7.1/go.mod h1:yG0g5Mdq73fMn9JP4akgRQPSne973L+Qx6iVH+LjtSM= +sigs.k8s.io/controller-runtime v0.19.0 h1:nWVM7aq+Il2ABxwiCizrVDSlmDcshi9llbaFbC0ji/Q= +sigs.k8s.io/controller-runtime v0.19.0/go.mod h1:iRmWllt8IlaLjvTTDLhRBXIEtkCK6hwVBJJsYS9Ajf4= +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/hack/fake-apiserver/k8s/metal3-fkas-system.yaml b/hack/fake-apiserver/k8s/metal3-fkas-system.yaml new file mode 100644 index 0000000000..bee4726496 --- /dev/null +++ b/hack/fake-apiserver/k8s/metal3-fkas-system.yaml @@ -0,0 +1,71 @@ +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: metal3-fkas-sa + namespace: default +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: metal3-fkas-role +rules: +- apiGroups: ["metal3.io", "infrastructure.cluster.x-k8s.io"] + resources: ["baremetalhosts", "metal3machines"] + verbs: ["get", "list", "watch"] +- apiGroups: ["cluster.x-k8s.io"] + resources: ["machines"] + verbs: ["get", "list", "watch"] +- apiGroups: [""] + resources: ["secrets"] + verbs: ["get", "list"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: metal3-fkas-rolebinding +subjects: +- kind: ServiceAccount + name: metal3-fkas-sa + namespace: default +roleRef: + kind: ClusterRole + name: metal3-fkas-role + apiGroup: rbac.authorization.k8s.io +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: metal3-fkas-system + namespace: default +spec: + replicas: 1 + selector: + matchLabels: + app: metal3-fkas-system + template: + metadata: + labels: + app: metal3-fkas-system + spec: + serviceAccountName: metal3-fkas-sa + containers: + - name: metal3-fkas-reconciler + image: quay.io/metal3-io/metal3-fkas:latest + imagePullPolicy: IfNotPresent + command: ["/reconciler"] + env: + - name: DEBUG + value: "true" + - image: quay.io/metal3-io/metal3-fkas:latest + imagePullPolicy: IfNotPresent + ports: + - containerPort: 3333 + env: + - name: POD_IP + valueFrom: + fieldRef: + fieldPath: status.podIP + - name: DEBUG + value: "true" + name: metal3-fkas diff --git a/hack/fake-apiserver/k8s/metal3-fkas.yaml b/hack/fake-apiserver/k8s/metal3-fkas.yaml new file mode 100644 index 0000000000..9449dde8ab --- /dev/null +++ b/hack/fake-apiserver/k8s/metal3-fkas.yaml @@ -0,0 +1,61 @@ +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: metal3-fkas-sa + namespace: default +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: metal3-fkas-role +rules: +- apiGroups: ["cluster.x-k8s.io"] + resources: ["machines"] + verbs: ["get", "list", "watch"] +- apiGroups: [""] + resources: ["secrets"] + verbs: ["get", "list"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: metal3-fkas-rolebinding +subjects: +- kind: ServiceAccount + name: metal3-fkas-sa + namespace: default +roleRef: + kind: ClusterRole + name: metal3-fkas-role + apiGroup: rbac.authorization.k8s.io +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: metal3-fkas-system + namespace: default +spec: + replicas: 1 + selector: + matchLabels: + app: metal3-fkas-system + template: + metadata: + labels: + app: metal3-fkas-system + spec: + serviceAccountName: metal3-fkas-sa + containers: + - image: quay.io/metal3-io/metal3-fkas:latest + imagePullPolicy: IfNotPresent + ports: + - containerPort: 3333 + env: + - name: POD_IP + valueFrom: + fieldRef: + fieldPath: status.podIP + - name: DEBUG + value: "true" + name: metal3-fkas diff --git a/hack/verify-release.sh b/hack/verify-release.sh index 7400908d8e..809f25dd10 100755 --- a/hack/verify-release.sh +++ b/hack/verify-release.sh @@ -39,7 +39,7 @@ set +o pipefail # enable support for **/go.mod, and make it ignore hack/tools/go.mod shopt -s globstar -GLOBIGNORE=./hack/tools/go.mod +GLOBIGNORE=./hack/tools/go.mod:./hack/fake-apiserver/go.mod # user input VERSION="${1:?release version missing, provide without leading v. Example: 1.5.0}"