From 61495e86b867c981b0e10ff8d49a94e402a0dc90 Mon Sep 17 00:00:00 2001 From: Jacques Massa Date: Wed, 10 Jul 2024 19:01:08 -0400 Subject: [PATCH] add managed cilium crd operator --- Makefile | 7 +- deploy/hubble/grafana/dashboards/README.md | 3 + .../retina/templates/operator/deployment.yaml | 12 + .../controller/helm/retina/values.yaml | 10 +- .../helm/retina/templates/operator.yaml | 16 +- .../controller/helm/retina/values.yaml | 6 + get-certs.sh | 30 + go.mod | 64 ++ go.sum | 183 +++++ operator/Dockerfile | 17 +- operator/cilium-crds/config/config_linux.go | 32 + operator/cilium-crds/k8s/LICENSE | 201 +++++ operator/cilium-crds/k8s/apis/cell.go | 76 ++ operator/cilium-crds/k8s/apis/register.go | 135 ++++ operator/cilium-crds/k8s/resource_ctors.go | 62 ++ operator/cilium-crds/k8s/resources.go | 54 ++ operator/cmd/cilium-crds/LICENSE | 201 +++++ operator/cmd/cilium-crds/cells_linux.go | 252 +++++++ operator/cmd/cilium-crds/flags.go | 80 ++ operator/cmd/cilium-crds/flags_provider.go | 19 + operator/cmd/cilium-crds/lifecycle.go | 38 + operator/cmd/cilium-crds/metrics.go | 18 + operator/cmd/cilium-crds/root_linux.go | 202 +++++ operator/cmd/cilium-crds/zap_linux.go | 80 ++ operator/cmd/cilium_crds_cmd_linux.go | 47 ++ operator/cmd/legacy/deployment.go | 290 +++++++ operator/cmd/root.go | 48 ++ operator/config/config.go | 27 +- operator/main.go | 296 +------- .../operator/cilium-crds/cache/types.go | 12 + .../operator/cilium-crds/endpoint/cell.go | 9 + .../endpoint/endpoint_controller.go | 685 +++++++++++++++++ .../endpoint/endpoint_controller_test.go | 709 ++++++++++++++++++ .../cilium-crds/endpoint/identitymanager.go | 128 ++++ .../endpoint/identitymanager_test.go | 145 ++++ .../operator/cilium-crds/endpoint/types.go | 151 ++++ pkg/utils/testutil/cilium/endpoint_client.go | 145 ++++ pkg/utils/testutil/cilium/errors.go | 28 + pkg/utils/testutil/cilium/errors_test.go | 14 + pkg/utils/testutil/cilium/identity_client.go | 120 +++ pkg/utils/testutil/cilium/resource.go | 122 +++ pkg/utils/testutil/cilium/versioned_client.go | 121 +++ test/e2e/retina_e2e_test.go | 11 +- 43 files changed, 4605 insertions(+), 301 deletions(-) create mode 100644 deploy/hubble/grafana/dashboards/README.md create mode 100755 get-certs.sh create mode 100644 operator/cilium-crds/config/config_linux.go create mode 100644 operator/cilium-crds/k8s/LICENSE create mode 100644 operator/cilium-crds/k8s/apis/cell.go create mode 100644 operator/cilium-crds/k8s/apis/register.go create mode 100644 operator/cilium-crds/k8s/resource_ctors.go create mode 100644 operator/cilium-crds/k8s/resources.go create mode 100644 operator/cmd/cilium-crds/LICENSE create mode 100644 operator/cmd/cilium-crds/cells_linux.go create mode 100644 operator/cmd/cilium-crds/flags.go create mode 100644 operator/cmd/cilium-crds/flags_provider.go create mode 100644 operator/cmd/cilium-crds/lifecycle.go create mode 100644 operator/cmd/cilium-crds/metrics.go create mode 100644 operator/cmd/cilium-crds/root_linux.go create mode 100644 operator/cmd/cilium-crds/zap_linux.go create mode 100644 operator/cmd/cilium_crds_cmd_linux.go create mode 100644 operator/cmd/legacy/deployment.go create mode 100644 operator/cmd/root.go create mode 100644 pkg/controllers/operator/cilium-crds/cache/types.go create mode 100644 pkg/controllers/operator/cilium-crds/endpoint/cell.go create mode 100644 pkg/controllers/operator/cilium-crds/endpoint/endpoint_controller.go create mode 100644 pkg/controllers/operator/cilium-crds/endpoint/endpoint_controller_test.go create mode 100644 pkg/controllers/operator/cilium-crds/endpoint/identitymanager.go create mode 100644 pkg/controllers/operator/cilium-crds/endpoint/identitymanager_test.go create mode 100644 pkg/controllers/operator/cilium-crds/endpoint/types.go create mode 100644 pkg/utils/testutil/cilium/endpoint_client.go create mode 100644 pkg/utils/testutil/cilium/errors.go create mode 100644 pkg/utils/testutil/cilium/errors_test.go create mode 100644 pkg/utils/testutil/cilium/identity_client.go create mode 100644 pkg/utils/testutil/cilium/resource.go create mode 100644 pkg/utils/testutil/cilium/versioned_client.go diff --git a/Makefile b/Makefile index a35a0dfcf9..3b7bd39cb2 100644 --- a/Makefile +++ b/Makefile @@ -49,6 +49,10 @@ ALL_ARCH.windows = amd64 ENABLE_TLS ?= true CERT_DIR := $(REPO_ROOT)/.certs +CERT_FILES := tls.crt:tls-client-cert-file \ + tls.key:tls-client-key-file \ + ca.crt:tls-ca-cert-files + # TAG is OS and platform agonstic, which can be used for binary version and image manifest tag, # while RETINA_PLATFORM_TAG is platform specific, which can be used for image built for specific platforms. RETINA_PLATFORM_TAG ?= $(TAG)-$(subst /,-,$(PLATFORM)) @@ -525,7 +529,8 @@ quick-deploy-hubble: $(MAKE) helm-uninstall || true $(MAKE) helm-install-without-tls HELM_IMAGE_TAG=$(TAG)-linux-amd64 + .PHONY: simplify-dashboards simplify-dashboards: - cd deploy/legacy/graphana/dashboards && go test . -tags=dashboard,simplifydashboard -v && cd $(REPO_ROOT) + cd deploy/legacy/grafana/dashboards && go test . -tags=dashboard,simplifydashboard -v && cd $(REPO_ROOT) diff --git a/deploy/hubble/grafana/dashboards/README.md b/deploy/hubble/grafana/dashboards/README.md new file mode 100644 index 0000000000..aaf58c0147 --- /dev/null +++ b/deploy/hubble/grafana/dashboards/README.md @@ -0,0 +1,3 @@ +# DAshboards + +Dashboards here are a copy of dashboards in the Dashboard [https://msazure.visualstudio.com/One/_git/Azure-Observability-ECG-Grafana-DashboardAuthoring?path=/Azure%20Dashboards/Managed%20Prometheus/Network-Observability] Authoring Repo. diff --git a/deploy/hubble/manifests/controller/helm/retina/templates/operator/deployment.yaml b/deploy/hubble/manifests/controller/helm/retina/templates/operator/deployment.yaml index 60bbf52647..4a43317785 100644 --- a/deploy/hubble/manifests/controller/helm/retina/templates/operator/deployment.yaml +++ b/deploy/hubble/manifests/controller/helm/retina/templates/operator/deployment.yaml @@ -54,6 +54,18 @@ spec: image: {{ .Values.operator.repository }}:{{ .Values.operator.tag }} imagePullPolicy: {{ .Values.operator.pullPolicy }} name: retina-operator + {{- if .Values.operator.container.command }} + command: + {{- range .Values.operator.container.command }} + - {{ . }} + {{- end }} + {{- end }} + {{- if .Values.operator.container.args}} + args: + {{- range $.Values.operator.container.args}} + - {{ . | quote }} + {{- end}} + {{- end}} env: # this env var is used by retina OSS telemetry and zap - name: POD_NAME diff --git a/deploy/hubble/manifests/controller/helm/retina/values.yaml b/deploy/hubble/manifests/controller/helm/retina/values.yaml index 75b91a54e7..5a01a753ed 100644 --- a/deploy/hubble/manifests/controller/helm/retina/values.yaml +++ b/deploy/hubble/manifests/controller/helm/retina/values.yaml @@ -22,6 +22,13 @@ operator: leaderElection: true identityGCInterval: 15m # cilium default endpointGCInterval: 5m # cilium default + container: + command: + - "/retina-operator" + args: + - "manage-cilium-crds" + - "--config-dir" + - "/retina" agent: leaderElection: false @@ -32,7 +39,8 @@ agent: init: enabled: true name: retina-agent-init - repository: acndev.azurecr.io/retina-agent-init + repository: ghcr.io/microsoft/retina/retina-init + tag: "latest" pullPolicy: Always container: diff --git a/deploy/legacy/manifests/controller/helm/retina/templates/operator.yaml b/deploy/legacy/manifests/controller/helm/retina/templates/operator.yaml index 248cd24585..f8ac9e0459 100644 --- a/deploy/legacy/manifests/controller/helm/retina/templates/operator.yaml +++ b/deploy/legacy/manifests/controller/helm/retina/templates/operator.yaml @@ -50,10 +50,20 @@ spec: securityContext: runAsNonRoot: true containers: - - command: - - /retina-operator - image: {{ .Values.operator.repository }}:{{ .Values.operator.tag }} + - image: {{ .Values.operator.repository }}:{{ .Values.operator.tag }} name: retina-operator + {{- if .Values.operator.container.command }} + command: + {{- range .Values.operator.container.command }} + - {{ . }} + {{- end }} + {{- end }} + {{- if .Values.operator.container.args}} + args: + {{- range $.Values.operator.container.args}} + - {{ . | quote }} + {{- end}} + {{- end}} volumeMounts: - name: retina-operator-config mountPath: /retina/ diff --git a/deploy/legacy/manifests/controller/helm/retina/values.yaml b/deploy/legacy/manifests/controller/helm/retina/values.yaml index ddb6f84303..19174e56c2 100644 --- a/deploy/legacy/manifests/controller/helm/retina/values.yaml +++ b/deploy/legacy/manifests/controller/helm/retina/values.yaml @@ -23,6 +23,12 @@ operator: requests: cpu: 10m memory: 128Mi + container: + command: + - "/retina-operator" + args: + - "--config" + - "/retina/operator-config.yaml" image: repository: ghcr.io/microsoft/retina/retina-agent diff --git a/get-certs.sh b/get-certs.sh new file mode 100755 index 0000000000..880a77bdcb --- /dev/null +++ b/get-certs.sh @@ -0,0 +1,30 @@ +#!/usr/bin/env bash + +set -euo pipefail +set -x + +# Directory where certificates will be stored +CERT_DIR="$(pwd)/.certs" +mkdir -p "$CERT_DIR" + +declare -A CERT_FILES=( + ["tls.crt"]="tls-client-cert-file" + ["tls.key"]="tls-client-key-file" + ["ca.crt"]="tls-ca-cert-files" +) + +for FILE in "${!CERT_FILES[@]}"; do + KEY="${CERT_FILES[$FILE]}" + JSONPATH="{.data['${FILE//./\\.}']}" + + # Retrieve the secret and decode it + kubectl get secret hubble-relay-client-certs -n kube-system \ + -o jsonpath="${JSONPATH}" | \ + base64 -d > "$CERT_DIR/$FILE" + + # Set the appropriate hubble CLI config + hubble config set "$KEY" "$CERT_DIR/$FILE" +done + +hubble config set tls true +hubble config set tls-server-name instance.hubble-relay.cilium.io diff --git a/go.mod b/go.mod index 4bdf0f6fbd..0ed4e4ea1b 100644 --- a/go.mod +++ b/go.mod @@ -3,11 +3,13 @@ module github.com/microsoft/retina go 1.22.3 require ( + github.com/go-chi/chi/v5 v5.1.0 github.com/go-chi/chi/v5 v5.1.0 github.com/google/uuid v1.6.0 github.com/prometheus/client_golang v1.19.1 github.com/spf13/cobra v1.8.1 go.uber.org/zap v1.27.0 + go.uber.org/zap v1.27.0 k8s.io/client-go v0.30.1 ) @@ -37,6 +39,10 @@ require ( github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.9 // indirect github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.13 // indirect github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.13 // indirect + github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.3 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.9 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.13 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.13 // indirect github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 // indirect github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.13 // indirect github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3 // indirect @@ -47,6 +53,15 @@ require ( github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.2 // indirect github.com/aws/aws-sdk-go-v2/service/sts v1.30.1 // indirect github.com/aws/smithy-go v1.20.3 // indirect + github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.13 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.15 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.15 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.13 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.22.1 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.2 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.30.1 // indirect + github.com/aws/smithy-go v1.20.3 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/blang/semver/v4 v4.0.0 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect @@ -54,6 +69,9 @@ require ( github.com/cilium/dns v1.1.51-0.20231120140355-729345173dc3 // indirect github.com/cilium/lumberjack/v2 v2.3.0 // indirect github.com/cilium/stream v0.0.0-20240226091623-f979d32855f8 // indirect + github.com/cilium/dns v1.1.51-0.20231120140355-729345173dc3 // indirect + github.com/cilium/lumberjack/v2 v2.3.0 // indirect + github.com/cilium/stream v0.0.0-20240226091623-f979d32855f8 // indirect github.com/cncf/xds/go v0.0.0-20231128003011-0fa0005c9caa // indirect github.com/containerd/cgroups/v3 v3.0.2 // indirect github.com/containerd/containerd v1.7.14 // indirect @@ -64,6 +82,7 @@ require ( github.com/containerd/ttrpc v1.2.3 // indirect github.com/containerd/typeurl/v2 v2.1.1 // indirect github.com/containernetworking/cni v1.1.2 // indirect + github.com/containernetworking/cni v1.1.2 // indirect github.com/coreos/go-semver v0.3.1 // indirect github.com/coreos/go-systemd/v22 v22.5.0 // indirect github.com/cyphar/filepath-securejoin v0.2.4 // indirect @@ -80,6 +99,7 @@ require ( github.com/emicklei/go-restful/v3 v3.11.2 // indirect github.com/envoyproxy/protoc-gen-validate v1.0.4 // indirect github.com/evanphx/json-patch v5.9.0+incompatible // indirect + github.com/evanphx/json-patch v5.9.0+incompatible // indirect github.com/evanphx/json-patch/v5 v5.9.0 // indirect github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect github.com/fatih/color v1.16.0 // indirect @@ -87,6 +107,7 @@ require ( github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/go-errors/errors v1.4.2 // indirect github.com/go-gorp/gorp/v3 v3.1.0 // indirect + github.com/go-jose/go-jose/v3 v3.0.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/analysis v0.23.0 // indirect github.com/go-openapi/errors v0.22.0 // indirect @@ -98,6 +119,16 @@ require ( github.com/go-openapi/strfmt v0.23.0 // indirect github.com/go-openapi/swag v0.23.0 // indirect github.com/go-openapi/validate v0.24.0 // indirect + github.com/go-openapi/analysis v0.23.0 // indirect + github.com/go-openapi/errors v0.22.0 // indirect + github.com/go-openapi/jsonpointer v0.21.0 // indirect + github.com/go-openapi/jsonreference v0.21.0 // indirect + github.com/go-openapi/loads v0.22.0 // indirect + github.com/go-openapi/runtime v0.28.0 // indirect + github.com/go-openapi/spec v0.21.0 // indirect + github.com/go-openapi/strfmt v0.23.0 // indirect + github.com/go-openapi/swag v0.23.0 // indirect + github.com/go-openapi/validate v0.24.0 // indirect github.com/go-task/slim-sprig/v3 v3.0.0 // indirect github.com/gobwas/glob v0.2.3 // indirect github.com/godbus/dbus/v5 v5.1.0 // indirect @@ -108,8 +139,10 @@ require ( github.com/google/btree v1.1.2 // indirect github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49 // indirect github.com/google/gops v0.3.27 // indirect + github.com/google/gops v0.3.27 // indirect github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6 // indirect github.com/google/renameio/v2 v2.0.0 // indirect + github.com/google/renameio/v2 v2.0.0 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/gorilla/mux v1.8.1 // indirect github.com/gorilla/websocket v1.5.1 // indirect @@ -121,6 +154,7 @@ require ( github.com/hashicorp/go-hclog v1.5.0 // indirect github.com/hashicorp/go-immutable-radix v1.3.1 // indirect github.com/hashicorp/go-immutable-radix/v2 v2.1.0 // indirect + github.com/hashicorp/go-immutable-radix/v2 v2.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-rootcerts v1.0.2 // indirect github.com/hashicorp/golang-lru v0.5.4 // indirect @@ -141,6 +175,7 @@ require ( github.com/lib/pq v1.10.9 // indirect github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect github.com/mackerelio/go-osstat v0.2.4 // indirect + github.com/mackerelio/go-osstat v0.2.4 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect @@ -177,6 +212,7 @@ require ( github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/procfs v0.15.1 // indirect + github.com/prometheus/procfs v0.15.1 // indirect github.com/rogpeppe/go-internal v1.12.0 // indirect github.com/rubenv/sql-migrate v1.5.2 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect @@ -188,21 +224,29 @@ require ( github.com/sourcegraph/conc v0.3.0 // indirect github.com/spf13/afero v1.11.0 // indirect github.com/spf13/cast v1.6.0 // indirect + github.com/spiffe/go-spiffe/v2 v2.1.7 // indirect + github.com/spiffe/spire-api-sdk v1.9.1 // indirect github.com/subosito/gotenv v1.6.0 // indirect github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 // indirect github.com/tidwall/gjson v1.17.1 // indirect github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/pretty v1.2.0 // indirect github.com/tidwall/sjson v1.2.5 // indirect + github.com/tidwall/gjson v1.17.1 // indirect + github.com/tidwall/match v1.1.1 // indirect + github.com/tidwall/pretty v1.2.0 // indirect + github.com/tidwall/sjson v1.2.5 // indirect github.com/vishvananda/netns v0.0.4 // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect github.com/xeipuuv/gojsonschema v1.2.0 // indirect github.com/xlab/treeprint v1.2.0 // indirect + github.com/zeebo/errs v1.3.0 // indirect go.etcd.io/etcd/api/v3 v3.5.12 // indirect go.etcd.io/etcd/client/pkg/v3 v3.5.12 // indirect go.etcd.io/etcd/client/v3 v3.5.12 // indirect go.mongodb.org/mongo-driver v1.14.0 // indirect + go.mongodb.org/mongo-driver v1.14.0 // indirect go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect go.starlark.net v0.0.0-20230814145427-12f4cb8177e4 // indirect @@ -217,6 +261,7 @@ require ( google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240311132316-a219d84964c2 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect google.golang.org/grpc v1.62.1 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect @@ -231,6 +276,7 @@ require ( require ( github.com/go-chi/chi v4.1.2+incompatible github.com/go-logr/logr v1.4.2 // indirect + github.com/go-logr/logr v1.4.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/google/go-cmp v0.6.0 @@ -244,8 +290,10 @@ require ( go.uber.org/multierr v1.11.0 // indirect golang.org/x/net v0.26.0 // indirect golang.org/x/oauth2 v0.21.0 // indirect + golang.org/x/oauth2 v0.21.0 // indirect golang.org/x/sync v0.7.0 golang.org/x/sys v0.22.0 + golang.org/x/sys v0.22.0 golang.org/x/term v0.21.0 // indirect google.golang.org/protobuf v1.34.2 gopkg.in/yaml.v2 v2.4.0 // indirect @@ -265,10 +313,12 @@ require ( github.com/Azure/azure-sdk-for-go v68.0.0+incompatible github.com/Azure/azure-sdk-for-go/sdk/azcore v1.12.0 github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0 + github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/containerservice/armcontainerservice/v4 v4.8.0 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dashboard/armdashboard v1.2.0 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/monitor/armmonitor v0.11.0 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v5 v5.2.0 + github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v5 v5.2.0 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.2.0 github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.3.2 github.com/Microsoft/hcsshim v0.12.0-rc.3 @@ -277,15 +327,22 @@ require ( github.com/aws/aws-sdk-go-v2/config v1.27.24 github.com/aws/aws-sdk-go-v2/credentials v1.17.24 github.com/aws/aws-sdk-go-v2/service/s3 v1.58.0 + github.com/aws/aws-sdk-go-v2 v1.30.1 + github.com/aws/aws-sdk-go-v2/config v1.27.24 + github.com/aws/aws-sdk-go-v2/credentials v1.17.24 + github.com/aws/aws-sdk-go-v2/service/s3 v1.58.0 github.com/cakturk/go-netstat v0.0.0-20200220111822-e5b49efee7a5 github.com/cilium/cilium v1.16.0-pre.1.0.20240403152809-b9853ecbcaeb + github.com/cilium/cilium v1.16.0-pre.1.0.20240403152809-b9853ecbcaeb github.com/cilium/ebpf v0.15.0 github.com/cilium/proxy v0.0.0-20231031145409-f19708f3d018 + github.com/cilium/proxy v0.0.0-20231031145409-f19708f3d018 github.com/cilium/workerpool v1.2.0 github.com/florianl/go-tc v0.4.3 github.com/go-logr/zapr v1.3.0 github.com/google/gopacket v1.1.19 github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 + github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 github.com/inspektor-gadget/inspektor-gadget v0.27.0 github.com/jellydator/ttlcache/v3 v3.1.1 github.com/jsternberg/zap-logfmt v1.3.0 @@ -296,6 +353,8 @@ require ( github.com/prometheus/client_model v0.6.1 github.com/prometheus/common v0.55.0 github.com/safchain/ethtool v0.4.1 + github.com/prometheus/common v0.55.0 + github.com/safchain/ethtool v0.4.1 github.com/sirupsen/logrus v1.9.3 github.com/spf13/viper v1.19.0 github.com/vishvananda/netlink v1.2.1-beta.2.0.20240524165444-4d4ba1473f21 @@ -303,8 +362,13 @@ require ( go.opentelemetry.io/otel v1.28.0 go.opentelemetry.io/otel/metric v1.28.0 go.opentelemetry.io/otel/trace v1.28.0 + go.etcd.io/etcd v3.3.27+incompatible + go.opentelemetry.io/otel v1.28.0 + go.opentelemetry.io/otel/metric v1.28.0 + go.opentelemetry.io/otel/trace v1.28.0 go.uber.org/mock v0.4.0 golang.org/x/exp v0.0.0-20240318143956-a85f2c67cd81 + golang.org/x/exp v0.0.0-20240318143956-a85f2c67cd81 gopkg.in/natefinch/lumberjack.v2 v2.2.1 gotest.tools v2.2.0+incompatible gotest.tools/v3 v3.5.1 diff --git a/go.sum b/go.sum index 3e3213aeb9..6c88a3ab12 100644 --- a/go.sum +++ b/go.sum @@ -1,4 +1,5 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= code.cloudfoundry.org/clock v0.0.0-20180518195852-02e53af36e6c/go.mod h1:QD9Lzhd/ux6eNQVUDVRJX/RKTigpewimNYBi7ivZKY8= code.cloudfoundry.org/clock v1.0.0 h1:kFXWQM4bxYvdBw2X8BbBeXwQNgfoWv1vqAk2ZZyBN2o= code.cloudfoundry.org/clock v1.0.0/go.mod h1:QD9Lzhd/ux6eNQVUDVRJX/RKTigpewimNYBi7ivZKY8= @@ -14,6 +15,8 @@ github.com/Azure/azure-sdk-for-go/sdk/azcore v1.12.0 h1:1nGuui+4POelzDwI7RG56yfQ github.com/Azure/azure-sdk-for-go/sdk/azcore v1.12.0/go.mod h1:99EvauvlcJ1U06amZiksfYz/3aFGyIhWGHVyiZXtBAI= github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0 h1:tfLQ34V6F7tVSwoTf/4lH5sE0o6eCJuNDTmH09nDpbc= github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0/go.mod h1:9kIvujWAA58nmPmWB1m23fyWic1kYZMxD9CxaWn4Qpg= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0 h1:tfLQ34V6F7tVSwoTf/4lH5sE0o6eCJuNDTmH09nDpbc= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0/go.mod h1:9kIvujWAA58nmPmWB1m23fyWic1kYZMxD9CxaWn4Qpg= github.com/Azure/azure-sdk-for-go/sdk/internal v1.9.0 h1:H+U3Gk9zY56G3u872L82bk4thcsy2Gghb9ExT4Zvm1o= github.com/Azure/azure-sdk-for-go/sdk/internal v1.9.0/go.mod h1:mgrmMSgaLp9hmax62XQTd0N4aAqSE5E0DulSpVYK7vc= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/containerservice/armcontainerservice/v4 v4.8.0 h1:0nGmzwBv5ougvzfGPCO2ljFRHvun57KpNrVCMrlk0ns= @@ -24,12 +27,16 @@ github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal/v2 v2.0.0 h1:PTFG github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal/v2 v2.0.0/go.mod h1:LRr2FzBTQlONPPa5HREE5+RjSCTXl7BwOvYOaWTqCaI= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal/v3 v3.0.0 h1:Kb8eVvjdP6kZqYnER5w/PiGCFp91yVgaxve3d7kCEpY= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal/v3 v3.0.0/go.mod h1:lYq15QkJyEsNegz5EhI/0SXQ6spvGfgwBH/Qyzkoc/s= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal/v3 v3.0.0 h1:Kb8eVvjdP6kZqYnER5w/PiGCFp91yVgaxve3d7kCEpY= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal/v3 v3.0.0/go.mod h1:lYq15QkJyEsNegz5EhI/0SXQ6spvGfgwBH/Qyzkoc/s= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/managementgroups/armmanagementgroups v1.0.0 h1:pPvTJ1dY0sA35JOeFq6TsY2xj6Z85Yo23Pj4wCCvu4o= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/managementgroups/armmanagementgroups v1.0.0/go.mod h1:mLfWfj8v3jfWKsL9G4eoBoXVcsqcIUTapmdKy7uGOp0= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/monitor/armmonitor v0.11.0 h1:Ds0KRF8ggpEGg4Vo42oX1cIt/IfOhHWJBikksZbVxeg= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/monitor/armmonitor v0.11.0/go.mod h1:jj6P8ybImR+5topJ+eH6fgcemSFBmU6/6bFF8KkwuDI= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v5 v5.2.0 h1:qBlqTo40ARdI7Pmq+enBiTnejZk2BF+PHgktgG8k3r8= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v5 v5.2.0/go.mod h1:UmyOatRyQodVpp55Jr5WJmnkmVW4wKfo85uHFmMEjfM= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v5 v5.2.0 h1:qBlqTo40ARdI7Pmq+enBiTnejZk2BF+PHgktgG8k3r8= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v5 v5.2.0/go.mod h1:UmyOatRyQodVpp55Jr5WJmnkmVW4wKfo85uHFmMEjfM= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.2.0 h1:Dd+RhdJn0OTtVGaeDLZpcumkIVCtA/3/Fo42+eoYvVM= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.2.0/go.mod h1:5kakwfW5CjC9KK+Q4wjXAg+ShuIm2mBMua0ZFj2C8PE= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.5.0 h1:AifHbc4mg0x9zW52WOpKbsHaDKuRhlI7TVl47thgQ70= @@ -87,6 +94,7 @@ github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuy github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-metrics v0.4.1 h1:hR91U9KYmb6bLBYLQjyM+3j+rcd/UhE+G78SFnF8gJA= @@ -111,6 +119,20 @@ github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.13 h1:5SAoZ4jYpGH4721ZN github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.13/go.mod h1:+rdA6ZLpaSeM7tSg/B0IEDinCIBJGmW8rKDFkYpP04g= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.13 h1:WIijqeaAO7TYFLbhsZmi2rgLEAtWOC1LhxCAVTJlSKw= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.13/go.mod h1:i+kbfa76PQbWw/ULoWnp51EYVWH4ENln76fLQE3lXT8= +github.com/aws/aws-sdk-go-v2 v1.30.1 h1:4y/5Dvfrhd1MxRDD77SrfsDaj8kUkkljU7XE83NPV+o= +github.com/aws/aws-sdk-go-v2 v1.30.1/go.mod h1:nIQjQVp5sfpQcTc9mPSr1B0PaWK5ByX9MOoDadSN4lc= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.3 h1:tW1/Rkad38LA15X4UQtjXZXNKsCgkshC3EbmcUmghTg= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.3/go.mod h1:UbnqO+zjqk3uIt9yCACHJ9IVNhyhOCnYk8yA19SAWrM= +github.com/aws/aws-sdk-go-v2/config v1.27.24 h1:NM9XicZ5o1CBU/MZaHwFtimRpWx9ohAUAqkG6AqSqPo= +github.com/aws/aws-sdk-go-v2/config v1.27.24/go.mod h1:aXzi6QJTuQRVVusAO8/NxpdTeTyr/wRcybdDtfUwJSs= +github.com/aws/aws-sdk-go-v2/credentials v1.17.24 h1:YclAsrnb1/GTQNt2nzv+756Iw4mF8AOzcDfweWwwm/M= +github.com/aws/aws-sdk-go-v2/credentials v1.17.24/go.mod h1:Hld7tmnAkoBQdTMNYZGzztzKRdA4fCdn9L83LOoigac= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.9 h1:Aznqksmd6Rfv2HQN9cpqIV/lQRMaIpJkLLaJ1ZI76no= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.9/go.mod h1:WQr3MY7AxGNxaqAtsDWn+fBxmd4XvLkzeqQ8P1VM0/w= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.13 h1:5SAoZ4jYpGH4721ZNoS1znQrhOfZinOhc4XuTXx/nVc= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.13/go.mod h1:+rdA6ZLpaSeM7tSg/B0IEDinCIBJGmW8rKDFkYpP04g= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.13 h1:WIijqeaAO7TYFLbhsZmi2rgLEAtWOC1LhxCAVTJlSKw= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.13/go.mod h1:i+kbfa76PQbWw/ULoWnp51EYVWH4ENln76fLQE3lXT8= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 h1:hT8rVHwugYE2lEfdFE0QWVo81lF7jMrYJVDWI+f+VxU= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0/go.mod h1:8tu/lYfQfFe6IGnaOdrpVgEL2IrrDOf6/m9RQum4NkY= github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.13 h1:THZJJ6TU/FOiM7DZFnisYV9d49oxXWUzsVIMTuf3VNU= @@ -133,6 +155,26 @@ github.com/aws/aws-sdk-go-v2/service/sts v1.30.1 h1:+woJ607dllHJQtsnJLi52ycuqHMw github.com/aws/aws-sdk-go-v2/service/sts v1.30.1/go.mod h1:jiNR3JqT15Dm+QWq2SRgh0x0bCNSRP2L25+CqPNpJlQ= github.com/aws/smithy-go v1.20.3 h1:ryHwveWzPV5BIof6fyDvor6V3iUL7nTfiTKXHiW05nE= github.com/aws/smithy-go v1.20.3/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.13 h1:THZJJ6TU/FOiM7DZFnisYV9d49oxXWUzsVIMTuf3VNU= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.13/go.mod h1:VISUTg6n+uBaYIWPBaIG0jk7mbBxm7DUqBtU2cUDDWI= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3 h1:dT3MqvGhSoaIhRseqw2I0yH81l7wiR2vjs57O51EAm8= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3/go.mod h1:GlAeCkHwugxdHaueRr4nhPuY+WW+gR8UjlcqzPr1SPI= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.15 h1:2jyRZ9rVIMisyQRnhSS/SqlckveoxXneIumECVFP91Y= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.15/go.mod h1:bDRG3m382v1KJBk1cKz7wIajg87/61EiiymEyfLvAe0= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.15 h1:I9zMeF107l0rJrpnHpjEiiTSCKYAIw8mALiXcPsGBiA= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.15/go.mod h1:9xWJ3Q/S6Ojusz1UIkfycgD1mGirJfLLKqq3LPT7WN8= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.13 h1:Eq2THzHt6P41mpjS2sUzz/3dJYFRqdWZ+vQaEMm98EM= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.13/go.mod h1:FgwTca6puegxgCInYwGjmd4tB9195Dd6LCuA+8MjpWw= +github.com/aws/aws-sdk-go-v2/service/s3 v1.58.0 h1:4rhV0Hn+bf8IAIUphRX1moBcEvKJipCPmswMCl6Q5mw= +github.com/aws/aws-sdk-go-v2/service/s3 v1.58.0/go.mod h1:hdV0NTYd0RwV4FvNKhKUNbPLZoq9CTr/lke+3I7aCAI= +github.com/aws/aws-sdk-go-v2/service/sso v1.22.1 h1:p1GahKIjyMDZtiKoIn0/jAj/TkMzfzndDv5+zi2Mhgc= +github.com/aws/aws-sdk-go-v2/service/sso v1.22.1/go.mod h1:/vWdhoIoYA5hYoPZ6fm7Sv4d8701PiG5VKe8/pPJL60= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.2 h1:ORnrOK0C4WmYV/uYt3koHEWBLYsRDwk2Np+eEoyV4Z0= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.2/go.mod h1:xyFHA4zGxgYkdD73VeezHt3vSKEG9EmFnGwoKlP00u4= +github.com/aws/aws-sdk-go-v2/service/sts v1.30.1 h1:+woJ607dllHJQtsnJLi52ycuqHMwlW+Wqm2Ppsfp4nQ= +github.com/aws/aws-sdk-go-v2/service/sts v1.30.1/go.mod h1:jiNR3JqT15Dm+QWq2SRgh0x0bCNSRP2L25+CqPNpJlQ= +github.com/aws/smithy-go v1.20.3 h1:ryHwveWzPV5BIof6fyDvor6V3iUL7nTfiTKXHiW05nE= +github.com/aws/smithy-go v1.20.3/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= @@ -169,6 +211,10 @@ github.com/cilium/cilium v1.16.0-pre.1.0.20240403152809-b9853ecbcaeb h1:77M/pRhF github.com/cilium/cilium v1.16.0-pre.1.0.20240403152809-b9853ecbcaeb/go.mod h1:ks3XSifKcx50E7JwdyKssalQ10xvwC+/sHmpBvDezmM= github.com/cilium/dns v1.1.51-0.20231120140355-729345173dc3 h1:3PErIjIq4DlOwNsQNPcILFzbGnxPuKuqJsHEFpiwstM= github.com/cilium/dns v1.1.51-0.20231120140355-729345173dc3/go.mod h1:/7LC2GOgyXJ7maupZlaVIumYQiGPIgllSf6mA9sg6RU= +github.com/cilium/cilium v1.16.0-pre.1.0.20240403152809-b9853ecbcaeb h1:77M/pRhFWIImKh9KNCbx+afVN9E8zBmySuoKnw9wkWQ= +github.com/cilium/cilium v1.16.0-pre.1.0.20240403152809-b9853ecbcaeb/go.mod h1:ks3XSifKcx50E7JwdyKssalQ10xvwC+/sHmpBvDezmM= +github.com/cilium/dns v1.1.51-0.20231120140355-729345173dc3 h1:3PErIjIq4DlOwNsQNPcILFzbGnxPuKuqJsHEFpiwstM= +github.com/cilium/dns v1.1.51-0.20231120140355-729345173dc3/go.mod h1:/7LC2GOgyXJ7maupZlaVIumYQiGPIgllSf6mA9sg6RU= github.com/cilium/ebpf v0.5.0/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs= github.com/cilium/ebpf v0.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2usCA= github.com/cilium/ebpf v0.8.1/go.mod h1:f5zLIM0FSNuAkSyLAN7X+Hy6yznlF1mNiWUMfxMtrgk= @@ -178,16 +224,27 @@ github.com/cilium/fake v0.6.1 h1:cLkNx1nkF0b0pPW79JaQxaI5oG2/rBzRKpp0YUg1fTA= github.com/cilium/fake v0.6.1/go.mod h1:V9lCbbcsnSf3vB6sdOP7Q0bsUUJ/jyHPZxnFAw5nPUc= github.com/cilium/lumberjack/v2 v2.3.0 h1:IhVJMvPpqDYmQzC0KDhAoy7KlaRsyOsZnT97Nsa3u0o= github.com/cilium/lumberjack/v2 v2.3.0/go.mod h1:yfbtPGmg4i//5oEqzaMxDqSWqgfZFmMoV70Mc2k6v0A= +github.com/cilium/fake v0.6.1 h1:cLkNx1nkF0b0pPW79JaQxaI5oG2/rBzRKpp0YUg1fTA= +github.com/cilium/fake v0.6.1/go.mod h1:V9lCbbcsnSf3vB6sdOP7Q0bsUUJ/jyHPZxnFAw5nPUc= +github.com/cilium/lumberjack/v2 v2.3.0 h1:IhVJMvPpqDYmQzC0KDhAoy7KlaRsyOsZnT97Nsa3u0o= +github.com/cilium/lumberjack/v2 v2.3.0/go.mod h1:yfbtPGmg4i//5oEqzaMxDqSWqgfZFmMoV70Mc2k6v0A= github.com/cilium/proxy v0.0.0-20231031145409-f19708f3d018 h1:R/QlThqx099hS6req1k2Q87fvLSRgCEicQGate9vxO4= github.com/cilium/proxy v0.0.0-20231031145409-f19708f3d018/go.mod h1:p044XccCmONGIUbx3bJ7qvHXK0RcrdvIvbTGiu/RjUA= github.com/cilium/stream v0.0.0-20240226091623-f979d32855f8 h1:j6VF1s6gz3etRH5ObCr0UUyJblP9cK5fbgkQTz8fTRA= github.com/cilium/stream v0.0.0-20240226091623-f979d32855f8/go.mod h1:/e83AwqvNKpyg4n3C41qmnmj1x2G9DwzI+jb7GkF4lI= +github.com/cilium/stream v0.0.0-20240226091623-f979d32855f8 h1:j6VF1s6gz3etRH5ObCr0UUyJblP9cK5fbgkQTz8fTRA= +github.com/cilium/stream v0.0.0-20240226091623-f979d32855f8/go.mod h1:/e83AwqvNKpyg4n3C41qmnmj1x2G9DwzI+jb7GkF4lI= github.com/cilium/workerpool v1.2.0 h1:Wc2iOPTvCgWKQXeq4L5tnx4QFEI+z5q1+bSpSS0cnAY= github.com/cilium/workerpool v1.2.0/go.mod h1:GOYJhwlnIjR+jWSDNBb5kw47G1H/XA9X4WOBpgr4pQU= github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= +github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20231128003011-0fa0005c9caa h1:jQCWAUqqlij9Pgj2i/PB79y4KOPYVyFYdROxgaCwdTQ= github.com/cncf/xds/go v0.0.0-20231128003011-0fa0005c9caa/go.mod h1:x/1Gn8zydmfq8dk6e9PdstVsDgu9RuyIIJqAaF//0IM= github.com/containerd/cgroups/v3 v3.0.2 h1:f5WFqIVSgo5IZmtTT3qVBo6TzI1ON6sycSBKkymb9L0= @@ -251,11 +308,15 @@ github.com/emicklei/go-restful/v3 v3.11.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRr github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v1.0.4 h1:gVPz/FMfvh57HdSJQyvBtF00j8JU4zdyUgIUNhlgg0A= github.com/envoyproxy/protoc-gen-validate v1.0.4/go.mod h1:qys6tmnRsYrQqIhm2bvKZH4Blx/1gTIZ2UKVY1M+Yew= github.com/evanphx/json-patch v5.9.0+incompatible h1:fBXyNpNMuTTDdquAq/uisOr2lShz4oaXpDTX2bLe7ls= github.com/evanphx/json-patch v5.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch v5.9.0+incompatible h1:fBXyNpNMuTTDdquAq/uisOr2lShz4oaXpDTX2bLe7ls= +github.com/evanphx/json-patch v5.9.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/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d h1:105gxyaGwCFad8crR9dcMQWvV9Hvulu6hwUh4tWPJnM= @@ -277,16 +338,22 @@ github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHk github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= 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/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-chi/chi v4.1.2+incompatible h1:fGFk2Gmi/YKXk0OmGfBh0WgmN3XB8lVnEyNz34tQRec= github.com/go-chi/chi v4.1.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw= github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= +github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw= +github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= github.com/go-gorp/gorp/v3 v3.1.0 h1:ItKF/Vbuj31dmV4jxA1qblpSwkl9g1typ24xoe70IGs= github.com/go-gorp/gorp/v3 v3.1.0/go.mod h1:dLEjIyyRNiXvNZ8PSmzpt1GsWAUK8kjVhEpjH8TixEw= +github.com/go-jose/go-jose/v3 v3.0.3 h1:fFKWeig/irsp7XD2zBxvnmA/XaRWp5V3CBsZXJF7G7k= +github.com/go-jose/go-jose/v3 v3.0.3/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= @@ -294,6 +361,8 @@ github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V 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/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= @@ -318,12 +387,33 @@ github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+Gr github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= github.com/go-openapi/validate v0.24.0 h1:LdfDKwNbpB6Vn40xhTdNZAnfLECL81w+VX3BumrGD58= github.com/go-openapi/validate v0.24.0/go.mod h1:iyeX1sEufmv3nPbBdX3ieNviWnOZaJ1+zquzJEf2BAQ= +github.com/go-openapi/analysis v0.23.0 h1:aGday7OWupfMs+LbmLZG4k0MYXIANxcuBTYUC03zFCU= +github.com/go-openapi/analysis v0.23.0/go.mod h1:9mz9ZWaSlV8TvjQHLl2mUW2PbZtemkE8yA5v22ohupo= +github.com/go-openapi/errors v0.22.0 h1:c4xY/OLxUBSTiepAg3j/MHuAv5mJhnf53LLMWFB+u/w= +github.com/go-openapi/errors v0.22.0/go.mod h1:J3DmZScxCDufmIMsdOuDHxJbdOGC0xtUynjIx092vXE= +github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= +github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= +github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ= +github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4= +github.com/go-openapi/loads v0.22.0 h1:ECPGd4jX1U6NApCGG1We+uEozOAvXvJSF4nnwHZ8Aco= +github.com/go-openapi/loads v0.22.0/go.mod h1:yLsaTCS92mnSAZX5WWoxszLj0u+Ojl+Zs5Stn1oF+rs= +github.com/go-openapi/runtime v0.28.0 h1:gpPPmWSNGo214l6n8hzdXYhPuJcGtziTOgUpvsFWGIQ= +github.com/go-openapi/runtime v0.28.0/go.mod h1:QN7OzcS+XuYmkQLw05akXk0jRH/eZ3kb18+1KwW9gyc= +github.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9ZY= +github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk= +github.com/go-openapi/strfmt v0.23.0 h1:nlUS6BCqcnAk0pyhi9Y+kdDVZdZMHfEKQiS4HaMgO/c= +github.com/go-openapi/strfmt v0.23.0/go.mod h1:NrtIpfKtWIygRkKVsxh7XQMDQW5HKQl6S5ik2elW+K4= +github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= +github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= +github.com/go-openapi/validate v0.24.0 h1:LdfDKwNbpB6Vn40xhTdNZAnfLECL81w+VX3BumrGD58= +github.com/go-openapi/validate v0.24.0/go.mod h1:iyeX1sEufmv3nPbBdX3ieNviWnOZaJ1+zquzJEf2BAQ= github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI= github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow= github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= 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/gobuffalo/logger v1.0.6 h1:nnZNpxYo0zx+Aj9RfMPBm+x9zAU2OayFh/xrAWi34HU= @@ -356,6 +446,7 @@ github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfb 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.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= @@ -398,6 +489,9 @@ github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo github.com/google/gops v0.3.27 h1:BDdWfedShsBbeatZ820oA4DbVOC8yJ4NI8xAlDFWfgI= github.com/google/gops v0.3.27/go.mod h1:lYqabmfnq4Q6UumWNx96Hjup5BDAVc8zmfIy0SkNCSk= github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/gops v0.3.27 h1:BDdWfedShsBbeatZ820oA4DbVOC8yJ4NI8xAlDFWfgI= +github.com/google/gops v0.3.27/go.mod h1:lYqabmfnq4Q6UumWNx96Hjup5BDAVc8zmfIy0SkNCSk= +github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6 h1:k7nVchz72niMH6YLQNvHSdIE7iqsQxK1P41mySCvssg= github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw= github.com/google/renameio/v2 v2.0.0 h1:UifI23ZTGY8Tt29JbYFiuyIU3eX+RNFtUwefq9qAhxg= @@ -422,6 +516,7 @@ github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:Fecb 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/hashicorp/consul/api v1.28.2 h1:mXfkRHrpHN4YY3RqL09nXU1eHKLNiuAN4kHvDQ16k/8= @@ -444,6 +539,8 @@ github.com/hashicorp/go-immutable-radix/v2 v2.1.0/go.mod h1:hgdqLXA4f6NIjRVisM1T github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-msgpack v1.1.5 h1:9byZdVjKTe5mce63pRVNP1L7UAmdHOTEMGehn6KvJWs= github.com/hashicorp/go-msgpack v1.1.5/go.mod h1:gWVc3sv/wbDmR3rQsj1CAktEZzoz1YNK9NfGLXJ69/4= +github.com/hashicorp/go-msgpack v1.1.5 h1:9byZdVjKTe5mce63pRVNP1L7UAmdHOTEMGehn6KvJWs= +github.com/hashicorp/go-msgpack v1.1.5/go.mod h1:gWVc3sv/wbDmR3rQsj1CAktEZzoz1YNK9NfGLXJ69/4= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= @@ -454,6 +551,8 @@ github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR3 github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= github.com/hashicorp/go-sockaddr v1.0.5 h1:dvk7TIXCZpmfOlM+9mlcrWmWjw/wlKT+VDq2wMvfPJU= github.com/hashicorp/go-sockaddr v1.0.5/go.mod h1:uoUUmtwU7n9Dv3O4SNLeFvg0SxQ3lyjsj6+CCykpaxI= +github.com/hashicorp/go-sockaddr v1.0.5 h1:dvk7TIXCZpmfOlM+9mlcrWmWjw/wlKT+VDq2wMvfPJU= +github.com/hashicorp/go-sockaddr v1.0.5/go.mod h1:uoUUmtwU7n9Dv3O4SNLeFvg0SxQ3lyjsj6+CCykpaxI= github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= @@ -461,6 +560,8 @@ github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/C github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= +github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= @@ -479,6 +580,7 @@ github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU= github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= @@ -546,6 +648,8 @@ github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhn github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= github.com/mackerelio/go-osstat v0.2.4 h1:qxGbdPkFo65PXOb/F/nhDKpF2nGmGaCFDLXoZjJTtUs= github.com/mackerelio/go-osstat v0.2.4/go.mod h1:Zy+qzGdZs3A9cuIqmgbJvwbmLQH9dJvtio5ZjJTbdlQ= +github.com/mackerelio/go-osstat v0.2.4 h1:qxGbdPkFo65PXOb/F/nhDKpF2nGmGaCFDLXoZjJTtUs= +github.com/mackerelio/go-osstat v0.2.4/go.mod h1:Zy+qzGdZs3A9cuIqmgbJvwbmLQH9dJvtio5ZjJTbdlQ= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= @@ -602,6 +706,8 @@ github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKju github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA= github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME= +github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA= +github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME= github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI= github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= @@ -652,6 +758,8 @@ github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= @@ -659,12 +767,18 @@ github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= +github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= github.com/onsi/ginkgo/v2 v2.19.0 h1:9Cnnf7UHo57Hy3k6/m5k3dRfGTMXGvxhHFvkDTCTpvA= github.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To= github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk= github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= @@ -719,12 +833,17 @@ github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+ github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc= github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8= +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.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= 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/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/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= @@ -738,6 +857,8 @@ github.com/s3rj1k/go-fanotify/fanotify v0.0.0-20210917134616-9c00a300bb7a h1:np2 github.com/s3rj1k/go-fanotify/fanotify v0.0.0-20210917134616-9c00a300bb7a/go.mod h1:wiP6GQ2T378F+YIyuNw7yXtBxJZR+fqrrn1Z6UHZi0Q= github.com/safchain/ethtool v0.4.1 h1:S6mEleTADqgynileXoiapt/nKnatyR6bmIHoF+h2ADo= github.com/safchain/ethtool v0.4.1/go.mod h1:XLLnZmy4OCRTkksP/UiMjij96YmIsBfmBQcs7H6tA48= +github.com/safchain/ethtool v0.4.1 h1:S6mEleTADqgynileXoiapt/nKnatyR6bmIHoF+h2ADo= +github.com/safchain/ethtool v0.4.1/go.mod h1:XLLnZmy4OCRTkksP/UiMjij96YmIsBfmBQcs7H6tA48= github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= @@ -748,6 +869,8 @@ github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUt github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= +github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= @@ -768,6 +891,10 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI= github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg= +github.com/spiffe/go-spiffe/v2 v2.1.7 h1:VUkM1yIyg/x8X7u1uXqSRVRCdMdfRIEdFBzpqoeASGk= +github.com/spiffe/go-spiffe/v2 v2.1.7/go.mod h1:QJDGdhXllxjxvd5B+2XnhhXB/+rC8gr+lNrtOryiWeE= +github.com/spiffe/spire-api-sdk v1.9.1 h1:DqaUvlBd7iNt6zoaW1At3AgU+RUOfzTXH5994/cFD8g= +github.com/spiffe/spire-api-sdk v1.9.1/go.mod h1:4uuhFlN6KBWjACRP3xXwrOTNnvaLp1zJs8Lribtr4fI= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= @@ -795,6 +922,9 @@ github.com/tedsuo/ifrit v0.0.0-20180802180643-bea94bb476cc/go.mod h1:eyZnKCc955u github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/gjson v1.17.1 h1:wlYEnwqAHgzmhNUFfw7Xalt2JzQvsMx2Se4PcoFCT/U= github.com/tidwall/gjson v1.17.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.17.1 h1:wlYEnwqAHgzmhNUFfw7Xalt2JzQvsMx2Se4PcoFCT/U= +github.com/tidwall/gjson v1.17.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= @@ -822,6 +952,10 @@ github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50 h1:hlE8//ciYMzt github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50/go.mod h1:NUSPSUX/bi6SeDMUh6brw0nXpxHnc96TguQh0+r/ssA= github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f h1:ERexzlUfuTvpE74urLSbIQW0Z/6hF9t8U4NsJLaioAY= github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg= +github.com/zeebo/errs v1.3.0 h1:hmiaKqgYZzcVgRL1Vkc1Mn2914BbzB0IBxs+ebeutGs= +github.com/zeebo/errs v1.3.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4= +go.etcd.io/etcd v3.3.27+incompatible h1:5hMrpf6REqTHV2LW2OclNpRtxI0k9ZplMemJsMSWju0= +go.etcd.io/etcd v3.3.27+incompatible/go.mod h1:yaeTdrJi5lOmYerz05bd8+V7KubZs8YSFZfzsF9A6aI= go.etcd.io/etcd v3.3.27+incompatible h1:5hMrpf6REqTHV2LW2OclNpRtxI0k9ZplMemJsMSWju0= go.etcd.io/etcd v3.3.27+incompatible/go.mod h1:yaeTdrJi5lOmYerz05bd8+V7KubZs8YSFZfzsF9A6aI= go.etcd.io/etcd/api/v3 v3.5.12 h1:W4sw5ZoU2Juc9gBWuLk5U6fHfNVyY1WC5g9uiXZio/c= @@ -832,22 +966,33 @@ go.etcd.io/etcd/client/v3 v3.5.12 h1:v5lCPXn1pf1Uu3M4laUE2hp/geOTc5uPcYYsNe1lDxg go.etcd.io/etcd/client/v3 v3.5.12/go.mod h1:tSbBCakoWmmddL+BKVAJHa9km+O/E+bumDe9mSbPiqw= go.mongodb.org/mongo-driver v1.14.0 h1:P98w8egYRjYe3XDjxhYJagTokP/H6HzlsnojRgZRd80= go.mongodb.org/mongo-driver v1.14.0/go.mod h1:Vzb0Mk/pa7e6cWw85R4F/endUC3u0U9jGcNU603k65c= +go.mongodb.org/mongo-driver v1.14.0 h1:P98w8egYRjYe3XDjxhYJagTokP/H6HzlsnojRgZRd80= +go.mongodb.org/mongo-driver v1.14.0/go.mod h1:Vzb0Mk/pa7e6cWw85R4F/endUC3u0U9jGcNU603k65c= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw= 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 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.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/otlptracehttp v1.22.0 h1:FyjCyI9jVEfqhUh2MoSkmolPjfh5fp2hnV0b0irxH4Q= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.22.0/go.mod h1:hYwym2nDEeZfG/motx0p7L7J1N1vyzIThemQsb4g2qY= 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/exporters/otlp/otlptrace/otlptracehttp v1.22.0 h1:FyjCyI9jVEfqhUh2MoSkmolPjfh5fp2hnV0b0irxH4Q= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.22.0/go.mod h1:hYwym2nDEeZfG/motx0p7L7J1N1vyzIThemQsb4g2qY= +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.24.0 h1:YMPPDNymmQN3ZgczicBY3B6sf9n62Dlj9pWD3ucgoDw= go.opentelemetry.io/otel/sdk v1.24.0/go.mod h1:KVrIYw6tEubO9E96HQpcmpTKDVn9gdv35HoYiQWGDFg= 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 v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +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.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I= go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= go.starlark.net v0.0.0-20230814145427-12f4cb8177e4 h1:Ydko8M6UfXgvSpGOnbAjRMQDIvBheUsjBjkm6Azcpf4= @@ -865,6 +1010,8 @@ go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN8 go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M= go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= @@ -876,11 +1023,14 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20240318143956-a85f2c67cd81 h1:6R2FC06FonbXQ8pK11/PDFY6N6LWlf9KlzibaCapmqc= golang.org/x/exp v0.0.0-20240318143956-a85f2c67cd81/go.mod h1:CQ1k9gNrJ50XIzaKCRR2hssIjF07kZFEiieALBM/ARQ= +golang.org/x/exp v0.0.0-20240318143956-a85f2c67cd81 h1:6R2FC06FonbXQ8pK11/PDFY6N6LWlf9KlzibaCapmqc= +golang.org/x/exp v0.0.0-20240318143956-a85f2c67cd81/go.mod h1:CQ1k9gNrJ50XIzaKCRR2hssIjF07kZFEiieALBM/ARQ= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= @@ -889,12 +1039,14 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB 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.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -906,6 +1058,8 @@ golang.org/x/net v0.0.0-20191007182048-72f939374954/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200202094626-16171245cfb2/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-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= @@ -915,6 +1069,7 @@ golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210928044308-7d9f5e0b762b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= @@ -922,9 +1077,13 @@ golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su 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.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs= +golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs= golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -935,6 +1094,7 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/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.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -951,13 +1111,16 @@ golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -970,6 +1133,8 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201218084310-7d0127a74742/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210110051926-789bb1bd4061/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210123111255-9b0068b26619/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210216163648-f7da38b97c65/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -996,15 +1161,21 @@ golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.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.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= +golang.org/x/sys v0.22.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.0.0-20220526004731-065cf7ba2467/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.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA= golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1014,6 +1185,8 @@ 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.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= @@ -1028,8 +1201,10 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 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.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -1042,6 +1217,7 @@ google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9Ywl google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9 h1:9+tzLLstTlPTRyJTh+ah5wIMsBW5c4tQwGTN3thOW9Y= google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9/go.mod h1:mqHbVIp48Muh7Ywss/AD6I5kNVKZMmAa/QEW58Gxp2s= @@ -1049,11 +1225,16 @@ google.golang.org/genproto/googleapis/api v0.0.0-20240311132316-a219d84964c2 h1: google.golang.org/genproto/googleapis/api v0.0.0-20240311132316-a219d84964c2/go.mod h1:O1cOfN1Cy6QEYr7VxtjOyP5AdAuR0aJ/MYZaaof623Y= google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 h1:NnYq6UN9ReLM9/Y01KWNOWyI5xQ9kbIms5GGJVwS/Yc= google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 h1:NnYq6UN9ReLM9/Y01KWNOWyI5xQ9kbIms5GGJVwS/Yc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= google.golang.org/grpc v1.62.1 h1:B4n+nfKzOICUXMgyrNd19h/I9oH0L1pizfk1d4zSgTk= google.golang.org/grpc v1.62.1/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= @@ -1068,6 +1249,7 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= @@ -1087,6 +1269,7 @@ gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYs gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/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.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/operator/Dockerfile b/operator/Dockerfile index d04131f637..3ba4e91745 100644 --- a/operator/Dockerfile +++ b/operator/Dockerfile @@ -6,10 +6,23 @@ ARG APP_INSIGHTS_ID WORKDIR /workspace COPY . . +# Default linux/architecture. +ARG GOOS=linux +ENV GOOS=${GOOS} + +ARG GOARCH=amd64 +ENV GOARCH=${GOARCH} + RUN make manifests -RUN --mount=type=cache,target="/root/.cache/go-build" CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "-X main.version="$VERSION" -X "main.applicationInsightsID"="$APP_INSIGHTS_ID"" -a -o retina-operator operator/main.go +RUN --mount=type=cache,target="/root/.cache/go-build" \ + CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build \ + -ldflags "-X main.version="$VERSION" \ + -X "main.applicationInsightsID"="$APP_INSIGHTS_ID"" \ + -a -o retina-operator operator/main.go + +##################### controller ####################### +FROM --platform=$BUILDPLATFORM mcr.microsoft.com/mirror/gcr/distroless/cc-debian11:latest@sha256:b53fbf5f81f4a120a489fedff2092e6fcbeacf7863fce3e45d99cc58dc230ccc as controller -FROM scratch WORKDIR / COPY --from=builder /workspace/retina-operator . USER 65532:65532 diff --git a/operator/cilium-crds/config/config_linux.go b/operator/cilium-crds/config/config_linux.go new file mode 100644 index 0000000000..993022f92b --- /dev/null +++ b/operator/cilium-crds/config/config_linux.go @@ -0,0 +1,32 @@ +package config + +import ( + "github.com/cilium/cilium/pkg/hive/cell" + "github.com/spf13/pflag" + + sharedconfig "github.com/microsoft/retina/pkg/shared/config" +) + +type Config struct { + EnableTelemetry bool + LeaderElection bool +} + +func (c Config) Flags(flags *pflag.FlagSet) { + flags.Bool("enable-telemetry", c.EnableTelemetry, "enable telemetry (send logs and metrics to a remote server)") + flags.Bool("leader-election", c.LeaderElection, "Enable leader election for operator. Ensures there is only one active operator Pod") +} + +var ( + DefaultConfig = Config{ + EnableTelemetry: false, + LeaderElection: false, + } + + Cell = cell.Module( + "operator-config", + "Operator Config", + cell.Config(DefaultConfig), + sharedconfig.Cell, + ) +) diff --git a/operator/cilium-crds/k8s/LICENSE b/operator/cilium-crds/k8s/LICENSE new file mode 100644 index 0000000000..931b48fd07 --- /dev/null +++ b/operator/cilium-crds/k8s/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} Authors of Cilium and Retina + + 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. \ No newline at end of file diff --git a/operator/cilium-crds/k8s/apis/cell.go b/operator/cilium-crds/k8s/apis/cell.go new file mode 100644 index 0000000000..0c3082d066 --- /dev/null +++ b/operator/cilium-crds/k8s/apis/cell.go @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of Retina and Cilium + +// NOTE: this file was copied and modified from Cilium's pkg/k8s/apis/cell.go +// to create only the Cilium CRDs which are necessary for Retina (i.e. CiliumEndpoint and CiliumIdentity). + +package apis + +import ( + "fmt" + + "github.com/sirupsen/logrus" + "github.com/spf13/pflag" + + "github.com/cilium/cilium/pkg/hive/cell" + k8sClient "github.com/cilium/cilium/pkg/k8s/client" +) + +// SkipCRDCreation specifies whether the CustomResourceDefinition will be +// created by the daemon +const SkipCRDCreation = "skip-crd-creation" + +// RegisterCRDsCell is a cell that creates all the Cilium CRDs. +var RegisterCRDsCell = cell.Module( + "create-crds", + "Create Cilium CRDs", + + cell.Config(defaultConfig), + + cell.Invoke(createCRDs), +) + +type RegisterCRDsConfig struct { + // SkipCRDCreation disables creation of the CustomResourceDefinition + // for the operator + SkipCRDCreation bool +} + +var defaultConfig = RegisterCRDsConfig{} + +func (c RegisterCRDsConfig) Flags(flags *pflag.FlagSet) { + flags.Bool(SkipCRDCreation, false, "When true, Kubernetes Custom Resource Definitions will not be created") +} + +// RegisterCRDsFunc is a function that register all the CRDs for a k8s group +type RegisterCRDsFunc func(k8sClient.Clientset) error + +type params struct { + cell.In + + Logger logrus.FieldLogger + Lifecycle cell.Lifecycle + + Clientset k8sClient.Clientset + + Config RegisterCRDsConfig +} + +func createCRDs(p params) { + p.Lifecycle.Append(cell.Hook{ + OnStart: func(_ cell.HookContext) error { + // Register the CRDs after validating that we are running on a supported + // version of K8s. + if !p.Clientset.IsEnabled() || p.Config.SkipCRDCreation { + p.Logger.Info("Skipping creation of CRDs") + return nil + } + + if err := RegisterCRDs(p.Clientset); err != nil { + return fmt.Errorf("unable to create CRDs: %w", err) + } + + return nil + }, + }) +} diff --git a/operator/cilium-crds/k8s/apis/register.go b/operator/cilium-crds/k8s/apis/register.go new file mode 100644 index 0000000000..54429415e2 --- /dev/null +++ b/operator/cilium-crds/k8s/apis/register.go @@ -0,0 +1,135 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of Retina and Cilium + +// NOTE: this file was copied and modified from Cilium's pkg/k8s/apis/cilium.io/client/register.go +// to create only the Cilium CRDs which are necessary for Retina (i.e. CiliumEndpoint and CiliumIdentity). + +package apis + +import ( + "context" + "fmt" + + "golang.org/x/sync/errgroup" + + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + apiextensionsclient "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + k8sconst "github.com/cilium/cilium/pkg/k8s/apis/cilium.io" + apisclient "github.com/cilium/cilium/pkg/k8s/apis/cilium.io/client" + k8sconstv2 "github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2" + "github.com/cilium/cilium/pkg/k8s/apis/crdhelpers" + "github.com/cilium/cilium/pkg/k8s/client" + "github.com/cilium/cilium/pkg/k8s/synced" + "github.com/cilium/cilium/pkg/versioncheck" +) + +var necessaryCRDNames = []string{ + synced.CRDResourceName(k8sconstv2.CEPName), + synced.CRDResourceName(k8sconstv2.CIDName), +} + +// Define a custom error type for missing CRDs +type CRDNotFoundError struct { + CRDName string +} + +func (e *CRDNotFoundError) Error() string { + return "CRD not found: " + e.CRDName +} + +// RegisterCRDs registers all CRDs with the K8s apiserver. +func RegisterCRDs(clientset client.Clientset) error { + if err := createCustomResourceDefinitions(clientset); err != nil { + return fmt.Errorf("Unable to create custom resource definition: %w", err) + } + + return nil +} + +// createCustomResourceDefinitions creates our CRD objects in the Kubernetes +// cluster. +func createCustomResourceDefinitions(clientset apiextensionsclient.Interface) error { + g, _ := errgroup.WithContext(context.Background()) + + crds, err := customResourceDefinitionList() + if err != nil { + return fmt.Errorf("Unable to get CRD list: %w", err) + } + + for _, crd := range crds { + crd := crd + g.Go(func() error { + return createCRD(crd.Name, crd.FullName)(clientset) + }) + } + + if err := g.Wait(); err != nil { + return fmt.Errorf("Unable to create CRD: %w", err) + } + + return nil +} + +func customResourceDefinitionList() (map[string]*apisclient.CRDList, error) { + crds := apisclient.CustomResourceDefinitionList() + + necessaryCRDs := make(map[string]*apisclient.CRDList, len(necessaryCRDNames)) + + for _, crdName := range necessaryCRDNames { + crd, ok := crds[crdName] + if !ok { + return nil, fmt.Errorf("%w", &CRDNotFoundError{CRDName: crdName}) + } + + necessaryCRDs[crdName] = crd + } + + return necessaryCRDs, nil +} + +// createCRD creates and updates a CRD. +// It should be called on agent startup but is idempotent and safe to call again. +func createCRD(crdVersionedName, crdMetaName string) func(clientset apiextensionsclient.Interface) error { + return func(clientset apiextensionsclient.Interface) error { + ciliumCRD := apisclient.GetPregeneratedCRD(crdVersionedName) + + err := crdhelpers.CreateUpdateCRD( + clientset, + constructV1CRD(crdMetaName, ciliumCRD), + crdhelpers.NewDefaultPoller(), + k8sconst.CustomResourceDefinitionSchemaVersionKey, + versioncheck.MustVersion(k8sconst.CustomResourceDefinitionSchemaVersion), + ) + if err != nil { + return fmt.Errorf("Unable to create CRD %s: %w", crdMetaName, err) + } + return nil + } +} + +func constructV1CRD( + name string, + template apiextensionsv1.CustomResourceDefinition, +) *apiextensionsv1.CustomResourceDefinition { + return &apiextensionsv1.CustomResourceDefinition{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Labels: map[string]string{ + k8sconst.CustomResourceDefinitionSchemaVersionKey: k8sconst.CustomResourceDefinitionSchemaVersion, + }, + }, + Spec: apiextensionsv1.CustomResourceDefinitionSpec{ + Group: k8sconst.CustomResourceDefinitionGroup, + Names: apiextensionsv1.CustomResourceDefinitionNames{ + Kind: template.Spec.Names.Kind, + Plural: template.Spec.Names.Plural, + ShortNames: template.Spec.Names.ShortNames, + Singular: template.Spec.Names.Singular, + }, + Scope: template.Spec.Scope, + Versions: template.Spec.Versions, + }, + } +} diff --git a/operator/cilium-crds/k8s/resource_ctors.go b/operator/cilium-crds/k8s/resource_ctors.go new file mode 100644 index 0000000000..0437830c90 --- /dev/null +++ b/operator/cilium-crds/k8s/resource_ctors.go @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of Retina and Cilium + +// NOTE: copied and slimmed down for our use case + +package k8s + +import ( + "errors" + "fmt" + "strconv" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/tools/cache" + + "github.com/cilium/cilium/pkg/hive/cell" + cilium_api_v2 "github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2" + cilium_api_v2alpha1 "github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2alpha1" + "github.com/cilium/cilium/pkg/k8s/client" + "github.com/cilium/cilium/pkg/k8s/resource" + "github.com/cilium/cilium/pkg/k8s/utils" +) + +var ErrNotACiliumEndpoint = errors.New("object is not a *cilium_api_v2.CiliumEndpoint") + +func CiliumEndpointResource(lc cell.Lifecycle, cs client.Clientset, opts ...func(*metav1.ListOptions)) (resource.Resource[*cilium_api_v2.CiliumEndpoint], error) { + if !cs.IsEnabled() { + return nil, nil + } + lw := utils.ListerWatcherWithModifiers( + utils.ListerWatcherFromTyped[*cilium_api_v2.CiliumEndpointList](cs.CiliumV2().CiliumEndpoints("")), + opts..., + ) + indexers := cache.Indexers{ + cache.NamespaceIndex: cache.MetaNamespaceIndexFunc, + CiliumEndpointIndexIdentity: identityIndexFunc, + } + return resource.New[*cilium_api_v2.CiliumEndpoint]( + lc, lw, resource.WithMetric("CiliumEndpoint"), resource.WithIndexers(indexers)), nil +} + +func identityIndexFunc(obj interface{}) ([]string, error) { + if t, ok := obj.(*cilium_api_v2.CiliumEndpoint); ok { + if t.Status.Identity != nil { + id := strconv.FormatInt(t.Status.Identity.ID, 10) + return []string{id}, nil + } + return []string{"0"}, nil + } + return nil, fmt.Errorf("%w - found %T", ErrNotACiliumEndpoint, obj) +} + +func CiliumEndpointSliceResource(lc cell.Lifecycle, cs client.Clientset, opts ...func(*metav1.ListOptions)) (resource.Resource[*cilium_api_v2alpha1.CiliumEndpointSlice], error) { + if !cs.IsEnabled() { + return nil, nil + } + lw := utils.ListerWatcherWithModifiers( + utils.ListerWatcherFromTyped[*cilium_api_v2alpha1.CiliumEndpointSliceList](cs.CiliumV2alpha1().CiliumEndpointSlices()), + opts..., + ) + return resource.New[*cilium_api_v2alpha1.CiliumEndpointSlice](lc, lw, resource.WithMetric("CiliumEndpointSlice")), nil +} diff --git a/operator/cilium-crds/k8s/resources.go b/operator/cilium-crds/k8s/resources.go new file mode 100644 index 0000000000..4647396f98 --- /dev/null +++ b/operator/cilium-crds/k8s/resources.go @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of Retina and Cilium + +// NOTE: we've slimmed down the resources required here to: +// - identities +// - endpoints +// - endpointslices (required for identitygc) +// - ciliumnodes (required for endpointgc) + +package k8s + +import ( + "github.com/cilium/cilium/pkg/hive/cell" + "github.com/cilium/cilium/pkg/k8s" + cilium_api_v2 "github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2" + cilium_api_v2alpha1 "github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2alpha1" + "github.com/cilium/cilium/pkg/k8s/resource" + slim_corev1 "github.com/cilium/cilium/pkg/k8s/slim/k8s/api/core/v1" +) + +const ( + CiliumEndpointIndexIdentity = "identity" +) + +// ResourcesCell provides a set of handles to Kubernetes resources used throughout the +// operator. Each of the resources share a client-go informer and backing store so we only +// have one watch API call for each resource kind and that we maintain only one copy of each object. +// +// See pkg/k8s/resource/resource.go for documentation on the Resource[T] type. +var ResourcesCell = cell.Module( + "k8s-resources", + "Operator Kubernetes resources", + + cell.Config(k8s.DefaultConfig), + cell.Provide( + k8s.CiliumIdentityResource, + CiliumEndpointResource, + CiliumEndpointSliceResource, + k8s.CiliumNodeResource, + k8s.PodResource, + k8s.NamespaceResource, + ), +) + +// Resources is a convenience struct to group all the operator k8s resources as cell constructor parameters. +type Resources struct { + cell.In + + Identities resource.Resource[*cilium_api_v2.CiliumIdentity] + CiliumEndpoints resource.Resource[*cilium_api_v2.CiliumEndpoint] + CiliumEndpointSlices resource.Resource[*cilium_api_v2alpha1.CiliumEndpointSlice] + CiliumNodes resource.Resource[*cilium_api_v2.CiliumNode] + Pods resource.Resource[*slim_corev1.Pod] +} diff --git a/operator/cmd/cilium-crds/LICENSE b/operator/cmd/cilium-crds/LICENSE new file mode 100644 index 0000000000..931b48fd07 --- /dev/null +++ b/operator/cmd/cilium-crds/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} Authors of Cilium and Retina + + 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. \ No newline at end of file diff --git a/operator/cmd/cilium-crds/cells_linux.go b/operator/cmd/cilium-crds/cells_linux.go new file mode 100644 index 0000000000..ef8598d3b1 --- /dev/null +++ b/operator/cmd/cilium-crds/cells_linux.go @@ -0,0 +1,252 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of Cilium and Retina + +// NOTE: separated the cells from root.go into this file. +// See other note in root.go for modification info. + +package ciliumcrds + +import ( + "context" + "fmt" + "sync/atomic" + + "github.com/microsoft/retina/pkg/shared/telemetry" + "github.com/sirupsen/logrus" + k8sruntime "k8s.io/apimachinery/pkg/runtime" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + clientgoscheme "k8s.io/client-go/kubernetes/scheme" + ctrl "sigs.k8s.io/controller-runtime" + logf "sigs.k8s.io/controller-runtime/pkg/log" + zapf "sigs.k8s.io/controller-runtime/pkg/log/zap" + + "github.com/microsoft/retina/operator/cilium-crds/config" + operatorK8s "github.com/microsoft/retina/operator/cilium-crds/k8s" + "github.com/microsoft/retina/operator/cilium-crds/k8s/apis" + endpointcontroller "github.com/microsoft/retina/pkg/controllers/operator/cilium-crds/endpoint" + + "github.com/cilium/cilium/operator/auth" + "github.com/cilium/cilium/operator/endpointgc" + "github.com/cilium/cilium/operator/identitygc" + operatorMetrics "github.com/cilium/cilium/operator/metrics" + operatorOption "github.com/cilium/cilium/operator/option" + cmtypes "github.com/cilium/cilium/pkg/clustermesh/types" + "github.com/cilium/cilium/pkg/controller" + "github.com/cilium/cilium/pkg/hive/cell" + k8sClient "github.com/cilium/cilium/pkg/k8s/client" + "github.com/cilium/cilium/pkg/kvstore/store" + "github.com/cilium/cilium/pkg/option" + "github.com/cilium/cilium/pkg/pprof" +) + +const operatorK8sNamespace = "kube-system" + +var ( + Operator = cell.Module( + "operator", + "Retina Operator", + + cell.Invoke(func(l logrus.FieldLogger) { + // to help prevent user confusion, explain why logs may include lines referencing "cilium" or "cilium operator" + // e.g. level=info msg="Cilium Operator go version go1.21.4 linux/amd64" subsys=retina-operator + l.Info("starting hive. Some logs will say 'cilium' since some code is derived from cilium") + }), + Infrastructure, + ControlPlane, + ) + + Infrastructure = cell.Module( + "operator-infra", + "Operator Infrastructure", + + // operator config + config.Cell, + + // start sending logs to zap telemetry (if enabled) + cell.Invoke(setupZapHook), + + cell.Provide(func(cfg config.Config) telemetry.Config { + return telemetry.Config{ + Component: "retina-operator", + EnableTelemetry: cfg.EnableTelemetry, + ApplicationInsightsID: applicationInsightsID, + RetinaVersion: retinaVersion, + } + }), + telemetry.Constructor, + + // Register the pprof HTTP handlers, to get runtime profiling data. + pprof.Cell, + cell.Config(pprof.Config{ + Pprof: true, + PprofAddress: option.PprofAddressAgent, + PprofPort: option.PprofPortAgent, + }), + + // // Runs the gops agent, a tool to diagnose Go processes. + // gops.Cell(defaults.GopsPortOperator), + + // Provides Clientset, API for accessing Kubernetes objects. + k8sClient.Cell, + + // Provides the modular metrics registry, metric HTTP server and legacy metrics cell. + // NOTE: no server/metrics are created when --enable-metrics=false (default) + operatorMetrics.Cell, + cell.Provide(func( + operatorCfg *operatorOption.OperatorConfig, + ) operatorMetrics.SharedConfig { + return operatorMetrics.SharedConfig{ + // Cloud provider specific allocators needs to read operatorCfg.EnableMetrics + // to add their metrics when it's set to true. Therefore, we leave the flag as global + // instead of declaring it as part of the metrics cell. + // This should be changed once the IPAM allocator is modularized. + EnableMetrics: operatorCfg.EnableMetrics, + EnableGatewayAPI: operatorCfg.EnableGatewayAPI, + } + }), + cell.Provide(func() *k8sruntime.Scheme { + scheme := k8sruntime.NewScheme() + + //+kubebuilder:scaffold:scheme + utilruntime.Must(clientgoscheme.AddToScheme(scheme)) + + return scheme + }), + ) + + // ControlPlane implements the control functions. + ControlPlane = cell.Module( + "operator-controlplane", + "Operator Control Plane", + + cell.Config(cmtypes.DefaultClusterInfo), + cell.Invoke(func(cinfo cmtypes.ClusterInfo) error { + err := cinfo.Validate() + if err != nil { + return fmt.Errorf("error validating cluster info: %w", err) + } + return nil + }), + + cell.Invoke( + registerOperatorHooks, + ), + + cell.Provide(func() *option.DaemonConfig { + return option.Config + }), + + cell.Provide(func() *operatorOption.OperatorConfig { + return operatorOption.Config + }), + + cell.Provide(func( + daemonCfg *option.DaemonConfig, + _ *operatorOption.OperatorConfig, + ) identitygc.SharedConfig { + return identitygc.SharedConfig{ + IdentityAllocationMode: daemonCfg.IdentityAllocationMode, + } + }), + + // TODO uncomment if we use endpoint slices + // cell.Provide(func( + // daemonCfg *option.DaemonConfig, + // ) ciliumendpointslice.SharedConfig { + // return ciliumendpointslice.SharedConfig{ + // EnableCiliumEndpointSlice: daemonCfg.EnableCiliumEndpointSlice, + // } + // }), + + cell.Provide(func( + operatorCfg *operatorOption.OperatorConfig, + daemonCfg *option.DaemonConfig, + ) endpointgc.SharedConfig { + return endpointgc.SharedConfig{ + Interval: operatorCfg.EndpointGCInterval, + DisableCiliumEndpointCRD: daemonCfg.DisableCiliumEndpointCRD, + } + }), + + // TODO: uncomment to expose healthz endpoint for this hive? + // api.HealthHandlerCell( + // kvstoreEnabled, + // isLeader.Load, + // ), + + // NOTE: might need to uncomment to support metrics? Code might be legacy though? + // api.MetricsHandlerCell, + + controller.Cell, + + // These cells are started only after the operator is elected leader. + WithLeaderLifecycle( + // The CRDs registration should be the first operation to be invoked after the operator is elected leader. + apis.RegisterCRDsCell, + + // below cluster of cells carries out Retina's custom operator logic for creating Cilium Identities and Endpoints + cell.Provide(func(scheme *k8sruntime.Scheme) (ctrl.Manager, error) { + // controller-runtime requires its own logger + logf.SetLogger(zapf.New()) + + manager, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ + Scheme: scheme, + // Metrics: server.Options{ + // BindAddress: metricsAddr, + // }, + // FIXME readiness probe not working after controller-runtime upgrade + // FIXME: don't know where this field went in controller-runtime v0.16.2 (previous: v0.15.0) + // Port: 9443, + // HealthProbeBindAddress: probeAddr, + }) + if err != nil { + return nil, fmt.Errorf("failed to create manager: %w", err) + } + return manager, nil + }), + endpointcontroller.Cell, + + // below cells are required to run identitygc and endpointgc when the operator is elected leader + + // NOTE: we've slimmed down the resources required here (see resources.go for more info) + operatorK8s.ResourcesCell, + + // NOTE: gc cells require this auth cell + // this cell will do nothing since --mesh-auth-mutual-enabled=false + auth.Cell, + + store.Cell, + + identitygc.Cell, + + // TODO: uncomment if we use endpoint slices + // CiliumEndpointSlice controller depends on the CiliumEndpoint and + // CiliumEndpointSlice resources. It reconciles the state of CESs in the + // cluster based on the CEPs and CESs events. + // It is disabled if CiliumEndpointSlice is disabled in the cluster - + // when --enable-cilium-endpoint-slice is false. + // ciliumendpointslice.Cell, + + // Cilium Endpoint Garbage Collector. It removes all leaked Cilium + // Endpoints. Either once or periodically it validates all the present + // Cilium Endpoints and delete the ones that should be deleted. + endpointgc.Cell, + + // only send heartbeat if leader + telemetry.Heartbeat, + ), + ) + + FlagsHooks []ProviderFlagsHooks + + leaderElectionResourceLockName = "cilium-operator-resource-lock" + + // Use a Go context so we can tell the leaderelection code when we + // want to step down + leaderElectionCtx context.Context + leaderElectionCtxCancel context.CancelFunc + + // isLeader is an atomic boolean value that is true when the Operator is + // elected leader. Otherwise, it is false. + isLeader atomic.Bool +) diff --git a/operator/cmd/cilium-crds/flags.go b/operator/cmd/cilium-crds/flags.go new file mode 100644 index 0000000000..c2d6e1482a --- /dev/null +++ b/operator/cmd/cilium-crds/flags.go @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of Cilium and Retina + +// NOTE: copied and slimmed down for our use case + +package ciliumcrds + +import ( + "fmt" + "time" + + "github.com/spf13/cobra" + "github.com/spf13/viper" + + operatorOption "github.com/cilium/cilium/operator/option" + "github.com/cilium/cilium/pkg/defaults" + "github.com/cilium/cilium/pkg/option" +) + +var ( + durationLeaderElector = 2 * time.Second + durationNonLeaderOperator = 15 * time.Second + durationActingMaster = 10 * time.Second +) + +func InitGlobalFlags(cmd *cobra.Command, vp *viper.Viper) { + flags := cmd.Flags() + + // include this line so that we don't see the following log from Cilium: + // "Running Cilium with \"kvstore\"=\"\" requires identity allocation via CRDs. Changing identity-allocation-mode to \"crd\"" + flags.String(option.IdentityAllocationMode, option.IdentityAllocationModeCRD, "Identity allocation mode") + + flags.String(option.ConfigFile, "", `Configuration file (to configure the operator, this argument is required)`) + option.BindEnv(vp, option.ConfigFile) + + flags.String(option.ConfigDir, "", `Configuration directory that contains a file for each option`) + option.BindEnv(vp, option.ConfigDir) + + flags.BoolP(option.DebugArg, "D", false, "Enable debugging mode") + option.BindEnv(vp, option.DebugArg) + + // NOTE: without this the option gets overridden from the default value to the zero value via option.Config.Populate(vp) + // specifically, here options.Config.AllocatorListTimeout gets overridden from the default value to 0s + flags.Duration(option.AllocatorListTimeoutName, defaults.AllocatorListTimeout, "timeout to list initial allocator state") + // similar overriding happens for option.Config.KVstoreConnectivityTimeout + flags.Duration(option.KVstoreConnectivityTimeout, defaults.KVstoreConnectivityTimeout, "Time after which an incomplete kvstore operation is considered failed") + // similar overriding happens for option.Config.KVstorePeriodicSync + flags.Duration(option.KVstorePeriodicSync, defaults.KVstorePeriodicSync, "Periodic KVstore synchronization interval") + + flags.Duration(operatorOption.EndpointGCInterval, operatorOption.EndpointGCIntervalDefault, "GC interval for cilium endpoints") + option.BindEnv(vp, operatorOption.EndpointGCInterval) + + flags.Bool(operatorOption.EnableMetrics, false, "Enable Prometheus metrics") + option.BindEnv(vp, operatorOption.EnableMetrics) + + flags.StringSlice(option.LogDriver, []string{}, "Logging endpoints to use for example syslog") + option.BindEnv(vp, option.LogDriver) + + flags.Var(option.NewNamedMapOptions(option.LogOpt, &option.Config.LogOpt, nil), + option.LogOpt, `Log driver options for cilium-operator, `+ + `configmap example for syslog driver: {"syslog.level":"info","syslog.facility":"local4"}`) + option.BindEnv(vp, option.LogOpt) + + flags.Duration(operatorOption.LeaderElectionLeaseDuration, durationNonLeaderOperator, + "Duration that non-leader operator candidates will wait before forcing to acquire leadership") + option.BindEnv(vp, operatorOption.LeaderElectionLeaseDuration) + + flags.Duration(operatorOption.LeaderElectionRenewDeadline, durationActingMaster, + "Duration that current acting master will retry refreshing leadership in before giving up the lock") + option.BindEnv(vp, operatorOption.LeaderElectionRenewDeadline) + + flags.Duration(operatorOption.LeaderElectionRetryPeriod, durationLeaderElector, + "Duration that LeaderElector clients should wait between retries of the actions") + option.BindEnv(vp, operatorOption.LeaderElectionRetryPeriod) + + err := vp.BindPFlags(flags) + if err != nil { + fmt.Printf("Failed to bind flags: %v\n", err) + } +} diff --git a/operator/cmd/cilium-crds/flags_provider.go b/operator/cmd/cilium-crds/flags_provider.go new file mode 100644 index 0000000000..f4285fd845 --- /dev/null +++ b/operator/cmd/cilium-crds/flags_provider.go @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of Cilium and Retina + +// NOTE: we could reference this file Cilium's code, but it is a small file. +// Referencing Cilium's code requires importing dependencies +// we don't need from their operator, which has BGP dependencies for instance. +// This is currently resulting in an error in go mod tidy: +// module go.universe.tf/metallb@latest found (v0.13.12), but does not contain package go.universe.tf/metallb/pkg/speaker + +package ciliumcrds + +import ( + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +type ProviderFlagsHooks interface { + RegisterProviderFlag(cmd *cobra.Command, vp *viper.Viper) +} diff --git a/operator/cmd/cilium-crds/lifecycle.go b/operator/cmd/cilium-crds/lifecycle.go new file mode 100644 index 0000000000..f348ec88b5 --- /dev/null +++ b/operator/cmd/cilium-crds/lifecycle.go @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of Cilium and Retina + +// NOTE: we could reference this file Cilium's code, but it is a small file. +// If we were to import this code from Cilium's operator/cmd/ package, +// that would require dependencies we don't need from their operator (for instance, BGP dependencies). +// At time of writing, trying to import that code was also resulting in an error in go mod tidy: +// module go.universe.tf/metallb@latest found (v0.13.12), but does not contain package go.universe.tf/metallb/pkg/speaker + +package ciliumcrds + +import ( + "github.com/cilium/cilium/pkg/hive/cell" +) + +// LeaderLifecycle is the inner lifecycle of the operator that is started when this +// operator instance is elected leader. It implements cell.Lifecycle allowing cells +// to use it. +type LeaderLifecycle struct { + cell.DefaultLifecycle +} + +func WithLeaderLifecycle(cells ...cell.Cell) cell.Cell { + return cell.Module( + "leader-lifecycle", + "Operator Leader Lifecycle", + + cell.Provide( + func() *LeaderLifecycle { return &LeaderLifecycle{} }, + ), + cell.Decorate( + func(lc *LeaderLifecycle) cell.Lifecycle { + return lc + }, + cells..., + ), + ) +} diff --git a/operator/cmd/cilium-crds/metrics.go b/operator/cmd/cilium-crds/metrics.go new file mode 100644 index 0000000000..902bcae56b --- /dev/null +++ b/operator/cmd/cilium-crds/metrics.go @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of Cilium and Retina + +// NOTE: changed to say networkobservability_operator + +package ciliumcrds + +import ( + "github.com/spf13/cobra" +) + +const RetinaOperatorMetricsNamespace = "networkobservability_operator" + +// MetricsCmd represents the metrics command for the operator. +var MetricsCmd = &cobra.Command{ + Use: "metrics", + Short: "Access metric status of the operator", +} diff --git a/operator/cmd/cilium-crds/root_linux.go b/operator/cmd/cilium-crds/root_linux.go new file mode 100644 index 0000000000..f078d3c182 --- /dev/null +++ b/operator/cmd/cilium-crds/root_linux.go @@ -0,0 +1,202 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of Cilium and Retina + +// NOTE: this file was originally a modified/slimmed-down version of Cilium's operator +// to provide Retina with a hive to run Cilium's garbage collection Cells. +// Now, it contains Retina-related code ported into Cells. + +package ciliumcrds + +import ( + "context" + "crypto/rand" + "fmt" + "math/big" + "os" + "path/filepath" + "sync" + + operatorOption "github.com/cilium/cilium/operator/option" + "github.com/cilium/cilium/pkg/hive" + "github.com/cilium/cilium/pkg/hive/cell" + k8sClient "github.com/cilium/cilium/pkg/k8s/client" + k8sversion "github.com/cilium/cilium/pkg/k8s/version" + "github.com/cilium/cilium/pkg/logging" + "github.com/cilium/cilium/pkg/logging/logfields" + "github.com/cilium/cilium/pkg/metrics" + "github.com/cilium/cilium/pkg/option" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" + "github.com/spf13/viper" + "k8s.io/client-go/tools/leaderelection" + "k8s.io/client-go/tools/leaderelection/resourcelock" +) + +var ( + // set at build time in Dockerfile + applicationInsightsID string + retinaVersion string + + // set logger field: subsys=retina-operator + binaryName = filepath.Base(os.Args[0]) + logger = logging.DefaultLogger.WithField(logfields.LogSubsys, binaryName) + operatorIDLength = 10 +) + +func Execute(h *hive.Hive) { + initEnv(h.Viper()) + + if err := h.Run(); err != nil { + logger.Fatal(err) + } +} + +func registerOperatorHooks(l logrus.FieldLogger, lc cell.Lifecycle, llc *LeaderLifecycle, clientset k8sClient.Clientset, shutdowner hive.Shutdowner) { + var wg sync.WaitGroup + lc.Append(cell.Hook{ + OnStart: func(cell.HookContext) error { + wg.Add(1) + go func() { + runOperator(l, llc, clientset, shutdowner) + wg.Done() + }() + return nil + }, + OnStop: func(ctx cell.HookContext) error { + if err := llc.Stop(ctx); err != nil { + return errors.Wrap(err, "failed to stop operator") + } + doCleanup() + wg.Wait() + return nil + }, + }) +} + +func initEnv(vp *viper.Viper) { + // Prepopulate option.Config with options from CLI. + + // NOTE: if the flag is not provided in operator/cmd/flags.go InitGlobalFlags(), these Populate methods override + // the default values provided in option.Config or operatorOption.Config respectively. + // The values will be overridden to the "zero value". + // Maybe could create a cell.Config for these instead? + option.Config.Populate(vp) + operatorOption.Config.Populate(vp) + + // add hooks after setting up metrics in the option.Confog + logging.DefaultLogger.Hooks.Add(metrics.NewLoggingHook()) + + // Logging should always be bootstrapped first. Do not add any code above this! + if err := logging.SetupLogging(option.Config.LogDriver, logging.LogOptions(option.Config.LogOpt), binaryName, option.Config.Debug); err != nil { + logger.Fatal(err) + } + + option.LogRegisteredOptions(vp, logger) + logger.Infof("retina operator version: %s", retinaVersion) +} + +func doCleanup() { + isLeader.Store(false) + + // Cancelling this context here makes sure that if the operator hold the + // leader lease, it will be released. + leaderElectionCtxCancel() +} + +// runOperator implements the logic of leader election for cilium-operator using +// built-in leader election capability in kubernetes. +// See: https://github.com/kubernetes/client-go/blob/master/examples/leader-election/main.go +func runOperator(l logrus.FieldLogger, lc *LeaderLifecycle, clientset k8sClient.Clientset, shutdowner hive.Shutdowner) { + isLeader.Store(false) + + leaderElectionCtx, leaderElectionCtxCancel = context.WithCancel(context.Background()) + + // We only support Operator in HA mode for Kubernetes Versions having support for + // LeasesResourceLock. + // See docs on capabilities.LeasesResourceLock for more context. + if !k8sversion.Capabilities().LeasesResourceLock { + l.Info("Support for coordination.k8s.io/v1 not present, fallback to non HA mode") + + if err := lc.Start(leaderElectionCtx); err != nil { + l.WithError(err).Fatal("Failed to start leading") + } + return + } + + // Get hostname for identity name of the lease lock holder. + // We identify the leader of the operator cluster using hostname. + operatorID, err := os.Hostname() + if err != nil { + l.WithError(err).Fatal("Failed to get hostname when generating lease lock identity") + } + operatorID, err = randomStringWithPrefix(operatorID+"-", operatorIDLength) + if err != nil { + l.WithError(err).Fatal("Failed to generate random string for lease lock identity") + } + + leResourceLock, err := resourcelock.NewFromKubeconfig( + resourcelock.LeasesResourceLock, + operatorK8sNamespace, + leaderElectionResourceLockName, + resourcelock.ResourceLockConfig{ + // Identity name of the lock holder + Identity: operatorID, + }, + clientset.RestConfig(), + operatorOption.Config.LeaderElectionRenewDeadline) + if err != nil { + l.WithError(err).Fatal("Failed to create resource lock for leader election") + } + + // Start the leader election for running cilium-operators + l.Info("Waiting for leader election") + leaderelection.RunOrDie(leaderElectionCtx, leaderelection.LeaderElectionConfig{ + Name: leaderElectionResourceLockName, + + Lock: leResourceLock, + ReleaseOnCancel: true, + + LeaseDuration: operatorOption.Config.LeaderElectionLeaseDuration, + RenewDeadline: operatorOption.Config.LeaderElectionRenewDeadline, + RetryPeriod: operatorOption.Config.LeaderElectionRetryPeriod, + + Callbacks: leaderelection.LeaderCallbacks{ + OnStartedLeading: func(ctx context.Context) { + if err := lc.Start(ctx); err != nil { + l.WithError(err).Error("Failed to start when elected leader, shutting down") + shutdowner.Shutdown(hive.ShutdownWithError(err)) + } + }, + OnStoppedLeading: func() { + l.WithField("operator-id", operatorID).Info("Leader election lost") + // Cleanup everything here, and exit. + shutdowner.Shutdown(hive.ShutdownWithError(errors.New("Leader election lost"))) + }, + OnNewLeader: func(identity string) { + if identity == operatorID { + l.Info("Leading the operator HA deployment") + } else { + l.WithFields(logrus.Fields{ + "newLeader": identity, + "operatorID": operatorID, + }).Info("Leader re-election complete") + } + }, + }, + }) +} + +// RandomStringWithPrefix returns a random string of length n + len(prefix) with +// the given prefix, containing upper- and lowercase runes. +func randomStringWithPrefix(prefix string, n int) (string, error) { + const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + bytes := make([]byte, n) + for i := range bytes { + num, err := rand.Int(rand.Reader, big.NewInt(int64(len(letters)))) + if err != nil { + return "", fmt.Errorf("failed to generate random number: %w", err) + } + bytes[i] = letters[num.Int64()] + } + return prefix + string(bytes), nil +} diff --git a/operator/cmd/cilium-crds/zap_linux.go b/operator/cmd/cilium-crds/zap_linux.go new file mode 100644 index 0000000000..9f31d4df34 --- /dev/null +++ b/operator/cmd/cilium-crds/zap_linux.go @@ -0,0 +1,80 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +package ciliumcrds + +import ( + "fmt" + "io" + + zaphook "github.com/Sytten/logrus-zap-hook" + "github.com/cilium/cilium/pkg/hive/cell" + "github.com/cilium/cilium/pkg/logging" + "github.com/cilium/cilium/pkg/option" + "github.com/microsoft/retina/pkg/log" + "github.com/sirupsen/logrus" + "go.uber.org/zap" + "k8s.io/client-go/rest" + + "github.com/microsoft/retina/operator/cilium-crds/config" +) + +// TODO refactor to another package? Like shared/telemetry/ + +const logFileName = "retina-operator.log" + +var ( + MaxFileSizeMB = 100 + MaxBackups = 3 + MaxAgeDays = 30 +) + +type params struct { + cell.In + + Logger logrus.FieldLogger + K8sCfg *rest.Config + DaemonCfg *option.DaemonConfig + OperatorCfg config.Config +} + +func setupZapHook(p params) { + // modify default logger + // properly report the caller (otherwise, will get caller=zap.go every time) + logging.DefaultLogger.ReportCaller = true + // discard default logger output in favor of zap + logging.DefaultLogger.SetOutput(io.Discard) + + lOpts := &log.LogOpts{ + Level: p.DaemonCfg.LogOpt[logging.LevelOpt], + File: false, + FileName: logFileName, + MaxFileSizeMB: MaxFileSizeMB, + MaxBackups: MaxBackups, + MaxAgeDays: MaxAgeDays, + ApplicationInsightsID: applicationInsightsID, + EnableTelemetry: p.OperatorCfg.EnableTelemetry, + } + + persistentFields := []zap.Field{ + zap.String("version", retinaVersion), + zap.String("apiserver", p.K8sCfg.Host), + } + + _, err := log.SetupZapLogger(lOpts, persistentFields...) + if err != nil { + fmt.Printf("failed to setup zap logger: %v", err) + } + + namedLogger := log.Logger().Named("retina-operator-v2") + namedLogger.Info("Traces telemetry initialized with zapai", zap.String("version", retinaVersion), zap.String("appInsightsID", lOpts.ApplicationInsightsID)) + + var hook *zaphook.ZapHook + hook, err = zaphook.NewZapHook(namedLogger.Logger) + if err != nil { + p.Logger.WithError(err).Error("failed to create zap hook") + return + } + + logging.DefaultLogger.Hooks.Add(hook) +} diff --git a/operator/cmd/cilium_crds_cmd_linux.go b/operator/cmd/cilium_crds_cmd_linux.go new file mode 100644 index 0000000000..701be12166 --- /dev/null +++ b/operator/cmd/cilium_crds_cmd_linux.go @@ -0,0 +1,47 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +package cmd + +import ( + "fmt" + + "github.com/cilium/cilium/pkg/hive" + "github.com/cilium/cilium/pkg/option" + ciliumcrds "github.com/microsoft/retina/operator/cmd/cilium-crds" + "github.com/spf13/cobra" +) + +var ( + h = hive.New(ciliumcrds.Operator) + cmd = &cobra.Command{ + Use: "manage-cilium-crds", + Short: "Start the Retina operator for Hubble control plane", + Run: func(cobraCmd *cobra.Command, _ []string) { + fmt.Println("Starting Retina Operator with Cilium CRDs") + ciliumcrds.Execute(h) + }, + } +) + +func init() { + h.RegisterFlags(cmd.Flags()) + cmd.AddCommand(h.Command(), ciliumcrds.MetricsCmd) + + ciliumcrds.InitGlobalFlags(cmd, h.Viper()) + + // Enable fallback to direct API probing to check for support of Leases in + // case Discovery API fails. + h.Viper().Set(option.K8sEnableAPIDiscovery, true) + + // not sure where flags hooks is set + for _, hook := range ciliumcrds.FlagsHooks { + hook.RegisterProviderFlag(cmd, h.Viper()) + } + + cobra.OnInitialize( + option.InitConfig(cmd, "Retina-Operator", "retina-operators", h.Viper()), + ) + + rootCmd.AddCommand(cmd) +} diff --git a/operator/cmd/legacy/deployment.go b/operator/cmd/legacy/deployment.go new file mode 100644 index 0000000000..1a15d0755c --- /dev/null +++ b/operator/cmd/legacy/deployment.go @@ -0,0 +1,290 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +package legacy + +import ( + "context" + "fmt" + "net/http" + "net/http/pprof" + "os" + "time" + + "go.uber.org/zap/zapcore" + + // Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.) + // to ensure that exec-entrypoint and run can make use of them. + + "go.uber.org/zap" + v1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + apiextv1 "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextensions/v1" + k8sruntime "k8s.io/apimachinery/pkg/runtime" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + "k8s.io/client-go/kubernetes" + clientgoscheme "k8s.io/client-go/kubernetes/scheme" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/healthz" + crzap "sigs.k8s.io/controller-runtime/pkg/log/zap" + metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server" + + retinav1alpha1 "github.com/microsoft/retina/crd/api/v1alpha1" + deploy "github.com/microsoft/retina/deploy/legacy" + "github.com/microsoft/retina/operator/cache" + config "github.com/microsoft/retina/operator/config" + captureUtils "github.com/microsoft/retina/pkg/capture/utils" + captureController "github.com/microsoft/retina/pkg/controllers/operator/capture" + metricsconfiguration "github.com/microsoft/retina/pkg/controllers/operator/metricsconfiguration" + podcontroller "github.com/microsoft/retina/pkg/controllers/operator/pod" + retinaendpointcontroller "github.com/microsoft/retina/pkg/controllers/operator/retinaendpoint" + "github.com/microsoft/retina/pkg/log" + "github.com/microsoft/retina/pkg/telemetry" +) + +var ( + scheme = k8sruntime.NewScheme() + mainLogger *log.ZapLogger + oconfig *config.OperatorConfig + + MaxPodChannelBuffer = 250 + MaxMetricsConfigurationChannelBuffer = 50 + MaxTracesConfigurationChannelBuffer = 50 + MaxRetinaEndpointChannelBuffer = 250 + + MaxFileSizeMB = 100 + MaxBackups = 3 + MaxAgeDays = 30 + + HeartbeatFrequency = 5 * time.Minute + + version = "undefined" + + // applicationInsightsID is the instrumentation key for Azure Application Insights + // It is set during the build process using the -ldflags flag + // If it is set, the application will send telemetry to the corresponding Application Insights resource. + applicationInsightsID string +) + +func init() { + //+kubebuilder:scaffold:scheme + utilruntime.Must(clientgoscheme.AddToScheme(scheme)) + utilruntime.Must(retinav1alpha1.AddToScheme(scheme)) +} + +type Operator struct { + metricsAddr string + probeAddr string + enableLeaderElection bool + configFile string +} + +func NewOperator(metricsAddr, probeAddr, configFile string, enableLeaderElection bool) *Operator { + return &Operator{ + metricsAddr: metricsAddr, + probeAddr: probeAddr, + enableLeaderElection: enableLeaderElection, + configFile: configFile, + } +} + +func (o *Operator) Start() { + mainLogger = log.Logger().Named("main") + + mainLogger.Sugar().Infof("Starting legacy operator") + + opts := &crzap.Options{ + Development: false, + } + + var err error + oconfig, err = config.GetConfig(o.configFile) + if err != nil { + fmt.Printf("failed to load config with err %s", err.Error()) + os.Exit(1) + } + + mainLogger.Sugar().Infof("Operator configuration", zap.Any("configuration", oconfig)) + + // Set Capture config + oconfig.CaptureConfig.CaptureImageVersion = version + oconfig.CaptureConfig.CaptureImageVersionSource = captureUtils.VersionSourceOperatorImageVersion + + if err != nil { + fmt.Printf("failed to load config with err %s", err.Error()) + os.Exit(1) + } + + err = initLogging(oconfig, applicationInsightsID) + if err != nil { + fmt.Printf("failed to initialize logging with err %s", err.Error()) + os.Exit(1) + } + + ctrl.SetLogger(crzap.New(crzap.UseFlagOptions(opts), crzap.Encoder(zapcore.NewConsoleEncoder(log.EncoderConfig())))) + + mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ + Scheme: scheme, + Metrics: metricsserver.Options{ + BindAddress: o.metricsAddr, + }, + HealthProbeBindAddress: o.probeAddr, + LeaderElection: o.enableLeaderElection, + LeaderElectionID: "16937e39.retina.sh", + + // LeaderElectionReleaseOnCancel defines if the leader should step down voluntarily + // when the Manager ends. This requires the binary to immediately end when the + // Manager is stopped, otherwise, this setting is unsafe. Setting this significantly + // speeds up voluntary leader transitions as the new leader don't have to wait + // LeaseDuration time first. + // + // In the default scaffold provided, the program ends immediately after + // the manager stops, so would be fine to enable this option. However, + // if you are doing or is intended to do any operation such as perform cleanups + // after the manager stops then its usage might be unsafe. + // LeaderElectionReleaseOnCancel: true, + }) + if err != nil { + mainLogger.Error("Unable to start manager", zap.Error(err)) + os.Exit(1) + } + + ctx := context.Background() + clientset, err := apiextv1.NewForConfig(mgr.GetConfig()) + if err != nil { + mainLogger.Error("Failed to get apiextension clientset", zap.Error(err)) + os.Exit(1) + } + + if oconfig.InstallCRDs { + mainLogger.Sugar().Infof("Installing CRDs") + + var crds map[string]*v1.CustomResourceDefinition + crds, err = deploy.InstallOrUpdateCRDs(ctx, oconfig.EnableRetinaEndpoint, clientset) + if err != nil { + mainLogger.Error("unable to register CRDs", zap.Error(err)) + os.Exit(1) + } + for name := range crds { + mainLogger.Info("CRD registered", zap.String("name", name)) + } + } + + apiserverURL, err := telemetry.GetK8SApiserverURLFromKubeConfig() + if err != nil { + mainLogger.Error("Apiserver URL is cannot be found", zap.Error(err)) + os.Exit(1) + } + + var tel telemetry.Telemetry + if oconfig.EnableTelemetry && applicationInsightsID != "" { + mainLogger.Info("telemetry enabled", zap.String("applicationInsightsID", applicationInsightsID)) + properties := map[string]string{ + "version": version, + telemetry.PropertyApiserver: apiserverURL, + } + tel = telemetry.NewAppInsightsTelemetryClient("retina-operator", properties) + } else { + mainLogger.Info("telemetry disabled", zap.String("apiserver", apiserverURL)) + tel = telemetry.NewNoopTelemetry() + } + + kubeClient, err := kubernetes.NewForConfig(mgr.GetConfig()) + if err != nil { + mainLogger.Error("Failed to get clientset", zap.Error(err)) + os.Exit(1) + } + + if err = captureController.NewCaptureReconciler( + mgr.GetClient(), mgr.GetScheme(), kubeClient, oconfig.CaptureConfig, + ).SetupWithManager(mgr); err != nil { + mainLogger.Error("Unable to setup retina capture controller with manager", zap.Error(err)) + os.Exit(1) + } + + ctrlCtx := ctrl.SetupSignalHandler() + + //+kubebuilder:scaffold:builder + + // TODO(mainred): retina-operater is responsible for recycling created retinaendpoints if remotecontext is switched off. + // Tracked by https://github.com/microsoft/retina/issues/522 + if oconfig.RemoteContext { + // Create RetinaEndpoint out of Pod to extract only the necessary fields of Pods to reduce the pressure of APIServer + // when RetinaEndpoint is enabled. + // TODO(mainred): An alternative of RetinaEndpoint, and possible long term solution, is to use CiliumEndpoint + // created for Cilium unmanged Pods. + if oconfig.EnableRetinaEndpoint { + mainLogger.Info("RetinaEndpoint is enabled") + + retinaendpointchannel := make(chan cache.PodCacheObject, MaxRetinaEndpointChannelBuffer) + ke := retinaendpointcontroller.New(mgr.GetClient(), retinaendpointchannel) + // start reconcile the cached Pod before manager starts to not miss any events + go ke.ReconcilePod(ctrlCtx) + + pc := podcontroller.New(mgr.GetClient(), mgr.GetScheme(), retinaendpointchannel) + if err = (pc).SetupWithManager(mgr); err != nil { + mainLogger.Error("Unable to create controller", zap.String("controller", "podcontroller"), zap.Error(err)) + os.Exit(1) + } + } + } + + mc := metricsconfiguration.New(mgr.GetClient(), mgr.GetScheme()) + if err = (mc).SetupWithManager(mgr); err != nil { + mainLogger.Error("Unable to create controller", zap.String("controller", "metricsconfiguration"), zap.Error(err)) + os.Exit(1) + } + + //+kubebuilder:scaffold:builder + + if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { + mainLogger.Error("Unable to set up health check", zap.Error(err)) + os.Exit(1) + } + if err := mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil { + mainLogger.Error("Unable to set up ready check", zap.Error(err)) + os.Exit(1) + } + + mainLogger.Info("Starting manager") + if err := mgr.Start(ctrlCtx); err != nil { + mainLogger.Error("Problem running manager", zap.Error(err)) + os.Exit(1) + } + + // start heartbeat goroutine for application insights + go tel.Heartbeat(ctx, HeartbeatFrequency) +} + +func EnablePProf() { + pprofmux := http.NewServeMux() + pprofmux.HandleFunc("/debug/pprof/", pprof.Index) + pprofmux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline) + pprofmux.HandleFunc("/debug/pprof/profile", pprof.Profile) + pprofmux.HandleFunc("/debug/pprof/symbol", pprof.Symbol) + pprofmux.HandleFunc("/debug/pprof/trace", pprof.Trace) + pprofmux.Handle("/debug/pprof/goroutine", pprof.Handler("goroutine")) + + if err := http.ListenAndServe(":8082", pprofmux); err != nil { //nolint:gosec // TODO replace with secure server that supports timeout + panic(err) + } +} + +func initLogging(cfg *config.OperatorConfig, applicationInsightsID string) error { + logOpts := &log.LogOpts{ + Level: cfg.LogLevel, + File: false, + MaxFileSizeMB: MaxFileSizeMB, + MaxBackups: MaxBackups, + MaxAgeDays: MaxAgeDays, + ApplicationInsightsID: applicationInsightsID, + EnableTelemetry: cfg.EnableTelemetry, + } + + _, err := log.SetupZapLogger(logOpts) + if err != nil { + mainLogger.Error("Failed to setup zap logger", zap.Error(err)) + return fmt.Errorf("failed to setup zap logger: %w", err) + } + + return nil +} diff --git a/operator/cmd/root.go b/operator/cmd/root.go new file mode 100644 index 0000000000..f689f12909 --- /dev/null +++ b/operator/cmd/root.go @@ -0,0 +1,48 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +package cmd + +import ( + "fmt" + "os" + + "github.com/microsoft/retina/operator/cmd/legacy" + "github.com/spf13/cobra" +) + +const ( + configFileName = "retina/operator-config.yaml" +) + +var ( + metricsAddr string + probeAddr string + enableLeaderElection bool + cfgFile string + + rootCmd = &cobra.Command{ + Use: "retina-operator", + Short: "Retina Operator", + Long: "Start Retina Operator", + Run: func(cmd *cobra.Command, args []string) { + fmt.Println("Starting Retina Operator") + d := legacy.NewOperator(metricsAddr, probeAddr, cfgFile, enableLeaderElection) + d.Start() + }, + } +) + +func init() { + rootCmd.Flags().StringVar(&metricsAddr, "metrics-addr", ":8080", "The address the metric endpoint binds to.") + rootCmd.Flags().StringVar(&probeAddr, "probe-addr", ":8081", "The address the probe endpoint binds to.") + rootCmd.Flags().BoolVar(&enableLeaderElection, "enable-leader-election", false, "Enable leader election for controller manager.") + rootCmd.Flags().StringVar(&cfgFile, "config", configFileName, "config file") +} + +func Execute() { + if err := rootCmd.Execute(); err != nil { + fmt.Println(err) + os.Exit(1) + } +} diff --git a/operator/config/config.go b/operator/config/config.go index cc68631744..ade4c9ab2f 100644 --- a/operator/config/config.go +++ b/operator/config/config.go @@ -1,6 +1,11 @@ package config -import "github.com/microsoft/retina/pkg/config" +import ( + "fmt" + + "github.com/microsoft/retina/pkg/config" + "github.com/spf13/viper" +) type OperatorConfig struct { config.CaptureConfig `mapstructure:",squash"` @@ -12,3 +17,23 @@ type OperatorConfig struct { EnableRetinaEndpoint bool `yaml:"enableRetinaEndpoint"` RemoteContext bool `yaml:"remoteContext"` } + +func GetConfig(cfgFileName string) (*OperatorConfig, error) { + viper.SetConfigType("yaml") + viper.SetConfigFile(cfgFileName) + err := viper.ReadInConfig() + if err != nil { + return nil, fmt.Errorf("error reading config file: %w", err) + } + + viper.AutomaticEnv() + + var cfg OperatorConfig + viper.SetDefault("EnableRetinaEndpoint", true) + err = viper.Unmarshal(&cfg) + if err != nil { + return nil, fmt.Errorf("error unmarshalling config: %w", err) + } + + return &cfg, nil +} diff --git a/operator/main.go b/operator/main.go index d9eea22ffd..ea58c130a9 100644 --- a/operator/main.go +++ b/operator/main.go @@ -1,298 +1,10 @@ -/* -Copyright 2023. - -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. -*/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. package main -import ( - "context" - "flag" - "fmt" - "net/http" - "net/http/pprof" - "os" - "time" - - "go.uber.org/zap/zapcore" - - // Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.) - // to ensure that exec-entrypoint and run can make use of them. - - "github.com/spf13/viper" - "go.uber.org/zap" - apiextv1 "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextensions/v1" - k8sruntime "k8s.io/apimachinery/pkg/runtime" - utilruntime "k8s.io/apimachinery/pkg/util/runtime" - "k8s.io/client-go/kubernetes" - clientgoscheme "k8s.io/client-go/kubernetes/scheme" - _ "k8s.io/client-go/plugin/pkg/client/auth" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/healthz" - crzap "sigs.k8s.io/controller-runtime/pkg/log/zap" - metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server" - - retinav1alpha1 "github.com/microsoft/retina/crd/api/v1alpha1" - deploy "github.com/microsoft/retina/deploy/legacy" - "github.com/microsoft/retina/operator/cache" - config "github.com/microsoft/retina/operator/config" - captureUtils "github.com/microsoft/retina/pkg/capture/utils" - captureController "github.com/microsoft/retina/pkg/controllers/operator/capture" - metricsconfiguration "github.com/microsoft/retina/pkg/controllers/operator/metricsconfiguration" - podcontroller "github.com/microsoft/retina/pkg/controllers/operator/pod" - retinaendpointcontroller "github.com/microsoft/retina/pkg/controllers/operator/retinaendpoint" - "github.com/microsoft/retina/pkg/log" - "github.com/microsoft/retina/pkg/telemetry" -) - -var ( - scheme = k8sruntime.NewScheme() - mainLogger *log.ZapLogger - oconfig *config.OperatorConfig - - MAX_POD_CHANNEL_BUFFER = 250 - MAX_METRICS_CONFIGURATION_CHANNEL_BUFFER = 50 - MAX_TRACES_CONFIGURATION_CHANNEL_BUFFER = 50 - MAX_RETINA_ENDPOINT_CHANNEL_BUFFER = 250 - - version = "undefined" - - // applicationInsightsID is the instrumentation key for Azure Application Insights - // It is set during the build process using the -ldflags flag - // If it is set, the application will send telemetry to the corresponding Application Insights resource. - applicationInsightsID string -) - -func init() { - //+kubebuilder:scaffold:scheme - utilruntime.Must(clientgoscheme.AddToScheme(scheme)) - utilruntime.Must(retinav1alpha1.AddToScheme(scheme)) - - var err error - oconfig, err = LoadConfig() - if err != nil { - fmt.Printf("failed to load config with err %s", err.Error()) - os.Exit(1) - } - - err = initLogging(oconfig, applicationInsightsID) - if err != nil { - fmt.Printf("failed to initialize logging with err %s", err.Error()) - os.Exit(1) - } - mainLogger = log.Logger().Named("main") -} +import "github.com/microsoft/retina/operator/cmd" func main() { - go EnablePProf() - var metricsAddr string - var enableLeaderElection bool - var probeAddr string - flag.StringVar(&metricsAddr, "metrics-bind-address", ":8080", "The address the metric endpoint binds to.") - flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.") - flag.BoolVar(&enableLeaderElection, "leader-elect", false, - "Enable leader election for controller manager. "+ - "Enabling this will ensure there is only one active controller manager.") - - mainLogger.Sugar().Infof("Operator configuration", zap.Any("configuration", oconfig)) - - opts := &crzap.Options{ - Development: false, - } - - ctrl.SetLogger(crzap.New(crzap.UseFlagOptions(opts), crzap.Encoder(zapcore.NewConsoleEncoder(log.EncoderConfig())))) - - mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ - Scheme: scheme, - Metrics: metricsserver.Options{ - BindAddress: metricsAddr, - }, - HealthProbeBindAddress: probeAddr, - LeaderElection: enableLeaderElection, - LeaderElectionID: "16937e39.retina.sh", - - // LeaderElectionReleaseOnCancel defines if the leader should step down voluntarily - // when the Manager ends. This requires the binary to immediately end when the - // Manager is stopped, otherwise, this setting is unsafe. Setting this significantly - // speeds up voluntary leader transitions as the new leader don't have to wait - // LeaseDuration time first. - // - // In the default scaffold provided, the program ends immediately after - // the manager stops, so would be fine to enable this option. However, - // if you are doing or is intended to do any operation such as perform cleanups - // after the manager stops then its usage might be unsafe. - // LeaderElectionReleaseOnCancel: true, - }) - if err != nil { - mainLogger.Error("Unable to start manager", zap.Error(err)) - os.Exit(1) - } - - ctx := context.Background() - clientset, err := apiextv1.NewForConfig(mgr.GetConfig()) - if err != nil { - mainLogger.Error("Failed to get apiextension clientset", zap.Error(err)) - os.Exit(1) - } - - if oconfig.InstallCRDs { - mainLogger.Sugar().Infof("Installing CRDs") - crds, err := deploy.InstallOrUpdateCRDs(ctx, oconfig.EnableRetinaEndpoint, clientset) - if err != nil { - mainLogger.Error("unable to register CRDs", zap.Error(err)) - os.Exit(1) - } - for name := range crds { - mainLogger.Info("CRD registered", zap.String("name", name)) - } - } - - apiserverURL, err := telemetry.GetK8SApiserverURLFromKubeConfig() - if err != nil { - mainLogger.Error("Apiserver URL is cannot be found", zap.Error(err)) - os.Exit(1) - } - - var tel telemetry.Telemetry - if oconfig.EnableTelemetry && applicationInsightsID != "" { - mainLogger.Info("telemetry enabled", zap.String("applicationInsightsID", applicationInsightsID)) - properties := map[string]string{ - "version": version, - telemetry.PropertyApiserver: apiserverURL, - } - tel = telemetry.NewAppInsightsTelemetryClient("retina-operator", properties) - } else { - mainLogger.Info("telemetry disabled", zap.String("apiserver", apiserverURL)) - tel = telemetry.NewNoopTelemetry() - } - - kubeClient, err := kubernetes.NewForConfig(mgr.GetConfig()) - if err != nil { - mainLogger.Error("Failed to get clientset", zap.Error(err)) - os.Exit(1) - } - - if err = captureController.NewCaptureReconciler( - mgr.GetClient(), mgr.GetScheme(), kubeClient, oconfig.CaptureConfig, - ).SetupWithManager(mgr); err != nil { - mainLogger.Error("Unable to setup retina capture controller with manager", zap.Error(err)) - os.Exit(1) - } - - ctrlCtx := ctrl.SetupSignalHandler() - - //+kubebuilder:scaffold:builder - - // TODO(mainred): retina-operater is responsible for recycling created retinaendpoints if remotecontext is switched off. - // Tracked by https://github.com/microsoft/retina/issues/522 - if oconfig.RemoteContext { - // Create RetinaEndpoint out of Pod to extract only the necessary fields of Pods to reduce the pressure of APIServer - // when RetinaEndpoint is enabled. - // TODO(mainred): An alternative of RetinaEndpoint, and possible long term solution, is to use CiliumEndpoint - // created for Cilium unmanged Pods. - if oconfig.EnableRetinaEndpoint { - mainLogger.Info("RetinaEndpoint is enabled") - - retinaendpointchannel := make(chan cache.PodCacheObject, MAX_RETINA_ENDPOINT_CHANNEL_BUFFER) - ke := retinaendpointcontroller.New(mgr.GetClient(), retinaendpointchannel) - // start reconcile the cached Pod before manager starts to not miss any events - go ke.ReconcilePod(ctrlCtx) - - pc := podcontroller.New(mgr.GetClient(), mgr.GetScheme(), retinaendpointchannel) - if err = (pc).SetupWithManager(mgr); err != nil { - mainLogger.Error("Unable to create controller", zap.String("controller", "podcontroller"), zap.Error(err)) - os.Exit(1) - } - } - } - - mc := metricsconfiguration.New(mgr.GetClient(), mgr.GetScheme()) - if err = (mc).SetupWithManager(mgr); err != nil { - mainLogger.Error("Unable to create controller", zap.String("controller", "metricsconfiguration"), zap.Error(err)) - os.Exit(1) - } - - //+kubebuilder:scaffold:builder - - if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { - mainLogger.Error("Unable to set up health check", zap.Error(err)) - os.Exit(1) - } - if err := mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil { - mainLogger.Error("Unable to set up ready check", zap.Error(err)) - os.Exit(1) - } - - mainLogger.Info("Starting manager") - if err := mgr.Start(ctrlCtx); err != nil { - mainLogger.Error("Problem running manager", zap.Error(err)) - os.Exit(1) - } - - // start heartbeat goroutine for application insights - go tel.Heartbeat(ctx, 5*time.Minute) -} - -func LoadConfig() (*config.OperatorConfig, error) { - viper.SetConfigType("yaml") - viper.SetConfigFile("retina/operator-config.yaml") - err := viper.ReadInConfig() - if err != nil { - return nil, err - } - - viper.AutomaticEnv() - - var config config.OperatorConfig - - // Check pkg/config/config.go for the explanation of setting EnableRetinaEndpoint defaults to true. - viper.SetDefault("EnableRetinaEndpoint", true) - err = viper.Unmarshal(&config) - - // Set Capture config - config.CaptureConfig.CaptureImageVersion = version - config.CaptureConfig.CaptureImageVersionSource = captureUtils.VersionSourceOperatorImageVersion - - return &config, err -} - -func EnablePProf() { - pprofmux := http.NewServeMux() - pprofmux.HandleFunc("/debug/pprof/", pprof.Index) - pprofmux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline) - pprofmux.HandleFunc("/debug/pprof/profile", pprof.Profile) - pprofmux.HandleFunc("/debug/pprof/symbol", pprof.Symbol) - pprofmux.HandleFunc("/debug/pprof/trace", pprof.Trace) - pprofmux.Handle("/debug/pprof/goroutine", pprof.Handler("goroutine")) - - if err := http.ListenAndServe(":8082", pprofmux); err != nil { - panic(err) - } -} - -func initLogging(config *config.OperatorConfig, applicationInsightsID string) error { - logOpts := &log.LogOpts{ - Level: config.LogLevel, - File: false, - MaxFileSizeMB: 100, - MaxBackups: 3, - MaxAgeDays: 30, - ApplicationInsightsID: applicationInsightsID, - EnableTelemetry: config.EnableTelemetry, - } - - log.SetupZapLogger(logOpts) - - return nil + cmd.Execute() } diff --git a/pkg/controllers/operator/cilium-crds/cache/types.go b/pkg/controllers/operator/cilium-crds/cache/types.go new file mode 100644 index 0000000000..4ff90001d9 --- /dev/null +++ b/pkg/controllers/operator/cilium-crds/cache/types.go @@ -0,0 +1,12 @@ +package cache + +import ( + slim_corev1 "github.com/cilium/cilium/pkg/k8s/slim/k8s/api/core/v1" + + "github.com/cilium/cilium/pkg/k8s/resource" +) + +type PodCacheObject struct { + Key resource.Key + Pod *slim_corev1.Pod +} diff --git a/pkg/controllers/operator/cilium-crds/endpoint/cell.go b/pkg/controllers/operator/cilium-crds/endpoint/cell.go new file mode 100644 index 0000000000..324676d756 --- /dev/null +++ b/pkg/controllers/operator/cilium-crds/endpoint/cell.go @@ -0,0 +1,9 @@ +package endpointcontroller + +import "github.com/cilium/cilium/pkg/hive/cell" + +var Cell = cell.Module( + "endpointcontroller", + "controller for cilium endpoint and identity creation and updates", + cell.Invoke(registerEndpointController), +) diff --git a/pkg/controllers/operator/cilium-crds/endpoint/endpoint_controller.go b/pkg/controllers/operator/cilium-crds/endpoint/endpoint_controller.go new file mode 100644 index 0000000000..0609d28084 --- /dev/null +++ b/pkg/controllers/operator/cilium-crds/endpoint/endpoint_controller.go @@ -0,0 +1,685 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +package endpointcontroller + +import ( + "context" + "encoding/json" + "reflect" + "sync" + "time" + + "github.com/microsoft/retina/pkg/controllers/operator/cilium-crds/cache" + + "github.com/pkg/errors" + "github.com/sirupsen/logrus" + "go.uber.org/zap" + k8serrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + + "github.com/cilium/cilium/pkg/hive/cell" + "github.com/cilium/cilium/pkg/k8s" + ciliumv2 "github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2" + k8sClient "github.com/cilium/cilium/pkg/k8s/client" + "github.com/cilium/cilium/pkg/k8s/client/clientset/versioned" + "github.com/cilium/cilium/pkg/k8s/resource" + slim_corev1 "github.com/cilium/cilium/pkg/k8s/slim/k8s/api/core/v1" + slim_clientset "github.com/cilium/cilium/pkg/k8s/slim/k8s/client/clientset/versioned" + "github.com/cilium/cilium/pkg/labels" + "github.com/cilium/workerpool" +) + +const ( + RequestTimeout = 15 * time.Second + MaxWorkers = 20 + + // useOwnerReferences determines whether we set the ownerReferences field on CiliumEndpoints to the Pod that it is associated with. + // With this, k8s will automatically delete the CiliumEndpoint when the Pod is deleted. + // Not sure if this useful. Could be required for endpointgc.Cell? + useOwnerReferences = false +) + +var ErrClientsetDisabled = errors.New("failure due to clientset disabled") + +// endpointReconciler managed the lifecycle of CiliumEndpoints and CiliumIdentities from Pods. +type endpointReconciler struct { + *sync.Mutex + l logrus.FieldLogger + clientset versioned.Interface + ciliumSlimClientSet slim_clientset.Interface + // podEvents represents Pod CRUD events relayed from the Pod controller. If the Pod was deleted, the PodCacheObject.Pod field will be nil + podEvents chan cache.PodCacheObject + // ciliumEndpoints has a store of CiliumEndpoints in API Server + ciliumEndpoints resource.Resource[*ciliumv2.CiliumEndpoint] + + // pods is a store of Pods in API Server + pods resource.Resource[*slim_corev1.Pod] + + // namespace is a store of Namespaces in API Server + namespaces resource.Resource[*slim_corev1.Namespace] + + identityManager *IdentityManager + + // store of processed pods and namespaces + // processedPodCache map in store pod key to PodEndpoint. + // It contains only pods which we have processed via Pod events. + // It contains endpoint goal state, and is independent of ciliumEndpoints store. + // When endpointReconciler is leading, all endpoint state should be in API Server. + store *Store + + wp *workerpool.WorkerPool +} + +type params struct { + cell.In + + Logger logrus.FieldLogger + Lifecycle cell.Lifecycle + Clientset k8sClient.Clientset + CiliumEndpoints resource.Resource[*ciliumv2.CiliumEndpoint] + Namespaces resource.Resource[*slim_corev1.Namespace] + Pods resource.Resource[*slim_corev1.Pod] +} + +func registerEndpointController(p params) error { + if !p.Clientset.IsEnabled() { + return ErrClientsetDisabled + } + + l := p.Logger.WithField("component", "endpointcontroller") + r := &endpointReconciler{ + Mutex: &sync.Mutex{}, + l: l, + clientset: p.Clientset, + ciliumSlimClientSet: p.Clientset.Slim(), + ciliumEndpoints: p.CiliumEndpoints, + pods: p.Pods, + namespaces: p.Namespaces, + store: NewStore(), + } + + p.Lifecycle.Append(r) + + return nil +} + +func (r *endpointReconciler) Start(_ cell.HookContext) error { + // NOTE: we must create IdentityManager on leader election since its allocator auto-starts on creation. + // There is a way to disable auto-start but then there is no exposed function to simply start(). + im, err := NewIdentityManager(r.l, r.clientset) + if err != nil { + return errors.Wrap(err, "failed to create identity manager") + } + + r.identityManager = im + + // making sure we have only one thread running at a time. + r.wp = workerpool.New(MaxWorkers) + + if err := r.wp.Submit("namespace-controller", r.runNamespaceEvents); err != nil { + return errors.Wrap(err, "failed to submit task to namespace workerpool") + } + + if err := r.wp.Submit("endpoint-reconciler", r.run); err != nil { + return errors.Wrap(err, "failed to submit task to endpoint workerpool") + } + + return nil +} + +func (r *endpointReconciler) Stop(_ cell.HookContext) error { + if err := r.wp.Close(); err != nil { + return errors.Wrap(err, "failed to stop endpoint workerpool") + } + + return nil +} + +func (r *endpointReconciler) run(pctx context.Context) error { + r.l.Info("start to reconcile Pods for CiliumEndpoints") + + podEvents := r.pods.Events(pctx) + + for { + select { + case ev, ok := <-podEvents: + if !ok { + r.l.Info("Pod Events channel is closed. Stopping reconciling Pods for CiliumEndpoints") + return nil + } + + if ev.Object != nil && ev.Object.Spec.HostNetwork { + r.l.WithField("podKey", ev.Key.String()).Debug("pod is host networked, skipping") + ev.Done(nil) + continue + } + + err := r.wp.Submit("pod-event-handler", func(ctx context.Context) error { + return r.runEventHandler(ctx, ev) + }) + if err != nil { + r.l.WithError(err).WithField("podKey", ev.Key.String()).Error("failed to submit pod event handler") + } + + case <-pctx.Done(): + r.l.Info("stop reconciling Pods for CiliumEndpoints") + return nil + } + } +} + +func (r *endpointReconciler) runEventHandler(pctx context.Context, ev resource.Event[*slim_corev1.Pod]) error { + var err error + ctx, cancel := context.WithTimeout(pctx, RequestTimeout) + switch ev.Kind { + case resource.Sync: + // Ignore the update/ + case resource.Upsert: + // HANDLE UPSERT + if ev.Object != nil && ev.Object.Spec.HostNetwork { + r.l.WithField("podKey", ev.Key.String()).Debug("pod is host networked, skipping") + } else { + err = r.ReconcilePod(ctx, ev.Key, ev.Object) + } + case resource.Delete: + err = r.HandlePodDelete(ctx, ev.Key) + } + cancel() + + if err != nil { + r.l.WithError(err).WithField("podKey", ev.Key.String()).Error("error creating cilium endpoint. requeuing pod") + } + ev.Done(err) + + return nil +} + +func (r *endpointReconciler) runNamespaceEvents(pctx context.Context) error { + r.l.Info("start to reconcile Namespaces for CiliumEndpoints") + + namespaceEvents := r.namespaces.Events(pctx) + + for { + select { + case ev, ok := <-namespaceEvents: + if !ok { + r.l.Info("Namespace Events channel is closed. Stopping reconciling Namespaces for CiliumEndpoints") + return nil + } + + var err error + ctx, cancel := context.WithTimeout(pctx, RequestTimeout) + switch ev.Kind { + case resource.Sync: + // Ignore the update/ + case resource.Upsert: + // HANDLE UPSERT + err = r.reconcileNamespace(ctx, ev.Object) + case resource.Delete: + err = r.handleNamespaceDelete(ctx, ev.Key.Name) + } + cancel() + + if err != nil { + r.l.WithError(err).WithField("namespaceKey", ev.Key.String()).Error("error creating cilium endpoint. requeuing namespace") + } + ev.Done(err) + case <-pctx.Done(): + r.l.Info("stop reconciling Namespaces for CiliumEndpoints") + return nil + } + } +} + +func (r *endpointReconciler) ReconcilePodsInNamespace(ctx context.Context, namespace string) error { + r.Mutex.Lock() + defer r.Mutex.Unlock() + r.l.Debug("reconciling pods in namespace", zap.String("namespace ", namespace)) + podList := r.store.ListPodKeysByNamespace(namespace) + for _, podKey := range podList { + pod, ok := r.store.GetPod(podKey) + if !ok { + r.l.WithField("podKey", podKey.String()).Debug("pod not found in cache, skipping") + continue + } + + if pod.toDelete { + r.l.WithField("podKey", podKey.String()).Debug("pod marked for deletion, skipping") + continue + } + + newPEP := pod.deepCopy() + endpointsLabels, err := r.ciliumEndpointsLabels(ctx, pod.podObj) + if err != nil { + return errors.Wrap(err, "failed to get pod labels") + } + newPEP.lbls = endpointsLabels + + r.l.Debug("upserting pod in namespace", + zap.String("namespace ", namespace), + zap.String("podKey", podKey.String()), + zap.Any("old labels ", pod.lbls), + zap.Any("new labels ", newPEP.lbls), + ) + + err = r.handlePodUpsert(ctx, newPEP) + if err != nil { + r.l.Error("failed to upsert pod", zap.Error(err), zap.String("podKey", podKey.String())) + } + + if err != nil { + return errors.Wrap(err, "failed to upsert pod") + } + + } + + return nil +} + +func (r *endpointReconciler) ReconcilePod(ctx context.Context, podKey resource.Key, pod *slim_corev1.Pod) error { + r.Mutex.Lock() + defer r.Mutex.Unlock() + r.l.Debug("reconciling pod with lock", zap.String("namespace", podKey.Namespace), zap.String("pod ", podKey.Name)) + return r.reconcilePod(ctx, podKey, pod) +} + +func (r *endpointReconciler) reconcilePod(ctx context.Context, podKey resource.Key, pod *slim_corev1.Pod) error { + if pod == nil || pod.DeletionTimestamp != nil { + // the pod has been deleted + if err := r.handlePodDelete(ctx, podKey); err != nil { + return errors.Wrap(err, "failed to delete endpoint for deleted pod") + } + + return nil + } + + if pod.Status.PodIP == "" || pod.Status.HostIP == "" { + r.l.WithField("podKey", podKey.String()).Trace("pod missing an IP, skipping") + return nil + } + + podLabels, err := r.ciliumEndpointsLabels(ctx, pod) + if err != nil { + return errors.Wrap(err, "failed to get pod labels") + } + newPEP := &PodEndpoint{ + key: podKey, + lbls: podLabels, + ipv4: pod.Status.PodIP, + nodeIP: pod.Status.HostIP, + // TODO: set to false if in follower mode + processedAsLeader: true, + uid: pod.ObjectMeta.UID, + podObj: pod, + } + + if err := r.handlePodUpsert(ctx, newPEP); err != nil { + return errors.Wrap(err, "failed to upsert endpoint") + } + + return nil +} + +func (r *endpointReconciler) HandlePodDelete(ctx context.Context, n resource.Key) error { + r.Mutex.Lock() + defer r.Mutex.Unlock() + r.l.Debug("handling pod delete with lock", zap.String("podKey", n.String())) + return r.handlePodDelete(ctx, n) +} + +func (r *endpointReconciler) handlePodDelete(ctx context.Context, n resource.Key) error { + pep, ok := r.store.GetToDeletePod(n) + if !ok { + // do not do anything if we have not processed the pod + // let endpointgc delete the CiliumEndpoint as necessary + r.l.WithField("podKey", n.String()).Trace("pod not found in cache, skipping deletion") + return nil + } + + r.l.WithField("podKey", n.String()).Trace("handling pod delete") + + // delete CEP even if we haven't processed the Pod (and incremented identity reference count) + err := r.clientset.CiliumV2().CiliumEndpoints(n.Namespace).Delete(ctx, n.Name, metav1.DeleteOptions{}) + if err != nil && !k8serrors.IsNotFound(err) { + r.l.WithError(err).WithField("podKey", n.String()).Error("failed to delete CiliumEndpoint") + return errors.Wrap(err, "failed to delete endpoint") + } + + r.l.WithField("podKey", n.String()).Debug("deleted CiliumEndpoint") + + // Identity reference count must be modified after CiliumEndpoint is successfully deleted. + // Otherwise, we could decrement reference multiple times if CiliumEndpoint deletion fails and we retry this method. + r.identityManager.DecrementReference(ctx, pep.lbls) + r.store.DeletePod(n) + + return nil +} + +func (r *endpointReconciler) handlePodUpsert(ctx context.Context, newPEP *PodEndpoint) error { //nolint:gocyclo // This function is too complex and should be refactored + r.l.WithField("podKey", newPEP.key.String()).Trace("handling pod upsert") + + oldPEP, inCache := r.store.GetPod(newPEP.key) + inStore := false + if inCache { + r.l.WithFields(logrus.Fields{ + "podKey": newPEP.key.String(), + "pep": oldPEP, + }).Trace("PodEndpoint found in cache") + } else { + // this call will block until the store is synced with API Server + store, err := r.ciliumEndpoints.Store(ctx) + if err != nil { + return errors.Wrap(err, "failed to get store") + } + + key := resource.Key{Namespace: newPEP.key.Namespace, Name: newPEP.key.Name} + oldCEP, ok, err := store.GetByKey(key) + if err != nil { + return errors.Wrap(err, "failed to get from CiliumEndpoint store") + } + + inStore = ok + + if inStore { + r.l.WithFields(logrus.Fields{ + "podKey": newPEP.key.String(), + "ownerReferences": oldCEP.ObjectMeta.OwnerReferences, + "endpointID": oldCEP.Status.ID, + "identity": oldCEP.Status.Identity, + "networking": oldCEP.Status.Networking, + }).Trace("CiliumEndpoint found in store") + + if oldCEP.Status.Networking == nil || len(oldCEP.Status.Networking.Addressing) == 0 || oldCEP.Status.Networking.Addressing[0].IPV4 == "" { + // FIXME handle IPv6 and dual-stack + inStore = false + r.l.WithFields(logrus.Fields{ + "podKey": newPEP.key.String(), + "cep": oldCEP, + }).Warn("CiliumEndpoint has no ipv4 address, ignoring") + } else { + oldPEP = &PodEndpoint{ + key: newPEP.key, + endpointID: oldCEP.Status.ID, + ipv4: oldCEP.Status.Networking.Addressing[0].IPV4, + nodeIP: oldCEP.Status.Networking.NodeIP, + } + + if oldCEP.Status.Identity != nil { + oldPEP.identityID = oldCEP.Status.Identity.ID + oldPEP.lbls = labels.NewLabelsFromModel(oldCEP.Status.Identity.Labels) + } + } + } + } + + if inCache || inStore { + // patch existing CEP + // specify initial IDs (might change) + newPEP.endpointID = oldPEP.endpointID + newPEP.identityID = oldPEP.identityID + + sameNetworking := newPEP.ipv4 == oldPEP.ipv4 && newPEP.nodeIP == oldPEP.nodeIP + equalLabels := newPEP.lbls.Equals(oldPEP.lbls) + + r.l.WithFields(logrus.Fields{ + "podKey": newPEP.key.String(), + "inCache": inCache, + "sameNetworking": sameNetworking, + "equalLabels": equalLabels, + "oldLbls": oldPEP.lbls, + "newLbls": newPEP.lbls, + }).Trace("patching CiliumEndpoint") + + // allocate new identity if labels have changed or if we haven't assigned an identity ID to this pod yet + // TODO when implementing follower check if inCache or processedAsLeader + shouldAllocateNewIdentity := !inCache || !equalLabels || newPEP.identityID == 0 + if shouldAllocateNewIdentity { + r.l.WithFields(logrus.Fields{ + "podKey": newPEP.key.String(), + "pep": oldPEP, + }).Trace("creating new identity for pod") + + identityID, err := r.identityManager.GetIdentityAndIncrementReference(ctx, newPEP.lbls) + if err != nil { + return errors.Wrap(err, "failed to get identity ID for updated/uncached pod") + } + + newPEP.identityID = identityID + } + + if sameNetworking && equalLabels { + // nothing to do. pod already has a CEP with the same networking and labels + r.l.WithField("podKey", newPEP.key.String()).Trace("pod already processed") + r.store.AddPod(newPEP) + return nil + } + + if !sameNetworking { + r.l.WithField("podKey", newPEP.key.String()).Trace("pod networking has changed") + // change endpoint id since networking has changed + // FIXME use endpoint allocator to get new endpoint ID + newPEP.endpointID++ + } + + status := newPEP.endpointStatus() + replaceCEPStatus := []k8s.JSONPatch{ + { + OP: "replace", + Path: "/status", + Value: status, + }, + } + + if !sameNetworking && useOwnerReferences { + // Pod has changed, update ownerReferences. + // This might be a pointless patch call (resulting in CEP not found) + // since the CEP will be deleted when the original OwnerReference Pod is deleted from API Server. + replaceCEPStatus = append(replaceCEPStatus, k8s.JSONPatch{ + OP: "test", + Path: "/metadata/ownerReferences", + Value: []metav1.OwnerReference{ + { + APIVersion: "v1", + Kind: "Pod", + Name: newPEP.key.Name, + UID: newPEP.uid, + }, + }, + }) + } + + createStatusPatch, err := json.Marshal(replaceCEPStatus) + if err != nil { + r.l.WithFields(logrus.Fields{ + "podKey": newPEP.key.String(), + "pep": newPEP, + "uid": newPEP.uid, + }).Debug("marshalling status failed") + + if shouldAllocateNewIdentity { + // decrement reference for new identity + r.l.WithField("podKey", newPEP.key.String()).Trace("marshal failed, decrementing reference for new identity") + r.identityManager.DecrementReference(ctx, newPEP.lbls) + } + + // TODO release newly allocated endpoint ID if networking not the same + + return errors.Wrap(err, "failed to marshal status patch") + } + + _, err = r.clientset.CiliumV2().CiliumEndpoints(newPEP.key.Namespace).Patch(ctx, newPEP.key.Name, types.JSONPatchType, createStatusPatch, metav1.PatchOptions{}) + if (err == nil || k8serrors.IsNotFound(err)) && inCache && !equalLabels { + // decrement reference for old identity + // TODO when implementing follower check if processedAsLeader + r.identityManager.DecrementReference(ctx, oldPEP.lbls) + } + + if err == nil { + // Update the pod in cache and return + r.store.AddPod(newPEP) + return nil + } + if shouldAllocateNewIdentity { + // Decrement reference for new identity. + // May end up incrementing reference count for this same identity again if we try to create the CEP below. + // No downside to decrementing reference here and then incrementing again below (will not affect API Server). + r.l.WithField("podKey", newPEP.key.String()).Trace("patch unsuccessful, decrementing reference for new identity") + r.identityManager.DecrementReference(ctx, newPEP.lbls) + } + + // TODO if networking changed, release newly allocated endpoint ID. + // May end up getting another endpoint ID below if we try to create the CEP below. + // No downside to this. + + if !k8serrors.IsNotFound(err) && err != nil { + r.l.WithError(err).WithFields(logrus.Fields{ + "podKey": newPEP.key.String(), + "pep": newPEP, + "uid": newPEP.uid, + }).Error("failed to patch CiliumEndpoint") + + return errors.Wrap(err, "failed to patch endpoint") + } + + r.l.WithField("podKey", newPEP.key.String()).Debug("patch unsuccessful because CiliumEndpoint is not in API Server. now creating CiliumEndpoint") + + // Endpoint was not found, create it below. + // Make sure the pod does not exist in the cache so that we don't try to patch it again (in case of a retry after a failure below). + // The CEP should (eventually) not exist in the CEP store too since API Server says it does not exist. + r.store.DeletePod(newPEP.key) + } + + // create CEP + // get new identity ID + identityID, err := r.identityManager.GetIdentityAndIncrementReference(ctx, newPEP.lbls) + if err != nil { + return errors.Wrap(err, "failed to get identity ID for new pod") + } + + newPEP.identityID = identityID + // FIXME specify endpoint ID with allocator + newPEP.endpointID = 1 + + // create CiliumEndpoint + newCEP := &ciliumv2.CiliumEndpoint{ + ObjectMeta: metav1.ObjectMeta{ + Name: newPEP.key.Name, + Namespace: newPEP.key.Namespace, + }, + Status: newPEP.endpointStatus(), + } + + if useOwnerReferences { + newCEP.ObjectMeta.OwnerReferences = []metav1.OwnerReference{ + { + APIVersion: "v1", + Kind: "Pod", + Name: newPEP.key.Name, + UID: newPEP.uid, + }, + } + } + + _, err = r.clientset.CiliumV2().CiliumEndpoints(newPEP.key.Namespace).Create(ctx, newCEP, metav1.CreateOptions{}) + if err != nil { + r.l.WithError(err).WithField("podKey", newPEP.key.String()).Error("failed to create CiliumEndpoint") + r.identityManager.DecrementReference(ctx, newPEP.lbls) + // FIXME release newly allocated endpoint ID + return errors.Wrap(err, "failed to create endpoint") + } + + r.l.WithField("podKey", newPEP.key.String()).Debug("created CiliumEndpoint") + r.store.AddPod(newPEP) + return nil +} + +func (r *endpointReconciler) reconcileNamespace(ctx context.Context, namespace *slim_corev1.Namespace) error { + if namespace == nil { + return nil + } + + // check if namespace is being deleted + if namespace.DeletionTimestamp != nil { + return r.handleNamespaceDelete(ctx, namespace.GetName()) + } + + // check if namespace is in cache + oldNs, ok := r.store.GetNamespace(namespace.GetName()) + if !ok { + r.l.Debug("Adding new namespace to cache", zap.String("namespace ", namespace.GetName())) + // if this is the first time we see this namespace, add it to cache + // there might not be any pods in this namespace yet + r.store.AddNamespace(namespace) + return nil + } + + if oldNs.GetResourceVersion() == namespace.GetResourceVersion() { + r.l.Debug("Namespace already processed", zap.String("namespace ", namespace.GetName())) + return nil + } + + if reflect.DeepEqual(oldNs.Labels, namespace.Labels) { + r.l.Debug("Namespace labels are the same", zap.String("namespace ", namespace.GetName())) + return nil + } + + r.l.Debug("Updating namespace in cache", zap.String("namespace ", namespace.GetName())) + r.store.AddNamespace(namespace) + + // now get all pods and update them as well + err := r.ReconcilePodsInNamespace(ctx, namespace.GetName()) + if err != nil { + return errors.Wrap(err, "failed to reconcile pods in namespace"+namespace.GetName()) + } + return nil +} + +func (r *endpointReconciler) handleNamespaceDelete(_ context.Context, namespaceName string) error { + _, ok := r.store.GetNamespace(namespaceName) + if !ok { + r.l.Debug("Adding new namespace to cache", zap.String("namespace ", namespaceName)) + return nil + } + + r.l.Debug("Deleting namespace from cache", zap.String("namespace ", namespaceName)) + // Ignore deleting the pods for this NS, pod controller will eventually clean it up. + // Once deleting all the pods in the namespace, delete the namespace + r.store.DeleteNamespace(namespaceName) + return nil +} + +func (r *endpointReconciler) ciliumEndpointsLabels(ctx context.Context, pod *slim_corev1.Pod) (labels.Labels, error) { + // Get namespace labels from cache + ns, ok := r.store.GetNamespace(pod.Namespace) + var err error + if !ok { + ns, err = r.ciliumSlimClientSet.CoreV1().Namespaces().Get(ctx, pod.Namespace, metav1.GetOptions{}) + if err != nil { + r.l.WithError(err).WithFields(logrus.Fields{ + "podKey": pod.Name, + "ns": pod.Namespace, + }).Error("failed to get namespace") + return nil, errors.Wrap(err, "failed to get namespace") + } + r.store.AddNamespace(ns) + } + _, ciliumLabels, _, err := k8s.GetPodMetadata(ns, pod) + if err != nil { + r.l.WithError(err).WithFields(logrus.Fields{ + "podKey": pod.Name, + "ns": pod.Namespace, + }).Error("failed to get pod metadata") + return nil, errors.Wrap(err, "failed to get pod metadata") + } + lbls := make(labels.Labels, len(ciliumLabels)) + for k, v := range ciliumLabels { + lbls[k] = labels.Label{ + Key: k, + Value: v, + Source: labels.LabelSourceK8s, + } + } + return lbls, nil +} diff --git a/pkg/controllers/operator/cilium-crds/endpoint/endpoint_controller_test.go b/pkg/controllers/operator/cilium-crds/endpoint/endpoint_controller_test.go new file mode 100644 index 0000000000..3fe9ea4631 --- /dev/null +++ b/pkg/controllers/operator/cilium-crds/endpoint/endpoint_controller_test.go @@ -0,0 +1,709 @@ +package endpointcontroller + +import ( + "context" + "fmt" + "sync" + "testing" + "time" + + ciliumv2 "github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2" + ciliumclient "github.com/cilium/cilium/pkg/k8s/client" + "github.com/cilium/cilium/pkg/k8s/resource" + v1 "github.com/cilium/cilium/pkg/k8s/slim/k8s/api/core/v1" + slim_metav1 "github.com/cilium/cilium/pkg/k8s/slim/k8s/apis/meta/v1" + corev1 "github.com/cilium/cilium/pkg/k8s/slim/k8s/client/clientset/versioned/typed/core/v1" + "github.com/cilium/cilium/pkg/labels" + "github.com/cilium/cilium/pkg/option" + "github.com/microsoft/retina/pkg/controllers/operator/cilium-crds/cache" + ciliumutil "github.com/microsoft/retina/pkg/utils/testutil/cilium" + "github.com/sirupsen/logrus" + "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func podTestX() (resource.Key, *v1.Pod) { + return resource.Key{ + Name: "x", + Namespace: "test", + }, + &v1.Pod{ + ObjectMeta: slim_metav1.ObjectMeta{ + UID: "111", + Labels: map[string]string{ + "k1": "v1", + }, + Name: "x", + Namespace: "test", + }, + Status: v1.PodStatus{ + PodIP: "1.2.3.4", + HostIP: "10.0.0.1", + }, + } +} + +func createNamespace(c corev1.CoreV1Interface) { + // Create the namespace. + err, _ := c.Namespaces().Create(context.TODO(), &v1.Namespace{ + ObjectMeta: slim_metav1.ObjectMeta{ + Name: "test", + }, + }, metav1.CreateOptions{ + TypeMeta: metav1.TypeMeta{ + Kind: "Namespace", + APIVersion: "v1", + }, + }) + if err != nil { + fmt.Printf("Error creating namespace %s:\n", err) + } +} + +func TestPodCreate(t *testing.T) { + r, ciliumEndpoints := newTestEndpointReconciler(t) + + createNamespace(r.ciliumSlimClientSet.CoreV1()) + podKey, pod := podTestX() + + require.NoError(t, r.ReconcilePod(context.TODO(), podKey, pod)) + key := resource.Key{Name: "x", Namespace: "test"} + pep, ok := r.store.GetPod(key) + require.True(t, ok) + require.NotNil(t, pep) + identityID := pep.identityID + require.Greater(t, identityID, int64(0)) + + var expectedEndpointID int64 = 1 // FIXME switch to mock allocator once endpoint IDs are allocated by the operator + expectedCache := map[resource.Key]*PodEndpoint{ + key: { + key: key, + endpointID: expectedEndpointID, + identityID: identityID, + lbls: labels.Labels{ + "k1": labels.Label{ + Key: "k1", + Value: "v1", + Source: "k8s", + }, + "io.kubernetes.pod.namespace": labels.Label{ + Key: "io.kubernetes.pod.namespace", + Value: "test", + Source: "k8s", + }, + "io.cilium.k8s.policy.cluster": labels.Label{ + Key: "io.cilium.k8s.policy.cluster", + Value: "", + Source: "k8s", + }, + }, + ipv4: "1.2.3.4", + nodeIP: "10.0.0.1", + processedAsLeader: true, + uid: "111", + podObj: pod, + }, + } + + require.Equal(t, expectedCache, r.store.Pods) + cep := getAndAssertCEPExists(t, ciliumEndpoints, pod) + expectedCEP := &ciliumv2.CiliumEndpoint{ + ObjectMeta: metav1.ObjectMeta{ + Name: "x", + Namespace: "test", + }, + Status: ciliumv2.EndpointStatus{ + ID: expectedEndpointID, + Identity: &ciliumv2.EndpointIdentity{ + ID: identityID, + Labels: []string{"k8s:io.cilium.k8s.policy.cluster", "k8s:io.kubernetes.pod.namespace=test", "k8s:k1=v1"}, + }, + Networking: &ciliumv2.EndpointNetworking{ + NodeIP: "10.0.0.1", + Addressing: ciliumv2.AddressPairList{ + { + IPV4: "1.2.3.4", + }, + }, + }, + + State: "ready", + }, + } + t.Logf("%+v", cep.Status.Identity.Labels) + require.Equal(t, expectedCEP, cep) +} + +func TestPodDelete(t *testing.T) { + r, ciliumEndpoints := newTestEndpointReconciler(t) + + createNamespace(r.ciliumSlimClientSet.CoreV1()) + podKey, pod := podTestX() + + require.NoError(t, r.ReconcilePod(context.TODO(), podKey, pod)) + pod = nil + require.NoError(t, r.ReconcilePod(context.TODO(), podKey, pod)) + assertCEPDoesNotExist(t, ciliumEndpoints, podKey) +} + +func TestPodDeleteNoOp(t *testing.T) { + r, ciliumEndpoints := newTestEndpointReconciler(t) + + createNamespace(r.ciliumSlimClientSet.CoreV1()) + podKey, _ := podTestX() + + require.NoError(t, r.ReconcilePod(context.TODO(), podKey, nil)) + assertCEPDoesNotExist(t, ciliumEndpoints, podKey) +} + +func TestPodLabelsChanged(t *testing.T) { + r, ciliumEndpoints := newTestEndpointReconciler(t) + + createNamespace(r.ciliumSlimClientSet.CoreV1()) + podKey, pod := podTestX() + + require.NoError(t, r.ReconcilePod(context.TODO(), podKey, pod)) + key := resource.Key{Name: "x", Namespace: "test"} + pep, ok := r.store.GetPod(key) + require.True(t, ok) + require.NotNil(t, pep) + identityID := pep.identityID + require.Greater(t, identityID, int64(0)) + + var expectedEndpointID int64 = 1 // FIXME switch to mock allocator once endpoint IDs are allocated by the operator + expectedCache := map[resource.Key]*PodEndpoint{ + key: { + key: key, + endpointID: expectedEndpointID, + identityID: identityID, + lbls: labels.Labels{ + "k1": labels.Label{ + Key: "k1", + Value: "v1", + Source: "k8s", + }, + "io.kubernetes.pod.namespace": labels.Label{ + Key: "io.kubernetes.pod.namespace", + Value: "test", + Source: "k8s", + }, + "io.cilium.k8s.policy.cluster": labels.Label{ + Key: "io.cilium.k8s.policy.cluster", + Value: "", + Source: "k8s", + }, + }, + ipv4: "1.2.3.4", + nodeIP: "10.0.0.1", + processedAsLeader: true, + uid: "111", + podObj: pod, + }, + } + + require.Equal(t, expectedCache, r.store.Pods) + cep := getAndAssertCEPExists(t, ciliumEndpoints, pod) + expectedCEP := &ciliumv2.CiliumEndpoint{ + ObjectMeta: metav1.ObjectMeta{ + Name: "x", + Namespace: "test", + }, + Status: ciliumv2.EndpointStatus{ + ID: expectedEndpointID, + Identity: &ciliumv2.EndpointIdentity{ + ID: identityID, + Labels: []string{"k8s:io.cilium.k8s.policy.cluster", "k8s:io.kubernetes.pod.namespace=test", "k8s:k1=v1"}, + }, + Networking: &ciliumv2.EndpointNetworking{ + NodeIP: "10.0.0.1", + Addressing: ciliumv2.AddressPairList{ + { + IPV4: "1.2.3.4", + }, + }, + }, + + State: "ready", + }, + } + t.Logf("%+v", cep.Status.Identity.Labels) + require.Equal(t, expectedCEP, cep) + + podKeyNew, podNew := podTestX() + podNew.ObjectMeta.Labels["k1"] = "v2" + podNew.ObjectMeta.Labels["k2"] = "v2" + + require.NoError(t, r.ReconcilePod(context.TODO(), podKeyNew, podNew)) + + pep, ok = r.store.GetPod(key) + require.True(t, ok) + require.NotNil(t, pep) + require.NotEqual(t, identityID, pep.identityID) + identityID = pep.identityID + expectedCacheNew := map[resource.Key]*PodEndpoint{ + key: { + key: key, + endpointID: expectedEndpointID, + identityID: identityID, + lbls: labels.Labels{ + "k2": labels.Label{ + Key: "k2", + Value: "v2", + Source: "k8s", + }, + "k1": labels.Label{ + Key: "k1", + Value: "v2", + Source: "k8s", + }, + "io.kubernetes.pod.namespace": labels.Label{ + Key: "io.kubernetes.pod.namespace", + Value: "test", + Source: "k8s", + }, + "io.cilium.k8s.policy.cluster": labels.Label{ + Key: "io.cilium.k8s.policy.cluster", + Value: "", + Source: "k8s", + }, + }, + ipv4: "1.2.3.4", + nodeIP: "10.0.0.1", + processedAsLeader: true, + uid: "111", + podObj: podNew, + }, + } + + require.Equal(t, expectedCacheNew, r.store.Pods) + cep = getAndAssertCEPExists(t, ciliumEndpoints, podNew) + expectedCEP = &ciliumv2.CiliumEndpoint{ + ObjectMeta: metav1.ObjectMeta{ + Name: "x", + Namespace: "test", + }, + Status: ciliumv2.EndpointStatus{ + ID: expectedEndpointID, + Identity: &ciliumv2.EndpointIdentity{ + ID: identityID, + Labels: []string{"k8s:io.cilium.k8s.policy.cluster", "k8s:io.kubernetes.pod.namespace=test", "k8s:k1=v2", "k8s:k2=v2"}, + }, + Networking: &ciliumv2.EndpointNetworking{ + NodeIP: "10.0.0.1", + Addressing: ciliumv2.AddressPairList{ + { + IPV4: "1.2.3.4", + }, + }, + }, + + State: "ready", + }, + } + t.Logf("%+v", cep.Status.Identity.Labels) + require.Equal(t, expectedCEP, cep) +} + +func TestPodNetworkingChanged(t *testing.T) { + r, ciliumEndpoints := newTestEndpointReconciler(t) + + createNamespace(r.ciliumSlimClientSet.CoreV1()) + podKey, pod := podTestX() + var expectedEndpointID int64 = 1 + require.NoError(t, r.ReconcilePod(context.TODO(), podKey, pod)) + cep := getAndAssertCEPExists(t, ciliumEndpoints, pod) + key := resource.Key{Name: "x", Namespace: "test"} + pep, ok := r.store.GetPod(key) + require.True(t, ok) + require.NotNil(t, pep) + identityID := pep.identityID + expectedCEP := &ciliumv2.CiliumEndpoint{ + ObjectMeta: metav1.ObjectMeta{ + Name: "x", + Namespace: "test", + }, + Status: ciliumv2.EndpointStatus{ + ID: expectedEndpointID, + Identity: &ciliumv2.EndpointIdentity{ + ID: identityID, + Labels: []string{"k8s:io.cilium.k8s.policy.cluster", "k8s:io.kubernetes.pod.namespace=test", "k8s:k1=v1"}, + }, + Networking: &ciliumv2.EndpointNetworking{ + NodeIP: "10.0.0.1", + Addressing: ciliumv2.AddressPairList{ + { + IPV4: "1.2.3.4", + }, + }, + }, + + State: "ready", + }, + } + t.Logf("%+v", cep.Status.Identity.Labels) + require.Equal(t, expectedCEP, cep) + + podKeyNew, podNew := podTestX() + podNew.Status.PodIP = "4.3.2.1" + + require.NoError(t, r.ReconcilePod(context.TODO(), podKeyNew, podNew)) + expectedEndpointID++ + + pep, ok = r.store.GetPod(key) + require.True(t, ok) + require.NotNil(t, pep) + identityID = pep.identityID + expectedCacheNew := map[resource.Key]*PodEndpoint{ + key: { + key: key, + endpointID: expectedEndpointID, + identityID: identityID, + lbls: labels.Labels{ + "k1": labels.Label{ + Key: "k1", + Value: "v1", + Source: "k8s", + }, + "io.kubernetes.pod.namespace": labels.Label{ + Key: "io.kubernetes.pod.namespace", + Value: "test", + Source: "k8s", + }, + "io.cilium.k8s.policy.cluster": labels.Label{ + Key: "io.cilium.k8s.policy.cluster", + Value: "", + Source: "k8s", + }, + }, + ipv4: "4.3.2.1", + nodeIP: "10.0.0.1", + processedAsLeader: true, + uid: "111", + podObj: podNew, + }, + } + require.Equal(t, expectedCacheNew, r.store.Pods) + cep = getAndAssertCEPExists(t, ciliumEndpoints, podNew) + + expectedCEPNew := &ciliumv2.CiliumEndpoint{ + ObjectMeta: metav1.ObjectMeta{ + Name: "x", + Namespace: "test", + }, + Status: ciliumv2.EndpointStatus{ + ID: expectedEndpointID, + Identity: &ciliumv2.EndpointIdentity{ + ID: identityID, + Labels: []string{"k8s:io.cilium.k8s.policy.cluster", "k8s:io.kubernetes.pod.namespace=test", "k8s:k1=v1"}, + }, + Networking: &ciliumv2.EndpointNetworking{ + NodeIP: "10.0.0.1", + Addressing: ciliumv2.AddressPairList{ + { + IPV4: "4.3.2.1", + }, + }, + }, + + State: "ready", + }, + } + require.Equal(t, expectedCEPNew, cep) + + // Changing the Node IP + podNew.Status.HostIP = "10.10.10.10" + + require.NoError(t, r.ReconcilePod(context.TODO(), podKeyNew, podNew)) + expectedEndpointID++ + + pep, ok = r.store.GetPod(key) + require.True(t, ok) + require.NotNil(t, pep) + identityID = pep.identityID + expectedCacheNew = map[resource.Key]*PodEndpoint{ + key: { + key: key, + endpointID: expectedEndpointID, + identityID: identityID, + lbls: labels.Labels{ + "k1": labels.Label{ + Key: "k1", + Value: "v1", + Source: "k8s", + }, + "io.kubernetes.pod.namespace": labels.Label{ + Key: "io.kubernetes.pod.namespace", + Value: "test", + Source: "k8s", + }, + "io.cilium.k8s.policy.cluster": labels.Label{ + Key: "io.cilium.k8s.policy.cluster", + Value: "", + Source: "k8s", + }, + }, + ipv4: "4.3.2.1", + nodeIP: "10.10.10.10", + processedAsLeader: true, + uid: "111", + podObj: podNew, + }, + } + require.Equal(t, expectedCacheNew, r.store.Pods) + cep = getAndAssertCEPExists(t, ciliumEndpoints, podNew) + + expectedCEPNew = &ciliumv2.CiliumEndpoint{ + ObjectMeta: metav1.ObjectMeta{ + Name: "x", + Namespace: "test", + }, + Status: ciliumv2.EndpointStatus{ + ID: expectedEndpointID, + Identity: &ciliumv2.EndpointIdentity{ + ID: identityID, + Labels: []string{"k8s:io.cilium.k8s.policy.cluster", "k8s:io.kubernetes.pod.namespace=test", "k8s:k1=v1"}, + }, + Networking: &ciliumv2.EndpointNetworking{ + NodeIP: "10.10.10.10", + Addressing: ciliumv2.AddressPairList{ + { + IPV4: "4.3.2.1", + }, + }, + }, + + State: "ready", + }, + } + require.Equal(t, expectedCEPNew, cep) +} + +func TestNamespaceDelete(t *testing.T) { + r, ciliumEndpoints := newTestEndpointReconciler(t) + + createNamespace(r.ciliumSlimClientSet.CoreV1()) + podKey, pod := podTestX() + + require.NoError(t, r.ReconcilePod(context.TODO(), podKey, pod)) + require.NoError(t, r.reconcileNamespace(context.TODO(), &v1.Namespace{ + ObjectMeta: slim_metav1.ObjectMeta{ + Name: "test", + DeletionTimestamp: &slim_metav1.Time{Time: time.Now()}, + }, + })) + + require.Empty(t, r.store.Namespaces) + // deleting namespace does not delete the endpoint in cache. + // we will let pod controller delete the endpoint + _ = getAndAssertCEPExists(t, ciliumEndpoints, pod) +} + +func TestNamespaceUpdate(t *testing.T) { + r, ciliumEndpoints := newTestEndpointReconciler(t) + + createNamespace(r.ciliumSlimClientSet.CoreV1()) + podKey, pod := podTestX() + + require.NoError(t, r.ReconcilePod(context.TODO(), podKey, pod)) + require.NoError(t, r.reconcileNamespace(context.TODO(), &v1.Namespace{ + ObjectMeta: slim_metav1.ObjectMeta{ + Name: "test", + Labels: map[string]string{"k1": "v1"}, + ResourceVersion: "3", + }, + })) + + cep := getAndAssertCEPExists(t, ciliumEndpoints, pod) + var expectedEndpointID int64 = 1 + require.NoError(t, r.ReconcilePod(context.TODO(), podKey, pod)) + key := resource.Key{Name: "x", Namespace: "test"} + pep, ok := r.store.GetPod(key) + require.True(t, ok) + require.NotNil(t, pep) + identityID := pep.identityID + expectedCEP := &ciliumv2.CiliumEndpoint{ + ObjectMeta: metav1.ObjectMeta{ + Name: "x", + Namespace: "test", + }, + Status: ciliumv2.EndpointStatus{ + ID: expectedEndpointID, + Identity: &ciliumv2.EndpointIdentity{ + ID: identityID, + Labels: []string{"k8s:io.cilium.k8s.namespace.labels.k1=v1", "k8s:io.cilium.k8s.policy.cluster", "k8s:io.kubernetes.pod.namespace=test", "k8s:k1=v1"}, + }, + Networking: &ciliumv2.EndpointNetworking{ + NodeIP: "10.0.0.1", + Addressing: ciliumv2.AddressPairList{ + { + IPV4: "1.2.3.4", + }, + }, + }, + + State: "ready", + }, + } + t.Logf("%+v", cep.Status.Identity.Labels) + require.Equal(t, expectedCEP, cep) +} + +func TestUpdateFailurePodLabelsChanged(_ *testing.T) { +} + +func TestUpdateFailurePodNetworkingChanged(_ *testing.T) { +} + +func TestBootupNoOp(_ *testing.T) { +} + +func TestBootupPodLabelsChanged(_ *testing.T) { +} + +func TestBootupPodNetworkingChanged(_ *testing.T) { +} + +func TestBootupUpdateFailurePodLabelsChanged(_ *testing.T) { +} + +func TestBootupUpdateFailurePodNetworkingChanged(_ *testing.T) { +} + +func TestPodWithoutIP(t *testing.T) { + r, ciliumEndpoints := newTestEndpointReconciler(t) + + createNamespace(r.ciliumSlimClientSet.CoreV1()) + + podKey, pod := podTestX() + pod.Status.PodIP = "" + require.NoError(t, r.ReconcilePod(context.TODO(), podKey, pod)) + assertCEPDoesNotExist(t, ciliumEndpoints, podKey) + + podKey, pod = podTestX() + pod.Status.HostIP = "" + require.NoError(t, r.ReconcilePod(context.TODO(), podKey, pod)) + assertCEPDoesNotExist(t, ciliumEndpoints, podKey) +} + +func TestStoreFailure(t *testing.T) { + r, ciliumEndpoints := newTestEndpointReconciler(t) + + ciliumEndpoints.FailOnNextStoreCall() + + createNamespace(r.ciliumSlimClientSet.CoreV1()) + podKey, pod := podTestX() + require.Error(t, r.ReconcilePod(context.TODO(), podKey, pod)) +} + +func TestSortedLabels(t *testing.T) { + r, _ := newTestEndpointReconciler(t) + + createNamespace(r.ciliumSlimClientSet.CoreV1()) + + pod := cache.PodCacheObject{ + Key: resource.Key{ + Namespace: "test", + Name: "a", + }, + Pod: &v1.Pod{ + ObjectMeta: slim_metav1.ObjectMeta{ + Labels: map[string]string{ + "k3": "v3", + "k1": "v1", + "k2": "v2", + "k5": "v5", + "k4": "v4", + }, + Namespace: "test", + Name: "a", + }, + }, + } + + lbls, err := r.ciliumEndpointsLabels(context.Background(), pod.Pod) + require.NoError(t, err) + + expected := make(labels.Labels) + expected["k1"] = labels.Label{ + Key: "k1", + Value: "v1", + Source: "k8s", + } + expected["k2"] = labels.Label{ + Key: "k2", + Value: "v2", + Source: "k8s", + } + expected["k3"] = labels.Label{ + Key: "k3", + Value: "v3", + Source: "k8s", + } + expected["k4"] = labels.Label{ + Key: "k4", + Value: "v4", + Source: "k8s", + } + expected["k5"] = labels.Label{ + Key: "k5", + Value: "v5", + Source: "k8s", + } + expected["io.kubernetes.pod.namespace"] = labels.Label{ + Key: "io.kubernetes.pod.namespace", + Value: "test", + Source: "k8s", + } + expected["io.cilium.k8s.policy.cluster"] = labels.Label{ + Key: "io.cilium.k8s.policy.cluster", + Value: "", + Source: "k8s", + } + + require.Equal(t, expected, lbls) + require.Equal(t, "k8s:io.cilium.k8s.policy.cluster,k8s:io.kubernetes.pod.namespace=test,k8s:k1=v1,k8s:k2=v2,k8s:k3=v3,k8s:k4=v4,k8s:k5=v5", lbls.String()) +} + +func newTestEndpointReconciler(t *testing.T) (*endpointReconciler, *ciliumutil.MockResource[*ciliumv2.CiliumEndpoint]) { + t.Helper() + l := logrus.New() + l.SetLevel(logrus.DebugLevel) + ciliumEndpoints := ciliumutil.NewMockResource[*ciliumv2.CiliumEndpoint](l) + + fakeClientSet, _ := ciliumclient.NewFakeClientset() + + m := ciliumutil.NewMockVersionedClient(l, ciliumEndpoints) + r := &endpointReconciler{ + l: l, + clientset: m, + podEvents: nil, + ciliumEndpoints: ciliumEndpoints, + ciliumSlimClientSet: fakeClientSet.SlimFakeClientset, + store: NewStore(), + Mutex: &sync.Mutex{}, + } + + // make sure to use CRD mode (this is referenced in InitIdentityAllocator) + option.Config.IdentityAllocationMode = option.IdentityAllocationModeCRD + im, err := NewIdentityManager(l, m) + require.NoError(t, err) + r.identityManager = im + + return r, ciliumEndpoints +} + +func assertCEPDoesNotExist(t *testing.T, ciliumEndpoints *ciliumutil.MockResource[*ciliumv2.CiliumEndpoint], key resource.Key) { + t.Helper() + _, ok, err := ciliumEndpoints.GetByKey(key) + require.NoError(t, err, "error getting from store") + require.False(t, ok, "cilium endpoint should not exist") +} + +func getAndAssertCEPExists(t *testing.T, ciliumEndpoints *ciliumutil.MockResource[*ciliumv2.CiliumEndpoint], pod *v1.Pod) *ciliumv2.CiliumEndpoint { + t.Helper() + key := resource.Key{Name: pod.Name, Namespace: pod.Namespace} + cep, ok, err := ciliumEndpoints.GetByKey(key) + require.NoError(t, err) + require.True(t, ok, "cilium endpoint should exist") + return cep +} diff --git a/pkg/controllers/operator/cilium-crds/endpoint/identitymanager.go b/pkg/controllers/operator/cilium-crds/endpoint/identitymanager.go new file mode 100644 index 0000000000..f7be8e16c6 --- /dev/null +++ b/pkg/controllers/operator/cilium-crds/endpoint/identitymanager.go @@ -0,0 +1,128 @@ +package endpointcontroller + +import ( + "context" + + "github.com/pkg/errors" + "github.com/sirupsen/logrus" + + "github.com/cilium/cilium/pkg/identity" + icache "github.com/cilium/cilium/pkg/identity/cache" + "github.com/cilium/cilium/pkg/k8s/client/clientset/versioned" + "github.com/cilium/cilium/pkg/labels" + "github.com/cilium/cilium/pkg/option" +) + +// IdentityManager is analogous to Cilium Daemon's identity allocation. +// Cilium has an IPCacche holding IP to Identity mapping. +// In IPCache.InjectLabels(), IPCacche is told of IPs which have been updated. +// Within this function, identities are allocated/released via CachingIdentityAllocator. +type IdentityManager struct { + l logrus.FieldLogger + // alloc is the CachingIdentityAllocator which helps in: + // - allocating/releasing identities (maintaining reference counts and creating CRDs) + // - syncing identity "keys", preventing them from being garbage collected + // The struct performs a bit more than is needed including: + // - logic for local identities (e.g. node-local CIDR identity), which we do not use + // - a go routine for notifications on identity changes + alloc *icache.CachingIdentityAllocator + // labelIdentities maps sorted labels (via labels.Labels.String()) to allocated identity + labelIdentities map[string]identity.NumericIdentity +} + +// owner is a no-op implementation of IdentityAllocatorOwner +type owner struct{} + +// UpdateIdentities is a callback when identities are updated +func (o *owner) UpdateIdentities(_, _ icache.IdentityCache) { + // no-op +} + +// GetNodeSuffix() is only used for KVStoreBackend (we use CRDBackend) +func (o *owner) GetNodeSuffix() string { + return "" +} + +func NewIdentityManager(l logrus.FieldLogger, client versioned.Interface) (*IdentityManager, error) { + im := &IdentityManager{ + l: l.WithField("component", "identitymanager"), + alloc: icache.NewCachingIdentityAllocator(&owner{}), + labelIdentities: make(map[string]identity.NumericIdentity), + } + + im.l.Info("initializing identity allocator") + _ = im.alloc.InitIdentityAllocator(client) + return im, nil +} + +// DecrementReference modifies the corresponding identity's reference count in the allocator's store. +// For proper garbage collection of stale identities, this must be called exactly once per deleted/relabeled Pod. +// Whenever reference count is not 0, then the identity will exist in the local store, and syncLocalKeys() will make sure it exists. +func (im *IdentityManager) DecrementReference(ctx context.Context, lbls labels.Labels) { + sortedLabels := lbls.String() + id, ok := im.labelIdentities[sortedLabels] + if !ok { + im.l.WithField("labels", sortedLabels).Warn("expected identity for labels") + return + } + + idObj := im.alloc.LookupIdentityByID(ctx, id) + if idObj == nil { + im.l.WithField("identity", id).Warn("expected identity for id") + return + } + + // modifies the reference count for the identity. + // If reference count reaches 0, the allocator's store will release the key, meaning identitygc will be able to work, + // since syncLocalKeys() will no longer make sure the identity exists. + // notifyOwner=false because no need to notify owner (via UpdateIdentities callback). + // Since Release() is a local operation (deleting CiliumIdentity happens in identitygc cell), + // it does not make sense to pass a separate context with a kvstore timeout. + released, err := im.alloc.Release(ctx, idObj, false) + if err != nil { + // possible errors are + // 1. ctx cancelled (in which case, hive is shutting down) + // 2. identity not found in localKeys cache (nothing to worry about, and GC on CiliumIdentities will work as expected) + im.l.WithError(err).WithFields(logrus.Fields{ + "identity": idObj, + "identityLabels": idObj.Labels, + }).Warning( + "error while releasing previously allocated identity", + ) + } + + if !released || err != nil { + return + } + + im.l.WithFields(logrus.Fields{ + "identity": idObj, + "identityLabels": idObj.Labels, + }).Info( + "released identity due to no more references", + ) + + delete(im.labelIdentities, sortedLabels) +} + +// GetIdentityAndIncrementReference will create/get an identity ID and increment the reference count in the allocator's store. +// For proper garbage collection of stale identities, this must be called exactly once per created/relabeled Pod. +// Whenever reference count is not 0, then the identity will exist in the local store, and syncLocalKeys() will make sure it exists. +func (im *IdentityManager) GetIdentityAndIncrementReference(ctx context.Context, lbls labels.Labels) (int64, error) { + // notifyOwner=false because no need to notify owner (via UpdateIdentities callback). + // oldNID=identity.InvalidIdentity would only be used for local identities (e.g. node-local CIDR identity), which we don't use. + // Since this operation will create the CiliumIdentity if needed, + // pass in a context that completes either once ctx is done or kvstore timeout is reached. + allocateCtx, cancel := context.WithTimeout(ctx, option.Config.KVstoreConnectivityTimeout) + defer cancel() + idObj, _, err := im.alloc.AllocateIdentity(allocateCtx, lbls, false, identity.InvalidIdentity) + if err != nil { + return -1, errors.Wrap(err, "failed to allocate identity") + } + + // info-level logging occurs within Allocator for reusing or allocating a new identity/global key + + im.labelIdentities[lbls.String()] = idObj.ID + + return int64(idObj.ID), nil +} diff --git a/pkg/controllers/operator/cilium-crds/endpoint/identitymanager_test.go b/pkg/controllers/operator/cilium-crds/endpoint/identitymanager_test.go new file mode 100644 index 0000000000..719f6a29c5 --- /dev/null +++ b/pkg/controllers/operator/cilium-crds/endpoint/identitymanager_test.go @@ -0,0 +1,145 @@ +package endpointcontroller + +import ( + "context" + "strconv" + "testing" + + ciliumutil "github.com/microsoft/retina/pkg/utils/testutil/cilium" + + "github.com/sirupsen/logrus" + "github.com/stretchr/testify/require" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/cilium/cilium/pkg/labels" + "github.com/cilium/cilium/pkg/option" +) + +func TestGetIdentities(t *testing.T) { + l := logrus.New() + m := ciliumutil.NewMockVersionedClient(l, nil) + + // make sure to use CRD mode (this is referenced in InitIdentityAllocator) + option.Config.IdentityAllocationMode = option.IdentityAllocationModeCRD + im, err := NewIdentityManager(l, m) + require.NoError(t, err) + + lbls := labels.Labels{ + "k1": labels.Label{ + Key: "k1", + Value: "v1", + Source: "k8s", + }, + "io.kubernetes.pod.namespace": labels.Label{ + Key: "io.kubernetes.pod.namespace", + Value: "x", + Source: "k8s", + }, + } + + id, err := im.GetIdentityAndIncrementReference(context.TODO(), lbls) + + require.NoError(t, err) + require.Len(t, im.labelIdentities, 1) + require.Greater(t, int(id), 0) + + // identity should be in API Server + idObj, err := m.CiliumV2().CiliumIdentities().Get(context.TODO(), strconv.FormatInt(id, 10), metav1.GetOptions{}) + require.NoError(t, err) + require.Equal(t, strconv.FormatInt(id, 10), idObj.Name) + idLabels := map[string]string{ + "k1": "v1", + "io.kubernetes.pod.namespace": "x", + } + require.Equal(t, idLabels, idObj.Labels) + + // same labels should return the same identity + id2, err := im.GetIdentityAndIncrementReference(context.TODO(), lbls) + + require.NoError(t, err) + require.Equal(t, id, id2) + require.Len(t, im.labelIdentities, 1) + + // new labels should return a new identity + newLbls := labels.Labels{ + "k1": labels.Label{ + Key: "k1", + Value: "v1", + Source: "k8s", + }, + "k2": labels.Label{ + Key: "k2", + Value: "v2", + Source: "k8s", + }, + "io.kubernetes.pod.namespace": labels.Label{ + Key: "io.kubernetes.pod.namespace", + Value: "x", + Source: "k8s", + }, + } + + id3, err := im.GetIdentityAndIncrementReference(context.TODO(), newLbls) + + require.NoError(t, err) + require.NotEqual(t, id, id3) + require.Len(t, im.labelIdentities, 2) + require.Greater(t, int(id), 0) + + // identity should be in API Server + idObj, err = m.CiliumV2().CiliumIdentities().Get(context.TODO(), strconv.FormatInt(id3, 10), metav1.GetOptions{}) + require.NoError(t, err) + require.Equal(t, strconv.FormatInt(id3, 10), idObj.Name) + idLabels = map[string]string{ + "k1": "v1", + "k2": "v2", + "io.kubernetes.pod.namespace": "x", + } + require.Equal(t, idLabels, idObj.Labels) +} + +func TestDecrementReference(t *testing.T) { + l := logrus.New() + m := ciliumutil.NewMockVersionedClient(l, nil) + // make sure to use CRD mode (this is referenced in InitIdentityAllocator) + option.Config.IdentityAllocationMode = option.IdentityAllocationModeCRD + im, err := NewIdentityManager(l, m) + require.NoError(t, err) + + lbls := labels.Labels{ + "k1": labels.Label{ + Key: "k1", + Value: "v1", + Source: "k8s", + }, + "io.kubernetes.pod.namespace": labels.Label{ + Key: "io.kubernetes.pod.namespace", + Value: "x", + Source: "k8s", + }, + } + + id, err := im.GetIdentityAndIncrementReference(context.TODO(), lbls) + require.NoError(t, err) + _, err = im.GetIdentityAndIncrementReference(context.TODO(), lbls) + require.NoError(t, err) + + // still a reference. identity should still exist + im.DecrementReference(context.TODO(), lbls) + require.Len(t, im.labelIdentities, 1) + + // no more references. identity should be deleted + im.DecrementReference(context.TODO(), lbls) + require.Empty(t, im.labelIdentities) + + // IdentityManager's allocator should not delete the identity (identitygc cell does garbage collection) + idObj, err := m.CiliumV2().CiliumIdentities().Get(context.TODO(), strconv.FormatInt(id, 10), metav1.GetOptions{}) + require.NoError(t, err) + require.Equal(t, strconv.FormatInt(id, 10), idObj.Name) + idLabels := map[string]string{ + "k1": "v1", + "io.kubernetes.pod.namespace": "x", + } + require.Equal(t, idLabels, idObj.Labels) +} diff --git a/pkg/controllers/operator/cilium-crds/endpoint/types.go b/pkg/controllers/operator/cilium-crds/endpoint/types.go new file mode 100644 index 0000000000..72f931aa2e --- /dev/null +++ b/pkg/controllers/operator/cilium-crds/endpoint/types.go @@ -0,0 +1,151 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +package endpointcontroller + +import ( + "sync" + + ciliumv2 "github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2" + "github.com/cilium/cilium/pkg/k8s/resource" + slim_corev1 "github.com/cilium/cilium/pkg/k8s/slim/k8s/api/core/v1" + "github.com/cilium/cilium/pkg/labels" + "k8s.io/apimachinery/pkg/types" +) + +// PodEndpoint represents a Pod/CiliumEndpoint +type PodEndpoint struct { + key resource.Key + endpointID int64 + identityID int64 + lbls labels.Labels + ipv4 string + nodeIP string + // processedAsLeader will be used when implementing follower mode. + // Follower would process all pod events, but it won't use IdentityManager (increment identity references) until leading + // since IdentityManager implicitly affects API Server. + processedAsLeader bool + + uid types.UID + + // toDelete is used to mark the pod for deletion + toDelete bool + + // podObj is the pod object + podObj *slim_corev1.Pod +} + +func (pep *PodEndpoint) endpointStatus() ciliumv2.EndpointStatus { + return ciliumv2.EndpointStatus{ + ID: pep.endpointID, + Identity: &ciliumv2.EndpointIdentity{ + ID: pep.identityID, + Labels: pep.lbls.GetPrintableModel(), + }, + Networking: &ciliumv2.EndpointNetworking{ + NodeIP: pep.nodeIP, + Addressing: ciliumv2.AddressPairList{ + { + IPV4: pep.ipv4, + }, + }, + }, + + State: "ready", + } +} + +func (pep *PodEndpoint) deepCopy() *PodEndpoint { + return &PodEndpoint{ + key: pep.key, + endpointID: pep.endpointID, + identityID: pep.identityID, + lbls: pep.lbls, + ipv4: pep.ipv4, + nodeIP: pep.nodeIP, + processedAsLeader: pep.processedAsLeader, + uid: pep.uid, + podObj: pep.podObj, + } +} + +type Store struct { //nolint:gocritic // This should be rewritten to limit exposure of mutex to external packages. + *sync.RWMutex + + // Pods is a map of Pod key to PodEndpoint + // this is the expected endpoint state for the pod + // and is used to determine if the pod needs to be updated + Pods map[resource.Key]*PodEndpoint + + // Namespaces is a map of Namespace name to Namespace + // this is used to determine if the namespace needs to be updated + Namespaces map[string]*slim_corev1.Namespace +} + +func NewStore() *Store { + return &Store{ + RWMutex: &sync.RWMutex{}, + Pods: make(map[resource.Key]*PodEndpoint), + Namespaces: make(map[string]*slim_corev1.Namespace), + } +} + +func (s *Store) AddPod(pod *PodEndpoint) { + s.Lock() + defer s.Unlock() + s.Pods[pod.key] = pod +} + +func (s *Store) AddNamespace(namespace *slim_corev1.Namespace) { + s.Lock() + defer s.Unlock() + s.Namespaces[namespace.GetName()] = namespace +} + +func (s *Store) GetPod(key resource.Key) (*PodEndpoint, bool) { + s.RLock() + defer s.RUnlock() + pod, ok := s.Pods[key] + return pod, ok +} + +func (s *Store) GetToDeletePod(key resource.Key) (*PodEndpoint, bool) { + s.Lock() + defer s.Unlock() + pod, ok := s.Pods[key] + if ok { + pod.toDelete = true + } + return pod, ok +} + +func (s *Store) GetNamespace(key string) (*slim_corev1.Namespace, bool) { + s.RLock() + defer s.RUnlock() + namespace, ok := s.Namespaces[key] + return namespace, ok +} + +func (s *Store) DeletePod(key resource.Key) { + s.Lock() + defer s.Unlock() + delete(s.Pods, key) +} + +func (s *Store) DeleteNamespace(key string) { + s.Lock() + defer s.Unlock() + delete(s.Namespaces, key) +} + +func (s *Store) ListPodKeysByNamespace(namespace string) []resource.Key { + s.RLock() + defer s.RUnlock() + keys := make([]resource.Key, 0) + for key, pod := range s.Pods { + if pod.key.Namespace == namespace { + keys = append(keys, key) + } + } + return keys +} diff --git a/pkg/utils/testutil/cilium/endpoint_client.go b/pkg/utils/testutil/cilium/endpoint_client.go new file mode 100644 index 0000000000..dc57a5e240 --- /dev/null +++ b/pkg/utils/testutil/cilium/endpoint_client.go @@ -0,0 +1,145 @@ +//go:unit + +package ciliumutil + +import ( + "context" + "encoding/json" + "fmt" + + "github.com/sirupsen/logrus" + + v2 "github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2" + ciliumv2 "github.com/cilium/cilium/pkg/k8s/client/clientset/versioned/typed/cilium.io/v2" + "github.com/cilium/cilium/pkg/k8s/resource" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/watch" +) + +// ensure all interfaces are implemented +var _ ciliumv2.CiliumEndpointInterface = &MockEndpointClient{} + +type MockEndpointClient struct { + l logrus.FieldLogger + namespace string + ciliumEndpoints *MockResource[*v2.CiliumEndpoint] + watchers []watch.Interface +} + +func NewMockEndpointClient(l logrus.FieldLogger, namespace string, ciliumEndpoints *MockResource[*v2.CiliumEndpoint]) *MockEndpointClient { + return &MockEndpointClient{ + l: l, + namespace: namespace, + ciliumEndpoints: ciliumEndpoints, + watchers: make([]watch.Interface, 0), + } +} + +func (m *MockEndpointClient) Create(_ context.Context, ciliumEndpoint *v2.CiliumEndpoint, _ v1.CreateOptions) (*v2.CiliumEndpoint, error) { + m.l.Info("MockEndpointClient.Create() called") + _, ok, err := m.ciliumEndpoints.GetByKey(resource.NewKey(ciliumEndpoint)) + if err != nil { + return nil, err + } + if ok { + return nil, ErrAlreadyExists + } + + m.ciliumEndpoints.Upsert(ciliumEndpoint) + return ciliumEndpoint, nil +} + +func (m *MockEndpointClient) Update(_ context.Context, ciliumEndpoint *v2.CiliumEndpoint, _ v1.UpdateOptions) (*v2.CiliumEndpoint, error) { + m.l.Info("MockEndpointClient.Update() called") + m.ciliumEndpoints.cache[resource.NewKey(ciliumEndpoint)] = ciliumEndpoint + return ciliumEndpoint, nil +} + +func (m *MockEndpointClient) UpdateStatus(_ context.Context, _ *v2.CiliumEndpoint, _ v1.UpdateOptions) (*v2.CiliumEndpoint, error) { + m.l.Warn("MockEndpointClient.UpdateStatus() called but this returns nil because it's not implemented") + return nil, ErrNotImplemented +} + +func (m *MockEndpointClient) Delete(_ context.Context, name string, _ v1.DeleteOptions) error { + m.l.Info("MockEndpointClient.Delete() called") + _, ok, err := m.ciliumEndpoints.GetByKey(resource.Key{Name: name, Namespace: m.namespace}) + if err != nil { + return err + } + if !ok { + return ErrNotFound{} + } + m.ciliumEndpoints.Delete(resource.Key{Name: name, Namespace: m.namespace}) + return nil +} + +func (m *MockEndpointClient) DeleteCollection(_ context.Context, _ v1.DeleteOptions, _ v1.ListOptions) error { + m.l.Warn("MockEndpointClient.DeleteCollection() called but this is not implemented") + return ErrNotImplemented +} + +func (m *MockEndpointClient) Get(_ context.Context, name string, _ v1.GetOptions) (*v2.CiliumEndpoint, error) { + m.l.Info("MockEndpointClient.Get() called") + item, _, err := m.ciliumEndpoints.GetByKey(resource.Key{Name: name, Namespace: m.namespace}) + if err != nil { + return nil, err + } + return item, nil +} + +func (m *MockEndpointClient) List(_ context.Context, _ v1.ListOptions) (*v2.CiliumEndpointList, error) { + m.l.Info("MockEndpointClient.List() called") + + items := make([]v2.CiliumEndpoint, 0, len(m.ciliumEndpoints.cache)) + for _, cep := range m.ciliumEndpoints.cache { + items = append(items, *cep) + } + + return &v2.CiliumEndpointList{Items: items}, nil +} + +func (m *MockEndpointClient) Watch(_ context.Context, _ v1.ListOptions) (watch.Interface, error) { + m.l.Warn("MockEndpointClient.Watch() called but this returns a fake watch because it's not implemented") + + // not sure if watching is important for us + w := watch.NewFake() + m.watchers = append(m.watchers, w) + return w, nil +} + +func (m *MockEndpointClient) Patch(_ context.Context, name string, _ types.PatchType, data []byte, _ v1.PatchOptions, _ ...string) (result *v2.CiliumEndpoint, err error) { + key := resource.Key{Name: name, Namespace: m.namespace} + cep, ok, err := m.ciliumEndpoints.GetByKey(key) + if err != nil { + return nil, err + } + + if !ok { + return nil, ErrNotFound{} + } + + var replaceCEPStatus []JSONPatch + err = json.Unmarshal(data, &replaceCEPStatus) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal patch data: %w", err) + } + + cep.Status = replaceCEPStatus[0].Value + m.ciliumEndpoints.Upsert(cep) + cep, ok, err = m.ciliumEndpoints.GetByKey(key) + if err != nil { + return nil, err + } + if !ok { + return nil, ErrNotFound{} + } + + return cep, nil +} + +type JSONPatch struct { + OP string `json:"op,omitempty"` + Path string `json:"path,omitempty"` + Value v2.EndpointStatus `json:"value"` +} diff --git a/pkg/utils/testutil/cilium/errors.go b/pkg/utils/testutil/cilium/errors.go new file mode 100644 index 0000000000..91e05a443d --- /dev/null +++ b/pkg/utils/testutil/cilium/errors.go @@ -0,0 +1,28 @@ +//go:unit + +package ciliumutil + +import ( + "github.com/pkg/errors" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +var ( + ErrAlreadyExists = errors.New("already exists") + ErrNotImplemented = errors.New("not implemented") +) + +const ErrCodeNotFound = 404 + +type ErrNotFound struct{} + +func (e ErrNotFound) Error() string { + return "not found on API server" +} + +func (e ErrNotFound) Status() v1.Status { + return v1.Status{ + Reason: v1.StatusReasonNotFound, + Code: ErrCodeNotFound, + } +} diff --git a/pkg/utils/testutil/cilium/errors_test.go b/pkg/utils/testutil/cilium/errors_test.go new file mode 100644 index 0000000000..1a8528ed4d --- /dev/null +++ b/pkg/utils/testutil/cilium/errors_test.go @@ -0,0 +1,14 @@ +package ciliumutil + +import ( + "testing" + + "github.com/stretchr/testify/require" + + apierrors "k8s.io/apimachinery/pkg/api/errors" +) + +func TestErrNotFound(t *testing.T) { + err := ErrNotFound{} + require.True(t, apierrors.IsNotFound(err)) +} diff --git a/pkg/utils/testutil/cilium/identity_client.go b/pkg/utils/testutil/cilium/identity_client.go new file mode 100644 index 0000000000..625c990c81 --- /dev/null +++ b/pkg/utils/testutil/cilium/identity_client.go @@ -0,0 +1,120 @@ +//go:unit + +package ciliumutil + +import ( + "context" + + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/watch" + + "github.com/sirupsen/logrus" + + v2 "github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2" + ciliumv2 "github.com/cilium/cilium/pkg/k8s/client/clientset/versioned/typed/cilium.io/v2" +) + +// ensure all interfaces are implemented +var _ ciliumv2.CiliumIdentityInterface = &MockIdentityClient{} + +// MockIdentityClient is a mock implementation of ciliumv2.CiliumIdentityInterface. +// We only implement what's needed. These methods are used by: +// - CRDBackend within the Allocator within the IdentityManager +// - identitygc cell +type MockIdentityClient struct { + l logrus.FieldLogger + // identities maps identity name to identity + // namespace is irrelevant since identity names must be globally unique numbers + identities map[string]*v2.CiliumIdentity + watchers []watch.Interface +} + +func NewMockIdentityClient(l logrus.FieldLogger) *MockIdentityClient { + return &MockIdentityClient{ + l: l, + identities: make(map[string]*v2.CiliumIdentity), + watchers: make([]watch.Interface, 0), + } +} + +func (m *MockIdentityClient) GetIdentities() map[string]*v2.CiliumIdentity { + return m.identities +} + +func (m *MockIdentityClient) Create(_ context.Context, ciliumIdentity *v2.CiliumIdentity, _ v1.CreateOptions) (*v2.CiliumIdentity, error) { + m.l.Info("MockIdentityClient.Create() called") + if _, ok := m.identities[ciliumIdentity.Name]; ok { + return nil, ErrAlreadyExists + } + + m.identities[ciliumIdentity.Name] = ciliumIdentity + return ciliumIdentity, nil +} + +func (m *MockIdentityClient) Update(_ context.Context, ciliumIdentity *v2.CiliumIdentity, _ v1.UpdateOptions) (*v2.CiliumIdentity, error) { + m.l.Info("MockIdentityClient.Update() called") + + if _, ok := m.identities[ciliumIdentity.Name]; ok { + m.l.Info("MockIdentityClient.Update() found existing identity") + } else { + m.l.Info("MockIdentityClient.Update() did not find existing identity") + } + + m.identities[ciliumIdentity.Name] = ciliumIdentity + return ciliumIdentity, nil +} + +func (m *MockIdentityClient) Delete(_ context.Context, name string, _ v1.DeleteOptions) error { + m.l.Info("MockIdentityClient.Delete() called") + + if _, ok := m.identities[name]; ok { + m.l.Info("MockIdentityClient.Delete() found existing identity") + } else { + m.l.Info("MockIdentityClient.Delete() did not find existing identity") + } + + delete(m.identities, name) + return nil +} + +func (m *MockIdentityClient) DeleteCollection(_ context.Context, _ v1.DeleteOptions, _ v1.ListOptions) error { + m.l.Warn("MockIdentityClient.DeleteCollection() called but this is not implemented") + return ErrNotImplemented +} + +func (m *MockIdentityClient) Get(_ context.Context, name string, _ v1.GetOptions) (*v2.CiliumIdentity, error) { + m.l.Info("MockIdentityClient.Get() called") + + if identity, ok := m.identities[name]; ok { + m.l.Info("MockIdentityClient.Get() found existing identity") + return identity, nil + } + + return nil, ErrNotFound{} +} + +func (m *MockIdentityClient) List(_ context.Context, _ v1.ListOptions) (*v2.CiliumIdentityList, error) { + m.l.Info("MockIdentityClient.List() called") + + items := make([]v2.CiliumIdentity, 0, len(m.identities)) + for _, identity := range m.identities { + items = append(items, *identity) + } + + return &v2.CiliumIdentityList{Items: items}, nil +} + +func (m *MockIdentityClient) Watch(_ context.Context, _ v1.ListOptions) (watch.Interface, error) { + m.l.Warn("MockIdentityClient.Watch() called but this returns a fake watch because it's not implemented") + + // not sure if watching is important for us + w := watch.NewFake() + m.watchers = append(m.watchers, w) + return w, nil +} + +func (m *MockIdentityClient) Patch(_ context.Context, _ string, _ types.PatchType, _ []byte, _ v1.PatchOptions, _ ...string) (result *v2.CiliumIdentity, err error) { + m.l.Warn("MockIdentityClient.Patch() called but this returns nil because it's not implemented") + return nil, ErrNotImplemented +} diff --git a/pkg/utils/testutil/cilium/resource.go b/pkg/utils/testutil/cilium/resource.go new file mode 100644 index 0000000000..fa2e4d3a08 --- /dev/null +++ b/pkg/utils/testutil/cilium/resource.go @@ -0,0 +1,122 @@ +//go:unit + +package ciliumutil + +import ( + "context" + + "github.com/pkg/errors" + "github.com/sirupsen/logrus" + + k8sRuntime "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/tools/cache" + + ciliumv2 "github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2" + "github.com/cilium/cilium/pkg/k8s/resource" +) + +var ErrMockStoreFailure = errors.New("mock store failure") + +// ensure all interfaces are implemented +var ( + _ resource.Resource[*ciliumv2.CiliumEndpoint] = NewMockResource[*ciliumv2.CiliumEndpoint](nil) + _ resource.Store[*ciliumv2.CiliumEndpoint] = NewMockResource[*ciliumv2.CiliumEndpoint](nil) +) + +// MockResource is a mock implementation of resource.Resource AND resource.Store +// It currently only implements the methods used in the endpoint controller +// i.e. Store() and GetByKey() +// plus some helpers to add/remove items from the cache and error on the next call to Store() +type MockResource[T k8sRuntime.Object] struct { + l logrus.FieldLogger + cache map[resource.Key]T + shouldFailNextStoreCall bool +} + +func NewMockResource[T k8sRuntime.Object](l logrus.FieldLogger) *MockResource[T] { + return &MockResource[T]{ + l: l, + cache: make(map[resource.Key]T), + } +} + +func (r *MockResource[T]) Upsert(obj T) { + r.l.Info("Upsert() called") + r.cache[resource.NewKey(obj)] = obj +} + +func (r *MockResource[T]) Delete(k resource.Key) { + r.l.Info("Delete() called") + delete(r.cache, k) +} + +// FailOnNextStoreCall will cause the next call to Store() to return an error +func (r *MockResource[T]) FailOnNextStoreCall() { + r.l.Info("next call to Store() will fail") + r.shouldFailNextStoreCall = true +} + +func (r *MockResource[T]) Observe(_ context.Context, _ func(resource.Event[T]), _ func(error)) { + r.l.Warn("Observe() called but this is not implemented") +} + +func (r *MockResource[T]) Events(_ context.Context, _ ...resource.EventsOpt) <-chan resource.Event[T] { + r.l.Warn("Events() called but this returns nil because it's not implemented") + return nil +} + +func (r *MockResource[T]) Store(context.Context) (resource.Store[T], error) { + if r.shouldFailNextStoreCall { + r.l.Info("Store() failed") + r.shouldFailNextStoreCall = false + return nil, ErrMockStoreFailure + } + + r.l.Info("Store() succeeded") + return r, nil +} + +func (r *MockResource[T]) List() []T { + r.l.Warn("List() called but this returns nil because it's not implemented") + return nil +} + +func (r *MockResource[T]) IterKeys() resource.KeyIter { + r.l.Warn("IterKeys() called but this returns nil because it's not implemented") + return nil +} + +func (r *MockResource[T]) Get(obj T) (item T, exists bool, err error) { + r.l.Warn("Get() called but this returns nil because it's not implemented") + return obj, false, nil +} + +func (r *MockResource[T]) GetByKey(key resource.Key) (item T, exists bool, err error) { + if _, ok := r.cache[key]; ok { + r.l.Info("GetByKey() called and found item") + return r.cache[key], true, nil + } + + r.l.Info("GetByKey() called and no item found") + return item, false, nil +} + +func (r *MockResource[T]) IndexKeys(_, _ string) ([]string, error) { + r.l.Warn("IndexKeys() called but this returns nil because it's not implemented") + return nil, nil +} + +func (r *MockResource[T]) ByIndex(_, _ string) ([]T, error) { + r.l.Warn("ByIndex() called but this returns nil because it's not implemented") + return nil, nil +} + +func (r *MockResource[T]) CacheStore() cache.Store { + r.l.Warn("CacheStore() called but this returns nil because it's not implemented") + return nil +} + +func (r *MockResource[T]) Release() { + // Implement the logic required by the Release method or leave it as a stub if it's just for testing/mocking + r.l.Warn("Release() called but this is a stub implementation") +} diff --git a/pkg/utils/testutil/cilium/versioned_client.go b/pkg/utils/testutil/cilium/versioned_client.go new file mode 100644 index 0000000000..1a3de98557 --- /dev/null +++ b/pkg/utils/testutil/cilium/versioned_client.go @@ -0,0 +1,121 @@ +//go:unit + +package ciliumutil + +import ( + "github.com/sirupsen/logrus" + + "k8s.io/client-go/rest" + + v2 "github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2" + "github.com/cilium/cilium/pkg/k8s/client/clientset/versioned" + ciliumv2 "github.com/cilium/cilium/pkg/k8s/client/clientset/versioned/typed/cilium.io/v2" + ciliumv2alpha1 "github.com/cilium/cilium/pkg/k8s/client/clientset/versioned/typed/cilium.io/v2alpha1" + discovery "k8s.io/client-go/discovery" +) + +// ensure all interfaces are implemented +var ( + _ versioned.Interface = &MockVersionedClient{} + _ ciliumv2.CiliumV2Interface = &MockCiliumV2Client{} +) + +// MockVersionedClient is a mock implementation of versioned.Interface +// Currently it only returns a real value for CiliumV2() +type MockVersionedClient struct { + l logrus.FieldLogger + c *MockCiliumV2Client +} + +func NewMockVersionedClient(l logrus.FieldLogger, ciliumEndpoints *MockResource[*v2.CiliumEndpoint]) *MockVersionedClient { + return &MockVersionedClient{ + l: l, + c: NewMockCiliumV2Client(l, ciliumEndpoints), + } +} + +func (m *MockVersionedClient) Discovery() discovery.DiscoveryInterface { + m.l.Warn("MockVersionedClient.Discovery() called but this returns nil because it's not implemented") + return nil +} + +func (m *MockVersionedClient) CiliumV2() ciliumv2.CiliumV2Interface { + m.l.Info("MockVersionedClient.CiliumV2() called") + return m.c +} + +func (m *MockVersionedClient) CiliumV2alpha1() ciliumv2alpha1.CiliumV2alpha1Interface { + m.l.Warn("MockVersionedClient.CiliumV2alpha1() called but this returns nil because it's not implemented") + return nil +} + +// MockCiliumV2Client is a mock implementation of ciliumv2.CiliumV2Interface. +// Currently it only returns a real value for CiliumIdentities() +type MockCiliumV2Client struct { + l logrus.FieldLogger + identitiyClient *MockIdentityClient + ciliumEndpoints *MockResource[*v2.CiliumEndpoint] +} + +func NewMockCiliumV2Client(l logrus.FieldLogger, ciliumEndpoints *MockResource[*v2.CiliumEndpoint]) *MockCiliumV2Client { + return &MockCiliumV2Client{ + l: l, + identitiyClient: NewMockIdentityClient(l), + ciliumEndpoints: ciliumEndpoints, + } +} + +func (m *MockCiliumV2Client) RESTClient() rest.Interface { + m.l.Warn("MockCiliumV2Client.RESTClient() called but this returns nil because it's not implemented") + return nil +} + +func (m *MockCiliumV2Client) CiliumClusterwideEnvoyConfigs() ciliumv2.CiliumClusterwideEnvoyConfigInterface { + m.l.Warn("MockCiliumV2Client.CiliumClusterwideEnvoyConfigs() called but this returns nil because it's not implemented") + return nil +} + +func (m *MockCiliumV2Client) CiliumClusterwideNetworkPolicies() ciliumv2.CiliumClusterwideNetworkPolicyInterface { + m.l.Warn("MockCiliumV2Client.CiliumClusterwideNetworkPolicies() called but this returns nil because it's not implemented") + return nil +} + +func (m *MockCiliumV2Client) CiliumEgressGatewayPolicies() ciliumv2.CiliumEgressGatewayPolicyInterface { + m.l.Warn("MockCiliumV2Client.CiliumEgressGatewayPolicies() called but this returns nil because it's not implemented") + return nil +} + +func (m *MockCiliumV2Client) CiliumEndpoints(namespace string) ciliumv2.CiliumEndpointInterface { + m.l.Info("MockCiliumV2Client.CiliumEndpoints() called") + return NewMockEndpointClient(m.l, namespace, m.ciliumEndpoints) +} + +func (m *MockCiliumV2Client) CiliumEnvoyConfigs(_ string) ciliumv2.CiliumEnvoyConfigInterface { + m.l.Warn("MockCiliumV2Client.CiliumEnvoyConfigs() called but this returns nil because it's not implemented") + return nil +} + +func (m *MockCiliumV2Client) CiliumExternalWorkloads() ciliumv2.CiliumExternalWorkloadInterface { + m.l.Warn("MockCiliumV2Client.CiliumExternalWorkloads() called but this returns nil because it's not implemented") + return nil +} + +func (m *MockCiliumV2Client) CiliumIdentities() ciliumv2.CiliumIdentityInterface { + m.l.Info("MockCiliumV2Client.CiliumIdentities() called") + return m.identitiyClient +} + +func (m *MockCiliumV2Client) CiliumLocalRedirectPolicies(_ string) ciliumv2.CiliumLocalRedirectPolicyInterface { + m.l.Warn("MockCiliumV2Client.CiliumLocalRedirectPolicies() called but this returns nil because it's not implemented") + return nil +} + +func (m *MockCiliumV2Client) CiliumNetworkPolicies(_ string) ciliumv2.CiliumNetworkPolicyInterface { + m.l.Warn("MockCiliumV2Client.CiliumNetworkPolicies() called but this returns nil because it's not implemented") + return nil +} + +func (m *MockCiliumV2Client) CiliumNodes() ciliumv2.CiliumNodeInterface { + m.l.Warn("MockCiliumV2Client.CiliumNodes() called but this returns nil because it's not implemented") + return nil +} diff --git a/test/e2e/retina_e2e_test.go b/test/e2e/retina_e2e_test.go index 29aac9f9f8..f804eef495 100644 --- a/test/e2e/retina_e2e_test.go +++ b/test/e2e/retina_e2e_test.go @@ -1,6 +1,8 @@ package retina import ( + "crypto/rand" + "math/big" "os" "os/user" "path/filepath" @@ -14,6 +16,8 @@ import ( "github.com/stretchr/testify/require" ) +var locations = []string{"eastus2", "centralus", "southcentralus", "uksouth", "centralindia", "westus2"} + // TestE2ERetina tests all e2e scenarios for retina func TestE2ERetina(t *testing.T) { curuser, err := user.Current() @@ -26,7 +30,12 @@ func TestE2ERetina(t *testing.T) { location := os.Getenv("AZURE_LOCATION") if location == "" { - location = "eastus" + var nBig *big.Int + nBig, err = rand.Int(rand.Reader, big.NewInt(int64(len(locations)))) + if err != nil { + t.Fatalf("Failed to generate a secure random index: %v", err) + } + location = locations[nBig.Int64()] } cwd, err := os.Getwd()