diff --git a/plugins/admin/README.adoc b/plugins/admin/README.adoc index dbb2e721..505694be 100644 --- a/plugins/admin/README.adoc +++ b/plugins/admin/README.adoc @@ -120,7 +120,7 @@ Use "kn admin autoscaling [command] --help" for more information about a comman ==== ---- $ kn admin domain set --custom-domain mydomain.com -Updated Knative route domain mydomain.com +Set Knative route domain mydomain.com ---- ==== @@ -128,7 +128,7 @@ Updated Knative route domain mydomain.com ==== ---- $ kn admin domain set --custom-domain test.com --selector app=v1 -Updated Knative route domain test.com +Set Knative route domain test.com with selector [app=v1] ---- ==== diff --git a/plugins/admin/pkg/command/autoscaling/autoscaling.go b/plugins/admin/pkg/command/autoscaling/autoscaling.go index 9f17b25c..dd8987f2 100644 --- a/plugins/admin/pkg/command/autoscaling/autoscaling.go +++ b/plugins/admin/pkg/command/autoscaling/autoscaling.go @@ -24,9 +24,7 @@ func NewAutoscalingCmd(p *pkg.AdminParams) *cobra.Command { var AutoscalingCmd = &cobra.Command{ Use: "autoscaling", Short: "Manage autoscaling config", - Long: `Manage autoscaling provided by Knative Pod Autoscaler (KPA). For example: - -kn admin autoscaling update - to manage autoscaling config`, + Long: `Manage autoscaling provided by Knative Pod Autoscaler (KPA).`, } AutoscalingCmd.AddCommand(NewAutoscalingUpdateCommand(p)) AutoscalingCmd.InitDefaultHelpFlag() diff --git a/plugins/admin/pkg/command/autoscaling/update.go b/plugins/admin/pkg/command/autoscaling/update.go index 4a68c9ba..0b45f9a7 100644 --- a/plugins/admin/pkg/command/autoscaling/update.go +++ b/plugins/admin/pkg/command/autoscaling/update.go @@ -17,11 +17,13 @@ package autoscaling import ( "errors" "fmt" + + "knative.dev/client-contrib/plugins/admin/pkg/command/utils" + "knative.dev/client-contrib/plugins/admin/pkg" "github.com/spf13/cobra" corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/equality" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" _ "k8s.io/client-go/plugin/pkg/client/auth/oidc" @@ -29,7 +31,7 @@ import ( ) var ( - ScaleToZero bool + scaleToZero bool enableScaleToZero = "enable-scale-to-zero" knativeServing = "knative-serving" configAutoscaler = "config-autoscaler" @@ -38,15 +40,13 @@ var ( func NewAutoscalingUpdateCommand(p *pkg.AdminParams) *cobra.Command { AutoscalingUpdateCommand := &cobra.Command{ Use: "update", - Short: "update autoscaling config", - Long: `update autoscaling config provided by Knative Pod Autoscaler (KPA) - -For example: -# To enable scale-to-zero -kn admin autoscaling update --scale-to-zero -# To disable scale-to-zero -kn admin autoscaling update --no-scale-to-zero -`, + Short: "Update autoscaling config", + Long: `Update autoscaling config provided by Knative Pod Autoscaler (KPA)`, + Example: ` + # To enable scale-to-zero + kn admin autoscaling update --scale-to-zero + # To disable scale-to-zero + kn admin autoscaling update --no-scale-to-zero`, PreRunE: func(cmd *cobra.Command, args []string) error { if cmd.Flags().NFlag() == 0 { return errors.New("'autoscaling update' requires flag(s)") @@ -54,33 +54,32 @@ kn admin autoscaling update --no-scale-to-zero return nil }, RunE: func(cmd *cobra.Command, args []string) error { - var scaleToZero string + var desiredScaleToZero string if cmd.Flags().Changed("scale-to-zero") { - scaleToZero = "true" + desiredScaleToZero = "true" } else if cmd.Flags().Changed("no-scale-to-zero") { - scaleToZero = "false" + desiredScaleToZero = "false" } + currentCm := &corev1.ConfigMap{} currentCm, err := p.ClientSet.CoreV1().ConfigMaps(knativeServing).Get(configAutoscaler, metav1.GetOptions{}) if err != nil { - return fmt.Errorf("Failed to get ConfigMaps: %+v", err) + return fmt.Errorf("failed to get ConfigMaps: %+v", err) } desiredCm := currentCm.DeepCopy() - desiredCm.Data[enableScaleToZero] = scaleToZero - if !equality.Semantic.DeepEqual(desiredCm.Data[enableScaleToZero], currentCm.Data[enableScaleToZero]) { - _, err = p.ClientSet.CoreV1().ConfigMaps(knativeServing).Update(desiredCm) - if err != nil { - return fmt.Errorf("Failed to update ConfigMaps: %+v", err) - } - cmd.Printf("Updated Knative autoscaling config %s: %s\n", enableScaleToZero, scaleToZero) - } else { - cmd.Printf("Knative autoscaling config %s: %s not changed\n", enableScaleToZero, currentCm.Data[enableScaleToZero]) + desiredCm.Data[enableScaleToZero] = desiredScaleToZero + + err = utils.UpdateConfigMap(p.ClientSet, desiredCm) + if err != nil { + return fmt.Errorf("failed to update ConfigMap %s in namespace %s: %+v", configAutoscaler, knativeServing, err) } + cmd.Printf("Updated Knative autoscaling config %s: %s\n", enableScaleToZero, desiredScaleToZero) + return nil }, } - flags.AddBothBoolFlagsUnhidden(AutoscalingUpdateCommand.Flags(), &ScaleToZero, "scale-to-zero", "", true, + flags.AddBothBoolFlagsUnhidden(AutoscalingUpdateCommand.Flags(), &scaleToZero, "scale-to-zero", "", true, "Enable scale-to-zero if set.") return AutoscalingUpdateCommand diff --git a/plugins/admin/pkg/command/autoscaling/update_test.go b/plugins/admin/pkg/command/autoscaling/update_test.go index eb07f0f5..2ea36c43 100644 --- a/plugins/admin/pkg/command/autoscaling/update_test.go +++ b/plugins/admin/pkg/command/autoscaling/update_test.go @@ -53,7 +53,7 @@ func TestNewAsUpdateSetCommand(t *testing.T) { } cmd := NewAutoscalingUpdateCommand(&p) _, err := testutil.ExecuteCommand(cmd, "--scale-to-zero") - assert.ErrorContains(t, err, "Failed to get ConfigMaps", err) + assert.ErrorContains(t, err, "failed to get ConfigMaps", err) }) t.Run("enable scale-to-zero successfully", func(t *testing.T) { @@ -131,4 +131,3 @@ func TestNewAsUpdateSetCommand(t *testing.T) { }) } - diff --git a/plugins/admin/pkg/command/domain/domain.go b/plugins/admin/pkg/command/domain/domain.go index b6a26f50..46901a1d 100644 --- a/plugins/admin/pkg/command/domain/domain.go +++ b/plugins/admin/pkg/command/domain/domain.go @@ -24,11 +24,7 @@ func NewDomainCmd(p *pkg.AdminParams) *cobra.Command { var domainCmd = &cobra.Command{ Use: "domain", Short: "Manage route domain", - Long: `Manage default route domain or custom route domain for Service with selectors. For example: - -kn admin domain set - to set Knative route domain - -kn admin domain unset - to unset Knative route domain`, + Long: `Manage default route domain or custom route domain for Service with selectors.`, } domainCmd.AddCommand(NewDomainSetCommand(p)) domainCmd.AddCommand(NewDomainUnSetCommand(p)) diff --git a/plugins/admin/pkg/command/domain/set.go b/plugins/admin/pkg/command/domain/set.go index ca63cd6a..03bec4ef 100644 --- a/plugins/admin/pkg/command/domain/set.go +++ b/plugins/admin/pkg/command/domain/set.go @@ -19,30 +19,34 @@ import ( "fmt" "strings" + "knative.dev/client-contrib/plugins/admin/pkg/command/utils" + "knative.dev/client-contrib/plugins/admin/pkg" "github.com/spf13/cobra" corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/equality" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -var selector []string -var domain string +var ( + selector []string + domain string + knativeServing = "knative-serving" + configDomain = "config-domain" +) // NewDomainSetCommand return the command to set knative custom domain func NewDomainSetCommand(p *pkg.AdminParams) *cobra.Command { domainSetCommand := &cobra.Command{ Use: "set", - Short: "set route domain", - Long: `Set Knative route domain for service - -For example: -# To set a default route domain -kn admin domain set --custom-domain mydomain.com -# To set a route domain for service having label app=v1 -kn admin domain set --custom-domain mydomain.com --selector app=v1 -`, + Short: "Set route domain", + Long: `Set Knative route domain for service`, + Example:` + # To set a default route domain + kn admin domain set --custom-domain mydomain.com + + # To set a route domain for service having label app=v1 + kn admin domain set --custom-domain mydomain.com --selector app=v1`, PreRunE: func(cmd *cobra.Command, args []string) error { domain = strings.TrimSpace(domain) if domain == "" { @@ -52,9 +56,9 @@ kn admin domain set --custom-domain mydomain.com --selector app=v1 }, RunE: func(cmd *cobra.Command, args []string) error { currentCm := &corev1.ConfigMap{} - currentCm, err := p.ClientSet.CoreV1().ConfigMaps("knative-serving").Get("config-domain", metav1.GetOptions{}) + currentCm, err := p.ClientSet.CoreV1().ConfigMaps(knativeServing).Get(configDomain, metav1.GetOptions{}) if err != nil { - return fmt.Errorf("failed to get configmaps: %+v", err) + return fmt.Errorf("failed to get ConfigMap %s in namespace %s: %+v", configDomain, knativeServing, err) } desiredCm := currentCm.DeepCopy() labels := "selector:\n" @@ -79,14 +83,16 @@ kn admin domain set --custom-domain mydomain.com --selector app=v1 } desiredCm.Data[domain] = value - if !equality.Semantic.DeepEqual(desiredCm.Data, currentCm.Data) { - _, err = p.ClientSet.CoreV1().ConfigMaps("knative-serving").Update(desiredCm) - if err != nil { - return fmt.Errorf("Failed to update ConfigMaps: %+v", err) - } - cmd.Printf("Updated knative route domain to %q\n", domain) + + err = utils.UpdateConfigMap(p.ClientSet, desiredCm) + if err != nil { + return fmt.Errorf("failed to update ConfigMap %s in namespace %s: %+v", configDomain, knativeServing, err) + } + + if value == "" { + cmd.Printf("Set knative route domain %q\n", domain) } else { - cmd.Printf("Knative route domain %q not changed\n", domain) + cmd.Printf("Set knative route domain %q with selector %+v\n", domain, selector) } return nil }, diff --git a/plugins/admin/pkg/command/domain/set_test.go b/plugins/admin/pkg/command/domain/set_test.go index 6e3f32da..811c8bf0 100644 --- a/plugins/admin/pkg/command/domain/set_test.go +++ b/plugins/admin/pkg/command/domain/set_test.go @@ -49,8 +49,8 @@ func TestNewDomainSetCommand(t *testing.T) { t.Run("incompleted args", func(t *testing.T) { cm := &corev1.ConfigMap{ ObjectMeta: metav1.ObjectMeta{ - Name: "config-domain", - Namespace: "knative-serving", + Name: configDomain, + Namespace: knativeServing, }, Data: make(map[string]string), } @@ -71,14 +71,14 @@ func TestNewDomainSetCommand(t *testing.T) { } cmd := NewDomainSetCommand(&p) _, err := testutil.ExecuteCommand(cmd, "--custom-domain", "dummy.domain") - assert.ErrorContains(t, err, "failed to get configmaps", err) + assert.ErrorContains(t, err, "failed to get ConfigMap", err) }) t.Run("setting domain config without selector", func(t *testing.T) { cm := &corev1.ConfigMap{ ObjectMeta: metav1.ObjectMeta{ - Name: "config-domain", - Namespace: "knative-serving", + Name: configDomain, + Namespace: knativeServing, }, Data: make(map[string]string), } @@ -90,7 +90,7 @@ func TestNewDomainSetCommand(t *testing.T) { _, err := testutil.ExecuteCommand(cmd, "--custom-domain", "dummy.domain") assert.NilError(t, err) - cm, err = client.CoreV1().ConfigMaps("knative-serving").Get("config-domain", metav1.GetOptions{}) + cm, err = client.CoreV1().ConfigMaps(knativeServing).Get(configDomain, metav1.GetOptions{}) assert.NilError(t, err) assert.Check(t, len(cm.Data) == 1, "expected configmap lengh to be 1") @@ -102,8 +102,8 @@ func TestNewDomainSetCommand(t *testing.T) { t.Run("setting domain config with unchanged value", func(t *testing.T) { cm := &corev1.ConfigMap{ ObjectMeta: metav1.ObjectMeta{ - Name: "config-domain", - Namespace: "knative-serving", + Name: configDomain, + Namespace: knativeServing, }, Data: map[string]string{ "dummy.domain": "", @@ -118,7 +118,7 @@ func TestNewDomainSetCommand(t *testing.T) { _, err := testutil.ExecuteCommand(cmd, "--custom-domain", "dummy.domain") assert.NilError(t, err) - updated, err := client.CoreV1().ConfigMaps("knative-serving").Get("config-domain", metav1.GetOptions{}) + updated, err := client.CoreV1().ConfigMaps(knativeServing).Get(configDomain, metav1.GetOptions{}) assert.NilError(t, err) assert.Check(t, equality.Semantic.DeepEqual(updated, cm), "configmap should not changed") @@ -127,8 +127,8 @@ func TestNewDomainSetCommand(t *testing.T) { t.Run("adding domain config without selector with existing domain configuration", func(t *testing.T) { cm := &corev1.ConfigMap{ ObjectMeta: metav1.ObjectMeta{ - Name: "config-domain", - Namespace: "knative-serving", + Name: configDomain, + Namespace: knativeServing, }, Data: map[string]string{ "foo.bar": "", @@ -141,9 +141,9 @@ func TestNewDomainSetCommand(t *testing.T) { cmd := NewDomainSetCommand(&p) o, err := testutil.ExecuteCommand(cmd, "--custom-domain", "dummy.domain") assert.NilError(t, err) - assert.Check(t, strings.Contains(o, "Updated knative route domain to"), "expected update information in standard output") + assert.Check(t, strings.Contains(o, "Set knative route domain \"dummy.domain\""), "expected update information in standard output") - cm, err = client.CoreV1().ConfigMaps("knative-serving").Get("config-domain", metav1.GetOptions{}) + cm, err = client.CoreV1().ConfigMaps(knativeServing).Get(configDomain, metav1.GetOptions{}) assert.NilError(t, err) assert.Check(t, len(cm.Data) == 1, "expected configmap lengh to be 1, actual %d", len(cm.Data)) @@ -155,8 +155,8 @@ func TestNewDomainSetCommand(t *testing.T) { t.Run("adding domain config with selector", func(t *testing.T) { cm := &corev1.ConfigMap{ ObjectMeta: metav1.ObjectMeta{ - Name: "config-domain", - Namespace: "knative-serving", + Name: configDomain, + Namespace: knativeServing, }, Data: map[string]string{ "foo.bar": "", @@ -170,9 +170,9 @@ func TestNewDomainSetCommand(t *testing.T) { o, err := testutil.ExecuteCommand(cmd, "--custom-domain", "dummy.domain", "--selector", "app=dummy") assert.NilError(t, err) - assert.Check(t, strings.Contains(o, "Updated knative route domain to"), "invalid output %q", o) + assert.Check(t, strings.Contains(o, "Set knative route domain \"dummy.domain\" with selector [app=dummy]"), "invalid output %q", o) - cm, err = client.CoreV1().ConfigMaps("knative-serving").Get("config-domain", metav1.GetOptions{}) + cm, err = client.CoreV1().ConfigMaps(knativeServing).Get(configDomain, metav1.GetOptions{}) assert.NilError(t, err) assert.Check(t, len(cm.Data) == 2, "expected configmap lengh to be 2, actual %d", len(cm.Data)) diff --git a/plugins/admin/pkg/command/domain/unset.go b/plugins/admin/pkg/command/domain/unset.go index 4b1eb9e0..f10ec2ac 100644 --- a/plugins/admin/pkg/command/domain/unset.go +++ b/plugins/admin/pkg/command/domain/unset.go @@ -18,6 +18,8 @@ import ( "errors" "fmt" + "knative.dev/client-contrib/plugins/admin/pkg/command/utils" + "knative.dev/client-contrib/plugins/admin/pkg" "github.com/spf13/cobra" @@ -29,13 +31,11 @@ import ( func NewDomainUnSetCommand(p *pkg.AdminParams) *cobra.Command { domainUnSetCommand := &cobra.Command{ Use: "unset", - Short: "unset route domain", - Long: `unset Knative route domain for service - -For example: -# To unset a route domain -kn admin domain unset --custom-domain mydomain.com -`, + Short: "Unset route domain", + Long: `Unset Knative route domain for service`, + Example: ` + # To unset a route domain + kn admin domain unset --custom-domain mydomain.com`, PreRunE: func(cmd *cobra.Command, args []string) error { if domain == "" { return errors.New("'domain unset' requires the route name to run provided with the --custom-domain option") @@ -44,7 +44,7 @@ kn admin domain unset --custom-domain mydomain.com }, RunE: func(cmd *cobra.Command, args []string) error { currentCm := &corev1.ConfigMap{} - currentCm, err := p.ClientSet.CoreV1().ConfigMaps("knative-serving").Get("config-domain", metav1.GetOptions{}) + currentCm, err := p.ClientSet.CoreV1().ConfigMaps(knativeServing).Get(configDomain, metav1.GetOptions{}) if err != nil { return fmt.Errorf("failed to get configmaps: %+v", err) } @@ -58,10 +58,11 @@ kn admin domain unset --custom-domain mydomain.com return fmt.Errorf("Knative route domain %s not found\n", domain) } - _, err = p.ClientSet.CoreV1().ConfigMaps("knative-serving").Update(desiredCm) + err = utils.UpdateConfigMap(p.ClientSet, desiredCm) if err != nil { - return fmt.Errorf("Failed to update ConfigMaps: %+v", err) + return fmt.Errorf("failed to update ConfigMap %s in namespace %s: %+v", configDomain, knativeServing, err) } + cmd.Printf("Unset Knative route domain %s\n", domain) return nil }, diff --git a/plugins/admin/pkg/command/domain/unset_test.go b/plugins/admin/pkg/command/domain/unset_test.go index 24727681..d42e43e1 100644 --- a/plugins/admin/pkg/command/domain/unset_test.go +++ b/plugins/admin/pkg/command/domain/unset_test.go @@ -31,8 +31,8 @@ func TestNewDomainUnSetCommand(t *testing.T) { t.Run("incompleted args", func(t *testing.T) { cm := &corev1.ConfigMap{ ObjectMeta: metav1.ObjectMeta{ - Name: "config-domain", - Namespace: "knative-serving", + Name: configDomain, + Namespace: knativeServing, }, Data: make(map[string]string), } @@ -59,8 +59,8 @@ func TestNewDomainUnSetCommand(t *testing.T) { t.Run("route domain not found", func(t *testing.T) { cm := &corev1.ConfigMap{ ObjectMeta: metav1.ObjectMeta{ - Name: "config-domain", - Namespace: "knative-serving", + Name: configDomain, + Namespace: knativeServing, }, Data: map[string]string{ "dummy.domain": "", @@ -79,8 +79,8 @@ func TestNewDomainUnSetCommand(t *testing.T) { t.Run("unset domain", func(t *testing.T) { cm := &corev1.ConfigMap{ ObjectMeta: metav1.ObjectMeta{ - Name: "config-domain", - Namespace: "knative-serving", + Name: configDomain, + Namespace: knativeServing, }, Data: map[string]string{ "dummy1.domain": "", @@ -95,7 +95,7 @@ func TestNewDomainUnSetCommand(t *testing.T) { _, err := testutil.ExecuteCommand(cmd, "--custom-domain", "dummy1.domain") assert.NilError(t, err) - cm, err = client.CoreV1().ConfigMaps("knative-serving").Get("config-domain", metav1.GetOptions{}) + cm, err = client.CoreV1().ConfigMaps(knativeServing).Get(configDomain, metav1.GetOptions{}) assert.NilError(t, err) assert.Check(t, len(cm.Data) == 1, "expected configmap lengh to be 1") @@ -108,7 +108,7 @@ func TestNewDomainUnSetCommand(t *testing.T) { _, err = testutil.ExecuteCommand(cmd, "--custom-domain", "dummy2.domain") assert.NilError(t, err) - cm, err = client.CoreV1().ConfigMaps("knative-serving").Get("config-domain", metav1.GetOptions{}) + cm, err = client.CoreV1().ConfigMaps(knativeServing).Get(configDomain, metav1.GetOptions{}) assert.NilError(t, err) assert.Check(t, len(cm.Data) == 0, "expected configmap lengh to be 0") }) diff --git a/plugins/admin/pkg/command/registry/add.go b/plugins/admin/pkg/command/registry/add.go index 1d8b9a96..3630a3a2 100644 --- a/plugins/admin/pkg/command/registry/add.go +++ b/plugins/admin/pkg/command/registry/add.go @@ -56,16 +56,16 @@ var prflags prcmdFlags func NewPrAddCommand(p *pkg.AdminParams) *cobra.Command { var prAddCmd = &cobra.Command{ Use: "add", - Short: "add registry with credentials", - Long: `add registry with credentials to enable Service deployment from it -For example: - -kn admin registry add \ - --secret-name=[SECRET_NAME] - --server=[REGISTRY_SERVER_URL] \ - --email=[REGISTRY_EMAIL] \ - --username=[REGISTRY_USER] \ - --password=[REGISTRY_PASSWORD]`, + Short: "Add registry with credentials", + Long: `Add registry with credentials to enable Service deployment from it`, + Example: ` + # add registry with credentials + kn admin registry add \ + --secret-name=[SECRET_NAME] + --server=[REGISTRY_SERVER_URL] \ + --email=[REGISTRY_EMAIL] \ + --username=[REGISTRY_USER] \ + --password=[REGISTRY_PASSWORD]`, PreRunE: func(cmd *cobra.Command, args []string) error { if prflags.Username == "" { return errors.New("'registry add' requires the registry username to run provided with the --username option") @@ -126,7 +126,7 @@ kn admin registry add \ if err != nil { return fmt.Errorf("failed to add registry secret in default serviceaccount: %v", err) } - cmd.Printf("Private registry %s added for default serviceaccount\n", prflags.Server) + cmd.Printf("Private registry %s is added for default serviceaccount\n", prflags.Server) return nil }, } diff --git a/plugins/admin/pkg/command/registry/registry.go b/plugins/admin/pkg/command/registry/registry.go index b5e709d9..29511863 100644 --- a/plugins/admin/pkg/command/registry/registry.go +++ b/plugins/admin/pkg/command/registry/registry.go @@ -24,15 +24,7 @@ func NewPrivateRegistryCmd(p *pkg.AdminParams) *cobra.Command { var privateRegistryCmd = &cobra.Command{ Use: "registry", Short: "Manage registry", - Long: `Manage Service deployment from a registry with credentials -For example: - -kn admin registry add \ - --secret-name=[SECRET_NAME] - --server=[REGISTRY_SERVER_URL] \ - --email=[REGISTRY_EMAIL] \ - --username=[REGISTRY_USER] \ - --password=[REGISTRY_PASSWORD]`, + Long: `Manage Service deployment from a registry with credentials`, } privateRegistryCmd.AddCommand(NewPrAddCommand(p)) privateRegistryCmd.InitDefaultHelpCmd() diff --git a/plugins/admin/pkg/command/utils/utils.go b/plugins/admin/pkg/command/utils/utils.go new file mode 100644 index 00000000..e3a32dad --- /dev/null +++ b/plugins/admin/pkg/command/utils/utils.go @@ -0,0 +1,37 @@ +// Copyright © 2020 The Knative 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 ( + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/equality" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/util/retry" +) + +func UpdateConfigMap(client kubernetes.Interface, desiredCm *corev1.ConfigMap) error { + return retry.RetryOnConflict(retry.DefaultRetry, func() error { + currentCm, err := client.CoreV1().ConfigMaps(desiredCm.Namespace).Get(desiredCm.Name, metav1.GetOptions{}) + if err != nil { + return err + } + if equality.Semantic.DeepEqual(desiredCm, currentCm) { + return nil + } + _, err = client.CoreV1().ConfigMaps(desiredCm.Namespace).Update(desiredCm) + return err + }) +} diff --git a/plugins/admin/pkg/command/utils/utils_test.go b/plugins/admin/pkg/command/utils/utils_test.go new file mode 100644 index 00000000..523eb426 --- /dev/null +++ b/plugins/admin/pkg/command/utils/utils_test.go @@ -0,0 +1,93 @@ +// Copyright © 2020 The Knative 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 ( + "gotest.tools/assert" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/equality" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + k8sfake "k8s.io/client-go/kubernetes/fake" + "testing" +) + +func TestUtils(t *testing.T) { + t.Run("report error if ConfigMap not found", func(t *testing.T) { + desiredCm := &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "config-domain", + Namespace: "knative-serving", + }, + Data: make(map[string]string), + } + client := k8sfake.NewSimpleClientset() + err := UpdateConfigMap(client, desiredCm) + assert.ErrorContains(t, err, "configmaps \"config-domain\" not found", err) + }) + + t.Run("update ConfigMap successfully", func(t *testing.T) { + oriCm := &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "config-domain", + Namespace: "knative-serving", + }, + Data: make(map[string]string), + } + desiredCm := &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "config-domain", + Namespace: "knative-serving", + }, + Data: map[string]string{ + "dummy.domain": "", + }, + } + client := k8sfake.NewSimpleClientset(oriCm) + err := UpdateConfigMap(client, desiredCm) + assert.NilError(t, err) + + cm, err := client.CoreV1().ConfigMaps("knative-serving").Get("config-domain", metav1.GetOptions{}) + assert.NilError(t, err) + assert.Check(t, len(cm.Data) == 1, "expected configmap lengh to be 1") + }) + + t.Run("ConfigMap not changed if desired one is equal to the existed one", func(t *testing.T) { + oriCm := &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "config-domain", + Namespace: "knative-serving", + }, + Data: map[string]string{ + "dummy.domain": "", + }, + } + desiredCm := &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "config-domain", + Namespace: "knative-serving", + }, + Data: map[string]string{ + "dummy.domain": "", + }, + } + client := k8sfake.NewSimpleClientset(oriCm) + err := UpdateConfigMap(client, desiredCm) + assert.NilError(t, err) + + updated, err := client.CoreV1().ConfigMaps("knative-serving").Get("config-domain", metav1.GetOptions{}) + assert.NilError(t, err) + assert.Check(t, equality.Semantic.DeepEqual(updated, oriCm), "configmap should not changed") + }) +} diff --git a/plugins/admin/vendor/k8s.io/apimachinery/pkg/util/wait/doc.go b/plugins/admin/vendor/k8s.io/apimachinery/pkg/util/wait/doc.go new file mode 100644 index 00000000..3f0c968e --- /dev/null +++ b/plugins/admin/vendor/k8s.io/apimachinery/pkg/util/wait/doc.go @@ -0,0 +1,19 @@ +/* +Copyright 2014 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Package wait provides tools for polling or listening for changes +// to a condition. +package wait // import "k8s.io/apimachinery/pkg/util/wait" diff --git a/plugins/admin/vendor/k8s.io/apimachinery/pkg/util/wait/wait.go b/plugins/admin/vendor/k8s.io/apimachinery/pkg/util/wait/wait.go new file mode 100644 index 00000000..386c3e7e --- /dev/null +++ b/plugins/admin/vendor/k8s.io/apimachinery/pkg/util/wait/wait.go @@ -0,0 +1,512 @@ +/* +Copyright 2014 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package wait + +import ( + "context" + "errors" + "math/rand" + "sync" + "time" + + "k8s.io/apimachinery/pkg/util/runtime" +) + +// For any test of the style: +// ... +// <- time.After(timeout): +// t.Errorf("Timed out") +// The value for timeout should effectively be "forever." Obviously we don't want our tests to truly lock up forever, but 30s +// is long enough that it is effectively forever for the things that can slow down a run on a heavily contended machine +// (GC, seeks, etc), but not so long as to make a developer ctrl-c a test run if they do happen to break that test. +var ForeverTestTimeout = time.Second * 30 + +// NeverStop may be passed to Until to make it never stop. +var NeverStop <-chan struct{} = make(chan struct{}) + +// Group allows to start a group of goroutines and wait for their completion. +type Group struct { + wg sync.WaitGroup +} + +func (g *Group) Wait() { + g.wg.Wait() +} + +// StartWithChannel starts f in a new goroutine in the group. +// stopCh is passed to f as an argument. f should stop when stopCh is available. +func (g *Group) StartWithChannel(stopCh <-chan struct{}, f func(stopCh <-chan struct{})) { + g.Start(func() { + f(stopCh) + }) +} + +// StartWithContext starts f in a new goroutine in the group. +// ctx is passed to f as an argument. f should stop when ctx.Done() is available. +func (g *Group) StartWithContext(ctx context.Context, f func(context.Context)) { + g.Start(func() { + f(ctx) + }) +} + +// Start starts f in a new goroutine in the group. +func (g *Group) Start(f func()) { + g.wg.Add(1) + go func() { + defer g.wg.Done() + f() + }() +} + +// Forever calls f every period for ever. +// +// Forever is syntactic sugar on top of Until. +func Forever(f func(), period time.Duration) { + Until(f, period, NeverStop) +} + +// Until loops until stop channel is closed, running f every period. +// +// Until is syntactic sugar on top of JitterUntil with zero jitter factor and +// with sliding = true (which means the timer for period starts after the f +// completes). +func Until(f func(), period time.Duration, stopCh <-chan struct{}) { + JitterUntil(f, period, 0.0, true, stopCh) +} + +// UntilWithContext loops until context is done, running f every period. +// +// UntilWithContext is syntactic sugar on top of JitterUntilWithContext +// with zero jitter factor and with sliding = true (which means the timer +// for period starts after the f completes). +func UntilWithContext(ctx context.Context, f func(context.Context), period time.Duration) { + JitterUntilWithContext(ctx, f, period, 0.0, true) +} + +// NonSlidingUntil loops until stop channel is closed, running f every +// period. +// +// NonSlidingUntil is syntactic sugar on top of JitterUntil with zero jitter +// factor, with sliding = false (meaning the timer for period starts at the same +// time as the function starts). +func NonSlidingUntil(f func(), period time.Duration, stopCh <-chan struct{}) { + JitterUntil(f, period, 0.0, false, stopCh) +} + +// NonSlidingUntilWithContext loops until context is done, running f every +// period. +// +// NonSlidingUntilWithContext is syntactic sugar on top of JitterUntilWithContext +// with zero jitter factor, with sliding = false (meaning the timer for period +// starts at the same time as the function starts). +func NonSlidingUntilWithContext(ctx context.Context, f func(context.Context), period time.Duration) { + JitterUntilWithContext(ctx, f, period, 0.0, false) +} + +// JitterUntil loops until stop channel is closed, running f every period. +// +// If jitterFactor is positive, the period is jittered before every run of f. +// If jitterFactor is not positive, the period is unchanged and not jittered. +// +// If sliding is true, the period is computed after f runs. If it is false then +// period includes the runtime for f. +// +// Close stopCh to stop. f may not be invoked if stop channel is already +// closed. Pass NeverStop to if you don't want it stop. +func JitterUntil(f func(), period time.Duration, jitterFactor float64, sliding bool, stopCh <-chan struct{}) { + var t *time.Timer + var sawTimeout bool + + for { + select { + case <-stopCh: + return + default: + } + + jitteredPeriod := period + if jitterFactor > 0.0 { + jitteredPeriod = Jitter(period, jitterFactor) + } + + if !sliding { + t = resetOrReuseTimer(t, jitteredPeriod, sawTimeout) + } + + func() { + defer runtime.HandleCrash() + f() + }() + + if sliding { + t = resetOrReuseTimer(t, jitteredPeriod, sawTimeout) + } + + // NOTE: b/c there is no priority selection in golang + // it is possible for this to race, meaning we could + // trigger t.C and stopCh, and t.C select falls through. + // In order to mitigate we re-check stopCh at the beginning + // of every loop to prevent extra executions of f(). + select { + case <-stopCh: + return + case <-t.C: + sawTimeout = true + } + } +} + +// JitterUntilWithContext loops until context is done, running f every period. +// +// If jitterFactor is positive, the period is jittered before every run of f. +// If jitterFactor is not positive, the period is unchanged and not jittered. +// +// If sliding is true, the period is computed after f runs. If it is false then +// period includes the runtime for f. +// +// Cancel context to stop. f may not be invoked if context is already expired. +func JitterUntilWithContext(ctx context.Context, f func(context.Context), period time.Duration, jitterFactor float64, sliding bool) { + JitterUntil(func() { f(ctx) }, period, jitterFactor, sliding, ctx.Done()) +} + +// Jitter returns a time.Duration between duration and duration + maxFactor * +// duration. +// +// This allows clients to avoid converging on periodic behavior. If maxFactor +// is 0.0, a suggested default value will be chosen. +func Jitter(duration time.Duration, maxFactor float64) time.Duration { + if maxFactor <= 0.0 { + maxFactor = 1.0 + } + wait := duration + time.Duration(rand.Float64()*maxFactor*float64(duration)) + return wait +} + +// ErrWaitTimeout is returned when the condition exited without success. +var ErrWaitTimeout = errors.New("timed out waiting for the condition") + +// ConditionFunc returns true if the condition is satisfied, or an error +// if the loop should be aborted. +type ConditionFunc func() (done bool, err error) + +// Backoff holds parameters applied to a Backoff function. +type Backoff struct { + // The initial duration. + Duration time.Duration + // Duration is multiplied by factor each iteration, if factor is not zero + // and the limits imposed by Steps and Cap have not been reached. + // Should not be negative. + // The jitter does not contribute to the updates to the duration parameter. + Factor float64 + // The sleep at each iteration is the duration plus an additional + // amount chosen uniformly at random from the interval between + // zero and `jitter*duration`. + Jitter float64 + // The remaining number of iterations in which the duration + // parameter may change (but progress can be stopped earlier by + // hitting the cap). If not positive, the duration is not + // changed. Used for exponential backoff in combination with + // Factor and Cap. + Steps int + // A limit on revised values of the duration parameter. If a + // multiplication by the factor parameter would make the duration + // exceed the cap then the duration is set to the cap and the + // steps parameter is set to zero. + Cap time.Duration +} + +// Step (1) returns an amount of time to sleep determined by the +// original Duration and Jitter and (2) mutates the provided Backoff +// to update its Steps and Duration. +func (b *Backoff) Step() time.Duration { + if b.Steps < 1 { + if b.Jitter > 0 { + return Jitter(b.Duration, b.Jitter) + } + return b.Duration + } + b.Steps-- + + duration := b.Duration + + // calculate the next step + if b.Factor != 0 { + b.Duration = time.Duration(float64(b.Duration) * b.Factor) + if b.Cap > 0 && b.Duration > b.Cap { + b.Duration = b.Cap + b.Steps = 0 + } + } + + if b.Jitter > 0 { + duration = Jitter(duration, b.Jitter) + } + return duration +} + +// contextForChannel derives a child context from a parent channel. +// +// The derived context's Done channel is closed when the returned cancel function +// is called or when the parent channel is closed, whichever happens first. +// +// Note the caller must *always* call the CancelFunc, otherwise resources may be leaked. +func contextForChannel(parentCh <-chan struct{}) (context.Context, context.CancelFunc) { + ctx, cancel := context.WithCancel(context.Background()) + + go func() { + select { + case <-parentCh: + cancel() + case <-ctx.Done(): + } + }() + return ctx, cancel +} + +// ExponentialBackoff repeats a condition check with exponential backoff. +// +// It repeatedly checks the condition and then sleeps, using `backoff.Step()` +// to determine the length of the sleep and adjust Duration and Steps. +// Stops and returns as soon as: +// 1. the condition check returns true or an error, +// 2. `backoff.Steps` checks of the condition have been done, or +// 3. a sleep truncated by the cap on duration has been completed. +// In case (1) the returned error is what the condition function returned. +// In all other cases, ErrWaitTimeout is returned. +func ExponentialBackoff(backoff Backoff, condition ConditionFunc) error { + for backoff.Steps > 0 { + if ok, err := condition(); err != nil || ok { + return err + } + if backoff.Steps == 1 { + break + } + time.Sleep(backoff.Step()) + } + return ErrWaitTimeout +} + +// Poll tries a condition func until it returns true, an error, or the timeout +// is reached. +// +// Poll always waits the interval before the run of 'condition'. +// 'condition' will always be invoked at least once. +// +// Some intervals may be missed if the condition takes too long or the time +// window is too short. +// +// If you want to Poll something forever, see PollInfinite. +func Poll(interval, timeout time.Duration, condition ConditionFunc) error { + return pollInternal(poller(interval, timeout), condition) +} + +func pollInternal(wait WaitFunc, condition ConditionFunc) error { + done := make(chan struct{}) + defer close(done) + return WaitFor(wait, condition, done) +} + +// PollImmediate tries a condition func until it returns true, an error, or the timeout +// is reached. +// +// PollImmediate always checks 'condition' before waiting for the interval. 'condition' +// will always be invoked at least once. +// +// Some intervals may be missed if the condition takes too long or the time +// window is too short. +// +// If you want to immediately Poll something forever, see PollImmediateInfinite. +func PollImmediate(interval, timeout time.Duration, condition ConditionFunc) error { + return pollImmediateInternal(poller(interval, timeout), condition) +} + +func pollImmediateInternal(wait WaitFunc, condition ConditionFunc) error { + done, err := condition() + if err != nil { + return err + } + if done { + return nil + } + return pollInternal(wait, condition) +} + +// PollInfinite tries a condition func until it returns true or an error +// +// PollInfinite always waits the interval before the run of 'condition'. +// +// Some intervals may be missed if the condition takes too long or the time +// window is too short. +func PollInfinite(interval time.Duration, condition ConditionFunc) error { + done := make(chan struct{}) + defer close(done) + return PollUntil(interval, condition, done) +} + +// PollImmediateInfinite tries a condition func until it returns true or an error +// +// PollImmediateInfinite runs the 'condition' before waiting for the interval. +// +// Some intervals may be missed if the condition takes too long or the time +// window is too short. +func PollImmediateInfinite(interval time.Duration, condition ConditionFunc) error { + done, err := condition() + if err != nil { + return err + } + if done { + return nil + } + return PollInfinite(interval, condition) +} + +// PollUntil tries a condition func until it returns true, an error or stopCh is +// closed. +// +// PollUntil always waits interval before the first run of 'condition'. +// 'condition' will always be invoked at least once. +func PollUntil(interval time.Duration, condition ConditionFunc, stopCh <-chan struct{}) error { + ctx, cancel := contextForChannel(stopCh) + defer cancel() + return WaitFor(poller(interval, 0), condition, ctx.Done()) +} + +// PollImmediateUntil tries a condition func until it returns true, an error or stopCh is closed. +// +// PollImmediateUntil runs the 'condition' before waiting for the interval. +// 'condition' will always be invoked at least once. +func PollImmediateUntil(interval time.Duration, condition ConditionFunc, stopCh <-chan struct{}) error { + done, err := condition() + if err != nil { + return err + } + if done { + return nil + } + select { + case <-stopCh: + return ErrWaitTimeout + default: + return PollUntil(interval, condition, stopCh) + } +} + +// WaitFunc creates a channel that receives an item every time a test +// should be executed and is closed when the last test should be invoked. +type WaitFunc func(done <-chan struct{}) <-chan struct{} + +// WaitFor continually checks 'fn' as driven by 'wait'. +// +// WaitFor gets a channel from 'wait()'', and then invokes 'fn' once for every value +// placed on the channel and once more when the channel is closed. If the channel is closed +// and 'fn' returns false without error, WaitFor returns ErrWaitTimeout. +// +// If 'fn' returns an error the loop ends and that error is returned. If +// 'fn' returns true the loop ends and nil is returned. +// +// ErrWaitTimeout will be returned if the 'done' channel is closed without fn ever +// returning true. +// +// When the done channel is closed, because the golang `select` statement is +// "uniform pseudo-random", the `fn` might still run one or multiple time, +// though eventually `WaitFor` will return. +func WaitFor(wait WaitFunc, fn ConditionFunc, done <-chan struct{}) error { + stopCh := make(chan struct{}) + defer close(stopCh) + c := wait(stopCh) + for { + select { + case _, open := <-c: + ok, err := fn() + if err != nil { + return err + } + if ok { + return nil + } + if !open { + return ErrWaitTimeout + } + case <-done: + return ErrWaitTimeout + } + } +} + +// poller returns a WaitFunc that will send to the channel every interval until +// timeout has elapsed and then closes the channel. +// +// Over very short intervals you may receive no ticks before the channel is +// closed. A timeout of 0 is interpreted as an infinity, and in such a case +// it would be the caller's responsibility to close the done channel. +// Failure to do so would result in a leaked goroutine. +// +// Output ticks are not buffered. If the channel is not ready to receive an +// item, the tick is skipped. +func poller(interval, timeout time.Duration) WaitFunc { + return WaitFunc(func(done <-chan struct{}) <-chan struct{} { + ch := make(chan struct{}) + + go func() { + defer close(ch) + + tick := time.NewTicker(interval) + defer tick.Stop() + + var after <-chan time.Time + if timeout != 0 { + // time.After is more convenient, but it + // potentially leaves timers around much longer + // than necessary if we exit early. + timer := time.NewTimer(timeout) + after = timer.C + defer timer.Stop() + } + + for { + select { + case <-tick.C: + // If the consumer isn't ready for this signal drop it and + // check the other channels. + select { + case ch <- struct{}{}: + default: + } + case <-after: + return + case <-done: + return + } + } + }() + + return ch + }) +} + +// resetOrReuseTimer avoids allocating a new timer if one is already in use. +// Not safe for multiple threads. +func resetOrReuseTimer(t *time.Timer, d time.Duration, sawTimeout bool) *time.Timer { + if t == nil { + return time.NewTimer(d) + } + if !t.Stop() && !sawTimeout { + <-t.C + } + t.Reset(d) + return t +} diff --git a/plugins/admin/vendor/k8s.io/client-go/util/retry/OWNERS b/plugins/admin/vendor/k8s.io/client-go/util/retry/OWNERS new file mode 100644 index 00000000..dec3e88d --- /dev/null +++ b/plugins/admin/vendor/k8s.io/client-go/util/retry/OWNERS @@ -0,0 +1,4 @@ +# See the OWNERS docs at https://go.k8s.io/owners + +reviewers: +- caesarxuchao diff --git a/plugins/admin/vendor/k8s.io/client-go/util/retry/util.go b/plugins/admin/vendor/k8s.io/client-go/util/retry/util.go new file mode 100644 index 00000000..15e2722f --- /dev/null +++ b/plugins/admin/vendor/k8s.io/client-go/util/retry/util.go @@ -0,0 +1,105 @@ +/* +Copyright 2016 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package retry + +import ( + "time" + + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/util/wait" +) + +// DefaultRetry is the recommended retry for a conflict where multiple clients +// are making changes to the same resource. +var DefaultRetry = wait.Backoff{ + Steps: 5, + Duration: 10 * time.Millisecond, + Factor: 1.0, + Jitter: 0.1, +} + +// DefaultBackoff is the recommended backoff for a conflict where a client +// may be attempting to make an unrelated modification to a resource under +// active management by one or more controllers. +var DefaultBackoff = wait.Backoff{ + Steps: 4, + Duration: 10 * time.Millisecond, + Factor: 5.0, + Jitter: 0.1, +} + +// OnError allows the caller to retry fn in case the error returned by fn is retriable +// according to the provided function. backoff defines the maximum retries and the wait +// interval between two retries. +func OnError(backoff wait.Backoff, retriable func(error) bool, fn func() error) error { + var lastErr error + err := wait.ExponentialBackoff(backoff, func() (bool, error) { + err := fn() + switch { + case err == nil: + return true, nil + case retriable(err): + lastErr = err + return false, nil + default: + return false, err + } + }) + if err == wait.ErrWaitTimeout { + err = lastErr + } + return err +} + +// RetryOnConflict is used to make an update to a resource when you have to worry about +// conflicts caused by other code making unrelated updates to the resource at the same +// time. fn should fetch the resource to be modified, make appropriate changes to it, try +// to update it, and return (unmodified) the error from the update function. On a +// successful update, RetryOnConflict will return nil. If the update function returns a +// "Conflict" error, RetryOnConflict will wait some amount of time as described by +// backoff, and then try again. On a non-"Conflict" error, or if it retries too many times +// and gives up, RetryOnConflict will return an error to the caller. +// +// err := retry.RetryOnConflict(retry.DefaultRetry, func() error { +// // Fetch the resource here; you need to refetch it on every try, since +// // if you got a conflict on the last update attempt then you need to get +// // the current version before making your own changes. +// pod, err := c.Pods("mynamespace").Get(name, metav1.GetOptions{}) +// if err ! nil { +// return err +// } +// +// // Make whatever updates to the resource are needed +// pod.Status.Phase = v1.PodFailed +// +// // Try to update +// _, err = c.Pods("mynamespace").UpdateStatus(pod) +// // You have to return err itself here (not wrapped inside another error) +// // so that RetryOnConflict can identify it correctly. +// return err +// }) +// if err != nil { +// // May be conflict if max retries were hit, or may be something unrelated +// // like permissions or a network error +// return err +// } +// ... +// +// TODO: Make Backoff an interface? +func RetryOnConflict(backoff wait.Backoff, fn func() error) error { + return OnError(backoff, errors.IsConflict, fn) +} diff --git a/plugins/admin/vendor/modules.txt b/plugins/admin/vendor/modules.txt index 986246c8..246e2925 100644 --- a/plugins/admin/vendor/modules.txt +++ b/plugins/admin/vendor/modules.txt @@ -196,6 +196,7 @@ k8s.io/apimachinery/pkg/util/sets k8s.io/apimachinery/pkg/util/strategicpatch k8s.io/apimachinery/pkg/util/validation k8s.io/apimachinery/pkg/util/validation/field +k8s.io/apimachinery/pkg/util/wait k8s.io/apimachinery/pkg/util/yaml k8s.io/apimachinery/pkg/version k8s.io/apimachinery/pkg/watch @@ -310,6 +311,7 @@ k8s.io/client-go/util/connrotation k8s.io/client-go/util/flowcontrol k8s.io/client-go/util/homedir k8s.io/client-go/util/keyutil +k8s.io/client-go/util/retry # k8s.io/klog v1.0.0 k8s.io/klog # k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a