diff --git a/Makefile b/Makefile index 0126d00a..9d6eaae1 100644 --- a/Makefile +++ b/Makefile @@ -175,6 +175,7 @@ build-windows: save-version gen-version $(BUILD_UI) ## Build for Windows # Target: build-ui # Description: Builds the UI for the dashboard. # Usage: make build-ui +# TODO: use env var to define the build mode .PHONY: build-ui build-ui: gen-version ## Build UI for the dashboard @echo "🧀 Building UI for the dashboard ..." @@ -292,3 +293,33 @@ check: ## Check the lint, test, and build @$(MAKE) build || (echo "❌ Build check failed!" && exit 1) @echo "✅ Build check passed!" @echo "🎉 All checks passed successfully!" + +# controller-gen path +CONTROLLER_GEN = ${GOPATH}/bin/controller-gen +# controller-gen version +CONTROLLER_GEN_VERSION = v0.17.1 + +# Target: install-controller-gen +# Description: Install controller_gen. +# Usage: +# make install-controller-gen +.PHONY: install-controller-gen +install-controller-gen: + $(GO) install sigs.k8s.io/controller-tools/cmd/controller-gen@$(CONTROLLER_GEN_VERSION) + +# Target: generate-crds +# Description: Generate CRDs into a special dir. +# Usage: +# make generate-crds +.PHONY: generate-crds +generate-crds: + @# generate rbac, webhook and crds + $(CONTROLLER_GEN) crd paths="./pkg/kubernetes/apis/cluster/v1beta1/..." output:crd:artifacts:config=config/crds/ + $(CONTROLLER_GEN) crd paths="./pkg/kubernetes/apis/search/v1beta1/..." output:crd:artifacts:config=config/crds/ + +# Target: manifests +# Description: Install controller_gen and generate CRDs into a special dir. +# Usage: +# make manifests +.PHONY: manifests +manifests: install-controller-gen generate-crds diff --git a/api/openapispec/docs.go b/api/openapispec/docs.go index d588053b..b804a128 100644 --- a/api/openapispec/docs.go +++ b/api/openapispec/docs.go @@ -545,6 +545,20 @@ var doc = `{ "name": "description", "in": "formData", "required": true + }, + { + "type": "string", + "description": "cluster mode", + "name": "clusterMode", + "in": "formData", + "required": true + }, + { + "type": "integer", + "description": "cluster scale level", + "name": "clusterLevel", + "in": "formData", + "required": true } ], "responses": { @@ -920,6 +934,69 @@ var doc = `{ } } }, + "/rest-api/v1/cluster/{clusterName}/agentYml": { + "get": { + "description": "Obtain the agent yaml in secret for cluster.", + "consumes": [ + "text/plain", + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "cluster" + ], + "summary": "Get agent yaml", + "parameters": [ + { + "type": "string", + "description": "The name of the cluster", + "name": "clusterName", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "Verification passed server version", + "schema": { + "type": "string" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "string" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "type": "string" + } + }, + "404": { + "description": "Not Found", + "schema": { + "type": "string" + } + }, + "429": { + "description": "Too Many Requests", + "schema": { + "type": "string" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "type": "string" + } + } + } + } + }, "/rest-api/v1/clusters": { "get": { "description": "This endpoint lists all cluster resources.", @@ -2265,6 +2342,14 @@ var doc = `{ "cluster.ClusterPayload": { "type": "object", "properties": { + "clusterLevel": { + "description": "clusterLevel is the scale level of cluster to be created", + "type": "integer" + }, + "clusterMode": { + "description": "ClusterMode is the mode of cluster to be created", + "type": "string" + }, "description": { "description": "ClusterDescription is the description of cluster to be created", "type": "string" diff --git a/api/openapispec/swagger.json b/api/openapispec/swagger.json index d5092eea..78e30607 100644 --- a/api/openapispec/swagger.json +++ b/api/openapispec/swagger.json @@ -529,6 +529,20 @@ "name": "description", "in": "formData", "required": true + }, + { + "type": "string", + "description": "cluster mode", + "name": "clusterMode", + "in": "formData", + "required": true + }, + { + "type": "integer", + "description": "cluster scale level", + "name": "clusterLevel", + "in": "formData", + "required": true } ], "responses": { @@ -904,6 +918,69 @@ } } }, + "/rest-api/v1/cluster/{clusterName}/agentYml": { + "get": { + "description": "Obtain the agent yaml in secret for cluster.", + "consumes": [ + "text/plain", + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "cluster" + ], + "summary": "Get agent yaml", + "parameters": [ + { + "type": "string", + "description": "The name of the cluster", + "name": "clusterName", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "Verification passed server version", + "schema": { + "type": "string" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "string" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "type": "string" + } + }, + "404": { + "description": "Not Found", + "schema": { + "type": "string" + } + }, + "429": { + "description": "Too Many Requests", + "schema": { + "type": "string" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "type": "string" + } + } + } + } + }, "/rest-api/v1/clusters": { "get": { "description": "This endpoint lists all cluster resources.", @@ -2249,6 +2326,14 @@ "cluster.ClusterPayload": { "type": "object", "properties": { + "clusterLevel": { + "description": "clusterLevel is the scale level of cluster to be created", + "type": "integer" + }, + "clusterMode": { + "description": "ClusterMode is the mode of cluster to be created", + "type": "string" + }, "description": { "description": "ClusterDescription is the description of cluster to be created", "type": "string" diff --git a/api/openapispec/swagger.yaml b/api/openapispec/swagger.yaml index 46f4b1eb..9ce91cc6 100644 --- a/api/openapispec/swagger.yaml +++ b/api/openapispec/swagger.yaml @@ -86,6 +86,12 @@ definitions: type: object cluster.ClusterPayload: properties: + clusterLevel: + description: clusterLevel is the scale level of cluster to be created + type: integer + clusterMode: + description: ClusterMode is the mode of cluster to be created + type: string description: description: ClusterDescription is the description of cluster to be created type: string @@ -774,6 +780,48 @@ paths: summary: Update updates the cluster metadata by name. tags: - cluster + /rest-api/v1/cluster/{clusterName}/agentYml: + get: + consumes: + - text/plain + - application/json + description: Obtain the agent yaml in secret for cluster. + parameters: + - description: The name of the cluster + in: path + name: clusterName + required: true + type: string + produces: + - application/json + responses: + "200": + description: Verification passed server version + schema: + type: string + "400": + description: Bad Request + schema: + type: string + "401": + description: Unauthorized + schema: + type: string + "404": + description: Not Found + schema: + type: string + "429": + description: Too Many Requests + schema: + type: string + "500": + description: Internal Server Error + schema: + type: string + summary: Get agent yaml + tags: + - cluster /rest-api/v1/cluster/config/file: post: consumes: @@ -800,6 +848,16 @@ paths: name: description required: true type: string + - description: cluster mode + in: formData + name: clusterMode + required: true + type: string + - description: cluster scale level + in: formData + name: clusterLevel + required: true + type: integer produces: - text/plain responses: diff --git a/cmd/cert-generator/main.go b/cmd/cert-generator/main.go index 8d4e56a2..73272f66 100644 --- a/cmd/cert-generator/main.go +++ b/cmd/cert-generator/main.go @@ -25,14 +25,15 @@ import ( "k8s.io/apiserver/pkg/server" "k8s.io/client-go/rest" "k8s.io/client-go/tools/clientcmd" - "k8s.io/component-base/cli" + "k8s.io/klog/v2" ) func main() { ctx := server.SetupSignalContext() command := NewCertGeneratorCommand(ctx) - code := cli.Run(command) - os.Exit(code) + if err := command.Execute(); err != nil { + klog.Fatal(err) + } } func NewCertGeneratorCommand(ctx context.Context) *cobra.Command { diff --git a/cmd/karpor/app/agent.go b/cmd/karpor/app/agent.go new file mode 100644 index 00000000..b1a1cd03 --- /dev/null +++ b/cmd/karpor/app/agent.go @@ -0,0 +1,127 @@ +// Copyright The Karpor Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package app + +import ( + "context" + + esclient "github.com/elastic/go-elasticsearch/v8" + "github.com/pkg/errors" + "github.com/spf13/cobra" + "github.com/spf13/pflag" + "k8s.io/client-go/dynamic" + "k8s.io/klog/v2/klogr" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/healthz" + + "github.com/KusionStack/karpor/pkg/infra/search/storage/elasticsearch" + clusterv1beta1 "github.com/KusionStack/karpor/pkg/kubernetes/apis/cluster/v1beta1" + "github.com/KusionStack/karpor/pkg/kubernetes/scheme" + "github.com/KusionStack/karpor/pkg/syncer" + "github.com/KusionStack/karpor/pkg/syncer/utils" +) + +type agentOptions struct { + syncerOptions + ClusterName string + ClusterMode string +} + +func NewAgentOptions() *agentOptions { + return &agentOptions{ + syncerOptions: *NewSyncerOptions(), + } +} + +func (o *agentOptions) AddFlags(fs *pflag.FlagSet) { + o.syncerOptions.AddFlags(fs) + fs.StringVar(&o.ClusterName, "cluster-name", "", "The cluster name in hub cluster.") + fs.StringVar(&o.ClusterMode, "cluster-mode", "pull", "The cluster mode.") +} + +func NewAgentCommand(ctx context.Context) *cobra.Command { + options := NewAgentOptions() + cmd := &cobra.Command{ + Use: "agent", + Short: "start a resource syncer agent which deployed in user cluster", + RunE: func(cmd *cobra.Command, args []string) error { + // use the same logical as the Non-HA syncer in controller cluster + return runAgent(ctx, options) + }, + } + options.AddFlags(cmd.Flags()) + return cmd +} + +func runAgent(ctx context.Context, options *agentOptions) error { + ctrl.SetLogger(klogr.New()) + log := ctrl.Log.WithName("setup") + + if options.ClusterMode == clusterv1beta1.PushClusterMode { + // apply crds + dynamicClient, err := dynamic.NewForConfig(ctrl.GetConfigOrDie()) + if err != nil { + return errors.Wrapf(err, "failed to build dynamic client for ageng") + } + err = utils.ApplyCrds(ctx, dynamicClient) + if err != nil { + return err + } + } + + mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ + Scheme: scheme.Scheme, + MetricsBindAddress: options.MetricsAddr, + HealthProbeBindAddress: options.ProbeAddr, + }) + if err != nil { + log.Error(err, "unable to start manager") + return err + } + + // TODO: add startup parameters to change the type of storage + //nolint:contextcheck + es, err := elasticsearch.NewStorage(esclient.Config{ + Addresses: options.ElasticSearchAddresses, + }) + if err != nil { + log.Error(err, "unable to init elasticsearch client") + return err + } + + //nolint:contextcheck + if err = syncer.NewAgentReconciler(es, options.ClusterName).SetupWithManager(mgr); err != nil { + log.Error(err, "unable to create resource syncer") + return err + } + + if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { + log.Error(err, "unable to set up health check") + return err + } + + if err := mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil { + log.Error(err, "unable to set up ready check") + return err + } + + log.Info("starting manager") + if err := mgr.Start(ctx); err != nil { + log.Error(err, "problem running manager") + return err + } + + return nil +} diff --git a/cmd/karpor/app/options/core_options.go b/cmd/karpor/app/options/core_options.go index 228fc4f6..7b2c6992 100644 --- a/cmd/karpor/app/options/core_options.go +++ b/cmd/karpor/app/options/core_options.go @@ -15,15 +15,18 @@ package options import ( - "github.com/KusionStack/karpor/pkg/kubernetes/registry" "github.com/spf13/pflag" + + "github.com/KusionStack/karpor/pkg/kubernetes/registry" ) type CoreOptions struct { - EnableRBAC bool - ReadOnlyMode bool - GithubBadge bool - Version bool + EnableRBAC bool + ReadOnlyMode bool + GithubBadge bool + Version bool + HighAvailability bool + EnableAI bool } func NewCoreOptions() *CoreOptions { @@ -50,4 +53,6 @@ func (o *CoreOptions) AddFlags(fs *pflag.FlagSet) { fs.BoolVar(&o.ReadOnlyMode, "read-only-mode", false, "turn on the read only mode") fs.BoolVar(&o.GithubBadge, "github-badge", false, "whether to display the github badge") fs.BoolVarP(&o.Version, "version", "V", o.Version, "Print version and exit") + fs.BoolVar(&o.HighAvailability, "high-availability", false, "whether to use high-availability feature.") + fs.BoolVar(&o.EnableAI, "enable-ai", false, "whether to enable llm.") } diff --git a/cmd/karpor/app/options/recommended.go b/cmd/karpor/app/options/recommended.go index 43b2ffc2..61a7dd8b 100644 --- a/cmd/karpor/app/options/recommended.go +++ b/cmd/karpor/app/options/recommended.go @@ -18,10 +18,6 @@ import ( "fmt" "time" - karporopenapi "github.com/KusionStack/karpor/pkg/kubernetes/generated/openapi" - k8sopenapi "github.com/KusionStack/karpor/pkg/kubernetes/openapi" - "github.com/KusionStack/karpor/pkg/kubernetes/registry" - "github.com/KusionStack/karpor/pkg/kubernetes/scheme" "github.com/spf13/pflag" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/sets" @@ -41,6 +37,11 @@ import ( "k8s.io/kube-openapi/pkg/common" kubeoptions "k8s.io/kubernetes/pkg/kubeapiserver/options" "k8s.io/kubernetes/pkg/serviceaccount" + + karporopenapi "github.com/KusionStack/karpor/pkg/kubernetes/generated/openapi" + k8sopenapi "github.com/KusionStack/karpor/pkg/kubernetes/openapi" + "github.com/KusionStack/karpor/pkg/kubernetes/registry" + "github.com/KusionStack/karpor/pkg/kubernetes/scheme" ) // RecommendedOptions contains the recommended options for running an API server. @@ -118,9 +119,6 @@ func (o *RecommendedOptions) ApplyTo(config *server.RecommendedConfig) error { if err := o.ServerRun.ApplyTo(genericConfig); err != nil { return err } - if err := o.Etcd.Complete(genericConfig.StorageObjectCountTracker, genericConfig.DrainedNotify(), genericConfig.AddPostStartHook); err != nil { - return err - } if err := o.Etcd.ApplyTo(genericConfig); err != nil { return err } @@ -152,7 +150,7 @@ func (o *RecommendedOptions) ApplyTo(config *server.RecommendedConfig) error { config.ClientConfig = kubeClientConfig config.SharedInformerFactory = informer - if err := o.Authentication.ApplyTo(&genericConfig.Authentication, genericConfig.SecureServing, config.EgressSelector, config.OpenAPIConfig, config.OpenAPIV3Config, client, informer); err != nil { + if err := o.Authentication.ApplyTo(&genericConfig.Authentication, genericConfig.SecureServing, config.EgressSelector, config.OpenAPIConfig, client, informer); err != nil { return err } diff --git a/cmd/karpor/app/server.go b/cmd/karpor/app/server.go index 2f513c32..8c7cfdf3 100644 --- a/cmd/karpor/app/server.go +++ b/cmd/karpor/app/server.go @@ -26,12 +26,6 @@ import ( "os" "time" - "github.com/KusionStack/karpor/cmd/karpor/app/options" - "github.com/KusionStack/karpor/pkg/kubernetes/registry" - "github.com/KusionStack/karpor/pkg/kubernetes/scheme" - "github.com/KusionStack/karpor/pkg/server" - proxyutil "github.com/KusionStack/karpor/pkg/util/proxy" - "github.com/KusionStack/karpor/pkg/version" "github.com/spf13/cobra" "github.com/spf13/pflag" "k8s.io/apimachinery/pkg/runtime/schema" @@ -45,6 +39,13 @@ import ( authzmodes "k8s.io/kubernetes/pkg/kubeapiserver/authorizer/modes" "k8s.io/kubernetes/pkg/serviceaccount" netutils "k8s.io/utils/net" + + "github.com/KusionStack/karpor/cmd/karpor/app/options" + "github.com/KusionStack/karpor/pkg/kubernetes/registry" + "github.com/KusionStack/karpor/pkg/kubernetes/scheme" + "github.com/KusionStack/karpor/pkg/server" + proxyutil "github.com/KusionStack/karpor/pkg/util/proxy" + "github.com/KusionStack/karpor/pkg/version" ) const ( @@ -86,7 +87,7 @@ func NewOptions(out, errOut io.Writer) (*Options, error) { ); err != nil { return nil, fmt.Errorf("error creating self-signed certificates: %v", err) } - o.RecommendedOptions.Admission.DisablePlugins = []string{"MutatingAdmissionWebhook", "NamespaceLifecycle", "ValidatingAdmissionWebhook", "ValidatingAdmissionPolicy"} + o.RecommendedOptions.Admission.DisablePlugins = []string{"MutatingAdmissionWebhook", "NamespaceLifecycle", "ValidatingAdmissionWebhook"} o.RecommendedOptions.Authorization.Modes = []string{"RBAC"} o.RecommendedOptions.ServerRun.CorsAllowedOriginList = []string{".*"} return o, nil @@ -97,8 +98,8 @@ func NewOptions(out, errOut io.Writer) (*Options, error) { func NewServerCommand(ctx context.Context) *cobra.Command { o, err := NewOptions(os.Stdout, os.Stderr) if err != nil { - klog.Background().Error(err, "Unable to initialize command options") - klog.FlushAndExit(klog.ExitFlushTimeout, 1) + klog.Error(err, "Unable to initialize command options") + klog.Flush() } expvar.Publish("CoreOptions", expvar.Func(func() interface{} { diff --git a/cmd/karpor/app/syncer.go b/cmd/karpor/app/syncer.go index 90b07dd4..6e34552c 100644 --- a/cmd/karpor/app/syncer.go +++ b/cmd/karpor/app/syncer.go @@ -16,22 +16,41 @@ package app import ( "context" + "crypto" + "crypto/x509" + + "k8s.io/klog/v2/klogr" "github.com/KusionStack/karpor/pkg/infra/search/storage/elasticsearch" "github.com/KusionStack/karpor/pkg/kubernetes/scheme" "github.com/KusionStack/karpor/pkg/syncer" + "github.com/KusionStack/karpor/pkg/util/certgenerator" + esclient "github.com/elastic/go-elasticsearch/v8" "github.com/spf13/cobra" "github.com/spf13/pflag" - "k8s.io/klog/v2" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/healthz" ) +const ( + defaultCertFile = "/etc/karpor/ca/ca.crt" + defaultKeyFile = "/etc/karpor/ca/ca.key" +) + type syncerOptions struct { + HighAvailability bool + OnlyPushMode bool + MetricsAddr string ProbeAddr string ElasticSearchAddresses []string + + ExternalEndpoint string + AgentImageTag string + + CaCertFile string + CaKeyFile string } func NewSyncerOptions() *syncerOptions { @@ -39,9 +58,17 @@ func NewSyncerOptions() *syncerOptions { } func (o *syncerOptions) AddFlags(fs *pflag.FlagSet) { + fs.BoolVar(&o.HighAvailability, "high-availability", false, "Whether to use high-availability feature.") + fs.BoolVar(&o.OnlyPushMode, "only-push-mode", false, "Only push mode in high availability feature.") + fs.StringVar(&o.MetricsAddr, "metrics-bind-address", ":8080", "The address the metric endpoint binds to.") fs.StringVar(&o.ProbeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.") fs.StringSliceVar(&o.ElasticSearchAddresses, "elastic-search-addresses", nil, "The elastic search address.") + fs.StringVar(&o.ExternalEndpoint, "external-addresses", "", "The external address that expose to user cluster in pull mode.") + fs.StringVar(&o.AgentImageTag, "agent-image-tag", "v0.0.0", "The agent image tag.") + + fs.StringVar(&o.CaCertFile, "ca-cert-file", defaultCertFile, "Root CA certificate file for karpor server.") + fs.StringVar(&o.CaKeyFile, "ca-key-file", defaultKeyFile, "Root KEY file for karpor server..") } func NewSyncerCommand(ctx context.Context) *cobra.Command { @@ -50,15 +77,15 @@ func NewSyncerCommand(ctx context.Context) *cobra.Command { Use: "syncer", Short: "start a resource syncer to sync resource from clusters", RunE: func(cmd *cobra.Command, args []string) error { - return run(ctx, options) + return runSyncer(ctx, options) }, } options.AddFlags(cmd.Flags()) return cmd } -func run(ctx context.Context, options *syncerOptions) error { - ctrl.SetLogger(klog.NewKlogr()) +func runSyncer(ctx context.Context, options *syncerOptions) error { + ctrl.SetLogger(klogr.New()) log := ctrl.Log.WithName("setup") mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ @@ -81,8 +108,18 @@ func run(ctx context.Context, options *syncerOptions) error { return err } + var caCert *x509.Certificate + var caKey crypto.Signer + if options.HighAvailability && !options.OnlyPushMode { + caCert, caKey, err = certgenerator.LoadCertificate(options.CaCertFile, options.CaKeyFile) + if err != nil { + log.Error(err, "unable to load certificate") + return err + } + } + //nolint:contextcheck - if err = syncer.NewSyncReconciler(es).SetupWithManager(mgr); err != nil { + if err = syncer.NewSyncReconciler(es, options.HighAvailability, options.ElasticSearchAddresses, options.ExternalEndpoint, options.AgentImageTag, caCert, caKey).SetupWithManager(mgr); err != nil { log.Error(err, "unable to create resource syncer") return err } diff --git a/cmd/karpor/main.go b/cmd/karpor/main.go index 748bcd08..b15ad996 100644 --- a/cmd/karpor/main.go +++ b/cmd/karpor/main.go @@ -17,11 +17,10 @@ limitations under the License. package main import ( - "os" + genericapiserver "k8s.io/apiserver/pkg/server" + "k8s.io/klog/v2" "github.com/KusionStack/karpor/cmd/karpor/app" - genericapiserver "k8s.io/apiserver/pkg/server" - "k8s.io/component-base/cli" ) // @title Karpor @@ -33,8 +32,12 @@ func main() { cmd := app.NewServerCommand(ctx) syncCmd := app.NewSyncerCommand(ctx) + agentCmd := app.NewAgentCommand(ctx) + cmd.AddCommand(syncCmd) + cmd.AddCommand(agentCmd) - code := cli.Run(cmd) - os.Exit(code) + if err := cmd.Execute(); err != nil { + klog.Fatal(err) + } } diff --git a/config/agent.tpl b/config/agent.tpl new file mode 100644 index 00000000..c20f68d6 --- /dev/null +++ b/config/agent.tpl @@ -0,0 +1,137 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: karpor +spec: + finalizers: + - kubernetes +{{- if eq .ClusterMode "pull" }} +--- +apiVersion: v1 +data: + config: |- + apiVersion: v1 + clusters: + - cluster: + insecure-skip-tls-verify: true + server: {{ .ExternalEndpoint }} + name: karpor + contexts: + - context: + cluster: karpor + user: {{ .ClusterName }} + name: default + current-context: default + kind: Config + users: + - name: {{ .ClusterName }} + user: + client-certificate-data: {{ .CaCert }} + client-key-data: {{ .CaKey }} +kind: ConfigMap +metadata: + name: karpor-kubeconfig + namespace: karpor +{{- end }} +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: karpor-agent + namespace: karpor +spec: + replicas: 1 + revisionHistoryLimit: 10 + selector: + matchLabels: + app.kubernetes.io/component: karpor-agent + app.kubernetes.io/instance: karpor + app.kubernetes.io/name: karpor + strategy: + rollingUpdate: + maxSurge: 25% + maxUnavailable: 25% + type: RollingUpdate + template: + metadata: + labels: + app.kubernetes.io/component: karpor-agent + app.kubernetes.io/instance: karpor + app.kubernetes.io/name: karpor + spec: + containers: + - args: + - agent + - --elastic-search-addresses={{ range .StorageAddresses }}{{.}} {{ end }} + - --cluster-name={{ .ClusterName }} + - --cluster-mode={{ .ClusterMode }} + command: + - /karpor +{{- if eq .ClusterMode "pull" }} + env: + - name: KUBECONFIG + value: /etc/karpor/config +{{- end }} + image: kusionstack/karpor:{{ .AgentImageTag }} + imagePullPolicy: IfNotPresent + name: karpor-agent + ports: + - containerPort: 7443 + protocol: TCP + resources: +{{- if eq .Level 3 }} + limits: + cpu: 1 + ephemeral-storage: 20Gi + memory: 2Gi + requests: + cpu: 500m + ephemeral-storage: 4Gi + memory: 512Mi +{{- else if eq .Level 2 }} + limits: + cpu: 500m + ephemeral-storage: 10Gi + memory: 1Gi + requests: + cpu: 250m + ephemeral-storage: 2Gi + memory: 256Mi +{{- else }} + limits: + cpu: 250m + ephemeral-storage: 5Gi + memory: 500Mi + requests: + cpu: 125m + ephemeral-storage: 1Gi + memory: 128Mi +{{- end }} +{{- if eq .ClusterMode "pull" }} + volumeMounts: + - mountPath: /etc/karpor/ + name: karpor-kubeconfig +{{- end }} + dnsPolicy: ClusterFirst + restartPolicy: Always + terminationGracePeriodSeconds: 30 +{{- if eq .ClusterMode "pull" }} + volumes: + - configMap: + defaultMode: 420 + name: karpor-kubeconfig + name: karpor-kubeconfig +{{- end }} +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: karpor +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: cluster-admin +subjects: +- kind: ServiceAccount + name: default + namespace: karpor diff --git a/config/crds/cluster.karpor.io_clusters.yaml b/config/crds/cluster.karpor.io_clusters.yaml new file mode 100644 index 00000000..435f8ea1 --- /dev/null +++ b/config/crds/cluster.karpor.io_clusters.yaml @@ -0,0 +1,139 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.17.1 + name: clusters.cluster.karpor.io +spec: + group: cluster.karpor.io + names: + kind: Cluster + listKind: ClusterList + plural: clusters + singular: cluster + scope: Cluster + versions: + - name: v1beta1 + schema: + openAPIV3Schema: + description: Cluster is an extension type to access a cluster + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + properties: + access: + properties: + caBundle: + format: byte + type: string + credential: + properties: + execConfig: + properties: + apiVersion: + type: string + args: + items: + type: string + type: array + command: + type: string + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + installHint: + type: string + interactiveMode: + type: string + provideClusterInfo: + type: boolean + required: + - args + - command + - env + - provideClusterInfo + type: object + serviceAccountToken: + type: string + type: + type: string + x509: + properties: + certificate: + format: byte + type: string + privateKey: + format: byte + type: string + required: + - certificate + - privateKey + type: object + required: + - type + type: object + endpoint: + type: string + insecure: + type: boolean + required: + - endpoint + type: object + description: + type: string + displayName: + type: string + finalized: + type: boolean + level: + description: cluster scale level, optional value 1, 2, 3, default + 1 + type: integer + mode: + type: string + provider: + type: string + required: + - access + - displayName + - level + - provider + type: object + status: + properties: + healthy: + type: boolean + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crds/search.karpor.io_syncregistries.yaml b/config/crds/search.karpor.io_syncregistries.yaml new file mode 100644 index 00000000..25421f72 --- /dev/null +++ b/config/crds/search.karpor.io_syncregistries.yaml @@ -0,0 +1,286 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.17.1 + name: syncregistries.search.karpor.io +spec: + group: search.karpor.io + names: + kind: SyncRegistry + listKind: SyncRegistryList + plural: syncregistries + singular: syncregistry + scope: Cluster + versions: + - name: v1beta1 + schema: + openAPIV3Schema: + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + properties: + clusterLabelSelector: + description: ClusterLabelSelector is used to filter the target clusters + that need to be synced from. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + clusters: + description: Clusters is the list of the target clusters to be be + synced from. + items: + type: string + type: array + syncResources: + items: + description: ResourceSyncRule is used to specify the way to sync + the specified resource + properties: + apiVersion: + description: APIVersion represents the group version of the + target resource. + type: string + maxConcurrent: + description: 'MaxConcurrent is the maximum number of workers + (default: 10)' + type: integer + namespace: + description: |- + Namespace specifies the namespace in which the ListWatch of the target resources is limited + to. + type: string + remainAfterDeleted: + description: RemainAfterDeleted indicates whether the resource + should remain in ES after being deleted in k8s. + type: boolean + resource: + description: Resource is the the target resource. + type: string + resyncPeriod: + description: ResynPeriod is the period to resync + type: string + selectors: + description: Selectors are used to filter the target resources + to sync. Multiple selectors are ORed. + items: + description: Selector represents a resource filter + properties: + fieldSelector: + description: |- + FieldSelector is a filter to select resources by fields. + If non-nil and non-empty, only the resource match this filter will be selected. + properties: + matchFields: + additionalProperties: + type: string + description: |- + MatchFields is a map of {field,value} pairs. A single {field,value} in the matchFields + map means that the specified field should have an exact match with the specified value. + Multiple entries are ANDed. + type: object + serverSupported: + description: |- + ServerSupported indicates whether the matchFields is supported by the API server. + If not supported, the client-side filtering will be utilized instead." + type: boolean + type: object + labelSelector: + description: |- + LabelSelector is a filter to select resources by labels. + If non-nil and non-empty, only the resource match this filter will be selected. + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + type: object + type: array + transform: + description: |- + Transform is the rule applied to the original resource to transform it to the desired target + resource. + properties: + type: + description: Type is the type of transformer. + type: string + valueTemplate: + description: ValueTemplate is the template of the input + data to be paased to the transformer + type: string + required: + - type + - valueTemplate + type: object + transformRefName: + description: TransformRefName is the name of the TransformRule + type: string + trim: + description: Trim defines the trimming strategy for the resources + of the current type. + properties: + retain: + description: Retain specifies which fields should be retained + after trimming. + properties: + jsonPaths: + description: |- + JSONPaths specifies the path of the field to be retained. + For usage, please refer to https://kubernetes.io/docs/reference/kubectl/jsonpath/ + items: + type: string + type: array + type: object + type: object + trimRefName: + description: TrimRefName is the name of the TrimRule. + type: string + required: + - apiVersion + - resource + type: object + type: array + syncResourcesRefName: + type: string + type: object + status: + properties: + clusters: + items: + properties: + cluster: + type: string + resources: + items: + properties: + apiVersion: + type: string + kind: + type: string + lastTransitionTime: + format: date-time + type: string + message: + type: string + reason: + type: string + status: + type: string + required: + - apiVersion + - kind + - lastTransitionTime + - status + type: object + type: array + status: + type: string + required: + - cluster + - status + type: object + type: array + lastTransitionTime: + format: date-time + type: string + required: + - lastTransitionTime + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crds/search.karpor.io_syncresources.yaml b/config/crds/search.karpor.io_syncresources.yaml new file mode 100644 index 00000000..bcfa063b --- /dev/null +++ b/config/crds/search.karpor.io_syncresources.yaml @@ -0,0 +1,188 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.17.1 + name: syncresources.search.karpor.io +spec: + group: search.karpor.io + names: + kind: SyncResources + listKind: SyncResourcesList + plural: syncresources + singular: syncresources + scope: Cluster + versions: + - name: v1beta1 + schema: + openAPIV3Schema: + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + properties: + syncResources: + items: + description: ResourceSyncRule is used to specify the way to sync + the specified resource + properties: + apiVersion: + description: APIVersion represents the group version of the + target resource. + type: string + maxConcurrent: + description: 'MaxConcurrent is the maximum number of workers + (default: 10)' + type: integer + namespace: + description: |- + Namespace specifies the namespace in which the ListWatch of the target resources is limited + to. + type: string + remainAfterDeleted: + description: RemainAfterDeleted indicates whether the resource + should remain in ES after being deleted in k8s. + type: boolean + resource: + description: Resource is the the target resource. + type: string + resyncPeriod: + description: ResynPeriod is the period to resync + type: string + selectors: + description: Selectors are used to filter the target resources + to sync. Multiple selectors are ORed. + items: + description: Selector represents a resource filter + properties: + fieldSelector: + description: |- + FieldSelector is a filter to select resources by fields. + If non-nil and non-empty, only the resource match this filter will be selected. + properties: + matchFields: + additionalProperties: + type: string + description: |- + MatchFields is a map of {field,value} pairs. A single {field,value} in the matchFields + map means that the specified field should have an exact match with the specified value. + Multiple entries are ANDed. + type: object + serverSupported: + description: |- + ServerSupported indicates whether the matchFields is supported by the API server. + If not supported, the client-side filtering will be utilized instead." + type: boolean + type: object + labelSelector: + description: |- + LabelSelector is a filter to select resources by labels. + If non-nil and non-empty, only the resource match this filter will be selected. + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + type: object + type: array + transform: + description: |- + Transform is the rule applied to the original resource to transform it to the desired target + resource. + properties: + type: + description: Type is the type of transformer. + type: string + valueTemplate: + description: ValueTemplate is the template of the input + data to be paased to the transformer + type: string + required: + - type + - valueTemplate + type: object + transformRefName: + description: TransformRefName is the name of the TransformRule + type: string + trim: + description: Trim defines the trimming strategy for the resources + of the current type. + properties: + retain: + description: Retain specifies which fields should be retained + after trimming. + properties: + jsonPaths: + description: |- + JSONPaths specifies the path of the field to be retained. + For usage, please refer to https://kubernetes.io/docs/reference/kubectl/jsonpath/ + items: + type: string + type: array + type: object + type: object + trimRefName: + description: TrimRefName is the name of the TrimRule. + type: string + required: + - apiVersion + - resource + type: object + type: array + type: object + type: object + served: true + storage: true diff --git a/config/crds/search.karpor.io_transformrules.yaml b/config/crds/search.karpor.io_transformrules.yaml new file mode 100644 index 00000000..811e3388 --- /dev/null +++ b/config/crds/search.karpor.io_transformrules.yaml @@ -0,0 +1,56 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.17.1 + name: transformrules.search.karpor.io +spec: + group: search.karpor.io + names: + kind: TransformRule + listKind: TransformRuleList + plural: transformrules + singular: transformrule + scope: Cluster + versions: + - name: v1beta1 + schema: + openAPIV3Schema: + description: |- + TransformRule is used to define the rule to transform the original resource into the desired + target resource. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + properties: + type: + description: Type is the type of transformer. + type: string + valueTemplate: + description: ValueTemplate is the template of the input data to be + paased to the transformer + type: string + required: + - type + - valueTemplate + type: object + type: object + served: true + storage: true diff --git a/config/crds/search.karpor.io_trimrules.yaml b/config/crds/search.karpor.io_trimrules.yaml new file mode 100644 index 00000000..72dbf84f --- /dev/null +++ b/config/crds/search.karpor.io_trimrules.yaml @@ -0,0 +1,58 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.17.1 + name: trimrules.search.karpor.io +spec: + group: search.karpor.io + names: + kind: TrimRule + listKind: TrimRuleList + plural: trimrules + singular: trimrule + scope: Cluster + versions: + - name: v1beta1 + schema: + openAPIV3Schema: + description: |- + TrimRule defines the strategy of trimming k8s objects, which can save + informer memory by discarding redundant fields. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + properties: + retain: + description: Retain specifies which fields should be retained after + trimming. + properties: + jsonPaths: + description: |- + JSONPaths specifies the path of the field to be retained. + For usage, please refer to https://kubernetes.io/docs/reference/kubectl/jsonpath/ + items: + type: string + type: array + type: object + type: object + type: object + served: true + storage: true diff --git a/config/embed.go b/config/embed.go index 5c3ac5bb..98d453f6 100644 --- a/config/embed.go +++ b/config/embed.go @@ -32,3 +32,23 @@ var DefaultRelationship []byte //go:embed default-sync-strategy.yaml var DefaultSyncStrategy []byte + +var CrdList = [][]byte{ClustersCrd, SyncRegistriesCrd, SyncResourcesCrd, TransformRulesCrd, TrimRulesCrd} + +//go:embed crds/cluster.karpor.io_clusters.yaml +var ClustersCrd []byte + +//go:embed crds/search.karpor.io_syncregistries.yaml +var SyncRegistriesCrd []byte + +//go:embed crds/search.karpor.io_syncresources.yaml +var SyncResourcesCrd []byte + +//go:embed crds/search.karpor.io_transformrules.yaml +var TransformRulesCrd []byte + +//go:embed crds/search.karpor.io_trimrules.yaml +var TrimRulesCrd []byte + +//go:embed agent.tpl +var AgentTpl []byte diff --git a/docs/api.md b/docs/api.md index a1b0cb59..debd6b3d 100644 --- a/docs/api.md +++ b/docs/api.md @@ -46,6 +46,7 @@ Karpor is a brand new Kubernetes visualization tool that focuses on search, insi |---------|---------|--------|---------| | DELETE | /rest-api/v1/cluster/{clusterName} | [delete rest API v1 cluster cluster name](#delete-rest-api-v1-cluster-cluster-name) | Delete removes a cluster resource by name. | | GET | /rest-api/v1/cluster/{clusterName} | [get rest API v1 cluster cluster name](#get-rest-api-v1-cluster-cluster-name) | Get returns a cluster resource by name. | +| GET | /rest-api/v1/cluster/{clusterName}/agentYml | [get rest API v1 cluster cluster name agent yml](#get-rest-api-v1-cluster-cluster-name-agent-yml) | Get agent yaml | | GET | /rest-api/v1/clusters | [get rest API v1 clusters](#get-rest-api-v1-clusters) | List lists all cluster resources. | | POST | /rest-api/v1/cluster/{clusterName} | [post rest API v1 cluster cluster name](#post-rest-api-v1-cluster-cluster-name) | Create creates a cluster resource. | | POST | /rest-api/v1/cluster/config/file | [post rest API v1 cluster config file](#post-rest-api-v1-cluster-config-file) | Upload kubeConfig file for cluster | @@ -657,6 +658,94 @@ Status: Internal Server Error +### Get agent yaml (*GetRestAPIV1ClusterClusterNameAgentYml*) + +``` +GET /rest-api/v1/cluster/{clusterName}/agentYml +``` + +Obtain the agent yaml in secret for cluster. + +#### Consumes + * application/json + * text/plain + +#### Produces + * application/json + +#### Parameters + +| Name | Source | Type | Go type | Separator | Required | Default | Description | +|------|--------|------|---------|-----------| :------: |---------|-------------| +| clusterName | `path` | string | `string` | | ✓ | | The name of the cluster | + +#### All responses +| Code | Status | Description | Has headers | Schema | +|------|--------|-------------|:-----------:|--------| +| [200](#get-rest-api-v1-cluster-cluster-name-agent-yml-200) | OK | Verification passed server version | | [schema](#get-rest-api-v1-cluster-cluster-name-agent-yml-200-schema) | +| [400](#get-rest-api-v1-cluster-cluster-name-agent-yml-400) | Bad Request | Bad Request | | [schema](#get-rest-api-v1-cluster-cluster-name-agent-yml-400-schema) | +| [401](#get-rest-api-v1-cluster-cluster-name-agent-yml-401) | Unauthorized | Unauthorized | | [schema](#get-rest-api-v1-cluster-cluster-name-agent-yml-401-schema) | +| [404](#get-rest-api-v1-cluster-cluster-name-agent-yml-404) | Not Found | Not Found | | [schema](#get-rest-api-v1-cluster-cluster-name-agent-yml-404-schema) | +| [429](#get-rest-api-v1-cluster-cluster-name-agent-yml-429) | Too Many Requests | Too Many Requests | | [schema](#get-rest-api-v1-cluster-cluster-name-agent-yml-429-schema) | +| [500](#get-rest-api-v1-cluster-cluster-name-agent-yml-500) | Internal Server Error | Internal Server Error | | [schema](#get-rest-api-v1-cluster-cluster-name-agent-yml-500-schema) | + +#### Responses + + +##### 200 - Verification passed server version +Status: OK + +###### Schema + + + + + +##### 400 - Bad Request +Status: Bad Request + +###### Schema + + + + + +##### 401 - Unauthorized +Status: Unauthorized + +###### Schema + + + + + +##### 404 - Not Found +Status: Not Found + +###### Schema + + + + + +##### 429 - Too Many Requests +Status: Too Many Requests + +###### Schema + + + + + +##### 500 - Internal Server Error +Status: Internal Server Error + +###### Schema + + + + + ### List lists all cluster resources. (*GetRestAPIV1Clusters*) ``` @@ -2228,6 +2317,8 @@ Uploads a KubeConfig file for cluster, with a maximum size of 2MB. | Name | Source | Type | Go type | Separator | Required | Default | Description | |------|--------|------|---------|-----------| :------: |---------|-------------| +| clusterLevel | `formData` | integer | `int64` | | ✓ | | cluster scale level | +| clusterMode | `formData` | string | `string` | | ✓ | | cluster mode | | description | `formData` | string | `string` | | ✓ | | cluster description | | displayName | `formData` | string | `string` | | ✓ | | cluster display name | | file | `formData` | file | `io.ReadCloser` | | ✓ | | Upload file with field name 'file' | @@ -2801,6 +2892,8 @@ Status: Internal Server Error | Name | Type | Go type | Required | Default | Description | Example | |------|------|---------|:--------:| ------- |-------------|---------| +| clusterLevel | integer| `int64` | | | clusterLevel is the scale level of cluster to be created | | +| clusterMode | string| `string` | | | ClusterMode is the mode of cluster to be created | | | description | string| `string` | | | ClusterDescription is the description of cluster to be created | | | displayName | string| `string` | | | ClusterDisplayName is the display name of cluster to be created | | | kubeConfig | string| `string` | | | ClusterKubeConfig is the kubeconfig of cluster to be created | | @@ -2924,10 +3017,16 @@ Status: Internal Server Error | Name | Type | Go type | Required | Default | Description | Example | |------|------|---------|:--------:| ------- |-------------|---------| -| issuesTotal | integer| `int64` | | | IssuesTotal is the total count of all issues found during the audit.
This count can be used to understand the overall number of problems
that need to be addressed. | | +| issuesTotal | integer| `int64` | | | IssuesTotal is the total count of all issues found during the audit. +This count can be used to understand the overall number of problems +that need to be addressed. | | | resourceTotal | integer| `int64` | | | ResourceTotal is the count of unique resources audited during the scan. | | -| score | number| `float64` | | | Score represents the calculated score of the audited manifest based on
the number and severity of issues. It provides a quantitative measure
of the security posture of the resources in the manifest. | | -| severityStatistic | map of integer| `map[string]int64` | | | SeverityStatistic is a mapping of severity levels to their respective
number of occurrences. It allows for a quick overview of the distribution
of issues across different severity categories. | | +| score | number| `float64` | | | Score represents the calculated score of the audited manifest based on +the number and severity of issues. It provides a quantitative measure +of the security posture of the resources in the manifest. | | +| severityStatistic | map of integer| `map[string]int64` | | | SeverityStatistic is a mapping of severity levels to their respective +number of occurrences. It allows for a quick overview of the distribution +of issues across different severity categories. | | @@ -3010,5 +3109,8 @@ Status: Internal Server Error | Name | Type | Go type | Required | Default | Description | Example | |------|------|---------|:--------:| ------- |-------------|---------| -| object | [interface{}](#interface)| `interface{}` | | | Object is a JSON compatible map with string, float, int, bool, []interface{}, or
map[string]interface{}
children. | | +| object | [interface{}](#interface)| `interface{}` | | | Object is a JSON compatible map with string, float, int, bool, []interface{}, or +map[string]interface{} +children. | | + diff --git a/docs/cli/karpor.md b/docs/cli/karpor.md index 41c5e576..f3a2b2a1 100644 --- a/docs/cli/karpor.md +++ b/docs/cli/karpor.md @@ -15,10 +15,14 @@ karpor [flags] ``` --admission-control-config-file string File with admission control configuration. --advertise-address ip The IP address on which to advertise the apiserver to members of the cluster. This address must be reachable by the rest of the cluster. If blank, the --bind-address will be used. If --bind-address is unspecified, the host's default interface will be used. - --ai-auth-token string The ai auth token (same as api key) + --ai-auth-token string The ai auth token --ai-backend string The ai backend (default "openai") --ai-base-url string The ai base url + --ai-http-proxy string The ai http proxy + --ai-https-proxy string The ai https proxy --ai-model string The ai model (default "gpt-3.5-turbo") + --ai-no-proxy string The ai no-proxy + --ai-proxy-enabled The ai proxy enable --ai-temperature float32 The ai temperature (default 1) --ai-top-p float32 The ai top-p (default 1) --anonymous-auth Enables anonymous requests to the secure port of the API server. Requests that are not rejected by another authentication method are treated as anonymous requests. Anonymous requests have a username of system:anonymous, and a group name of system:unauthenticated. (default true) @@ -32,7 +36,7 @@ karpor [flags] --audit-log-compress If set, the rotated log files will be compressed using gzip. --audit-log-format string Format of saved audits. "legacy" indicates 1-line text format for each event. "json" indicates structured json format. Known formats are legacy,json. (default "json") --audit-log-maxage int The maximum number of days to retain old audit log files based on the timestamp encoded in their filename. - --audit-log-maxbackup int The maximum number of old audit log files to retain. Setting a value of 0 will mean there's no restriction on the number of files. + --audit-log-maxbackup int The maximum number of old audit log files to retain. --audit-log-maxsize int The maximum size in megabytes of the audit log file before it gets rotated. --audit-log-mode string Strategy for sending audit events. Blocking indicates sending events should block server responses. Batch causes the backend to buffer and write events asynchronously. Known modes are batch,blocking,blocking-strict. (default "blocking") --audit-log-path string If set, all requests coming to the apiserver will be logged to this file. '-' means standard out. @@ -65,18 +69,19 @@ karpor [flags] --client-ca-file string If set, any request presenting a client certificate signed by one of the authorities in the client-ca-file is authenticated with an identity corresponding to the CommonName of the client certificate. --contention-profiling Enable lock contention profiling, if profiling is enabled --cors-allowed-origins strings List of allowed origins for CORS, comma separated. An allowed origin can be a regular expression to support subdomain matching. If this list is empty CORS will not be enabled. (default [.*]) + --default-watch-cache-size int Default watch cache size. If zero, watch cache will be disabled for resources that do not have a default watch size set. (default 100) --delete-collection-workers int Number of workers spawned for DeleteCollection call. These are used to speed up namespace cleanup. (default 1) - --disable-admission-plugins strings admission plugins that should be disabled although they are in the default enabled plugins list (NamespaceLifecycle, MutatingAdmissionWebhook, ValidatingAdmissionPolicy, ValidatingAdmissionWebhook). Comma-delimited list of admission plugins: MutatingAdmissionWebhook, NamespaceLifecycle, ValidatingAdmissionPolicy, ValidatingAdmissionWebhook. The order of plugins in this flag does not matter. (default [MutatingAdmissionWebhook,NamespaceLifecycle,ValidatingAdmissionWebhook,ValidatingAdmissionPolicy]) + --disable-admission-plugins strings admission plugins that should be disabled although they are in the default enabled plugins list (NamespaceLifecycle, MutatingAdmissionWebhook, ValidatingAdmissionWebhook). Comma-delimited list of admission plugins: MutatingAdmissionWebhook, NamespaceLifecycle, ValidatingAdmissionWebhook. The order of plugins in this flag does not matter. (default [MutatingAdmissionWebhook,NamespaceLifecycle,ValidatingAdmissionWebhook]) --egress-selector-config-file string File with apiserver egress selector configuration. --elastic-search-addresses strings The elastic search address --elastic-search-password string The elastic search password --elastic-search-username string The elastic search username - --enable-admission-plugins strings admission plugins that should be enabled in addition to default enabled ones (NamespaceLifecycle, MutatingAdmissionWebhook, ValidatingAdmissionPolicy, ValidatingAdmissionWebhook). Comma-delimited list of admission plugins: MutatingAdmissionWebhook, NamespaceLifecycle, ValidatingAdmissionPolicy, ValidatingAdmissionWebhook. The order of plugins in this flag does not matter. + --enable-admission-plugins strings admission plugins that should be enabled in addition to default enabled ones (NamespaceLifecycle, MutatingAdmissionWebhook, ValidatingAdmissionWebhook). Comma-delimited list of admission plugins: MutatingAdmissionWebhook, NamespaceLifecycle, ValidatingAdmissionWebhook. The order of plugins in this flag does not matter. + --enable-ai whether to enable llm. --enable-garbage-collector Enables the generic garbage collector. MUST be synced with the corresponding flag of the kube-controller-manager. (default true) --enable-priority-and-fairness If true and the APIPriorityAndFairness feature gate is enabled, replace the max-in-flight handler with an enhanced one that queues and dispatches with priority and fairness (default true) --enable-rbac trun on to enable RBAC authorization --encryption-provider-config string The file containing configuration for encryption providers to be used for storing secrets in etcd - --encryption-provider-config-automatic-reload Determines if the file set by --encryption-provider-config should be automatically reloaded if the disk contents change. Setting this to true disables the ability to uniquely identify distinct KMS plugins via the API server healthz endpoints. --etcd-cafile string SSL Certificate Authority file used to secure etcd communication. --etcd-certfile string SSL certification file used to secure etcd communication. --etcd-compaction-interval duration The interval of compaction requests. If 0, the compaction request from apiserver is disabled. (default 5m0s) @@ -85,7 +90,6 @@ karpor [flags] --etcd-healthcheck-timeout duration The timeout to use when checking etcd health. (default 2s) --etcd-keyfile string SSL key file used to secure etcd communication. --etcd-prefix string The prefix to prepend to all resource paths in etcd. (default "/registry/karpor") - --etcd-readycheck-timeout duration The timeout to use when checking etcd readiness (default 2s) --etcd-servers strings List of etcd servers to connect with (scheme://ip:port), comma separated. --etcd-servers-overrides strings Per-resource etcd servers overrides, comma separated. The individual override format: group/resource#servers, where servers are URLs, semicolon separated. Note that this applies only to resources compiled into this server binary. --external-hostname string The hostname to use when generating externalized URLs for this master (e.g. Swagger API Docs or OpenID Discovery). @@ -93,113 +97,109 @@ karpor [flags] APIListChunking=true|false (BETA - default=true) APIPriorityAndFairness=true|false (BETA - default=true) APIResponseCompression=true|false (BETA - default=true) - APISelfSubjectReview=true|false (ALPHA - default=false) - APIServerIdentity=true|false (BETA - default=true) + APIServerIdentity=true|false (ALPHA - default=false) APIServerTracing=true|false (ALPHA - default=false) - AggregatedDiscoveryEndpoint=true|false (ALPHA - default=false) AllAlpha=true|false (ALPHA - default=false) AllBeta=true|false (BETA - default=false) - AnyVolumeDataSource=true|false (BETA - default=true) + AnyVolumeDataSource=true|false (ALPHA - default=false) AppArmor=true|false (BETA - default=true) - CPUManagerPolicyAlphaOptions=true|false (ALPHA - default=false) - CPUManagerPolicyBetaOptions=true|false (BETA - default=true) - CPUManagerPolicyOptions=true|false (BETA - default=true) - CSIMigrationPortworx=true|false (BETA - default=false) - CSIMigrationRBD=true|false (ALPHA - default=false) - CSINodeExpandSecret=true|false (ALPHA - default=false) + CPUManager=true|false (BETA - default=true) + CPUManagerPolicyOptions=true|false (ALPHA - default=false) + CSIInlineVolume=true|false (BETA - default=true) + CSIMigration=true|false (BETA - default=true) + CSIMigrationAWS=true|false (BETA - default=false) + CSIMigrationAzureDisk=true|false (BETA - default=false) + CSIMigrationAzureFile=true|false (BETA - default=false) + CSIMigrationGCE=true|false (BETA - default=false) + CSIMigrationOpenStack=true|false (BETA - default=true) + CSIMigrationvSphere=true|false (BETA - default=false) + CSIStorageCapacity=true|false (BETA - default=true) + CSIVolumeFSGroupPolicy=true|false (BETA - default=true) CSIVolumeHealth=true|false (ALPHA - default=false) - ComponentSLIs=true|false (ALPHA - default=false) - ContainerCheckpoint=true|false (ALPHA - default=false) - CronJobTimeZone=true|false (BETA - default=true) - CrossNamespaceVolumeDataSource=true|false (ALPHA - default=false) + CSRDuration=true|false (BETA - default=true) + ConfigurableFSGroupPolicy=true|false (BETA - default=true) + ControllerManagerLeaderMigration=true|false (BETA - default=true) CustomCPUCFSQuotaPeriod=true|false (ALPHA - default=false) - CustomResourceValidationExpressions=true|false (BETA - default=true) + DaemonSetUpdateSurge=true|false (BETA - default=true) + DefaultPodTopologySpread=true|false (BETA - default=true) + DelegateFSGroupToCSIDriver=true|false (ALPHA - default=false) + DevicePlugins=true|false (BETA - default=true) + DisableAcceleratorUsageMetrics=true|false (BETA - default=true) DisableCloudProviders=true|false (ALPHA - default=false) - DisableKubeletCloudCredentialProviders=true|false (ALPHA - default=false) - DownwardAPIHugePages=true|false (BETA - default=true) - DynamicResourceAllocation=true|false (ALPHA - default=false) - EventedPLEG=true|false (ALPHA - default=false) - ExpandedDNSConfig=true|false (BETA - default=true) + DownwardAPIHugePages=true|false (BETA - default=false) + EfficientWatchResumption=true|false (BETA - default=true) + EndpointSliceTerminatingCondition=true|false (BETA - default=true) + EphemeralContainers=true|false (ALPHA - default=false) + ExpandCSIVolumes=true|false (BETA - default=true) + ExpandInUsePersistentVolumes=true|false (BETA - default=true) + ExpandPersistentVolumes=true|false (BETA - default=true) + ExpandedDNSConfig=true|false (ALPHA - default=false) ExperimentalHostUserNamespaceDefaulting=true|false (BETA - default=false) - GRPCContainerProbe=true|false (BETA - default=true) + GenericEphemeralVolume=true|false (BETA - default=true) GracefulNodeShutdown=true|false (BETA - default=true) - GracefulNodeShutdownBasedOnPodPriority=true|false (BETA - default=true) HPAContainerMetrics=true|false (ALPHA - default=false) HPAScaleToZero=true|false (ALPHA - default=false) - HonorPVReclaimPolicy=true|false (ALPHA - default=false) - IPTablesOwnershipCleanup=true|false (ALPHA - default=false) + IPv6DualStack=true|false (BETA - default=true) InTreePluginAWSUnregister=true|false (ALPHA - default=false) InTreePluginAzureDiskUnregister=true|false (ALPHA - default=false) InTreePluginAzureFileUnregister=true|false (ALPHA - default=false) InTreePluginGCEUnregister=true|false (ALPHA - default=false) InTreePluginOpenStackUnregister=true|false (ALPHA - default=false) - InTreePluginPortworxUnregister=true|false (ALPHA - default=false) - InTreePluginRBDUnregister=true|false (ALPHA - default=false) InTreePluginvSphereUnregister=true|false (ALPHA - default=false) - JobMutableNodeSchedulingDirectives=true|false (BETA - default=true) - JobPodFailurePolicy=true|false (BETA - default=true) - JobReadyPods=true|false (BETA - default=true) - KMSv2=true|false (ALPHA - default=false) + IndexedJob=true|false (BETA - default=true) + IngressClassNamespacedParams=true|false (BETA - default=true) + JobTrackingWithFinalizers=true|false (ALPHA - default=false) + KubeletCredentialProviders=true|false (ALPHA - default=false) KubeletInUserNamespace=true|false (ALPHA - default=false) KubeletPodResources=true|false (BETA - default=true) - KubeletPodResourcesGetAllocatable=true|false (BETA - default=true) - KubeletTracing=true|false (ALPHA - default=false) - LegacyServiceAccountTokenTracking=true|false (ALPHA - default=false) + KubeletPodResourcesGetAllocatable=true|false (ALPHA - default=false) + LocalStorageCapacityIsolation=true|false (BETA - default=true) LocalStorageCapacityIsolationFSQuotaMonitoring=true|false (ALPHA - default=false) LogarithmicScaleDown=true|false (BETA - default=true) - MatchLabelKeysInPodTopologySpread=true|false (ALPHA - default=false) - MaxUnavailableStatefulSet=true|false (ALPHA - default=false) MemoryManager=true|false (BETA - default=true) MemoryQoS=true|false (ALPHA - default=false) - MinDomainsInPodTopologySpread=true|false (BETA - default=false) - MinimizeIPTablesRestore=true|false (ALPHA - default=false) - MultiCIDRRangeAllocator=true|false (ALPHA - default=false) - NetworkPolicyStatus=true|false (ALPHA - default=false) - NodeInclusionPolicyInPodTopologySpread=true|false (BETA - default=true) - NodeOutOfServiceVolumeDetach=true|false (BETA - default=true) + MixedProtocolLBService=true|false (ALPHA - default=false) + NetworkPolicyEndPort=true|false (BETA - default=true) NodeSwap=true|false (ALPHA - default=false) - OpenAPIEnums=true|false (BETA - default=true) - OpenAPIV3=true|false (BETA - default=true) - PDBUnhealthyPodEvictionPolicy=true|false (ALPHA - default=false) - PodAndContainerStatsFromCRI=true|false (ALPHA - default=false) + NonPreemptingPriority=true|false (BETA - default=true) + PodAffinityNamespaceSelector=true|false (BETA - default=true) PodDeletionCost=true|false (BETA - default=true) - PodDisruptionConditions=true|false (BETA - default=true) - PodHasNetworkCondition=true|false (ALPHA - default=false) - PodSchedulingReadiness=true|false (ALPHA - default=false) - ProbeTerminationGracePeriod=true|false (BETA - default=true) + PodOverhead=true|false (BETA - default=true) + PodSecurity=true|false (ALPHA - default=false) + PreferNominatedNode=true|false (BETA - default=true) + ProbeTerminationGracePeriod=true|false (BETA - default=false) ProcMountType=true|false (ALPHA - default=false) - ProxyTerminatingEndpoints=true|false (BETA - default=true) + ProxyTerminatingEndpoints=true|false (ALPHA - default=false) QOSReserved=true|false (ALPHA - default=false) ReadWriteOncePod=true|false (ALPHA - default=false) - RecoverVolumeExpansionFailure=true|false (ALPHA - default=false) RemainingItemCount=true|false (BETA - default=true) - RetroactiveDefaultStorageClass=true|false (BETA - default=true) + RemoveSelfLink=true|false (BETA - default=true) RotateKubeletServerCertificate=true|false (BETA - default=true) - SELinuxMountReadWriteOncePod=true|false (ALPHA - default=false) - SeccompDefault=true|false (BETA - default=true) - ServerSideFieldValidation=true|false (BETA - default=true) + SeccompDefault=true|false (ALPHA - default=false) + ServiceInternalTrafficPolicy=true|false (BETA - default=true) + ServiceLBNodePortControl=true|false (BETA - default=true) + ServiceLoadBalancerClass=true|false (BETA - default=true) SizeMemoryBackedVolumes=true|false (BETA - default=true) - StatefulSetAutoDeletePVC=true|false (ALPHA - default=false) - StatefulSetStartOrdinal=true|false (ALPHA - default=false) + StatefulSetMinReadySeconds=true|false (ALPHA - default=false) StorageVersionAPI=true|false (ALPHA - default=false) StorageVersionHash=true|false (BETA - default=true) - TopologyAwareHints=true|false (BETA - default=true) + SuspendJob=true|false (BETA - default=true) + TTLAfterFinished=true|false (BETA - default=true) + TopologyAwareHints=true|false (ALPHA - default=false) TopologyManager=true|false (BETA - default=true) - TopologyManagerPolicyAlphaOptions=true|false (ALPHA - default=false) - TopologyManagerPolicyBetaOptions=true|false (BETA - default=false) - TopologyManagerPolicyOptions=true|false (ALPHA - default=false) - UserNamespacesStatelessPodsSupport=true|false (ALPHA - default=false) - ValidatingAdmissionPolicy=true|false (ALPHA - default=false) VolumeCapacityPriority=true|false (ALPHA - default=false) WinDSR=true|false (ALPHA - default=false) WinOverlay=true|false (BETA - default=true) - WindowsHostNetwork=true|false (ALPHA - default=true) (default APIPriorityAndFairness=true) + WindowsHostProcessContainers=true|false (ALPHA - default=false) (default APIPriorityAndFairness=true) --github-badge whether to display the github badge --goaway-chance float To prevent HTTP/2 clients from getting stuck on a single apiserver, randomly close a connection (GOAWAY). The client's other in-flight requests won't be affected, and the client will reconnect, likely landing on a different apiserver after going through the load balancer again. This argument sets the fraction of requests that will be sent a GOAWAY. Clusters with single apiservers, or which don't use a load balancer, should NOT enable this. Min is 0 (off), Max is .02 (1/50 requests); .001 (1/1000) is a recommended starting point. -h, --help help for karpor + --high-availability whether to use high-availability feature. --http2-max-streams-per-connection int The limit that the server gives to clients for the maximum number of streams in an HTTP/2 connection. Zero means to use golang's default. (default 1000) --lease-reuse-duration-seconds int The time in seconds that each lease is reused. A lower value could avoid large number of objects reusing the same lease. Notice that a too small value may cause performance problems at storage layer. (default 60) --livez-grace-period duration This option represents the maximum amount of time it should take for apiserver to complete its startup sequence and become live. From apiserver's start time to when this amount of time has elapsed, /livez will assume that unfinished post-start hooks will complete successfully and therefore return true. + --log-flush-frequency duration Maximum number of seconds between log flushes (default 5s) + --master-service-namespace string DEPRECATED: the namespace from which the Kubernetes master services should be injected into pods. (default "default") --max-mutating-requests-inflight int This and --max-requests-inflight are summed to determine the server's total concurrency limit (which must be positive) if --enable-priority-and-fairness is true. Otherwise, this flag limits the maximum number of mutating requests in flight, or a zero value disables the limit completely. (default 200) --max-requests-inflight int This and --max-mutating-requests-inflight are summed to determine the server's total concurrency limit (which must be positive) if --enable-priority-and-fairness is true. Otherwise, this flag limits the maximum number of non-mutating requests in flight, or a zero value disables the limit completely. (default 400) --min-request-timeout int An optional field indicating the minimum number of seconds a handler must keep a request open before timing it out. Currently only honored by the watch request handler, which picks a randomized value above this number as the connection timeout, to spread out load. (default 1800) @@ -217,31 +217,30 @@ karpor [flags] --secure-port int The port on which to serve HTTPS with authentication and authorization. If 0, don't serve HTTPS at all. (default 443) --service-account-extend-token-expiration Turns on projected service account expiration extension during token generation, which helps safe transition from legacy token to bound service account token feature. If this flag is enabled, admission injected tokens would be extended up to 1 year to prevent unexpected failure during transition, ignoring value of service-account-max-token-expiration. (default true) --service-account-issuer stringArray Identifier of the service account token issuer. The issuer will assert this identifier in "iss" claim of issued tokens. This value is a string or URI. If this option is not a valid URI per the OpenID Discovery 1.0 spec, the ServiceAccountIssuerDiscovery feature will remain disabled, even if the feature gate is set to true. It is highly recommended that this value comply with the OpenID spec: https://openid.net/specs/openid-connect-discovery-1_0.html. In practice, this means that service-account-issuer must be an https URL. It is also highly recommended that this URL be capable of serving OpenID discovery documents at {service-account-issuer}/.well-known/openid-configuration. When this flag is specified multiple times, the first is used to generate tokens and all are used to determine which issuers are accepted. - --service-account-jwks-uri string Overrides the URI for the JSON Web Key Set in the discovery doc served at /.well-known/openid-configuration. This flag is useful if the discovery docand key set are served to relying parties from a URL other than the API server's external (as auto-detected or overridden with external-hostname). - --service-account-key-file stringArray File containing PEM-encoded x509 RSA or ECDSA private or public keys, used to verify ServiceAccount tokens. The specified file can contain multiple keys, and the flag can be specified multiple times with different files. If unspecified, --tls-private-key-file is used. Must be specified when --service-account-signing-key-file is provided + --service-account-jwks-uri string Overrides the URI for the JSON Web Key Set in the discovery doc served at /.well-known/openid-configuration. This flag is useful if the discovery docand key set are served to relying parties from a URL other than the API server's external (as auto-detected or overridden with external-hostname). Only valid if the ServiceAccountIssuerDiscovery feature gate is enabled. + --service-account-key-file stringArray File containing PEM-encoded x509 RSA or ECDSA private or public keys, used to verify ServiceAccount tokens. The specified file can contain multiple keys, and the flag can be specified multiple times with different files. If unspecified, --tls-private-key-file is used. Must be specified when --service-account-signing-key is provided --service-account-lookup If true, validate ServiceAccount tokens exist in etcd as part of authentication. (default true) --service-account-max-token-expiration duration The maximum validity duration of a token created by the service account token issuer. If an otherwise valid TokenRequest with a validity duration larger than this value is requested, a token will be issued with a validity duration of this value. --service-account-signing-key-file string Path to the file that contains the current private key of the service account token issuer. The issuer will sign issued ID tokens with this private key. --shutdown-delay-duration duration Time to delay the termination. During that time the server keeps serving requests normally. The endpoints /healthz and /livez will return success, but /readyz immediately returns failure. Graceful termination starts after this delay has elapsed. This can be used to allow load balancer to stop sending traffic to this server. - --shutdown-send-retry-after If true the HTTP Server will continue listening until all non long running request(s) in flight have been drained, during this window all incoming requests will be rejected with a status code 429 and a 'Retry-After' response header, in addition 'Connection: close' response header is set in order to tear down the TCP connection when idle. --storage-backend string The storage backend for persistence. Options: 'etcd3' (default). - --storage-media-type string The media type to use to store objects in storage. Some resources or storage backends may only support a specific media type and will ignore this setting. Supported media types: [application/json, application/yaml, application/vnd.kubernetes.protobuf] (default "application/json") + --storage-media-type string The media type to use to store objects in storage. Some resources or storage backends may only support a specific media type and will ignore this setting. (default "application/json") --strict-transport-security-directives strings List of directives for HSTS, comma separated. If this list is empty, then HSTS directives will not be added. Example: 'max-age=31536000,includeSubDomains,preload' --tls-cert-file string File containing the default x509 Certificate for HTTPS. (CA cert, if any, concatenated after server cert). If HTTPS serving is enabled, and --tls-cert-file and --tls-private-key-file are not provided, a self-signed certificate and key are generated for the public address and saved to the directory specified by --cert-dir. (default "apiserver.local.config/certificates/apiserver.crt") --tls-cipher-suites strings Comma-separated list of cipher suites for the server. If omitted, the default Go cipher suites will be used. - Preferred values: TLS_AES_128_GCM_SHA256, TLS_AES_256_GCM_SHA384, TLS_CHACHA20_POLY1305_SHA256, TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305, TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256. - Insecure values: TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, TLS_ECDHE_ECDSA_WITH_RC4_128_SHA, TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, TLS_ECDHE_RSA_WITH_RC4_128_SHA, TLS_RSA_WITH_3DES_EDE_CBC_SHA, TLS_RSA_WITH_AES_128_CBC_SHA, TLS_RSA_WITH_AES_128_CBC_SHA256, TLS_RSA_WITH_AES_128_GCM_SHA256, TLS_RSA_WITH_AES_256_CBC_SHA, TLS_RSA_WITH_AES_256_GCM_SHA384, TLS_RSA_WITH_RC4_128_SHA. + Preferred values: TLS_AES_128_GCM_SHA256, TLS_AES_256_GCM_SHA384, TLS_CHACHA20_POLY1305_SHA256, TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305, TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, TLS_RSA_WITH_3DES_EDE_CBC_SHA, TLS_RSA_WITH_AES_128_CBC_SHA, TLS_RSA_WITH_AES_128_GCM_SHA256, TLS_RSA_WITH_AES_256_CBC_SHA, TLS_RSA_WITH_AES_256_GCM_SHA384. + Insecure values: TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, TLS_ECDHE_ECDSA_WITH_RC4_128_SHA, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, TLS_ECDHE_RSA_WITH_RC4_128_SHA, TLS_RSA_WITH_AES_128_CBC_SHA256, TLS_RSA_WITH_RC4_128_SHA. --tls-min-version string Minimum TLS version supported. Possible values: VersionTLS10, VersionTLS11, VersionTLS12, VersionTLS13 --tls-private-key-file string File containing the default x509 private key matching --tls-cert-file. (default "apiserver.local.config/certificates/apiserver.key") --tls-sni-cert-key namedCertKey A pair of x509 certificate and private key file paths, optionally suffixed with a list of domain patterns which are fully qualified domain names, possibly with prefixed wildcard segments. The domain patterns also allow IP addresses, but IPs should only be used if the apiserver has visibility to the IP address requested by a client. If no domain patterns are provided, the names of the certificate are extracted. Non-wildcard matches trump over wildcard matches, explicit domain patterns trump over extracted names. For multiple key/certificate pairs, use the --tls-sni-cert-key multiple times. Examples: "example.crt,example.key" or "foo.crt,foo.key:*.foo.com,foo.com". (default []) --tracing-config-file string File with apiserver tracing configuration. -V, --version Print version and exit --watch-cache Enable watch caching in the apiserver (default true) - --watch-cache-sizes strings Watch cache size settings for some resources (pods, nodes, etc.), comma separated. The individual setting format: resource[.group]#size, where resource is lowercase plural (no version), group is omitted for resources of apiVersion v1 (the legacy core API) and included for others, and size is a number. This option is only meaningful for resources built into the apiserver, not ones defined by CRDs or aggregated from external servers, and is only consulted if the watch-cache is enabled. The only meaningful size setting to supply here is zero, which means to disable watch caching for the associated resource; all non-zero values are equivalent and mean to not disable watch caching for that resource + --watch-cache-sizes strings Watch cache size settings for some resources (pods, nodes, etc.), comma separated. The individual setting format: resource[.group]#size, where resource is lowercase plural (no version), group is omitted for resources of apiVersion v1 (the legacy core API) and included for others, and size is a number. It takes effect when watch-cache is enabled. Some resources (replicationcontrollers, endpoints, nodes, pods, services, apiservices.apiregistration.k8s.io) have system defaults set by heuristics, others default to default-watch-cache-size ``` ### SEE ALSO * [karpor syncer](karpor_syncer.md) - start a resource syncer to sync resource from clusters -###### Auto generated by spf13/cobra on 27-Nov-2024 +###### Auto generated by spf13/cobra on 12-Mar-2025 diff --git a/docs/cli/karpor_syncer.md b/docs/cli/karpor_syncer.md index ce2063d2..97cd7fe4 100644 --- a/docs/cli/karpor_syncer.md +++ b/docs/cli/karpor_syncer.md @@ -9,14 +9,26 @@ karpor syncer [flags] ### Options ``` + --agent-image-tag string The agent image tag. (default "v0.0.0") + --ca-cert-file string Root CA certificate file for karpor server. (default "/etc/karpor/ca/ca.crt") + --ca-key-file string Root KEY file for karpor server.. (default "/etc/karpor/ca/ca.key") --elastic-search-addresses strings The elastic search address. + --external-addresses string The external address that expose to user cluster in pull mode. --health-probe-bind-address string The address the probe endpoint binds to. (default ":8081") -h, --help help for syncer + --high-availability Whether to use high-availability feature. --metrics-bind-address string The address the metric endpoint binds to. (default ":8080") + --only-push-mode Only push mode in high availability feature. +``` + +### Options inherited from parent commands + +``` + --log-flush-frequency duration Maximum number of seconds between log flushes (default 5s) ``` ### SEE ALSO * [karpor](karpor.md) - Launch an API server -###### Auto generated by spf13/cobra on 27-Nov-2024 +###### Auto generated by spf13/cobra on 12-Mar-2025 diff --git a/go.mod b/go.mod index 2238ecf7..0b28a07e 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/dominikbraun/graph v0.23.0 github.com/elastic/go-elasticsearch/v8 v8.7.0 github.com/elliotxx/esquery v0.2.0-alpha.1 - github.com/elliotxx/kubeaudit v0.0.0-20240124033725-e70be1692249 + github.com/elliotxx/kubeaudit v0.0.0-20250218094432-a62ce515167e github.com/elliotxx/safe v1.0.0 github.com/evanphx/json-patch v4.12.0+incompatible github.com/go-chi/chi/v5 v5.0.10 @@ -34,31 +34,19 @@ require ( k8s.io/api v0.26.1 k8s.io/apiextensions-apiserver v0.26.1 k8s.io/apimachinery v0.26.1 - k8s.io/apiserver v0.26.1 - k8s.io/cli-runtime v0.26.1 + k8s.io/apiserver v0.22.2 k8s.io/client-go v0.26.1 - k8s.io/cloud-provider v0.26.1 - k8s.io/cluster-bootstrap v0.26.1 - k8s.io/code-generator v0.26.1 + k8s.io/cloud-provider v0.22.2 + k8s.io/code-generator v0.22.2 k8s.io/component-base v0.26.1 - k8s.io/component-helpers v0.26.1 - k8s.io/controller-manager v0.26.1 - k8s.io/cri-api v0.26.1 - k8s.io/csi-translation-lib v0.26.1 - k8s.io/dynamic-resource-allocation v0.26.1 + k8s.io/controller-manager v0.22.2 + k8s.io/csi-translation-lib v0.22.2 k8s.io/klog/v2 v2.80.1 - k8s.io/kms v0.26.1 - k8s.io/kube-controller-manager v0.26.1 k8s.io/kube-openapi v0.0.0-20230106171958-10e5f0effbd2 - k8s.io/kube-proxy v0.26.1 - k8s.io/kube-scheduler v0.26.1 - k8s.io/kubectl v0.26.1 - k8s.io/kubelet v0.26.1 - k8s.io/kubernetes v1.26.1 - k8s.io/legacy-cloud-providers v0.26.1 - k8s.io/metrics v0.26.1 - k8s.io/mount-utils v0.26.1 - k8s.io/pod-security-admission v0.26.1 + k8s.io/kubernetes v1.22.2 + k8s.io/metrics v0.22.2 + k8s.io/mount-utils v0.22.2 + k8s.io/pod-security-admission v0.22.2 k8s.io/utils v0.0.0-20221128185143-99ec85e7a448 sigs.k8s.io/controller-runtime v0.14.6 sigs.k8s.io/structured-merge-diff/v4 v4.2.3 @@ -66,30 +54,35 @@ require ( ) require ( + cloud.google.com/go v0.65.0 // indirect + github.com/Azure/go-autorest v14.2.0+incompatible // indirect + github.com/Azure/go-autorest/autorest v0.11.18 // indirect + github.com/Azure/go-autorest/autorest/adal v0.9.13 // indirect + github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect + github.com/Azure/go-autorest/logger v0.2.1 // indirect + github.com/Azure/go-autorest/tracing v0.6.0 // indirect github.com/KyleBanks/depth v1.2.1 // indirect github.com/Masterminds/goutils v1.1.1 // indirect github.com/Masterminds/semver/v3 v3.1.1 // indirect github.com/NYTimes/gziphandler v1.1.1 // indirect github.com/ajg/form v1.5.1 // indirect - github.com/antlr/antlr4/runtime/Go/antlr v1.4.10 // indirect github.com/beorn7/perks v1.0.1 // indirect - github.com/blang/semver/v4 v4.0.0 // indirect - github.com/cenkalti/backoff/v4 v4.1.3 // indirect + github.com/blang/semver v3.5.1+incompatible // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/coreos/go-oidc v2.1.0+incompatible // indirect github.com/coreos/go-semver v0.3.0 // indirect github.com/coreos/go-systemd/v22 v22.3.2 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect + github.com/cyphar/filepath-securejoin v0.2.2 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/docker/distribution v2.8.1+incompatible // indirect github.com/elastic/elastic-transport-go/v8 v8.2.0 // indirect github.com/elastic/go-elasticsearch/v7 v7.6.0 // indirect - github.com/emicklei/go-restful/v3 v3.9.0 // indirect - github.com/evanphx/json-patch/v5 v5.6.0 // indirect + github.com/emicklei/go-restful v2.9.5+incompatible // indirect github.com/fatih/structs v1.1.0 // indirect github.com/felixge/httpsnoop v1.0.3 // indirect + github.com/form3tech-oss/jwt-go v3.2.3+incompatible // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect - github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/jsonpointer v0.20.0 // indirect github.com/go-openapi/jsonreference v0.20.2 // indirect github.com/go-openapi/spec v0.20.11 // indirect @@ -97,12 +90,11 @@ require ( github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.2 // indirect - github.com/google/cel-go v0.12.6 // indirect - github.com/google/gnostic v0.5.7-v3refs // indirect github.com/google/go-cmp v0.5.9 // indirect + github.com/googleapis/gnostic v0.5.5 // indirect github.com/gopherjs/gopherjs v1.17.2 // indirect github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0 // indirect + github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect github.com/huandu/xstrings v1.3.2 // indirect github.com/imdario/mergo v0.3.12 // indirect github.com/inconshreveable/mousetrap v1.0.1 // indirect @@ -113,13 +105,12 @@ require ( github.com/matttproud/golang_protobuf_extensions v1.0.2 // indirect github.com/mitchellh/copystructure v1.1.1 // indirect github.com/mitchellh/reflectwalk v1.0.1 // indirect - github.com/moby/sys/mountinfo v0.6.2 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect github.com/opencontainers/go-digest v1.0.0 // indirect - github.com/opencontainers/selinux v1.10.0 // indirect + github.com/opencontainers/runc v1.0.2 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pquerna/cachecontrol v0.1.0 // indirect github.com/prometheus/client_golang v1.14.0 // indirect @@ -133,23 +124,24 @@ require ( github.com/smarty/assertions v1.16.0 // indirect github.com/smartystreets/goconvey v1.8.1 // indirect github.com/spf13/cast v1.3.1 // indirect - github.com/stoewer/go-strcase v1.2.0 // indirect github.com/stretchr/objx v0.5.0 // indirect github.com/swaggo/files/v2 v2.0.0 // indirect go.etcd.io/etcd/api/v3 v3.5.5 // indirect go.etcd.io/etcd/client/pkg/v3 v3.5.5 // indirect go.etcd.io/etcd/client/v3 v3.5.5 // indirect - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.35.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.35.0 // indirect - go.opentelemetry.io/otel v1.10.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.10.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.10.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.10.0 // indirect - go.opentelemetry.io/otel/metric v0.31.0 // indirect - go.opentelemetry.io/otel/sdk v1.10.0 // indirect - go.opentelemetry.io/otel/trace v1.10.0 // indirect - go.opentelemetry.io/proto/otlp v0.19.0 // indirect + go.opentelemetry.io/contrib v0.20.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.20.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.20.0 // indirect + go.opentelemetry.io/otel v0.20.0 // indirect + go.opentelemetry.io/otel/exporters/otlp v0.20.0 // indirect + go.opentelemetry.io/otel/metric v0.20.0 // indirect + go.opentelemetry.io/otel/sdk v0.20.0 // indirect + go.opentelemetry.io/otel/sdk/export/metric v0.20.0 // indirect + go.opentelemetry.io/otel/sdk/metric v0.20.0 // indirect + go.opentelemetry.io/otel/trace v0.20.0 // indirect + go.opentelemetry.io/proto/otlp v0.7.0 // indirect go.uber.org/atomic v1.7.0 // indirect + go.uber.org/goleak v1.2.0 // indirect go.uber.org/zap v1.24.0 // indirect golang.org/x/arch v0.12.0 // indirect golang.org/x/crypto v0.16.0 // indirect @@ -169,14 +161,44 @@ require ( gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect gopkg.in/square/go-jose.v2 v2.2.2 // indirect + k8s.io/cluster-bootstrap v0.0.0 // indirect + k8s.io/component-helpers v0.22.2 // indirect k8s.io/gengo v0.0.0-20220902162205-c0856e24416d // indirect + k8s.io/kubelet v0.0.0 // indirect sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.35 // indirect - sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect ) replace ( github.com/KusionStack/karpor/hack/cert-generator => ./hack/cert-generator // the below fixes the "go list -m all" execution - k8s.io/kube-aggregator => k8s.io/kube-aggregator v0.26.1 - k8s.io/sample-apiserver => k8s.io/sample-apiserver v0.26.1 + + github.com/go-logr/logr => github.com/go-logr/logr v0.4.0 + k8s.io/api => k8s.io/api v0.22.2 + k8s.io/apiextensions-apiserver => k8s.io/apiextensions-apiserver v0.22.2 + k8s.io/apimachinery => k8s.io/apimachinery v0.22.2 + k8s.io/apiserver => k8s.io/apiserver v0.22.2 + k8s.io/cli-runtime => k8s.io/cli-runtime v0.22.2 + k8s.io/client-go => k8s.io/client-go v0.22.2 + k8s.io/cloud-provider => k8s.io/cloud-provider v0.22.2 + k8s.io/cluster-bootstrap => k8s.io/cluster-bootstrap v0.22.2 + k8s.io/code-generator => k8s.io/code-generator v0.22.2 + k8s.io/component-base => k8s.io/component-base v0.22.2 + k8s.io/component-helpers => k8s.io/component-helpers v0.22.2 + k8s.io/controller-manager => k8s.io/controller-manager v0.22.2 + k8s.io/cri-api => k8s.io/cri-api v0.22.2 + k8s.io/csi-translation-lib => k8s.io/csi-translation-lib v0.22.2 + k8s.io/klog/v2 => k8s.io/klog/v2 v2.9.0 + k8s.io/kube-aggregator => k8s.io/kube-aggregator v0.22.2 + k8s.io/kube-controller-manager => k8s.io/kube-controller-manager v0.22.2 + k8s.io/kube-openapi => k8s.io/kube-openapi v0.0.0-20211109043538-20434351676c + k8s.io/kube-proxy => k8s.io/kube-proxy v0.22.2 + k8s.io/kube-scheduler => k8s.io/kube-scheduler v0.22.2 + k8s.io/kubectl => k8s.io/kubectl v0.22.2 + k8s.io/kubelet => k8s.io/kubelet v0.22.2 + k8s.io/legacy-cloud-providers => k8s.io/legacy-cloud-providers v0.22.2 + k8s.io/metrics => k8s.io/metrics v0.22.2 + k8s.io/mount-utils => k8s.io/mount-utils v0.22.2 + k8s.io/pod-security-admission => k8s.io/pod-security-admission v0.22.2 + k8s.io/sample-apiserver => k8s.io/sample-apiserver v0.22.2 + sigs.k8s.io/controller-runtime => sigs.k8s.io/controller-runtime v0.10.3 ) diff --git a/go.sum b/go.sum index a319e47f..ec95ebb1 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,4 @@ +bitbucket.org/bertimus9/systemstat v0.0.0-20180207000608-0eeff89b0690/go.mod h1:Ulb78X89vxKYgdL24HMTiXYHlyHEvruOj1ZPlqeNEZM= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= @@ -6,15 +7,15 @@ cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxK cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.51.0/go.mod h1:hWtGJ6gnXH+KgDv+V0zFGDvpi07n3z8ZNj3T1RW0Gcw= cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0 h1:Dg9iHVQfrhq82rUNu9ZxUDrJLaxFUe/HlCVaLyRruq8= cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= -cloud.google.com/go v0.97.0 h1:3DXvAyifywvq64LfkKaMOmkWPS1CikIQdMe2lY9vxU8= -cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= @@ -23,6 +24,7 @@ cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4g cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= @@ -33,73 +35,162 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20201218220906-28db891af037/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/Azure/azure-sdk-for-go v55.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= +github.com/Azure/go-ansiterm v0.0.0-20210608223527-2377c96fe795/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= +github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs= +github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= +github.com/Azure/go-autorest/autorest v0.11.18 h1:90Y4srNYrwOtAgVo3ndrQkTYn6kf1Eg/AjTFJ8Is2aM= +github.com/Azure/go-autorest/autorest v0.11.18/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA= +github.com/Azure/go-autorest/autorest/adal v0.9.13 h1:Mp5hbtOePIzM8pJVRa3YLrWWmZtoxRXqUEzCfJt3+/Q= +github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M= +github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw= +github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= +github.com/Azure/go-autorest/autorest/mocks v0.4.1 h1:K0laFcLE6VLTOwNgSxaGbUcLPuGXlNkbVvq4cW4nIHk= +github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= +github.com/Azure/go-autorest/autorest/to v0.4.0/go.mod h1:fE8iZBn7LQR7zH/9XU2NcPR4o9jEImooCeWJcYV/zLE= +github.com/Azure/go-autorest/autorest/validation v0.1.0/go.mod h1:Ha3z/SqBeaalWQvokg3NZAlQTalVMtOIAs1aGK7G6u8= +github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+ZtXWSmf4Tg= +github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= +github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo= +github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/GoogleCloudPlatform/k8s-cloud-provider v0.0.0-20200415212048-7901bc822317/go.mod h1:DF8FZRxMHMGv/vP2lQP6h+dYzzjpuRn24VeRiYn3qjQ= +github.com/JeffAshton/win_pdh v0.0.0-20161109143554-76bb4ee9f0ab/go.mod h1:3VYc5hodBMJ5+l/7J4xAyMeuM2PNuepvHlGs8yilUCA= github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= +github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd/go.mod h1:64YHyfSL2R96J44Nlwm39UHepQbyR5q10x7iYa1ks2E= github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= github.com/Masterminds/sprig/v3 v3.2.2 h1:17jRggJu518dr3QaafizSXOjKYp94wKfABxUmyxvxX8= github.com/Masterminds/sprig/v3 v3.2.2/go.mod h1:UoaO7Yp8KlPnJIYWTFkMaqPUYKTfGFPhxNuwnnxkKlk= +github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw= +github.com/Microsoft/go-winio v0.4.15/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw= +github.com/Microsoft/hcsshim v0.8.10-0.20200715222032-5eafd1556990/go.mod h1:ay/0dTb7NsG8QMDfsRfLHgZo/6xAJShLe1+ePPflihk= +github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I= github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU= github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= +github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= -github.com/antlr/antlr4/runtime/Go/antlr v1.4.10 h1:yL7+Jz0jTC6yykIK/Wh74gnTJnrGr5AyrNMXuA0gves= -github.com/antlr/antlr4/runtime/Go/antlr v1.4.10/go.mod h1:F7bn7fEU90QkQ3tnmaTx3LTKLEDqnwWODIYppRQ5hnY= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= +github.com/auth0/go-jwt-middleware v1.0.1/go.mod h1:YSeUX3z6+TF2H+7padiEqNJ73Zy9vXW72U//IgN0BIM= +github.com/aws/aws-sdk-go v1.35.24/go.mod h1:tlPOdRjfxPBpNIwqDj61rmsnA85v9jc0Ps9+muhnW+k= +github.com/aws/aws-sdk-go v1.38.49/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= +github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM= github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= -github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bits-and-blooms/bitset v1.2.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA= +github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= +github.com/blang/semver v3.5.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= +github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= +github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= +github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= github.com/bytedance/mockey v1.2.13 h1:jokWZAm/pUEbD939Rhznz615MKUCZNuvCFQlJ2+ntoo= github.com/bytedance/mockey v1.2.13/go.mod h1:1BPHF9sol5R1ud/+0VEHGQq/+i2lN+GTsr3O2Q9IENY= -github.com/cenkalti/backoff/v4 v4.1.3 h1:cFAlzYUlVYDysBEH2T5hyJZMh3+5+WCBvSnK6Q8UtC4= -github.com/cenkalti/backoff/v4 v4.1.3/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/certifi/gocertifi v0.0.0-20191021191039-0944d244cd40/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= +github.com/certifi/gocertifi v0.0.0-20200922220541-2c3bb06c6054/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5/go.mod h1:/iP1qXHoty45bqomnu2LM+VVyAEdWN+vtSHGlQgyxbw= +github.com/checkpoint-restore/go-criu/v5 v5.0.0/go.mod h1:cfwC0EG7HMUenopBsUf9d89JlCLQIfgVcNsNN0t6T2M= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/cilium/ebpf v0.0.0-20200110133405-4032b1d8aae3/go.mod h1:MA5e5Lr8slmEg9bt0VpxxWqJlO4iwu3FBdHUzV7wQVg= +github.com/cilium/ebpf v0.5.0/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs= +github.com/cilium/ebpf v0.6.2/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/clusterhq/flocker-go v0.0.0-20160920122132-2b8b7259d313/go.mod h1:P1wt9Z3DP8O6W3rvwCt0REIlshg1InHImaLW0t3ObY0= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= -github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cockroachdb/datadriven v0.0.0-20200714090401-bf6692d28da5/go.mod h1:h6jFvWxBdQXxjopDMZyH2UVceIRfR84bdzbkoKrsWNo= +github.com/cockroachdb/errors v1.2.4/go.mod h1:rQD95gz6FARkaKkQXUksEje/d9a6wBJoCr5oaCLELYA= +github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f/go.mod h1:i/u985jwjWRlyHXQbwatDASoW0RMlZ/3i9yJHE2xLkI= +github.com/container-storage-interface/spec v1.5.0/go.mod h1:8K96oQNkJ7pFcC2R9Z1ynGGBB1I93kcS6PGg3SsOk8s= +github.com/containerd/cgroups v0.0.0-20200531161412-0dbf7f05ba59/go.mod h1:pA0z1pT8KYB3TCXK/ocprsh7MAkoW8bZVzPdih9snmM= +github.com/containerd/console v0.0.0-20180822173158-c12b1e7919c1/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw= +github.com/containerd/console v1.0.2/go.mod h1:ytZPjGgY2oeTkAONYafi2kSj0aYggsf8acV1PGKCbzQ= +github.com/containerd/containerd v1.3.2/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.4.4/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= +github.com/containerd/fifo v0.0.0-20190226154929-a9fb20d87448/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI= +github.com/containerd/go-runc v0.0.0-20180907222934-5a6d9f37cfa3/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0= +github.com/containerd/ttrpc v0.0.0-20190828154514-0e0f228740de/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o= +github.com/containerd/ttrpc v1.0.2/go.mod h1:UAxOpgT9ziI0gJrmKvgcZivgxOp8iFPSk8httJEt98Y= +github.com/containerd/typeurl v0.0.0-20180627222232-a93fcdb778cd/go.mod h1:Cm3kwCdlkCfMSHURc+r6fwoGH6/F1hH3S4sg0rLFWPc= +github.com/containerd/typeurl v1.0.1/go.mod h1:TB1hUtrpaiO88KEK56ijojHS1+NeF0izUACaJW2mdXg= +github.com/containernetworking/cni v0.8.1/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= +github.com/coredns/caddy v1.1.0/go.mod h1:A6ntJQlAWuQfFlsd9hvigKbo2WS0VUs2l1e2F+BawD4= +github.com/coredns/corefile-migration v1.0.12/go.mod h1:NJOI8ceUF/NTgEwtjD+TUq3/BnH/GF7WAM3RzCa3hBo= +github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-oidc v2.1.0+incompatible h1:sdJrfw8akMnCuUlaZU3tE/uYXFgfqom8DBE9so9EBsM= github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd/v22 v22.0.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk= +github.com/coreos/go-systemd/v22 v22.3.1/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/go-systemd/v22 v22.3.2 h1:D9/bQk5vlXQFZ6Kwuu6zaiXJ9oTPe68++AzAJc1DzSI= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/cyphar/filepath-securejoin v0.2.2 h1:jCwT2GTP+PY5nBz3c/YL5PAIbusElVrPujOBSCj8xRg= +github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/daviddengcn/go-colortext v0.0.0-20160507010035-511bcaf42ccd/go.mod h1:dv4zxwHi5C/8AeI+4gX4dCWOIvNi7I6JCSX0HvlKPgE= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= +github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/distribution v2.8.1+incompatible h1:Q50tZOPR6T/hjNsyc9g8/syEs6bk8XXApsHjKukMl68= github.com/docker/distribution v2.8.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/docker v20.10.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= github.com/dominikbraun/graph v0.23.0 h1:TdZB4pPqCLFxYhdyMFb1TBdFxp8XLcJfTTBQucVPgCo= github.com/dominikbraun/graph v0.23.0/go.mod h1:yOjYyogZLY1LSG9E33JWZJiq5k83Qy2C6POAuiViluc= @@ -111,41 +202,55 @@ github.com/elastic/go-elasticsearch/v7 v7.6.0 h1:sYpGLpEFHgLUKLsZUBfuaVI9QgHjS3J github.com/elastic/go-elasticsearch/v7 v7.6.0/go.mod h1:OJ4wdbtDNk5g503kvlHLyErCgQwwzmDtaFC4XyOxXA4= github.com/elastic/go-elasticsearch/v8 v8.7.0 h1:ZvbT1YHppBC0QxGnMmaDUxoDa26clwhRaB3Gp5E3UcY= github.com/elastic/go-elasticsearch/v8 v8.7.0/go.mod h1:lVb8SvJV8McVkdswpL8YR5QKIkhlWaoSq60YpHilOLI= +github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= github.com/elliotxx/esquery v0.2.0-alpha.1 h1:NDxHoBMBZnTuISmSTGHb7K551gowePXxybR+qiKoS9w= github.com/elliotxx/esquery v0.2.0-alpha.1/go.mod h1:M0jQkd9aNU779vIJTw/x8s8srOcOthtKqyI1+XwJQrs= -github.com/elliotxx/kubeaudit v0.0.0-20240124033725-e70be1692249 h1:E/wRJDd2voFkTP5BjmgX8LU2hn8oTF6GccfdkN6qQIE= -github.com/elliotxx/kubeaudit v0.0.0-20240124033725-e70be1692249/go.mod h1:IuYK2mfGRZ8vfJrzBmc63lkL6at7xvoI98sTCPkCNUU= +github.com/elliotxx/kubeaudit v0.0.0-20250218094432-a62ce515167e h1:I2HbUoPpGiz6ZWiNSDTCy/VJNPsa0pyEgj+XKVRAd7g= +github.com/elliotxx/kubeaudit v0.0.0-20250218094432-a62ce515167e/go.mod h1:A2Ph5nwhaA1X6A+f0fZ0vCJVva6mLT4stn0qDQKxHxw= github.com/elliotxx/safe v1.0.0 h1:SOe7tVXDavTEDBPrFG/m/G++M2w9fU7idfwt7w5WkTQ= github.com/elliotxx/safe v1.0.0/go.mod h1:iNcUMga5s3unppx7+pExhbGybGEuyGgN0F/srkHKlxw= -github.com/emicklei/go-restful/v3 v3.9.0 h1:XwGDlfxEnQZzuopoqxwSEllNcCOM9DhhFyhFIIGKwxE= -github.com/emicklei/go-restful/v3 v3.9.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/emicklei/go-restful v2.9.5+incompatible h1:spTtZBk5DYEvbxMVutUuTyh1Ao2r4iyvLdACqsl/Ljk= +github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/euank/go-kmsg-parser v2.0.0+incompatible/go.mod h1:MhmAMZ8V4CYH4ybgdRwPr2TU5ThnS43puaKEMpja1uw= github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ= +github.com/evanphx/json-patch v4.11.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84= github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= -github.com/evanphx/json-patch/v5 v5.6.0 h1:b91NhWfaz02IuVxO9faSllyAtNXHMPkC5J8sJCLunww= -github.com/evanphx/json-patch/v5 v5.6.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4= +github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZMPRZwes7CROmyNKgQzC3XPs6L/G2EJLHddWejkmf4= +github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= +github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk= github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= +github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= +github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/form3tech-oss/jwt-go v3.2.3+incompatible h1:7ZaBxOI7TMoYBfyA3cQHErNNyAWIKUMIwqxEtgHOs5c= github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= +github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= +github.com/fvbommel/sortorder v1.0.1/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui720w+kxuqRs0= +github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-chi/chi/v5 v5.0.10 h1:rLz5avzKpjqxrYwXNfmjkrYYXOyLJd37pz53UFHC6vk= github.com/go-chi/chi/v5 v5.0.10/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4= github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0= +github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -157,38 +262,42 @@ github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9 github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= -github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= -github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= -github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/go-logr/zapr v1.2.3 h1:a9vnzlIBPQBBkeaR9IuMUfmVOrQlkoC4YfPoFkX3T7A= -github.com/go-logr/zapr v1.2.3/go.mod h1:eIauM6P8qSvTw5o2ez6UEAfGjQKrxQTl5EoK+Qa2oG4= +github.com/go-logr/logr v0.4.0 h1:K7/B1jt6fIBQVd4Owv2MqGQClcgf0R266+7C/QjRcLc= +github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= +github.com/go-logr/zapr v0.4.0 h1:uc1uML3hRYL9/ZZPdgHS/n8Nzo+eaYL/Efxkkamf7OM= +github.com/go-logr/zapr v0.4.0/go.mod h1:tabnROwaDl0UNxkVeFRbY8bwB37GwRv0P8lg6aAiEnk= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= github.com/go-openapi/jsonpointer v0.20.0 h1:ESKJdU9ASRfaPNOPRx12IUyA1vn3R9GiE3KYD14BXdQ= github.com/go-openapi/jsonpointer v0.20.0/go.mod h1:6PGzBjjIIumbLYysB73Klnms1mwnU4G3YHOECG3CedA= +github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= +github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg= github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo= github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= github.com/go-openapi/spec v0.20.11 h1:J/TzFDLTt4Rcl/l1PmyErvkqlJDncGvPTMnCI39I4gY= github.com/go-openapi/spec v0.20.11/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6VaaBKcWA= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU= github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-ozzo/ozzo-validation v3.5.0+incompatible/go.mod h1:gsEKFIVnabGBt6mXmxK0MoFy+cZoTJY6mu5Ll3LVLBU= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/glog v1.0.0 h1:nfP3RFugxnNRyKgeWd4oI1nYvXpxrx8ck8ZrcizshdQ= -github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= +github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -216,16 +325,15 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golangplus/testing v0.0.0-20180327235837-af21d9c3145e/go.mod h1:0AA//k/eakGydO4jKRoRL2j92ZKSzTgj9tclaCrvXHk= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= -github.com/google/cel-go v0.12.6 h1:kjeKudqV0OygrAqA9fX6J55S8gj+Jre2tckIm5RoG4M= -github.com/google/cel-go v0.12.6/go.mod h1:Jk7ljRzLBhkmiAwBoUxB1sZSCVBAzkqPF25olK/iRDw= -github.com/google/gnostic v0.5.7-v3refs h1:FhTMOKj2VhjpouxvWJAV1TL304uMlb9zcDqkl6cEI54= -github.com/google/gnostic v0.5.7-v3refs/go.mod h1:73MKFl6jIHelAJNaBGFzt3SPtZULs9dYrGFt8OiIsHQ= +github.com/google/cadvisor v0.39.2/go.mod h1:kN93gpdevu+bpS227TyHVZyCU5bbqCzTj5T9drl34MI= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -233,6 +341,7 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= @@ -252,38 +361,76 @@ github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hf github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU= +github.com/googleapis/gnostic v0.5.5 h1:9fHAtK0uDfpveeqqo1hkEZJcFvYXAiCN3UutL8F9xHw= +github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA= +github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g= github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k= +github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw= github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0 h1:BZHcxBETFHIdVyhyEfOvn/RdU/QGdLI4y34qQGjGWO0= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4ZsPv9hVvWI6+ch50m39Pf2Ks= +github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= +github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= +github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= +github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= +github.com/heketi/heketi v10.3.0+incompatible/go.mod h1:bB9ly3RchcQqsQ9CpyaQwvva7RS5ytVoSoholZQON6o= +github.com/heketi/tests v0.0.0-20151005000721-f3775cbcefd6/go.mod h1:xGMAM8JLi7UkZt1i4FQeQy0R2T8GLUwQhOP5M1gBhy4= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw= github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/hupe1980/go-huggingface v0.0.15 h1:tTWmUGGunC/BYz4hrwS8SSVtMYVYjceG2uhL8HxeXvw= github.com/hupe1980/go-huggingface v0.0.15/go.mod h1:IRvsik3+b9BJyw9hCfw1arI6gDObcVto1UA8f3kt8mM= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc= github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/ishidawataru/sctp v0.0.0-20190723014705-7c296d48a2b5/go.mod h1:DM4VvS+hD/kDi1U1QsX2fnZowwBhqD0Dk3bRPKF/Oc8= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/jonboulle/clockwork v0.2.2 h1:UOGuzwb1PwsrDAObMuhUnj0p5ULPj8V/xJ7Kx9qUBdQ= github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= @@ -300,9 +447,14 @@ github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7 github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= +github.com/karrick/godirwalk v1.16.1/go.mod h1:j4mkqPuvaLI8mp1DroR3P6ad7cyYd4c1qeJ3RV7ULlk= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= @@ -314,24 +466,50 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/libopenstorage/openstorage v1.0.0/go.mod h1:Sp1sIObHjat1BeXhfMqLZ14wnOzEhNx2YQedreMcUyc= +github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= +github.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc= +github.com/lpabon/godbc v0.1.1/go.mod h1:Jo9QV0cf3U6jZABgiJ2skINAXb9j8m51r07g4KI92ZA= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/matttproud/golang_protobuf_extensions v1.0.2 h1:hAHbPm5IJGijwng3PWk09JkG9WeqChjprR5s9bBZ+OM= github.com/matttproud/golang_protobuf_extensions v1.0.2/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/mindprince/gonvml v0.0.0-20190828220739-9ebdce4bb989/go.mod h1:2eu9pRWp8mo84xCg6KswZ+USQHjwgRhNp06sozOdsTY= +github.com/mistifyio/go-zfs v2.1.2-0.20190413222219-f784269be439+incompatible/go.mod h1:8AuVvqP/mXw1px98n46wfvcGfQ4ci2FwoAjKYxuo3Z4= +github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= github.com/mitchellh/copystructure v1.1.1 h1:Bp6x9R1Wn16SIz3OfeDr0b7RnCG2OB66Y7PQyC/cvq4= github.com/mitchellh/copystructure v1.1.1/go.mod h1:EBArHfARyrSWO/+Wyr9zwEkc6XMFB9XyNgFNmRkZZU4= +github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= +github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4= github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE= +github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/mitchellh/reflectwalk v1.0.1 h1:FVzMWA5RllMAKIdUSC8mdWo3XtwoecrH79BY70sEEpE= github.com/mitchellh/reflectwalk v1.0.1/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= -github.com/moby/sys/mountinfo v0.6.2 h1:BzJjoreD5BMFNmD9Rus6gdd1pLuecOFPt8wC+Vygl78= -github.com/moby/sys/mountinfo v0.6.2/go.mod h1:IJb6JQeOklcdMU9F5xQ8ZALD+CUr5VlGpwtX+VE0rpI= +github.com/moby/ipvs v1.0.1/go.mod h1:2pngiyseZbIKXNv7hsKj3O9UEz30c53MT9005gt2hxQ= +github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= +github.com/moby/sys/mountinfo v0.4.1/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A= +github.com/moby/term v0.0.0-20201216013528-df9cb8a40635/go.mod h1:FBS0z0QWA44HXygs7VXDUOGoN/1TV3RuWkLO04am3wc= +github.com/moby/term v0.0.0-20210610120745-9d4ed1856297/go.mod h1:vgPCkQMyxTZ7IDy8SXRufE172gr8+K/JE/7hHFxHW3A= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -339,32 +517,65 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/mohae/deepcopy v0.0.0-20170603005431-491d3605edfb/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= +github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4= +github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ= +github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/mvdan/xurls v1.1.0/go.mod h1:tQlNn3BED8bE/15hnSL2HLkDeLWpNPAwtw7wkEq44oU= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA= github.com/oliveagle/jsonpath v0.0.0-20180606110733-2e52cf6e6852 h1:Yl0tPBa8QPjGmesFh1D0rDy+q1Twx6FyU7VWHi8wZbI= github.com/oliveagle/jsonpath v0.0.0-20180606110733-2e52cf6e6852/go.mod h1:eqOVx5Vwu4gd2mmMZvVZsgIqNSaW3xxRThUJ0k/TPk4= -github.com/onsi/ginkgo/v2 v2.6.0 h1:9t9b9vRUbFq3C4qKFCGkVuq/fIHji802N1nrtkh1mNc= -github.com/onsi/ginkgo/v2 v2.6.0/go.mod h1:63DOGlLAH8+REH8jUGdL3YpCpu7JODesutUjdENfUAc= -github.com/onsi/gomega v1.24.1 h1:KORJXNNTzJXzu4ScJWssJfJMnJ+2QJqhoQSRwNlze9E= -github.com/onsi/gomega v1.24.1/go.mod h1:3AOiACssS3/MajrniINInwbfOOtfZvplPzuRSmvt1jM= +github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= +github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= +github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= +github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.15.0 h1:WjP/FQ/sk43MRmnEcT+MlDw2TFvkrXlprrPST/IudjU= +github.com/onsi/gomega v1.15.0/go.mod h1:cIuvLEne0aoVhAgh/O6ac0Op8WWw9H6eYCriF+tEHG0= +github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= -github.com/opencontainers/selinux v1.10.0 h1:rAiKF8hTcgLI3w0DHm6i0ylVVcOrlgR1kK99DRLDhyU= -github.com/opencontainers/selinux v1.10.0/go.mod h1:2i0OySw99QjzBBQByd1Gr9gSjvuho1lHsJxIJ3gGbJI= +github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= +github.com/opencontainers/runc v0.0.0-20190115041553-12f6a991201f/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= +github.com/opencontainers/runc v1.0.0-rc95/go.mod h1:z+bZxa/+Tz/FmYVWkhUajJdzFeOqjc5vrqskhVyHGUM= +github.com/opencontainers/runc v1.0.2 h1:opHZMaswlyxz1OuGpBE53Dwe4/xF7EZTY0A2L/FpCOg= +github.com/opencontainers/runc v1.0.2/go.mod h1:aTaHFFwQXuA71CiyxOdFFIorAoemI04suvGRQFzWTD0= +github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/selinux v1.8.0/go.mod h1:RScLhm78qiWa2gbVCcGkC7tCGdgk3ogry1nUQF8Evvo= +github.com/opencontainers/selinux v1.8.2/go.mod h1:MUIHuUEvKB1wtJjQdOyYRgOnLD2xAPP8dBsCoU0KuF8= +github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= github.com/pquerna/cachecontrol v0.1.0 h1:yJMy84ti9h/+OEWa752kBTKv4XC30OtVVHYv/8cTqKc= github.com/pquerna/cachecontrol v0.1.0/go.mod h1:NrUG3Z7Rdu85UNR3vm7SOsl1nFIeSiQnrHV5K9mBcUI= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= @@ -378,51 +589,88 @@ github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1: github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4= github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= +github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= github.com/prometheus/common v0.37.0 h1:ccBbHCgIiT9uSoFY0vX8H3zsNR5eLt17/RQLUvn8pXE= github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA= +github.com/prometheus/procfs v0.0.0-20180125133057-cb4147076ac7/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.0-20190522114515-bc1a522cf7b1/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo= github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4= +github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/quobyte/api v0.1.8/go.mod h1:jL7lIHrmqQ7yh05OJ+eEEdHr0u/kmT1Ff9iHd+4H6VI= +github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M= +github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= +github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rubiojr/go-vhd v0.0.0-20200706105327-02e210299021/go.mod h1:DM5xW0nvfNNm2uytzsvhI3OnX8uzaRAg8UX/CnDqbto= +github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/sashabaranov/go-openai v1.27.0 h1:L3hO6650YUbKrbGUC6yCjsUluhKZ9h1/jcgbTItI8Mo= github.com/sashabaranov/go-openai v1.27.0/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo= +github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= +github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/smarty/assertions v1.16.0 h1:EvHNkdRA4QHMrn75NZSoUQ/mAUXAYWfatfB01yTCzfY= github.com/smarty/assertions v1.16.0/go.mod h1:duaaFdCS0K9dnoM50iyek/eYINOZ64gbh1Xlf6LG7AI= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/assertions v1.1.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/smartystreets/goconvey v1.8.1 h1:qGjIddxOk4grTu9JPOU31tVfq3cNdBlNa5sSznIX1xY= github.com/smartystreets/goconvey v1.8.1/go.mod h1:+/u4qLyY6x1jReYOp7GOM2FSt8aP9CzCZL03bI28W60= +github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/soheilhy/cmux v0.1.5 h1:jjzc5WVemNEDTLwv9tlmemhC73tI08BNOIGwBOo10Js= github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= +github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo= github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA= github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/stoewer/go-strcase v1.2.0 h1:Z2iHWqGXH00XYgqDmNgQbIBxf3wrNq0F3feEy0ainaU= +github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= +github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= +github.com/storageos/go-api v2.2.0+incompatible/go.mod h1:ZrLn+e0ZuF3Y65PNF6dIwbJPZqfmtCXxFm9ckv0agOY= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= @@ -437,16 +685,30 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/swaggo/files/v2 v2.0.0 h1:hmAt8Dkynw7Ssz46F6pn8ok6YmGZqHSVLZ+HQM7i0kw= github.com/swaggo/files/v2 v2.0.0/go.mod h1:24kk2Y9NYEJ5lHuCra6iVwkMjIekMCaFq/0JQj66kyM= github.com/swaggo/http-swagger/v2 v2.0.2 h1:FKCdLsl+sFCx60KFsyM0rDarwiUSZ8DqbfSyIKC9OBg= github.com/swaggo/http-swagger/v2 v2.0.2/go.mod h1:r7/GBkAWIfK6E/OLnE8fXnviHiDeAHmgIyooa4xm3AQ= github.com/swaggo/swag v1.16.2 h1:28Pp+8DkQoV+HLzLx8RGJZXNGKbFqnuvSbAAtoxiY04= github.com/swaggo/swag v1.16.2/go.mod h1:6YzXnDcpr0767iOejs318CwYkCQqyGer6BizOg03f+E= +github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= +github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802 h1:uruHq4dN7GR16kFc5fp3d1RIYzJW5onx8Ybykw2YQFA= github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= +github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4= +github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= +github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU= +github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= +github.com/vmware/govmomi v0.20.3/go.mod h1:URlwyTFZX72RmxtxuaFL2Uj3fD1JTvZdx59bHWk6aFU= +github.com/willf/bitset v1.1.11/go.mod h1:83CECat5yLh5zVOf4P1ErAgKA5UDvKtgyUABdr3+MjI= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/xwb1989/sqlparser v0.0.0-20171128062118-da747e0c62c4 h1:w96oitIHwAbUymu2zUSla/82gOKNzpJYkFdwCHE/UOA= github.com/xwb1989/sqlparser v0.0.0-20171128062118-da747e0c62c4/go.mod h1:hzfGeIUDq/j97IG+FhNqkowIyEcD88LrW6fyU3K3WqY= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -454,71 +716,93 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU= go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4= +go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= go.etcd.io/etcd/api/v3 v3.5.5 h1:BX4JIbQ7hl7+jL+g+2j5UAr0o1bctCm6/Ct+ArBGkf0= go.etcd.io/etcd/api/v3 v3.5.5/go.mod h1:KFtNaxGDw4Yx/BA4iPPwevUTAuqcsPxzyX8PHydchN8= +go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= go.etcd.io/etcd/client/pkg/v3 v3.5.5 h1:9S0JUVvmrVl7wCF39iTQthdaaNIiAaQbmK75ogO6GU8= go.etcd.io/etcd/client/pkg/v3 v3.5.5/go.mod h1:ggrwbk069qxpKPq8/FKkQ3Xq9y39kbFR4LnKszpRXeQ= -go.etcd.io/etcd/client/v2 v2.305.5 h1:DktRP60//JJpnPC0VBymAN/7V71GHMdjDCBt4ZPXDjI= -go.etcd.io/etcd/client/v2 v2.305.5/go.mod h1:zQjKllfqfBVyVStbt4FaosoX2iYd8fV/GRy/PbowgP4= +go.etcd.io/etcd/client/v2 v2.305.0 h1:ftQ0nOOHMcbMS3KIaDQ0g5Qcd6bhaBrQT6b89DfwLTs= +go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= +go.etcd.io/etcd/client/v3 v3.5.0/go.mod h1:AIKXXVX/DQXtfTEqBryiLTUXwON+GuvO6Z7lLS/oTh0= go.etcd.io/etcd/client/v3 v3.5.5 h1:q++2WTJbUgpQu4B6hCuT7VkdwaTP7Qz6Daak3WzbrlI= go.etcd.io/etcd/client/v3 v3.5.5/go.mod h1:aApjR4WGlSumpnJ2kloS75h6aHUmAyaPLjHMxpc7E7c= -go.etcd.io/etcd/pkg/v3 v3.5.5 h1:Ablg7T7OkR+AeeeU32kdVhw/AGDsitkKPl7aW73ssjU= -go.etcd.io/etcd/pkg/v3 v3.5.5/go.mod h1:6ksYFxttiUGzC2uxyqiyOEvhAiD0tuIqSZkX3TyPdaE= -go.etcd.io/etcd/raft/v3 v3.5.5 h1:Ibz6XyZ60OYyRopu73lLM/P+qco3YtlZMOhnXNS051I= -go.etcd.io/etcd/raft/v3 v3.5.5/go.mod h1:76TA48q03g1y1VpTue92jZLr9lIHKUNcYdZOOGyx8rI= -go.etcd.io/etcd/server/v3 v3.5.5 h1:jNjYm/9s+f9A9r6+SC4RvNaz6AqixpOvhrFdT0PvIj0= -go.etcd.io/etcd/server/v3 v3.5.5/go.mod h1:rZ95vDw/jrvsbj9XpTqPrTAB9/kzchVdhRirySPkUBc= +go.etcd.io/etcd/pkg/v3 v3.5.0 h1:ntrg6vvKRW26JRmHTE0iNlDgYK6JX3hg/4cD62X0ixk= +go.etcd.io/etcd/pkg/v3 v3.5.0/go.mod h1:UzJGatBQ1lXChBkQF0AuAtkRQMYnHubxAEYIrC3MSsE= +go.etcd.io/etcd/raft/v3 v3.5.0 h1:kw2TmO3yFTgE+F0mdKkG7xMxkit2duBDa2Hu6D/HMlw= +go.etcd.io/etcd/raft/v3 v3.5.0/go.mod h1:UFOHSIvO/nKwd4lhkwabrTD3cqW5yVyYYf/KlD00Szc= +go.etcd.io/etcd/server/v3 v3.5.0 h1:jk8D/lwGEDlQU9kZXUFMSANkE22Sg5+mW27ip8xcF9E= +go.etcd.io/etcd/server/v3 v3.5.0/go.mod h1:3Ah5ruV+M+7RZr0+Y/5mNLwC+eQlni+mQmOVdCRJoS4= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.35.0 h1:xFSRQBbXF6VvYRf2lqMJXxoB72XI1K/azav8TekHHSw= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.35.0/go.mod h1:h8TWwRAhQpOd0aM5nYsRD8+flnkj+526GEIVlarH7eY= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.35.0 h1:Ajldaqhxqw/gNzQA45IKFWLdG7jZuXX/wBW1d5qvbUI= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.35.0/go.mod h1:9NiG9I2aHTKkcxqCILhjtyNA1QEiCjdBACv4IvrFQ+c= -go.opentelemetry.io/otel v1.10.0 h1:Y7DTJMR6zs1xkS/upamJYk0SxxN4C9AqRd77jmZnyY4= -go.opentelemetry.io/otel v1.10.0/go.mod h1:NbvWjCthWHKBEUMpf0/v8ZRZlni86PpGFEMA9pnQSnQ= -go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.10.0 h1:TaB+1rQhddO1sF71MpZOZAuSPW1klK2M8XxfrBMfK7Y= -go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.10.0/go.mod h1:78XhIg8Ht9vR4tbLNUhXsiOnE2HOuSeKAiAcoVQEpOY= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.10.0 h1:pDDYmo0QadUPal5fwXoY1pmMpFcdyhXOmL5drCrI3vU= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.10.0/go.mod h1:Krqnjl22jUJ0HgMzw5eveuCvFDXY4nSYb4F8t5gdrag= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.10.0 h1:KtiUEhQmj/Pa874bVYKGNVdq8NPKiacPbaRRtgXi+t4= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.10.0/go.mod h1:OfUCyyIiDvNXHWpcWgbF+MWvqPZiNa3YDEnivcnYsV0= -go.opentelemetry.io/otel/metric v0.31.0 h1:6SiklT+gfWAwWUR0meEMxQBtihpiEs4c+vL9spDTqUs= -go.opentelemetry.io/otel/metric v0.31.0/go.mod h1:ohmwj9KTSIeBnDBm/ZwH2PSZxZzoOaG2xZeekTRzL5A= -go.opentelemetry.io/otel/sdk v1.10.0 h1:jZ6K7sVn04kk/3DNUdJ4mqRlGDiXAVuIG+MMENpTNdY= -go.opentelemetry.io/otel/sdk v1.10.0/go.mod h1:vO06iKzD5baltJz1zarxMCNHFpUlUiOy4s65ECtn6kE= -go.opentelemetry.io/otel/trace v1.10.0 h1:npQMbR8o7mum8uF95yFbOEJffhs1sbCOfDh8zAJiH5E= -go.opentelemetry.io/otel/trace v1.10.0/go.mod h1:Sij3YYczqAdz+EhmGhE6TpTxUO5/F/AzrK+kxfGqySM= +go.opentelemetry.io/contrib v0.20.0 h1:ubFQUn0VCZ0gPwIoJfBJVpeBlyRMxu8Mm/huKWYd9p0= +go.opentelemetry.io/contrib v0.20.0/go.mod h1:G/EtFaa6qaN7+LxqfIAT3GiZa7Wv5DTBUzl5H4LY0Kc= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.20.0 h1:sO4WKdPAudZGKPcpZT4MJn6JaDmpyLrMPDGGyA1SttE= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.20.0/go.mod h1:oVGt1LRbBOBq1A5BQLlUg9UaU/54aiHw8cgjV3aWZ/E= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.20.0 h1:Q3C9yzW6I9jqEc8sawxzxZmY48fs9u220KXq6d5s3XU= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.20.0/go.mod h1:2AboqHi0CiIZU0qwhtUfCYD1GeUzvvIXWNkhDt7ZMG4= +go.opentelemetry.io/otel v0.20.0 h1:eaP0Fqu7SXHwvjiqDq83zImeehOHX8doTvU9AwXON8g= +go.opentelemetry.io/otel v0.20.0/go.mod h1:Y3ugLH2oa81t5QO+Lty+zXf8zC9L26ax4Nzoxm/dooo= +go.opentelemetry.io/otel/exporters/otlp v0.20.0 h1:PTNgq9MRmQqqJY0REVbZFvwkYOA85vbdQU/nVfxDyqg= +go.opentelemetry.io/otel/exporters/otlp v0.20.0/go.mod h1:YIieizyaN77rtLJra0buKiNBOm9XQfkPEKBeuhoMwAM= +go.opentelemetry.io/otel/metric v0.20.0 h1:4kzhXFP+btKm4jwxpjIqjs41A7MakRFUS86bqLHTIw8= +go.opentelemetry.io/otel/metric v0.20.0/go.mod h1:598I5tYlH1vzBjn+BTuhzTCSb/9debfNp6R3s7Pr1eU= +go.opentelemetry.io/otel/oteltest v0.20.0 h1:HiITxCawalo5vQzdHfKeZurV8x7ljcqAgiWzF6Vaeaw= +go.opentelemetry.io/otel/oteltest v0.20.0/go.mod h1:L7bgKf9ZB7qCwT9Up7i9/pn0PWIa9FqQ2IQ8LoxiGnw= +go.opentelemetry.io/otel/sdk v0.20.0 h1:JsxtGXd06J8jrnya7fdI/U/MR6yXA5DtbZy+qoHQlr8= +go.opentelemetry.io/otel/sdk v0.20.0/go.mod h1:g/IcepuwNsoiX5Byy2nNV0ySUF1em498m7hBWC279Yc= +go.opentelemetry.io/otel/sdk/export/metric v0.20.0 h1:c5VRjxCXdQlx1HjzwGdQHzZaVI82b5EbBgOu2ljD92g= +go.opentelemetry.io/otel/sdk/export/metric v0.20.0/go.mod h1:h7RBNMsDJ5pmI1zExLi+bJK+Dr8NQCh0qGhm1KDnNlE= +go.opentelemetry.io/otel/sdk/metric v0.20.0 h1:7ao1wpzHRVKf0OQ7GIxiQJA6X7DLX9o14gmVon7mMK8= +go.opentelemetry.io/otel/sdk/metric v0.20.0/go.mod h1:knxiS8Xd4E/N+ZqKmUPf3gTTZ4/0TjTXukfxjzSTpHE= +go.opentelemetry.io/otel/trace v0.20.0 h1:1DL6EXUdcg95gukhuRRvLDO/4X5THh/5dIV52lqtnbw= +go.opentelemetry.io/otel/trace v0.20.0/go.mod h1:6GjCW8zgDjwGHGa6GkyeB8+/5vjT16gUEi0Nf1iBdgw= +go.opentelemetry.io/proto/otlp v0.7.0 h1:rwOQPCuKAKmwGKq2aVNnYIibI6wnV7EvzgfTCzcdGg8= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= -go.opentelemetry.io/proto/otlp v0.19.0 h1:IVN6GR+mhC4s5yfcTbmzHYODqvWAp3ZedA2SJPI1Nnw= -go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= +go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5/go.mod h1:nmDLcffg48OtT/PSW0Hg7FvpRQsQh5OSqIylirxKC7o= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk= go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= +go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= golang.org/x/arch v0.12.0 h1:UsYJhbzPYGsT0HbEdmYcqtCv8UNGvnaL561NnIUvaKg= golang.org/x/arch v0.12.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY= golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= +golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190312203227-4b39c73a6495/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4= golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= @@ -526,6 +810,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/exp v0.0.0-20210220032938-85be41e4509f/go.mod h1:I6l2HNBLBZEcrOoCpyKLdY2lHoRZ8lI4x60KMCQDft4= +golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -541,29 +827,39 @@ golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPI golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mobile v0.0.0-20201217150744-e6ae53a27f4f/go.mod h1:skQtrUTUwhdJvXM/2KKJzY8pDgNr9I/FOMqDVRPBUS4= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191209134235-331c550502dd/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.1-0.20200828183125-ce943fd02449/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -574,12 +870,18 @@ golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/ golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= +golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= @@ -591,7 +893,6 @@ golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4Iltr golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b h1:clP8eMhB30EHdc0bd2Twtq6kgU7yl5ub2cQLSdrv1Dg= golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -607,9 +908,14 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -617,17 +923,28 @@ golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191002063906-3421d5a6bb1c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200120151820-655fe14d7479/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -635,28 +952,37 @@ golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210817190340-bfb29a6856f2/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4= golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= @@ -665,6 +991,7 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= @@ -673,23 +1000,34 @@ golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -697,6 +1035,7 @@ golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117012304-6edc0a871e69/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= @@ -717,6 +1056,7 @@ golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.16.0 h1:GO788SKMRunPIBCXiQyo2AaexLstOrVhuAL5YwsckQM= @@ -727,6 +1067,12 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gomodules.xyz/jsonpatch/v2 v2.2.0 h1:4pT439QV83L+G9FkcCriY6EkpcK6r6bK+A5FBUMI7qY= gomodules.xyz/jsonpatch/v2 v2.2.0/go.mod h1:WXp+iVDkoLQqPudfQ9GBlwB2eZ5DKOnjQZCYdOS8GPY= +gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= +gonum.org/v1/gonum v0.0.0-20190331200053-3d26580ed485/go.mod h1:2ltnJ7xHfj0zHS40VVPYEAAMTa3ZGguvHGBSJeRWqE0= +gonum.org/v1/gonum v0.6.2/go.mod h1:9mxDZsDKxgMAuccQkewq682L+0eCu4dCN2yonUJTCLU= +gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= +gonum.org/v1/netlib v0.0.0-20190331212654-76723241ea4e/go.mod h1:kS+toOQn6AQKjmKJ7gzohV1XkqsFehRA2FbsbkopSuQ= +gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= @@ -734,6 +1080,7 @@ google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEn google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.1-0.20200106000736-b8fc810ca6b5/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= @@ -764,6 +1111,7 @@ google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvx google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200117163144-32f20d992d24/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= @@ -772,6 +1120,7 @@ google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfG google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= @@ -782,14 +1131,16 @@ google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= -google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21 h1:hrbNEivu7Zn1pxvHk6MBrq9iE22woVILTHqexqBxe6I= google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= @@ -800,10 +1151,9 @@ google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= google.golang.org/grpc v1.41.0/go.mod h1:U3l9uK9J0sini8mHphKoXyaqDA/8VyGnDee1zzIUK6k= -google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= google.golang.org/grpc v1.51.0 h1:E1eGv1FTqoLIdnBCZufiSHgKjlqG6fKFf6pPWtMTh8U= google.golang.org/grpc v1.51.0/go.mod h1:wgNDFcnuBGmxLKI/qn4T+m5BtEBYXJPvibbUPsAIPww= @@ -831,12 +1181,20 @@ gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/gcfg.v1 v1.2.0/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= +gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/square/go-jose.v2 v2.2.2 h1:orlkJ3myw8CN1nVQHBFfloD+L3egixIa4FvUP6RosSA= gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/warnings.v0 v0.1.1/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= +gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -851,6 +1209,9 @@ gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= +gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= +gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= @@ -858,76 +1219,82 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -k8s.io/api v0.26.1 h1:f+SWYiPd/GsiWwVRz+NbFyCgvv75Pk9NK6dlkZgpCRQ= -k8s.io/api v0.26.1/go.mod h1:xd/GBNgR0f707+ATNyPmQ1oyKSgndzXij81FzWGsejg= -k8s.io/apiextensions-apiserver v0.26.1 h1:cB8h1SRk6e/+i3NOrQgSFij1B2S0Y0wDoNl66bn8RMI= -k8s.io/apiextensions-apiserver v0.26.1/go.mod h1:AptjOSXDGuE0JICx/Em15PaoO7buLwTs0dGleIHixSM= -k8s.io/apimachinery v0.26.1 h1:8EZ/eGJL+hY/MYCNwhmDzVqq2lPl3N3Bo8rvweJwXUQ= -k8s.io/apimachinery v0.26.1/go.mod h1:tnPmbONNJ7ByJNz9+n9kMjNP8ON+1qoAIIC70lztu74= -k8s.io/apiserver v0.26.1 h1:6vmnAqCDO194SVCPU3MU8NcDgSqsUA62tBUSWrFXhsc= -k8s.io/apiserver v0.26.1/go.mod h1:wr75z634Cv+sifswE9HlAo5FQ7UoUauIICRlOE+5dCg= -k8s.io/cli-runtime v0.26.1 h1:f9+bRQ1V3elQsx37KmZy5fRAh56mVLbE9A7EMdlqVdI= -k8s.io/cli-runtime v0.26.1/go.mod h1:+e5Ym/ARySKscUhZ8K3hZ+ZBo/wYPIcg+7b5sFYi6Gg= -k8s.io/client-go v0.26.1 h1:87CXzYJnAMGaa/IDDfRdhTzxk/wzGZ+/HUQpqgVSZXU= -k8s.io/client-go v0.26.1/go.mod h1:IWNSglg+rQ3OcvDkhY6+QLeasV4OYHDjdqeWkDQZwGE= -k8s.io/cloud-provider v0.26.1 h1:qEZmsGWGptOtVSpeMdTsapHX2BEqIk7rc5MA4caBqE0= -k8s.io/cloud-provider v0.26.1/go.mod h1:6PheIxRySYuRBBxtTUADya8S2rbr18xKi+fhGbLkduc= -k8s.io/cluster-bootstrap v0.26.1 h1:d36JXyk2/TBKqrUSXoCN6FyTTR3a7UOFVmQbm2YOGTA= -k8s.io/cluster-bootstrap v0.26.1/go.mod h1:Tf5X/siioEyBJjvQUzamT6w8KOnfT8QoIEoWyl2jb9k= -k8s.io/code-generator v0.26.1 h1:dusFDsnNSKlMFYhzIM0jAO1OlnTN5WYwQQ+Ai12IIlo= -k8s.io/code-generator v0.26.1/go.mod h1:OMoJ5Dqx1wgaQzKgc+ZWaZPfGjdRq/Y3WubFrZmeI3I= -k8s.io/component-base v0.26.1 h1:4ahudpeQXHZL5kko+iDHqLj/FSGAEUnSVO0EBbgDd+4= -k8s.io/component-base v0.26.1/go.mod h1:VHrLR0b58oC035w6YQiBSbtsf0ThuSwXP+p5dD/kAWU= -k8s.io/component-helpers v0.26.1 h1:Y5h1OYUJTGyHZlSAsc7mcfNsWF08S/MlrQyF/vn93mU= -k8s.io/component-helpers v0.26.1/go.mod h1:jxNTnHb1axLe93MyVuvKj9T/+f4nxBVrj/xf01/UNFk= -k8s.io/controller-manager v0.26.1 h1:KmwVTmZ61dxUoHI1TQXlfsbmmk1NVZPUTKjtRowRD30= -k8s.io/controller-manager v0.26.1/go.mod h1:2K95SC0wv5qVbXuC5dJnSgU6vM9J+YBgAaJdVijIy3E= -k8s.io/cri-api v0.26.1 h1:HTlvEzrhrjuXvjrrGWC2UMfM3vpxxtFJSs20QffHtMA= -k8s.io/cri-api v0.26.1/go.mod h1:I5TGOn/ziMzqIcUvsYZzVE8xDAB1JBkvcwvR0yDreuw= -k8s.io/csi-translation-lib v0.26.1 h1:GQT88qX4e903HlFne1ovGFilvsd7kJUVi6SWOkOg2SQ= -k8s.io/csi-translation-lib v0.26.1/go.mod h1:tbcXKaVAS3G9iIAi+8Ujp+LPLetZ+vZ2AsZj+c1yXd8= -k8s.io/dynamic-resource-allocation v0.26.1 h1:rQKykCT4c/TWDhIMGk91Azn3lvQznIRySaoTmFeeC4U= -k8s.io/dynamic-resource-allocation v0.26.1/go.mod h1:EDVbmVRc5fdAG+ezTdVyolgBWI2wgsUByhv8PnIxPKc= +k8s.io/api v0.22.2 h1:M8ZzAD0V6725Fjg53fKeTJxGsJvRbk4TEm/fexHMtfw= +k8s.io/api v0.22.2/go.mod h1:y3ydYpLJAaDI+BbSe2xmGcqxiWHmWjkEeIbiwHvnPR8= +k8s.io/apiextensions-apiserver v0.22.2 h1:zK7qI8Ery7j2CaN23UCFaC1hj7dMiI87n01+nKuewd4= +k8s.io/apiextensions-apiserver v0.22.2/go.mod h1:2E0Ve/isxNl7tWLSUDgi6+cmwHi5fQRdwGVCxbC+KFA= +k8s.io/apimachinery v0.22.2 h1:ejz6y/zNma8clPVfNDLnPbleBo6MpoFy/HBiBqCouVk= +k8s.io/apimachinery v0.22.2/go.mod h1:O3oNtNadZdeOMxHFVxOreoznohCpy0z6mocxbZr7oJ0= +k8s.io/apiserver v0.22.2 h1:TdIfZJc6YNhu2WxeAOWq1TvukHF0Sfx0+ln4XK9qnL4= +k8s.io/apiserver v0.22.2/go.mod h1:vrpMmbyjWrgdyOvZTSpsusQq5iigKNWv9o9KlDAbBHI= +k8s.io/cli-runtime v0.22.2/go.mod h1:tkm2YeORFpbgQHEK/igqttvPTRIHFRz5kATlw53zlMI= +k8s.io/client-go v0.22.2 h1:DaSQgs02aCC1QcwUdkKZWOeaVsQjYvWv8ZazcZ6JcHc= +k8s.io/client-go v0.22.2/go.mod h1:sAlhrkVDf50ZHx6z4K0S40wISNTarf1r800F+RlCF6U= +k8s.io/cloud-provider v0.22.2 h1:CiSDHMJiOd6qgYIP8ln9ueFHFU5Ld8TDZiYNIiMNbNk= +k8s.io/cloud-provider v0.22.2/go.mod h1:HUvZkUkV6dIKgWJQgGvnFhOeEHT87ZP39ij4K0fgkAs= +k8s.io/cluster-bootstrap v0.22.2 h1:jP6Nkp3CdSfr50cAn/7WGsNS52zrwMhvr0V+E3Vkh/w= +k8s.io/cluster-bootstrap v0.22.2/go.mod h1:ZkmQKprEqvrUccMnbRHISsMscA1dsQ8SffM9nHq6CgE= +k8s.io/code-generator v0.22.2 h1:+bUv9lpTnAWABtPkvO4x0kfz7j/kDEchVt0P/wXU3jQ= +k8s.io/code-generator v0.22.2/go.mod h1:eV77Y09IopzeXOJzndrDyCI88UBok2h6WxAlBwpxa+o= +k8s.io/component-base v0.22.2 h1:vNIvE0AIrLhjX8drH0BgCNJcR4QZxMXcJzBsDplDx9M= +k8s.io/component-base v0.22.2/go.mod h1:5Br2QhI9OTe79p+TzPe9JKNQYvEKbq9rTJDWllunGug= +k8s.io/component-helpers v0.22.2 h1:guQ9oYclE5LMydWFfAFA+u7SQgQzz2g+YgpJ5QooSyY= +k8s.io/component-helpers v0.22.2/go.mod h1:+N61JAR9aKYSWbnLA88YcFr9K/6ISYvRNybX7QW7Rs8= +k8s.io/controller-manager v0.22.2 h1:4JbMHSia+Ys80FAMW35mlkbNG+IBGemPOk0wWDkiWYo= +k8s.io/controller-manager v0.22.2/go.mod h1:zeDUbCc66IcMZ81U8qC5Z5pm9A8QkqD7839H8t7//yY= +k8s.io/cri-api v0.22.2/go.mod h1:mj5DGUtElRyErU5AZ8EM0ahxbElYsaLAMTPhLPQ40Eg= +k8s.io/csi-translation-lib v0.22.2 h1:1lW2AZjgYyfF6JTXLExxA/xwrocU5gfQkS/5Jg8Jpsk= +k8s.io/csi-translation-lib v0.22.2/go.mod h1:HYNFNKFADblw8nVm3eshFVWdmiccxPHN+SUmTKG3Ctk= +k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +k8s.io/gengo v0.0.0-20201214224949-b6c5ce23f027/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= k8s.io/gengo v0.0.0-20220902162205-c0856e24416d h1:U9tB195lKdzwqicbJvyJeOXV7Klv+wNAWENRnXEGi08= k8s.io/gengo v0.0.0-20220902162205-c0856e24416d/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= -k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= -k8s.io/klog/v2 v2.80.1 h1:atnLQ121W371wYYFawwYx1aEY2eUfs4l3J72wtgAwV4= -k8s.io/klog/v2 v2.80.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= -k8s.io/kms v0.26.1 h1:JE0n4J4+8/Z+egvXz2BTJeJ9ecsm4ZSLKF7ttVXXm/4= -k8s.io/kms v0.26.1/go.mod h1:ReC1IEGuxgfN+PDCIpR6w8+XMmDE7uJhxcCwMZFdIYc= -k8s.io/kube-controller-manager v0.26.1 h1:yq6177At/MHD/yKqfTj3cQI74SaA8o+yFCxw+FdEBkU= -k8s.io/kube-controller-manager v0.26.1/go.mod h1:V0mR9yO6TLzv1xqFq2FNAQSM37JqpdD+dCPJMghHWPw= -k8s.io/kube-openapi v0.0.0-20230106171958-10e5f0effbd2 h1:KYLG+YkfkVf4kdRVshpZZPHpiAyesloMrDE9k1B9Jaw= -k8s.io/kube-openapi v0.0.0-20230106171958-10e5f0effbd2/go.mod h1:/tm0KhSzkvLFxnfnIwgoRdtgzvT2UbF3Xz3hTqgr6MA= -k8s.io/kube-proxy v0.26.1 h1:uYt22aiLhIYKxMfmP0mxOMZn0co9UXwlA2uV0uJTDt4= -k8s.io/kube-proxy v0.26.1/go.mod h1:z7TSAvTeD8xmEzNGgwoiXZ0BCE13IPKXp/tSoBBNzaM= -k8s.io/kube-scheduler v0.26.1 h1:OsNOWNPYUeMIkm+LpX5V3Z7EhhZ8wYnOPIoL4dz2g4U= -k8s.io/kube-scheduler v0.26.1/go.mod h1:9SZcwHMANGrfXCJUOw/rPi8Iwd0X4HXx4czCHcR4HiU= -k8s.io/kubectl v0.26.1 h1:K8A0Jjlwg8GqrxOXxAbjY5xtmXYeYjLU96cHp2WMQ7s= -k8s.io/kubectl v0.26.1/go.mod h1:miYFVzldVbdIiXMrHZYmL/EDWwJKM+F0sSsdxsATFPo= -k8s.io/kubelet v0.26.1 h1:wQyCQYmLW6GN3v7gVTxnc3jAE4zMYDlzdF3FZV4rKas= -k8s.io/kubelet v0.26.1/go.mod h1:gFVZ1Ab4XdjtnYdVRATwGwku7FhTxo6LVEZwYoQaDT8= -k8s.io/kubernetes v1.26.1 h1:N+qxlptxpSU/VSLvqBGWyyw/kNhJRpEn1b5YP57+5rk= -k8s.io/kubernetes v1.26.1/go.mod h1:dEfAfGVZBOr2uZLeVazLPj/8E+t8jYFbQqCiBudkB8o= -k8s.io/legacy-cloud-providers v0.26.1 h1:1uWqF3CkFVzwOQyFObJRjTMkIe8bMBthxrJLoYx00l8= -k8s.io/legacy-cloud-providers v0.26.1/go.mod h1:Jw8kvGYWOKSuuefb6ascH5z/YvTZ/ouwzVqUKmm5upk= -k8s.io/metrics v0.26.1 h1:iB+QdMLa2V70a7zb0XYEcaUpPM0y+p4fZN0UtxcPHLk= -k8s.io/metrics v0.26.1/go.mod h1:fMeLXmK/xgvckFG63GJ0kDjFiQH7P0Dpi5Lvhlo5DXE= -k8s.io/mount-utils v0.26.1 h1:deN1IBPyi5UFEAgQYXBEDUejzQUNzRC1ML7BUMWljzA= -k8s.io/mount-utils v0.26.1/go.mod h1:au99w4FWU5ZWelLb3Yx6kJc8RZ387IyWVM9tN65Yhxo= -k8s.io/pod-security-admission v0.26.1 h1:EDIxsYFeKMzNvN/JB0PgQcuwBP6fIkIG2O8ZWJhzOp4= -k8s.io/pod-security-admission v0.26.1/go.mod h1:hCbYTG5UtLlivmukkMPjAWf23PUBUHzEvR60xNVWN4c= +k8s.io/klog/v2 v2.9.0 h1:D7HV+n1V57XeZ0m6tdRkfknthUaM06VFbWldOFh8kzM= +k8s.io/klog/v2 v2.9.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec= +k8s.io/kube-aggregator v0.22.2/go.mod h1:hsd0LEmVQSvMc0UzAwmcm/Gk3HzLp50mq/o6cu1ky2A= +k8s.io/kube-controller-manager v0.22.2/go.mod h1:n8Wh6HHmB+EBy3INhucPEeyZE05qtq8ZWcBgFREYwBk= +k8s.io/kube-openapi v0.0.0-20211109043538-20434351676c h1:jvamsI1tn9V0S8jicyX82qaFC0H/NKxv2e5mbqsgR80= +k8s.io/kube-openapi v0.0.0-20211109043538-20434351676c/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw= +k8s.io/kube-proxy v0.22.2/go.mod h1:pk0QwfYdTsg7aC9ycMF5MFbasIxhBAPFCvfwdmNikZs= +k8s.io/kube-scheduler v0.22.2/go.mod h1:aaElZivB8w1u8Ki7QcwuRSL7AcVWC7xa0LzeiT8zQ7I= +k8s.io/kubectl v0.22.2/go.mod h1:BApg2j0edxLArCOfO0ievI27EeTQqBDMNU9VQH734iQ= +k8s.io/kubelet v0.22.2 h1:7ol5AXXxcW97dUE8W/QiPjkXu1ZuGshG5VmgDmviZsc= +k8s.io/kubelet v0.22.2/go.mod h1:ORIRua2/wTcx5UnEvxWosu650/8fatmzbMRC7m6WjAM= +k8s.io/kubernetes v1.22.2 h1:EkPl3JQjkm9UA7dteLJJQOEwTsJbVINEJtaHAzm/OvE= +k8s.io/kubernetes v1.22.2/go.mod h1:Snea7fgIObGgHmLbUJ3OgjGEr5bjj16iEdp5oHS6eS8= +k8s.io/legacy-cloud-providers v0.22.2/go.mod h1:oC6zhm9nhJ5M4VTDHzsO/4MpddZR5JqEt55zZ52JRMc= +k8s.io/metrics v0.22.2 h1:ZQbsg2ENzp+JyhQMp3tsFZK9i5KxvSTDrdkgoWRL568= +k8s.io/metrics v0.22.2/go.mod h1:GUcsBtpsqQD1tKFS/2wCKu4ZBowwRncLOJH1rgWs3uw= +k8s.io/mount-utils v0.22.2 h1:w/CJq+Cofkr81Rp89UkokgEbuu8Js0LwMI/RWWEE+gs= +k8s.io/mount-utils v0.22.2/go.mod h1:dHl6c2P60T5LHUnZxVslyly9EDCMzvhtISO5aY+Z4sk= +k8s.io/pod-security-admission v0.22.2 h1:HJxe31fI1VLR3wYbICOdWKb9tXPnoh8sgA2gC4yTr4E= +k8s.io/pod-security-admission v0.22.2/go.mod h1:5FK/TIw6rySU522cZVueMcS/LPPovNHbsm1I1gLfVfU= +k8s.io/sample-apiserver v0.22.2/go.mod h1:h+/DIV5EmuNq4vfPr5TSXy9mIBVXXlPAKQMPbjPrlFM= +k8s.io/system-validators v1.5.0/go.mod h1:bPldcLgkIUK22ALflnsXk8pvkTEndYdNuaHH6gRrl0Q= +k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +k8s.io/utils v0.0.0-20210819203725-bdf08cb9a70a/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20221128185143-99ec85e7a448 h1:KTgPnR10d5zhztWptI952TNtt/4u5h3IzDXkdIMuo2Y= k8s.io/utils v0.0.0-20221128185143-99ec85e7a448/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +modernc.org/cc v1.0.0/go.mod h1:1Sk4//wdnYJiUIxnW8ddKpaOJCF37yAdqYnkxUpaYxw= +modernc.org/golex v1.0.0/go.mod h1:b/QX9oBD/LhixY6NDh+IdGv17hgB+51fET1i2kPSmvk= +modernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k= +modernc.org/strutil v1.0.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs= +modernc.org/xc v1.0.0/go.mod h1:mRNCo0bvLjGhHO9WsyuKVU4q0ceiDDDoEeWDJHrNx8I= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.22/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.35 h1:+xBL5uTc+BkPBwmMi3vYfUJjq+N3K+H6PXeETwf5cPI= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.35/go.mod h1:WxjusMwXlKzfAs4p9km6XJRndVt2FROgMVCE4cdohFo= -sigs.k8s.io/controller-runtime v0.14.6 h1:oxstGVvXGNnMvY7TAESYk+lzr6S3V5VFxQ6d92KcwQA= -sigs.k8s.io/controller-runtime v0.14.6/go.mod h1:WqIdsAY6JBsjfc/CqO0CORmNtoCtE4S6qbPc9s68h+0= -sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 h1:iXTIw73aPyC+oRdyqqvVJuloN1p0AC/kzH07hu3NE+k= -sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= +sigs.k8s.io/controller-runtime v0.10.3 h1:s5Ttmw/B4AuIbwrXD3sfBkXwnPMMWrqpVj4WRt1dano= +sigs.k8s.io/controller-runtime v0.10.3/go.mod h1:CQp8eyUQZ/Q7PJvnIrB6/hgfTC1kBkGylwsLgOQi1WY= +sigs.k8s.io/kustomize/api v0.8.11/go.mod h1:a77Ls36JdfCWojpUqR6m60pdGY1AYFix4AH83nJtY1g= +sigs.k8s.io/kustomize/cmd/config v0.9.13/go.mod h1:7547FLF8W/lTaDf0BDqFTbZxM9zqwEJqCKN9sSR0xSs= +sigs.k8s.io/kustomize/kustomize/v4 v4.2.0/go.mod h1:MOkR6fmhwG7hEDRXBYELTi5GSFcLwfqwzTRHW3kv5go= +sigs.k8s.io/kustomize/kyaml v0.11.0/go.mod h1:GNMwjim4Ypgp/MueD3zXHLRJEjz7RvtPae0AwlvEMFM= +sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= +sigs.k8s.io/structured-merge-diff/v4 v4.1.2/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= diff --git a/pkg/core/handler/cluster/cluster.go b/pkg/core/handler/cluster/cluster.go index ff4f376e..b70ab76f 100644 --- a/pkg/core/handler/cluster/cluster.go +++ b/pkg/core/handler/cluster/cluster.go @@ -20,11 +20,6 @@ import ( "strconv" "strings" - "github.com/KusionStack/karpor/pkg/core/handler" - "github.com/KusionStack/karpor/pkg/core/manager/cluster" - "github.com/KusionStack/karpor/pkg/infra/multicluster" - "github.com/KusionStack/karpor/pkg/util/clusterinstall" - "github.com/KusionStack/karpor/pkg/util/ctxutil" "github.com/go-chi/chi/v5" "github.com/pkg/errors" _ "k8s.io/api/core/v1" @@ -33,6 +28,12 @@ import ( "k8s.io/apiserver/pkg/server" "k8s.io/client-go/tools/clientcmd" k8syaml "sigs.k8s.io/yaml" + + "github.com/KusionStack/karpor/pkg/core/handler" + "github.com/KusionStack/karpor/pkg/core/manager/cluster" + "github.com/KusionStack/karpor/pkg/infra/multicluster" + "github.com/KusionStack/karpor/pkg/util/clusterinstall" + "github.com/KusionStack/karpor/pkg/util/ctxutil" ) // Get returns an HTTP handler function that reads a cluster @@ -112,7 +113,8 @@ func Create(clusterMgr *cluster.ClusterManager, c *server.CompletedConfig) http. } client, _ := multicluster.BuildMultiClusterClient(r.Context(), c.LoopbackClientConfig, "") - clusterCreated, err := clusterMgr.CreateCluster(r.Context(), client, cluster, payload.ClusterDisplayName, payload.ClusterDescription, payload.ClusterKubeConfig) + clusterCreated, err := clusterMgr.CreateCluster(r.Context(), client, cluster, payload.ClusterDisplayName, payload.ClusterDescription, + payload.ClusterMode, payload.ClusterKubeConfig, payload.ClusterLevel) handler.HandleResult(w, r, ctx, err, clusterCreated) } } @@ -242,13 +244,15 @@ func Delete(clusterMgr *cluster.ClusterManager, c *server.CompletedConfig) http. // @Tags cluster // @Accept multipart/form-data // @Produce plain -// @Param file formData file true "Upload file with field name 'file'" -// @Param name formData string true "cluster name" -// @Param displayName formData string true "cluster display name" -// @Param description formData string true "cluster description" -// @Success 200 {object} UploadData "Returns the content of the uploaded KubeConfig file." -// @Failure 400 {string} string "The uploaded file is too large or the request is invalid." -// @Failure 500 {string} string "Internal server error." +// @Param file formData file true "Upload file with field name 'file'" +// @Param name formData string true "cluster name" +// @Param displayName formData string true "cluster display name" +// @Param description formData string true "cluster description" +// @Param clusterMode formData string true "cluster mode" +// @Param clusterLevel formData int true "cluster scale level" +// @Success 200 {object} UploadData "Returns the content of the uploaded KubeConfig file." +// @Failure 400 {string} string "The uploaded file is too large or the request is invalid." +// @Failure 500 {string} string "Internal server error." // @Router /rest-api/v1/cluster/config/file [post] func UploadKubeConfig(clusterMgr *cluster.ClusterManager) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { @@ -271,6 +275,13 @@ func UploadKubeConfig(clusterMgr *cluster.ClusterManager) http.HandlerFunc { name := r.FormValue("name") displayName := r.FormValue("displayName") description := r.FormValue("description") + clusterMode := r.FormValue("clusterMode") + clusterLevel := r.FormValue("clusterLevel") + level, err := strconv.Atoi(clusterLevel) + if err != nil { + log.Info("failed to parse cluster level") + level = 1 + } file, fileHeader, err := r.FormFile("file") if err != nil { handler.FailureRender(ctx, w, r, errors.Wrapf(err, "failed to get uploaded file")) @@ -297,7 +308,7 @@ func UploadKubeConfig(clusterMgr *cluster.ClusterManager) http.HandlerFunc { } // Convert the rest.Config to Cluster object. - clusterObj, err := clusterinstall.ConvertKubeconfigToCluster(name, displayName, description, restConfig) + clusterObj, err := clusterinstall.ConvertKubeconfigToCluster(name, displayName, description, clusterMode, level, restConfig) if err != nil { handler.FailureRender(ctx, w, r, errors.Wrapf(err, "error convert kubeconfig to cluster")) return @@ -343,12 +354,12 @@ func UploadKubeConfig(clusterMgr *cluster.ClusterManager) http.HandlerFunc { // @Accept json // @Produce json // @Param request body ValidatePayload true "KubeConfig payload to validate" -// @Success 200 {string} string "Verification passed server version" -// @Failure 400 {object} string "Bad Request" -// @Failure 401 {object} string "Unauthorized" -// @Failure 429 {object} string "Too Many Requests" -// @Failure 404 {object} string "Not Found" -// @Failure 500 {object} string "Internal Server Error" +// @Success 200 {string} string "Verification passed server version" +// @Failure 400 {object} string "Bad Request" +// @Failure 401 {object} string "Unauthorized" +// @Failure 429 {object} string "Too Many Requests" +// @Failure 404 {object} string "Not Found" +// @Failure 500 {object} string "Internal Server Error" // @Router /rest-api/v1/cluster/config/validate [post] func ValidateKubeConfig(clusterMgr *cluster.ClusterManager) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { @@ -381,3 +392,40 @@ func ValidateKubeConfig(clusterMgr *cluster.ClusterManager) http.HandlerFunc { } } } + +// GetAgentYml returns an HTTP handler function to obtain the agent yaml of the special cluster. +// +// @Summary Get agent yaml +// @Description Obtain the agent yaml in secret for cluster. +// @Tags cluster +// @Accept plain +// @Accept json +// @Produce json +// @Param clusterName path string true "The name of the cluster" +// @Success 200 {string} string "Verification passed server version" +// @Failure 400 {object} string "Bad Request" +// @Failure 401 {object} string "Unauthorized" +// @Failure 429 {object} string "Too Many Requests" +// @Failure 404 {object} string "Not Found" +// @Failure 500 {object} string "Internal Server Error" +// @Router /rest-api/v1/cluster/{clusterName}/agentYml [get] +func GetAgentYml(clusterMgr *cluster.ClusterManager, c *server.CompletedConfig) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + // Extract the context and logger from the request. + ctx := r.Context() + logger := ctxutil.GetLogger(ctx) + cluster := chi.URLParam(r, "clusterName") + logger.Info("Getting cluster...", "cluster", cluster) + + client, err := multicluster.BuildHubClients(r.Context(), c.LoopbackClientConfig) + if err != nil { + handler.FailureRender(ctx, w, r, err) + return + } + + agentYaml, err := clusterMgr.GetAgentYamlForCluster(r.Context(), client, cluster) + handler.HandleResult(w, r, ctx, err, map[string]string{ + "agentYml": string(agentYaml), + }) + } +} diff --git a/pkg/core/handler/cluster/types.go b/pkg/core/handler/cluster/types.go index e61bd5a6..85c6542a 100644 --- a/pkg/core/handler/cluster/types.go +++ b/pkg/core/handler/cluster/types.go @@ -33,9 +33,11 @@ var sortCriteriaMap = map[string]cluster.SortCriteria{ // //nolint:tagliatelle type ClusterPayload struct { - ClusterDisplayName string `json:"displayName"` // ClusterDisplayName is the display name of cluster to be created - ClusterDescription string `json:"description"` // ClusterDescription is the description of cluster to be created - ClusterKubeConfig string `json:"kubeConfig"` // ClusterKubeConfig is the kubeconfig of cluster to be created + ClusterDisplayName string `json:"displayName"` // ClusterDisplayName is the display name of cluster to be created + ClusterDescription string `json:"description"` // ClusterDescription is the description of cluster to be created + ClusterKubeConfig string `json:"kubeConfig"` // ClusterKubeConfig is the kubeconfig of cluster to be created + ClusterMode string `json:"clusterMode"` // ClusterMode is the mode of cluster to be created + ClusterLevel int `json:"clusterLevel"` // clusterLevel is the scale level of cluster to be created } type UploadData struct { diff --git a/pkg/core/manager/cluster/manager.go b/pkg/core/manager/cluster/manager.go index 84faea32..b810b5cb 100644 --- a/pkg/core/manager/cluster/manager.go +++ b/pkg/core/manager/cluster/manager.go @@ -20,11 +20,6 @@ import ( "fmt" "time" - "github.com/KusionStack/karpor/pkg/core/handler" - "github.com/KusionStack/karpor/pkg/infra/multicluster" - clusterv1beta1 "github.com/KusionStack/karpor/pkg/kubernetes/apis/cluster/v1beta1" - "github.com/KusionStack/karpor/pkg/util/clusterinstall" - "github.com/KusionStack/karpor/pkg/util/ctxutil" errors2 "github.com/pkg/errors" yaml "gopkg.in/yaml.v3" v1 "k8s.io/api/core/v1" @@ -35,7 +30,14 @@ import ( "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" "k8s.io/client-go/tools/clientcmd" + corev1 "k8s.io/kubernetes/pkg/apis/core/v1" k8syaml "sigs.k8s.io/yaml" + + "github.com/KusionStack/karpor/pkg/core/handler" + "github.com/KusionStack/karpor/pkg/infra/multicluster" + clusterv1beta1 "github.com/KusionStack/karpor/pkg/kubernetes/apis/cluster/v1beta1" + "github.com/KusionStack/karpor/pkg/util/clusterinstall" + "github.com/KusionStack/karpor/pkg/util/ctxutil" ) type ClusterManager struct{} @@ -68,7 +70,7 @@ func (c *ClusterManager) GetCluster( func (c *ClusterManager) CreateCluster( ctx context.Context, client *multicluster.MultiClusterClient, - name, displayName, description, kubeconfig string, + name, displayName, description, clusterMode, kubeconfig string, clusterLevel int, ) (*unstructured.Unstructured, error) { clusterGVR := clusterv1beta1.SchemeGroupVersion.WithResource("clusters") // Make sure the cluster does not exist first @@ -91,6 +93,8 @@ func (c *ClusterManager) CreateCluster( name, displayName, description, + clusterMode, + clusterLevel, restConfig, ) if err != nil { @@ -142,6 +146,8 @@ func (c *ClusterManager) UpdateCredential( } displayName := currentObj.Object["spec"].(map[string]interface{})["displayName"].(string) description := currentObj.Object["spec"].(map[string]interface{})["description"].(string) + clusterMode := currentObj.Object["spec"].(map[string]interface{})["mode"].(string) + clusterLevel := currentObj.Object["spec"].(map[string]interface{})["level"].(int) // Create new restConfig from updated kubeconfig restConfig, err := clientcmd.RESTConfigFromKubeConfig([]byte(kubeconfig)) @@ -153,7 +159,9 @@ func (c *ClusterManager) UpdateCredential( clusterObj, err := clusterinstall.ConvertKubeconfigToCluster( name, displayName, + clusterMode, description, + clusterLevel, restConfig, ) if err != nil { @@ -466,3 +474,22 @@ func (c *ClusterManager) ValidateKubeConfigFor( return info.String(), nil } } + +// GetAgentYamlForCluster returns the agent yaml byte for a given cluster +func (c *ClusterManager) GetAgentYamlForCluster( + ctx context.Context, + client *multicluster.MultiClusterClient, + name string, +) ([]byte, error) { + secret, err := client.ClientSet.CoreV1().Secrets("karpor").Get(ctx, fmt.Sprintf("%s-agent", name), metav1.GetOptions{}) + if err != nil && !errors.IsNotFound(err) { + return nil, err + } + if errors.IsNotFound(err) { + return nil, nil + } + if secret == nil || secret.Data == nil { + return nil, errors.NewNotFound(corev1.Resource("secrets"), name) + } + return secret.Data["config"], nil +} diff --git a/pkg/core/manager/cluster/manager_test.go b/pkg/core/manager/cluster/manager_test.go index 3e8f5682..0850b9ed 100644 --- a/pkg/core/manager/cluster/manager_test.go +++ b/pkg/core/manager/cluster/manager_test.go @@ -20,22 +20,24 @@ import ( "fmt" "testing" - "github.com/KusionStack/karpor/pkg/infra/multicluster" - clusterv1beta1 "github.com/KusionStack/karpor/pkg/kubernetes/apis/cluster/v1beta1" "github.com/bytedance/mockey" "github.com/stretchr/testify/require" "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/client-go/dynamic" + "k8s.io/client-go/dynamic/fake" k8syaml "sigs.k8s.io/yaml" + + "github.com/KusionStack/karpor/pkg/infra/multicluster" + clusterv1beta1 "github.com/KusionStack/karpor/pkg/kubernetes/apis/cluster/v1beta1" ) // TestGetCluster tests the GetCluster method of the ClusterManager for various // scenarios. func TestGetCluster(t *testing.T) { manager := NewClusterManager() - mockey.Mock((*dynamic.DynamicClient).Resource).Return(&mockNamespaceableResource{}).Build() + mockey.Mock((*fake.FakeDynamicClient).Resource).Return(&mockNamespaceableResource{}).Build() defer mockey.UnPatchAll() testCases := []struct { @@ -59,7 +61,9 @@ func TestGetCluster(t *testing.T) { t.Run(tc.name, func(t *testing.T) { cluster, err := manager.GetCluster( context.TODO(), - &multicluster.MultiClusterClient{}, + &multicluster.MultiClusterClient{ + DynamicClient: &fake.FakeDynamicClient{}, + }, tc.clusterName, ) if tc.expectError { @@ -124,7 +128,7 @@ func TestValidateKubeConfigFor(t *testing.T) { // various scenarios. func TestCreateCluster(t *testing.T) { manager := NewClusterManager() - mockey.Mock((*dynamic.DynamicClient).Resource).Return(&mockNamespaceableResource{}).Build() + mockey.Mock((*fake.FakeDynamicClient).Resource).Return(&mockNamespaceableResource{}).Build() defer mockey.UnPatchAll() testCases := []struct { @@ -132,6 +136,8 @@ func TestCreateCluster(t *testing.T) { clusterName string displayName string description string + clusterMode string + clusterLevel int kubeConfig string expectError bool expectedErrorMessage string @@ -168,11 +174,15 @@ func TestCreateCluster(t *testing.T) { t.Run(tc.name, func(t *testing.T) { cluster, err := manager.CreateCluster( context.TODO(), - &multicluster.MultiClusterClient{}, + &multicluster.MultiClusterClient{ + DynamicClient: &fake.FakeDynamicClient{}, + }, tc.clusterName, tc.displayName, tc.description, + tc.clusterMode, tc.kubeConfig, + tc.clusterLevel, ) if tc.expectError { @@ -196,7 +206,7 @@ func TestCreateCluster(t *testing.T) { // various scenarios. func TestUpdateMetadata(t *testing.T) { manager := NewClusterManager() - mockey.Mock((*dynamic.DynamicClient).Resource).Return(&mockNamespaceableResource{}).Build() + mockey.Mock((*fake.FakeDynamicClient).Resource).Return(&mockNamespaceableResource{}).Build() defer mockey.UnPatchAll() testCases := []struct { @@ -204,6 +214,8 @@ func TestUpdateMetadata(t *testing.T) { clusterName string displayName string description string + clusterMode string + clusterLevel int expectError bool expectedError string }{ @@ -235,7 +247,9 @@ func TestUpdateMetadata(t *testing.T) { t.Run(tc.name, func(t *testing.T) { updatedCluster, err := manager.UpdateMetadata( context.TODO(), - &multicluster.MultiClusterClient{}, + &multicluster.MultiClusterClient{ + DynamicClient: &fake.FakeDynamicClient{}, + }, tc.clusterName, tc.displayName, tc.description, @@ -266,7 +280,7 @@ func TestUpdateMetadata(t *testing.T) { // for various scenarios. func TestUpdateCredential(t *testing.T) { manager := NewClusterManager() - mockey.Mock((*dynamic.DynamicClient).Resource).Return(&mockNamespaceableResource{}).Build() + mockey.Mock((*fake.FakeDynamicClient).Resource).Return(&mockNamespaceableResource{}).Build() defer mockey.UnPatchAll() testCases := []struct { @@ -302,7 +316,9 @@ func TestUpdateCredential(t *testing.T) { t.Run(tc.name, func(t *testing.T) { updatedCluster, err := manager.UpdateCredential( context.TODO(), - &multicluster.MultiClusterClient{}, + &multicluster.MultiClusterClient{ + DynamicClient: &fake.FakeDynamicClient{}, + }, tc.clusterName, tc.kubeConfig, ) @@ -326,7 +342,7 @@ func TestUpdateCredential(t *testing.T) { // various scenarios. func TestDeleteCluster(t *testing.T) { manager := NewClusterManager() - mockey.Mock((*dynamic.DynamicClient).Resource).Return(&mockNamespaceableResource{}).Build() + mockey.Mock((*fake.FakeDynamicClient).Resource).Return(&mockNamespaceableResource{}).Build() defer mockey.UnPatchAll() testCases := []struct { @@ -350,7 +366,9 @@ func TestDeleteCluster(t *testing.T) { t.Run(tc.name, func(t *testing.T) { err := manager.DeleteCluster( context.TODO(), - &multicluster.MultiClusterClient{}, + &multicluster.MultiClusterClient{ + DynamicClient: &fake.FakeDynamicClient{}, + }, tc.clusterName, ) if tc.expectError { @@ -366,7 +384,7 @@ func TestDeleteCluster(t *testing.T) { // clusters. func TestListCluster(t *testing.T) { manager := NewClusterManager() - mockey.Mock((*dynamic.DynamicClient).Resource).Return(&mockNamespaceableResource{}).Build() + mockey.Mock((*fake.FakeDynamicClient).Resource).Return(&mockNamespaceableResource{}).Build() defer mockey.UnPatchAll() testCases := []struct { @@ -393,7 +411,9 @@ func TestListCluster(t *testing.T) { t.Run(tc.name, func(t *testing.T) { result, err := manager.ListCluster( context.TODO(), - &multicluster.MultiClusterClient{}, + &multicluster.MultiClusterClient{ + DynamicClient: &fake.FakeDynamicClient{}, + }, tc.orderBy, tc.descending, ) @@ -411,7 +431,7 @@ func TestListCluster(t *testing.T) { // cluster names. func TestListClusterName(t *testing.T) { manager := NewClusterManager() - mockey.Mock((*dynamic.DynamicClient).Resource).Return(&mockNamespaceableResource{}).Build() + mockey.Mock((*fake.FakeDynamicClient).Resource).Return(&mockNamespaceableResource{}).Build() defer mockey.UnPatchAll() testCases := []struct { @@ -438,7 +458,9 @@ func TestListClusterName(t *testing.T) { t.Run(tc.name, func(t *testing.T) { names, err := manager.ListClusterName( context.TODO(), - &multicluster.MultiClusterClient{}, + &multicluster.MultiClusterClient{ + DynamicClient: &fake.FakeDynamicClient{}, + }, tc.orderBy, tc.descending, ) @@ -455,7 +477,7 @@ func TestListClusterName(t *testing.T) { // TestGetYAMLForCluster tests the GetYAMLForCluster method. func TestGetYAMLForCluster(t *testing.T) { manager := NewClusterManager() - mockey.Mock((*dynamic.DynamicClient).Resource).Return(&mockNamespaceableResource{}).Build() + mockey.Mock((*fake.FakeDynamicClient).Resource).Return(&mockNamespaceableResource{}).Build() defer mockey.UnPatchAll() testCases := []struct { @@ -484,6 +506,8 @@ spec: privateKey: M2I5NioqKioqKioqKioqKioqKioqKioqKioqKjY1MzY= description: mock-description displayName: Existing Cluster + level: 2 + mode: pull `, }, { @@ -497,7 +521,9 @@ spec: t.Run(tc.name, func(t *testing.T) { yamlData, err := manager.GetYAMLForCluster( context.TODO(), - &multicluster.MultiClusterClient{}, + &multicluster.MultiClusterClient{ + DynamicClient: &fake.FakeDynamicClient{}, + }, tc.clusterName, ) @@ -731,6 +757,8 @@ func newMockCluster(name string) *unstructured.Unstructured { "caBundle": "sensitive-ca-bundle", }, }, + "mode": "pull", + "level": 2, } // Set annotations on the object @@ -821,7 +849,7 @@ func (m *mockNamespaceableResource) Delete( func TestClusterManager_GetNamespace(t *testing.T) { manager := NewClusterManager() - mockey.Mock((*dynamic.DynamicClient).Resource).Return(&mockNamespaceableResource{}).Build() + mockey.Mock((*fake.FakeDynamicClient).Resource).Return(&mockNamespaceableResource{}).Build() defer mockey.UnPatchAll() testCases := []struct { @@ -842,7 +870,7 @@ func TestClusterManager_GetNamespace(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { summary, err := manager.GetNamespace(context.TODO(), &multicluster.MultiClusterClient{ - DynamicClient: &dynamic.DynamicClient{}, + DynamicClient: &fake.FakeDynamicClient{}, }, tc.namespace) if tc.expectError { @@ -857,7 +885,7 @@ func TestClusterManager_GetNamespace(t *testing.T) { func TestClusterManager_GetNamespaceYAML(t *testing.T) { manager := NewClusterManager() - mockey.Mock((*dynamic.DynamicClient).Resource).Return(&mockNamespaceableResource{}).Build() + mockey.Mock((*fake.FakeDynamicClient).Resource).Return(&mockNamespaceableResource{}).Build() defer mockey.UnPatchAll() expectResult, _ := k8syaml.Marshal(newMockCluster("existing-cluster")) @@ -879,7 +907,7 @@ func TestClusterManager_GetNamespaceYAML(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { summary, err := manager.GetNamespaceYAML(context.TODO(), &multicluster.MultiClusterClient{ - DynamicClient: &dynamic.DynamicClient{}, + DynamicClient: &fake.FakeDynamicClient{}, }, tc.namespace) if tc.expectError { diff --git a/pkg/core/manager/cluster/types.go b/pkg/core/manager/cluster/types.go index 55d2b973..e633d629 100644 --- a/pkg/core/manager/cluster/types.go +++ b/pkg/core/manager/cluster/types.go @@ -104,3 +104,7 @@ type User struct { Username string `yaml:"username,omitempty"` Password string `yaml:"password,omitempty"` } + +type AgentYml struct { + AgentYml string `json:"agentYml"` +} diff --git a/pkg/core/manager/insight/events_test.go b/pkg/core/manager/insight/events_test.go index f1934d72..e6a1c3d5 100644 --- a/pkg/core/manager/insight/events_test.go +++ b/pkg/core/manager/insight/events_test.go @@ -18,11 +18,12 @@ import ( "context" "testing" - "github.com/KusionStack/karpor/pkg/core/entity" "github.com/bytedance/mockey" "github.com/stretchr/testify/require" genericapiserver "k8s.io/apiserver/pkg/server" - "k8s.io/client-go/dynamic" + "k8s.io/client-go/dynamic/fake" + + "github.com/KusionStack/karpor/pkg/core/entity" ) func TestInsightManager_GetResourceEvents(t *testing.T) { @@ -31,7 +32,7 @@ func TestInsightManager_GetResourceEvents(t *testing.T) { require.NoError(t, err, "Unexpected error initializing InsightManager") // Set up mocks for dynamic client - mockey.Mock((*dynamic.DynamicClient).Resource).Return(&mockEventResource{}).Build() + mockey.Mock((*fake.FakeDynamicClient).Resource).Return(&mockEventResource{}).Build() defer mockey.UnPatchAll() // Test cases @@ -87,7 +88,7 @@ func TestInsightManager_GetNamespaceGVKEvents(t *testing.T) { require.NoError(t, err, "Unexpected error initializing InsightManager") // Set up mocks for dynamic client - mockey.Mock((*dynamic.DynamicClient).Resource).Return(&mockEventResource{}).Build() + mockey.Mock((*fake.FakeDynamicClient).Resource).Return(&mockEventResource{}).Build() defer mockey.UnPatchAll() // Test cases @@ -141,7 +142,8 @@ func TestInsightManager_GetNamespaceEvents(t *testing.T) { require.NoError(t, err, "Unexpected error initializing InsightManager") // Set up mocks for dynamic client - mockey.Mock((*dynamic.DynamicClient).Resource).Return(&mockEventResource{}).Build() + mockey.Mock((*fake.FakeDynamicClient).Resource).Return(&mockEventResource{}).Build() + defer mockey.UnPatchAll() // Test cases @@ -197,7 +199,7 @@ func TestInsightManager_GetGVKEvents(t *testing.T) { require.NoError(t, err, "Unexpected error initializing InsightManager") // Set up mocks for dynamic client - mockey.Mock((*dynamic.DynamicClient).Resource).Return(&mockEventResource{}).Build() + mockey.Mock((*fake.FakeDynamicClient).Resource).Return(&mockEventResource{}).Build() defer mockey.UnPatchAll() // Test cases @@ -253,7 +255,7 @@ func TestInsightManager_GetClusterEvents(t *testing.T) { require.NoError(t, err, "Unexpected error initializing InsightManager") // Set up mocks for dynamic client - mockey.Mock((*dynamic.DynamicClient).Resource).Return(&mockEventResource{}).Build() + mockey.Mock((*fake.FakeDynamicClient).Resource).Return(&mockEventResource{}).Build() defer mockey.UnPatchAll() // Test cases diff --git a/pkg/core/manager/insight/resource_test.go b/pkg/core/manager/insight/resource_test.go index c8ef0aa2..67a0e398 100644 --- a/pkg/core/manager/insight/resource_test.go +++ b/pkg/core/manager/insight/resource_test.go @@ -19,12 +19,13 @@ import ( "reflect" "testing" - "github.com/KusionStack/karpor/pkg/core/entity" "github.com/bytedance/mockey" "github.com/stretchr/testify/require" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" genericapiserver "k8s.io/apiserver/pkg/server" - "k8s.io/client-go/dynamic" + "k8s.io/client-go/dynamic/fake" + + "github.com/KusionStack/karpor/pkg/core/entity" ) // TestGetResource tests the TestGetResource method of the InsightManager for @@ -35,7 +36,7 @@ func TestGetResource(t *testing.T) { require.NoError(t, err, "Unexpected error initializing InsightManager") // Set up mocks for dynamic client - mockey.Mock((*dynamic.DynamicClient).Resource).Return(&mockNamespaceableResource{}).Build() + mockey.Mock((*fake.FakeDynamicClient).Resource).Return(&mockNamespaceableResource{}).Build() defer mockey.UnPatchAll() // Test cases @@ -109,7 +110,7 @@ func TestInsightManager_GetYAMLForResource(t *testing.T) { require.NoError(t, err, "Unexpected error initializing InsightManager") // Set up mocks for dynamic client - mockey.Mock((*dynamic.DynamicClient).Resource).Return(&mockNamespaceableResource{}).Build() + mockey.Mock((*fake.FakeDynamicClient).Resource).Return(&mockNamespaceableResource{}).Build() defer mockey.UnPatchAll() // Test cases diff --git a/pkg/core/manager/insight/summary_test.go b/pkg/core/manager/insight/summary_test.go index 1cba9db5..2bf8bd7f 100644 --- a/pkg/core/manager/insight/summary_test.go +++ b/pkg/core/manager/insight/summary_test.go @@ -18,15 +18,16 @@ import ( "context" "testing" - "github.com/KusionStack/karpor/pkg/core/entity" "github.com/bytedance/mockey" "github.com/stretchr/testify/require" "k8s.io/apimachinery/pkg/api/resource" "k8s.io/apimachinery/pkg/version" genericapiserver "k8s.io/apiserver/pkg/server" "k8s.io/client-go/discovery" - "k8s.io/client-go/dynamic" + "k8s.io/client-go/dynamic/fake" "k8s.io/client-go/kubernetes" + + "github.com/KusionStack/karpor/pkg/core/entity" ) func TestInsightManager_GetResourceSummary(t *testing.T) { @@ -36,7 +37,7 @@ func TestInsightManager_GetResourceSummary(t *testing.T) { require.NoError(t, err, "Unexpected error initializing InsightManager") // Set up mocks for dynamic client - mockey.Mock((*dynamic.DynamicClient).Resource).Return(&mockNamespaceableResource{}).Build() + mockey.Mock((*fake.FakeDynamicClient).Resource).Return(&mockNamespaceableResource{}).Build() defer mockey.UnPatchAll() // Test cases @@ -95,7 +96,7 @@ func TestInsightManager_GetGVKSummary(t *testing.T) { require.NoError(t, err, "Unexpected error initializing InsightManager") // Set up mocks for dynamic client - mockey.Mock((*dynamic.DynamicClient).Resource).Return(&mockNamespaceableResource{}).Build() + mockey.Mock((*fake.FakeDynamicClient).Resource).Return(&mockNamespaceableResource{}).Build() defer mockey.UnPatchAll() // Test cases @@ -152,7 +153,7 @@ func TestInsightManager_GetNamespaceSummary(t *testing.T) { require.NoError(t, err, "Unexpected error initializing InsightManager") // Set up mocks for dynamic client - mockey.Mock((*dynamic.DynamicClient).Resource).Return(&mockNamespaceableResource{}).Build() + mockey.Mock((*fake.FakeDynamicClient).Resource).Return(&mockNamespaceableResource{}).Build() defer mockey.UnPatchAll() // Test cases diff --git a/pkg/core/manager/insight/test_helper.go b/pkg/core/manager/insight/test_helper.go index 9173cf89..c3670ab4 100644 --- a/pkg/core/manager/insight/test_helper.go +++ b/pkg/core/manager/insight/test_helper.go @@ -17,9 +17,6 @@ package insight import ( "context" - "github.com/KusionStack/karpor/pkg/core/entity" - "github.com/KusionStack/karpor/pkg/infra/multicluster" - "github.com/KusionStack/karpor/pkg/infra/search/storage" coreV1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/resource" @@ -28,8 +25,13 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/client-go/discovery" "k8s.io/client-go/dynamic" + "k8s.io/client-go/dynamic/fake" "k8s.io/client-go/kubernetes" v1 "k8s.io/client-go/kubernetes/typed/core/v1" + + "github.com/KusionStack/karpor/pkg/core/entity" + "github.com/KusionStack/karpor/pkg/infra/multicluster" + "github.com/KusionStack/karpor/pkg/infra/search/storage" ) // mockMultiClusterClient returns a mock MultiClusterClient for testing purposes. @@ -38,7 +40,7 @@ func mockMultiClusterClient() *multicluster.MultiClusterClient { ClientSet: &kubernetes.Clientset{ DiscoveryClient: &discovery.DiscoveryClient{}, }, - DynamicClient: &dynamic.DynamicClient{}, + DynamicClient: &fake.FakeDynamicClient{}, MetricsClient: nil, } } diff --git a/pkg/core/manager/insight/topology_test.go b/pkg/core/manager/insight/topology_test.go index 894534a4..8b0c24b0 100644 --- a/pkg/core/manager/insight/topology_test.go +++ b/pkg/core/manager/insight/topology_test.go @@ -19,17 +19,18 @@ import ( "reflect" "testing" - "github.com/KusionStack/karpor/pkg/core/entity" - "github.com/KusionStack/karpor/pkg/infra/topology" "github.com/bytedance/mockey" "github.com/stretchr/testify/require" genericapiserver "k8s.io/apiserver/pkg/server" - "k8s.io/client-go/dynamic" + "k8s.io/client-go/dynamic/fake" + + "github.com/KusionStack/karpor/pkg/core/entity" + "github.com/KusionStack/karpor/pkg/infra/topology" ) func TestInsightManager_GetTopologyForCluster(t *testing.T) { // Set up mocks for dynamic client - mockey.Mock((*dynamic.DynamicClient).Resource).Return(&mockNamespaceableResource{}).Build() + mockey.Mock((*fake.FakeDynamicClient).Resource).Return(&mockNamespaceableResource{}).Build() mockey.Mock(topology.GVRNamespaced).Return(true).Build() defer mockey.UnPatchAll() @@ -88,7 +89,7 @@ func TestInsightManager_GetTopologyForResource(t *testing.T) { require.NoError(t, err, "Unexpected error initializing InsightManager") // Set up mocks for dynamic client - mockey.Mock((*dynamic.DynamicClient).Resource).Return(&mockNamespaceableResource{}).Build() + mockey.Mock((*fake.FakeDynamicClient).Resource).Return(&mockNamespaceableResource{}).Build() defer mockey.UnPatchAll() // Test cases @@ -155,7 +156,7 @@ func TestInsightManager_GetTopologyForResource(t *testing.T) { func TestInsightManager_GetTopologyForClusterNamespace(t *testing.T) { // Set up mocks for dynamic client - mockey.Mock((*dynamic.DynamicClient).Resource).Return(&mockNamespaceableResource{}).Build() + mockey.Mock((*fake.FakeDynamicClient).Resource).Return(&mockNamespaceableResource{}).Build() mockey.Mock(topology.GVRNamespaced).Return(true).Build() defer mockey.UnPatchAll() diff --git a/pkg/core/middleware/logger.go b/pkg/core/middleware/logger.go index 258a2a0b..08f2c136 100644 --- a/pkg/core/middleware/logger.go +++ b/pkg/core/middleware/logger.go @@ -19,7 +19,7 @@ import ( "net/http" "github.com/go-chi/chi/v5/middleware" - "k8s.io/klog/v2" + "k8s.io/klog/v2/klogr" ) // APILoggerKey is a context key used for associating a logger with a request. @@ -33,8 +33,7 @@ func APILogger(next http.Handler) http.Handler { // Retrieve the request ID from the context and create a logger with it. if requestID := middleware.GetReqID(ctx); len(requestID) > 0 { - logger := klog.FromContext(ctx).WithValues("requestID", requestID) - ctx = context.WithValue(ctx, APILoggerKey, logger) + ctx = context.WithValue(ctx, APILoggerKey, klogr.New().WithValues("requestID", requestID)) } // Continue serving the request with the new context. diff --git a/pkg/core/route/route.go b/pkg/core/route/route.go index c3cded43..691cc7c0 100644 --- a/pkg/core/route/route.go +++ b/pkg/core/route/route.go @@ -20,6 +20,12 @@ import ( "expvar" "net/http" + "github.com/go-chi/chi/v5" + "github.com/go-chi/chi/v5/middleware" + httpswagger "github.com/swaggo/http-swagger/v2" + genericapiserver "k8s.io/apiserver/pkg/server" + "k8s.io/klog/v2" + docs "github.com/KusionStack/karpor/api/openapispec" aggregatorhandler "github.com/KusionStack/karpor/pkg/core/handler/aggregator" authnhandler "github.com/KusionStack/karpor/pkg/core/handler/authn" @@ -44,11 +50,6 @@ import ( "github.com/KusionStack/karpor/pkg/infra/search/storage" "github.com/KusionStack/karpor/pkg/kubernetes/registry" "github.com/KusionStack/karpor/pkg/kubernetes/registry/search" - "github.com/go-chi/chi/v5" - "github.com/go-chi/chi/v5/middleware" - httpswagger "github.com/swaggo/http-swagger/v2" - genericapiserver "k8s.io/apiserver/pkg/server" - "k8s.io/klog/v2" ) // NewCoreRoute creates and configures an instance of chi.Mux with the given @@ -156,6 +157,7 @@ func setupRestAPIV1( r.Post("/", clusterhandler.Create(clusterMgr, genericConfig)) r.Put("/", clusterhandler.Update(clusterMgr, genericConfig)) r.Delete("/", clusterhandler.Delete(clusterMgr, genericConfig)) + r.Get("/agentYml", clusterhandler.GetAgentYml(clusterMgr, genericConfig)) }) r.Post("/config/file", clusterhandler.UploadKubeConfig(clusterMgr)) r.Post("/config/validate", clusterhandler.ValidateKubeConfig(clusterMgr)) diff --git a/pkg/infra/multicluster/multicluster.go b/pkg/infra/multicluster/multicluster.go index 225052e0..7ff16c08 100644 --- a/pkg/infra/multicluster/multicluster.go +++ b/pkg/infra/multicluster/multicluster.go @@ -35,7 +35,7 @@ import ( // MultiClusterClient represents the client used to interact with multiple Kubernetes clusters. type MultiClusterClient struct { ClientSet *kubernetes.Clientset - DynamicClient *dynamic.DynamicClient + DynamicClient dynamic.Interface MetricsClient *metricsv1beta1.MetricsV1beta1Client } @@ -94,7 +94,7 @@ func BuildHubClients( // the request func BuildSpokeClients( ctx context.Context, - hubDynamicClient *dynamic.DynamicClient, + hubDynamicClient dynamic.Interface, name string, ) (*MultiClusterClient, error) { clusterGVR := clusterv1beta1.SchemeGroupVersion.WithResource("clusters") diff --git a/pkg/infra/search/storage/elasticsearch/resource.go b/pkg/infra/search/storage/elasticsearch/resource.go index bcc205ad..dc3546a8 100644 --- a/pkg/infra/search/storage/elasticsearch/resource.go +++ b/pkg/infra/search/storage/elasticsearch/resource.go @@ -21,11 +21,12 @@ import ( "fmt" "time" - "github.com/KusionStack/karpor/pkg/infra/search/storage" "github.com/elliotxx/esquery" "k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" + + "github.com/KusionStack/karpor/pkg/infra/search/storage" ) const ( @@ -124,7 +125,7 @@ func (s *Storage) GetResource(ctx context.Context, cluster string, obj runtime.O } if len(resp.Hits.Hits) == 0 { - return fmt.Errorf("no resource found for cluster: %s, namespace: %s, name: %s", cluster, unObj.GetNamespace(), unObj.GetName()) + return ErrNotFound } res, err := storage.Map2Resource(resp.Hits.Hits[0].Source) diff --git a/pkg/infra/search/storage/types.go b/pkg/infra/search/storage/types.go index 4b444d19..5408871b 100644 --- a/pkg/infra/search/storage/types.go +++ b/pkg/infra/search/storage/types.go @@ -76,6 +76,7 @@ type SearchStorage interface { type SearchStorageGetter interface { GetSearchStorage() (SearchStorage, error) } + type ResourceStorageGetter interface { GetResourceStorage() (ResourceStorage, error) } diff --git a/pkg/infra/topology/children.go b/pkg/infra/topology/children.go index 4cdaab7c..1ddfd3e8 100644 --- a/pkg/infra/topology/children.go +++ b/pkg/infra/topology/children.go @@ -20,18 +20,19 @@ import ( "context" "errors" - "github.com/KusionStack/karpor/pkg/util/ctxutil" - topologyutil "github.com/KusionStack/karpor/pkg/util/topology" "github.com/dominikbraun/graph" k8serrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/client-go/dynamic" + + "github.com/KusionStack/karpor/pkg/util/ctxutil" + topologyutil "github.com/KusionStack/karpor/pkg/util/topology" ) // GetChildResourcesList returns an *unstructured.UnstructuredList representing all resources that matches the child GVK in the current namespace -func GetChildResourcesList(ctx context.Context, client *dynamic.DynamicClient, childRelation *Relationship, namespace string) (*unstructured.UnstructuredList, error) { +func GetChildResourcesList(ctx context.Context, client dynamic.Interface, childRelation *Relationship, namespace string) (*unstructured.UnstructuredList, error) { log := ctxutil.GetLogger(ctx) childAPIVersion := childRelation.Group + "/" + childRelation.Version @@ -59,7 +60,7 @@ func GetChildResourcesList(ctx context.Context, client *dynamic.DynamicClient, c // GetChildren returns a graph that includes all of the child resources for the current obj that are described by the childRelation func GetChildren( ctx context.Context, - client *dynamic.DynamicClient, + client dynamic.Interface, obj unstructured.Unstructured, childRelation *Relationship, namespace, objName string, @@ -131,7 +132,7 @@ func GetChildren( func GetChildrenByOwnerReference( ctx context.Context, childResList *unstructured.UnstructuredList, - client *dynamic.DynamicClient, + client dynamic.Interface, obj unstructured.Unstructured, childGVK schema.GroupVersionKind, relationshipGraph graph.Graph[string, RelationshipGraphNode], diff --git a/pkg/infra/topology/common.go b/pkg/infra/topology/common.go index 10aec254..b60d7122 100644 --- a/pkg/infra/topology/common.go +++ b/pkg/infra/topology/common.go @@ -32,7 +32,7 @@ func GetByJSONPath( ctx context.Context, relatedResList *unstructured.UnstructuredList, relationshipType string, - client *dynamic.DynamicClient, + client dynamic.Interface, obj unstructured.Unstructured, relation *Relationship, relatedGVK schema.GroupVersionKind, @@ -91,7 +91,7 @@ func GetByLabelSelector( ctx context.Context, relatedResList *unstructured.UnstructuredList, relationshipType string, - client *dynamic.DynamicClient, + client dynamic.Interface, obj unstructured.Unstructured, relation *Relationship, relatedGVK schema.GroupVersionKind, diff --git a/pkg/infra/topology/parents.go b/pkg/infra/topology/parents.go index f298b25a..b3637636 100644 --- a/pkg/infra/topology/parents.go +++ b/pkg/infra/topology/parents.go @@ -31,7 +31,7 @@ import ( ) // GetParentResourcesList returns an *unstructured.UnstructuredList representing all resources that matches the parent GVK in the current namespace -func GetParentResourcesList(ctx context.Context, client *dynamic.DynamicClient, parentRelation *Relationship, namespace string) (*unstructured.UnstructuredList, error) { +func GetParentResourcesList(ctx context.Context, client dynamic.Interface, parentRelation *Relationship, namespace string) (*unstructured.UnstructuredList, error) { log := ctxutil.GetLogger(ctx) parentAPIVersion := parentRelation.Group + "/" + parentRelation.Version @@ -61,7 +61,7 @@ func GetParentResourcesList(ctx context.Context, client *dynamic.DynamicClient, // GetParents returns a graph that includes all of the parent resources for the current obj that are described by the parentRelation func GetParents( ctx context.Context, - client *dynamic.DynamicClient, + client dynamic.Interface, obj unstructured.Unstructured, parentRelation *Relationship, namespace, objName string, @@ -111,7 +111,7 @@ func GetParents( // GetParentsByOwnerReference returns a graph that includes all of the parent resources for the current obj described by its OwnerReferences field func GetParentsByOwnerReference( ctx context.Context, - client *dynamic.DynamicClient, + client dynamic.Interface, obj unstructured.Unstructured, objResourceNode ResourceGraphNode, relationshipGraph graph.Graph[string, RelationshipGraphNode], diff --git a/pkg/infra/topology/relationship.go b/pkg/infra/topology/relationship.go index a0267fc8..19d4f39b 100644 --- a/pkg/infra/topology/relationship.go +++ b/pkg/infra/topology/relationship.go @@ -105,7 +105,7 @@ func FindNodeOnGraph(g graph.Graph[string, RelationshipGraphNode], group, versio } // BuildBuiltinRelationshipGraph returns the relationship graph built from the YAML describing resource relationships -func BuildBuiltinRelationshipGraph(ctx context.Context, client *dynamic.DynamicClient) (graph.Graph[string, RelationshipGraphNode], *RelationshipGraph, error) { +func BuildBuiltinRelationshipGraph(ctx context.Context, client dynamic.Interface) (graph.Graph[string, RelationshipGraphNode], *RelationshipGraph, error) { log := ctxutil.GetLogger(ctx) // TODO: Obtaining topological relationship from CR in the future. @@ -192,7 +192,7 @@ func BuildBuiltinRelationshipGraph(ctx context.Context, client *dynamic.DynamicC } // BuildRelationshipGraph builds the complete relationship graph including the built-in one and customer-specified one -func BuildRelationshipGraph(ctx context.Context, client *dynamic.DynamicClient) (graph.Graph[string, RelationshipGraphNode], *RelationshipGraph, error) { +func BuildRelationshipGraph(ctx context.Context, client dynamic.Interface) (graph.Graph[string, RelationshipGraphNode], *RelationshipGraph, error) { res, rg, _ := BuildBuiltinRelationshipGraph(ctx, client) // TODO: Also include customized relationship graph return res, rg, nil @@ -236,7 +236,7 @@ func RelationshipEquals(r, relation *Relationship) bool { } // CountRelationshipGraph returns the same RelationshipGraph with the count for each resource -func (rg *RelationshipGraph) CountRelationshipGraph(ctx context.Context, dynamicClient *dynamic.DynamicClient, discoveryClient *discovery.DiscoveryClient, countNamespace string) (*RelationshipGraph, error) { +func (rg *RelationshipGraph) CountRelationshipGraph(ctx context.Context, dynamicClient dynamic.Interface, discoveryClient *discovery.DiscoveryClient, countNamespace string) (*RelationshipGraph, error) { log := ctxutil.GetLogger(ctx) for _, node := range rg.RelationshipNodes { diff --git a/pkg/kubernetes/apis/cluster/types.go b/pkg/kubernetes/apis/cluster/types.go index 3eb3b21e..05ec2461 100644 --- a/pkg/kubernetes/apis/cluster/types.go +++ b/pkg/kubernetes/apis/cluster/types.go @@ -28,6 +28,14 @@ const ( CredentialTypeOIDC CredentialType = "OIDC" ) +const ( + PullClusterMode = "pull" + PushClusterMode = "push" +) + +// +kubebuilder:object:root=true +// +kubebuilder:resource:scope=Cluster +// +kubebuilder:subresource:status // +genclient // +genclient:nonNamespaced // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object @@ -58,6 +66,9 @@ type ClusterSpec struct { Description string `json:"description,omitempty"` DisplayName string `json:"displayName,omitempty"` Finalized *bool `json:"finalized,omitempty"` + Mode string `json:"mode,omitempty"` + // cluster scale level, optional value 1, 2, 3, default 1 + Level int `json:"level"` } type ClusterStatus struct { diff --git a/pkg/kubernetes/apis/cluster/v1beta1/types.go b/pkg/kubernetes/apis/cluster/v1beta1/types.go index 4a80096a..b5f5a3b9 100644 --- a/pkg/kubernetes/apis/cluster/v1beta1/types.go +++ b/pkg/kubernetes/apis/cluster/v1beta1/types.go @@ -28,6 +28,18 @@ const ( CredentialTypeOIDC CredentialType = "OIDC" ) +const ( + PullClusterMode = "pull" + PushClusterMode = "push" +) + +const ( + ClusterFinalizer = "finalizers.cluster.karpor.io" +) + +// +kubebuilder:object:root=true +// +kubebuilder:resource:scope=Cluster +// +kubebuilder:subresource:status // +genclient // +genclient:nonNamespaced // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object @@ -59,6 +71,9 @@ type ClusterSpec struct { Description string `json:"description"` DisplayName string `json:"displayName"` Finalized *bool `json:"finalized,omitempty"` + Mode string `json:"mode,omitempty"` + // cluster scale level, optional value 1, 2, 3, default 1 + Level int `json:"level"` } type ClusterStatus struct { diff --git a/pkg/kubernetes/apis/cluster/v1beta1/zz_generated.conversion.go b/pkg/kubernetes/apis/cluster/v1beta1/zz_generated.conversion.go index 194e8c33..339cfc80 100644 --- a/pkg/kubernetes/apis/cluster/v1beta1/zz_generated.conversion.go +++ b/pkg/kubernetes/apis/cluster/v1beta1/zz_generated.conversion.go @@ -297,6 +297,8 @@ func autoConvert_v1beta1_ClusterSpec_To_cluster_ClusterSpec(in *ClusterSpec, out out.Description = in.Description out.DisplayName = in.DisplayName out.Finalized = (*bool)(unsafe.Pointer(in.Finalized)) + out.Mode = in.Mode + out.Level = in.Level return nil } @@ -313,6 +315,8 @@ func autoConvert_cluster_ClusterSpec_To_v1beta1_ClusterSpec(in *cluster.ClusterS out.Description = in.Description out.DisplayName = in.DisplayName out.Finalized = (*bool)(unsafe.Pointer(in.Finalized)) + out.Mode = in.Mode + out.Level = in.Level return nil } diff --git a/pkg/kubernetes/apis/search/v1beta1/types.go b/pkg/kubernetes/apis/search/v1beta1/types.go index 6c722392..229391d3 100644 --- a/pkg/kubernetes/apis/search/v1beta1/types.go +++ b/pkg/kubernetes/apis/search/v1beta1/types.go @@ -20,6 +20,9 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) +// +kubebuilder:object:root=true +// +kubebuilder:resource:scope=Cluster +// +kubebuilder:subresource:status // +genclient // +genclient:nonNamespaced // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object @@ -62,6 +65,8 @@ type SyncRegistryList struct { Items []SyncRegistry `json:"items"` } +// +kubebuilder:object:root=true +// +kubebuilder:resource:scope=Cluster // +genclient // +genclient:nonNamespaced // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object @@ -137,6 +142,8 @@ type ResourceSyncRule struct { RemainAfterDeleted bool `json:"remainAfterDeleted,omitempty"` } +// +kubebuilder:object:root=true +// +kubebuilder:resource:scope=Cluster // +genclient // +genclient:nonNamespaced // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object @@ -175,6 +182,8 @@ type TrimRuleList struct { Items []TrimRule `json:"items"` } +// +kubebuilder:object:root=true +// +kubebuilder:resource:scope=Cluster // +genclient // +genclient:nonNamespaced // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object diff --git a/pkg/kubernetes/generated/clientset/versioned/clientset.go b/pkg/kubernetes/generated/clientset/versioned/clientset.go index 5242486d..1f205fe8 100644 --- a/pkg/kubernetes/generated/clientset/versioned/clientset.go +++ b/pkg/kubernetes/generated/clientset/versioned/clientset.go @@ -20,7 +20,6 @@ package versioned import ( "fmt" - "net/http" clusterv1beta1 "github.com/KusionStack/karpor/pkg/kubernetes/generated/clientset/versioned/typed/cluster/v1beta1" searchv1beta1 "github.com/KusionStack/karpor/pkg/kubernetes/generated/clientset/versioned/typed/search/v1beta1" @@ -35,7 +34,8 @@ type Interface interface { SearchV1beta1() searchv1beta1.SearchV1beta1Interface } -// Clientset contains the clients for groups. +// Clientset contains the clients for groups. Each group has exactly one +// version included in a Clientset. type Clientset struct { *discovery.DiscoveryClient clusterV1beta1 *clusterv1beta1.ClusterV1beta1Client @@ -63,49 +63,26 @@ func (c *Clientset) Discovery() discovery.DiscoveryInterface { // NewForConfig creates a new Clientset for the given config. // If config's RateLimiter is not set and QPS and Burst are acceptable, // NewForConfig will generate a rate-limiter in configShallowCopy. -// NewForConfig is equivalent to NewForConfigAndClient(c, httpClient), -// where httpClient was generated with rest.HTTPClientFor(c). func NewForConfig(c *rest.Config) (*Clientset, error) { configShallowCopy := *c - - if configShallowCopy.UserAgent == "" { - configShallowCopy.UserAgent = rest.DefaultKubernetesUserAgent() - } - - // share the transport between all clients - httpClient, err := rest.HTTPClientFor(&configShallowCopy) - if err != nil { - return nil, err - } - - return NewForConfigAndClient(&configShallowCopy, httpClient) -} - -// NewForConfigAndClient creates a new Clientset for the given config and http client. -// Note the http client provided takes precedence over the configured transport values. -// If config's RateLimiter is not set and QPS and Burst are acceptable, -// NewForConfigAndClient will generate a rate-limiter in configShallowCopy. -func NewForConfigAndClient(c *rest.Config, httpClient *http.Client) (*Clientset, error) { - configShallowCopy := *c if configShallowCopy.RateLimiter == nil && configShallowCopy.QPS > 0 { if configShallowCopy.Burst <= 0 { return nil, fmt.Errorf("burst is required to be greater than 0 when RateLimiter is not set and QPS is set to greater than 0") } configShallowCopy.RateLimiter = flowcontrol.NewTokenBucketRateLimiter(configShallowCopy.QPS, configShallowCopy.Burst) } - var cs Clientset var err error - cs.clusterV1beta1, err = clusterv1beta1.NewForConfigAndClient(&configShallowCopy, httpClient) + cs.clusterV1beta1, err = clusterv1beta1.NewForConfig(&configShallowCopy) if err != nil { return nil, err } - cs.searchV1beta1, err = searchv1beta1.NewForConfigAndClient(&configShallowCopy, httpClient) + cs.searchV1beta1, err = searchv1beta1.NewForConfig(&configShallowCopy) if err != nil { return nil, err } - cs.DiscoveryClient, err = discovery.NewDiscoveryClientForConfigAndClient(&configShallowCopy, httpClient) + cs.DiscoveryClient, err = discovery.NewDiscoveryClientForConfig(&configShallowCopy) if err != nil { return nil, err } @@ -115,11 +92,12 @@ func NewForConfigAndClient(c *rest.Config, httpClient *http.Client) (*Clientset, // NewForConfigOrDie creates a new Clientset for the given config and // panics if there is an error in the config. func NewForConfigOrDie(c *rest.Config) *Clientset { - cs, err := NewForConfig(c) - if err != nil { - panic(err) - } - return cs + var cs Clientset + cs.clusterV1beta1 = clusterv1beta1.NewForConfigOrDie(c) + cs.searchV1beta1 = searchv1beta1.NewForConfigOrDie(c) + + cs.DiscoveryClient = discovery.NewDiscoveryClientForConfigOrDie(c) + return &cs } // New creates a new Clientset for the given RESTClient. diff --git a/pkg/kubernetes/generated/clientset/versioned/typed/cluster/v1beta1/cluster_client.go b/pkg/kubernetes/generated/clientset/versioned/typed/cluster/v1beta1/cluster_client.go index 42f02782..d14bfcc3 100644 --- a/pkg/kubernetes/generated/clientset/versioned/typed/cluster/v1beta1/cluster_client.go +++ b/pkg/kubernetes/generated/clientset/versioned/typed/cluster/v1beta1/cluster_client.go @@ -19,8 +19,6 @@ limitations under the License. package v1beta1 import ( - "net/http" - v1beta1 "github.com/KusionStack/karpor/pkg/kubernetes/apis/cluster/v1beta1" "github.com/KusionStack/karpor/pkg/kubernetes/generated/clientset/versioned/scheme" rest "k8s.io/client-go/rest" @@ -41,28 +39,12 @@ func (c *ClusterV1beta1Client) Clusters() ClusterInterface { } // NewForConfig creates a new ClusterV1beta1Client for the given config. -// NewForConfig is equivalent to NewForConfigAndClient(c, httpClient), -// where httpClient was generated with rest.HTTPClientFor(c). func NewForConfig(c *rest.Config) (*ClusterV1beta1Client, error) { config := *c if err := setConfigDefaults(&config); err != nil { return nil, err } - httpClient, err := rest.HTTPClientFor(&config) - if err != nil { - return nil, err - } - return NewForConfigAndClient(&config, httpClient) -} - -// NewForConfigAndClient creates a new ClusterV1beta1Client for the given config and http client. -// Note the http client provided takes precedence over the configured transport values. -func NewForConfigAndClient(c *rest.Config, h *http.Client) (*ClusterV1beta1Client, error) { - config := *c - if err := setConfigDefaults(&config); err != nil { - return nil, err - } - client, err := rest.RESTClientForConfigAndClient(&config, h) + client, err := rest.RESTClientFor(&config) if err != nil { return nil, err } diff --git a/pkg/kubernetes/generated/clientset/versioned/typed/cluster/v1beta1/fake/fake_cluster.go b/pkg/kubernetes/generated/clientset/versioned/typed/cluster/v1beta1/fake/fake_cluster.go index 35173048..cab94797 100644 --- a/pkg/kubernetes/generated/clientset/versioned/typed/cluster/v1beta1/fake/fake_cluster.go +++ b/pkg/kubernetes/generated/clientset/versioned/typed/cluster/v1beta1/fake/fake_cluster.go @@ -110,7 +110,7 @@ func (c *FakeClusters) UpdateStatus(ctx context.Context, cluster *v1beta1.Cluste // Delete takes name of the cluster and deletes it. Returns an error if one occurs. func (c *FakeClusters) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { _, err := c.Fake. - Invokes(testing.NewRootDeleteActionWithOptions(clustersResource, name, opts), &v1beta1.Cluster{}) + Invokes(testing.NewRootDeleteAction(clustersResource, name), &v1beta1.Cluster{}) return err } diff --git a/pkg/kubernetes/generated/clientset/versioned/typed/search/v1beta1/fake/fake_syncregistry.go b/pkg/kubernetes/generated/clientset/versioned/typed/search/v1beta1/fake/fake_syncregistry.go index d73f6d89..271478db 100644 --- a/pkg/kubernetes/generated/clientset/versioned/typed/search/v1beta1/fake/fake_syncregistry.go +++ b/pkg/kubernetes/generated/clientset/versioned/typed/search/v1beta1/fake/fake_syncregistry.go @@ -110,7 +110,7 @@ func (c *FakeSyncRegistries) UpdateStatus(ctx context.Context, syncRegistry *v1b // Delete takes name of the syncRegistry and deletes it. Returns an error if one occurs. func (c *FakeSyncRegistries) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { _, err := c.Fake. - Invokes(testing.NewRootDeleteActionWithOptions(syncregistriesResource, name, opts), &v1beta1.SyncRegistry{}) + Invokes(testing.NewRootDeleteAction(syncregistriesResource, name), &v1beta1.SyncRegistry{}) return err } diff --git a/pkg/kubernetes/generated/clientset/versioned/typed/search/v1beta1/fake/fake_syncresources.go b/pkg/kubernetes/generated/clientset/versioned/typed/search/v1beta1/fake/fake_syncresources.go index 325039f9..acd9d372 100644 --- a/pkg/kubernetes/generated/clientset/versioned/typed/search/v1beta1/fake/fake_syncresources.go +++ b/pkg/kubernetes/generated/clientset/versioned/typed/search/v1beta1/fake/fake_syncresources.go @@ -99,7 +99,7 @@ func (c *FakeSyncResourceses) Update(ctx context.Context, syncResources *v1beta1 // Delete takes name of the syncResources and deletes it. Returns an error if one occurs. func (c *FakeSyncResourceses) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { _, err := c.Fake. - Invokes(testing.NewRootDeleteActionWithOptions(syncresourcesesResource, name, opts), &v1beta1.SyncResources{}) + Invokes(testing.NewRootDeleteAction(syncresourcesesResource, name), &v1beta1.SyncResources{}) return err } diff --git a/pkg/kubernetes/generated/clientset/versioned/typed/search/v1beta1/fake/fake_transformrule.go b/pkg/kubernetes/generated/clientset/versioned/typed/search/v1beta1/fake/fake_transformrule.go index 7f45812b..d927e79f 100644 --- a/pkg/kubernetes/generated/clientset/versioned/typed/search/v1beta1/fake/fake_transformrule.go +++ b/pkg/kubernetes/generated/clientset/versioned/typed/search/v1beta1/fake/fake_transformrule.go @@ -99,7 +99,7 @@ func (c *FakeTransformRules) Update(ctx context.Context, transformRule *v1beta1. // Delete takes name of the transformRule and deletes it. Returns an error if one occurs. func (c *FakeTransformRules) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { _, err := c.Fake. - Invokes(testing.NewRootDeleteActionWithOptions(transformrulesResource, name, opts), &v1beta1.TransformRule{}) + Invokes(testing.NewRootDeleteAction(transformrulesResource, name), &v1beta1.TransformRule{}) return err } diff --git a/pkg/kubernetes/generated/clientset/versioned/typed/search/v1beta1/fake/fake_trimrule.go b/pkg/kubernetes/generated/clientset/versioned/typed/search/v1beta1/fake/fake_trimrule.go index 4d0eb975..d1303a95 100644 --- a/pkg/kubernetes/generated/clientset/versioned/typed/search/v1beta1/fake/fake_trimrule.go +++ b/pkg/kubernetes/generated/clientset/versioned/typed/search/v1beta1/fake/fake_trimrule.go @@ -99,7 +99,7 @@ func (c *FakeTrimRules) Update(ctx context.Context, trimRule *v1beta1.TrimRule, // Delete takes name of the trimRule and deletes it. Returns an error if one occurs. func (c *FakeTrimRules) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { _, err := c.Fake. - Invokes(testing.NewRootDeleteActionWithOptions(trimrulesResource, name, opts), &v1beta1.TrimRule{}) + Invokes(testing.NewRootDeleteAction(trimrulesResource, name), &v1beta1.TrimRule{}) return err } diff --git a/pkg/kubernetes/generated/clientset/versioned/typed/search/v1beta1/search_client.go b/pkg/kubernetes/generated/clientset/versioned/typed/search/v1beta1/search_client.go index 05f6e6f2..0ba1d548 100644 --- a/pkg/kubernetes/generated/clientset/versioned/typed/search/v1beta1/search_client.go +++ b/pkg/kubernetes/generated/clientset/versioned/typed/search/v1beta1/search_client.go @@ -19,8 +19,6 @@ limitations under the License. package v1beta1 import ( - "net/http" - v1beta1 "github.com/KusionStack/karpor/pkg/kubernetes/apis/search/v1beta1" "github.com/KusionStack/karpor/pkg/kubernetes/generated/clientset/versioned/scheme" rest "k8s.io/client-go/rest" @@ -56,28 +54,12 @@ func (c *SearchV1beta1Client) TrimRules() TrimRuleInterface { } // NewForConfig creates a new SearchV1beta1Client for the given config. -// NewForConfig is equivalent to NewForConfigAndClient(c, httpClient), -// where httpClient was generated with rest.HTTPClientFor(c). func NewForConfig(c *rest.Config) (*SearchV1beta1Client, error) { config := *c if err := setConfigDefaults(&config); err != nil { return nil, err } - httpClient, err := rest.HTTPClientFor(&config) - if err != nil { - return nil, err - } - return NewForConfigAndClient(&config, httpClient) -} - -// NewForConfigAndClient creates a new SearchV1beta1Client for the given config and http client. -// Note the http client provided takes precedence over the configured transport values. -func NewForConfigAndClient(c *rest.Config, h *http.Client) (*SearchV1beta1Client, error) { - config := *c - if err := setConfigDefaults(&config); err != nil { - return nil, err - } - client, err := rest.RESTClientForConfigAndClient(&config, h) + client, err := rest.RESTClientFor(&config) if err != nil { return nil, err } diff --git a/pkg/kubernetes/generated/informers/externalversions/cluster/v1beta1/cluster.go b/pkg/kubernetes/generated/informers/externalversions/cluster/v1beta1/cluster.go index 456fb061..21689c93 100644 --- a/pkg/kubernetes/generated/informers/externalversions/cluster/v1beta1/cluster.go +++ b/pkg/kubernetes/generated/informers/externalversions/cluster/v1beta1/cluster.go @@ -76,17 +76,14 @@ func NewFilteredClusterInformer(client versioned.Interface, resyncPeriod time.Du ) } -// defaultInformer provides a default implementation for the cluster informer using the given client and resync period. func (f *clusterInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { return NewFilteredClusterInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) } -// Informer returns the SharedIndexInformer for the cluster informer. func (f *clusterInformer) Informer() cache.SharedIndexInformer { return f.factory.InformerFor(&clusterv1beta1.Cluster{}, f.defaultInformer) } -// Lister returns the ClusterLister for the cluster informer. func (f *clusterInformer) Lister() v1beta1.ClusterLister { return v1beta1.NewClusterLister(f.Informer().GetIndexer()) } diff --git a/pkg/kubernetes/generated/informers/externalversions/factory.go b/pkg/kubernetes/generated/informers/externalversions/factory.go index 90cb7bd5..55d6976f 100644 --- a/pkg/kubernetes/generated/informers/externalversions/factory.go +++ b/pkg/kubernetes/generated/informers/externalversions/factory.go @@ -48,11 +48,6 @@ type sharedInformerFactory struct { // startedInformers is used for tracking which informers have been started. // This allows Start() to be called multiple times safely. startedInformers map[reflect.Type]bool - // wg tracks how many goroutines were started. - wg sync.WaitGroup - // shuttingDown is true when Shutdown has been called. It may still be running - // because it needs to wait for goroutines. - shuttingDown bool } // WithCustomResyncConfig sets a custom resync period for the specified informer types. @@ -113,42 +108,20 @@ func NewSharedInformerFactoryWithOptions(client versioned.Interface, defaultResy return factory } -// Start starts all informers in the factory and waits for the stop channel to close before exiting. +// Start initializes all requested informers. func (f *sharedInformerFactory) Start(stopCh <-chan struct{}) { f.lock.Lock() defer f.lock.Unlock() - if f.shuttingDown { - return - } - for informerType, informer := range f.informers { if !f.startedInformers[informerType] { - f.wg.Add(1) - // We need a new variable in each loop iteration, - // otherwise the goroutine would use the loop variable - // and that keeps changing. - informer := informer - go func() { - defer f.wg.Done() - informer.Run(stopCh) - }() + go informer.Run(stopCh) f.startedInformers[informerType] = true } } } -// Shutdown stops all running informers in the factory. -func (f *sharedInformerFactory) Shutdown() { - f.lock.Lock() - f.shuttingDown = true - f.lock.Unlock() - - // Will return immediately if there is nothing to wait for. - f.wg.Wait() -} - -// WaitForCacheSync waits for all started informers to sync with the cluster state. +// WaitForCacheSync waits for all started informers' cache were synced. func (f *sharedInformerFactory) WaitForCacheSync(stopCh <-chan struct{}) map[reflect.Type]bool { informers := func() map[reflect.Type]cache.SharedIndexInformer { f.lock.Lock() @@ -195,68 +168,19 @@ func (f *sharedInformerFactory) InformerFor(obj runtime.Object, newFunc internal // SharedInformerFactory provides shared informers for resources in all known // API group versions. -// -// It is typically used like this: -// -// ctx, cancel := context.Background() -// defer cancel() -// factory := NewSharedInformerFactory(client, resyncPeriod) -// defer factory.WaitForStop() // Returns immediately if nothing was started. -// genericInformer := factory.ForResource(resource) -// typedInformer := factory.SomeAPIGroup().V1().SomeType() -// factory.Start(ctx.Done()) // Start processing these informers. -// synced := factory.WaitForCacheSync(ctx.Done()) -// for v, ok := range synced { -// if !ok { -// fmt.Fprintf(os.Stderr, "caches failed to sync: %v", v) -// return -// } -// } -// -// // Creating informers can also be created after Start, but then -// // Start must be called again: -// anotherGenericInformer := factory.ForResource(resource) -// factory.Start(ctx.Done()) type SharedInformerFactory interface { internalinterfaces.SharedInformerFactory - - // Start initializes all requested informers. They are handled in goroutines - // which run until the stop channel gets closed. - Start(stopCh <-chan struct{}) - - // Shutdown marks a factory as shutting down. At that point no new - // informers can be started anymore and Start will return without - // doing anything. - // - // In addition, Shutdown blocks until all goroutines have terminated. For that - // to happen, the close channel(s) that they were started with must be closed, - // either before Shutdown gets called or while it is waiting. - // - // Shutdown may be called multiple times, even concurrently. All such calls will - // block until all goroutines have terminated. - Shutdown() - - // WaitForCacheSync blocks until all started informers' caches were synced - // or the stop channel gets closed. - WaitForCacheSync(stopCh <-chan struct{}) map[reflect.Type]bool - - // ForResource gives generic access to a shared informer of the matching type. ForResource(resource schema.GroupVersionResource) (GenericInformer, error) - - // InternalInformerFor returns the SharedIndexInformer for obj using an internal - // client. - InformerFor(obj runtime.Object, newFunc internalinterfaces.NewInformerFunc) cache.SharedIndexInformer + WaitForCacheSync(stopCh <-chan struct{}) map[reflect.Type]bool Cluster() cluster.Interface Search() search.Interface } -// Cluster returns the cluster informer. func (f *sharedInformerFactory) Cluster() cluster.Interface { return cluster.New(f, f.namespace, f.tweakListOptions) } -// Search returns the search informer. func (f *sharedInformerFactory) Search() search.Interface { return search.New(f, f.namespace, f.tweakListOptions) } diff --git a/pkg/kubernetes/generated/informers/externalversions/search/v1beta1/syncregistry.go b/pkg/kubernetes/generated/informers/externalversions/search/v1beta1/syncregistry.go index a01e732c..2f1edc17 100644 --- a/pkg/kubernetes/generated/informers/externalversions/search/v1beta1/syncregistry.go +++ b/pkg/kubernetes/generated/informers/externalversions/search/v1beta1/syncregistry.go @@ -76,17 +76,14 @@ func NewFilteredSyncRegistryInformer(client versioned.Interface, resyncPeriod ti ) } -// defaultInformer provides the default implementation for the shared informer used by the SyncRegistryInformer. func (f *syncRegistryInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { return NewFilteredSyncRegistryInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) } -// Informer returns the shared informer for the SyncRegistryInformer. func (f *syncRegistryInformer) Informer() cache.SharedIndexInformer { return f.factory.InformerFor(&searchv1beta1.SyncRegistry{}, f.defaultInformer) } -// Lister returns the lister for the SyncRegistryInformer. func (f *syncRegistryInformer) Lister() v1beta1.SyncRegistryLister { return v1beta1.NewSyncRegistryLister(f.Informer().GetIndexer()) } diff --git a/pkg/kubernetes/generated/informers/externalversions/search/v1beta1/syncresources.go b/pkg/kubernetes/generated/informers/externalversions/search/v1beta1/syncresources.go index 335d512f..5c386813 100644 --- a/pkg/kubernetes/generated/informers/externalversions/search/v1beta1/syncresources.go +++ b/pkg/kubernetes/generated/informers/externalversions/search/v1beta1/syncresources.go @@ -76,17 +76,14 @@ func NewFilteredSyncResourcesInformer(client versioned.Interface, resyncPeriod t ) } -// defaultInformer provides a default SharedIndexInformer implementation for SyncResources resources. func (f *syncResourcesInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { return NewFilteredSyncResourcesInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) } -// Informer returns the SharedIndexInformer for SyncResources resources. func (f *syncResourcesInformer) Informer() cache.SharedIndexInformer { return f.factory.InformerFor(&searchv1beta1.SyncResources{}, f.defaultInformer) } -// Lister returns the SyncResourcesLister for SyncResources resources. func (f *syncResourcesInformer) Lister() v1beta1.SyncResourcesLister { return v1beta1.NewSyncResourcesLister(f.Informer().GetIndexer()) } diff --git a/pkg/kubernetes/generated/informers/externalversions/search/v1beta1/transformrule.go b/pkg/kubernetes/generated/informers/externalversions/search/v1beta1/transformrule.go index 509aebd2..443efd2c 100644 --- a/pkg/kubernetes/generated/informers/externalversions/search/v1beta1/transformrule.go +++ b/pkg/kubernetes/generated/informers/externalversions/search/v1beta1/transformrule.go @@ -76,17 +76,14 @@ func NewFilteredTransformRuleInformer(client versioned.Interface, resyncPeriod t ) } -// defaultInformer provides a default implementation for the SharedIndexInformer interface. func (f *transformRuleInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { return NewFilteredTransformRuleInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) } -// Informer returns the SharedIndexInformer for TransformRule resources. func (f *transformRuleInformer) Informer() cache.SharedIndexInformer { return f.factory.InformerFor(&searchv1beta1.TransformRule{}, f.defaultInformer) } -// Lister returns the TransformRuleLister for TransformRule resources. func (f *transformRuleInformer) Lister() v1beta1.TransformRuleLister { return v1beta1.NewTransformRuleLister(f.Informer().GetIndexer()) } diff --git a/pkg/kubernetes/generated/openapi/zz_generated.openapi.go b/pkg/kubernetes/generated/openapi/zz_generated.openapi.go index 116b9869..7e411dba 100644 --- a/pkg/kubernetes/generated/openapi/zz_generated.openapi.go +++ b/pkg/kubernetes/generated/openapi/zz_generated.openapi.go @@ -361,8 +361,22 @@ func schema_kubernetes_apis_cluster_v1beta1_ClusterSpec(ref common.ReferenceCall Format: "", }, }, + "mode": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Format: "", + }, + }, + "level": { + SchemaProps: spec.SchemaProps{ + Description: "cluster scale level, optional value 1, 2, 3, default 1", + Default: 0, + Type: []string{"integer"}, + Format: "int32", + }, + }, }, - Required: []string{"provider", "access", "displayName"}, + Required: []string{"provider", "access", "displayName", "level"}, }, }, Dependencies: []string{ @@ -1828,13 +1842,6 @@ func schema_pkg_apis_meta_v1_CreateOptions(ref common.ReferenceCallback) common. Format: "", }, }, - "fieldValidation": { - SchemaProps: spec.SchemaProps{ - Description: "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields, provided that the `ServerSideFieldValidation` feature gate is also enabled. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23 and is the default behavior when the `ServerSideFieldValidation` feature gate is disabled. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default when the `ServerSideFieldValidation` feature gate is enabled. - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", - Type: []string{"string"}, - Format: "", - }, - }, }, }, }, @@ -2087,7 +2094,7 @@ func schema_pkg_apis_meta_v1_GroupVersionKind(ref common.ReferenceCallback) comm return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ - Description: "GroupVersionKind unambiguously identifies a kind. It doesn't anonymously include GroupVersion to avoid automatic coercion. It doesn't use a GroupVersion to avoid custom marshalling", + Description: "GroupVersionKind unambiguously identifies a kind. It doesn't anonymously include GroupVersion to avoid automatic coersion. It doesn't use a GroupVersion to avoid custom marshalling", Type: []string{"object"}, Properties: map[string]spec.Schema{ "group": { @@ -2122,7 +2129,7 @@ func schema_pkg_apis_meta_v1_GroupVersionResource(ref common.ReferenceCallback) return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ - Description: "GroupVersionResource unambiguously identifies a resource. It doesn't anonymously include GroupVersion to avoid automatic coercion. It doesn't use a GroupVersion to avoid custom marshalling", + Description: "GroupVersionResource unambiguously identifies a resource. It doesn't anonymously include GroupVersion to avoid automatic coersion. It doesn't use a GroupVersion to avoid custom marshalling", Type: []string{"object"}, Properties: map[string]spec.Schema{ "group": { @@ -2343,7 +2350,7 @@ func schema_pkg_apis_meta_v1_ListMeta(ref common.ReferenceCallback) common.OpenA Properties: map[string]spec.Schema{ "selfLink": { SchemaProps: spec.SchemaProps{ - Description: "Deprecated: selfLink is a legacy read-only field that is no longer populated by the system.", + Description: "selfLink is a URL representing this object. Populated by the system. Read-only.\n\nDEPRECATED Kubernetes will stop propagating this field in 1.20 release and the field is planned to be removed in 1.21 release.", Type: []string{"string"}, Format: "", }, @@ -2495,7 +2502,7 @@ func schema_pkg_apis_meta_v1_ManagedFieldsEntry(ref common.ReferenceCallback) co }, "time": { SchemaProps: spec.SchemaProps{ - Description: "Time is the timestamp of when the ManagedFields entry was added. The timestamp will also be updated if a field is added, the manager changes any of the owned fields value or removes a field. The timestamp does not update when a field is removed from the entry because another manager took it over.", + Description: "Time is timestamp of when these fields were set. It should always be empty if Operation is 'Apply'", Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Time"), }, }, @@ -2555,7 +2562,7 @@ func schema_pkg_apis_meta_v1_ObjectMeta(ref common.ReferenceCallback) common.Ope }, "generateName": { SchemaProps: spec.SchemaProps{ - Description: "GenerateName is an optional prefix, used by the server, to generate a unique name ONLY IF the Name field has not been provided. If this field is used, the name returned to the client will be different than the name passed. This value will also be combined with a unique suffix. The provided value has the same validation rules as the Name field, and may be truncated by the length of the suffix required to make the value unique on the server.\n\nIf this field is specified and the generated name exists, the server will return a 409.\n\nApplied only if Name is not specified. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#idempotency", + Description: "GenerateName is an optional prefix, used by the server, to generate a unique name ONLY IF the Name field has not been provided. If this field is used, the name returned to the client will be different than the name passed. This value will also be combined with a unique suffix. The provided value has the same validation rules as the Name field, and may be truncated by the length of the suffix required to make the value unique on the server.\n\nIf this field is specified and the generated name exists, the server will NOT return a 409 - instead, it will either return 201 Created or 500 with Reason ServerTimeout indicating a unique name could not be found in the time allotted, and the client should retry (optionally after the time indicated in the Retry-After header).\n\nApplied only if Name is not specified. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#idempotency", Type: []string{"string"}, Format: "", }, @@ -2569,7 +2576,7 @@ func schema_pkg_apis_meta_v1_ObjectMeta(ref common.ReferenceCallback) common.Ope }, "selfLink": { SchemaProps: spec.SchemaProps{ - Description: "Deprecated: selfLink is a legacy read-only field that is no longer populated by the system.", + Description: "SelfLink is a URL representing this object. Populated by the system. Read-only.\n\nDEPRECATED Kubernetes will stop propagating this field in 1.20 release and the field is planned to be removed in 1.21 release.", Type: []string{"string"}, Format: "", }, @@ -2687,6 +2694,13 @@ func schema_pkg_apis_meta_v1_ObjectMeta(ref common.ReferenceCallback) common.Ope }, }, }, + "clusterName": { + SchemaProps: spec.SchemaProps{ + Description: "The name of the cluster which the object belongs to. This is used to distinguish resources with same name and namespace in different clusters. This field is not set anywhere right now and apiserver is going to ignore it if set in create or update request.", + Type: []string{"string"}, + Format: "", + }, + }, "managedFields": { SchemaProps: spec.SchemaProps{ Description: "ManagedFields maps workflow-id and version to the set of fields that are managed by that workflow. This is mostly for internal housekeeping, and users typically shouldn't need to set or understand this field. A workflow can be the user's name, a controller's name, or the name of a specific apply path like \"ci-cd\". The set of fields is always in the version that the workflow used when modifying the object.", @@ -2757,7 +2771,7 @@ func schema_pkg_apis_meta_v1_OwnerReference(ref common.ReferenceCallback) common }, "blockOwnerDeletion": { SchemaProps: spec.SchemaProps{ - Description: "If true, AND if the owner has the \"foregroundDeletion\" finalizer, then the owner cannot be deleted from the key-value store until this reference is removed. See https://kubernetes.io/docs/concepts/architecture/garbage-collection/#foreground-deletion for how the garbage collector interacts with this field and enforces the foreground deletion. Defaults to false. To set this field, a user needs \"delete\" permission of the owner, otherwise 422 (Unprocessable Entity) will be returned.", + Description: "If true, AND if the owner has the \"foregroundDeletion\" finalizer, then the owner cannot be deleted from the key-value store until this reference is removed. Defaults to false. To set this field, a user needs \"delete\" permission of the owner, otherwise 422 (Unprocessable Entity) will be returned.", Type: []string{"boolean"}, Format: "", }, @@ -2922,13 +2936,6 @@ func schema_pkg_apis_meta_v1_PatchOptions(ref common.ReferenceCallback) common.O Format: "", }, }, - "fieldValidation": { - SchemaProps: spec.SchemaProps{ - Description: "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields, provided that the `ServerSideFieldValidation` feature gate is also enabled. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23 and is the default behavior when the `ServerSideFieldValidation` feature gate is disabled. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default when the `ServerSideFieldValidation` feature gate is enabled. - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", - Type: []string{"string"}, - Format: "", - }, - }, }, }, }, @@ -3549,13 +3556,6 @@ func schema_pkg_apis_meta_v1_UpdateOptions(ref common.ReferenceCallback) common. Format: "", }, }, - "fieldValidation": { - SchemaProps: spec.SchemaProps{ - Description: "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields, provided that the `ServerSideFieldValidation` feature gate is also enabled. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23 and is the default behavior when the `ServerSideFieldValidation` feature gate is disabled. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default when the `ServerSideFieldValidation` feature gate is enabled. - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", - Type: []string{"string"}, - Format: "", - }, - }, }, }, }, @@ -3596,7 +3596,7 @@ func schema_k8sio_apimachinery_pkg_runtime_RawExtension(ref common.ReferenceCall return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ - Description: "RawExtension is used to hold extensions in external versions.\n\nTo use this, make a field which has RawExtension as its type in your external, versioned struct, and Object in your internal struct. You also need to register your various plugin types.\n\n// Internal package:\n\n\ttype MyAPIObject struct {\n\t\truntime.TypeMeta `json:\",inline\"`\n\t\tMyPlugin runtime.Object `json:\"myPlugin\"`\n\t}\n\n\ttype PluginA struct {\n\t\tAOption string `json:\"aOption\"`\n\t}\n\n// External package:\n\n\ttype MyAPIObject struct {\n\t\truntime.TypeMeta `json:\",inline\"`\n\t\tMyPlugin runtime.RawExtension `json:\"myPlugin\"`\n\t}\n\n\ttype PluginA struct {\n\t\tAOption string `json:\"aOption\"`\n\t}\n\n// On the wire, the JSON will look something like this:\n\n\t{\n\t\t\"kind\":\"MyAPIObject\",\n\t\t\"apiVersion\":\"v1\",\n\t\t\"myPlugin\": {\n\t\t\t\"kind\":\"PluginA\",\n\t\t\t\"aOption\":\"foo\",\n\t\t},\n\t}\n\nSo what happens? Decode first uses json or yaml to unmarshal the serialized data into your external MyAPIObject. That causes the raw JSON to be stored, but not unpacked. The next step is to copy (using pkg/conversion) into the internal struct. The runtime package's DefaultScheme has conversion functions installed which will unpack the JSON stored in RawExtension, turning it into the correct object type, and storing it in the Object. (TODO: In the case where the object is of an unknown type, a runtime.Unknown object will be created and stored.)", + Description: "RawExtension is used to hold extensions in external versions.\n\nTo use this, make a field which has RawExtension as its type in your external, versioned struct, and Object in your internal struct. You also need to register your various plugin types.\n\n// Internal package: type MyAPIObject struct {\n\truntime.TypeMeta `json:\",inline\"`\n\tMyPlugin runtime.Object `json:\"myPlugin\"`\n} type PluginA struct {\n\tAOption string `json:\"aOption\"`\n}\n\n// External package: type MyAPIObject struct {\n\truntime.TypeMeta `json:\",inline\"`\n\tMyPlugin runtime.RawExtension `json:\"myPlugin\"`\n} type PluginA struct {\n\tAOption string `json:\"aOption\"`\n}\n\n// On the wire, the JSON will look something like this: {\n\t\"kind\":\"MyAPIObject\",\n\t\"apiVersion\":\"v1\",\n\t\"myPlugin\": {\n\t\t\"kind\":\"PluginA\",\n\t\t\"aOption\":\"foo\",\n\t},\n}\n\nSo what happens? Decode first uses json or yaml to unmarshal the serialized data into your external MyAPIObject. That causes the raw JSON to be stored, but not unpacked. The next step is to copy (using pkg/conversion) into the internal struct. The runtime package's DefaultScheme has conversion functions installed which will unpack the JSON stored in RawExtension, turning it into the correct object type, and storing it in the Object. (TODO: In the case where the object is of an unknown type, a runtime.Unknown object will be created and stored.)", Type: []string{"object"}, }, }, @@ -3607,7 +3607,7 @@ func schema_k8sio_apimachinery_pkg_runtime_TypeMeta(ref common.ReferenceCallback return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ - Description: "TypeMeta is shared by all top level objects. The proper way to use it is to inline it in your type, like this:\n\n\ttype MyAwesomeAPIObject struct {\n\t runtime.TypeMeta `json:\",inline\"`\n\t ... // other fields\n\t}\n\nfunc (obj *MyAwesomeAPIObject) SetGroupVersionKind(gvk *metav1.GroupVersionKind) { metav1.UpdateTypeMeta(obj,gvk) }; GroupVersionKind() *GroupVersionKind\n\nTypeMeta is provided here for convenience. You may use it directly from this package or define your own with the same fields.", + Description: "TypeMeta is shared by all top level objects. The proper way to use it is to inline it in your type, like this: type MyAwesomeAPIObject struct {\n runtime.TypeMeta `json:\",inline\"`\n ... // other fields\n} func (obj *MyAwesomeAPIObject) SetGroupVersionKind(gvk *metav1.GroupVersionKind) { metav1.UpdateTypeMeta(obj,gvk) }; GroupVersionKind() *GroupVersionKind\n\nTypeMeta is provided here for convenience. You may use it directly from this package or define your own with the same fields.", Type: []string{"object"}, Properties: map[string]spec.Schema{ "apiVersion": { diff --git a/pkg/kubernetes/internalimport/internal_import.go b/pkg/kubernetes/internalimport/internal_import.go index 62fab0ee..7b806785 100644 --- a/pkg/kubernetes/internalimport/internal_import.go +++ b/pkg/kubernetes/internalimport/internal_import.go @@ -17,28 +17,9 @@ package internalimport // To use k8s.io/kubernetes as a library, it is required to import the relevant packages related to // k8s and specify the specific version in go.mod. import ( - _ "k8s.io/api" - _ "k8s.io/apimachinery" - _ "k8s.io/apiserver" - _ "k8s.io/cli-runtime" - _ "k8s.io/client-go" _ "k8s.io/cloud-provider" - _ "k8s.io/cluster-bootstrap" - _ "k8s.io/code-generator" - _ "k8s.io/component-base" - _ "k8s.io/component-helpers" _ "k8s.io/controller-manager" - _ "k8s.io/cri-api" _ "k8s.io/csi-translation-lib" - _ "k8s.io/dynamic-resource-allocation" - _ "k8s.io/kms" - _ "k8s.io/kube-controller-manager" - _ "k8s.io/kube-proxy" - _ "k8s.io/kube-scheduler" - _ "k8s.io/kubectl" - _ "k8s.io/kubelet" - _ "k8s.io/legacy-cloud-providers" - _ "k8s.io/metrics" _ "k8s.io/mount-utils" _ "k8s.io/pod-security-admission" ) diff --git a/pkg/kubernetes/openapi/zz_generated.openapi.go b/pkg/kubernetes/openapi/zz_generated.openapi.go index efd1f631..58db9595 100644 --- a/pkg/kubernetes/openapi/zz_generated.openapi.go +++ b/pkg/kubernetes/openapi/zz_generated.openapi.go @@ -48307,7 +48307,7 @@ func schema_apimachinery_pkg_api_resource_Quantity(ref common.ReferenceCallback) Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ Description: "Quantity is a fixed-point representation of a number. It provides convenient marshaling/unmarshaling in JSON and YAML, in addition to String() and AsInt64() accessors.\n\nThe serialization format is:\n\n``` ::= \n\n\t(Note that may be empty, from the \"\" case in .)\n\n ::= 0 | 1 | ... | 9 ::= | ::= | . | . | . ::= \"+\" | \"-\" ::= | ::= | | ::= Ki | Mi | Gi | Ti | Pi | Ei\n\n\t(International System of units; See: http://physics.nist.gov/cuu/Units/binary.html)\n\n ::= m | \"\" | k | M | G | T | P | E\n\n\t(Note that 1024 = 1Ki but 1000 = 1k; I didn't choose the capitalization.)\n\n ::= \"e\" | \"E\" ```\n\nNo matter which of the three exponent forms is used, no quantity may represent a number greater than 2^63-1 in magnitude, nor may it have more than 3 decimal places. Numbers larger or more precise will be capped or rounded up. (E.g.: 0.1m will rounded up to 1m.) This may be extended in the future if we require larger or smaller quantities.\n\nWhen a Quantity is parsed from a string, it will remember the type of suffix it had, and will use the same type again when it is serialized.\n\nBefore serializing, Quantity will be put in \"canonical form\". This means that Exponent/suffix will be adjusted up or down (with a corresponding increase or decrease in Mantissa) such that:\n\n- No precision is lost - No fractional digits will be emitted - The exponent (or suffix) is as large as possible.\n\nThe sign will be omitted unless the number is negative.\n\nExamples:\n\n- 1.5 will be serialized as \"1500m\" - 1.5Gi will be serialized as \"1536Mi\"\n\nNote that the quantity will NEVER be internally represented by a floating point number. That is the whole point of this exercise.\n\nNon-canonical values will still parse as long as they are well formed, but will be re-emitted in their canonical form. (So always use canonical form, or don't diff.)\n\nThis format is intended to make it difficult to use these numbers without writing some sort of special handling code in the hopes that that will cause implementors to also use a fixed point implementation.", - OneOf: common.GenerateOpenAPIV3OneOfSchema(resource.Quantity{}.OpenAPIV3OneOfTypes()), + OneOf: []spec.Schema{{SchemaProps: spec.SchemaProps{Type: []string{"string", "number"}}}}, Format: resource.Quantity{}.OpenAPISchemaFormat(), }, }, @@ -50778,7 +50778,7 @@ func schema_apimachinery_pkg_util_intstr_IntOrString(ref common.ReferenceCallbac Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ Description: "IntOrString is a type that can hold an int32 or a string. When used in JSON or YAML marshalling and unmarshalling, it produces or consumes the inner type. This allows you to have, for example, a JSON field that can accept a name or number.", - OneOf: common.GenerateOpenAPIV3OneOfSchema(intstr.IntOrString{}.OpenAPIV3OneOfTypes()), + OneOf: []spec.Schema{{SchemaProps: spec.SchemaProps{Type: []string{"integer", "string"}}}}, Format: intstr.IntOrString{}.OpenAPISchemaFormat(), }, }, diff --git a/pkg/kubernetes/registry/cluster/storage_provider.go b/pkg/kubernetes/registry/cluster/storage_provider.go index 9519e360..d6dee72c 100644 --- a/pkg/kubernetes/registry/cluster/storage_provider.go +++ b/pkg/kubernetes/registry/cluster/storage_provider.go @@ -15,13 +15,14 @@ package cluster import ( - "github.com/KusionStack/karpor/pkg/kubernetes/apis/cluster" - "github.com/KusionStack/karpor/pkg/kubernetes/registry" - "github.com/KusionStack/karpor/pkg/kubernetes/scheme" "k8s.io/apiserver/pkg/registry/generic" "k8s.io/apiserver/pkg/registry/rest" genericapiserver "k8s.io/apiserver/pkg/server" serverstorage "k8s.io/apiserver/pkg/server/storage" + + "github.com/KusionStack/karpor/pkg/kubernetes/apis/cluster" + "github.com/KusionStack/karpor/pkg/kubernetes/registry" + "github.com/KusionStack/karpor/pkg/kubernetes/scheme" ) var _ registry.RESTStorageProvider = &RESTStorageProvider{} @@ -35,7 +36,7 @@ func (p RESTStorageProvider) GroupName() string { func (p RESTStorageProvider) NewRESTStorage( apiResourceConfigSource serverstorage.APIResourceConfigSource, restOptionsGetter generic.RESTOptionsGetter, -) (genericapiserver.APIGroupInfo, error) { +) (genericapiserver.APIGroupInfo, bool, error) { apiGroupInfo := genericapiserver.NewDefaultAPIGroupInfo( cluster.GroupName, scheme.Scheme, @@ -46,7 +47,7 @@ func (p RESTStorageProvider) NewRESTStorage( v1beta1 := map[string]rest.Storage{} clusterStorage, err := NewREST(restOptionsGetter) if err != nil { - return genericapiserver.APIGroupInfo{}, err + return genericapiserver.APIGroupInfo{}, false, err } v1beta1["clusters"] = clusterStorage.Cluster @@ -54,5 +55,5 @@ func (p RESTStorageProvider) NewRESTStorage( v1beta1["clusters/proxy"] = clusterStorage.Proxy apiGroupInfo.VersionedResourcesStorageMap["v1beta1"] = v1beta1 - return apiGroupInfo, nil + return apiGroupInfo, true, nil } diff --git a/pkg/kubernetes/registry/search/storage_provider.go b/pkg/kubernetes/registry/search/storage_provider.go index aed948cd..57174f3c 100644 --- a/pkg/kubernetes/registry/search/storage_provider.go +++ b/pkg/kubernetes/registry/search/storage_provider.go @@ -54,7 +54,7 @@ func (p RESTStorageProvider) GroupName() string { func (p RESTStorageProvider) NewRESTStorage( apiResourceConfigSource serverstorage.APIResourceConfigSource, restOptionsGetter generic.RESTOptionsGetter, -) (genericapiserver.APIGroupInfo, error) { +) (genericapiserver.APIGroupInfo, bool, error) { apiGroupInfo := genericapiserver.NewDefaultAPIGroupInfo( search.GroupName, scheme.Scheme, @@ -64,11 +64,11 @@ func (p RESTStorageProvider) NewRESTStorage( storageMap, err := p.v1beta1Storage(restOptionsGetter) if err != nil { - return genericapiserver.APIGroupInfo{}, err + return genericapiserver.APIGroupInfo{}, false, err } apiGroupInfo.VersionedResourcesStorageMap["v1beta1"] = storageMap - return apiGroupInfo, nil + return apiGroupInfo, true, nil } func (p RESTStorageProvider) v1beta1Storage( diff --git a/pkg/kubernetes/registry/types.go b/pkg/kubernetes/registry/types.go index 91d6cfd2..bc6256b0 100644 --- a/pkg/kubernetes/registry/types.go +++ b/pkg/kubernetes/registry/types.go @@ -26,10 +26,7 @@ import ( // RESTStorageProvider is a factory type for REST storage. type RESTStorageProvider interface { GroupName() string - NewRESTStorage( - apiResourceConfigSource serverstorage.APIResourceConfigSource, - restOptionsGetter generic.RESTOptionsGetter, - ) (genericapiserver.APIGroupInfo, error) + NewRESTStorage(apiResourceConfigSource serverstorage.APIResourceConfigSource, restOptionsGetter generic.RESTOptionsGetter) (genericapiserver.APIGroupInfo, bool, error) } // ExtraConfig holds custom apiserver config diff --git a/pkg/server/server.go b/pkg/server/server.go index 2b09b964..cc9a7e81 100644 --- a/pkg/server/server.go +++ b/pkg/server/server.go @@ -22,6 +22,13 @@ import ( "io/fs" "net/http" + "github.com/go-chi/chi/v5" + "k8s.io/apiserver/pkg/registry/generic" + genericapiserver "k8s.io/apiserver/pkg/server" + serverstorage "k8s.io/apiserver/pkg/server/storage" + "k8s.io/klog/v2" + rbacrest "k8s.io/kubernetes/pkg/registry/rbac/rest" + "github.com/KusionStack/karpor/pkg/core/route" "github.com/KusionStack/karpor/pkg/kubernetes/registry" clusterstorage "github.com/KusionStack/karpor/pkg/kubernetes/registry/cluster" @@ -29,12 +36,6 @@ import ( searchstorage "github.com/KusionStack/karpor/pkg/kubernetes/registry/search" "github.com/KusionStack/karpor/pkg/kubernetes/scheme" "github.com/KusionStack/karpor/ui" - "github.com/go-chi/chi/v5" - "k8s.io/apiserver/pkg/registry/generic" - genericapiserver "k8s.io/apiserver/pkg/server" - serverstorage "k8s.io/apiserver/pkg/server/storage" - "k8s.io/klog/v2" - rbacrest "k8s.io/kubernetes/pkg/registry/rbac/rest" ) // KarporServer is the carrier of the main process of Karpor. @@ -183,7 +184,7 @@ func (s *KarporServer) InstallAPIs( apiGroupsInfo := []*genericapiserver.APIGroupInfo{} for _, restStorageProvider := range restStorageProviders { groupName := restStorageProvider.GroupName() - apiGroupInfo, err := restStorageProvider.NewRESTStorage( + apiGroupInfo, _, err := restStorageProvider.NewRESTStorage( apiResourceConfigSource, restOptionsGetter, ) diff --git a/pkg/syncer/agent.go b/pkg/syncer/agent.go new file mode 100644 index 00000000..73b1d6a7 --- /dev/null +++ b/pkg/syncer/agent.go @@ -0,0 +1,230 @@ +// Copyright The Karpor Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package syncer + +import ( + "context" + "fmt" + "os" + "os/user" + "path" + "path/filepath" + "sync" + + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/client-go/discovery" + "k8s.io/client-go/rest" + "k8s.io/client-go/restmapper" + "k8s.io/client-go/tools/clientcmd" + clientcmdapi "k8s.io/client-go/tools/clientcmd/api" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/builder" + "sigs.k8s.io/controller-runtime/pkg/event" + "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/predicate" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + "sigs.k8s.io/controller-runtime/pkg/source" + + "github.com/KusionStack/karpor/pkg/infra/search/storage" + clusterv1beta1 "github.com/KusionStack/karpor/pkg/kubernetes/apis/cluster/v1beta1" + searchv1beta1 "github.com/KusionStack/karpor/pkg/kubernetes/apis/search/v1beta1" + "github.com/KusionStack/karpor/pkg/syncer/utils" +) + +type AgentReconciler struct { + SyncReconciler + gvrToGVKCache sync.Map + discoveryClient discovery.DiscoveryInterface + clusterName string +} + +// NewAgentReconciler creates a new instance of the AgentReconciler structure with the given storage. +func NewAgentReconciler(storage storage.ResourceStorage, clusterName string) *AgentReconciler { + return &AgentReconciler{ + SyncReconciler: SyncReconciler{ + storage: storage, + }, + + clusterName: clusterName, + } +} + +// SetupWithManager sets up the AgentReconciler with the given manager and registers it as a controller. Different from the SyncReconcile, it only focus on the special cluster. +func (r *AgentReconciler) SetupWithManager(mgr ctrl.Manager) error { + // only focus on the special cluster + clusterFilter := predicate.Funcs{ + CreateFunc: func(e event.CreateEvent) bool { + return e.Object.GetName() == r.clusterName + }, + UpdateFunc: func(e event.UpdateEvent) bool { + return e.ObjectNew.GetName() == r.clusterName + }, + DeleteFunc: func(e event.DeleteEvent) bool { + return e.Object.GetName() == r.clusterName + }, + GenericFunc: func(e event.GenericEvent) bool { + return e.Object.GetName() == r.clusterName + }, + } + + controller, err := ctrl.NewControllerManagedBy(mgr). + For(&clusterv1beta1.Cluster{}, builder.WithPredicates(clusterFilter)). + Watches(&source.Kind{Type: &searchv1beta1.SyncRegistry{}}, &handler.Funcs{ + CreateFunc: r.CreateEvent, + UpdateFunc: r.UpdateEvent, + DeleteFunc: r.DeleteEvent, + }). + Build(r) + if err != nil { + return err + } + r.client = mgr.GetClient() + r.controller = controller + r.discoveryClient = discovery.NewDiscoveryClientForConfigOrDie(mgr.GetConfig()) + // TODO: + r.mgr = NewMultiClusterSyncManager(context.Background(), r.controller, r.storage) + return nil +} + +// Reconcile is the main entry point for the syncer reconciler, which is called whenever there is a change in the watched resources. +func (r *AgentReconciler) Reconcile(ctx context.Context, req reconcile.Request) (reconcile.Result, error) { + logger := ctrl.LoggerFrom(ctx) + + var cluster clusterv1beta1.Cluster + if err := r.client.Get(ctx, req.NamespacedName, &cluster); err != nil { + if apierrors.IsNotFound(err) { + logger.Info("cluster doesn't exist", "cluster", req.Name) + return reconcile.Result{}, r.stopCluster(ctx, req.Name) + } + return reconcile.Result{}, err + } + + if !cluster.DeletionTimestamp.IsZero() { + logger.Info("cluster is being deleted", "cluster", cluster.Name) + + err := r.stopCluster(ctx, cluster.Name) + if err != nil { + return reconcile.Result{}, err + } + + if len(cluster.Finalizers) > 0 { + cluster.Finalizers = nil + err := r.client.Update(ctx, &cluster) + if err != nil && !apierrors.IsNotFound(err) { + return reconcile.Result{}, err + } + } + + return reconcile.Result{}, nil + } + + return reconcile.Result{}, r.handleClusterAddOrUpdate(ctx, cluster.DeepCopy(), buildClusterConfigInAgent, r.getResources) +} + +// getResources retrieves the list of resource sync rules for the given cluster. +func (r *AgentReconciler) getResources(ctx context.Context, cluster *clusterv1beta1.Cluster) (allResources []*searchv1beta1.ResourceSyncRule, validResources []*searchv1beta1.ResourceSyncRule, retErr error) { + registries, err := r.getRegistries(ctx, cluster) + if err != nil { + return nil, nil, err + } + + for _, registry := range registries { + if !registry.DeletionTimestamp.IsZero() { + continue + } + + resources, err := r.getNormalizedResources(ctx, ®istry) + if err != nil { + return nil, nil, err + } + + for gvr, rule := range resources { + allResources = append(allResources, rule) + gvk, err := r.gvrToGVK(ctx, gvr) + if err != nil { + return nil, nil, err + } + if _, exist := utils.GetSyncGVK(gvk); exist { + // If gvk map consist special gvk, it means that gvk is already reconciled by dynamic reconciler + utils.SetSyncGVK(gvk, *rule) + } else { + // only set rule without in gvk map + validResources = append(validResources, rule) + } + } + } + return allResources, validResources, nil +} + +func (r *AgentReconciler) gvrToGVK(ctx context.Context, gvr schema.GroupVersionResource) (schema.GroupVersionKind, error) { + if val, ok := r.gvrToGVKCache.Load(gvr); ok { + return val.(schema.GroupVersionKind), nil + } + + zero := schema.GroupVersionKind{} + + groupResources, err := restmapper.GetAPIGroupResources(r.discoveryClient) + if err != nil { + return zero, err + } + + mapper := restmapper.NewDiscoveryRESTMapper(groupResources) + gvk, err := mapper.KindFor(gvr) + if err != nil { + return zero, err + } + + ctrl.LoggerFrom(ctx).Info("GVR to GVV", "gvr", gvr, "gvk", gvk) + r.gvrToGVKCache.Store(gvr, gvk) + return gvk, nil +} + +func buildClusterConfigInAgent(cluster *clusterv1beta1.Cluster) (*rest.Config, error) { + loadingRules := &clientcmd.ClientConfigLoadingRules{ + WarnIfAllMissing: false, + Precedence: []string{clientcmd.RecommendedHomeFile}, + MigrationRules: map[string]string{ + clientcmd.RecommendedHomeFile: filepath.Join(os.Getenv("HOME"), clientcmd.RecommendedHomeDir, ".kubeconfig"), + }, + } + if _, ok := os.LookupEnv("HOME"); !ok { + u, err := user.Current() + if err != nil { + return nil, fmt.Errorf("could not get current user: %v", err) + } + loadingRules.Precedence = append(loadingRules.Precedence, path.Join(u.HomeDir, clientcmd.RecommendedHomeDir, clientcmd.RecommendedFileName)) + } + cfg, err := loadConfigWithContext("", loadingRules, "") + if err != nil { + return nil, err + } + if cfg.QPS == 0.0 { + cfg.QPS = 20.0 + cfg.Burst = 30.0 + } + return cfg, nil +} + +func loadConfigWithContext(apiServerURL string, loader clientcmd.ClientConfigLoader, context string) (*rest.Config, error) { + return clientcmd.NewNonInteractiveDeferredLoadingClientConfig( + loader, + &clientcmd.ConfigOverrides{ + ClusterInfo: clientcmdapi.Cluster{ + Server: apiServerURL, + }, + CurrentContext: context, + }).ClientConfig() +} diff --git a/pkg/syncer/agent_test.go b/pkg/syncer/agent_test.go new file mode 100644 index 00000000..16d56e4f --- /dev/null +++ b/pkg/syncer/agent_test.go @@ -0,0 +1,212 @@ +// Copyright The Karpor Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//nolint:dupl +package syncer + +import ( + "context" + "testing" + + "github.com/bytedance/mockey" + "github.com/stretchr/testify/require" + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/restmapper" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + "github.com/KusionStack/karpor/pkg/infra/search/storage" + "github.com/KusionStack/karpor/pkg/infra/search/storage/elasticsearch" + clusterv1beta1 "github.com/KusionStack/karpor/pkg/kubernetes/apis/cluster/v1beta1" + searchv1beta1 "github.com/KusionStack/karpor/pkg/kubernetes/apis/search/v1beta1" + "github.com/KusionStack/karpor/pkg/kubernetes/scheme" +) + +func TestAgentReconciler_Reconcile(t *testing.T) { + tests := []struct { + name string + cluster *clusterv1beta1.Cluster + req reconcile.Request + wantErr bool + }{ + { + "test no error", + &clusterv1beta1.Cluster{ObjectMeta: metav1.ObjectMeta{Name: "cluster1"}}, + reconcile.Request{NamespacedName: types.NamespacedName{Name: "cluster1"}}, + false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r := &AgentReconciler{ + SyncReconciler: SyncReconciler{ + client: fake.NewClientBuilder().WithRuntimeObjects(tt.cluster).WithScheme(scheme.Scheme).Build(), + }, + } + m := mockey.Mock((*SyncReconciler).handleClusterAddOrUpdate).Return(nil).Build() + defer m.UnPatch() + _, err := r.Reconcile(context.TODO(), tt.req) + if tt.wantErr { + require.Error(t, err) + } else { + require.NoError(t, err) + } + }) + } +} + +func TestNewAgentReconciler(t *testing.T) { + tests := []struct { + name string + storage storage.ResourceStorage + clusterName string + }{ + { + "test nil", + nil, + "example-cluster", + }, + { + "test not nil", + &elasticsearch.Storage{}, + "example-cluster2", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := NewAgentReconciler(tt.storage, tt.clusterName) + require.Equal(t, got.storage, tt.storage) + require.Equal(t, got.clusterName, tt.clusterName) + }) + } +} + +func TestAgentReconciler_getResources(t *testing.T) { + tests := []struct { + name string + cluster *clusterv1beta1.Cluster + registries []searchv1beta1.SyncRegistry + resources map[schema.GroupVersionResource]*searchv1beta1.ResourceSyncRule + allResources []*searchv1beta1.ResourceSyncRule + validResources []*searchv1beta1.ResourceSyncRule + wantErr bool + }{ + { + "test no error", + &clusterv1beta1.Cluster{ObjectMeta: metav1.ObjectMeta{Name: "cluster1"}}, + []searchv1beta1.SyncRegistry{ + { + ObjectMeta: metav1.ObjectMeta{Name: "example-registry1"}, + Spec: searchv1beta1.SyncRegistrySpec{ + Clusters: []string{"cluster1"}, + }, + }, + }, + map[schema.GroupVersionResource]*searchv1beta1.ResourceSyncRule{ + v1.SchemeGroupVersion.WithResource("pods"): { + APIVersion: "v1", + Resource: "pods", + }, + }, + []*searchv1beta1.ResourceSyncRule{ + { + APIVersion: "v1", + Resource: "pods", + }, + }, + []*searchv1beta1.ResourceSyncRule{ + { + APIVersion: "v1", + Resource: "pods", + }, + }, + false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r := &AgentReconciler{ + SyncReconciler: SyncReconciler{ + client: fake.NewClientBuilder().WithRuntimeObjects(tt.cluster).WithScheme(scheme.Scheme).Build(), + }, + } + r.gvrToGVKCache.Store(v1.SchemeGroupVersion.WithResource("pods"), schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Pod"}) + + mockey.Mock((*SyncReconciler).getRegistries).Return(tt.registries, nil).Build() + mockey.Mock((*SyncReconciler).getNormalizedResources).Return(tt.resources, nil).Build() + defer mockey.UnPatchAll() + allResources, validResources, err := r.getResources(context.TODO(), tt.cluster) + require.Equal(t, allResources, tt.allResources) + require.Equal(t, validResources, tt.validResources) + if tt.wantErr { + require.Error(t, err) + } else { + require.NoError(t, err) + } + }) + } +} + +func TestAgentReconciler_gvrToGVK(t *testing.T) { + tests := []struct { + name string + gvk schema.GroupVersionKind + gvr schema.GroupVersionResource + wantErr bool + }{ + { + name: "test gvrToGVK", + gvk: v1.SchemeGroupVersion.WithKind("Pod"), + gvr: v1.SchemeGroupVersion.WithResource("pods"), + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r := &AgentReconciler{} + + mockey.Mock(restmapper.GetAPIGroupResources).Return([]*restmapper.APIGroupResources{ + { + Group: metav1.APIGroup{ + Name: "", + Versions: []metav1.GroupVersionForDiscovery{ + {GroupVersion: "v1", Version: "v1"}, + }, + }, + VersionedResources: map[string][]metav1.APIResource{ + "v1": { + { + Name: "pods", + Kind: "Pod", + Version: "v1", + Group: "", + }, + }, + }, + }, + }, nil).Build() + + got, err := r.gvrToGVK(context.Background(), tt.gvr) + require.Equal(t, tt.gvk, got) + if tt.wantErr { + require.Error(t, err) + } else { + require.NoError(t, err) + } + }) + } +} diff --git a/pkg/syncer/cache/resource_informer.go b/pkg/syncer/cache/resource_informer.go index f58e5bde..6b4c983e 100644 --- a/pkg/syncer/cache/resource_informer.go +++ b/pkg/syncer/cache/resource_informer.go @@ -23,10 +23,21 @@ import ( "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/watch" - "k8s.io/client-go/tools/cache" + clientgocache "k8s.io/client-go/tools/cache" "k8s.io/klog/v2" ) +// TransformFunc allows for transforming an object before it will be processed +// and put into the controller cache and before the corresponding handlers will +// be called on it. +// TransformFunc (similarly to ResourceEventHandler functions) should be able +// to correctly handle the tombstone of type cache.DeletedFinalStateUnknown +// +// The most common usage pattern is to clean-up some parts of the object to +// reduce component memory usage if a given component doesn't care about them. +// given controller doesn't care for them +type TransformFunc func(interface{}) (interface{}, error) + // ResourceHandler defines the interface for handling resource events. type ResourceHandler interface { OnAdd(obj interface{}) error @@ -63,23 +74,23 @@ type ResourceSelector interface { } // NewResourceInformer creates a new informer that watches for resource events and handles them using the provided ResourceHandler. -func NewResourceInformer(lw cache.ListerWatcher, +func NewResourceInformer(lw clientgocache.ListerWatcher, selector ResourceSelector, - transform cache.TransformFunc, + transform TransformFunc, resyncPeriod time.Duration, handler ResourceHandler, - knownObjects cache.KeyListerGetter, -) cache.Controller { + knownObjects clientgocache.KeyListerGetter, +) clientgocache.Controller { informerCache := NewResourceCache() - fifo := cache.NewDeltaFIFOWithOptions(cache.DeltaFIFOOptions{ + fifo := clientgocache.NewDeltaFIFOWithOptions(clientgocache.DeltaFIFOOptions{ KnownObjects: knownObjects, EmitDeltaTypeReplaced: true, }) - doProcess := func(obj interface{}, dType cache.DeltaType) error { + doProcess := func(obj interface{}, dType clientgocache.DeltaType) error { // transform if transform != nil { - if _, ok := obj.(cache.DeletedFinalStateUnknown); !ok { + if _, ok := obj.(clientgocache.DeletedFinalStateUnknown); !ok { transformed, err := transform(obj) if err != nil { return fmt.Errorf("error transforming object: %v, delta type: %s", err, dType) @@ -89,7 +100,7 @@ func NewResourceInformer(lw cache.ListerWatcher, } switch dType { - case cache.Sync, cache.Replaced, cache.Added, cache.Updated: + case clientgocache.Sync, clientgocache.Replaced, clientgocache.Added, clientgocache.Updated: if _, exists, err := informerCache.Get(obj); err == nil && exists { if newer, err := informerCache.IsNewer(obj); err != nil { return err @@ -107,7 +118,7 @@ func NewResourceInformer(lw cache.ListerWatcher, } return handler.OnAdd(obj) } - case cache.Deleted: + case clientgocache.Deleted: if err := informerCache.Delete(obj); err != nil { return err } @@ -116,12 +127,12 @@ func NewResourceInformer(lw cache.ListerWatcher, return nil } - cfg := &cache.Config{ + cfg := &clientgocache.Config{ Queue: fifo, ObjectType: &unstructured.Unstructured{}, FullResyncPeriod: resyncPeriod, RetryOnError: true, - ListerWatcher: &cache.ListWatch{ + ListerWatcher: &clientgocache.ListWatch{ ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { if selector != nil { selector.ApplyToList(&options) @@ -143,7 +154,7 @@ func NewResourceInformer(lw cache.ListerWatcher, } }() - deltas, ok := d.(cache.Deltas) + deltas, ok := d.(clientgocache.Deltas) if !ok { return errors.New("object given as Process argument is not Deltas") } @@ -161,5 +172,114 @@ func NewResourceInformer(lw cache.ListerWatcher, }, } - return cache.New(cfg) + return clientgocache.New(cfg) +} + +// NewInformerWithTransformer returns a Store and a controller for populating +// the store while also providing event notifications. You should only used +// the returned Store for Get/List operations; Add/Modify/Deletes will cause +// the event notifications to be faulty. +// The given transform function will be called on all objects before they will +// put into the Store and corresponding Add/Modify/Delete handlers will +// be invoked for them. +func NewInformerWithTransformer( + lw clientgocache.ListerWatcher, + objType runtime.Object, + resyncPeriod time.Duration, + h clientgocache.ResourceEventHandler, + transformer TransformFunc, +) (clientgocache.Store, clientgocache.Controller) { + // This will hold the client state, as we know it. + clientState := clientgocache.NewStore(clientgocache.DeletionHandlingMetaNamespaceKeyFunc) + + return clientState, newInformer(lw, objType, resyncPeriod, h, clientState, transformer) +} + +// newInformer returns a controller for populating the store while also +// providing event notifications. +// +// Parameters +// - lw is list and watch functions for the source of the resource you want to +// be informed of. +// - objType is an object of the type that you expect to receive. +// - resyncPeriod: if non-zero, will re-list this often (you will get OnUpdate +// calls, even if nothing changed). Otherwise, re-list will be delayed as +// long as possible (until the upstream source closes the watch or times out, +// or you stop the controller). +// - h is the object you want notifications sent to. +// - clientState is the store you want to populate +func newInformer( + lw clientgocache.ListerWatcher, + objType runtime.Object, + resyncPeriod time.Duration, + h clientgocache.ResourceEventHandler, + clientState clientgocache.Store, + transformer TransformFunc, +) clientgocache.Controller { + // This will hold incoming changes. Note how we pass clientState in as a + // KeyLister, that way resync operations will result in the correct set + // of update/delete deltas. + fifo := clientgocache.NewDeltaFIFOWithOptions(clientgocache.DeltaFIFOOptions{ + KnownObjects: clientState, + EmitDeltaTypeReplaced: true, + }) + + cfg := &clientgocache.Config{ + Queue: fifo, + ListerWatcher: lw, + ObjectType: objType, + FullResyncPeriod: resyncPeriod, + RetryOnError: false, + + Process: func(obj interface{}) error { + if deltas, ok := obj.(clientgocache.Deltas); ok { + return processDeltas(h, clientState, transformer, deltas) + } + return errors.New("object given as Process argument is not Deltas") + }, + } + return clientgocache.New(cfg) +} + +// Multiplexes updates in the form of a list of Deltas into a Store, and informs +// a given handler of events OnUpdate, OnAdd, OnDelete +func processDeltas( + // Object which receives event notifications from the given deltas + handler clientgocache.ResourceEventHandler, + clientState clientgocache.Store, + transformer TransformFunc, + deltas clientgocache.Deltas, +) error { + // from oldest to newest + for _, d := range deltas { + obj := d.Object + if transformer != nil { + var err error + obj, err = transformer(obj) + if err != nil { + return err + } + } + + switch d.Type { + case clientgocache.Sync, clientgocache.Replaced, clientgocache.Added, clientgocache.Updated: + if old, exists, err := clientState.Get(obj); err == nil && exists { + if err := clientState.Update(obj); err != nil { + return err + } + handler.OnUpdate(old, obj) + } else { + if err := clientState.Add(obj); err != nil { + return err + } + handler.OnAdd(obj) + } + case clientgocache.Deleted: + if err := clientState.Delete(obj); err != nil { + return err + } + handler.OnDelete(obj) + } + } + return nil } diff --git a/pkg/syncer/cache/resource_informer_test.go b/pkg/syncer/cache/resource_informer_test.go index c869702b..ddbfaede 100644 --- a/pkg/syncer/cache/resource_informer_test.go +++ b/pkg/syncer/cache/resource_informer_test.go @@ -16,14 +16,16 @@ package cache import ( "context" + "fmt" "strings" "sync" "testing" "time" - "github.com/KusionStack/karpor/pkg/syncer/utils" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + v1 "k8s.io/api/core/v1" + apiequality "k8s.io/apimachinery/pkg/api/equality" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/fields" @@ -35,6 +37,9 @@ import ( "k8s.io/client-go/dynamic/fake" clientgoscheme "k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/tools/cache" + fcache "k8s.io/client-go/tools/cache/testing" + + "github.com/KusionStack/karpor/pkg/syncer/utils" ) type OP uint @@ -196,7 +201,7 @@ func TestInformerWithTransformer(t *testing.T) { } tests := []struct { - transFunc cache.TransformFunc + transFunc TransformFunc updates []struct { action string data interface{} @@ -324,3 +329,125 @@ func makeUnstructured(namespace, name string, fields map[string]interface{}) *un } return u } + +func TestNewInformerWithTransformer(t *testing.T) { + // source simulates an apiserver object endpoint. + source := fcache.NewFakeControllerSource() + + makePod := func(name, generation string) *v1.Pod { + return &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: "namespace", + Labels: map[string]string{"generation": generation}, + }, + Spec: v1.PodSpec{ + Hostname: "hostname", + Subdomain: "subdomain", + }, + } + } + expectedPod := func(name, generation string) *v1.Pod { + pod := makePod(name, generation) + pod.Spec.Hostname = "new-hostname" + pod.Spec.Subdomain = "" + pod.Spec.NodeName = "nodename" + return pod + } + + source.Add(makePod("pod1", "1")) + source.Modify(makePod("pod1", "2")) + + type event struct { + eventType watch.EventType + previous interface{} + current interface{} + } + events := make(chan event, 10) + recordEvent := func(eventType watch.EventType, previous, current interface{}) { + events <- event{eventType: eventType, previous: previous, current: current} + } + verifyEvent := func(eventType watch.EventType, previous, current interface{}) { + select { + case event := <-events: + if event.eventType != eventType { + t.Errorf("expected type %v, got %v", eventType, event.eventType) + } + if !apiequality.Semantic.DeepEqual(event.previous, previous) { + t.Errorf("expected previous object %#v, got %#v", previous, event.previous) + } + if !apiequality.Semantic.DeepEqual(event.current, current) { + t.Errorf("expected object %#v, got %#v", current, event.current) + } + case <-time.After(wait.ForeverTestTimeout): + t.Errorf("failed to get event") + } + } + + podTransformer := func(obj interface{}) (interface{}, error) { + pod, ok := obj.(*v1.Pod) + if !ok { + return nil, fmt.Errorf("unexpected object type: %T", obj) + } + pod.Spec.Hostname = "new-hostname" + pod.Spec.Subdomain = "" + pod.Spec.NodeName = "nodename" + + // Clear out ResourceVersion to simplify comparisons. + pod.ResourceVersion = "" + + return pod, nil + } + + store, controller := NewInformerWithTransformer( + source, + &v1.Pod{}, + 0, + cache.ResourceEventHandlerFuncs{ + AddFunc: func(obj interface{}) { recordEvent(watch.Added, nil, obj) }, + UpdateFunc: func(oldObj, newObj interface{}) { recordEvent(watch.Modified, oldObj, newObj) }, + DeleteFunc: func(obj interface{}) { recordEvent(watch.Deleted, obj, nil) }, + }, + podTransformer, + ) + + verifyStore := func(expectedItems []interface{}) { + items := store.List() + if len(items) != len(expectedItems) { + t.Errorf("unexpected items %v, expected %v", items, expectedItems) + } + for _, expectedItem := range expectedItems { + found := false + for _, item := range items { + if apiequality.Semantic.DeepEqual(item, expectedItem) { + found = true + } + } + if !found { + t.Errorf("expected item %v not found in %v", expectedItem, items) + } + } + } + + stopCh := make(chan struct{}) + go controller.Run(stopCh) + + verifyEvent(watch.Added, nil, expectedPod("pod1", "2")) + verifyStore([]interface{}{expectedPod("pod1", "2")}) + + source.Add(makePod("pod2", "1")) + verifyEvent(watch.Added, nil, expectedPod("pod2", "1")) + verifyStore([]interface{}{expectedPod("pod1", "2"), expectedPod("pod2", "1")}) + + source.Add(makePod("pod3", "1")) + verifyEvent(watch.Added, nil, expectedPod("pod3", "1")) + + source.Modify(makePod("pod2", "2")) + verifyEvent(watch.Modified, expectedPod("pod2", "1"), expectedPod("pod2", "2")) + + source.Delete(makePod("pod1", "2")) + verifyEvent(watch.Deleted, expectedPod("pod1", "2"), nil) + verifyStore([]interface{}{expectedPod("pod2", "2"), expectedPod("pod3", "1")}) + + close(stopCh) +} diff --git a/pkg/syncer/dynamic_reconciler.go b/pkg/syncer/dynamic_reconciler.go new file mode 100644 index 00000000..0d3fb3e1 --- /dev/null +++ b/pkg/syncer/dynamic_reconciler.go @@ -0,0 +1,232 @@ +// Copyright The Karpor Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package syncer + +import ( + "context" + "reflect" + "time" + + "github.com/go-logr/logr" + "github.com/pkg/errors" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller" + "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/manager" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + "sigs.k8s.io/controller-runtime/pkg/source" + + "github.com/KusionStack/karpor/pkg/infra/search/storage" + "github.com/KusionStack/karpor/pkg/infra/search/storage/elasticsearch" + "github.com/KusionStack/karpor/pkg/kubernetes/apis/search/v1beta1" + syncercache "github.com/KusionStack/karpor/pkg/syncer/cache" + "github.com/KusionStack/karpor/pkg/syncer/transform" + "github.com/KusionStack/karpor/pkg/syncer/utils" +) + +type DynamicReconciler struct { + ctx context.Context + gvk schema.GroupVersionKind + clusterName string + logger logr.Logger + storage storage.ResourceStorage + + syncRule v1beta1.ResourceSyncRule + transformFunc syncercache.TransformFunc + trimFunc syncercache.TransformFunc + client client.Client + + scheme *runtime.Scheme +} + +func NewDynamicReconciler(ctx context.Context, clusterName string, gvk schema.GroupVersionKind, storage storage.ResourceStorage) *DynamicReconciler { + return &DynamicReconciler{ + ctx: ctx, + clusterName: clusterName, + gvk: gvk, + logger: ctrl.Log.WithName(gvk.String()), + storage: storage, + syncRule: utils.ZeroVal, + } +} + +func (r *DynamicReconciler) SetupWithManager(mgr manager.Manager) error { + // step 1: set client for DynamicReconciler + r.client = mgr.GetClient() + r.scheme = mgr.GetScheme() + + // step 2: set manager + c, err := controller.New(r.gvk.String(), mgr, controller.Options{ + Reconciler: r, + MaxConcurrentReconciles: 10, + }) + if err != nil { + return err + } + + obj := &unstructured.Unstructured{} + obj.SetGroupVersionKind(r.gvk) + err = c.Watch(&source.Kind{Type: obj}, &handler.EnqueueRequestForObject{}) + if err != nil { + return err + } + + // step 3: register transform func for template pkg + if err := transform.RegisterClusterTmplFunc(r.clusterName, "objectRef", r.getObject); err != nil { + r.logger.Error(err, "error in registering tmpl func") + } + + return nil +} + +func (r *DynamicReconciler) Reconcile(ctx context.Context, req reconcile.Request) (res reconcile.Result, retErr error) { + logger := r.logger + logger.WithValues("kind", r.gvk).WithValues("name", req.NamespacedName) + + defer func() { + if retErr != nil { + logger.Error(retErr, "reconciliation error") + } + if err := recover(); err != nil { + logger.Error(errors.New("expected no panic; recovered"), "") + } + }() + + if reflect.DeepEqual(r.syncRule, utils.ZeroVal) { + rule, exist := utils.GetSyncGVK(r.gvk) + if exist && !reflect.DeepEqual(rule, utils.ZeroVal) { + r.syncRule = rule + + if rule.Transform != nil { + transformFunc, err := parseTransformer(ctx, rule.Transform, r.clusterName) + if err != nil { + return reconcile.Result{}, err + } + r.transformFunc = transformFunc + } + + if rule.Trim != nil { + trimFunc, err := parseTrimer(ctx, rule.Trim) + if err != nil { + return reconcile.Result{}, err + } + r.trimFunc = trimFunc + } + + logger.Info("get rule now, start to sync.") + } else { + logger.Info("wait rule ready, restart in 20 seconds...") + return reconcile.Result{RequeueAfter: 20 * time.Second}, nil + } + } + + /* + If using protobuf type to receive unstructured Object in the 1.12 Kubernetes, it will decode failed. + To be compatible with old version kubernetes, it uses object.Object rather than unstructured Object. + */ + runtimeObj, err := r.scheme.New(r.gvk) + if err != nil { + runtimeObj = &unstructured.Unstructured{} + } + obj, err := runtimeObjectToObject(runtimeObj) + if err != nil { + return reconcile.Result{}, err + } + + err = r.client.Get(ctx, req.NamespacedName, obj) + if err != nil { + if apierrors.IsNotFound(err) { + obj := genUnObj(r.syncRule, req.String()) + err = r.storage.DeleteResource(ctx, r.clusterName, obj) + if errors.Is(err, elasticsearch.ErrNotFound) { + logger.Error(err, "failed to delete resource", "kind", r.gvk.String(), "name", obj.GetName(), "namespace", obj.GetNamespace()) + return reconcile.Result{}, nil + } + return reconcile.Result{}, err + } + return reconcile.Result{}, err + } + + unstructuredObj, err := objectToUnstructured(obj) + if err != nil { + return reconcile.Result{}, err + } + // trimer + if r.trimFunc != nil { + val, err := r.trimFunc(unstructuredObj) + if err != nil { + return reconcile.Result{}, err + } + unstructuredObj = val.(*unstructured.Unstructured) + } + // transformer + if r.transformFunc != nil { + val, err := r.transformFunc(unstructuredObj) + if err != nil { + return reconcile.Result{}, err + } + unstructuredObj = val.(*unstructured.Unstructured) + } + err = r.storage.SaveResource(ctx, r.clusterName, unstructuredObj) + if err != nil { + return reconcile.Result{}, errors.Wrap(err, "failed to save resource") + } + + return reconcile.Result{}, nil +} + +func (r *DynamicReconciler) getObject(apiVersion, kind, namespace, name string) (interface{}, error) { + gv, err := schema.ParseGroupVersion(apiVersion) + if err != nil { + return nil, err + } + + obj := &unstructured.Unstructured{} + obj.SetGroupVersionKind(schema.GroupVersionKind{ + Group: gv.Group, + Version: gv.Version, + Kind: kind, + }) + err = r.client.Get(context.Background(), client.ObjectKey{ + Namespace: namespace, + Name: name, + }, obj) + if err != nil { + return nil, err + } + return obj, nil +} + +func objectToUnstructured(obj interface{}) (*unstructured.Unstructured, error) { + unstructuredMap, err := runtime.DefaultUnstructuredConverter.ToUnstructured(obj) + if err != nil { + return nil, err + } + unstructuredObj := &unstructured.Unstructured{Object: unstructuredMap} + return unstructuredObj, nil +} + +func runtimeObjectToObject(runtimeObj interface{}) (client.Object, error) { + obj, ok := runtimeObj.(client.Object) + if !ok { + return nil, errors.New("error in convert runtimeObj") + } + return obj, nil +} diff --git a/pkg/syncer/dynamic_reconciler_test.go b/pkg/syncer/dynamic_reconciler_test.go new file mode 100644 index 00000000..edbb63a2 --- /dev/null +++ b/pkg/syncer/dynamic_reconciler_test.go @@ -0,0 +1,74 @@ +// Copyright The Karpor Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package syncer + +import ( + "context" + "testing" + + "github.com/bytedance/mockey" + "github.com/stretchr/testify/require" + v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + "github.com/KusionStack/karpor/pkg/infra/search/storage/elasticsearch" + "github.com/KusionStack/karpor/pkg/kubernetes/apis/search/v1beta1" + "github.com/KusionStack/karpor/pkg/kubernetes/scheme" + "github.com/KusionStack/karpor/pkg/syncer/utils" +) + +func TestDynamicReconciler_Reconcile(t *testing.T) { + tests := []struct { + name string + gvk schema.GroupVersionKind + pod *v1.Pod + req reconcile.Request + wantErr bool + }{ + { + "test no error with pods", + schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Pod"}, + &v1.Pod{}, + reconcile.Request{NamespacedName: types.NamespacedName{Name: "cluster1"}}, + false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r := NewDynamicReconciler(context.Background(), "cluster-name", tt.gvk, &elasticsearch.Storage{}) + r.client = fake.NewClientBuilder().WithRuntimeObjects(tt.pod).WithScheme(scheme.Scheme).Build() + r.scheme = scheme.Scheme + utils.SetSyncGVK(schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Pod"}, v1beta1.ResourceSyncRule{ + Namespace: "karpor", + Transform: &v1beta1.TransformRuleSpec{ + Type: "patch", + }, + Trim: &v1beta1.TrimRuleSpec{}, + }) + + m := mockey.Mock((*elasticsearch.Storage).SaveResource).Return(nil).Build() + defer m.UnPatch() + _, err := r.Reconcile(context.TODO(), tt.req) + if tt.wantErr { + require.Error(t, err) + } else { + require.NoError(t, err) + } + }) + } +} diff --git a/pkg/syncer/parser.go b/pkg/syncer/parser.go new file mode 100644 index 00000000..c1e6b8f3 --- /dev/null +++ b/pkg/syncer/parser.go @@ -0,0 +1,139 @@ +// Copyright The Karpor Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package syncer + +import ( + "bytes" + "context" + "fmt" + "text/template" + + sprig "github.com/Masterminds/sprig/v3" + "github.com/go-logr/logr" + "github.com/pkg/errors" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + clientgocache "k8s.io/client-go/tools/cache" + + "github.com/KusionStack/karpor/pkg/kubernetes/apis/search/v1beta1" + syncercache "github.com/KusionStack/karpor/pkg/syncer/cache" + "github.com/KusionStack/karpor/pkg/syncer/jsonextracter" + "github.com/KusionStack/karpor/pkg/syncer/transform" + "github.com/KusionStack/karpor/pkg/util/jsonpath" +) + +// parseTrimer creates and returns a trim function for the informerSource based on the provider trims. +func parseTrimer(ctx context.Context, t *v1beta1.TrimRuleSpec) (syncercache.TransformFunc, error) { + if t == nil || len(t.Retain.JSONPaths) == 0 { + //nolint:nilnil,nolintlint + return nil, nil + } + + extracters := make([]jsonextracter.Extracter, 0, len(t.Retain.JSONPaths)) + for _, p := range t.Retain.JSONPaths { + p, err := jsonpath.RelaxedJSONPathExpression(p) + if err != nil { + return nil, err + } + + ex, err := jsonextracter.BuildExtracter(p, true) + if err != nil { + return nil, err + } + extracters = append(extracters, ex) + } + + trimFunc := func(obj interface{}) (ret interface{}, err error) { + defer func() { + if err != nil { + logr.FromContext(ctx).Error(err, "error in triming object") + ret, err = obj, nil + } + }() + + if d, ok := obj.(clientgocache.DeletedFinalStateUnknown); ok { + // Since we import ES data into informer cache at startup, the + // resource that was deleted during the restart will generate + // DeletedFinalStateUnknown. + // We unwarp the object here, so there is no need for following + // steps including event handler to care about DeletedFinalStateUnknown. + obj = d.Obj + } + + u, ok := obj.(*unstructured.Unstructured) + if !ok { + return nil, fmt.Errorf("trim: object's type should be *unstructured.Unstructured, but received %T", obj) + } + + merged, err := jsonextracter.Merge(extracters, u.Object) + if err != nil { + return nil, err + } + + unObj := &unstructured.Unstructured{Object: merged} + return unObj, nil + } + + return trimFunc, nil +} + +// parseTransformer creates and returns a transformation function for the informerSource based on the provider transformers. +func parseTransformer(ctx context.Context, t *v1beta1.TransformRuleSpec, clusterName string) (syncercache.TransformFunc, error) { + if t == nil { + //nolint:nilnil,nolintlint + return nil, nil + } + + fn, found := transform.GetTransformFunc(t.Type) + if !found { + return nil, fmt.Errorf("unsupported transform type %q", t.Type) + } + + tmpl, err := newTemplate(t.ValueTemplate, clusterName) + if err != nil { + return nil, errors.Wrap(err, "invalid transform template") + } + + return func(obj interface{}) (ret interface{}, err error) { + defer func() { + if err != nil { + logr.FromContext(ctx).Error(err, "error in transforming object") + } + }() + + u, ok := obj.(*unstructured.Unstructured) + if !ok { + return nil, fmt.Errorf("transform: object's type should be *unstructured.Unstructured, but received %T", obj) + } + + templateData := struct { + *unstructured.Unstructured + Cluster string + }{ + Unstructured: u, + Cluster: clusterName, + } + var buf bytes.Buffer + if err := tmpl.Execute(&buf, templateData); err != nil { + return nil, errors.Wrap(err, "transform: error rendering template") + } + return fn(obj, buf.String()) + }, nil +} + +// newTemplate creates and returns a new text template from the provided string, which can be used for processing templates in the syncer. +func newTemplate(tmpl, cluster string) (*template.Template, error) { + clusterFuncs, _ := transform.GetClusterTmplFuncs(cluster) + return template.New("transformTemplate").Funcs(sprig.FuncMap()).Funcs(clusterFuncs).Parse(tmpl) +} diff --git a/pkg/syncer/parser_test.go b/pkg/syncer/parser_test.go new file mode 100644 index 00000000..6f7aa843 --- /dev/null +++ b/pkg/syncer/parser_test.go @@ -0,0 +1,29 @@ +// Copyright The Karpor Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package syncer + +import ( + "context" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/KusionStack/karpor/pkg/kubernetes/apis/search/v1beta1" +) + +func TestResourceSyncer_parseTransformer(t *testing.T) { + _, err := parseTransformer(context.Background(), &v1beta1.TransformRuleSpec{Type: "patch"}, "clusterName") + require.NoError(t, err) +} diff --git a/pkg/syncer/reconciler.go b/pkg/syncer/reconciler.go index e0532bae..e181b254 100644 --- a/pkg/syncer/reconciler.go +++ b/pkg/syncer/reconciler.go @@ -15,24 +15,40 @@ package syncer import ( + "bytes" "context" + "crypto" + "crypto/x509" + "encoding/base64" "fmt" "reflect" + templateUtil "text/template" - "github.com/KusionStack/karpor/pkg/infra/search/storage" - clusterv1beta1 "github.com/KusionStack/karpor/pkg/kubernetes/apis/cluster/v1beta1" - searchv1beta1 "github.com/KusionStack/karpor/pkg/kubernetes/apis/search/v1beta1" "github.com/pkg/errors" + "k8s.io/api/core/v1" + rbacv1 "k8s.io/api/rbac/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/types" + utilErr "k8s.io/apimachinery/pkg/util/errors" + "k8s.io/client-go/dynamic" "k8s.io/client-go/rest" clientcmdapi "k8s.io/client-go/tools/clientcmd/api" + "k8s.io/client-go/util/keyutil" "k8s.io/client-go/util/workqueue" "k8s.io/klog/v2" + "github.com/KusionStack/karpor/config" + "github.com/KusionStack/karpor/pkg/infra/search/storage" + clusterv1beta1 "github.com/KusionStack/karpor/pkg/kubernetes/apis/cluster/v1beta1" + searchv1beta1 "github.com/KusionStack/karpor/pkg/kubernetes/apis/search/v1beta1" + "github.com/KusionStack/karpor/pkg/syncer/template" + "github.com/KusionStack/karpor/pkg/syncer/utils" + "github.com/KusionStack/karpor/pkg/util/certgenerator" + ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller" @@ -50,14 +66,32 @@ const ( type SyncReconciler struct { storage storage.ResourceStorage + highAvailability bool + client client.Client controller controller.Controller mgr MultiClusterSyncManager + + storageAddresses []string + + externalEndpoint string + agentImageTag string + + caCert *x509.Certificate + caKey crypto.Signer } // NewSyncReconciler creates a new instance of the SyncReconciler structure with the given storage. -func NewSyncReconciler(storage storage.ResourceStorage) *SyncReconciler { - return &SyncReconciler{storage: storage} +func NewSyncReconciler(storage storage.ResourceStorage, highAvailability bool, storageAddresses []string, externalEndpoint, agentImageTag string, caCert *x509.Certificate, caKey crypto.Signer) *SyncReconciler { + return &SyncReconciler{ + storage: storage, + highAvailability: highAvailability, + storageAddresses: storageAddresses, + externalEndpoint: externalEndpoint, + caCert: caCert, + caKey: caKey, + agentImageTag: agentImageTag, + } } // SetupWithManager sets up the SyncReconciler with the given manager and registers it as a controller. @@ -121,7 +155,7 @@ func (r *SyncReconciler) DeleteEvent(de event.DeleteEvent, queue workqueue.RateL } // Reconcile is the main entry point for the syncer reconciler, which is called whenever there is a change in the watched resources. -func (r *SyncReconciler) Reconcile(ctx context.Context, req reconcile.Request) (reconcile.Result, error) { +func (r *SyncReconciler) Reconcile(ctx context.Context, req reconcile.Request) (res reconcile.Result, retErr error) { logger := ctrl.LoggerFrom(ctx) var cluster clusterv1beta1.Cluster @@ -133,9 +167,24 @@ func (r *SyncReconciler) Reconcile(ctx context.Context, req reconcile.Request) ( return reconcile.Result{}, err } + defer func() { + // patch of delete finalizer for high availability cluster. + if r.isReconcilerEnabled(&cluster) { + err := r.client.Update(ctx, &cluster) + if err != nil && !apierrors.IsNotFound(err) { + retErr = err + } + } + }() + if !cluster.DeletionTimestamp.IsZero() { logger.Info("cluster is being deleted", "cluster", cluster.Name) - return reconcile.Result{}, r.stopCluster(ctx, cluster.Name) + + err := r.reconcileDelete(ctx, &cluster) + if err != nil { + return reconcile.Result{}, err + } + return reconcile.Result{}, nil } // TODO: it's danger @@ -144,7 +193,49 @@ func (r *SyncReconciler) Reconcile(ctx context.Context, req reconcile.Request) ( // return reconcile.Result{}, r.stopCluster(ctx, cluster.Name) // } - return reconcile.Result{}, r.handleClusterAddOrUpdate(ctx, cluster.DeepCopy()) + if r.highAvailability { + // set finalizer for cluster. + cluster.SetFinalizers([]string{clusterv1beta1.ClusterFinalizer}) + if cluster.Spec.Mode == clusterv1beta1.PushClusterMode { + return reconcile.Result{}, r.handleClusterAddOrUpdateForPush(ctx, cluster.DeepCopy()) + } + // default pull mode + if r.isReconcilerEnabled(&cluster) { + return reconcile.Result{}, r.handleClusterAddOrUpdateForPull(ctx, cluster.DeepCopy()) + } + return reconcile.Result{}, nil + } + + return reconcile.Result{}, r.handleClusterAddOrUpdate(ctx, cluster.DeepCopy(), buildClusterConfigInSyncer, r.getResources) +} + +// reconcileDelete delete relevant resources for cluster. +func (r *SyncReconciler) reconcileDelete(ctx context.Context, cluster *clusterv1beta1.Cluster) error { + err := r.stopCluster(ctx, cluster.Name) + if err != nil { + return err + } + + if r.highAvailability && cluster.Spec.Mode == clusterv1beta1.PushClusterMode { + clusterConfig, err := buildClusterConfigInSyncer(cluster) + if err != nil { + return errors.Wrapf(err, "failed to build config for cluster %s", cluster.Name) + } + dynamicClient, err := dynamic.NewForConfig(clusterConfig) + if err != nil { + return errors.Wrapf(err, "failed to build dynamic client for cluster %s", cluster.Name) + } + + err = dynamicClient.Resource(clusterv1beta1.SchemeGroupVersion.WithResource("clusters")).Namespace("").Delete(ctx, cluster.Name, metav1.DeleteOptions{}) + if err != nil { + return errors.Wrapf(err, "failed to delete cluster cr %s in user cluster", cluster.Name) + } + } + + if r.isReconcilerEnabled(cluster) { + cluster.SetFinalizers(nil) + } + return nil } // stopCluster stops the reconciliation process for the given cluster. @@ -155,6 +246,21 @@ func (r *SyncReconciler) stopCluster(ctx context.Context, clusterName string) er return err } r.mgr.Stop(ctx, clusterName) + + if r.highAvailability { + // delete secret + secretName := fmt.Sprintf("%s-agent", clusterName) + err := r.client.Delete(ctx, &v1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: secretName, + Namespace: "karpor", + }, + }) + if err != nil && !apierrors.IsNotFound(err) { + return err + } + } + logger.Info("syncing cluster has been stopped", "cluster", clusterName) return nil } @@ -168,15 +274,15 @@ func (r *SyncReconciler) startCluster(ctx context.Context, clusterName string) e } // handleClusterAddOrUpdate is responsible for handling the addition or update of a cluster resource. -func (r *SyncReconciler) handleClusterAddOrUpdate(ctx context.Context, cluster *clusterv1beta1.Cluster) error { +func (r *SyncReconciler) handleClusterAddOrUpdate(ctx context.Context, cluster *clusterv1beta1.Cluster, buildClusterConfig func(cluster *clusterv1beta1.Cluster) (*rest.Config, error), getResourcesFunc func(ctx context.Context, cluster *clusterv1beta1.Cluster) ([]*searchv1beta1.ResourceSyncRule, []*searchv1beta1.ResourceSyncRule, error)) error { logger := ctrl.LoggerFrom(ctx) - resources, err := r.getResources(ctx, cluster) + allResources, validResources, err := getResourcesFunc(ctx, cluster) if err != nil { return errors.Wrapf(err, "error detecting sync resources of the cluster %s", cluster.Name) } - if len(resources) == 0 { + if len(allResources) == 0 { logger.Info("cluster has no resources to sync", "cluster", cluster.Name) return r.stopCluster(ctx, cluster.Name) } @@ -205,17 +311,65 @@ func (r *SyncReconciler) handleClusterAddOrUpdate(ctx context.Context, cluster * } } - if err := singleMgr.UpdateSyncResources(ctx, resources); err != nil { + if err := singleMgr.UpdateSyncResources(ctx, validResources); err != nil { return errors.Wrapf(err, "failed to update sync resources for cluster %s", cluster.Name) } return nil } +// handleClusterAddOrUpdateForPush dispatches the relevant crds resources to user cluster for a push mode cluster resource in high-availability scene. +func (r *SyncReconciler) handleClusterAddOrUpdateForPush(ctx context.Context, cluster *clusterv1beta1.Cluster) error { + logger := ctrl.LoggerFrom(ctx) + logger.V(5).Info("handle cluster has been added/updated, push mode", "cluster", cluster.Name) + + // build user client + clusterConfig, err := buildClusterConfigInSyncer(cluster) + if err != nil { + return errors.Wrapf(err, "failed to build config for cluster %s", cluster.Name) + } + dynamicClient, err := dynamic.NewForConfig(clusterConfig) + if err != nil { + return errors.Wrapf(err, "failed to build dynamic client for cluster %s", cluster.Name) + } + + // must apply crds before other resources. + err = utils.ApplyCrds(ctx, dynamicClient) + if err != nil { + return errors.Wrapf(err, "failed to apply crds for cluster %s", cluster.Name) + } + + err = r.dispatchResources(ctx, dynamicClient, cluster) + if err != nil { + return errors.Wrapf(err, "failed to dispatch resources for cluster %s", cluster.Name) + } + + err = r.generateAgentYaml(ctx, cluster) + if err != nil { + return errors.Wrapf(err, "failed to generate agent yaml for push mode cluster %s", cluster.Name) + } + + return err +} + +// handleClusterAddOrUpdate generate the cert for agent in user cluster +func (r *SyncReconciler) handleClusterAddOrUpdateForPull(ctx context.Context, cluster *clusterv1beta1.Cluster) error { + err := r.createClusterRoleAndBinding(ctx, cluster) + if err != nil { + return err + } + + err = r.generateAgentYaml(ctx, cluster) + if err != nil { + return errors.Wrapf(err, "failed to generate agent yaml for pull mmode cluster %s", cluster.Name) + } + return nil +} + // getResources retrieves the list of resource sync rules for the given cluster. -func (r *SyncReconciler) getResources(ctx context.Context, cluster *clusterv1beta1.Cluster) ([]*searchv1beta1.ResourceSyncRule, error) { +func (r *SyncReconciler) getResources(ctx context.Context, cluster *clusterv1beta1.Cluster) ([]*searchv1beta1.ResourceSyncRule, []*searchv1beta1.ResourceSyncRule, error) { registries, err := r.getRegistries(ctx, cluster) if err != nil { - return nil, err + return nil, nil, err } var allResources []*searchv1beta1.ResourceSyncRule @@ -226,14 +380,14 @@ func (r *SyncReconciler) getResources(ctx context.Context, cluster *clusterv1bet resources, err := r.getNormalizedResources(ctx, ®istry) if err != nil { - return nil, err + return nil, nil, err } for _, r := range resources { allResources = append(allResources, r) } } - return allResources, nil + return allResources, nil, nil } // getNormalizedResources retrieves the normalized resource sync rules from the given sync registry. @@ -269,6 +423,222 @@ func (r *SyncReconciler) getNormalizedResources(ctx context.Context, registry *s return ret, nil } +// dispatchResources dispatch cluster, syncregistry, syncresource, transformrule and trimrule resource to user cluster. +func (r *SyncReconciler) dispatchResources(ctx context.Context, dynamicClient dynamic.Interface, cluster *clusterv1beta1.Cluster) error { + // collect the resources needed to be dispatched + unstructuredObjectMap := map[schema.GroupVersionResource][]unstructured.Unstructured{} + err := r.getUnstructuredCluster(cluster, unstructuredObjectMap) + if err != nil { + return errors.Wrapf(err, "error get unstructured cluster cr for cluster %s", cluster.Name) + } + + err = r.getUnstructuredRegistries(ctx, cluster, unstructuredObjectMap) + if err != nil { + return errors.Wrapf(err, "error get unstructured objects of the syncregistries for cluster %s", cluster.Name) + } + + // dispatch resources + var errs []error + for gvr := range unstructuredObjectMap { + for idx := range unstructuredObjectMap[gvr] { + unstructuredObj := unstructuredObjectMap[gvr][idx] + err := utils.CreateOrUpdateUnstructured(ctx, dynamicClient, gvr, "", &unstructuredObj) + if err != nil { + errs = append(errs, err) + } + } + } + + return utilErr.NewAggregate(errs) +} + +// createClusterRoleAndBinding create clusterrole and clusterrolebinding for agent in pull mode +func (r *SyncReconciler) createClusterRoleAndBinding(ctx context.Context, cluster *clusterv1beta1.Cluster) error { + clusterRoleName := fmt.Sprintf("%s-clusterrole", cluster.Name) + clusterRole := &rbacv1.ClusterRole{ + ObjectMeta: metav1.ObjectMeta{ + Name: clusterRoleName, + }, + Rules: []rbacv1.PolicyRule{ + { + APIGroups: []string{clusterv1beta1.SchemeGroupVersion.Group}, + Resources: []string{"clusters"}, + Verbs: []string{"get"}, + ResourceNames: []string{cluster.Name}, + }, + { + APIGroups: []string{clusterv1beta1.SchemeGroupVersion.Group}, + Resources: []string{"clusters"}, + Verbs: []string{"list", "watch"}, + }, + { + APIGroups: []string{searchv1beta1.SchemeGroupVersion.Group}, + Resources: []string{"syncregistries"}, + Verbs: []string{"get", "watch", "list"}, + }, + { + APIGroups: []string{searchv1beta1.SchemeGroupVersion.Group}, + Resources: []string{"syncclusterresources"}, + Verbs: []string{"get", "watch", "list"}, + }, + { + APIGroups: []string{searchv1beta1.SchemeGroupVersion.Group}, + Resources: []string{"transformrules"}, + Verbs: []string{"get", "watch", "list"}, + }, + { + APIGroups: []string{searchv1beta1.SchemeGroupVersion.Group}, + Resources: []string{"trimrules"}, + Verbs: []string{"get", "watch", "list"}, + }, + }, + } + + err := r.client.Create(ctx, clusterRole) + if err != nil && !apierrors.IsAlreadyExists(err) { + return errors.Wrapf(err, "failed to create clusterrole %s", clusterRole.Name) + } + + clusterRoleBinding := &rbacv1.ClusterRoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprintf("%s-clusterrolebinding", cluster.Name), + }, + Subjects: []rbacv1.Subject{ + { + APIGroup: rbacv1.SchemeGroupVersion.Group, + Kind: rbacv1.UserKind, + Name: cluster.Name, + }, + }, + RoleRef: rbacv1.RoleRef{ + APIGroup: rbacv1.SchemeGroupVersion.Group, + Kind: "ClusterRole", + Name: clusterRoleName, + }, + } + + err = r.client.Create(ctx, clusterRoleBinding) + if err != nil && !apierrors.IsAlreadyExists(err) { + return errors.Wrapf(err, "failed to create clusterrolebinding %s", clusterRoleBinding.Name) + } + + return nil +} + +// generateAgentYaml generate the agent yaml to be deployed in user cluster, and save yaml into secret. +func (r *SyncReconciler) generateAgentYaml(ctx context.Context, cluster *clusterv1beta1.Cluster) error { + var certData, keyData []byte + if cluster.Spec.Mode == clusterv1beta1.PullClusterMode { + certConfig := certgenerator.Config{ + CAName: "kubernetes", + CommonName: cluster.Name, + Year: 100, + Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, + } + cert, key, err := certgenerator.NewCaCertAndKeyFromRoot(certConfig, r.caCert, r.caKey) + if err != nil { + return err + } + certData = certgenerator.EncodeCertPEM(cert) + keyData, err = keyutil.MarshalPrivateKeyToPEM(key) + if err != nil { + return fmt.Errorf("unable to marshal private key to PEM %s", err) + } + } + + agentYml, err := r.renderYamlFile(cluster, base64.StdEncoding.EncodeToString(certData), base64.StdEncoding.EncodeToString(keyData)) + if err != nil { + return errors.Wrap(err, "failed to render agent yaml") + } + + err = applyAgentYmlSecret(ctx, r.client, cluster, []byte(agentYml)) + if err != nil { + return err + } + + return nil +} + +// getUnstructuredCluster retrieves the cluster cr for the given cluster. +func (r *SyncReconciler) getUnstructuredCluster(cluster *clusterv1beta1.Cluster, unstructuredObjectMap map[schema.GroupVersionResource][]unstructured.Unstructured) error { + // only dispatch cluster cr once + unstructuredObj, err := utils.ConvertToUnstructured(cluster) + if err != nil { + return errors.Wrapf(err, "failed to convert to unstructured object") + } + unstructuredObjectMap[clusterv1beta1.SchemeGroupVersion.WithResource("clusters")] = []unstructured.Unstructured{*unstructuredObj} + + return nil +} + +// getRegistries retrieves the list of sync registries for the given cluster. +func (r *SyncReconciler) getUnstructuredRegistries(ctx context.Context, cluster *clusterv1beta1.Cluster, unstructuredObjectMap map[schema.GroupVersionResource][]unstructured.Unstructured) error { + registries, err := r.getRegistries(ctx, cluster) + if err != nil { + return errors.Wrapf(err, "failed to get registries") + } + + // init map value + unstructuredRegistries := make([]unstructured.Unstructured, 0, len(registries)) + var unstructuredTransformRules []unstructured.Unstructured + var unstructuredTrimRules []unstructured.Unstructured + + // avoid to collect duplicate cr + transformRuleMap := make(map[string]struct{}) + trimRuleMap := make(map[string]struct{}) + + // collect cr list + for idx := range registries { + registry := registries[idx] + + unstructuredObj, err := utils.ConvertToUnstructured(®istry) + if err != nil { + return errors.Wrapf(err, "failed to convert to unstructured object for registry %s", registry.Name) + } + // do not set status when update + unstructuredObj.Object["status"] = nil + unstructuredRegistries = append(unstructuredRegistries, *unstructuredObj) + + // obtain relevant cr + for _, sr := range registry.Spec.SyncResources { + if _, ok := transformRuleMap[sr.TransformRefName]; !ok && sr.TransformRefName != "" { + rule, err := r.extractTransformRule(ctx, &sr) + if err != nil { + return err + } + unstructuredObj, err = utils.ConvertToUnstructured(rule) + if err != nil { + return errors.Wrapf(err, "failed to convert to unstructured object for transformrule %s", rule.Name) + } + + unstructuredTransformRules = append(unstructuredTransformRules, *unstructuredObj) + transformRuleMap[sr.TransformRefName] = struct{}{} + } + + if _, ok := trimRuleMap[sr.TrimRefName]; !ok && sr.TrimRefName != "" { + rule, err := r.extractTrimRule(ctx, &sr) + if err != nil { + return err + } + unstructuredObj, err = utils.ConvertToUnstructured(rule) + if err != nil { + return errors.Wrapf(err, "failed to convert to unstructured object for trimrule %s", rule.Name) + } + + unstructuredTrimRules = append(unstructuredTrimRules, *unstructuredObj) + transformRuleMap[sr.TrimRefName] = struct{}{} + } + } + } + + // set map + unstructuredObjectMap[searchv1beta1.SchemeGroupVersion.WithResource("syncregistries")] = unstructuredRegistries + unstructuredObjectMap[searchv1beta1.SchemeGroupVersion.WithResource("transformrules")] = unstructuredTransformRules + unstructuredObjectMap[searchv1beta1.SchemeGroupVersion.WithResource("trimrules")] = unstructuredTrimRules + + return nil +} + // getRegistries retrieves the list of sync registries for the given cluster. func (r *SyncReconciler) getRegistries(ctx context.Context, cluster *clusterv1beta1.Cluster) ([]searchv1beta1.SyncRegistry, error) { var syncRegistriesList searchv1beta1.SyncRegistryList @@ -332,33 +702,17 @@ func (r *SyncReconciler) getNormalizedResource(ctx context.Context, rsr *searchv normalized := rsr.DeepCopy() if rsr.TransformRefName != "" { - if rsr.Transform != nil { - return nil, fmt.Errorf("specify both Transform and TransformRefName in ResourceSyncRule is not allowed") - } - - var rule searchv1beta1.TransformRule - err := r.client.Get(ctx, types.NamespacedName{Name: rsr.TransformRefName}, &rule) + rule, err := r.extractTransformRule(ctx, rsr) if err != nil { - if apierrors.IsNotFound(err) { - return nil, fmt.Errorf("TransformRule referenced by name %q doesn't exist", rsr.TransformRefName) - } - return nil, errors.Wrapf(err, "failed to list transformRule %s from lister", rsr.TransformRefName) + return nil, err } normalized.Transform = &rule.Spec } if rsr.TrimRefName != "" { - if rsr.Trim != nil { - return nil, fmt.Errorf("specify both Trim and TrimRefName in ResourceSyncRule is not allowed") - } - - var rule searchv1beta1.TrimRule - err := r.client.Get(ctx, types.NamespacedName{Name: rsr.TrimRefName}, &rule) + rule, err := r.extractTrimRule(ctx, rsr) if err != nil { - if apierrors.IsNotFound(err) { - return nil, fmt.Errorf("TrimRule referenced by name %q doesn't exist", rsr.TrimRefName) - } - return nil, errors.Wrapf(err, "failed to list trimRule %s from lister", rsr.TrimRefName) + return nil, err } normalized.Trim = &rule.Spec } @@ -366,8 +720,80 @@ func (r *SyncReconciler) getNormalizedResource(ctx context.Context, rsr *searchv return normalized, nil } -// buildClusterConfig creates a rest.Config object for the given cluster. -func buildClusterConfig(cluster *clusterv1beta1.Cluster) (*rest.Config, error) { +// extractTransformRule extras transform rules from syncrule +func (r *SyncReconciler) extractTransformRule(ctx context.Context, rsr *searchv1beta1.ResourceSyncRule) (*searchv1beta1.TransformRule, error) { + var rule searchv1beta1.TransformRule + if rsr.Transform != nil { + return nil, fmt.Errorf("specify both Transform and TransformRefName in ResourceSyncRule is not allowed") + } + + err := r.client.Get(ctx, types.NamespacedName{Name: rsr.TransformRefName}, &rule) + if err != nil { + if apierrors.IsNotFound(err) { + return nil, fmt.Errorf("TransformRule referenced by name %q doesn't exist", rsr.TransformRefName) + } + return nil, errors.Wrapf(err, "failed to list transformRule %s from lister", rsr.TransformRefName) + } + return &rule, nil +} + +// extractTransformRule extras trim rules from syncrule +func (r *SyncReconciler) extractTrimRule(ctx context.Context, rsr *searchv1beta1.ResourceSyncRule) (*searchv1beta1.TrimRule, error) { + var rule searchv1beta1.TrimRule + if rsr.Trim != nil { + return nil, fmt.Errorf("specify both Trim and TrimRefName in ResourceSyncRule is not allowed") + } + + err := r.client.Get(ctx, types.NamespacedName{Name: rsr.TrimRefName}, &rule) + if err != nil { + if apierrors.IsNotFound(err) { + return nil, fmt.Errorf("TrimRule referenced by name %q doesn't exist", rsr.TrimRefName) + } + return nil, errors.Wrapf(err, "failed to list trimRule %s from lister", rsr.TrimRefName) + } + return &rule, nil +} + +// renderYamlFile render agent yaml use known parameters. +func (r *SyncReconciler) renderYamlFile(cluster *clusterv1beta1.Cluster, certData, keyData string) (string, error) { + c := template.Config{ + ClusterName: cluster.Name, + Level: cluster.Spec.Level, + StorageAddresses: r.storageAddresses, + ClusterMode: cluster.Spec.Mode, + AgentImageTag: r.agentImageTag, + } + + if cluster.Spec.Mode == clusterv1beta1.PullClusterMode { + c.ExternalEndpoint = r.externalEndpoint + c.CaCert = certData + c.CaKey = keyData + } + + agentYml, err := templateUtil.New("").Parse(string(config.AgentTpl)) + if err != nil { + return "", errors.Wrap(err, "failed to parse agent yaml") + } + + var renderedTemplate bytes.Buffer + err = agentYml.Execute(&renderedTemplate, c) + if err != nil { + return "", errors.Wrap(err, "failed to render agent yaml") + } + + return renderedTemplate.String(), nil +} + +// isReconcilerEnabled verify if execute for cluster object, which can compatible for existed non-ha cluster. +func (r *SyncReconciler) isReconcilerEnabled(cluster *clusterv1beta1.Cluster) bool { + if r.highAvailability && cluster.Spec.Mode != clusterv1beta1.PushClusterMode && r.caCert == nil && r.caKey == nil { + return false + } + return true +} + +// buildClusterConfigInSyncer creates a rest.Config object for the given cluster in syncer. +func buildClusterConfigInSyncer(cluster *clusterv1beta1.Cluster) (*rest.Config, error) { access := cluster.Spec.Access if len(access.Endpoint) == 0 { return nil, fmt.Errorf("cluster %s's endpoint is empty", cluster.Name) @@ -418,3 +844,38 @@ func buildClusterConfig(cluster *clusterv1beta1.Cluster) (*rest.Config, error) { } return &config, nil } + +// applyAgentYmlSecret apply agent yml +func applyAgentYmlSecret(ctx context.Context, cli client.Client, cluster *clusterv1beta1.Cluster, content []byte) error { + secretName := fmt.Sprintf("%s-agent", cluster.Name) + newAgentSecret := &v1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: secretName, + Namespace: "karpor", + }, + Data: map[string][]byte{ + "config": content, + }, + } + + oldAgentSecret := &v1.Secret{} + err := cli.Get(ctx, client.ObjectKey{ + Name: secretName, + Namespace: "karpor", + }, oldAgentSecret) + if err != nil { + if !apierrors.IsNotFound(err) { + return errors.Wrap(err, "failed to create agent secret") + } + err = cli.Create(ctx, newAgentSecret) + if err != nil { + return errors.Wrap(err, "failed to update agent secret") + } + } else if !reflect.DeepEqual(oldAgentSecret.Data, newAgentSecret.Data) { + err = cli.Update(ctx, newAgentSecret) + if err != nil { + return errors.Wrap(err, "failed to update agent secret") + } + } + return nil +} diff --git a/pkg/syncer/reconciler_test.go b/pkg/syncer/reconciler_test.go index dd658d6f..1eeecbaa 100644 --- a/pkg/syncer/reconciler_test.go +++ b/pkg/syncer/reconciler_test.go @@ -17,27 +17,34 @@ package syncer import ( "context" + "crypto" + "crypto/x509" "testing" - "github.com/KusionStack/karpor/pkg/infra/search/storage" - "github.com/KusionStack/karpor/pkg/infra/search/storage/elasticsearch" - clusterv1beta1 "github.com/KusionStack/karpor/pkg/kubernetes/apis/cluster/v1beta1" - searchv1beta1 "github.com/KusionStack/karpor/pkg/kubernetes/apis/search/v1beta1" - "github.com/KusionStack/karpor/pkg/kubernetes/scheme" "github.com/bytedance/mockey" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/dynamic" + dynamicfake "k8s.io/client-go/dynamic/fake" "k8s.io/client-go/rest" "sigs.k8s.io/controller-runtime/pkg/client/fake" "sigs.k8s.io/controller-runtime/pkg/event" "sigs.k8s.io/controller-runtime/pkg/reconcile" + + "github.com/KusionStack/karpor/pkg/infra/search/storage" + "github.com/KusionStack/karpor/pkg/infra/search/storage/elasticsearch" + clusterv1beta1 "github.com/KusionStack/karpor/pkg/kubernetes/apis/cluster/v1beta1" + searchv1beta1 "github.com/KusionStack/karpor/pkg/kubernetes/apis/search/v1beta1" + "github.com/KusionStack/karpor/pkg/kubernetes/scheme" + "github.com/KusionStack/karpor/pkg/syncer/utils" ) -func Test_buildClusterConfig(t *testing.T) { +func Test_buildClusterConfigInSyncer(t *testing.T) { tests := []struct { name string cluster *clusterv1beta1.Cluster @@ -80,7 +87,7 @@ func Test_buildClusterConfig(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, err := buildClusterConfig(tt.cluster) + got, err := buildClusterConfigInSyncer(tt.cluster) if tt.wantErr { require.Error(t, err) } else { @@ -462,22 +469,43 @@ func TestSyncReconciler_getRegistries(t *testing.T) { func TestNewSyncReconciler(t *testing.T) { tests := []struct { - name string - storage storage.ResourceStorage + name string + storage storage.ResourceStorage + highAvailability bool + storageAddresses []string + externalEndpoint string + agentImageTag string + caCert *x509.Certificate + caKey crypto.Signer }{ { "test nil", nil, + false, + []string{"127.0.0.1"}, + "127.0.0.1", + "v1.0.0", + nil, + nil, }, { "test not nil", &elasticsearch.Storage{}, + false, + []string{"127.0.0.1"}, + "127.0.0.1", + "v1.0.0", + nil, + nil, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got := NewSyncReconciler(tt.storage) + got := NewSyncReconciler(tt.storage, tt.highAvailability, tt.storageAddresses, + tt.externalEndpoint, tt.agentImageTag, tt.caCert, tt.caKey) require.Equal(t, got.storage, tt.storage) + require.Equal(t, got.highAvailability, tt.highAvailability) + require.Equal(t, got.storageAddresses, tt.storageAddresses) }) } } @@ -567,12 +595,12 @@ func TestSyncReconciler_getResources(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { r := &SyncReconciler{client: fake.NewClientBuilder().WithRuntimeObjects(tt.srs...).WithScheme(scheme.Scheme).Build()} - got, err := r.getResources(context.TODO(), tt.cluster) + allResources, _, err := r.getResources(context.TODO(), tt.cluster) if tt.wantErr { require.Error(t, err) } else { require.NoError(t, err) - require.Equal(t, tt.want, got) + require.Equal(t, tt.want, allResources) } }) } @@ -683,7 +711,62 @@ func TestSyncReconciler_handleClusterAddOrUpdate(t *testing.T) { mgr: &fakeMultiClusterSyncManager{m2}, client: fake.NewClientBuilder().WithRuntimeObjects(tt.srs...).WithScheme(scheme.Scheme).Build(), } - err := r.handleClusterAddOrUpdate(context.TODO(), tt.cluster) + err := r.handleClusterAddOrUpdate(context.TODO(), tt.cluster, buildClusterConfigInSyncer, r.getResources) + if tt.wantErr { + require.Error(t, err) + } else { + require.NoError(t, err) + } + }) + } +} + +func TestSyncReconciler_dispatchResources(t *testing.T) { + tests := []struct { + name string + ctx context.Context + cluster *clusterv1beta1.Cluster + dynamicClient dynamic.Interface + objects []runtime.Object + wantErr bool + }{ + { + name: "test no error", + ctx: context.Background(), + cluster: &clusterv1beta1.Cluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: "cluster1", + }, + Spec: clusterv1beta1.ClusterSpec{ + Mode: clusterv1beta1.PullClusterMode, + Level: 2, + }, + }, + dynamicClient: &dynamicfake.FakeDynamicClient{}, + objects: []runtime.Object{ + &searchv1beta1.SyncRegistry{ + ObjectMeta: metav1.ObjectMeta{ + ResourceVersion: "1", + }, + Spec: searchv1beta1.SyncRegistrySpec{ + Clusters: []string{"cluster1"}, + SyncResources: []searchv1beta1.ResourceSyncRule{{APIVersion: "v1", Resource: "pods", TransformRefName: "tfr1", TrimRefName: "tr1"}}, + }, + }, + &searchv1beta1.TransformRule{ObjectMeta: metav1.ObjectMeta{Name: "tfr1"}, Spec: searchv1beta1.TransformRuleSpec{}}, + &searchv1beta1.TrimRule{ObjectMeta: metav1.ObjectMeta{Name: "tr1"}, Spec: searchv1beta1.TrimRuleSpec{}}, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mockey.Mock(utils.CreateOrUpdateUnstructured).Return(nil).Build() + mockey.Mock((*SyncReconciler).getUnstructuredRegistries).Return(nil).Build() + defer mockey.UnPatchAll() + + r := &SyncReconciler{client: fake.NewClientBuilder().WithRuntimeObjects(tt.objects...).WithScheme(scheme.Scheme).Build()} + err := r.dispatchResources(tt.ctx, tt.dynamicClient, tt.cluster) if tt.wantErr { require.Error(t, err) } else { @@ -692,3 +775,118 @@ func TestSyncReconciler_handleClusterAddOrUpdate(t *testing.T) { }) } } + +func TestSyncReconciler_getUnstructuredRegistries(t *testing.T) { + tests := []struct { + name string + ctx context.Context + cluster *clusterv1beta1.Cluster + unstructuredObjectMap map[schema.GroupVersionResource][]unstructured.Unstructured + objects []runtime.Object + wantErr bool + }{ + { + name: "test transform and trim rule", + cluster: &clusterv1beta1.Cluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: "cluster1", + }, + Spec: clusterv1beta1.ClusterSpec{ + Mode: clusterv1beta1.PullClusterMode, + Level: 2, + }, + }, + unstructuredObjectMap: map[schema.GroupVersionResource][]unstructured.Unstructured{}, + objects: []runtime.Object{ + &searchv1beta1.SyncRegistry{ + ObjectMeta: metav1.ObjectMeta{ + ResourceVersion: "1", + }, + Spec: searchv1beta1.SyncRegistrySpec{ + Clusters: []string{"cluster1"}, + SyncResources: []searchv1beta1.ResourceSyncRule{{APIVersion: "v1", Resource: "pods", TransformRefName: "tfr1", TrimRefName: "tr1"}}, + }, + }, + &searchv1beta1.TransformRule{ObjectMeta: metav1.ObjectMeta{Name: "tfr1"}, Spec: searchv1beta1.TransformRuleSpec{}}, + &searchv1beta1.TrimRule{ObjectMeta: metav1.ObjectMeta{Name: "tr1"}, Spec: searchv1beta1.TrimRuleSpec{}}, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r := &SyncReconciler{client: fake.NewClientBuilder().WithRuntimeObjects(tt.objects...).WithScheme(scheme.Scheme).Build()} + err := r.getUnstructuredRegistries(tt.ctx, tt.cluster, tt.unstructuredObjectMap) + if tt.wantErr { + require.Error(t, err) + } else { + require.NoError(t, err) + } + }) + } +} + +func TestSyncReconciler_renderYamlFile(t *testing.T) { + tests := []struct { + name string + cluster *clusterv1beta1.Cluster + certData string + keyData string + want string + wantErr bool + }{ + { + name: "test pull mode", + cluster: &clusterv1beta1.Cluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: "cluster1", + }, + Spec: clusterv1beta1.ClusterSpec{ + Mode: clusterv1beta1.PullClusterMode, + Level: 2, + }, + }, + certData: "cert", + keyData: "key", + want: renderResForPull, + wantErr: false, + }, + { + name: "test pull mode", + cluster: &clusterv1beta1.Cluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: "cluster1", + }, + Spec: clusterv1beta1.ClusterSpec{ + Mode: clusterv1beta1.PushClusterMode, + Level: 2, + }, + }, + certData: "cert", + keyData: "key", + want: renderResForPush, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r := &SyncReconciler{ + storageAddresses: []string{"https://localhost:6443"}, + agentImageTag: "latest", + externalEndpoint: "https://localhost:6443", + } + got, err := r.renderYamlFile(tt.cluster, tt.certData, tt.keyData) + if tt.wantErr { + require.Error(t, err) + } else { + require.NoError(t, err) + require.Equal(t, tt.want, got) + } + }) + } +} + +const ( + renderResForPull = "apiVersion: v1\nkind: Namespace\nmetadata:\n name: karpor\nspec:\n finalizers:\n - kubernetes\n---\napiVersion: v1\ndata:\n config: |-\n apiVersion: v1\n clusters:\n - cluster:\n insecure-skip-tls-verify: true\n server: https://localhost:6443\n name: karpor\n contexts:\n - context:\n cluster: karpor\n user: cluster1\n name: default\n current-context: default\n kind: Config\n users:\n - name: cluster1\n user:\n client-certificate-data: cert\n client-key-data: key\nkind: ConfigMap\nmetadata:\n name: karpor-kubeconfig\n namespace: karpor\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n name: karpor-agent\n namespace: karpor\nspec:\n replicas: 1\n revisionHistoryLimit: 10\n selector:\n matchLabels:\n app.kubernetes.io/component: karpor-agent\n app.kubernetes.io/instance: karpor\n app.kubernetes.io/name: karpor\n strategy:\n rollingUpdate:\n maxSurge: 25%\n maxUnavailable: 25%\n type: RollingUpdate\n template:\n metadata:\n labels:\n app.kubernetes.io/component: karpor-agent\n app.kubernetes.io/instance: karpor\n app.kubernetes.io/name: karpor\n spec:\n containers:\n - args:\n - agent\n - --elastic-search-addresses=https://localhost:6443 \n - --cluster-name=cluster1\n - --cluster-mode=pull\n command:\n - /karpor\n env:\n - name: KUBECONFIG\n value: /etc/karpor/config\n image: kusionstack/karpor:latest\n imagePullPolicy: IfNotPresent\n name: karpor-agent\n ports:\n - containerPort: 7443\n protocol: TCP\n resources:\n limits:\n cpu: 500m\n ephemeral-storage: 10Gi\n memory: 1Gi\n requests:\n cpu: 250m\n ephemeral-storage: 2Gi\n memory: 256Mi\n volumeMounts:\n - mountPath: /etc/karpor/\n name: karpor-kubeconfig\n dnsPolicy: ClusterFirst\n restartPolicy: Always\n terminationGracePeriodSeconds: 30\n volumes:\n - configMap:\n defaultMode: 420\n name: karpor-kubeconfig\n name: karpor-kubeconfig\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n name: karpor\nroleRef:\n apiGroup: rbac.authorization.k8s.io\n kind: ClusterRole\n name: cluster-admin\nsubjects:\n- kind: ServiceAccount\n name: default\n namespace: karpor\n" + renderResForPush = "apiVersion: v1\nkind: Namespace\nmetadata:\n name: karpor\nspec:\n finalizers:\n - kubernetes\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n name: karpor-agent\n namespace: karpor\nspec:\n replicas: 1\n revisionHistoryLimit: 10\n selector:\n matchLabels:\n app.kubernetes.io/component: karpor-agent\n app.kubernetes.io/instance: karpor\n app.kubernetes.io/name: karpor\n strategy:\n rollingUpdate:\n maxSurge: 25%\n maxUnavailable: 25%\n type: RollingUpdate\n template:\n metadata:\n labels:\n app.kubernetes.io/component: karpor-agent\n app.kubernetes.io/instance: karpor\n app.kubernetes.io/name: karpor\n spec:\n containers:\n - args:\n - agent\n - --elastic-search-addresses=https://localhost:6443 \n - --cluster-name=cluster1\n - --cluster-mode=push\n command:\n - /karpor\n image: kusionstack/karpor:latest\n imagePullPolicy: IfNotPresent\n name: karpor-agent\n ports:\n - containerPort: 7443\n protocol: TCP\n resources:\n limits:\n cpu: 500m\n ephemeral-storage: 10Gi\n memory: 1Gi\n requests:\n cpu: 250m\n ephemeral-storage: 2Gi\n memory: 256Mi\n dnsPolicy: ClusterFirst\n restartPolicy: Always\n terminationGracePeriodSeconds: 30\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n name: karpor\nroleRef:\n apiGroup: rbac.authorization.k8s.io\n kind: ClusterRole\n name: cluster-admin\nsubjects:\n- kind: ServiceAccount\n name: default\n namespace: karpor\n" +) diff --git a/pkg/syncer/single_cluster_sync_manager_test.go b/pkg/syncer/single_cluster_sync_manager_test.go index 6cfbde2c..e22ebc3f 100644 --- a/pkg/syncer/single_cluster_sync_manager_test.go +++ b/pkg/syncer/single_cluster_sync_manager_test.go @@ -22,9 +22,10 @@ import ( "testing" "time" + "k8s.io/klog/v2/klogr" + "github.com/KusionStack/karpor/pkg/infra/search/storage/elasticsearch" - searchv1beta1 "github.com/KusionStack/karpor/pkg/kubernetes/apis/search/v1beta1" "github.com/bytedance/mockey" "github.com/go-logr/logr" "github.com/stretchr/testify/mock" @@ -32,12 +33,13 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/wait" "k8s.io/client-go/rest" - "k8s.io/klog/v2" "sigs.k8s.io/controller-runtime/pkg/controller" "sigs.k8s.io/controller-runtime/pkg/handler" "sigs.k8s.io/controller-runtime/pkg/predicate" "sigs.k8s.io/controller-runtime/pkg/reconcile" "sigs.k8s.io/controller-runtime/pkg/source" + + searchv1beta1 "github.com/KusionStack/karpor/pkg/kubernetes/apis/search/v1beta1" ) var _ SingleClusterSyncManager = &fakeSingleClusterSyncManager{} @@ -158,7 +160,7 @@ func Test_singleClusterSyncManager_Start(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - s := &singleClusterSyncManager{logger: klog.NewKlogr()} + s := &singleClusterSyncManager{logger: klogr.New()} err := s.Start(context.TODO()) time.Sleep(1 * time.Second) if tt.wantErr { @@ -185,7 +187,7 @@ func Test_singleClusterSyncManager_Stop(t *testing.T) { t.Run(tt.name, func(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) s := &singleClusterSyncManager{ - logger: klog.NewKlogr(), + logger: klogr.New(), ch: make(chan struct{}), ctx: ctx, cancel: cancel, @@ -227,7 +229,7 @@ func Test_singleClusterSyncManager_process(t *testing.T) { defer m.UnPatch() ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) s := &singleClusterSyncManager{ - logger: klog.NewKlogr(), + logger: klogr.New(), ch: make(chan struct{}), ctx: ctx, cancel: cancel, @@ -292,7 +294,7 @@ func Test_singleClusterSyncManager_handleSyncResourcesUpdate(t *testing.T) { syncResources: syncResources, syncers: syncers, stopped: false, - logger: klog.NewKlogr(), + logger: klogr.New(), } m := mockey.Mock((*wait.Group).StartWithContext).Return().Build() diff --git a/pkg/syncer/source.go b/pkg/syncer/source.go index 07600128..52228221 100644 --- a/pkg/syncer/source.go +++ b/pkg/syncer/source.go @@ -19,12 +19,6 @@ import ( "fmt" "time" - "github.com/KusionStack/karpor/pkg/infra/search/storage" - "github.com/KusionStack/karpor/pkg/kubernetes/apis/search/v1beta1" - "github.com/KusionStack/karpor/pkg/syncer/internal" - "github.com/KusionStack/karpor/pkg/syncer/jsonextracter" - "github.com/KusionStack/karpor/pkg/syncer/utils" - "github.com/KusionStack/karpor/pkg/util/jsonpath" "github.com/go-logr/logr" "github.com/pkg/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -40,6 +34,12 @@ import ( ctrlhandler "sigs.k8s.io/controller-runtime/pkg/handler" "sigs.k8s.io/controller-runtime/pkg/predicate" "sigs.k8s.io/controller-runtime/pkg/source" + + "github.com/KusionStack/karpor/pkg/infra/search/storage" + "github.com/KusionStack/karpor/pkg/kubernetes/apis/search/v1beta1" + syncercache "github.com/KusionStack/karpor/pkg/syncer/cache" + "github.com/KusionStack/karpor/pkg/syncer/internal" + "github.com/KusionStack/karpor/pkg/syncer/utils" ) const ( @@ -163,7 +163,7 @@ func (s *informerSource) Stop(ctx context.Context) error { } // createInformer sets up and returns the informer and controller for the informerSource, using the provided context, event handler, workqueue, and predicates. -func (s *informerSource) createInformer(_ context.Context, handler ctrlhandler.EventHandler, queue workqueue.RateLimitingInterface, predicates ...predicate.Predicate) (clientgocache.Store, clientgocache.Controller, error) { +func (s *informerSource) createInformer(ctx context.Context, handler ctrlhandler.EventHandler, queue workqueue.RateLimitingInterface, predicates ...predicate.Predicate) (clientgocache.Store, clientgocache.Controller, error) { gvr, err := parseGVR(&s.ResourceSyncRule) if err != nil { return nil, nil, errors.Wrap(err, "error parsing GroupVersionResource") @@ -174,7 +174,7 @@ func (s *informerSource) createInformer(_ context.Context, handler ctrlhandler.E return nil, nil, fmt.Errorf("error parsing selectors: %v", selectors) } - trim, err := s.parseTrimer() + trim, err := parseTrimer(ctx, s.ResourceSyncRule.Trim) if err != nil { return nil, nil, errors.Wrap(err, "error parsing trim rule") } @@ -194,7 +194,7 @@ func (s *informerSource) createInformer(_ context.Context, handler ctrlhandler.E } h := &internal.EventHandler{EventHandler: handler, Queue: queue, Predicates: predicates} - cache, informer := clientgocache.NewTransformingInformer(lw, &unstructured.Unstructured{}, resyncPeriod, h, trim) + cache, informer := syncercache.NewInformerWithTransformer(lw, &unstructured.Unstructured{}, resyncPeriod, h, trim) return cache, informer, nil } @@ -237,57 +237,3 @@ func parseSelectors(rsr v1beta1.ResourceSyncRule) ([]utils.Selector, error) { } return selectors, nil } - -func (s *informerSource) parseTrimer() (clientgocache.TransformFunc, error) { - t := s.ResourceSyncRule.Trim - if t == nil || len(t.Retain.JSONPaths) == 0 { - return nil, nil - } - - extracters := make([]jsonextracter.Extracter, 0, len(t.Retain.JSONPaths)) - for _, p := range t.Retain.JSONPaths { - p, err := jsonpath.RelaxedJSONPathExpression(p) - if err != nil { - return nil, err - } - - ex, err := jsonextracter.BuildExtracter(p, true) - if err != nil { - return nil, err - } - extracters = append(extracters, ex) - } - - trimFunc := func(obj interface{}) (ret interface{}, err error) { - defer func() { - if err != nil { - s.logger.Error(err, "error in triming object") - ret, err = obj, nil - } - }() - - if d, ok := obj.(clientgocache.DeletedFinalStateUnknown); ok { - // Since we import ES data into informer cache at startup, the - // resource that was deleted during the restart will generate - // DeletedFinalStateUnknown. - // We unwarp the object here, so there is no need for following - // steps including event handler to care about DeletedFinalStateUnknown. - obj = d.Obj - } - - u, ok := obj.(*unstructured.Unstructured) - if !ok { - return nil, fmt.Errorf("trim: object's type should be *unstructured.Unstructured, but received %T", obj) - } - - merged, err := jsonextracter.Merge(extracters, u.Object) - if err != nil { - return nil, err - } - - unObj := &unstructured.Unstructured{Object: merged} - return unObj, nil - } - - return trimFunc, nil -} diff --git a/pkg/syncer/source_test.go b/pkg/syncer/source_test.go index 22306346..06bf5506 100644 --- a/pkg/syncer/source_test.go +++ b/pkg/syncer/source_test.go @@ -21,6 +21,7 @@ import ( "github.com/KusionStack/karpor/pkg/infra/search/storage/elasticsearch" "github.com/KusionStack/karpor/pkg/kubernetes/apis/search/v1beta1" + "github.com/KusionStack/karpor/pkg/syncer/cache" "github.com/KusionStack/karpor/pkg/syncer/utils" "github.com/bytedance/mockey" "github.com/stretchr/testify/mock" @@ -196,7 +197,7 @@ func Test_informerSource_Start(t *testing.T) { t.Run("test no error", func(t *testing.T) { mockey.Mock((*utils.ESImporter).ImportTo).Return(nil).Build() informer := &controllertest.FakeInformer{} - mockey.Mock(clientgocache.NewTransformingInformer).Return(clientgocache.NewStore(clientgocache.DeletionHandlingMetaNamespaceKeyFunc), informer).Build() + mockey.Mock(cache.NewInformerWithTransformer).Return(clientgocache.NewStore(clientgocache.DeletionHandlingMetaNamespaceKeyFunc), informer).Build() defer mockey.UnPatchAll() s := &informerSource{ ResourceSyncRule: v1beta1.ResourceSyncRule{APIVersion: "v1", Resource: "pods"}, diff --git a/pkg/syncer/syncer.go b/pkg/syncer/syncer.go index 43d638f7..15ae2e70 100644 --- a/pkg/syncer/syncer.go +++ b/pkg/syncer/syncer.go @@ -15,19 +15,11 @@ package syncer import ( - "bytes" "context" "fmt" "strings" - "text/template" "time" - "github.com/KusionStack/karpor/pkg/infra/search/storage" - "github.com/KusionStack/karpor/pkg/infra/search/storage/elasticsearch" - "github.com/KusionStack/karpor/pkg/kubernetes/apis/search/v1beta1" - "github.com/KusionStack/karpor/pkg/syncer/transform" - "github.com/KusionStack/karpor/pkg/syncer/utils" - sprig "github.com/Masterminds/sprig/v3" "github.com/go-logr/logr" "github.com/pkg/errors" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -39,6 +31,12 @@ import ( "k8s.io/client-go/util/workqueue" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/KusionStack/karpor/pkg/infra/search/storage" + "github.com/KusionStack/karpor/pkg/infra/search/storage/elasticsearch" + "github.com/KusionStack/karpor/pkg/kubernetes/apis/search/v1beta1" + syncercache "github.com/KusionStack/karpor/pkg/syncer/cache" + "github.com/KusionStack/karpor/pkg/syncer/utils" ) const ( @@ -65,7 +63,7 @@ type ResourceSyncer struct { logger logr.Logger - transformFunc clientgocache.TransformFunc + transformFunc syncercache.TransformFunc startTime time.Time } @@ -139,7 +137,7 @@ func (s *ResourceSyncer) Run(ctx context.Context) error { // Wait for the caches to be synced before starting workers s.logger.Info("Waiting for informer caches to sync") - if transformFunc, err := s.parseTransformer(); err != nil { + if transformFunc, err := parseTransformer(ctx, s.source.SyncRule().Transform, s.source.Cluster()); err != nil { s.logger.Error(err, "error in parsing transform rule") } else { s.transformFunc = transformFunc @@ -279,50 +277,6 @@ func (s *ResourceSyncer) sync(ctx context.Context, key string) error { return nil } -// parseTransformer creates and returns a transformation function for the informerSource based on the ResourceSyncRule's transformers. -func (s *ResourceSyncer) parseTransformer() (clientgocache.TransformFunc, error) { - t := s.source.SyncRule().Transform - if t == nil { - return nil, nil - } - - fn, found := transform.GetTransformFunc(t.Type) - if !found { - return nil, fmt.Errorf("unsupported transform type %q", t.Type) - } - - tmpl, err := newTemplate(t.ValueTemplate, s.source.Cluster()) - if err != nil { - return nil, errors.Wrap(err, "invalid transform template") - } - - return func(obj interface{}) (ret interface{}, err error) { - defer func() { - if err != nil { - s.logger.Error(err, "error in transforming object") - } - }() - - u, ok := obj.(*unstructured.Unstructured) - if !ok { - return nil, fmt.Errorf("transform: object's type should be *unstructured.Unstructured, but received %T", obj) - } - - templateData := struct { - *unstructured.Unstructured - Cluster string - }{ - Unstructured: u, - Cluster: s.source.Cluster(), - } - var buf bytes.Buffer - if err := tmpl.Execute(&buf, templateData); err != nil { - return nil, errors.Wrap(err, "transform: error rendering template") - } - return fn(obj, buf.String()) - }, nil -} - // genUnObj creates a new unstructured.Unstructured object based on the ResourceSyncRule and key. func genUnObj(sr v1beta1.ResourceSyncRule, key string) *unstructured.Unstructured { obj := &unstructured.Unstructured{} @@ -337,9 +291,3 @@ func genUnObj(sr v1beta1.ResourceSyncRule, key string) *unstructured.Unstructure } return obj } - -// newTemplate creates and returns a new text template from the provided string, which can be used for processing templates in the syncer. -func newTemplate(tmpl, cluster string) (*template.Template, error) { - clusterFuncs, _ := transform.GetClusterTmplFuncs(cluster) - return template.New("transformTemplate").Funcs(sprig.FuncMap()).Funcs(clusterFuncs).Parse(tmpl) -} diff --git a/pkg/syncer/syncer_test.go b/pkg/syncer/syncer_test.go index 5d76e9ea..2b1cdb10 100644 --- a/pkg/syncer/syncer_test.go +++ b/pkg/syncer/syncer_test.go @@ -20,14 +20,15 @@ import ( "testing" "time" - "github.com/KusionStack/karpor/pkg/infra/search/storage" - "github.com/KusionStack/karpor/pkg/infra/search/storage/elasticsearch" - "github.com/KusionStack/karpor/pkg/kubernetes/apis/search/v1beta1" "github.com/bytedance/mockey" "github.com/stretchr/testify/require" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/client-go/util/workqueue" + + "github.com/KusionStack/karpor/pkg/infra/search/storage" + "github.com/KusionStack/karpor/pkg/infra/search/storage/elasticsearch" + "github.com/KusionStack/karpor/pkg/kubernetes/apis/search/v1beta1" ) func Test_genUnObj(t *testing.T) { @@ -289,15 +290,3 @@ func (q *fakeQueue) Add(item interface{}) { } q.queue = append(q.queue, item) } - -func TestResourceSyncer_parseTransformer(t *testing.T) { - s := &ResourceSyncer{ - source: &informerSource{ - ResourceSyncRule: v1beta1.ResourceSyncRule{Transform: &v1beta1.TransformRuleSpec{Type: "patch"}}, - cluster: "clusterName", - }, - } - - _, err := s.parseTransformer() - require.NoError(t, err) -} diff --git a/pkg/syncer/template/metadata.go b/pkg/syncer/template/metadata.go new file mode 100644 index 00000000..e145f0a8 --- /dev/null +++ b/pkg/syncer/template/metadata.go @@ -0,0 +1,27 @@ +// Copyright The Karpor Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package template + +// Config is the struct that defines the metadata of the agent template. +type Config struct { + ExternalEndpoint string + CaCert string + CaKey string + StorageAddresses []string + ClusterName string + ClusterMode string + Level int + AgentImageTag string +} diff --git a/pkg/syncer/utils/dynamic_sync_rule.go b/pkg/syncer/utils/dynamic_sync_rule.go new file mode 100644 index 00000000..4fdd2a6b --- /dev/null +++ b/pkg/syncer/utils/dynamic_sync_rule.go @@ -0,0 +1,40 @@ +// Copyright The Karpor Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package utils + +import ( + "sync" + + "k8s.io/apimachinery/pkg/runtime/schema" + + searchv1beta1 "github.com/KusionStack/karpor/pkg/kubernetes/apis/search/v1beta1" +) + +var ( + syncGVR sync.Map + ZeroVal = searchv1beta1.ResourceSyncRule{} +) + +func SetSyncGVK(gvk schema.GroupVersionKind, rule searchv1beta1.ResourceSyncRule) { + syncGVR.Store(gvk.String(), rule) +} + +func GetSyncGVK(gvk schema.GroupVersionKind) (searchv1beta1.ResourceSyncRule, bool) { + rule, exist := syncGVR.Load(gvk.String()) + if !exist { + return ZeroVal, false + } + return rule.(searchv1beta1.ResourceSyncRule), exist +} diff --git a/pkg/syncer/utils/dynamic_sync_rule_test.go b/pkg/syncer/utils/dynamic_sync_rule_test.go new file mode 100644 index 00000000..a218c37d --- /dev/null +++ b/pkg/syncer/utils/dynamic_sync_rule_test.go @@ -0,0 +1,49 @@ +// Copyright The Karpor Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package utils + +import ( + "testing" + + "github.com/stretchr/testify/require" + "k8s.io/apimachinery/pkg/runtime/schema" + + searchv1beta1 "github.com/KusionStack/karpor/pkg/kubernetes/apis/search/v1beta1" +) + +func TestGetSyncGVK(t *testing.T) { + // Test cases + testCases := []struct { + name string + gvk schema.GroupVersionKind + resourceSyncRule searchv1beta1.ResourceSyncRule + exist bool + }{ + { + name: "Success - ListKeys", + gvk: schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Pod"}, + resourceSyncRule: ZeroVal, + exist: false, + }, + } + // Execute test cases + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + resourceSyncRule, exist := GetSyncGVK(tc.gvk) + require.Equal(t, tc.exist, exist) + require.Equal(t, tc.resourceSyncRule, resourceSyncRule) + }) + } +} diff --git a/pkg/syncer/utils/kubernetes.go b/pkg/syncer/utils/kubernetes.go new file mode 100644 index 00000000..3fd7863a --- /dev/null +++ b/pkg/syncer/utils/kubernetes.go @@ -0,0 +1,95 @@ +// Copyright The Karpor Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package utils + +import ( + "context" + "fmt" + + "github.com/pkg/errors" + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/client-go/dynamic" + "sigs.k8s.io/yaml" + + "github.com/KusionStack/karpor/config" +) + +// ConvertToUnstructured converts the structured object to unstructured +func ConvertToUnstructured(obj runtime.Object) (*unstructured.Unstructured, error) { + unstructuredObj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(obj) + if err != nil { + return nil, fmt.Errorf("failed to convert to Unstructured: %v", err) + } + + return &unstructured.Unstructured{ + Object: unstructuredObj, + }, nil +} + +// ApplyCrds applies crds to user cluster before other resources. +func ApplyCrds(ctx context.Context, dynamicClient dynamic.Interface) error { + for _, crd := range config.CrdList { + var objMap map[string]interface{} + err := yaml.Unmarshal(crd, &objMap) + if err != nil { + return err + } + + unstructuredObj := &unstructured.Unstructured{ + Object: objMap, + } + err = CreateOrUpdateUnstructured(ctx, dynamicClient, apiextensionsv1.SchemeGroupVersion.WithResource("customresourcedefinitions"), "", unstructuredObj) + if err != nil { + return err + } + } + + return nil +} + +// CreateOrUpdateUnstructured creates or updates object using dynamic client. +func CreateOrUpdateUnstructured(ctx context.Context, dynamicClient dynamic.Interface, gvr schema.GroupVersionResource, namespace string, newObject *unstructured.Unstructured) error { + resourceClient := dynamicClient.Resource(gvr).Namespace(namespace) + + existingObj, getErr := resourceClient.Get(ctx, newObject.GetName(), metav1.GetOptions{}) + if getErr != nil { + if apierrors.IsNotFound(getErr) { + // set initial resource version + newObject.SetResourceVersion("0") + _, createErr := resourceClient.Create(ctx, newObject, metav1.CreateOptions{}) + if createErr != nil { + return errors.Wrapf(createErr, "failed to create resource") + } + } else { + return errors.Wrapf(getErr, "failed to get resource: %v", getErr) + } + } else { + // set uid and resource version for existed object + newObject.SetResourceVersion(existingObj.GetResourceVersion()) + newObject.SetUID(existingObj.GetUID()) + + _, updateErr := resourceClient.Update(ctx, newObject, metav1.UpdateOptions{}) + if updateErr != nil { + return errors.Wrapf(updateErr, "failed to update resource: %v", newObject.GetName()) + } + } + + return nil +} diff --git a/pkg/util/certgenerator/generator_test.go b/pkg/util/certgenerator/generator_test.go index 8749d4d7..a028f0f3 100644 --- a/pkg/util/certgenerator/generator_test.go +++ b/pkg/util/certgenerator/generator_test.go @@ -23,10 +23,7 @@ import ( "github.com/bytedance/mockey" "github.com/stretchr/testify/require" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" - "k8s.io/client-go/kubernetes/fake" "k8s.io/client-go/rest" "k8s.io/client-go/util/keyutil" ) @@ -244,18 +241,8 @@ func TestGenerator_applyCertToSecret(t *testing.T) { mockey.Mock(EncodeCertPEM).Return([]byte("test-cert")).Build() // Mock keyutil.MarshalPrivateKeyToPEM mockey.Mock(keyutil.MarshalPrivateKeyToPEM).Return([]byte("test-key"), nil).Build() - // Create fake clientset with pre-created secret - secret := &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-cert", - Namespace: "test-ns", - }, - } - fakeClientset := fake.NewSimpleClientset(secret) - mockey.Mock((*kubernetes.Clientset).CoreV1).Return(fakeClientset.CoreV1()).Build() // Mock Secret Apply method - secretsClient := fakeClientset.CoreV1().Secrets(generator.namespace) - mockey.Mock(secretsClient.Apply).Return(&corev1.Secret{}, nil).Build() + mockey.Mock((*kubernetes.Clientset).CoreV1).Return(&FakeCoreV1{}).Build() }, expectError: false, }, @@ -296,18 +283,8 @@ func TestGenerator_applyKubeConfigToConfigMap(t *testing.T) { { name: "Success", mockSetup: func() { - // Create fake clientset with pre-created configmap - configMap := &corev1.ConfigMap{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-kubeconfig", - Namespace: "test-ns", - }, - } - fakeClientset := fake.NewSimpleClientset(configMap) - mockey.Mock((*kubernetes.Clientset).CoreV1).Return(fakeClientset.CoreV1()).Build() // Mock ConfigMap Apply method - configMapsClient := fakeClientset.CoreV1().ConfigMaps(generator.namespace) - mockey.Mock(configMapsClient.Apply).Return(&corev1.ConfigMap{}, nil).Build() + mockey.Mock((*kubernetes.Clientset).CoreV1).Return(&FakeCoreV1{}).Build() }, expectError: false, }, @@ -339,48 +316,48 @@ func TestGenerateConfig(t *testing.T) { expectError bool }{ { - name: "Success", + name: "Error - generateCert Failed", namespace: "test-ns", mockSetup: func() { // Mock generateCA mockey.Mock(generateCA).Return(&x509.Certificate{}, &rsa.PrivateKey{}, nil).Build() - // Mock generateCert - mockey.Mock(generateCert).Return(&x509.Certificate{}, &rsa.PrivateKey{}, nil).Build() - // Mock generateAdminKubeconfig - mockey.Mock(generateAdminKubeconfig).Return("test-kubeconfig", nil).Build() - }, - expectError: false, - }, - { - name: "Error - generateCA Failed", - namespace: "test-ns", - mockSetup: func() { - // Mock generateCA with error - mockey.Mock(generateCA).Return(nil, nil, errors.New("generateCA failed")).Build() + // Mock generateCert with error + mockey.Mock(generateCert).Return(nil, nil, errors.New("generateCert failed")).Build() }, expectError: true, }, { - name: "Error - generateCert Failed", + name: "Error - generateAdminKubeconfig Failed", namespace: "test-ns", mockSetup: func() { // Mock generateCA mockey.Mock(generateCA).Return(&x509.Certificate{}, &rsa.PrivateKey{}, nil).Build() - // Mock generateCert with error - mockey.Mock(generateCert).Return(nil, nil, errors.New("generateCert failed")).Build() + // Mock generateCert + mockey.Mock(generateCert).Return(&x509.Certificate{}, &rsa.PrivateKey{}, nil).Build() + // Mock generateAdminKubeconfig with error + mockey.Mock(generateAdminKubeconfig).Return("", errors.New("generateAdminKubeconfig failed")).Build() }, expectError: true, }, { - name: "Error - generateAdminKubeconfig Failed", + name: "Success", namespace: "test-ns", mockSetup: func() { // Mock generateCA mockey.Mock(generateCA).Return(&x509.Certificate{}, &rsa.PrivateKey{}, nil).Build() // Mock generateCert mockey.Mock(generateCert).Return(&x509.Certificate{}, &rsa.PrivateKey{}, nil).Build() - // Mock generateAdminKubeconfig with error - mockey.Mock(generateAdminKubeconfig).Return("", errors.New("generateAdminKubeconfig failed")).Build() + // Mock generateAdminKubeconfig + mockey.Mock(generateAdminKubeconfig).Return("test-kubeconfig", nil).Build() + }, + expectError: false, + }, + { + name: "Error - generateCA Failed", + namespace: "test-ns", + mockSetup: func() { + // Mock generateCA with error + mockey.Mock(generateCA).Return(nil, nil, errors.New("generateCA failed")).Build() }, expectError: true, }, @@ -388,11 +365,9 @@ func TestGenerateConfig(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - mockey.UnPatchAll() - defer mockey.UnPatchAll() - // Setup mocks tc.mockSetup() + defer mockey.UnPatchAll() caCert, caKey, kubeConfig, err := generateConfig(tc.namespace) if tc.expectError { @@ -436,11 +411,9 @@ func TestGenerateCA(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - mockey.UnPatchAll() - defer mockey.UnPatchAll() - // Setup mocks tc.mockSetup() + defer mockey.UnPatchAll() cert, key, err := generateCA() if tc.expectError { diff --git a/pkg/util/certgenerator/test_helper.go b/pkg/util/certgenerator/test_helper.go new file mode 100644 index 00000000..c5403817 --- /dev/null +++ b/pkg/util/certgenerator/test_helper.go @@ -0,0 +1,52 @@ +// Copyright The Karpor Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package certgenerator + +import ( + "context" + + coreV1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + applycorev1 "k8s.io/client-go/applyconfigurations/core/v1" + v1 "k8s.io/client-go/kubernetes/typed/core/v1" +) + +type FakeCoreV1 struct { + v1.CoreV1Interface +} + +func (FakeCoreV1) Secrets(namespace string) v1.SecretInterface { + return &FakeSecret{} +} + +func (FakeCoreV1) ConfigMaps(namespace string) v1.ConfigMapInterface { + return &FakeConfigMap{} +} + +type FakeSecret struct { + v1.SecretInterface +} + +func (f *FakeSecret) Apply(ctx context.Context, secret *applycorev1.SecretApplyConfiguration, opts metav1.ApplyOptions) (result *coreV1.Secret, err error) { + return &coreV1.Secret{}, nil +} + +type FakeConfigMap struct { + v1.ConfigMapInterface +} + +func (f *FakeConfigMap) Apply(ctx context.Context, secret *applycorev1.ConfigMapApplyConfiguration, opts metav1.ApplyOptions) (result *coreV1.ConfigMap, err error) { + return &coreV1.ConfigMap{}, nil +} diff --git a/pkg/util/certgenerator/util.go b/pkg/util/certgenerator/util.go index da31e252..99044ae9 100644 --- a/pkg/util/certgenerator/util.go +++ b/pkg/util/certgenerator/util.go @@ -20,6 +20,7 @@ import ( "crypto/elliptic" "crypto/rand" "crypto/rsa" + "crypto/tls" "crypto/x509" "crypto/x509/pkix" "encoding/pem" @@ -173,3 +174,21 @@ func EncodeCertPEM(cert *x509.Certificate) []byte { } return pem.EncodeToMemory(&block) } + +// LoadCertificate loads a certificate and its corresponding private key from files. +func LoadCertificate(certFile, keyFile string) (*x509.Certificate, crypto.Signer, error) { + // Load the certificate and key pair using tls.LoadX509KeyPair. + cert, err := tls.LoadX509KeyPair(certFile, keyFile) + if err != nil { + return nil, nil, fmt.Errorf("failed to load key pair: %w", err) + } + + // Parse the certificate. + certData, err := x509.ParseCertificate(cert.Certificate[0]) + if err != nil { + return nil, nil, fmt.Errorf("failed to parse certificate: %w", err) + } + + // Return the parsed certificate and the private key as a crypto.Signer. + return certData, cert.PrivateKey.(crypto.Signer), nil +} diff --git a/pkg/util/clusterinstall/cluster_install.go b/pkg/util/clusterinstall/cluster_install.go index 16971e75..7b144337 100644 --- a/pkg/util/clusterinstall/cluster_install.go +++ b/pkg/util/clusterinstall/cluster_install.go @@ -17,12 +17,13 @@ package clusterinstall import ( "fmt" - clusterv1beta1 "github.com/KusionStack/karpor/pkg/kubernetes/apis/cluster/v1beta1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/rest" + + clusterv1beta1 "github.com/KusionStack/karpor/pkg/kubernetes/apis/cluster/v1beta1" ) -func ConvertKubeconfigToCluster(name, displayName, description string, cfg *rest.Config) (*clusterv1beta1.Cluster, error) { +func ConvertKubeconfigToCluster(name, displayName, description, clusterMode string, clusterLevel int, cfg *rest.Config) (*clusterv1beta1.Cluster, error) { cluster := clusterv1beta1.Cluster{ TypeMeta: metav1.TypeMeta{ APIVersion: clusterv1beta1.SchemeGroupVersion.String(), @@ -36,6 +37,12 @@ func ConvertKubeconfigToCluster(name, displayName, description string, cfg *rest } else { cluster.Spec.DisplayName = name } + if clusterMode != "" { + cluster.Spec.Mode = clusterMode + } + if clusterLevel > 0 && clusterLevel <= 3 { + cluster.Spec.Level = clusterLevel + } access := clusterv1beta1.ClusterAccess{} if !cfg.Insecure { access.CABundle = cfg.CAData diff --git a/pkg/util/clusterinstall/cluster_install_test.go b/pkg/util/clusterinstall/cluster_install_test.go index 21f0dd8a..85894a01 100644 --- a/pkg/util/clusterinstall/cluster_install_test.go +++ b/pkg/util/clusterinstall/cluster_install_test.go @@ -19,19 +19,22 @@ import ( clientcmdapi "k8s.io/client-go/tools/clientcmd/api" - clusterv1beta1 "github.com/KusionStack/karpor/pkg/kubernetes/apis/cluster/v1beta1" "github.com/stretchr/testify/require" "k8s.io/client-go/rest" + + clusterv1beta1 "github.com/KusionStack/karpor/pkg/kubernetes/apis/cluster/v1beta1" ) func TestConvertKubeconfigToCluster(t *testing.T) { tests := []struct { - name string - displayName string - description string - cfg *rest.Config - wantCluster *clusterv1beta1.Cluster - wantErr bool + name string + displayName string + description string + clusterMode string + clusterLevel int + cfg *rest.Config + wantCluster *clusterv1beta1.Cluster + wantErr bool }{ // Test case with secure setup (non-insecure, with certificate data) { @@ -122,7 +125,7 @@ func TestConvertKubeconfigToCluster(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { // Call the function under test - cluster, err := ConvertKubeconfigToCluster(tc.name, tc.displayName, tc.description, tc.cfg) + cluster, err := ConvertKubeconfigToCluster(tc.name, tc.displayName, tc.description, tc.clusterMode, tc.clusterLevel, tc.cfg) // Assert that an error occurred when expected if tc.wantErr { require.Error(t, err) diff --git a/pkg/util/ctxutil/ctxutil.go b/pkg/util/ctxutil/ctxutil.go index 53117e32..bd51ba34 100644 --- a/pkg/util/ctxutil/ctxutil.go +++ b/pkg/util/ctxutil/ctxutil.go @@ -17,9 +17,10 @@ package ctxutil import ( "context" - "github.com/KusionStack/karpor/pkg/core/middleware" "github.com/go-logr/logr" - "k8s.io/klog/v2" + "k8s.io/klog/v2/klogr" + + "github.com/KusionStack/karpor/pkg/core/middleware" ) // GetLogger returns the logger from the given context. @@ -32,5 +33,5 @@ func GetLogger(ctx context.Context) logr.Logger { return logger } - return klog.NewKlogr() + return klogr.New() } diff --git a/pkg/util/ctxutil/ctxutil_test.go b/pkg/util/ctxutil/ctxutil_test.go index a7264550..e2b3c270 100644 --- a/pkg/util/ctxutil/ctxutil_test.go +++ b/pkg/util/ctxutil/ctxutil_test.go @@ -18,14 +18,15 @@ import ( "context" "testing" - "github.com/KusionStack/karpor/pkg/core/middleware" "github.com/go-logr/logr" "github.com/stretchr/testify/require" - "k8s.io/klog/v2" + "k8s.io/klog/v2/klogr" + + "github.com/KusionStack/karpor/pkg/core/middleware" ) func TestGetLogger(t *testing.T) { - mockLogger := klog.NewKlogr().WithName("mock") + mockLogger := klogr.New().WithName("mock") tests := []struct { name string ctx context.Context @@ -41,7 +42,7 @@ func TestGetLogger(t *testing.T) { name: "Logger not in context", ctx: context.Background(), // Expect the logger type to be klogr.Logger as default. - expectedLogger: klog.NewKlogr(), + expectedLogger: klogr.New(), }, } diff --git a/pkg/util/safeutil/safeutil.go b/pkg/util/safeutil/safeutil.go index c80843b4..8f5a88f4 100644 --- a/pkg/util/safeutil/safeutil.go +++ b/pkg/util/safeutil/safeutil.go @@ -19,10 +19,11 @@ import ( "fmt" "runtime/debug" - "github.com/KusionStack/karpor/pkg/util/ctxutil" "github.com/elliotxx/safe" "github.com/go-logr/logr" - "k8s.io/klog/v2" + "k8s.io/klog/v2/klogr" + + "github.com/KusionStack/karpor/pkg/util/ctxutil" ) // LoggerRecoverHandler returns a recover handler by the given logger. @@ -65,7 +66,7 @@ func LoggerRecoverHandler(logger logr.Logger) safe.RecoverHandler { // // safeutil.Go(func(){...}) func Go(do safe.DoFunc) { - safe.GoR(do, LoggerRecoverHandler(klog.NewKlogr())) + safe.GoR(do, LoggerRecoverHandler(klogr.New())) } // GoL starts a recoverable goroutine with a given logger. diff --git a/pkg/util/safeutil/safeutil_test.go b/pkg/util/safeutil/safeutil_test.go index e7e9e353..02afa7a3 100644 --- a/pkg/util/safeutil/safeutil_test.go +++ b/pkg/util/safeutil/safeutil_test.go @@ -20,7 +20,7 @@ import ( "github.com/elliotxx/safe" "github.com/go-logr/logr" - "k8s.io/klog/v2" + "k8s.io/klog/v2/klogr" ) func TestGo(t *testing.T) { @@ -56,7 +56,7 @@ func TestGoL(t *testing.T) { } getTestingLogger := func() logr.Logger { - logger := klog.NewKlogr() + logger := klogr.New() return logger } diff --git a/pkg/util/sql2es/convert.go b/pkg/util/sql2es/convert.go index 776a3549..2baf9433 100644 --- a/pkg/util/sql2es/convert.go +++ b/pkg/util/sql2es/convert.go @@ -34,8 +34,8 @@ func applyDefaultFilter(sel *sqlparser.Select, filter sqlparser.Expr) *sqlparser return sel } - getColNames := func(node sqlparser.SQLNode) sets.Set[string] { - names := sets.Set[string]{} + getColNames := func(node sqlparser.SQLNode) sets.String { + names := sets.String{} node.WalkSubtree(func(node sqlparser.SQLNode) (kontinue bool, err error) { switch node.(type) { case sqlparser.ColIdent, *sqlparser.ColIdent: diff --git a/pkg/version/VERSION b/pkg/version/VERSION index b417ed46..b82608c0 100644 --- a/pkg/version/VERSION +++ b/pkg/version/VERSION @@ -1 +1 @@ -default-version +v0.1.0 diff --git a/sdk/agent.go b/sdk/agent.go new file mode 100644 index 00000000..2a9bfc22 --- /dev/null +++ b/sdk/agent.go @@ -0,0 +1,105 @@ +// Copyright The Karpor Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package sdk + +import ( + "context" + + esclient "github.com/elastic/go-elasticsearch/v8" + "github.com/pkg/errors" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/client-go/dynamic" + "k8s.io/klog/v2/klogr" + coreinstall "k8s.io/kubernetes/pkg/apis/core/install" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/manager" + + "github.com/KusionStack/karpor/pkg/infra/search/storage/elasticsearch" + clusterinstall "github.com/KusionStack/karpor/pkg/kubernetes/apis/cluster/install" + searchinstall "github.com/KusionStack/karpor/pkg/kubernetes/apis/search/install" + "github.com/KusionStack/karpor/pkg/syncer" + "github.com/KusionStack/karpor/pkg/syncer/utils" +) + +type AgentOptions struct { + ElasticSearchAddresses []string + ClusterName string + + SyncGVKs []schema.GroupVersionKind +} + +/* +StartAgentWithOperator defines sdk entrance. +Developers can use this function to share memory with exited operator for special resources in SyncGVK. +Only support push mode. +*/ +func StartAgentWithOperator(ctx context.Context, existMgr manager.Manager, options *AgentOptions) error { + ctrl.SetLogger(klogr.New()) + log := ctrl.Log.WithName("setup") + + defer func() { + if err := recover(); err != nil { + log.Error(errors.New("recovered panic in karpor agent"), "") + } + }() + + // apply crds + dynamicClient, err := dynamic.NewForConfig(ctrl.GetConfigOrDie()) + if err != nil { + return errors.Wrapf(err, "failed to build dynamic client for ageng") + } + err = utils.ApplyCrds(ctx, dynamicClient) + if err != nil { + return err + } + + // TODO: add startup parameters to change the type of storage + //nolint:contextcheck + es, err := elasticsearch.NewStorage(esclient.Config{ + Addresses: options.ElasticSearchAddresses, + }) + if err != nil { + log.Error(err, "unable to init elasticsearch client") + return err + } + + // start operator for special resource + if len(options.SyncGVKs) != 0 { + for idx := range options.SyncGVKs { + gvk := options.SyncGVKs[idx] + utils.SetSyncGVK(gvk, utils.ZeroVal) + err := syncer.NewDynamicReconciler(ctx, options.ClusterName, gvk, es).SetupWithManager(existMgr) + if err != nil { + return err + } + } + } + + //nolint:contextcheck + if err = syncer.NewAgentReconciler(es, options.ClusterName).SetupWithManager(existMgr); err != nil { + log.Error(err, "unable to create resource syncer") + return err + } + + return nil +} + +// AddToScheme install necessary api to existed scheme +func AddToScheme(scheme *runtime.Scheme) { + clusterinstall.Install(scheme) + searchinstall.Install(scheme) + coreinstall.Install(scheme) +} diff --git a/sdk/example/example.go b/sdk/example/example.go new file mode 100644 index 00000000..595ad86e --- /dev/null +++ b/sdk/example/example.go @@ -0,0 +1,92 @@ +// Copyright The Karpor Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "context" + "fmt" + "os" + + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/KusionStack/karpor/sdk" +) + +type PodReconciler struct { + client.Client + Scheme *runtime.Scheme +} + +func (r *PodReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + pod := &corev1.Pod{} + if err := r.Get(ctx, req.NamespacedName, pod); err != nil { + return ctrl.Result{}, client.IgnoreNotFound(err) + } + + log := ctrl.Log.WithName("setup") + + log.Info("Reconciling Pod: %s/%s", pod.Namespace, pod.Name) + + return ctrl.Result{}, nil +} + +func (r *PodReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&corev1.Pod{}). + Complete(r) +} + +// example for sdk, only support push mode. +func main() { + scheme := runtime.NewScheme() + sdk.AddToScheme(scheme) + + mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ + Scheme: scheme, + }) + if err != nil { + fmt.Fprintf(os.Stderr, "unable to start manager: %v\n", err) + os.Exit(1) + } + + if err = (&PodReconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + }).SetupWithManager(mgr); err != nil { + fmt.Fprintf(os.Stderr, "unable to create controller: %v\n", err) + os.Exit(1) + } + + err = sdk.StartAgentWithOperator(context.Background(), mgr, &sdk.AgentOptions{ + ClusterName: "example-cluster", + ElasticSearchAddresses: []string{"http://127.0.0.1:9200"}, + SyncGVKs: []schema.GroupVersionKind{ + {Group: "", Version: "v1", Kind: "Pod"}, + }, + }) + if err != nil { + fmt.Fprintf(os.Stderr, "unable to add dynamic controller: %v\n", err) + os.Exit(1) + } + + if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil { + fmt.Fprintf(os.Stderr, "problem running manager: %v\n", err) + os.Exit(1) + } +} diff --git a/ui/package.json b/ui/package.json index ded7c44c..43b68d50 100644 --- a/ui/package.json +++ b/ui/package.json @@ -62,7 +62,9 @@ "release": "standard-version", "dev": "GENERATE_SOURCEMAP=false PORT=8000 craco start", "start": "GENERATE_SOURCEMAP=false PORT=8080 HTTPS=true craco start", + "start:inner": "GENERATE_SOURCEMAP=false PORT=8080 HTTPS=true REACT_APP_DEPLOY_MODE=HIGH_AVAILABILITY craco start", "build": "GENERATE_SOURCEMAP=false PUBLIC_URL=/public/ craco build", + "build:inner": "GENERATE_SOURCEMAP=false PUBLIC_URL=/public/ REACT_APP_DEPLOY_MODE=HIGH_AVAILABILITY craco build", "lint:prettier": "prettier --write src\"/**/*.+(js|ts|tsx|jsx|json|md|json)\"", "lint:style": "stylelint src\"/**/*.+(css|scss|less|stylus|sass|postcss)\" --fix", "lint:fix": "eslint --fix --ext .js,.jsx,.ts,.tsx src", diff --git a/ui/src/components/agentYaml/index.tsx b/ui/src/components/agentYaml/index.tsx new file mode 100644 index 00000000..3cb9dc40 --- /dev/null +++ b/ui/src/components/agentYaml/index.tsx @@ -0,0 +1,428 @@ +import React, { useEffect, useRef, useState, useCallback } from 'react' +import type { LegacyRef } from 'react' +import { Alert, Button, message, Space, Spin, Tooltip } from 'antd' +import { Resizable } from 're-resizable' +import { useTranslation } from 'react-i18next' +import { + CopyOutlined, + CloseOutlined, + PoweroffOutlined, + FullscreenExitOutlined, + FullscreenOutlined, +} from '@ant-design/icons' +import hljs from 'highlight.js' +import 'highlight.js/styles/lightfair.css' +import { useSelector } from 'react-redux' +import Markdown from 'react-markdown' +import axios from 'axios' +import { FullScreen, useFullScreenHandle } from 'react-full-screen' +import i18n from '@/i18n' +import aiSummarySvg from '@/assets/ai-summary.svg' + +import styles from './styles.module.less' + +// eslint-disable-next-line @typescript-eslint/no-var-requires +hljs.registerLanguage('yaml', require('highlight.js/lib/languages/yaml')) + +type InterpretStatus = + | 'idle' + | 'init' + | 'streaming' + | 'complete' + | 'error' + | 'loading' + +type IProps = { + data: any + height?: string | number +} + +const AgentYaml = (props: IProps) => { + const { t } = useTranslation() + const handle = useFullScreenHandle() + const yamlRef = useRef | any>() + const diagnosisContentRef = useRef(null) + const interpretEndRef = useRef(null) + const contentRef = useRef(null) + const observerRef = useRef(null) + const { data } = props + const [moduleHeight, setModuleHeight] = useState(500) + const [interpretStatus, setInterpretStatus] = + useState('idle') + const [interpret, setInterpret] = useState('') + const [isStreaming, setStreaming] = useState(false) + const abortControllerRef = useRef(null) + const { aiOptions } = useSelector((state: any) => state.globalSlice) + const isAIEnabled = aiOptions?.AIModel && aiOptions?.AIAuthToken + + useEffect(() => { + if (yamlRef.current && data) { + yamlRef.current.textContent = data + hljs.highlightElement(yamlRef.current) + } + }, [data]) + + // Function to scroll to the bottom of the container + const scrollToBottom = useCallback(() => { + if (diagnosisContentRef.current && interpretStatus === 'streaming') { + const container = diagnosisContentRef.current + const scrollHeight = container.scrollHeight + const height = container.clientHeight + const maxScroll = scrollHeight - height + container.scrollTo({ + top: maxScroll, + behavior: 'auto', + }) + } + }, [interpretStatus]) + + // Watch for content changes + useEffect(() => { + if (interpretStatus === 'streaming' && diagnosisContentRef.current) { + if (observerRef.current) { + observerRef.current.disconnect() + } + + const observer = new MutationObserver(() => { + scrollToBottom() + }) + + observer.observe(diagnosisContentRef.current, { + childList: true, + subtree: true, + characterData: true, + }) + + observerRef.current = observer + + return () => { + observer.disconnect() + } + } + }, [interpretStatus, scrollToBottom]) + + // Scroll when content updates + useEffect(() => { + if (interpretStatus === 'streaming') { + scrollToBottom() + } + }, [interpret, scrollToBottom, interpretStatus]) + + function copy() { + const textarea = document.createElement('textarea') + textarea.value = data + document.body.appendChild(textarea) + textarea.select() + document.execCommand('copy') + message.success(t('CopySuccess')) + document.body.removeChild(textarea) + } + + const handleInterpret = async () => { + try { + if (!data) { + message.warning(t('YAML.NoContent')) + return + } + + // Reset interpret state + setInterpret('') + setInterpretStatus('loading' as InterpretStatus) + + // Cancel any existing SSE connection + if (abortControllerRef.current) { + abortControllerRef.current.abort() + } + + // Create new AbortController for this request + const abortController = new AbortController() + abortControllerRef.current = abortController + + setStreaming(true) + + // Create new fetch request for interpret + const url = `${axios.defaults.baseURL}/rest-api/v1/insight/yaml/interpret/stream` + + // Send POST request and handle SSE response + fetch(url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Accept: 'text/event-stream', + }, + body: JSON.stringify({ + yaml: data, + language: i18n.language, + }), + signal: abortController.signal, + }) + .then(response => { + if (!response.ok) { + throw new Error(response.statusText) + } + + // Create a reader from the response body stream + const reader = response.body?.getReader() + const decoder = new TextDecoder() + + if (!reader) { + throw new Error('No response body') + } + + // Read the stream + const processStream = async () => { + try { + let streaming = true + while (streaming) { + const { done, value } = await reader.read() + + if (done) { + streaming = false + setInterpretStatus('complete' as InterpretStatus) + break + } + + // Decode the chunk and process events + const chunk = decoder.decode(value) + const events = chunk + .split('\n\n') + .filter(Boolean) + .map(event => event.replace('data: ', '')) + + for (const event of events) { + try { + const interpretEvent = JSON.parse(event) + + switch (interpretEvent.type) { + case 'start': + setInterpretStatus('streaming' as InterpretStatus) + break + case 'chunk': + setInterpret(prev => prev + interpretEvent.content) + if (interpretEndRef.current) { + interpretEndRef.current.scrollIntoView({ + behavior: 'smooth', + }) + } + break + case 'error': + streaming = false + setInterpretStatus('error' as InterpretStatus) + message.error(interpretEvent.content) + reader.cancel() + break + case 'complete': + streaming = false + setInterpretStatus('complete' as InterpretStatus) + reader.cancel() + break + } + } catch (error) { + console.error('Failed to parse interpret event:', error) + } + } + } + } catch (error) { + if (error.name === 'AbortError') { + console.log('Interpret stream aborted') + } else { + console.error('Error reading stream:', error) + setInterpretStatus('error' as InterpretStatus) + message.error(t('YAML.InterpretConnectionError')) + } + } + } + + processStream() + }) + .catch(error => { + if (error.name !== 'AbortError') { + console.error('Failed to start interpret:', error) + setInterpretStatus('error' as InterpretStatus) + message.error(t('YAML.FailedToStartInterpret')) + } + }) + } catch (error) { + console.error('Failed to start interpret:', error) + setInterpretStatus('error' as InterpretStatus) + message.error(t('YAML.FailedToInterpret')) + } finally { + setStreaming(false) + } + } + const contentToTopHeight = contentRef.current?.getBoundingClientRect()?.top + const dotToTopHeight = interpretEndRef.current?.getBoundingClientRect()?.top + + return ( +
+ { + const newModuleHeight = moduleHeight + d.height + setModuleHeight(newModuleHeight) + }} + handleStyles={{ + bottom: { + bottom: 0, + height: '6px', + cursor: 'row-resize', + background: 'transparent', + transition: 'background 0.3s ease', + }, + }} + handleClasses={{ + bottom: styles.resizeHandle, + }} + > +
+ +
+
+ + {data && ( + <> + {!handle.active && ( + + + )} + {handle.active && ( + + + + )} + +
+
+
+ + {interpretStatus !== 'idle' && ( +
+
+ +
+ ai summary +
+ {t('YAML.InterpretResult')} +
+ + {interpretStatus === 'streaming' && ( + +
+
+
+ {interpretStatus === 'loading' || + (interpretStatus === 'streaming' && !interpret) ? ( +
+ +

{t('EventAggregator.DiagnosisInProgress')}

+
+ ) : interpretStatus === 'streaming' ? ( + <> + {interpret} +
+ + ) : interpretStatus === 'error' ? ( + + ) : ( + {interpret} + )} +
+ {interpretStatus === 'streaming' && interpret && ( +
= 0 ? styles.yaml_content_streamingIndicatorFixed : ''}`} + > + + + +
+ )} +
+
+ )} +
+ +
+ ) +} + +export default AgentYaml diff --git a/ui/src/components/agentYaml/styles.module.less b/ui/src/components/agentYaml/styles.module.less new file mode 100644 index 00000000..3a7ad7b6 --- /dev/null +++ b/ui/src/components/agentYaml/styles.module.less @@ -0,0 +1,739 @@ +.yaml_content { + height: 100%; + overflow-y: auto; + border-radius: 8px; + position: relative; + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + display: flex; + gap: 16px; + background: #fff; + + .fullScreenConatiner { + width: 100%; + + .yaml_container { + height: 100%; + position: relative; + flex: 1; + + &:hover { + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2); + transform: translateY(-1px); + border-radius: 8px; + + .copy { + opacity: 1; + transform: scale(1.02); + } + } + + .copy { + position: absolute; + top: 16px; + right: 24px; + z-index: 10; + opacity: 0.95; + transform: translateY(0); + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + + &::before { + content: ''; + position: absolute; + top: -2px; + left: -2px; + right: -2px; + bottom: -2px; + background: linear-gradient(45deg, #7cdce7, #7ec699); + border-radius: 6px; + z-index: -1; + opacity: 0; + transition: opacity 0.3s cubic-bezier(0.4, 0, 0.2, 1); + } + + :global { + .ant-btn { + background: rgba(30, 30, 30, 0.95); + color: #7cdce7; + border: 1px solid rgba(124, 220, 231, 0.3); + height: 28px; + font-weight: 500; + font-family: 'JetBrains Mono', monospace; + letter-spacing: 0.5px; + backdrop-filter: blur(4px); + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + box-shadow: 0 0 2px rgba(20, 20, 20, 0.3); + border-radius: 4px; + display: flex; + align-items: center; + gap: 6px; + + .anticon { + font-size: 14px; + transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1); + } + + &:hover { + color: #fff; + border-color: rgba(124, 220, 231, 0.4); + transform: translateY(-1px); + box-shadow: 0 0 3px rgba(20, 20, 20, 0.4); + background: rgba(35, 35, 35, 0.95); + + .anticon { + transform: scale(1.1); + } + } + + &:active { + transform: translateY(0); + box-shadow: 0 0 1px rgba(20, 20, 20, 0.3); + background: rgba(28, 28, 28, 0.95); + } + } + } + + &:hover { + &::before { + opacity: 0.1; + } + } + } + + .magicWand { + font-size: 16px; + line-height: 1; + display: inline-block; + transform: translateY(1px); + filter: drop-shadow(0 0 4px rgba(147, 112, 219, 0.4)); + animation: sparkle 1.5s ease-in-out infinite; + } + + .yaml_box { + width: 100%; + height: 100%; + padding: 16px 20px; + overflow-y: auto; + font-family: 'JetBrains Mono', 'Fira Code', Menlo, Monaco, Consolas, + 'Courier New', monospace; + font-size: 13px; + line-height: 1.6; + color: #e0e0e0; + word-break: break-all; + white-space: pre-wrap; + background-color: #1e1e1e; + border-radius: 8px; + box-sizing: border-box; + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + + &::-webkit-scrollbar { + width: 8px; + height: 8px; + } + + &::-webkit-scrollbar-track { + background: rgba(147, 112, 219, 0.05); + border-radius: 4px; + } + + &::-webkit-scrollbar-thumb { + background: rgba(147, 112, 219, 0.2); + border-radius: 4px; + border: 2px solid transparent; + background-clip: padding-box; + + &:hover { + background: rgba(147, 112, 219, 0.3); + border: 2px solid transparent; + background-clip: padding-box; + } + } + + :global { + .hljs { + background: transparent; + padding: 0; + transition: color 0.3s cubic-bezier(0.4, 0, 0.2, 1); + + &-attr { + color: #7cdce7; + transition: color 0.3s cubic-bezier(0.4, 0, 0.2, 1); + + &:hover { + color: lighten(#7cdce7, 10%); + } + } + + &-string { + color: #7ec699; + transition: color 0.3s cubic-bezier(0.4, 0, 0.2, 1); + + &:hover { + color: lighten(#7ec699, 10%); + } + } + + &-number { + color: #f08d49; + transition: color 0.3s cubic-bezier(0.4, 0, 0.2, 1); + + &:hover { + color: lighten(#f08d49, 10%); + } + } + + &-boolean { + color: #cc99cd; + transition: color 0.3s cubic-bezier(0.4, 0, 0.2, 1); + + &:hover { + color: lighten(#cc99cd, 10%); + } + } + + &-null { + color: #cc99cd; + transition: color 0.3s cubic-bezier(0.4, 0, 0.2, 1); + + &:hover { + color: lighten(#cc99cd, 10%); + } + } + } + } + } + } + } + + .yaml_content_diagnosisPanel { + box-sizing: border-box; + width: 400px; + background: #2b1d3c; + border-radius: 12px; + display: flex; + flex-direction: column; + backdrop-filter: blur(8px); + z-index: 10; + animation: slideIn 0.3s cubic-bezier(0.4, 0, 0.2, 1); + + .yaml_content_diagnosisHeader { + display: flex; + justify-content: space-between; + align-items: center; + padding: 12px; + background: linear-gradient( + 135deg, + rgba(147, 112, 219, 0.15) 0%, + rgba(43, 29, 60, 0.95) 100% + ); + border-bottom: 1px solid rgba(147, 112, 219, 0.2); + border-radius: 12px 12px 0 0; + color: #e6e6fa; + font-size: 14px; + font-weight: 500; + backdrop-filter: blur(4px); + + .yaml_content_diagnosisHeader_aiIcon { + width: 18px; + height: 18px; + + img { + width: 100%; + height: 100%; + } + } + + .stopButton { + color: rgba(255, 255, 255, 0.45); + transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1); + width: 28px; + height: 28px; + display: flex; + align-items: center; + justify-content: center; + border-radius: 6px; + margin-right: 8px; + position: relative; + overflow: hidden; + + &::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(255, 77, 79, 0); + transition: background 0.3s ease; + z-index: 0; + } + + :global(.anticon) { + font-size: 16px; + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + position: relative; + z-index: 1; + } + + &:hover { + color: #ff4d4f; + transform: scale(1.05); + + &::before { + background: rgba(255, 77, 79, 0.15); + } + + :global(.anticon) { + transform: rotate(180deg) scale(1.1); + } + } + + &:active { + transform: scale(0.95); + + &::before { + background: rgba(255, 77, 79, 0.25); + } + } + } + + .anticon { + margin-right: 8px; + font-size: 16px; + color: #9370db; + text-shadow: 0 0 8px rgba(147, 112, 219, 0.4); + } + + button { + color: rgba(230, 230, 250, 0.85); + transition: all 0.3s ease; + border-radius: 6px; + width: 28px; + height: 28px; + display: flex; + align-items: center; + justify-content: center; + + &:hover { + color: #fff; + background: rgba(147, 112, 219, 0.2); + transform: scale(1.05); + } + + &:active { + transform: scale(0.95); + } + } + } + + .yaml_content_diagnosisBody { + position: relative; + width: 400px; + overflow-y: auto; + height: 100%; + box-sizing: border-box; + border-radius: 0 0 12px 12px; + + &::-webkit-scrollbar { + width: 8px; + height: 8px; + } + + &::-webkit-scrollbar-track { + background: rgba(147, 112, 219, 0.05); + border-radius: 4px; + } + + &::-webkit-scrollbar-thumb { + background: rgba(147, 112, 219, 0.2); + border-radius: 4px; + border: 2px solid transparent; + background-clip: padding-box; + + &:hover { + background: rgba(147, 112, 219, 0.3); + border: 2px solid transparent; + background-clip: padding-box; + } + } + + .yaml_content_diagnosisContent { + color: #d4d4d4; + background: #2b1d3c; + border-radius: 0 0 12px 12px; + padding: 16px; + font-size: 14px; + line-height: 1.6; + box-sizing: border-box; + word-wrap: break-word; + + .yaml_content_diagnosisLoading { + height: 100%; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 16px; + color: rgba(230, 230, 250, 0.85); + + :global(.ant-spin) { + .ant-spin-dot-item { + background-color: #9370db; + } + } + + p { + font-size: 14px; + color: rgba(230, 230, 250, 0.85); + } + } + + h1, + h2, + h3 { + color: #e6e6fa; + margin-bottom: 16px; + font-weight: 600; + letter-spacing: -0.01em; + } + + h1 { + font-size: 20px; + } + + h2 { + font-size: 18px; + margin-top: 24px; + padding-bottom: 8px; + border-bottom: 1px solid rgba(147, 112, 219, 0.2); + } + + h3 { + font-size: 16px; + margin-top: 20px; + } + + p { + margin-bottom: 16px; + line-height: 1.7; + } + + ul, + ol { + padding-left: 24px; + margin-bottom: 16px; + } + + li { + margin-bottom: 8px; + } + + code { + background: rgba(147, 112, 219, 0.1); + padding: 2px 6px; + border-radius: 4px; + font-family: 'Menlo', 'Monaco', 'Courier New', monospace; + font-size: 13px; + } + + pre { + background: rgba(20, 15, 30, 0.8); + padding: 16px; + border-radius: 8px; + margin-bottom: 16px; + overflow-x: auto; + border: 1px solid rgba(147, 112, 219, 0.1); + + &::-webkit-scrollbar { + width: 8px; + height: 8px; + } + + &::-webkit-scrollbar-track { + background: rgba(147, 112, 219, 0.05); + border-radius: 4px; + } + + &::-webkit-scrollbar-thumb { + background: rgba(147, 112, 219, 0.2); + border-radius: 4px; + border: 2px solid transparent; + background-clip: padding-box; + + &:hover { + background: rgba(147, 112, 219, 0.3); + border: 2px solid transparent; + background-clip: padding-box; + } + } + + code { + background: none; + padding: 0; + } + } + + :global { + .markdown-body { + color: #d4d4d4; + background: transparent; + padding: 16px; + + h1, + h2, + h3, + h4, + h5, + h6 { + color: rgba(230, 230, 250, 0.95); + border-bottom: 1px solid rgba(147, 112, 219, 0.2); + margin-top: 24px; + margin-bottom: 16px; + font-weight: 600; + line-height: 1.25; + } + + h1 { + font-size: 24px; + } + + h2 { + font-size: 20px; + } + + h3 { + font-size: 18px; + } + + h4 { + font-size: 16px; + } + + h5 { + font-size: 14px; + } + + h6 { + font-size: 13px; + } + + pre { + background: rgba(0, 0, 0, 0.4); + border: 1px solid rgba(147, 112, 219, 0.5); + border-radius: 6px; + padding: 12px; + margin: 12px 0; + overflow-x: auto; + + code { + background: transparent; + padding: 0; + color: #f0e6ff; + } + } + + code { + background: rgba(147, 112, 219, 0.2); + padding: 2px 6px; + border-radius: 4px; + color: #f0e6ff; + font-family: 'Menlo', 'Monaco', 'Courier New', monospace; + } + + ul, + ol { + padding-left: 24px; + margin: 8px 0; + + li { + margin: 4px 0; + } + } + + blockquote { + color: rgba(230, 230, 250, 0.85); + border-left: 4px solid rgba(147, 112, 219, 0.4); + background: rgba(147, 112, 219, 0.1); + margin: 16px 0; + padding: 12px 16px; + border-radius: 0 8px 8px 0; + } + + a { + color: #9370db; + text-decoration: none; + transition: all 0.2s ease; + border-bottom: 1px solid transparent; + + &:hover { + color: lighten(#9370db, 10%); + border-bottom-color: currentColor; + } + } + + table { + border-collapse: separate; + border-spacing: 0; + width: 100%; + margin: 16px 0; + border-radius: 8px; + border: 1px solid rgba(147, 112, 219, 0.2); + overflow: hidden; + + th, + td { + border: 1px solid rgba(147, 112, 219, 0.2); + padding: 12px; + } + + th { + background: rgba(147, 112, 219, 0.1); + font-weight: 600; + text-align: left; + } + + tr { + background-color: transparent; + transition: background-color 0.2s ease; + + &:nth-child(2n) { + background-color: rgba(147, 112, 219, 0.05); + } + + &:hover { + background-color: rgba(147, 112, 219, 0.1); + } + } + } + + hr { + border: none; + height: 1px; + background: linear-gradient( + to right, + rgba(147, 112, 219, 0.1), + rgba(147, 112, 219, 0.4), + rgba(147, 112, 219, 0.1) + ); + margin: 24px 0; + } + } + } + } + + .yaml_content_streamingIndicator { + display: flex; + align-items: center; + gap: 4px; + padding: 16px; + + .dot { + width: 6px; + height: 6px; + background-color: #4447c3; + border-radius: 50%; + opacity: 0.3; + animation: dotPulse 1.4s infinite; + + &:nth-child(2) { + animation-delay: 0.2s; + } + + &:nth-child(3) { + animation-delay: 0.4s; + } + } + } + + .yaml_content_streamingIndicatorFixed { + position: sticky; + bottom: 0; + left: 0; + right: 0; + } + } + } +} + +.resizeHandle { + position: absolute; + bottom: 0; + left: 0; + right: 0; + height: 6px; + cursor: row-resize; + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + + &::after { + content: ''; + position: absolute; + left: 50%; + top: 50%; + transform: translate(-50%, -50%) scaleX(0.6); + width: 60px; + height: 2px; + background: rgba(47, 84, 235, 0.1); + border-radius: 1px; + opacity: 0; + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + } + + &:hover { + background: rgba(47, 84, 235, 0.08); + + &::after { + opacity: 1; + transform: translate(-50%, -50%) scaleX(1); + background: rgba(47, 84, 235, 0.3); + } + } + + &:active { + background: rgba(47, 84, 235, 0.15); + + &::after { + opacity: 1; + background: rgba(47, 84, 235, 0.4); + transform: translate(-50%, -50%) scaleX(1.2); + box-shadow: 0 0 10px rgba(47, 84, 235, 0.3); + } + } +} + +@keyframes slideIn { + from { + opacity: 0; + transform: translateX(20px); + } + + to { + opacity: 1; + transform: translateX(0); + } +} + +@keyframes sparkle { + 0%, + 100% { + filter: drop-shadow(0 0 4px rgba(147, 112, 219, 0.4)); + transform: translateY(1px) scale(1); + } + + 50% { + filter: drop-shadow(0 0 8px rgba(147, 112, 219, 0.6)); + transform: translateY(1px) scale(1.1); + } +} + +@keyframes dotPulse { + 0% { + opacity: 0.3; + } + + 50% { + opacity: 1; + } + + 100% { + opacity: 0.3; + } +} diff --git a/ui/src/components/layout/index.tsx b/ui/src/components/layout/index.tsx index 995e61c8..021cc96a 100644 --- a/ui/src/components/layout/index.tsx +++ b/ui/src/components/layout/index.tsx @@ -18,6 +18,7 @@ import { setGithubBadge, setIsUnsafeMode, setAIOptions, + setIsHighAvailability, } from '@/store/modules/globalSlice' import { useTranslation } from 'react-i18next' import showPng from '@/assets/show.png' @@ -72,6 +73,7 @@ const LayoutPage = () => { dispatch(setVersionNumber(response?.Version)) dispatch(setGithubBadge(response?.CoreOptions?.GithubBadge)) dispatch(setAIOptions(response?.AIOptions)) + dispatch(setIsHighAvailability(response?.CoreOptions?.HighAvailability)) } }, [response, dispatch]) diff --git a/ui/src/pages/cluster/add/index.tsx b/ui/src/pages/cluster/add/index.tsx index f9a5890f..c85102ba 100644 --- a/ui/src/pages/cluster/add/index.tsx +++ b/ui/src/pages/cluster/add/index.tsx @@ -1,7 +1,16 @@ import React, { useState, useEffect } from 'react' import { useNavigate } from 'react-router-dom' import { ArrowLeftOutlined, UploadOutlined } from '@ant-design/icons' -import { Form, Input, Space, Button, Upload, message, notification } from 'antd' +import { + Form, + Input, + Space, + Button, + Upload, + message, + notification, + Select, +} from 'antd' import type { UploadProps } from 'antd' import { useTranslation } from 'react-i18next' import { useSelector } from 'react-redux' @@ -13,11 +22,13 @@ import styles from './styles.module.less' const { TextArea } = Input +const { Option } = Select + const RegisterCluster = () => { const { t } = useTranslation() const [form] = Form.useForm() const navigate = useNavigate() - const { isReadOnlyMode, isUnsafeMode } = useSelector( + const { isReadOnlyMode, isUnsafeMode, isHighAvailability } = useSelector( (state: any) => state.globalSlice, ) const [yamlContent, setYamlContent] = useState('') @@ -82,6 +93,10 @@ const RegisterCluster = () => { const uploadProps: UploadProps = { disabled: isReadOnlyMode, name: 'file', + data: { + clusterMode: form.getFieldValue('clusterMode'), + clusterLevel: form.getFieldValue('clusterLevel'), + }, action: `${HOST}/rest-api/v1/cluster/config/file`, headers: { Authorization: isUnsafeMode @@ -161,6 +176,41 @@ const RegisterCluster = () => { >