├── .github ├── FUNDING.yml └── workflows │ └── main.yaml ├── test ├── pvc.yaml ├── configmap.yaml ├── kind-cluster.yaml.tmp ├── kind-cluster.yaml ├── fv │ ├── verify_job_test.go │ └── fv_suite_test.go └── collector.yaml ├── hack └── tools │ ├── get-golangci-lint.sh │ ├── tools.go │ ├── go.mod │ └── go.sum ├── .gitignore ├── pkg └── utils │ ├── export.go │ ├── k8s_utils_suite_test.go │ ├── utils.go │ ├── configuration.go │ ├── collect.go │ ├── collect_test.go │ ├── logs.go │ └── resources.go ├── Dockerfile ├── scripts └── go_install.sh ├── k8s └── collector.yaml ├── cmd └── main.go ├── go.mod ├── .golangci.yaml ├── README.md ├── Makefile ├── LICENSE └── go.sum /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: gianlucam76 4 | -------------------------------------------------------------------------------- /test/pvc.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: PersistentVolumeClaim 3 | metadata: 4 | name: standard 5 | spec: 6 | accessModes: 7 | - ReadWriteOnce 8 | resources: 9 | requests: 10 | storage: 1Gi -------------------------------------------------------------------------------- /test/configmap.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - group: "" 3 | version: v1 4 | kind: Pod 5 | namespace: default 6 | - group: apps 7 | version: v1 8 | kind: Deployment 9 | logs: 10 | - namespace: kube-system 11 | sinceSeconds: 600 -------------------------------------------------------------------------------- /hack/tools/get-golangci-lint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euo pipefail 4 | 5 | # Define the URL for downloading the golangci-lint archive 6 | curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(pwd)/bin "$1" -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # If you prefer the allow list template instead of the deny list, see community template: 2 | # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore 3 | # 4 | # Binaries for programs and plugins 5 | *.exe 6 | *.exe~ 7 | *.dll 8 | *.so 9 | *.dylib 10 | 11 | bin/ 12 | 13 | # Test binary, built with `go test -c` 14 | *.test 15 | 16 | # vim files 17 | *.swp 18 | 19 | # Output of the go coverage tool, specifically when used with LiteIDE 20 | *.out 21 | 22 | # Needed tools 23 | hack/tools/bin/ 24 | 25 | # Dependency directories (remove the comment below to include it) 26 | # vendor/ 27 | 28 | # Go workspace file 29 | go.work 30 | 31 | .gitconfig 32 | -------------------------------------------------------------------------------- /pkg/utils/export.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023. projectsveltos.io. All rights reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package utils 18 | 19 | var ( 20 | LoadConfiguration = (*Collector).loadConfiguration 21 | ) 22 | -------------------------------------------------------------------------------- /test/kind-cluster.yaml.tmp: -------------------------------------------------------------------------------- 1 | kind: Cluster 2 | apiVersion: kind.x-k8s.io/v1alpha4 3 | networking: 4 | podSubnet: "10.110.0.0/16" 5 | serviceSubnet: "10.115.0.0/16" 6 | nodes: 7 | - role: control-plane 8 | kubeadmConfigPatches: 9 | - | 10 | kind: ClusterConfiguration 11 | apiServer: 12 | extraArgs: 13 | v: "10" 14 | image: kindest/node:v1.28.0 15 | extraMounts: 16 | - hostPath: /var/run/docker.sock 17 | containerPath: /var/run/docker.sock 18 | - hostPath: /usr/share/zoneinfo 19 | containerPath: /usr/share/zoneinfo 20 | - role: worker 21 | image: kindest/node:v1.28.0 22 | extraMounts: 23 | - hostPath: /var/run/docker.sock 24 | containerPath: /var/run/docker.sock 25 | - hostPath: /usr/share/zoneinfo 26 | containerPath: /usr/share/zoneinfo 27 | -------------------------------------------------------------------------------- /test/kind-cluster.yaml: -------------------------------------------------------------------------------- 1 | kind: Cluster 2 | apiVersion: kind.x-k8s.io/v1alpha4 3 | networking: 4 | podSubnet: "10.110.0.0/16" 5 | serviceSubnet: "10.115.0.0/16" 6 | nodes: 7 | - role: control-plane 8 | kubeadmConfigPatches: 9 | - | 10 | kind: ClusterConfiguration 11 | apiServer: 12 | extraArgs: 13 | v: "10" 14 | image: kindest/node:K8S_VERSION 15 | extraMounts: 16 | - hostPath: /var/run/docker.sock 17 | containerPath: /var/run/docker.sock 18 | - hostPath: /usr/share/zoneinfo 19 | containerPath: /usr/share/zoneinfo 20 | - role: worker 21 | image: kindest/node:K8S_VERSION 22 | extraMounts: 23 | - hostPath: /var/run/docker.sock 24 | containerPath: /var/run/docker.sock 25 | - hostPath: /usr/share/zoneinfo 26 | containerPath: /usr/share/zoneinfo 27 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Build the manager binary 2 | FROM golang:1.22.7 AS builder 3 | 4 | ARG BUILDOS 5 | ARG TARGETARCH 6 | 7 | WORKDIR /workspace 8 | # Copy the Go Modules manifests 9 | COPY go.mod go.mod 10 | COPY go.sum go.sum 11 | # cache deps before building and copying source so that we don't need to re-download as much 12 | # and so that source changes don't invalidate our downloaded layer 13 | RUN go mod download 14 | 15 | # Copy the go source 16 | COPY cmd/ cmd 17 | COPY pkg/ pkg/ 18 | 19 | # Build 20 | RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -o k8s-collector cmd/main.go 21 | 22 | # Use distroless as minimal base image to package the manager binary 23 | # Refer to https://github.com/GoogleContainerTools/distroless for more details 24 | FROM gcr.io/distroless/static:nonroot 25 | WORKDIR / 26 | COPY --from=builder /workspace/k8s-collector . 27 | USER nonroot:nonroot 28 | 29 | ENTRYPOINT ["/k8s-collector"] 30 | 31 | -------------------------------------------------------------------------------- /hack/tools/tools.go: -------------------------------------------------------------------------------- 1 | //go:build tools 2 | // +build tools 3 | 4 | /* 5 | Copyright 2022. projectsveltos.io. All rights reserved. 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and 17 | limitations under the License. 18 | */ 19 | 20 | // This package imports things required by build scripts, to force `go mod` to see them as 21 | // dependencies 22 | package tools 23 | 24 | import ( 25 | _ "github.com/onsi/ginkgo/v2/ginkgo" 26 | _ "golang.org/x/oauth2/google" 27 | _ "k8s.io/client-go/plugin/pkg/client/auth/azure" 28 | _ "sigs.k8s.io/controller-tools/cmd/controller-gen" 29 | _ "sigs.k8s.io/kind" 30 | ) 31 | -------------------------------------------------------------------------------- /scripts/go_install.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Copyright 2021 The Kubernetes Authors. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | set -o errexit 17 | set -o nounset 18 | set -o pipefail 19 | 20 | if [ -z "${1}" ]; then 21 | echo "must provide module as first parameter" 22 | exit 1 23 | fi 24 | 25 | if [ -z "${2}" ]; then 26 | echo "must provide binary name as second parameter" 27 | exit 1 28 | fi 29 | 30 | if [ -z "${3}" ]; then 31 | echo "must provide version as third parameter" 32 | exit 1 33 | fi 34 | 35 | if [ -z "${GOBIN}" ]; then 36 | echo "GOBIN is not set. Must set GOBIN to install the bin in a specified directory." 37 | exit 1 38 | fi 39 | 40 | rm -f "${GOBIN}/${2}"* || true 41 | 42 | # install the golang module specified as the first argument 43 | go install "${1}@${3}" 44 | mv "${GOBIN}/${2}" "${GOBIN}/${2}-${3}" 45 | ln -sf "${GOBIN}/${2}-${3}" "${GOBIN}/${2}" -------------------------------------------------------------------------------- /.github/workflows/main.yaml: -------------------------------------------------------------------------------- 1 | name: main 2 | on: 3 | workflow_dispatch: 4 | push: 5 | branches: 6 | - 'main' 7 | - 'dev' 8 | pull_request: 9 | types: [opened, edited, synchronize, reopened] 10 | 11 | 12 | jobs: 13 | build-static-test: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: checkout 17 | uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # tag=v4.1.7 18 | - name: Set up Go 19 | uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # tag=v5.0.2 20 | with: 21 | go-version: 1.22.7 22 | - name: Build 23 | run: make build 24 | - name: FMT 25 | run: make fmt 26 | - name: VET 27 | run: make vet 28 | - name: LINT 29 | run: make lint 30 | env: 31 | LINT: true 32 | build-ut: 33 | runs-on: ubuntu-latest 34 | steps: 35 | - name: checkout 36 | uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # tag=v4.1.7 37 | - name: Set up Go 38 | uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # tag=v5.0.2 39 | with: 40 | go-version: 1.22.7 41 | - name: ut 42 | run: make test 43 | env: 44 | UT: true 45 | FV: 46 | runs-on: ubuntu-latest 47 | steps: 48 | - name: checkout 49 | uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # tag=v4.1.7 50 | - name: Set up Go 51 | uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # tag=v5.0.2 52 | with: 53 | go-version: 1.22.7 54 | - name: fv 55 | run: make create-cluster fv 56 | env: 57 | FV: true 58 | 59 | -------------------------------------------------------------------------------- /test/fv/verify_job_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023. projectsveltos.io. All rights reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package fv_test 18 | 19 | import ( 20 | "context" 21 | 22 | . "github.com/onsi/ginkgo/v2" 23 | . "github.com/onsi/gomega" 24 | 25 | batchv1 "k8s.io/api/batch/v1" 26 | corev1 "k8s.io/api/core/v1" 27 | "k8s.io/apimachinery/pkg/types" 28 | ) 29 | 30 | var _ = Describe("Verify Job", Label("FV"), func() { 31 | 32 | It("Job collects all resources and logs", func() { 33 | By("Wait for job to complete") 34 | Eventually(func() bool { 35 | job := &batchv1.Job{} 36 | 37 | err := k8sClient.Get(context.TODO(), 38 | types.NamespacedName{Namespace: "default", Name: "k8s-collector"}, job) 39 | if err != nil { 40 | return false 41 | } 42 | 43 | for i := range job.Status.Conditions { 44 | condition := &job.Status.Conditions[i] 45 | if condition.Type == batchv1.JobComplete && 46 | condition.Status == corev1.ConditionTrue { 47 | return true 48 | } 49 | } 50 | 51 | return false 52 | }, timeout, pollingInterval).Should(BeTrue()) 53 | }) 54 | }) 55 | -------------------------------------------------------------------------------- /test/fv/fv_suite_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023. projectsveltos.io. All rights reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package fv_test 18 | 19 | import ( 20 | "testing" 21 | "time" 22 | 23 | . "github.com/onsi/ginkgo/v2" 24 | . "github.com/onsi/gomega" 25 | 26 | "k8s.io/apimachinery/pkg/runtime" 27 | clientgoscheme "k8s.io/client-go/kubernetes/scheme" 28 | "k8s.io/klog/v2" 29 | ctrl "sigs.k8s.io/controller-runtime" 30 | "sigs.k8s.io/controller-runtime/pkg/client" 31 | ) 32 | 33 | var ( 34 | k8sClient client.Client 35 | scheme *runtime.Scheme 36 | ) 37 | 38 | const ( 39 | timeout = 4 * time.Minute 40 | pollingInterval = 5 * time.Second 41 | ) 42 | 43 | func TestFv(t *testing.T) { 44 | RegisterFailHandler(Fail) 45 | RunSpecs(t, "FV Suite") 46 | } 47 | 48 | var _ = BeforeSuite(func() { 49 | restConfig := ctrl.GetConfigOrDie() 50 | 51 | scheme = runtime.NewScheme() 52 | 53 | ctrl.SetLogger(klog.Background()) 54 | 55 | Expect(clientgoscheme.AddToScheme(scheme)).To(Succeed()) 56 | 57 | var err error 58 | k8sClient, err = client.New(restConfig, client.Options{Scheme: scheme}) 59 | Expect(err).NotTo(HaveOccurred()) 60 | }) 61 | -------------------------------------------------------------------------------- /k8s/collector.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: default 5 | --- 6 | apiVersion: v1 7 | kind: ServiceAccount 8 | metadata: 9 | name: k8s-collector 10 | namespace: default 11 | --- 12 | apiVersion: batch/v1 13 | kind: Job 14 | metadata: 15 | name: k8s-collector 16 | namespace: default 17 | spec: 18 | template: 19 | spec: 20 | restartPolicy: Never 21 | serviceAccountName: k8s-collector 22 | containers: 23 | - name: k8s-collector 24 | image: projectsveltos/k8s-collector:main 25 | imagePullPolicy: IfNotPresent 26 | env: 27 | - name: COLLECTOR_NAMESPACE 28 | valueFrom: 29 | fieldRef: 30 | fieldPath: metadata.namespace 31 | command: 32 | - /k8s-collector 33 | args: 34 | - --config-map=k8s-collector 35 | - --dir=/tmp 36 | volumeMounts: 37 | - mountPath: /tmp 38 | name: tmp 39 | volumes: 40 | - emptyDir: {} 41 | name: tmp 42 | --- 43 | kind: ClusterRole 44 | apiVersion: rbac.authorization.k8s.io/v1 45 | metadata: 46 | name: k8s-collector 47 | rules: 48 | - apiGroups: 49 | - "*" 50 | resources: 51 | - "*" 52 | verbs: 53 | - get 54 | - list 55 | - apiGroups: 56 | - "batch/v1" 57 | verbs: 58 | - "get" 59 | - "list" 60 | - "watch" 61 | resources: 62 | - "pods/logs" 63 | --- 64 | apiVersion: rbac.authorization.k8s.io/v1 65 | kind: ClusterRoleBinding 66 | metadata: 67 | name: k8s-collector 68 | roleRef: 69 | apiGroup: rbac.authorization.k8s.io 70 | kind: ClusterRole 71 | name: k8s-collector 72 | subjects: 73 | - kind: ServiceAccount 74 | name: k8s-collector 75 | namespace: default 76 | -------------------------------------------------------------------------------- /test/collector.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: default 5 | --- 6 | apiVersion: v1 7 | kind: ServiceAccount 8 | metadata: 9 | name: k8s-collector 10 | namespace: default 11 | --- 12 | apiVersion: batch/v1 13 | kind: Job 14 | metadata: 15 | name: k8s-collector 16 | namespace: default 17 | spec: 18 | template: 19 | spec: 20 | restartPolicy: Never 21 | serviceAccountName: k8s-collector 22 | containers: 23 | - name: k8s-collector 24 | image: projectsveltos/k8s-collector-amd64:main 25 | imagePullPolicy: IfNotPresent 26 | env: 27 | - name: COLLECTOR_NAMESPACE 28 | valueFrom: 29 | fieldRef: 30 | fieldPath: metadata.namespace 31 | command: 32 | - /k8s-collector 33 | args: 34 | - --config-map=k8s-collector 35 | - --dir=/collection 36 | volumeMounts: 37 | - mountPath: /collection 38 | name: collection 39 | - mountPath: /tmp 40 | name: tmp 41 | volumes: 42 | - emptyDir: {} 43 | name: tmp 44 | - name: collection 45 | persistentVolumeClaim: 46 | claimName: standard 47 | --- 48 | kind: ClusterRole 49 | apiVersion: rbac.authorization.k8s.io/v1 50 | metadata: 51 | name: k8s-collector 52 | rules: 53 | - apiGroups: 54 | - "*" 55 | resources: 56 | - "*" 57 | verbs: 58 | - get 59 | - list 60 | - apiGroups: 61 | - "batch/v1" 62 | verbs: 63 | - "get" 64 | - "list" 65 | - "watch" 66 | resources: 67 | - "pods/logs" 68 | --- 69 | apiVersion: rbac.authorization.k8s.io/v1 70 | kind: ClusterRoleBinding 71 | metadata: 72 | name: k8s-collector 73 | roleRef: 74 | apiGroup: rbac.authorization.k8s.io 75 | kind: ClusterRole 76 | name: k8s-collector 77 | subjects: 78 | - kind: ServiceAccount 79 | name: k8s-collector 80 | namespace: default 81 | -------------------------------------------------------------------------------- /pkg/utils/k8s_utils_suite_test.go: -------------------------------------------------------------------------------- 1 | package utils_test 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | "time" 7 | 8 | . "github.com/onsi/ginkgo/v2" 9 | . "github.com/onsi/gomega" 10 | 11 | apierrors "k8s.io/apimachinery/pkg/api/errors" 12 | "k8s.io/apimachinery/pkg/runtime" 13 | "k8s.io/apimachinery/pkg/util/wait" 14 | clientgoscheme "k8s.io/client-go/kubernetes/scheme" 15 | "sigs.k8s.io/controller-runtime/pkg/client" 16 | "sigs.k8s.io/controller-runtime/pkg/envtest" 17 | ) 18 | 19 | var ( 20 | scheme *runtime.Scheme 21 | env *envtest.Environment 22 | k8sClient client.Client 23 | ) 24 | 25 | var ( 26 | cacheSyncBackoff = wait.Backoff{ 27 | Duration: 100 * time.Millisecond, 28 | Factor: 1.5, 29 | Steps: 8, 30 | Jitter: 0.4, 31 | } 32 | ) 33 | 34 | func TestK8sUtils(t *testing.T) { 35 | RegisterFailHandler(Fail) 36 | RunSpecs(t, "K8sUtils Suite") 37 | } 38 | 39 | var _ = BeforeSuite(func() { 40 | By("bootstrapping test environment") 41 | 42 | var err error 43 | scheme, err = setupScheme() 44 | Expect(err).To(BeNil()) 45 | 46 | env = &envtest.Environment{ 47 | Scheme: scheme, 48 | ErrorIfCRDPathMissing: true, 49 | } 50 | 51 | _, err = env.Start() 52 | if err != nil { 53 | Expect(err).To(BeNil()) 54 | } 55 | 56 | k8sClient, err = client.New(env.Config, client.Options{Scheme: scheme}) 57 | Expect(err).To(BeNil()) 58 | 59 | }) 60 | 61 | func setupScheme() (*runtime.Scheme, error) { 62 | s := runtime.NewScheme() 63 | if err := clientgoscheme.AddToScheme(s); err != nil { 64 | return nil, err 65 | } 66 | 67 | return s, nil 68 | } 69 | 70 | func waitForObject(ctx context.Context, c client.Client, obj client.Object) { 71 | // Makes sure the cache is updated with the new object 72 | objCopy := obj.DeepCopyObject().(client.Object) 73 | key := client.ObjectKeyFromObject(obj) 74 | if err := wait.ExponentialBackoff( 75 | cacheSyncBackoff, 76 | func() (done bool, err error) { 77 | if err := c.Get(ctx, key, objCopy); err != nil { 78 | if apierrors.IsNotFound(err) { 79 | return false, nil 80 | } 81 | return false, err 82 | } 83 | return true, nil 84 | }); err != nil { 85 | Expect(err).To(BeNil()) 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /pkg/utils/utils.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023. projectsveltos.io. All rights reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package utils 18 | 19 | import ( 20 | "fmt" 21 | "sync" 22 | 23 | "k8s.io/apimachinery/pkg/runtime" 24 | "k8s.io/client-go/kubernetes" 25 | "k8s.io/client-go/rest" 26 | "sigs.k8s.io/controller-runtime/pkg/client" 27 | ) 28 | 29 | // Collector is the client that implements methods to collect resources and logs 30 | type Collector struct { 31 | client client.Client 32 | restConfig *rest.Config 33 | clientset *kubernetes.Clientset 34 | scheme *runtime.Scheme 35 | configMapName string 36 | directory string 37 | } 38 | 39 | var ( 40 | collectorInstance *Collector 41 | mux sync.Mutex 42 | ) 43 | 44 | // GetCollectorInstance return k8sAccess instance used to access resources in the 45 | // management cluster. 46 | func GetCollectorInstance(scheme *runtime.Scheme, restConfig *rest.Config, 47 | directory, configMapName string) (*Collector, error) { 48 | 49 | mux.Lock() 50 | defer mux.Unlock() 51 | 52 | if collectorInstance == nil { 53 | cs, err := kubernetes.NewForConfig(restConfig) 54 | if err != nil { 55 | werr := fmt.Errorf("error in getting access to K8S: %w", err) 56 | return nil, werr 57 | } 58 | 59 | c, err := client.New(restConfig, client.Options{Scheme: scheme}) 60 | if err != nil { 61 | werr := fmt.Errorf("failed to connect: %w", err) 62 | return nil, werr 63 | } 64 | 65 | collectorInstance = &Collector{ 66 | scheme: scheme, 67 | client: c, 68 | clientset: cs, 69 | restConfig: restConfig, 70 | configMapName: configMapName, 71 | directory: directory, 72 | } 73 | } 74 | 75 | return collectorInstance, nil 76 | } 77 | 78 | // GetScheme returns scheme 79 | func (a *Collector) GetScheme() *runtime.Scheme { 80 | return a.scheme 81 | } 82 | 83 | // GetClient returns scheme 84 | func (a *Collector) GetClient() client.Client { 85 | return a.client 86 | } 87 | 88 | // GetConfig returns restConfig 89 | func (a *Collector) GetConfig() *rest.Config { 90 | return a.restConfig 91 | } 92 | -------------------------------------------------------------------------------- /hack/tools/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/gianlucam76/collector/hack/tools 2 | 3 | go 1.22.7 4 | 5 | require ( 6 | github.com/onsi/ginkgo/v2 v2.20.2 7 | golang.org/x/oauth2 v0.23.0 8 | k8s.io/client-go v0.31.1 9 | sigs.k8s.io/controller-tools v0.16.3 10 | sigs.k8s.io/kind v0.24.0 11 | ) 12 | 13 | require ( 14 | cloud.google.com/go/compute/metadata v0.5.1 // indirect 15 | github.com/BurntSushi/toml v1.4.0 // indirect 16 | github.com/alessio/shellescape v1.4.2 // indirect 17 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect 18 | github.com/evanphx/json-patch/v5 v5.9.0 // indirect 19 | github.com/fatih/color v1.17.0 // indirect 20 | github.com/fxamacker/cbor/v2 v2.7.0 // indirect 21 | github.com/go-logr/logr v1.4.2 // indirect 22 | github.com/go-task/slim-sprig/v3 v3.0.0 // indirect 23 | github.com/gobuffalo/flect v1.0.3 // indirect 24 | github.com/gogo/protobuf v1.3.2 // indirect 25 | github.com/google/gofuzz v1.2.0 // indirect 26 | github.com/google/pprof v0.0.0-20240910150728-a0b0bb1d4134 // indirect 27 | github.com/google/safetext v0.0.0-20240722112252-5a72de7e7962 // indirect 28 | github.com/google/uuid v1.6.0 // indirect 29 | github.com/inconshreveable/mousetrap v1.1.0 // indirect 30 | github.com/json-iterator/go v1.1.12 // indirect 31 | github.com/mattn/go-colorable v0.1.13 // indirect 32 | github.com/mattn/go-isatty v0.0.20 // indirect 33 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 34 | github.com/modern-go/reflect2 v1.0.2 // indirect 35 | github.com/pborman/uuid v1.2.1 // indirect 36 | github.com/pelletier/go-toml v1.9.5 // indirect 37 | github.com/pkg/errors v0.9.1 // indirect 38 | github.com/spf13/cobra v1.8.1 // indirect 39 | github.com/spf13/pflag v1.0.5 // indirect 40 | github.com/x448/float16 v0.8.4 // indirect 41 | golang.org/x/mod v0.21.0 // indirect 42 | golang.org/x/net v0.29.0 // indirect 43 | golang.org/x/sync v0.8.0 // indirect 44 | golang.org/x/sys v0.25.0 // indirect 45 | golang.org/x/term v0.24.0 // indirect 46 | golang.org/x/text v0.18.0 // indirect 47 | golang.org/x/time v0.6.0 // indirect 48 | golang.org/x/tools v0.25.0 // indirect 49 | gopkg.in/inf.v0 v0.9.1 // indirect 50 | gopkg.in/yaml.v2 v2.4.0 // indirect 51 | gopkg.in/yaml.v3 v3.0.1 // indirect 52 | k8s.io/api v0.31.1 // indirect 53 | k8s.io/apiextensions-apiserver v0.31.1 // indirect 54 | k8s.io/apimachinery v0.31.1 // indirect 55 | k8s.io/klog/v2 v2.130.1 // indirect 56 | k8s.io/utils v0.0.0-20240921022957-49e7df575cb6 // indirect 57 | sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect 58 | sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect 59 | sigs.k8s.io/yaml v1.4.0 // indirect 60 | ) 61 | -------------------------------------------------------------------------------- /pkg/utils/configuration.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023. projectsveltos.io. All rights reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package utils 18 | 19 | import ( 20 | libsveltosv1alpha1 "github.com/projectsveltos/libsveltos/api/v1alpha1" 21 | ) 22 | 23 | // Resource indicates the type of resources to collect. 24 | type Resource struct { 25 | // Namespace of the resource deployed in the Cluster. 26 | // Empty for resources scoped at cluster level. 27 | // +optional 28 | Namespace string `json:"namespace,omitempty" yaml:"namespace,omitempty"` 29 | 30 | // Group of the resource deployed in the Cluster. 31 | Group string `json:"group" yaml:"group"` 32 | 33 | // Version of the resource deployed in the Cluster. 34 | Version string `json:"version" yaml:"version"` 35 | 36 | // Kind of the resource deployed in the Cluster. 37 | // +kubebuilder:validation:MinLength=1 38 | Kind string `json:"kind" yaml:"kind"` 39 | 40 | // LabelFilters allows to filter resources based on current labels. 41 | LabelFilters []libsveltosv1alpha1.LabelFilter `json:"labelFilters,omitempty" yaml:"labelFilters,omitempty"` 42 | } 43 | 44 | // LogFilter allows to select which logs to collect 45 | type Log struct { 46 | // Namespace of the pods deployed in the Cluster. 47 | // +optional 48 | Namespace string `json:"namespace,omitempty" yaml:"namespace,omitempty"` 49 | 50 | // LabelFilters allows to filter pods based on current labels. 51 | LabelFilters []libsveltosv1alpha1.LabelFilter `json:"labelFilters,omitempty" yaml:"labelFilters,omitempty"` 52 | 53 | // A relative time in seconds before the current time from which to collect logs. 54 | // If this value precedes the time a pod was started, only logs since the pod start will be returned. 55 | // If this value is in the future, no logs will be returned. Only one of sinceSeconds or sinceTime may be specified. 56 | // +optional 57 | SinceSeconds *int64 `json:"sinceSeconds,omitempty" yaml:"sinceSeconds,omitempty"` 58 | } 59 | 60 | // Configuration defines the instruction for collector 61 | type Configuration struct { 62 | // Resources indicates what resorces to collect 63 | // +optional 64 | Resources []Resource `json:"resources,omitempty" yaml:"resources,omitempty"` 65 | 66 | // Logs indicates what pods' log to collect 67 | // +optional 68 | Logs []Log `json:"logs,omitempty" yaml:"logs,omitempty"` 69 | } 70 | -------------------------------------------------------------------------------- /cmd/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023. projectsveltos.io. All rights reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "context" 21 | "flag" 22 | "fmt" 23 | "log" 24 | "os" 25 | 26 | "k8s.io/apimachinery/pkg/runtime" 27 | clientgoscheme "k8s.io/client-go/kubernetes/scheme" 28 | "k8s.io/client-go/rest" 29 | cliflag "k8s.io/component-base/cli/flag" 30 | "k8s.io/klog/v2" 31 | "k8s.io/klog/v2/textlogger" 32 | ctrl "sigs.k8s.io/controller-runtime" 33 | 34 | "github.com/gianlucam76/k8s_collector/pkg/utils" 35 | "github.com/spf13/pflag" 36 | ) 37 | 38 | var ( 39 | configMapName string 40 | directory string 41 | ) 42 | 43 | func main() { 44 | klog.InitFlags(nil) 45 | 46 | initFlags(pflag.CommandLine) 47 | pflag.CommandLine.SetNormalizeFunc(cliflag.WordSepNormalizeFunc) 48 | pflag.CommandLine.AddGoFlagSet(flag.CommandLine) 49 | pflag.Parse() 50 | 51 | config := textlogger.NewConfig(textlogger.Verbosity(1)) 52 | logger := textlogger.NewLogger(config) 53 | 54 | if directory == "" { 55 | logger.Info("directory where to store logs and resources is not defined") 56 | panic(1) 57 | } 58 | 59 | ctx := context.Background() 60 | scheme, restConfig := initializeManagementClusterAccess() 61 | collector, err := utils.GetCollectorInstance(scheme, restConfig, directory, configMapName) 62 | if err != nil { 63 | logger.Info("failed to get collector instance: %v", err) 64 | } 65 | 66 | err = collector.CollectResouces(ctx, logger) 67 | if err != nil { 68 | logger.Info(fmt.Sprintf("failed to collect data: %v", err)) 69 | os.Exit(1) 70 | } 71 | } 72 | 73 | func initializeManagementClusterAccess() (*runtime.Scheme, *rest.Config) { 74 | scheme, err := getScheme() 75 | if err != nil { 76 | werr := fmt.Errorf("failed to get scheme %w", err) 77 | log.Fatal(werr) 78 | } 79 | 80 | restConfig := ctrl.GetConfigOrDie() 81 | restConfig.QPS = 100 82 | restConfig.Burst = 100 83 | 84 | return scheme, restConfig 85 | } 86 | 87 | func getScheme() (*runtime.Scheme, error) { 88 | scheme := runtime.NewScheme() 89 | if err := clientgoscheme.AddToScheme(scheme); err != nil { 90 | return nil, err 91 | } 92 | return scheme, nil 93 | } 94 | 95 | func initFlags(fs *pflag.FlagSet) { 96 | fs.StringVar(&configMapName, 97 | "config-map", "", 98 | "Name of the ConfigMap containing the configuration") 99 | 100 | fs.StringVar(&directory, 101 | "dir", "", 102 | "Name of the directory where logs and resources will be stored") 103 | } 104 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/gianlucam76/k8s_collector 2 | 3 | go 1.22.7 4 | 5 | require ( 6 | github.com/go-logr/logr v1.4.2 7 | github.com/onsi/ginkgo/v2 v2.20.2 8 | github.com/onsi/gomega v1.34.2 9 | github.com/pkg/errors v0.9.1 10 | github.com/projectsveltos/libsveltos v0.38.2 11 | github.com/spf13/pflag v1.0.5 12 | gopkg.in/yaml.v2 v2.4.0 13 | gopkg.in/yaml.v3 v3.0.1 14 | k8s.io/api v0.31.1 15 | k8s.io/apimachinery v0.31.1 16 | k8s.io/client-go v0.31.0 17 | k8s.io/component-base v0.31.0 18 | k8s.io/klog/v2 v2.130.1 19 | sigs.k8s.io/controller-runtime v0.19.0 20 | ) 21 | 22 | require ( 23 | github.com/beorn7/perks v1.0.1 // indirect 24 | github.com/cespare/xxhash/v2 v2.3.0 // indirect 25 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect 26 | github.com/emicklei/go-restful/v3 v3.12.1 // indirect 27 | github.com/evanphx/json-patch/v5 v5.9.0 // indirect 28 | github.com/fsnotify/fsnotify v1.7.0 // indirect 29 | github.com/fxamacker/cbor/v2 v2.7.0 // indirect 30 | github.com/go-openapi/jsonpointer v0.21.0 // indirect 31 | github.com/go-openapi/jsonreference v0.21.0 // indirect 32 | github.com/go-openapi/swag v0.23.0 // indirect 33 | github.com/go-task/slim-sprig/v3 v3.0.0 // indirect 34 | github.com/gogo/protobuf v1.3.2 // indirect 35 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect 36 | github.com/golang/protobuf v1.5.4 // indirect 37 | github.com/google/gnostic-models v0.6.8 // indirect 38 | github.com/google/go-cmp v0.6.0 // indirect 39 | github.com/google/gofuzz v1.2.0 // indirect 40 | github.com/google/pprof v0.0.0-20240910150728-a0b0bb1d4134 // indirect 41 | github.com/google/uuid v1.6.0 // indirect 42 | github.com/imdario/mergo v0.3.16 // indirect 43 | github.com/inconshreveable/mousetrap v1.1.0 // indirect 44 | github.com/josharian/intern v1.0.0 // indirect 45 | github.com/json-iterator/go v1.1.12 // indirect 46 | github.com/klauspost/compress v1.17.9 // indirect 47 | github.com/mailru/easyjson v0.7.7 // indirect 48 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 49 | github.com/modern-go/reflect2 v1.0.2 // indirect 50 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 51 | github.com/prometheus/client_golang v1.20.4 // indirect 52 | github.com/prometheus/client_model v0.6.1 // indirect 53 | github.com/prometheus/common v0.59.1 // indirect 54 | github.com/prometheus/procfs v0.15.1 // indirect 55 | github.com/spf13/cobra v1.8.1 // indirect 56 | github.com/x448/float16 v0.8.4 // indirect 57 | golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 // indirect 58 | golang.org/x/net v0.29.0 // indirect 59 | golang.org/x/oauth2 v0.23.0 // indirect 60 | golang.org/x/sys v0.25.0 // indirect 61 | golang.org/x/term v0.24.0 // indirect 62 | golang.org/x/text v0.18.0 // indirect 63 | golang.org/x/time v0.6.0 // indirect 64 | golang.org/x/tools v0.25.0 // indirect 65 | gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect 66 | google.golang.org/protobuf v1.34.2 // indirect 67 | gopkg.in/inf.v0 v0.9.1 // indirect 68 | k8s.io/apiextensions-apiserver v0.31.0 // indirect 69 | k8s.io/kube-openapi v0.0.0-20240903163716-9e1beecbcb38 // indirect 70 | k8s.io/utils v0.0.0-20240902221715-702e33fdd3c3 // indirect 71 | sigs.k8s.io/cluster-api v1.8.3 // indirect 72 | sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect 73 | sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect 74 | sigs.k8s.io/yaml v1.4.0 // indirect 75 | ) 76 | -------------------------------------------------------------------------------- /pkg/utils/collect.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023. projectsveltos.io. All rights reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package utils 18 | 19 | import ( 20 | "context" 21 | "encoding/json" 22 | "fmt" 23 | "os" 24 | 25 | "github.com/go-logr/logr" 26 | "github.com/pkg/errors" 27 | "gopkg.in/yaml.v3" 28 | corev1 "k8s.io/api/core/v1" 29 | "k8s.io/apimachinery/pkg/types" 30 | ) 31 | 32 | func (a *Collector) CollectResouces(ctx context.Context, logger logr.Logger) error { 33 | config, err := a.loadConfiguration(ctx, logger) 34 | if err != nil { 35 | logger.Info(fmt.Sprintf("failed to get configuration: %v", err)) 36 | return err 37 | } 38 | 39 | if config == nil { 40 | logger.Info("no configuration present") 41 | return nil 42 | } 43 | 44 | err = a.collectData(ctx, config, logger) 45 | 46 | return err 47 | } 48 | 49 | func (a *Collector) loadConfiguration(ctx context.Context, logger logr.Logger) (*Configuration, error) { 50 | namespace := os.Getenv("COLLECTOR_NAMESPACE") 51 | 52 | logger = logger.WithValues("configmap", fmt.Sprintf("%s/%s", namespace, a.configMapName)) 53 | logger.Info("getting ConfigMap ") 54 | 55 | configMap := &corev1.ConfigMap{} 56 | err := a.client.Get(ctx, types.NamespacedName{Namespace: namespace, Name: a.configMapName}, configMap) 57 | if err != nil { 58 | logger.Info(fmt.Sprintf("failed to get configMap: %v", err)) 59 | return nil, err 60 | } 61 | 62 | if configMap.Data == nil { 63 | logger.Info("configMap Data is nil") 64 | return nil, fmt.Errorf("nil ConfigMap.Data") 65 | } 66 | 67 | for k := range configMap.Data { 68 | var currentConfiguration Configuration 69 | 70 | err = yaml.Unmarshal([]byte(configMap.Data[k]), ¤tConfiguration) 71 | if err == nil { 72 | return ¤tConfiguration, nil 73 | } 74 | 75 | err = json.Unmarshal([]byte(configMap.Data[k]), ¤tConfiguration) 76 | if err == nil { 77 | return ¤tConfiguration, nil 78 | } 79 | 80 | logger.Info(fmt.Sprintf("content %q", configMap.Data[k])) 81 | logger.Info(fmt.Sprintf("configMap key: %q does not contain a valid configuration instance: %v", k, err)) 82 | } 83 | 84 | return nil, nil 85 | } 86 | 87 | func (a *Collector) collectData(ctx context.Context, configuration *Configuration, logger logr.Logger) error { 88 | logger.Info("collecting logs") 89 | var err error 90 | for i := range configuration.Logs { 91 | tmpErr := a.collectLogs(ctx, &configuration.Logs[i], logger) 92 | if tmpErr != nil { 93 | logger.Info(fmt.Sprintf("failed to collect logs %v", err)) 94 | if err == nil { 95 | err = tmpErr 96 | } else { 97 | err = errors.Wrap(err, tmpErr.Error()) 98 | } 99 | } 100 | } 101 | 102 | for i := range configuration.Resources { 103 | tmpErr := a.dumpResources(ctx, &configuration.Resources[i], logger) 104 | if tmpErr != nil { 105 | logger.Info(fmt.Sprintf("failed to dump resources %v", err)) 106 | if err == nil { 107 | err = tmpErr 108 | } else { 109 | err = errors.Wrap(err, tmpErr.Error()) 110 | } 111 | } 112 | } 113 | 114 | return err 115 | } 116 | -------------------------------------------------------------------------------- /pkg/utils/collect_test.go: -------------------------------------------------------------------------------- 1 | package utils_test 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "os" 7 | 8 | . "github.com/onsi/ginkgo/v2" 9 | . "github.com/onsi/gomega" 10 | "gopkg.in/yaml.v3" 11 | 12 | corev1 "k8s.io/api/core/v1" 13 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 14 | "k8s.io/klog/v2/textlogger" 15 | 16 | "github.com/gianlucam76/k8s_collector/pkg/utils" 17 | ) 18 | 19 | var ( 20 | data = `resources: 21 | - group: "" 22 | version: v1 23 | kind: Pod 24 | namespace: default 25 | - group: apps 26 | version: v1 27 | kind: Deployment 28 | logs: 29 | - namespace: kube-system 30 | sinceSeconds:600` 31 | ) 32 | 33 | var _ = Describe("Collect", func() { 34 | It("loadConfiguration loads configuration from ConfigMap (YAML)", func() { 35 | sinceSecond := int64(600) 36 | collectorConfig := &utils.Configuration{ 37 | Logs: []utils.Log{ 38 | {Namespace: "kube-system", SinceSeconds: &sinceSecond}, 39 | }, 40 | Resources: []utils.Resource{ 41 | {Group: "", Version: "v1", Kind: "Secret"}, 42 | {Group: "apps", Version: "v1", Kind: "Deployment"}, 43 | }, 44 | } 45 | 46 | dataBytes, err := yaml.Marshal(collectorConfig) 47 | Expect(err).To(BeNil()) 48 | 49 | configMap := &corev1.ConfigMap{ 50 | ObjectMeta: metav1.ObjectMeta{ 51 | Namespace: "default", 52 | Name: "foo", 53 | }, 54 | Data: map[string]string{ 55 | "config": string(dataBytes), 56 | }, 57 | } 58 | 59 | collector, err := utils.GetCollectorInstance(scheme, env.Config, "", configMap.Name) 60 | Expect(err).To(BeNil()) 61 | Expect(k8sClient.Create(context.TODO(), configMap)).To(Succeed()) 62 | 63 | waitForObject(context.TODO(), k8sClient, configMap) 64 | 65 | os.Setenv("COLLECTOR_NAMESPACE", configMap.Namespace) 66 | config := textlogger.NewConfig(textlogger.Verbosity(1)) 67 | logger := textlogger.NewLogger(config) 68 | collectorConfig, err = utils.LoadConfiguration(collector, context.TODO(), logger) 69 | Expect(err).To(BeNil()) 70 | Expect(collectorConfig).ToNot(BeNil()) 71 | }) 72 | 73 | It("loadConfiguration loads configuration from ConfigMap (JSON)", func() { 74 | sinceSecond := int64(600) 75 | collectorConfig := &utils.Configuration{ 76 | Logs: []utils.Log{ 77 | {Namespace: "kube-system", SinceSeconds: &sinceSecond}, 78 | }, 79 | Resources: []utils.Resource{ 80 | {Group: "", Version: "v1", Kind: "Service"}, 81 | {Group: "", Version: "v1", Kind: "Pod"}, 82 | {Group: "apps", Version: "v1", Kind: "Deployment"}, 83 | }, 84 | } 85 | 86 | dataBytes, err := json.Marshal(collectorConfig) 87 | Expect(err).To(BeNil()) 88 | 89 | configMap := &corev1.ConfigMap{ 90 | ObjectMeta: metav1.ObjectMeta{ 91 | Namespace: "default", 92 | Name: "bar", 93 | }, 94 | Data: map[string]string{ 95 | "config": string(dataBytes), 96 | }, 97 | } 98 | 99 | collector, err := utils.GetCollectorInstance(scheme, env.Config, "", configMap.Name) 100 | Expect(err).To(BeNil()) 101 | Expect(k8sClient.Create(context.TODO(), configMap)).To(Succeed()) 102 | 103 | waitForObject(context.TODO(), k8sClient, configMap) 104 | 105 | os.Setenv("COLLECTOR_NAMESPACE", configMap.Namespace) 106 | config := textlogger.NewConfig(textlogger.Verbosity(1)) 107 | logger := textlogger.NewLogger(config) 108 | collectorConfig, err = utils.LoadConfiguration(collector, context.TODO(), logger) 109 | Expect(err).To(BeNil()) 110 | Expect(collectorConfig).ToNot(BeNil()) 111 | }) 112 | 113 | It("loadConfiguration loads configuration from ConfigMap ", func() { 114 | dataBytes, err := yaml.Marshal(data) 115 | Expect(err).To(BeNil()) 116 | 117 | configMap := &corev1.ConfigMap{ 118 | ObjectMeta: metav1.ObjectMeta{ 119 | Namespace: "default", 120 | Name: "test", 121 | }, 122 | Data: map[string]string{ 123 | "config": string(dataBytes), 124 | }, 125 | } 126 | 127 | collector, err := utils.GetCollectorInstance(scheme, env.Config, "", configMap.Name) 128 | Expect(err).To(BeNil()) 129 | Expect(k8sClient.Create(context.TODO(), configMap)).To(Succeed()) 130 | 131 | waitForObject(context.TODO(), k8sClient, configMap) 132 | 133 | os.Setenv("COLLECTOR_NAMESPACE", configMap.Namespace) 134 | config := textlogger.NewConfig(textlogger.Verbosity(1)) 135 | logger := textlogger.NewLogger(config) 136 | collectorConfig, err := utils.LoadConfiguration(collector, context.TODO(), logger) 137 | Expect(err).To(BeNil()) 138 | Expect(collectorConfig).ToNot(BeNil()) 139 | Expect(len(collectorConfig.Resources)).To(Equal(2)) 140 | }) 141 | }) 142 | -------------------------------------------------------------------------------- /pkg/utils/logs.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023. projectsveltos.io. All rights reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package utils 18 | 19 | import ( 20 | "context" 21 | "fmt" 22 | "io" 23 | "os" 24 | "path" 25 | "path/filepath" 26 | 27 | "github.com/go-logr/logr" 28 | corev1 "k8s.io/api/core/v1" 29 | "k8s.io/apimachinery/pkg/labels" 30 | "sigs.k8s.io/controller-runtime/pkg/client" 31 | 32 | libsveltosv1alpha1 "github.com/projectsveltos/libsveltos/api/v1alpha1" 33 | ) 34 | 35 | const ( 36 | permission0600 = 0600 37 | permission0644 = 0644 38 | permission0755 = 0755 39 | ) 40 | 41 | func (a *Collector) collectLogs(ctx context.Context, log *Log, logger logr.Logger) error { 42 | options := client.ListOptions{} 43 | 44 | if len(log.LabelFilters) > 0 { 45 | labelFilter := "" 46 | for i := range log.LabelFilters { 47 | if labelFilter != "" { 48 | labelFilter += "," 49 | } 50 | f := log.LabelFilters[i] 51 | if f.Operation == libsveltosv1alpha1.OperationEqual { 52 | labelFilter += fmt.Sprintf("%s=%s", f.Key, f.Value) 53 | } else { 54 | labelFilter += fmt.Sprintf("%s!=%s", f.Key, f.Value) 55 | } 56 | } 57 | 58 | parsedSelector, err := labels.Parse(labelFilter) 59 | if err != nil { 60 | return err 61 | } 62 | options.LabelSelector = parsedSelector 63 | } 64 | 65 | if log.Namespace != "" { 66 | options.Namespace = log.Namespace 67 | } 68 | 69 | pods := &corev1.PodList{} 70 | if err := a.client.List(ctx, pods, &options); err != nil { 71 | return err 72 | } 73 | 74 | logger.Info(fmt.Sprintf("found %d pods", len(pods.Items))) 75 | for i := range pods.Items { 76 | if err := a.dumpPodLogs(ctx, log.SinceSeconds, &pods.Items[i]); err != nil { 77 | return err 78 | } 79 | } 80 | 81 | return nil 82 | } 83 | 84 | // dumpPodLogs collects logs for all containers in a pod and store them. 85 | // If pod has restarted, it will try to collect log from previous run as well. 86 | func (a *Collector) dumpPodLogs(ctx context.Context, since *int64, pod *corev1.Pod) error { 87 | for i := range pod.Spec.Containers { 88 | container := &pod.Spec.Containers[i] 89 | resourceFilePath := path.Join(a.directory, "logs", pod.Namespace, pod.Name+"-"+container.Name) 90 | err := os.MkdirAll(filepath.Dir(resourceFilePath), permission0755) 91 | if err != nil { 92 | return err 93 | } 94 | 95 | err = a.collectPodLogs(ctx, pod.Namespace, pod.Name, container.Name, resourceFilePath, since, false) 96 | if err != nil { 97 | return err 98 | } 99 | 100 | // If container restarted, collect previous logs as well 101 | for i := range pod.Status.ContainerStatuses { 102 | containerStatus := &pod.Status.ContainerStatuses[i] 103 | if containerStatus.Name == container.Name && 104 | containerStatus.RestartCount > 0 { 105 | 106 | resourceFilePath := path.Join(a.directory, "logs", pod.Namespace, 107 | pod.Name+"-"+container.Name+".previous") 108 | 109 | err := os.MkdirAll(filepath.Dir(resourceFilePath), permission0755) 110 | if err != nil { 111 | return err 112 | } 113 | 114 | err = a.collectPodLogs(ctx, pod.Namespace, pod.Name, container.Name, resourceFilePath, since, true) 115 | if err != nil { 116 | return err 117 | } 118 | } 119 | } 120 | } 121 | 122 | return nil 123 | } 124 | 125 | // collectPodLogs collect logs for a given namespace/pod container 126 | func (a *Collector) collectPodLogs(ctx context.Context, namespace, podName, containerName, filename string, 127 | since *int64, previous bool) (err error) { 128 | 129 | // open output file 130 | var fo *os.File 131 | fo, err = os.Create(filename) 132 | if err != nil { 133 | return err 134 | } 135 | // close fo on exit and check for its returned error 136 | defer func() { 137 | if cerr := fo.Close(); cerr != nil { 138 | if err == nil { 139 | err = cerr 140 | } 141 | } 142 | }() 143 | 144 | podLogOpts := corev1.PodLogOptions{} 145 | if containerName != "" { 146 | podLogOpts.Container = containerName 147 | } 148 | 149 | if previous { 150 | podLogOpts.Previous = previous 151 | } 152 | 153 | if since != nil { 154 | podLogOpts.SinceSeconds = since 155 | } 156 | 157 | req := a.clientset.CoreV1().Pods(namespace).GetLogs(podName, &podLogOpts) 158 | var podLogs io.ReadCloser 159 | podLogs, err = req.Stream(ctx) 160 | if err != nil { 161 | return err 162 | } 163 | defer podLogs.Close() 164 | 165 | _, err = io.Copy(fo, podLogs) 166 | 167 | return err 168 | } 169 | -------------------------------------------------------------------------------- /.golangci.yaml: -------------------------------------------------------------------------------- 1 | linters-settings: 2 | dupl: 3 | threshold: 200 4 | errorlint: 5 | # Use '%w' to format errors. 6 | errorf: true 7 | exhaustive: 8 | # Ignore auto-generated code. 9 | check-generated: false 10 | default-signifies-exhaustive: false 11 | forbidigo: 12 | forbid: 13 | # Forbid 'fmt.Print[|f|ln]() in shipping code. 14 | - 'fmt\.Print.*' 15 | funlen: 16 | lines: 100 17 | statements: 50 18 | goconst: 19 | min-len: 2 20 | min-occurrences: 2 21 | gocritic: 22 | enabled-tags: 23 | - diagnostic 24 | - experimental 25 | - opinionated 26 | - performance 27 | - style 28 | disabled-checks: 29 | - dupImport # https://github.com/go-critic/go-critic/issues/845 30 | - ifElseChain 31 | - octalLiteral 32 | - whyNoLint # conflicts with nolintlint config providing superset functionality 33 | - wrapperFunc 34 | gocyclo: 35 | min-complexity: 20 36 | goimports: 37 | local-prefixes: github.com/projectsveltos 38 | mnd: 39 | checks: argument,case,condition,return 40 | gomodguard: 41 | # Although this is almost empty, we will evolve the list below with the 42 | # modules we shouldn't use for technical and/or security reasons. 43 | blocked: 44 | modules: 45 | - k8s.io/kubernetes: 46 | reason: "There is no good, avoidable reason to use this package and often leads to issues such as https://bit.ly/3dlKScY. However, if you disagree please include @ravchama in code review highlighting the reason." 47 | versions: 48 | # Don't merge replace directives using local path. 49 | local_replace_directives: true 50 | govet: 51 | shadow: true 52 | settings: 53 | printf: 54 | funcs: 55 | - (github.com/golangci/golangci-lint/pkg/logutils.Log).Infof 56 | - (github.com/golangci/golangci-lint/pkg/logutils.Log).Warnf 57 | - (github.com/golangci/golangci-lint/pkg/logutils.Log).Errorf 58 | - (github.com/golangci/golangci-lint/pkg/logutils.Log).Fatalf 59 | grouper: 60 | const-require-grouping: true 61 | import-require-single-import: true 62 | import-require-grouping: true 63 | var-require-grouping: true 64 | lll: 65 | line-length: 160 66 | maintidx: 67 | # check https://bit.ly/3tlJX3n for maintainability index. 68 | ## 69 | # starting with 25 to begin with, with the plan to bump it to 40 eventuallly. 70 | ## 71 | under: 25 72 | misspell: 73 | locale: US 74 | nolintlint: 75 | allow-unused: false # report any unused nolint directives 76 | require-explanation: true # require an explanation for nolint directives 77 | require-specific: true # require nolint directives to be specific about which linter is being skipped 78 | revive: 79 | ignore-generated-header: true 80 | # this is a new linter, so let's start with Warning instead of errors to 81 | # begin with. 82 | severity: warning 83 | whitespace: 84 | multi-if: true 85 | multi-func: true 86 | 87 | linters: 88 | # please, do not use `enable-all`: it's deprecated and will be removed soon. 89 | # inverted configuration with `enable-all` and `disable` is not scalable during updates of golangci-lint 90 | disable-all: true 91 | fast: false 92 | enable: 93 | - bidichk 94 | - containedctx 95 | - bodyclose 96 | - dogsled 97 | - dupl 98 | - durationcheck 99 | - errcheck 100 | - errname 101 | - errorlint 102 | - exhaustive 103 | - exportloopref 104 | - forbidigo 105 | - funlen 106 | - goconst 107 | - gocritic 108 | - gocyclo 109 | - gofmt 110 | - goimports 111 | - mnd 112 | - gomodguard 113 | - goprintffuncname 114 | - gosec 115 | - gosimple 116 | - govet 117 | - grouper 118 | - ineffassign 119 | - lll 120 | - misspell 121 | - maintidx 122 | - nakedret 123 | - noctx 124 | - nolintlint 125 | - nosprintfhostport 126 | - predeclared 127 | - revive 128 | - staticcheck 129 | - typecheck 130 | - unconvert 131 | - unparam 132 | - unused 133 | - whitespace 134 | 135 | # don't enable: 136 | # - asciicheck 137 | # - exhaustivestruct # applicable to special cases. 138 | # - gochecknoglobals 139 | # - gocognit 140 | # - godot 141 | # - godox 142 | # - goerr113 143 | # - golint # deprecated 144 | # - interfacer # deprecated 145 | # - maligned #deprecated 146 | # - nestif 147 | # - prealloc 148 | # - scopelint # deprecated 149 | # - testpackage 150 | # - wsl 151 | 152 | issues: 153 | # Excluding configuration per path, per linter, per text and per-source 154 | exclude-rules: 155 | - path: _test\.go 156 | linters: 157 | - mnd 158 | - grouper 159 | - maintidx 160 | 161 | # https://github.com/go-critic/go-critic/issues/926 162 | - linters: 163 | - gocritic 164 | text: "unnecessaryDefer:" 165 | # Maximum issues count per one linter. 166 | # Set to 0 to disable. 167 | max-issues-per-linter: 0 168 | # Maximum count of issues with the same text. 169 | # Set to 0 to disable. 170 | max-same-issues: 0 171 | 172 | run: 173 | # Allow multiple parallel golangci-lint instances running. 174 | allow-parallel-runners: true 175 | # Timeout for analysis 176 | timeout: 5m 177 | 178 | -------------------------------------------------------------------------------- /pkg/utils/resources.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023. projectsveltos.io. All rights reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package utils 18 | 19 | import ( 20 | "context" 21 | "fmt" 22 | "os" 23 | "path" 24 | "path/filepath" 25 | 26 | "github.com/go-logr/logr" 27 | "gopkg.in/yaml.v2" 28 | apimeta "k8s.io/apimachinery/pkg/api/meta" 29 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 30 | "k8s.io/apimachinery/pkg/runtime" 31 | "k8s.io/apimachinery/pkg/runtime/schema" 32 | "k8s.io/client-go/discovery" 33 | "k8s.io/client-go/dynamic" 34 | "k8s.io/client-go/restmapper" 35 | "sigs.k8s.io/controller-runtime/pkg/client" 36 | 37 | libsveltosv1alpha1 "github.com/projectsveltos/libsveltos/api/v1alpha1" 38 | ) 39 | 40 | func (a *Collector) dumpResources(ctx context.Context, resource *Resource, logger logr.Logger) error { 41 | logger = logger.WithValues("gvk", fmt.Sprintf("%s:%s:%s", resource.Group, resource.Version, resource.Kind)) 42 | logger.Info("collecting resources") 43 | 44 | gvk := schema.GroupVersionKind{ 45 | Group: resource.Group, 46 | Version: resource.Version, 47 | Kind: resource.Kind, 48 | } 49 | 50 | dc := discovery.NewDiscoveryClientForConfigOrDie(a.restConfig) 51 | groupResources, err := restmapper.GetAPIGroupResources(dc) 52 | if err != nil { 53 | return err 54 | } 55 | mapper := restmapper.NewDiscoveryRESTMapper(groupResources) 56 | 57 | d := dynamic.NewForConfigOrDie(a.restConfig) 58 | 59 | mapping, err := mapper.RESTMapping(gvk.GroupKind(), gvk.Version) 60 | if err != nil { 61 | if apimeta.IsNoMatchError(err) { 62 | return nil 63 | } 64 | return err 65 | } 66 | 67 | resourceId := schema.GroupVersionResource{ 68 | Group: gvk.Group, 69 | Version: gvk.Version, 70 | Resource: mapping.Resource.Resource, 71 | } 72 | 73 | options := metav1.ListOptions{} 74 | 75 | if len(resource.LabelFilters) > 0 { 76 | labelFilter := "" 77 | for i := range resource.LabelFilters { 78 | if labelFilter != "" { 79 | labelFilter += "," 80 | } 81 | f := resource.LabelFilters[i] 82 | if f.Operation == libsveltosv1alpha1.OperationEqual { 83 | labelFilter += fmt.Sprintf("%s=%s", f.Key, f.Value) 84 | } else { 85 | labelFilter += fmt.Sprintf("%s!=%s", f.Key, f.Value) 86 | } 87 | } 88 | 89 | options.LabelSelector = labelFilter 90 | } 91 | 92 | if resource.Namespace != "" { 93 | if options.FieldSelector != "" { 94 | options.FieldSelector += "," 95 | } 96 | options.FieldSelector += fmt.Sprintf("metadata.namespace=%s", resource.Namespace) 97 | } 98 | 99 | list, err := d.Resource(resourceId).List(ctx, options) 100 | if err != nil { 101 | return err 102 | } 103 | 104 | logger.Info(fmt.Sprintf("collected %d resources", len(list.Items))) 105 | for i := range list.Items { 106 | err = a.dumpObject(&list.Items[i], logger) 107 | if err != nil { 108 | return err 109 | } 110 | } 111 | 112 | return nil 113 | } 114 | 115 | // dumpObject is a helper function to generically dump resource definition 116 | // given the resource reference and file path for dumping location. 117 | func (a *Collector) dumpObject(resource client.Object, logger logr.Logger) error { 118 | // Do not store resource version 119 | resource.SetResourceVersion("") 120 | err := a.addTypeInformationToObject(resource) 121 | if err != nil { 122 | return err 123 | } 124 | 125 | logger = logger.WithValues("kind", resource.GetObjectKind().GroupVersionKind().Kind) 126 | logger = logger.WithValues("resource", fmt.Sprintf("%s %s", 127 | resource.GetNamespace(), resource.GetName())) 128 | 129 | if !resource.GetDeletionTimestamp().IsZero() { 130 | logger.Info("resource is marked for deletion. Do not collect it.") 131 | } 132 | 133 | resourceYAML, err := yaml.Marshal(resource) 134 | if err != nil { 135 | return err 136 | } 137 | 138 | metaObj, err := apimeta.Accessor(resource) 139 | if err != nil { 140 | return err 141 | } 142 | 143 | kind := resource.GetObjectKind().GroupVersionKind().Kind 144 | namespace := metaObj.GetNamespace() 145 | name := metaObj.GetName() 146 | 147 | resourceFilePath := path.Join(a.directory, "resources", namespace, kind, name+".yaml") 148 | err = os.MkdirAll(filepath.Dir(resourceFilePath), permission0755) 149 | if err != nil { 150 | return err 151 | } 152 | 153 | f, err := os.OpenFile(resourceFilePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, permission0644) 154 | if err != nil { 155 | return err 156 | } 157 | defer f.Close() 158 | 159 | logger.Info(fmt.Sprintf("storing resource in %s", resourceFilePath)) 160 | return os.WriteFile(f.Name(), resourceYAML, permission0600) 161 | } 162 | 163 | func (a *Collector) addTypeInformationToObject(obj client.Object) error { 164 | gvks, _, err := a.scheme.ObjectKinds(obj) 165 | if err != nil { 166 | return err 167 | } 168 | 169 | for _, gvk := range gvks { 170 | if gvk.Kind == "" { 171 | continue 172 | } 173 | if gvk.Version == "" || gvk.Version == runtime.APIVersionInternal { 174 | continue 175 | } 176 | obj.GetObjectKind().SetGroupVersionKind(gvk) 177 | break 178 | } 179 | 180 | return nil 181 | } 182 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # k8s-collector 2 | A Kubernetes Job to collect resources, logs and events from a Kubernetes cluster. 3 | k8s-collector is a Job that can collect both resources (YAMLs) and logs when created. 4 | 5 | [k8s/collector.yaml](https://raw.githubusercontent.com/gianlucam76/k8s_collector/main/k8s/collector.yaml) contains the YAML to run it. Please be aware you will have to modify Job volume mounts (as it will store logs and resources in tmp directory). 6 | If you want to try it out on [Kind](https://kind.sigs.k8s.io) cluster, then you can use this YAML which uses a persistent volume provided by KinD 7 | [test/fv/collector.yaml](https://raw.githubusercontent.com/gianlucam76/k8s_collector/main/test/fv/collector.yaml) 8 | 9 | ```k8s-collector``` expects two argurments: 10 | 11 | 1. dir => this is the directory when all collected resources and logs will be stored 12 | 2. config-map => this is the name of the ConfigMap that contain the configuration on which logs/resources to collect. This README contains an example for such ConfigMap. ConfigMap must be in the same namespae of the Job. 13 | 14 | ```yaml 15 | apiVersion: batch/v1 16 | kind: Job 17 | metadata: 18 | name: k8s-collector 19 | namespace: default 20 | spec: 21 | template: 22 | spec: 23 | restartPolicy: Never 24 | serviceAccountName: k8s-collector 25 | containers: 26 | - name: k8s-collector 27 | image: projectsveltos/k8s-collector-amd64:main 28 | imagePullPolicy: IfNotPresent 29 | env: 30 | - name: COLLECTOR_NAMESPACE 31 | valueFrom: 32 | fieldRef: 33 | fieldPath: metadata.namespace 34 | command: 35 | - /k8s-collector 36 | args: 37 | - --config-map=k8s-collector 38 | - --dir=/collection 39 | ``` 40 | 41 | ### ConfigMap example 42 | Following is an example of ConfigMap containing the Collector configuration. 43 | Configuration is asking for: 44 | 45 | - v1 Secrets and appsv1 Deployments to be collected in all namespaces 46 | - logs to be collected for all pods in the kube-system namespace. Only the last 600 seconds. 47 | 48 | ```yaml 49 | apiVersion: v1 50 | data: 51 | config.json: '{"resources":[{"group":"","version":"v1","kind":"Secret"},{"group":"apps","version":"v1","kind":"Deployment"}],"logs":[{"namespace":"kube-system","sinceSeconds":600}]}' 52 | kind: ConfigMap 53 | metadata: 54 | name: k8s-collector 55 | namespace: default 56 | ``` 57 | 58 | The content of the ConfigMap can be either JSON or YAML. Same ConfigMap using YAML 59 | 60 | ```yaml 61 | apiVersion: v1 62 | data: 63 | config.yaml: | 64 | resources: 65 | - group: "" 66 | version: v1 67 | kind: Secret 68 | - group: apps 69 | version: v1 70 | kind: Deployment 71 | logs: 72 | - namespace: kube-system 73 | sinceSeconds:600 74 | kind: ConfigMap 75 | metadata: 76 | name: k8s-collector 77 | namespace: default 78 | ``` 79 | 80 | When collecting resources, specify the group/version/kind. That is mandatory. Anything can be passed, including CustomResourceDefinitions. 81 | You can filter resources by namespace and/or labels. 82 | For instance to collect all Secret instances but *only* Deployment instances: 83 | 84 | - in the __nginx__ namespace and 85 | - with labels app:nginx and version:latest 86 | 87 | use the following ConfigMap 88 | 89 | ```yaml 90 | apiVersion: v1 91 | data: 92 | config.yaml: | 93 | resources: 94 | - group: "" 95 | version: v" 96 | kind: Secret 97 | - group: apps 98 | version: v1 99 | kind: Deployment 100 | namespace: nginx 101 | labelFilters: 102 | - key: app 103 | operation: Equal 104 | value: nginx 105 | - key: version 106 | operation: Equal 107 | value: latest 108 | kind: ConfigMap 109 | metadata: 110 | name: k8s-collector 111 | namespace: default 112 | ``` 113 | 114 | When collecting logs, you can select a subset of Pods by specifying the namespace and the label filters (same as for resources). 115 | 116 | ### Collection folders 117 | k8s-collector will create two folders: 118 | 119 | 1. ```logs``` => this will contain collected logs 120 | 2. ```resources``` => this will contain collected resources 121 | 122 | Each directory contains one subdirectory per namespace. Sticking with above example in the ```logs``` directory we have a ```kube-system``` subdirectory (since we asked k8s-collector to collect logs in that directory only). 123 | Then within the ```kube-system``` sudirectory there is a log per pod/container pair. 124 | For instance 125 | 126 | ``` 127 | coredns-5dd5756b68-bb5hr-coredns kube-apiserver-sveltos-management-control-plane-kube-apiserver 128 | coredns-5dd5756b68-jjknb-coredns kube-controller-manager-sveltos-management-control-plane-kube-controller-manager 129 | etcd-sveltos-management-control-plane-etcd kube-proxy-qcvbf-kube-proxy 130 | kindnet-bf559-kindnet-cni kube-proxy-trk6j-kube-proxy 131 | kindnet-qzs4f-kindnet-cni kube-scheduler-sveltos-management-control-plane-kube-scheduler 132 | ``` 133 | 134 | The ```resource``` subdirectory contains one directory per namespace. And within each namespace directory, there is one directory per ```Kind``` 135 | For instance, we asked k8s-collector to collect __Secret__ and __Deployment__ from any namespace, so 136 | 137 | ```resources/cert-manager/``` contains two subdirectories: 138 | - Deployment => all collected Deployment instances in the cert-manager namespace will be here 139 | - Secret => all collected Secret instance in the cert-manager namespace will be here 140 | 141 | I developed to be used along with [Sveltos](https://github.com/projectsveltos) but it can be used on its own. 142 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Ensure Make is run with bash shell as some syntax below is bash-specific 2 | SHELL:=/usr/bin/env bash 3 | 4 | # Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set) 5 | ifeq (,$(shell go env GOBIN)) 6 | GOBIN=$(shell go env GOPATH)/bin 7 | else 8 | GOBIN=$(shell go env GOBIN) 9 | endif 10 | GO_INSTALL := ./scripts/go_install.sh 11 | 12 | REGISTRY ?= projectsveltos 13 | IMAGE_NAME ?= k8s-collector 14 | export COLLECTOR_IMG ?= $(REGISTRY)/$(IMAGE_NAME) 15 | TAG ?= main 16 | ARCH ?= amd64 17 | OS ?= $(shell uname -s | tr A-Z a-z) 18 | K8S_LATEST_VER ?= $(shell curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt) 19 | 20 | # Directories. 21 | ROOT_DIR:=$(shell dirname $(realpath $(firstword $(MAKEFILE_LIST)))) 22 | TOOLS_DIR := hack/tools 23 | BIN_DIR := bin 24 | TOOLS_BIN_DIR := $(abspath $(TOOLS_DIR)/$(BIN_DIR)) 25 | 26 | GOBUILD=go build 27 | 28 | GENERATED_FILES:=./manifest/manifest.yaml 29 | 30 | ## Tool Binaries 31 | GOLANGCI_LINT := $(TOOLS_BIN_DIR)/golangci-lint 32 | GOIMPORTS := $(TOOLS_BIN_DIR)/goimports 33 | GINKGO := $(TOOLS_BIN_DIR)/ginkgo 34 | KUBECTL := $(TOOLS_BIN_DIR)/kubectl 35 | CONTROLLER_GEN := $(TOOLS_BIN_DIR)/controller-gen 36 | KIND := $(TOOLS_BIN_DIR)/kind 37 | 38 | GOLANGCI_LINT_VERSION := "v1.59.0" 39 | 40 | KUSTOMIZE_VER := v5.3.0 41 | KUSTOMIZE_BIN := kustomize 42 | KUSTOMIZE := $(abspath $(TOOLS_BIN_DIR)/$(KUSTOMIZE_BIN)-$(KUSTOMIZE_VER)) 43 | KUSTOMIZE_PKG := sigs.k8s.io/kustomize/kustomize/v5 44 | $(KUSTOMIZE): # Build kustomize from tools folder. 45 | CGO_ENABLED=0 GOBIN=$(TOOLS_BIN_DIR) $(GO_INSTALL) $(KUSTOMIZE_PKG) $(KUSTOMIZE_BIN) $(KUSTOMIZE_VER) 46 | 47 | SETUP_ENVTEST_VER := v0.0.0-20240522175850-2e9781e9fc60 48 | SETUP_ENVTEST_BIN := setup-envtest 49 | SETUP_ENVTEST := $(abspath $(TOOLS_BIN_DIR)/$(SETUP_ENVTEST_BIN)-$(SETUP_ENVTEST_VER)) 50 | SETUP_ENVTEST_PKG := sigs.k8s.io/controller-runtime/tools/setup-envtest 51 | setup-envtest: $(SETUP_ENVTEST) ## Set up envtest (download kubebuilder assets) 52 | @echo KUBEBUILDER_ASSETS=$(KUBEBUILDER_ASSETS) 53 | 54 | $(SETUP_ENVTEST_BIN): $(SETUP_ENVTEST) ## Build a local copy of setup-envtest. 55 | 56 | $(SETUP_ENVTEST): # Build setup-envtest from tools folder. 57 | GOBIN=$(TOOLS_BIN_DIR) $(GO_INSTALL) $(SETUP_ENVTEST_PKG) $(SETUP_ENVTEST_BIN) $(SETUP_ENVTEST_VER) 58 | 59 | $(CONTROLLER_GEN): $(TOOLS_DIR)/go.mod # Build controller-gen from tools folder. 60 | cd $(TOOLS_DIR); $(GOBUILD) -tags=tools -o $(subst $(TOOLS_DIR)/hack/tools/,,$@) sigs.k8s.io/controller-tools/cmd/controller-gen 61 | 62 | $(GOLANGCI_LINT): # Build golangci-lint from tools folder. 63 | cd $(TOOLS_DIR); ./get-golangci-lint.sh $(GOLANGCI_LINT_VERSION) 64 | 65 | $(GOIMPORTS): 66 | cd $(TOOLS_DIR); $(GOBUILD) -tags=tools -o $(subst $(TOOLS_DIR)/hack/tools/,,$@) golang.org/x/tools/cmd/goimports 67 | 68 | $(GINKGO): $(TOOLS_DIR)/go.mod 69 | cd $(TOOLS_DIR) && $(GOBUILD) -tags tools -o $(subst $(TOOLS_DIR)/hack/tools/,,$@) github.com/onsi/ginkgo/v2/ginkgo 70 | 71 | $(KIND): $(TOOLS_DIR)/go.mod 72 | cd $(TOOLS_DIR) && $(GOBUILD) -tags tools -o $(subst $(TOOLS_DIR)/hack/tools/,,$@) sigs.k8s.io/kind 73 | 74 | $(KUBECTL): 75 | curl -L https://storage.googleapis.com/kubernetes-release/release/$(K8S_LATEST_VER)/bin/$(OS)/$(ARCH)/kubectl -o $@ 76 | chmod +x $@ 77 | 78 | 79 | .PHONY: help 80 | help: ## Display this help. 81 | @awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST) 82 | 83 | ##@ Tools 84 | 85 | .PHONY: tools 86 | tools: $(GOLANGCI_LINT) $(GOIMPORTS) $(KUSTOMIZE) $(GINKGO) $(KUBECTL) $(SETUP_ENVTEST) $(CONTROLLER_GEN) ## build all tools 87 | 88 | .PHONY: clean 89 | clean: ## Remove all built tools 90 | rm -rf $(TOOLS_BIN_DIR)/* 91 | 92 | ##@ generate 93 | 94 | .PHONY: generate-modules 95 | generate-modules: ## Run go mod tidy to ensure modules are up to date 96 | go mod tidy 97 | cd $(TOOLS_DIR); go mod tidy 98 | 99 | set-manifest-image: 100 | sed -i'' -e 's@image: .*@image: '"${MANIFEST_IMG}:$(MANIFEST_TAG)"'@' ./manifest/manifest.yaml 101 | 102 | ##@ docker 103 | PKEY ?= id_rsa 104 | 105 | .PHONY: docker-build 106 | docker-build: ## Build the docker image for k8s-collector 107 | docker build --load -t $(COLLECTOR_IMG):$(TAG) -f Dockerfile . 108 | 109 | .PHONY: docker-buildx 110 | docker-buildx: ## docker build for multiple arch and push to docker hub 111 | docker buildx build --push --platform linux/amd64,linux/arm64 -t $(COLLECTOR_IMG):$(TAG) . 112 | 113 | ##@ Build 114 | 115 | .PHONY: vet 116 | vet: ## Run go vet against code 117 | go vet ./... 118 | 119 | .PHONY: fmt 120 | fmt goimports: $(GOIMPORTS) ## Format and adjust import modules. 121 | $(GOIMPORTS) -local github.com/projectsveltos -w . 122 | 123 | .PHONY: lint 124 | lint: $(GOLANGCI_LINT) ## Lint codebase 125 | $(GOLANGCI_LINT) run -v --fast=false --max-issues-per-linter 0 --max-same-issues 0 --timeout 5m 126 | 127 | .PHONY: build 128 | build: fmt vet ## Build manager binary. 129 | go build -o $(BIN_DIR)/k8s-collector cmd/main.go 130 | 131 | ##@ Testing 132 | 133 | # KUBEBUILDER_ENVTEST_KUBERNETES_VERSION refers to the version of kubebuilder assets to be downloaded by envtest binary. 134 | KUBEBUILDER_ENVTEST_KUBERNETES_VERSION = 1.29.0 135 | 136 | ifeq ($(shell go env GOOS),darwin) # Use the darwin/amd64 binary until an arm64 version is available 137 | KUBEBUILDER_ASSETS ?= $(shell $(SETUP_ENVTEST) use --use-env -p path --arch amd64 $(KUBEBUILDER_ENVTEST_KUBERNETES_VERSION)) 138 | else 139 | KUBEBUILDER_ASSETS ?= $(shell $(SETUP_ENVTEST) use --use-env -p path $(KUBEBUILDER_ENVTEST_KUBERNETES_VERSION)) 140 | endif 141 | 142 | .PHONY: test 143 | test: | fmt vet $(SETUP_ENVTEST) ## Run uts. 144 | KUBEBUILDER_ASSETS="$(KUBEBUILDER_ASSETS)" go test $(shell go list ./... |grep -v test/fv |grep -v pkg/deployer/fake |grep -v test/helpers) $(TEST_ARGS) -coverprofile cover.out 145 | 146 | KIND_CONFIG ?= kind-cluster.yaml 147 | CONTROL_CLUSTER_NAME ?= sveltos-management 148 | 149 | # K8S_VERSION for the Kind cluster can be set as environment variable. If not defined, 150 | # this default value is used 151 | ifndef K8S_VERSION 152 | K8S_VERSION := v1.31.0 153 | endif 154 | 155 | 156 | .PHONY: load-image 157 | load-image: docker-build $(KIND) 158 | $(KIND) load docker-image $(COLLECTOR_IMG):$(TAG) --name $(CONTROL_CLUSTER_NAME) 159 | 160 | .PHONY: create-cluster 161 | create-cluster: $(KIND) $(KUBECTL) ## Create a new kind cluster designed for development 162 | sed -e "s/K8S_VERSION/$(K8S_VERSION)/g" test/$(KIND_CONFIG) > test/$(KIND_CONFIG).tmp 163 | $(KIND) create cluster --name=$(CONTROL_CLUSTER_NAME) --config test/$(KIND_CONFIG).tmp 164 | 165 | @echo "apply PersistentVolumeClaim" 166 | $(KUBECTL) apply -f test/pvc.yaml 167 | 168 | $(KUBECTL) create configmap k8s-collector --from-file test/configmap.yaml 169 | 170 | @echo 'Load k8s-collector image into cluster' 171 | $(MAKE) load-image 172 | $(KUBECTL) apply -f test/collector.yaml 173 | 174 | .PHONY: delete-cluster 175 | delete-cluster: $(KIND) ## Deletes the kind cluster $(CONTROL_CLUSTER_NAME) 176 | $(KIND) delete cluster --name $(CONTROL_CLUSTER_NAME) 177 | 178 | .PHONY: kind-test 179 | kind-test: test create-cluster fv ## Build docker image; start kind cluster; load docker image; install all cluster api components and run fv 180 | 181 | .PHONY: fv 182 | fv: $(KUBECTL) $(GINKGO) ## Run Sveltos Controller tests using existing cluster 183 | cd test/fv; $(GINKGO) -nodes 1 --label-filter='FV' --v --trace --randomize-all -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /hack/tools/go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go/compute/metadata v0.5.1 h1:NM6oZeZNlYjiwYje+sYFjEpP0Q0zCan1bmQW/KmIrGs= 2 | cloud.google.com/go/compute/metadata v0.5.1/go.mod h1:C66sj2AluDcIqakBq/M8lw8/ybHgOZqin2obFxa/E5k= 3 | github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0= 4 | github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= 5 | github.com/alessio/shellescape v1.4.2 h1:MHPfaU+ddJ0/bYWpgIeUnQUqKrlJ1S7BfEYPM4uEoM0= 6 | github.com/alessio/shellescape v1.4.2/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30= 7 | github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 8 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 9 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 10 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= 11 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 12 | github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= 13 | github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= 14 | github.com/evanphx/json-patch/v5 v5.9.0 h1:kcBlZQbplgElYIlo/n1hJbls2z/1awpXxpRi0/FOJfg= 15 | github.com/evanphx/json-patch/v5 v5.9.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ= 16 | github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4= 17 | github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI= 18 | github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= 19 | github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= 20 | github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= 21 | github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= 22 | github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= 23 | github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= 24 | github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= 25 | github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= 26 | github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= 27 | github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= 28 | github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU= 29 | github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= 30 | github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= 31 | github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= 32 | github.com/gobuffalo/flect v1.0.3 h1:xeWBM2nui+qnVvNM4S3foBhCAL2XgPU+a7FdpelbTq4= 33 | github.com/gobuffalo/flect v1.0.3/go.mod h1:A5msMlrHtLqh9umBSnvabjsMrCcCpAyzglnDvkbYKHs= 34 | github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= 35 | github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= 36 | github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= 37 | github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= 38 | github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= 39 | github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= 40 | github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 41 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 42 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 43 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 44 | github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= 45 | github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 46 | github.com/google/pprof v0.0.0-20240910150728-a0b0bb1d4134 h1:c5FlPPgxOn7kJz3VoPLkQYQXGBS3EklQ4Zfi57uOuqQ= 47 | github.com/google/pprof v0.0.0-20240910150728-a0b0bb1d4134/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= 48 | github.com/google/safetext v0.0.0-20240722112252-5a72de7e7962 h1:+9C/TgFfcCmZBV7Fjb3kQCGlkpFrhtvFDgbdQHB9RaA= 49 | github.com/google/safetext v0.0.0-20240722112252-5a72de7e7962/go.mod h1:H3K1Iu/utuCfa10JO+GsmKUYSWi7ug57Rk6GaDRHaaQ= 50 | github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 51 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 52 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 53 | github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= 54 | github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= 55 | github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= 56 | github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= 57 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 58 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 59 | github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= 60 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 61 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 62 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 63 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 64 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 65 | github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= 66 | github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= 67 | github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= 68 | github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= 69 | github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 70 | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 71 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 72 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 73 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 74 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 75 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 76 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 77 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= 78 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= 79 | github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= 80 | github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= 81 | github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= 82 | github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= 83 | github.com/onsi/ginkgo/v2 v2.20.2 h1:7NVCeyIWROIAheY21RLS+3j2bb52W0W82tkberYytp4= 84 | github.com/onsi/ginkgo/v2 v2.20.2/go.mod h1:K9gyxPIlb+aIvnZ8bd9Ak+YP18w3APlR+5coaZoE2ag= 85 | github.com/onsi/gomega v1.34.2 h1:pNCwDkzrsv7MS9kpaQvVb1aVLahQXyJ/Tv5oAZMI3i8= 86 | github.com/onsi/gomega v1.34.2/go.mod h1:v1xfxRgk0KIsG+QOdm7p8UosrOzPYRo60fd3B/1Dukc= 87 | github.com/pborman/uuid v1.2.1 h1:+ZZIw58t/ozdjRaXh/3awHfmWRbzYxJoAdNJxe/3pvw= 88 | github.com/pborman/uuid v1.2.1/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= 89 | github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= 90 | github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= 91 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 92 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 93 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 94 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= 95 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 96 | github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= 97 | github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= 98 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 99 | github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= 100 | github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= 101 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 102 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 103 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 104 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 105 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 106 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 107 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 108 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 109 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 110 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= 111 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 112 | github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= 113 | github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= 114 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 115 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 116 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 117 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 118 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 119 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 120 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 121 | golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= 122 | golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= 123 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 124 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 125 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 126 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 127 | golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= 128 | golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= 129 | golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= 130 | golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= 131 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 132 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 133 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 134 | golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= 135 | golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 136 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 137 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 138 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 139 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 140 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 141 | golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= 142 | golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 143 | golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM= 144 | golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8= 145 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 146 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 147 | golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= 148 | golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= 149 | golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U= 150 | golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= 151 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 152 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 153 | golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 154 | golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 155 | golang.org/x/tools v0.25.0 h1:oFU9pkj/iJgs+0DT+VMHrx+oBKs/LJMV+Uvg78sl+fE= 156 | golang.org/x/tools v0.25.0/go.mod h1:/vtpO8WL1N9cQC3FN5zPqb//fRXskFHbLKk4OW1Q7rg= 157 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 158 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 159 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 160 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 161 | google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= 162 | google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= 163 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 164 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 165 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 166 | gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= 167 | gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= 168 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= 169 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 170 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 171 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 172 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 173 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 174 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 175 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 176 | k8s.io/api v0.31.1 h1:Xe1hX/fPW3PXYYv8BlozYqw63ytA92snr96zMW9gWTU= 177 | k8s.io/api v0.31.1/go.mod h1:sbN1g6eY6XVLeqNsZGLnI5FwVseTrZX7Fv3O26rhAaI= 178 | k8s.io/apiextensions-apiserver v0.31.1 h1:L+hwULvXx+nvTYX/MKM3kKMZyei+UiSXQWciX/N6E40= 179 | k8s.io/apiextensions-apiserver v0.31.1/go.mod h1:tWMPR3sgW+jsl2xm9v7lAyRF1rYEK71i9G5dRtkknoQ= 180 | k8s.io/apimachinery v0.31.1 h1:mhcUBbj7KUjaVhyXILglcVjuS4nYXiwC+KKFBgIVy7U= 181 | k8s.io/apimachinery v0.31.1/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo= 182 | k8s.io/client-go v0.31.1 h1:f0ugtWSbWpxHR7sjVpQwuvw9a3ZKLXX0u0itkFXufb0= 183 | k8s.io/client-go v0.31.1/go.mod h1:sKI8871MJN2OyeqRlmA4W4KM9KBdBUpDLu/43eGemCg= 184 | k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= 185 | k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= 186 | k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag= 187 | k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98= 188 | k8s.io/utils v0.0.0-20240921022957-49e7df575cb6 h1:MDF6h2H/h4tbzmtIKTuctcwZmY0tY9mD9fNT47QO6HI= 189 | k8s.io/utils v0.0.0-20240921022957-49e7df575cb6/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= 190 | sigs.k8s.io/controller-tools v0.16.3 h1:z48C5/d4jCVQQvtiSBL5MYyZ3EO2eFIOXrIKMgHVhFY= 191 | sigs.k8s.io/controller-tools v0.16.3/go.mod h1:AEj6k+w1kYpLZv2einOH3mj52ips4W/6FUjnB5tkJGs= 192 | sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= 193 | sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= 194 | sigs.k8s.io/kind v0.24.0 h1:g4y4eu0qa+SCeKESLpESgMmVFBebL0BDa6f777OIWrg= 195 | sigs.k8s.io/kind v0.24.0/go.mod h1:t7ueEpzPYJvHA8aeLtI52rtFftNgUYUaCwvxjk7phfw= 196 | sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= 197 | sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= 198 | sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= 199 | sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= 200 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= 2 | github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= 3 | github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= 4 | github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= 5 | github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= 6 | github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 7 | github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 8 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 9 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 10 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= 11 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 12 | github.com/emicklei/go-restful/v3 v3.12.1 h1:PJMDIM/ak7btuL8Ex0iYET9hxM3CI2sjZtzpL63nKAU= 13 | github.com/emicklei/go-restful/v3 v3.12.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= 14 | github.com/evanphx/json-patch v5.7.0+incompatible h1:vgGkfT/9f8zE6tvSCe74nfpAVDQ2tG6yudJd8LBksgI= 15 | github.com/evanphx/json-patch v5.7.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= 16 | github.com/evanphx/json-patch/v5 v5.9.0 h1:kcBlZQbplgElYIlo/n1hJbls2z/1awpXxpRi0/FOJfg= 17 | github.com/evanphx/json-patch/v5 v5.9.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ= 18 | github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= 19 | github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= 20 | github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= 21 | github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= 22 | github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= 23 | github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= 24 | github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= 25 | github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= 26 | github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= 27 | github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= 28 | github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ= 29 | github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4= 30 | github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= 31 | github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= 32 | github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= 33 | github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= 34 | github.com/gobuffalo/flect v1.0.2 h1:eqjPGSo2WmjgY2XlpGwo2NXgL3RucAKo4k4qQMNA5sA= 35 | github.com/gobuffalo/flect v1.0.2/go.mod h1:A5msMlrHtLqh9umBSnvabjsMrCcCpAyzglnDvkbYKHs= 36 | github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= 37 | github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= 38 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= 39 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 40 | github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= 41 | github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= 42 | github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= 43 | github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= 44 | github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 45 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 46 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 47 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 48 | github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= 49 | github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 50 | github.com/google/pprof v0.0.0-20240910150728-a0b0bb1d4134 h1:c5FlPPgxOn7kJz3VoPLkQYQXGBS3EklQ4Zfi57uOuqQ= 51 | github.com/google/pprof v0.0.0-20240910150728-a0b0bb1d4134/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= 52 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 53 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 54 | github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= 55 | github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= 56 | github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= 57 | github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= 58 | github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= 59 | github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= 60 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 61 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 62 | github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= 63 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 64 | github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= 65 | github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= 66 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 67 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 68 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 69 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 70 | github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= 71 | github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= 72 | github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= 73 | github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= 74 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 75 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 76 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 77 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 78 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 79 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= 80 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= 81 | github.com/onsi/ginkgo/v2 v2.20.2 h1:7NVCeyIWROIAheY21RLS+3j2bb52W0W82tkberYytp4= 82 | github.com/onsi/ginkgo/v2 v2.20.2/go.mod h1:K9gyxPIlb+aIvnZ8bd9Ak+YP18w3APlR+5coaZoE2ag= 83 | github.com/onsi/gomega v1.34.2 h1:pNCwDkzrsv7MS9kpaQvVb1aVLahQXyJ/Tv5oAZMI3i8= 84 | github.com/onsi/gomega v1.34.2/go.mod h1:v1xfxRgk0KIsG+QOdm7p8UosrOzPYRo60fd3B/1Dukc= 85 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 86 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 87 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 88 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= 89 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 90 | github.com/projectsveltos/libsveltos v0.38.2 h1:CJHr+0KWXXcevxkmkzzHhrM5I61Oj3SyuR3/lxfDTO0= 91 | github.com/projectsveltos/libsveltos v0.38.2/go.mod h1:FITu0ZxiB0lfPVfFkIKJ9rk/ZWDup1OTymiIX+uEXtw= 92 | github.com/prometheus/client_golang v1.20.4 h1:Tgh3Yr67PaOv/uTqloMsCEdeuFTatm5zIq5+qNN23vI= 93 | github.com/prometheus/client_golang v1.20.4/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= 94 | github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= 95 | github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= 96 | github.com/prometheus/common v0.59.1 h1:LXb1quJHWm1P6wq/U824uxYi4Sg0oGvNeUm1z5dJoX0= 97 | github.com/prometheus/common v0.59.1/go.mod h1:GpWM7dewqmVYcd7SmRaiWVe9SSqjf0UrwnYnpEZNuT0= 98 | github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= 99 | github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= 100 | github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= 101 | github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= 102 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 103 | github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= 104 | github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= 105 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 106 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 107 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 108 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 109 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= 110 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 111 | github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= 112 | github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= 113 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 114 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 115 | go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= 116 | go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= 117 | go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= 118 | go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= 119 | go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= 120 | go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= 121 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 122 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 123 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 124 | golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk= 125 | golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY= 126 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 127 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 128 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 129 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 130 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 131 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 132 | golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= 133 | golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= 134 | golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= 135 | golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= 136 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 137 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 138 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 139 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 140 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 141 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 142 | golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= 143 | golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 144 | golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM= 145 | golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8= 146 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 147 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 148 | golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= 149 | golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= 150 | golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U= 151 | golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= 152 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 153 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 154 | golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 155 | golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 156 | golang.org/x/tools v0.25.0 h1:oFU9pkj/iJgs+0DT+VMHrx+oBKs/LJMV+Uvg78sl+fE= 157 | golang.org/x/tools v0.25.0/go.mod h1:/vtpO8WL1N9cQC3FN5zPqb//fRXskFHbLKk4OW1Q7rg= 158 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 159 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 160 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 161 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 162 | gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw= 163 | gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= 164 | google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= 165 | google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= 166 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 167 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 168 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 169 | gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= 170 | gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= 171 | gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= 172 | gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= 173 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 174 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 175 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 176 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 177 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 178 | k8s.io/api v0.31.1 h1:Xe1hX/fPW3PXYYv8BlozYqw63ytA92snr96zMW9gWTU= 179 | k8s.io/api v0.31.1/go.mod h1:sbN1g6eY6XVLeqNsZGLnI5FwVseTrZX7Fv3O26rhAaI= 180 | k8s.io/apiextensions-apiserver v0.31.0 h1:fZgCVhGwsclj3qCw1buVXCV6khjRzKC5eCFt24kyLSk= 181 | k8s.io/apiextensions-apiserver v0.31.0/go.mod h1:b9aMDEYaEe5sdK+1T0KU78ApR/5ZVp4i56VacZYEHxk= 182 | k8s.io/apimachinery v0.31.1 h1:mhcUBbj7KUjaVhyXILglcVjuS4nYXiwC+KKFBgIVy7U= 183 | k8s.io/apimachinery v0.31.1/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo= 184 | k8s.io/client-go v0.31.0 h1:QqEJzNjbN2Yv1H79SsS+SWnXkBgVu4Pj3CJQgbx0gI8= 185 | k8s.io/client-go v0.31.0/go.mod h1:Y9wvC76g4fLjmU0BA+rV+h2cncoadjvjjkkIGoTLcGU= 186 | k8s.io/component-base v0.31.0 h1:/KIzGM5EvPNQcYgwq5NwoQBaOlVFrghoVGr8lG6vNRs= 187 | k8s.io/component-base v0.31.0/go.mod h1:TYVuzI1QmN4L5ItVdMSXKvH7/DtvIuas5/mm8YT3rTo= 188 | k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= 189 | k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= 190 | k8s.io/kube-openapi v0.0.0-20240903163716-9e1beecbcb38 h1:1dWzkmJrrprYvjGwh9kEUxmcUV/CtNU8QM7h1FLWQOo= 191 | k8s.io/kube-openapi v0.0.0-20240903163716-9e1beecbcb38/go.mod h1:coRQXBK9NxO98XUv3ZD6AK3xzHCxV6+b7lrquKwaKzA= 192 | k8s.io/utils v0.0.0-20240902221715-702e33fdd3c3 h1:b2FmK8YH+QEwq/Sy2uAEhmqL5nPfGYbJOcaqjeYYZoA= 193 | k8s.io/utils v0.0.0-20240902221715-702e33fdd3c3/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= 194 | sigs.k8s.io/cluster-api v1.8.3 h1:N6i25rF5QMadwVg2UPfuO6CzmNXjqnF2r1MAO+kcsro= 195 | sigs.k8s.io/cluster-api v1.8.3/go.mod h1:pXv5LqLxuIbhGIXykyNKiJh+KrLweSBajVHHitPLyoY= 196 | sigs.k8s.io/controller-runtime v0.19.0 h1:nWVM7aq+Il2ABxwiCizrVDSlmDcshi9llbaFbC0ji/Q= 197 | sigs.k8s.io/controller-runtime v0.19.0/go.mod h1:iRmWllt8IlaLjvTTDLhRBXIEtkCK6hwVBJJsYS9Ajf4= 198 | sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= 199 | sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= 200 | sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= 201 | sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= 202 | sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= 203 | sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= 204 | --------------------------------------------------------------------------------