diff --git a/build/dlv.Dockerfile b/build/dlv.Dockerfile index 46238fb0..bf5dc770 100644 --- a/build/dlv.Dockerfile +++ b/build/dlv.Dockerfile @@ -1,6 +1,6 @@ -FROM golang:1.23 as delve -RUN curl --location --output delve-1.23.1.tar.gz https://github.com/go-delve/delve/archive/v1.23.1.tar.gz \ - && tar xzf delve-1.23.1.tar.gz -RUN cd delve-1.23.1 && CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o /go/dlv -ldflags '-extldflags "-static"' ./cmd/dlv/ +FROM golang:1.25 as delve +RUN curl --location --output delve-1.25.2.tar.gz https://github.com/go-delve/delve/archive/v1.25.2.tar.gz \ + && tar xzf delve-1.25.2.tar.gz +RUN cd delve-1.25.2 && CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o /go/dlv -ldflags '-extldflags "-static"' ./cmd/dlv/ FROM busybox COPY --from=delve /go/dlv /bin/dlv \ No newline at end of file diff --git a/go.mod b/go.mod index 4c058e28..7402d126 100644 --- a/go.mod +++ b/go.mod @@ -181,6 +181,7 @@ require ( github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect github.com/lib/pq v1.10.9 // indirect + github.com/lithammer/dedent v1.1.0 // indirect github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683 // indirect github.com/mailru/easyjson v0.9.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect diff --git a/pkg/daemon/action/status.go b/pkg/daemon/action/status.go index 329460c7..8df0f513 100644 --- a/pkg/daemon/action/status.go +++ b/pkg/daemon/action/status.go @@ -4,6 +4,7 @@ import ( "context" "strconv" "strings" + "time" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/yaml" @@ -24,13 +25,15 @@ const ( func (svr *Server) Status(ctx context.Context, req *rpc.StatusRequest) (*rpc.StatusResponse, error) { var list []*rpc.Status + timeoutCtx, cancelFunc := context.WithTimeout(ctx, 5*time.Second) + defer cancelFunc() if len(req.ConnectionIDs) != 0 { for _, connectionID := range req.ConnectionIDs { for _, options := range svr.connections { if options.GetConnectionID() == connectionID { result := genStatus(options) var err error - result.ProxyList, result.SyncList, err = gen(ctx, options, options.Sync) + result.ProxyList, result.SyncList, err = gen(timeoutCtx, options, options.Sync) if err != nil { plog.G(context.Background()).Errorf("Error generating status: %v", err) } @@ -42,7 +45,7 @@ func (svr *Server) Status(ctx context.Context, req *rpc.StatusRequest) (*rpc.Sta for _, options := range svr.connections { result := genStatus(options) var err error - result.ProxyList, result.SyncList, err = gen(ctx, options, options.Sync) + result.ProxyList, result.SyncList, err = gen(timeoutCtx, options, options.Sync) if err != nil { plog.G(context.Background()).Errorf("Error generating status: %v", err) } diff --git a/pkg/handler/connect.go b/pkg/handler/connect.go index 766081e1..2457e7cb 100644 --- a/pkg/handler/connect.go +++ b/pkg/handler/connect.go @@ -1079,7 +1079,7 @@ func upgradeDeploySpec(ctx context.Context, f cmdutil.Factory, ns, name, image s plog.G(ctx).Errorf("Failed to patch image update to pod template: %v", err) return err } - err = util.RolloutStatus(ctx, f, ns, fmt.Sprintf("%s/%s", p.Info.Mapping.Resource.GroupResource().String(), p.Info.Name), time.Minute*60) + err = util.RolloutStatus(ctx, f, ns, fmt.Sprintf("%s/%s", p.Info.Mapping.Resource.GroupResource().String(), p.Info.Name)) if err != nil { return err } diff --git a/pkg/handler/once.go b/pkg/handler/once.go index fa098b26..b5989189 100644 --- a/pkg/handler/once.go +++ b/pkg/handler/once.go @@ -2,12 +2,15 @@ package handler import ( "context" + "fmt" + "os" "strings" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/cli-runtime/pkg/genericiooptions" "k8s.io/client-go/kubernetes" - "k8s.io/client-go/util/retry" + "k8s.io/kubectl/pkg/cmd/rollout" cmdutil "k8s.io/kubectl/pkg/cmd/util" "github.com/wencaiwulue/kubevpn/v2/pkg/config" @@ -15,12 +18,12 @@ import ( "github.com/wencaiwulue/kubevpn/v2/pkg/util" ) -func Once(ctx context.Context, factory cmdutil.Factory) error { - clientset, err := factory.KubernetesClientSet() +func Once(ctx context.Context, f cmdutil.Factory) error { + clientset, err := f.KubernetesClientSet() if err != nil { return err } - namespace, _, err := factory.ToRawKubeConfigLoader().Namespace() + namespace, _, err := f.ToRawKubeConfigLoader().Namespace() if err != nil { return err } @@ -32,11 +35,11 @@ func Once(ctx context.Context, factory cmdutil.Factory) error { if err != nil { return err } - err = restartDeployment(ctx, namespace, clientset) + err = restartDeploy(ctx, f) if err != nil { return err } - err = getCIDR(ctx, factory) + err = getCIDR(ctx, f) if err != nil { return err } @@ -103,39 +106,29 @@ func genTLS(ctx context.Context, namespace string, clientset *kubernetes.Clients return nil } -func restartDeployment(ctx context.Context, namespace string, clientset *kubernetes.Clientset) error { +func restartDeploy(ctx context.Context, f cmdutil.Factory) error { deployName := config.ConfigMapPodTrafficManager plog.G(ctx).Infof("Restarting Deployment %s", deployName) - err := retry.RetryOnConflict(retry.DefaultRetry, func() error { - return scaleDeploy(ctx, namespace, clientset, deployName, 0) + o := rollout.NewRolloutRestartOptions(genericiooptions.IOStreams{ + In: os.Stdin, + Out: os.Stdout, + ErrOut: os.Stderr, }) + err := o.Complete(f, nil, []string{fmt.Sprintf("deploy/%s", deployName)}) if err != nil { return err } - err = retry.RetryOnConflict(retry.DefaultRetry, func() error { - return scaleDeploy(ctx, namespace, clientset, deployName, 1) - }) + err = o.Validate() + if err != nil { + return err + } + err = o.RunRestart() if err != nil { return err } return nil } -func scaleDeploy(ctx context.Context, namespace string, clientset *kubernetes.Clientset, deployName string, replicas int32) error { - scale, err := clientset.AppsV1().Deployments(namespace).GetScale(ctx, deployName, metav1.GetOptions{}) - if err != nil { - plog.G(ctx).Errorf("Failed to get scale: %v", err) - return err - } - scale.Spec.Replicas = replicas - scale, err = clientset.AppsV1().Deployments(namespace).UpdateScale(ctx, deployName, scale, metav1.UpdateOptions{}) - if err != nil { - plog.G(ctx).Errorf("Failed to update scale: %v", err) - return err - } - return err -} - func getCIDR(ctx context.Context, factory cmdutil.Factory) error { plog.G(ctx).Infof("Getting CIDR") c := &ConnectOptions{ diff --git a/pkg/handler/sync.go b/pkg/handler/sync.go index 4b739c46..1844e80a 100644 --- a/pkg/handler/sync.go +++ b/pkg/handler/sync.go @@ -263,7 +263,7 @@ func (d *SyncOptions) DoSync(ctx context.Context, kubeconfigJsonBytes []byte, im if err != nil { return err } - _ = util.RolloutStatus(ctx, d.factory, d.Namespace, workload, time.Minute*60) + _ = util.RolloutStatus(ctx, d.factory, d.Namespace, workload) if d.LocalDir != "" { err = d.SyncDir(ctx, fields.SelectorFromSet(labelsMap).String()) @@ -493,7 +493,7 @@ func (d *SyncOptions) Cleanup(ctx context.Context, workloads ...string) error { } for _, workload := range d.Workloads { plog.G(ctx).Infof("Wait workload %s", workload) - err := util.RolloutStatus(ctx, d.factory, d.Namespace, workload, time.Minute*60) + err := util.RolloutStatus(ctx, d.factory, d.Namespace, workload) if err != nil { plog.G(ctx).Warnf("Failed to rollback workload %s: %v", workload, err) } diff --git a/pkg/inject/fargate.go b/pkg/inject/fargate.go index 14d9e338..95ab158d 100644 --- a/pkg/inject/fargate.go +++ b/pkg/inject/fargate.go @@ -5,7 +5,6 @@ import ( "fmt" "net/netip" "strings" - "time" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -88,7 +87,7 @@ func InjectEnvoyAndSSH(ctx context.Context, nodeID string, f cmdutil.Factory, ma return err } plog.G(ctx).Infof("Patching workload %s", workload) - err = util.RolloutStatus(ctx, f, object.Namespace, workload, time.Minute*60) + err = util.RolloutStatus(ctx, f, object.Namespace, workload) if err != nil { return err } diff --git a/pkg/inject/mesh.go b/pkg/inject/mesh.go index 39dafad0..5bcc736b 100644 --- a/pkg/inject/mesh.go +++ b/pkg/inject/mesh.go @@ -7,7 +7,6 @@ import ( "reflect" "sort" "strings" - "time" "github.com/pkg/errors" v1 "k8s.io/api/core/v1" @@ -117,7 +116,7 @@ func InjectServiceMesh(ctx context.Context, nodeID string, f cmdutil.Factory, ma return err } plog.G(ctx).Infof("Patching workload %s", workload) - err = util.RolloutStatus(ctx, f, object.Namespace, workload, time.Minute*60) + err = util.RolloutStatus(ctx, f, object.Namespace, workload) return err } diff --git a/pkg/inject/proxy.go b/pkg/inject/proxy.go index 25f371af..adda27ea 100644 --- a/pkg/inject/proxy.go +++ b/pkg/inject/proxy.go @@ -80,7 +80,7 @@ func InjectVPN(ctx context.Context, nodeID string, f util.Factory, managerNamesp return err } } - err = util2.RolloutStatus(ctx, f, object.Namespace, workload, time.Minute*60) + err = util2.RolloutStatus(ctx, f, object.Namespace, workload) return err } diff --git a/pkg/util/file.go b/pkg/util/file.go index e7c0edb8..3233d887 100644 --- a/pkg/util/file.go +++ b/pkg/util/file.go @@ -122,10 +122,10 @@ func ParseDirMapping(dir string) (local, remote string, err error) { } func CleanupTempKubeConfigFile() error { - return filepath.Walk(config.GetTempPath(), func(path string, info fs.FileInfo, err error) error { - if strings.HasSuffix(path, ".kubeconfig") { - return os.Remove(path) + return filepath.WalkDir(config.GetTempPath(), func(path string, info fs.DirEntry, err error) error { + if info.IsDir() { + return nil } - return err + return os.Remove(path) }) } diff --git a/pkg/util/pod.go b/pkg/util/pod.go index 09be98ad..1843c69f 100644 --- a/pkg/util/pod.go +++ b/pkg/util/pod.go @@ -13,8 +13,6 @@ import ( "text/tabwriter" "time" - "github.com/distribution/reference" - "github.com/hashicorp/go-version" "github.com/moby/term" "github.com/pkg/errors" corev1 "k8s.io/api/core/v1" @@ -24,7 +22,6 @@ import ( "k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/watch" "k8s.io/cli-runtime/pkg/genericiooptions" - "k8s.io/cli-runtime/pkg/resource" "k8s.io/client-go/kubernetes" v12 "k8s.io/client-go/kubernetes/typed/core/v1" "k8s.io/client-go/rest" @@ -33,10 +30,7 @@ import ( "k8s.io/client-go/util/retry" "k8s.io/kubectl/pkg/cmd/exec" "k8s.io/kubectl/pkg/cmd/util" - "k8s.io/kubectl/pkg/polymorphichelpers" - scheme2 "k8s.io/kubectl/pkg/scheme" "k8s.io/kubectl/pkg/util/podutils" - pkgclient "sigs.k8s.io/controller-runtime/pkg/client" "github.com/wencaiwulue/kubevpn/v2/pkg/config" plog "github.com/wencaiwulue/kubevpn/v2/pkg/log" @@ -77,28 +71,6 @@ func PrintStatus(pod *corev1.Pod, writer io.Writer) { } } -func PrintStatusInline(pod *corev1.Pod) string { - var sb = bytes.NewBuffer(nil) - w := tabwriter.NewWriter(sb, 1, 1, 1, ' ', 0) - show := func(v1, v2 any) { - _, _ = fmt.Fprintf(w, "%v\t\t%v", v1, v2) - } - - for _, status := range pod.Status.ContainerStatuses { - if status.State.Waiting != nil { - show(status.State.Waiting.Reason, status.State.Waiting.Message) - } - if status.State.Running != nil { - show("ContainerRunning", "") - } - if status.State.Terminated != nil { - show(status.State.Terminated.Reason, status.State.Terminated.Message) - } - } - _ = w.Flush() - return sb.String() -} - func GetEnv(ctx context.Context, set *kubernetes.Clientset, config *rest.Config, ns, podName string) (map[string]string, error) { pod, err := set.CoreV1().Pods(ns).Get(ctx, podName, v1.GetOptions{}) if err != nil { @@ -304,30 +276,6 @@ func CheckPodStatus(ctx context.Context, cancelFunc context.CancelFunc, podName } } -func Rollback(f util.Factory, ns, workload string) { - r := f.NewBuilder(). - WithScheme(scheme2.Scheme, scheme2.Scheme.PrioritizedVersionsAllGroups()...). - NamespaceParam(ns).DefaultNamespace(). - ResourceTypeOrNameArgs(true, workload). - ContinueOnError(). - Latest(). - Flatten(). - Do() - if r.Err() == nil { - _ = r.Visit(func(info *resource.Info, err error) error { - if err != nil { - return err - } - rollbacker, err := polymorphichelpers.RollbackerFn(f, info.ResourceMapping()) - if err != nil { - return err - } - _, err = rollbacker.Rollback(info.Object, nil, 0, util.DryRunNone) - return err - }) - } -} - func GetRunningPodList(ctx context.Context, clientset *kubernetes.Clientset, ns string, labelSelector string) ([]corev1.Pod, error) { list, err := clientset.CoreV1().Pods(ns).List(ctx, v1.ListOptions{ LabelSelector: labelSelector, @@ -347,67 +295,6 @@ func GetRunningPodList(ctx context.Context, clientset *kubernetes.Clientset, ns return list.Items, nil } -// UpdateImage update to newer image -func UpdateImage(ctx context.Context, factory util.Factory, ns string, deployName string, image string) error { - clientSet, err2 := factory.KubernetesClientSet() - if err2 != nil { - return err2 - } - deployment, err := clientSet.AppsV1().Deployments(ns).Get(ctx, deployName, v1.GetOptions{}) - if err != nil { - return err - } - origin := deployment.DeepCopy() - newImg, err := reference.ParseNormalizedNamed(image) - if err != nil { - return err - } - newTag, ok := newImg.(reference.NamedTagged) - if !ok { - return nil - } - oldImg, err := reference.ParseNormalizedNamed(deployment.Spec.Template.Spec.Containers[0].Image) - if err != nil { - return err - } - var oldTag reference.NamedTagged - oldTag, ok = oldImg.(reference.NamedTagged) - if !ok { - return nil - } - if reference.Domain(newImg) != reference.Domain(oldImg) { - return nil - } - var oldVersion, newVersion *version.Version - oldVersion, err = version.NewVersion(oldTag.Tag()) - if err != nil { - return nil - } - newVersion, err = version.NewVersion(newTag.Tag()) - if err != nil { - return nil - } - if oldVersion.GreaterThanOrEqual(newVersion) { - return nil - } - - plog.G(ctx).Infof("Found newer image %s, set image from %s to it...", image, deployment.Spec.Template.Spec.Containers[0].Image) - for i := range deployment.Spec.Template.Spec.Containers { - deployment.Spec.Template.Spec.Containers[i].Image = image - } - p := pkgclient.MergeFrom(deployment) - data, err := pkgclient.MergeFrom(origin).Data(deployment) - if err != nil { - return err - } - _, err = clientSet.AppsV1().Deployments(ns).Patch(ctx, deployName, p.Type(), data, v1.PatchOptions{}) - if err != nil { - return err - } - err = RolloutStatus(ctx, factory, ns, fmt.Sprintf("deployments/%s", deployName), time.Minute*60) - return err -} - func DetectPodSupportIPv6(ctx context.Context, factory util.Factory, namespace string) (bool, error) { clientSet, err := factory.KubernetesClientSet() if err != nil { diff --git a/pkg/util/util.go b/pkg/util/util.go index c4ecd9a1..bc199593 100644 --- a/pkg/util/util.go +++ b/pkg/util/util.go @@ -14,8 +14,8 @@ import ( "runtime" "strings" "syscall" - "time" + "github.com/spf13/cobra" rbacv1 "k8s.io/api/rbac/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -23,11 +23,13 @@ import ( k8sruntime "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/watch" + "k8s.io/cli-runtime/pkg/genericiooptions" "k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/tools/cache" watchtools "k8s.io/client-go/tools/watch" "k8s.io/client-go/util/retry" + "k8s.io/kubectl/pkg/cmd/rollout" cmdutil "k8s.io/kubectl/pkg/cmd/util" "k8s.io/kubectl/pkg/polymorphichelpers" @@ -39,17 +41,30 @@ func IsWindows() bool { return runtime.GOOS == "windows" } -func RolloutStatus(ctx1 context.Context, factory cmdutil.Factory, ns, workloads string, timeout time.Duration) (err error) { +// RolloutStatus not use kubectl rollout options is this method can use context to cancel +func RolloutStatus(ctx1 context.Context, f cmdutil.Factory, ns, workloads string) (err error) { plog.GetLogger(ctx1).Infof("Checking rollout status for %s", workloads) defer func() { if err != nil { plog.G(ctx1).Errorf("Rollout status for %s failed: %s", workloads, err.Error()) + out := plog.GetLogger(ctx1).Out + streams := genericiooptions.IOStreams{ + In: os.Stdin, + Out: out, + ErrOut: out, + } + o := rollout.NewRolloutUndoOptions(streams) + cmd := &cobra.Command{} + cmdutil.AddDryRunFlag(cmd) + _ = o.Complete(f, cmd, []string{workloads}) + _ = o.Validate() + _ = o.RunUndo() } else { plog.G(ctx1).Infof("Rollout successfully for %s", workloads) } }() - client, _ := factory.DynamicClient() - r := factory.NewBuilder(). + client, _ := f.DynamicClient() + r := f.NewBuilder(). WithScheme(scheme.Scheme, scheme.Scheme.PrioritizedVersionsAllGroups()...). NamespaceParam(ns).DefaultNamespace(). ResourceTypeOrNameArgs(true, workloads). @@ -89,7 +104,7 @@ func RolloutStatus(ctx1 context.Context, factory cmdutil.Factory, ns, workloads } // if the rollout isn't done yet, keep watching deployment status - ctx, cancel := watchtools.ContextWithOptionalTimeout(ctx1, timeout) + ctx, cancel := context.WithCancel(ctx1) defer cancel() return func() error { _, err = watchtools.UntilWithSync(ctx, lw, &unstructured.Unstructured{}, nil, func(e watch.Event) (bool, error) { diff --git a/vendor/github.com/lithammer/dedent/.travis.yml b/vendor/github.com/lithammer/dedent/.travis.yml new file mode 100644 index 00000000..bc035e5e --- /dev/null +++ b/vendor/github.com/lithammer/dedent/.travis.yml @@ -0,0 +1,11 @@ +language: go + +go: + - "1.6" + - "1.7" + - "1.8" + - "1.9" + - "1.10" + - "1.11" + +sudo: false diff --git a/vendor/github.com/lithammer/dedent/LICENSE b/vendor/github.com/lithammer/dedent/LICENSE new file mode 100644 index 00000000..5da0fc61 --- /dev/null +++ b/vendor/github.com/lithammer/dedent/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2018 Peter Lithammer + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/github.com/lithammer/dedent/README.md b/vendor/github.com/lithammer/dedent/README.md new file mode 100644 index 00000000..34926a1c --- /dev/null +++ b/vendor/github.com/lithammer/dedent/README.md @@ -0,0 +1,52 @@ +# Dedent + +[![Build Status](https://travis-ci.org/lithammer/dedent.svg?branch=master)](https://travis-ci.org/lithammer/dedent) +[![Godoc](https://img.shields.io/badge/godoc-reference-blue.svg?style=flat)](https://godoc.org/github.com/lithammer/dedent) + +Removes common leading whitespace from multiline strings. Inspired by [`textwrap.dedent`](https://docs.python.org/3/library/textwrap.html#textwrap.dedent) in Python. + +## Usage / example + +Imagine the following snippet that prints a multiline string. You want the indentation to both look nice in the code as well as in the actual output. + +```go +package main + +import ( + "fmt" + + "github.com/lithammer/dedent" +) + +func main() { + s := ` + Lorem ipsum dolor sit amet, + consectetur adipiscing elit. + Curabitur justo tellus, facilisis nec efficitur dictum, + fermentum vitae ligula. Sed eu convallis sapien.` + fmt.Println(Dedent(s)) + fmt.Println("-------------") + fmt.Println(s) +} +``` + +To illustrate the difference, here's the output: + + +```bash +$ go run main.go +Lorem ipsum dolor sit amet, +consectetur adipiscing elit. +Curabitur justo tellus, facilisis nec efficitur dictum, +fermentum vitae ligula. Sed eu convallis sapien. +------------- + + Lorem ipsum dolor sit amet, + consectetur adipiscing elit. + Curabitur justo tellus, facilisis nec efficitur dictum, + fermentum vitae ligula. Sed eu convallis sapien. +``` + +## License + +MIT diff --git a/vendor/github.com/lithammer/dedent/dedent.go b/vendor/github.com/lithammer/dedent/dedent.go new file mode 100644 index 00000000..9d5bfbab --- /dev/null +++ b/vendor/github.com/lithammer/dedent/dedent.go @@ -0,0 +1,49 @@ +package dedent + +import ( + "regexp" + "strings" +) + +var ( + whitespaceOnly = regexp.MustCompile("(?m)^[ \t]+$") + leadingWhitespace = regexp.MustCompile("(?m)(^[ \t]*)(?:[^ \t\n])") +) + +// Dedent removes any common leading whitespace from every line in text. +// +// This can be used to make multiline strings to line up with the left edge of +// the display, while still presenting them in the source code in indented +// form. +func Dedent(text string) string { + var margin string + + text = whitespaceOnly.ReplaceAllString(text, "") + indents := leadingWhitespace.FindAllStringSubmatch(text, -1) + + // Look for the longest leading string of spaces and tabs common to all + // lines. + for i, indent := range indents { + if i == 0 { + margin = indent[1] + } else if strings.HasPrefix(indent[1], margin) { + // Current line more deeply indented than previous winner: + // no change (previous winner is still on top). + continue + } else if strings.HasPrefix(margin, indent[1]) { + // Current line consistent with and no deeper than previous winner: + // it's the new winner. + margin = indent[1] + } else { + // Current line and previous winner have no common whitespace: + // there is no margin. + margin = "" + break + } + } + + if margin != "" { + text = regexp.MustCompile("(?m)^"+margin).ReplaceAllString(text, "") + } + return text +} diff --git a/vendor/k8s.io/kubectl/pkg/cmd/rollout/rollout.go b/vendor/k8s.io/kubectl/pkg/cmd/rollout/rollout.go new file mode 100644 index 00000000..2ecbe482 --- /dev/null +++ b/vendor/k8s.io/kubectl/pkg/cmd/rollout/rollout.go @@ -0,0 +1,75 @@ +/* +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 rollout + +import ( + "github.com/lithammer/dedent" + "github.com/spf13/cobra" + + "k8s.io/cli-runtime/pkg/genericiooptions" + + cmdutil "k8s.io/kubectl/pkg/cmd/util" + "k8s.io/kubectl/pkg/util/i18n" + "k8s.io/kubectl/pkg/util/templates" +) + +var ( + rolloutLong = templates.LongDesc(i18n.T(` + Manage the rollout of one or many resources.`) + rolloutValidResources) + + rolloutExample = templates.Examples(` + # Rollback to the previous deployment + kubectl rollout undo deployment/abc + + # Check the rollout status of a daemonset + kubectl rollout status daemonset/foo + + # Restart a deployment + kubectl rollout restart deployment/abc + + # Restart deployments with the 'app=nginx' label + kubectl rollout restart deployment --selector=app=nginx`) + + rolloutValidResources = dedent.Dedent(` + Valid resource types include: + + * deployments + * daemonsets + * statefulsets + `) +) + +// NewCmdRollout returns a Command instance for 'rollout' sub command +func NewCmdRollout(f cmdutil.Factory, streams genericiooptions.IOStreams) *cobra.Command { + cmd := &cobra.Command{ + Use: "rollout SUBCOMMAND", + DisableFlagsInUseLine: true, + Short: i18n.T("Manage the rollout of a resource"), + Long: rolloutLong, + Example: rolloutExample, + Run: cmdutil.DefaultSubCommandRun(streams.Out), + } + // subcommands + cmd.AddCommand(NewCmdRolloutHistory(f, streams)) + cmd.AddCommand(NewCmdRolloutPause(f, streams)) + cmd.AddCommand(NewCmdRolloutResume(f, streams)) + cmd.AddCommand(NewCmdRolloutUndo(f, streams)) + cmd.AddCommand(NewCmdRolloutStatus(f, streams)) + cmd.AddCommand(NewCmdRolloutRestart(f, streams)) + + return cmd +} diff --git a/vendor/k8s.io/kubectl/pkg/cmd/rollout/rollout_history.go b/vendor/k8s.io/kubectl/pkg/cmd/rollout/rollout_history.go new file mode 100644 index 00000000..34379a45 --- /dev/null +++ b/vendor/k8s.io/kubectl/pkg/cmd/rollout/rollout_history.go @@ -0,0 +1,223 @@ +/* +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 rollout + +import ( + "fmt" + "sort" + + "github.com/spf13/cobra" + + "k8s.io/cli-runtime/pkg/genericclioptions" + "k8s.io/cli-runtime/pkg/genericiooptions" + "k8s.io/cli-runtime/pkg/printers" + "k8s.io/cli-runtime/pkg/resource" + cmdutil "k8s.io/kubectl/pkg/cmd/util" + "k8s.io/kubectl/pkg/polymorphichelpers" + "k8s.io/kubectl/pkg/scheme" + "k8s.io/kubectl/pkg/util/completion" + "k8s.io/kubectl/pkg/util/i18n" + "k8s.io/kubectl/pkg/util/templates" +) + +var ( + historyLong = templates.LongDesc(i18n.T(` + View previous rollout revisions and configurations.`)) + + historyExample = templates.Examples(` + # View the rollout history of a deployment + kubectl rollout history deployment/abc + + # View the details of daemonset revision 3 + kubectl rollout history daemonset/abc --revision=3`) +) + +// RolloutHistoryOptions holds the options for 'rollout history' sub command +type RolloutHistoryOptions struct { + PrintFlags *genericclioptions.PrintFlags + ToPrinter func(string) (printers.ResourcePrinter, error) + + Revision int64 + + Builder func() *resource.Builder + Resources []string + Namespace string + EnforceNamespace bool + LabelSelector string + + HistoryViewer polymorphichelpers.HistoryViewerFunc + RESTClientGetter genericclioptions.RESTClientGetter + + resource.FilenameOptions + genericiooptions.IOStreams +} + +// NewRolloutHistoryOptions returns an initialized RolloutHistoryOptions instance +func NewRolloutHistoryOptions(streams genericiooptions.IOStreams) *RolloutHistoryOptions { + return &RolloutHistoryOptions{ + PrintFlags: genericclioptions.NewPrintFlags("").WithTypeSetter(scheme.Scheme), + IOStreams: streams, + } +} + +// NewCmdRolloutHistory returns a Command instance for RolloutHistory sub command +func NewCmdRolloutHistory(f cmdutil.Factory, streams genericiooptions.IOStreams) *cobra.Command { + o := NewRolloutHistoryOptions(streams) + + validArgs := []string{"deployment", "daemonset", "statefulset"} + + cmd := &cobra.Command{ + Use: "history (TYPE NAME | TYPE/NAME) [flags]", + DisableFlagsInUseLine: true, + Short: i18n.T("View rollout history"), + Long: historyLong, + Example: historyExample, + ValidArgsFunction: completion.SpecifiedResourceTypeAndNameCompletionFunc(f, validArgs), + Run: func(cmd *cobra.Command, args []string) { + cmdutil.CheckErr(o.Complete(f, cmd, args)) + cmdutil.CheckErr(o.Validate()) + cmdutil.CheckErr(o.Run()) + }, + } + + cmd.Flags().Int64Var(&o.Revision, "revision", o.Revision, "See the details, including podTemplate of the revision specified") + cmdutil.AddLabelSelectorFlagVar(cmd, &o.LabelSelector) + + usage := "identifying the resource to get from a server." + cmdutil.AddFilenameOptionFlags(cmd, &o.FilenameOptions, usage) + + o.PrintFlags.AddFlags(cmd) + + return cmd +} + +// Complete completes al the required options +func (o *RolloutHistoryOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error { + o.Resources = args + + var err error + if o.Namespace, o.EnforceNamespace, err = f.ToRawKubeConfigLoader().Namespace(); err != nil { + return err + } + + o.ToPrinter = func(operation string) (printers.ResourcePrinter, error) { + o.PrintFlags.NamePrintFlags.Operation = operation + return o.PrintFlags.ToPrinter() + } + + o.HistoryViewer = polymorphichelpers.HistoryViewerFn + o.RESTClientGetter = f + o.Builder = f.NewBuilder + + return nil +} + +// Validate makes sure all the provided values for command-line options are valid +func (o *RolloutHistoryOptions) Validate() error { + if len(o.Resources) == 0 && cmdutil.IsFilenameSliceEmpty(o.Filenames, o.Kustomize) { + return fmt.Errorf("required resource not specified") + } + if o.Revision < 0 { + return fmt.Errorf("revision must be a positive integer: %v", o.Revision) + } + + return nil +} + +// Run performs the execution of 'rollout history' sub command +func (o *RolloutHistoryOptions) Run() error { + + r := o.Builder(). + WithScheme(scheme.Scheme, scheme.Scheme.PrioritizedVersionsAllGroups()...). + NamespaceParam(o.Namespace).DefaultNamespace(). + FilenameParam(o.EnforceNamespace, &o.FilenameOptions). + LabelSelectorParam(o.LabelSelector). + ResourceTypeOrNameArgs(true, o.Resources...). + ContinueOnError(). + Latest(). + Flatten(). + Do() + if err := r.Err(); err != nil { + return err + } + + if o.PrintFlags.OutputFlagSpecified() { + printer, err := o.PrintFlags.ToPrinter() + if err != nil { + return err + } + + return r.Visit(func(info *resource.Info, err error) error { + if err != nil { + return err + } + + mapping := info.ResourceMapping() + historyViewer, err := o.HistoryViewer(o.RESTClientGetter, mapping) + if err != nil { + return err + } + historyInfo, err := historyViewer.GetHistory(info.Namespace, info.Name) + if err != nil { + return err + } + + if o.Revision > 0 { + printer.PrintObj(historyInfo[o.Revision], o.Out) + } else { + sortedKeys := make([]int64, 0, len(historyInfo)) + for k := range historyInfo { + sortedKeys = append(sortedKeys, k) + } + sort.Slice(sortedKeys, func(i, j int) bool { return sortedKeys[i] < sortedKeys[j] }) + for _, k := range sortedKeys { + printer.PrintObj(historyInfo[k], o.Out) + } + } + + return nil + }) + } + + return r.Visit(func(info *resource.Info, err error) error { + if err != nil { + return err + } + + mapping := info.ResourceMapping() + historyViewer, err := o.HistoryViewer(o.RESTClientGetter, mapping) + if err != nil { + return err + } + historyInfo, err := historyViewer.ViewHistory(info.Namespace, info.Name, o.Revision) + if err != nil { + return err + } + + withRevision := "" + if o.Revision > 0 { + withRevision = fmt.Sprintf("with revision #%d", o.Revision) + } + + printer, err := o.ToPrinter(fmt.Sprintf("%s\n%s", withRevision, historyInfo)) + if err != nil { + return err + } + + return printer.PrintObj(info.Object, o.Out) + }) +} diff --git a/vendor/k8s.io/kubectl/pkg/cmd/rollout/rollout_pause.go b/vendor/k8s.io/kubectl/pkg/cmd/rollout/rollout_pause.go new file mode 100644 index 00000000..f9d06723 --- /dev/null +++ b/vendor/k8s.io/kubectl/pkg/cmd/rollout/rollout_pause.go @@ -0,0 +1,211 @@ +/* +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 rollout + +import ( + "fmt" + + "github.com/spf13/cobra" + + "k8s.io/apimachinery/pkg/types" + utilerrors "k8s.io/apimachinery/pkg/util/errors" + "k8s.io/cli-runtime/pkg/genericclioptions" + "k8s.io/cli-runtime/pkg/genericiooptions" + "k8s.io/cli-runtime/pkg/printers" + "k8s.io/cli-runtime/pkg/resource" + "k8s.io/kubectl/pkg/cmd/set" + cmdutil "k8s.io/kubectl/pkg/cmd/util" + "k8s.io/kubectl/pkg/polymorphichelpers" + "k8s.io/kubectl/pkg/scheme" + "k8s.io/kubectl/pkg/util/completion" + "k8s.io/kubectl/pkg/util/i18n" + "k8s.io/kubectl/pkg/util/templates" +) + +// PauseOptions is the start of the data required to perform the operation. As new fields are added, add them here instead of +// referencing the cmd.Flags() +type PauseOptions struct { + PrintFlags *genericclioptions.PrintFlags + ToPrinter func(string) (printers.ResourcePrinter, error) + + Pauser polymorphichelpers.ObjectPauserFunc + Builder func() *resource.Builder + Namespace string + EnforceNamespace bool + Resources []string + LabelSelector string + + resource.FilenameOptions + genericiooptions.IOStreams + + fieldManager string +} + +var ( + pauseLong = templates.LongDesc(i18n.T(` + Mark the provided resource as paused. + + Paused resources will not be reconciled by a controller. + Use "kubectl rollout resume" to resume a paused resource. + Currently only deployments support being paused.`)) + + pauseExample = templates.Examples(` + # Mark the nginx deployment as paused + # Any current state of the deployment will continue its function; new updates + # to the deployment will not have an effect as long as the deployment is paused + kubectl rollout pause deployment/nginx`) +) + +// NewCmdRolloutPause returns a Command instance for 'rollout pause' sub command +func NewCmdRolloutPause(f cmdutil.Factory, streams genericiooptions.IOStreams) *cobra.Command { + o := &PauseOptions{ + PrintFlags: genericclioptions.NewPrintFlags("paused").WithTypeSetter(scheme.Scheme), + IOStreams: streams, + } + + validArgs := []string{"deployment"} + + cmd := &cobra.Command{ + Use: "pause RESOURCE", + DisableFlagsInUseLine: true, + Short: i18n.T("Mark the provided resource as paused"), + Long: pauseLong, + Example: pauseExample, + ValidArgsFunction: completion.SpecifiedResourceTypeAndNameCompletionFunc(f, validArgs), + Run: func(cmd *cobra.Command, args []string) { + cmdutil.CheckErr(o.Complete(f, cmd, args)) + cmdutil.CheckErr(o.Validate()) + cmdutil.CheckErr(o.RunPause()) + }, + } + + o.PrintFlags.AddFlags(cmd) + + usage := "identifying the resource to get from a server." + cmdutil.AddFilenameOptionFlags(cmd, &o.FilenameOptions, usage) + cmdutil.AddFieldManagerFlagVar(cmd, &o.fieldManager, "kubectl-rollout") + cmdutil.AddLabelSelectorFlagVar(cmd, &o.LabelSelector) + return cmd +} + +// Complete completes all the required options +func (o *PauseOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error { + o.Pauser = polymorphichelpers.ObjectPauserFn + + var err error + o.Namespace, o.EnforceNamespace, err = f.ToRawKubeConfigLoader().Namespace() + if err != nil { + return err + } + + o.Resources = args + o.Builder = f.NewBuilder + + o.ToPrinter = func(operation string) (printers.ResourcePrinter, error) { + o.PrintFlags.NamePrintFlags.Operation = operation + return o.PrintFlags.ToPrinter() + } + + return nil +} + +func (o *PauseOptions) Validate() error { + if len(o.Resources) == 0 && cmdutil.IsFilenameSliceEmpty(o.Filenames, o.Kustomize) { + return fmt.Errorf("required resource not specified") + } + return nil +} + +// RunPause performs the execution of 'rollout pause' sub command +func (o *PauseOptions) RunPause() error { + r := o.Builder(). + WithScheme(scheme.Scheme, scheme.Scheme.PrioritizedVersionsAllGroups()...). + NamespaceParam(o.Namespace).DefaultNamespace(). + LabelSelectorParam(o.LabelSelector). + FilenameParam(o.EnforceNamespace, &o.FilenameOptions). + ResourceTypeOrNameArgs(true, o.Resources...). + ContinueOnError(). + Latest(). + Flatten(). + Do() + if err := r.Err(); err != nil { + return err + } + + allErrs := []error{} + infos, err := r.Infos() + if err != nil { + // restore previous command behavior where + // an error caused by retrieving infos due to + // at least a single broken object did not result + // in an immediate return, but rather an overall + // aggregation of errors. + allErrs = append(allErrs, err) + } + + patches := set.CalculatePatches(infos, scheme.DefaultJSONEncoder(), set.PatchFn(o.Pauser)) + + if len(patches) == 0 && len(allErrs) == 0 { + fmt.Fprintf(o.ErrOut, "No resources found in %s namespace.\n", o.Namespace) + return nil + } + + for _, patch := range patches { + info := patch.Info + + if patch.Err != nil { + resourceString := info.Mapping.Resource.Resource + if len(info.Mapping.Resource.Group) > 0 { + resourceString = resourceString + "." + info.Mapping.Resource.Group + } + allErrs = append(allErrs, fmt.Errorf("error: %s %q %v", resourceString, info.Name, patch.Err)) + continue + } + + if string(patch.Patch) == "{}" || len(patch.Patch) == 0 { + printer, err := o.ToPrinter("already paused") + if err != nil { + allErrs = append(allErrs, err) + continue + } + if err = printer.PrintObj(info.Object, o.Out); err != nil { + allErrs = append(allErrs, err) + } + continue + } + + obj, err := resource.NewHelper(info.Client, info.Mapping). + WithFieldManager(o.fieldManager). + Patch(info.Namespace, info.Name, types.StrategicMergePatchType, patch.Patch, nil) + if err != nil { + allErrs = append(allErrs, fmt.Errorf("failed to patch: %v", err)) + continue + } + + info.Refresh(obj, true) + printer, err := o.ToPrinter("paused") + if err != nil { + allErrs = append(allErrs, err) + continue + } + if err = printer.PrintObj(info.Object, o.Out); err != nil { + allErrs = append(allErrs, err) + } + } + + return utilerrors.NewAggregate(allErrs) +} diff --git a/vendor/k8s.io/kubectl/pkg/cmd/rollout/rollout_restart.go b/vendor/k8s.io/kubectl/pkg/cmd/rollout/rollout_restart.go new file mode 100644 index 00000000..6b03fd7b --- /dev/null +++ b/vendor/k8s.io/kubectl/pkg/cmd/rollout/rollout_restart.go @@ -0,0 +1,215 @@ +/* +Copyright 2019 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 rollout + +import ( + "fmt" + + "github.com/spf13/cobra" + + "k8s.io/apimachinery/pkg/types" + utilerrors "k8s.io/apimachinery/pkg/util/errors" + "k8s.io/cli-runtime/pkg/genericclioptions" + "k8s.io/cli-runtime/pkg/genericiooptions" + "k8s.io/cli-runtime/pkg/printers" + "k8s.io/cli-runtime/pkg/resource" + "k8s.io/kubectl/pkg/cmd/set" + cmdutil "k8s.io/kubectl/pkg/cmd/util" + "k8s.io/kubectl/pkg/polymorphichelpers" + "k8s.io/kubectl/pkg/scheme" + "k8s.io/kubectl/pkg/util/completion" + "k8s.io/kubectl/pkg/util/i18n" + "k8s.io/kubectl/pkg/util/templates" +) + +// RestartOptions is the start of the data required to perform the operation. As new fields are added, add them here instead of +// referencing the cmd.Flags() +type RestartOptions struct { + PrintFlags *genericclioptions.PrintFlags + ToPrinter func(string) (printers.ResourcePrinter, error) + + Resources []string + + Builder func() *resource.Builder + Restarter polymorphichelpers.ObjectRestarterFunc + Namespace string + EnforceNamespace bool + LabelSelector string + + resource.FilenameOptions + genericiooptions.IOStreams + + fieldManager string +} + +var ( + restartLong = templates.LongDesc(i18n.T(` + Restart a resource. + + Resource rollout will be restarted.`)) + + restartExample = templates.Examples(` + # Restart all deployments in the test-namespace namespace + kubectl rollout restart deployment -n test-namespace + + # Restart a deployment + kubectl rollout restart deployment/nginx + + # Restart a daemon set + kubectl rollout restart daemonset/abc + + # Restart deployments with the app=nginx label + kubectl rollout restart deployment --selector=app=nginx`) +) + +// NewRolloutRestartOptions returns an initialized RestartOptions instance +func NewRolloutRestartOptions(streams genericiooptions.IOStreams) *RestartOptions { + return &RestartOptions{ + PrintFlags: genericclioptions.NewPrintFlags("restarted").WithTypeSetter(scheme.Scheme), + IOStreams: streams, + } +} + +// NewCmdRolloutRestart returns a Command instance for 'rollout restart' sub command +func NewCmdRolloutRestart(f cmdutil.Factory, streams genericiooptions.IOStreams) *cobra.Command { + o := NewRolloutRestartOptions(streams) + + validArgs := []string{"deployment", "daemonset", "statefulset"} + + cmd := &cobra.Command{ + Use: "restart RESOURCE", + DisableFlagsInUseLine: true, + Short: i18n.T("Restart a resource"), + Long: restartLong, + Example: restartExample, + ValidArgsFunction: completion.SpecifiedResourceTypeAndNameCompletionFunc(f, validArgs), + Run: func(cmd *cobra.Command, args []string) { + cmdutil.CheckErr(o.Complete(f, cmd, args)) + cmdutil.CheckErr(o.Validate()) + cmdutil.CheckErr(o.RunRestart()) + }, + } + + usage := "identifying the resource to get from a server." + cmdutil.AddFilenameOptionFlags(cmd, &o.FilenameOptions, usage) + cmdutil.AddFieldManagerFlagVar(cmd, &o.fieldManager, "kubectl-rollout") + cmdutil.AddLabelSelectorFlagVar(cmd, &o.LabelSelector) + o.PrintFlags.AddFlags(cmd) + return cmd +} + +// Complete completes all the required options +func (o *RestartOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error { + o.Resources = args + + o.Restarter = polymorphichelpers.ObjectRestarterFn + + var err error + o.Namespace, o.EnforceNamespace, err = f.ToRawKubeConfigLoader().Namespace() + if err != nil { + return err + } + + o.ToPrinter = func(operation string) (printers.ResourcePrinter, error) { + o.PrintFlags.NamePrintFlags.Operation = operation + return o.PrintFlags.ToPrinter() + } + + o.Builder = f.NewBuilder + + return nil +} + +func (o *RestartOptions) Validate() error { + if len(o.Resources) == 0 && cmdutil.IsFilenameSliceEmpty(o.Filenames, o.Kustomize) { + return fmt.Errorf("required resource not specified") + } + return nil +} + +// RunRestart performs the execution of 'rollout restart' sub command +func (o RestartOptions) RunRestart() error { + r := o.Builder(). + WithScheme(scheme.Scheme, scheme.Scheme.PrioritizedVersionsAllGroups()...). + NamespaceParam(o.Namespace).DefaultNamespace(). + FilenameParam(o.EnforceNamespace, &o.FilenameOptions). + LabelSelectorParam(o.LabelSelector). + ResourceTypeOrNameArgs(true, o.Resources...). + ContinueOnError(). + Latest(). + Flatten(). + Do() + if err := r.Err(); err != nil { + return err + } + + allErrs := []error{} + infos, err := r.Infos() + if err != nil { + // restore previous command behavior where + // an error caused by retrieving infos due to + // at least a single broken object did not result + // in an immediate return, but rather an overall + // aggregation of errors. + allErrs = append(allErrs, err) + } + + patches := set.CalculatePatches(infos, scheme.DefaultJSONEncoder(), set.PatchFn(o.Restarter)) + + if len(patches) == 0 && len(allErrs) == 0 { + fmt.Fprintf(o.ErrOut, "No resources found in %s namespace.\n", o.Namespace) + return nil + } + + for _, patch := range patches { + info := patch.Info + + if patch.Err != nil { + resourceString := info.Mapping.Resource.Resource + if len(info.Mapping.Resource.Group) > 0 { + resourceString = resourceString + "." + info.Mapping.Resource.Group + } + allErrs = append(allErrs, fmt.Errorf("error: %s %q %v", resourceString, info.Name, patch.Err)) + continue + } + + if string(patch.Patch) == "{}" || len(patch.Patch) == 0 { + allErrs = append(allErrs, fmt.Errorf("failed to create patch for %v: if restart has already been triggered within the past second, please wait before attempting to trigger another", info.Name)) + continue + } + + obj, err := resource.NewHelper(info.Client, info.Mapping). + WithFieldManager(o.fieldManager). + Patch(info.Namespace, info.Name, types.StrategicMergePatchType, patch.Patch, nil) + if err != nil { + allErrs = append(allErrs, fmt.Errorf("failed to patch: %v", err)) + continue + } + + info.Refresh(obj, true) + printer, err := o.ToPrinter("restarted") + if err != nil { + allErrs = append(allErrs, err) + continue + } + if err = printer.PrintObj(info.Object, o.Out); err != nil { + allErrs = append(allErrs, err) + } + } + + return utilerrors.NewAggregate(allErrs) +} diff --git a/vendor/k8s.io/kubectl/pkg/cmd/rollout/rollout_resume.go b/vendor/k8s.io/kubectl/pkg/cmd/rollout/rollout_resume.go new file mode 100644 index 00000000..b9519ef4 --- /dev/null +++ b/vendor/k8s.io/kubectl/pkg/cmd/rollout/rollout_resume.go @@ -0,0 +1,215 @@ +/* +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 rollout + +import ( + "fmt" + + "github.com/spf13/cobra" + + "k8s.io/apimachinery/pkg/types" + utilerrors "k8s.io/apimachinery/pkg/util/errors" + "k8s.io/cli-runtime/pkg/genericclioptions" + "k8s.io/cli-runtime/pkg/genericiooptions" + "k8s.io/cli-runtime/pkg/printers" + "k8s.io/cli-runtime/pkg/resource" + "k8s.io/kubectl/pkg/cmd/set" + cmdutil "k8s.io/kubectl/pkg/cmd/util" + "k8s.io/kubectl/pkg/polymorphichelpers" + "k8s.io/kubectl/pkg/scheme" + "k8s.io/kubectl/pkg/util/completion" + "k8s.io/kubectl/pkg/util/i18n" + "k8s.io/kubectl/pkg/util/templates" +) + +// ResumeOptions is the start of the data required to perform the operation. As new fields are added, add them here instead of +// referencing the cmd.Flags() +type ResumeOptions struct { + PrintFlags *genericclioptions.PrintFlags + ToPrinter func(string) (printers.ResourcePrinter, error) + + Resources []string + + Builder func() *resource.Builder + Resumer polymorphichelpers.ObjectResumerFunc + Namespace string + EnforceNamespace bool + LabelSelector string + + resource.FilenameOptions + genericiooptions.IOStreams + + fieldManager string +} + +var ( + resumeLong = templates.LongDesc(i18n.T(` + Resume a paused resource. + + Paused resources will not be reconciled by a controller. By resuming a + resource, we allow it to be reconciled again. + Currently only deployments support being resumed.`)) + + resumeExample = templates.Examples(` + # Resume an already paused deployment + kubectl rollout resume deployment/nginx`) +) + +// NewRolloutResumeOptions returns an initialized ResumeOptions instance +func NewRolloutResumeOptions(streams genericiooptions.IOStreams) *ResumeOptions { + return &ResumeOptions{ + PrintFlags: genericclioptions.NewPrintFlags("resumed").WithTypeSetter(scheme.Scheme), + IOStreams: streams, + } +} + +// NewCmdRolloutResume returns a Command instance for 'rollout resume' sub command +func NewCmdRolloutResume(f cmdutil.Factory, streams genericiooptions.IOStreams) *cobra.Command { + o := NewRolloutResumeOptions(streams) + + validArgs := []string{"deployment"} + + cmd := &cobra.Command{ + Use: "resume RESOURCE", + DisableFlagsInUseLine: true, + Short: i18n.T("Resume a paused resource"), + Long: resumeLong, + Example: resumeExample, + ValidArgsFunction: completion.SpecifiedResourceTypeAndNameCompletionFunc(f, validArgs), + Run: func(cmd *cobra.Command, args []string) { + cmdutil.CheckErr(o.Complete(f, cmd, args)) + cmdutil.CheckErr(o.Validate()) + cmdutil.CheckErr(o.RunResume()) + }, + } + + usage := "identifying the resource to get from a server." + cmdutil.AddFilenameOptionFlags(cmd, &o.FilenameOptions, usage) + cmdutil.AddFieldManagerFlagVar(cmd, &o.fieldManager, "kubectl-rollout") + cmdutil.AddLabelSelectorFlagVar(cmd, &o.LabelSelector) + o.PrintFlags.AddFlags(cmd) + return cmd +} + +// Complete completes all the required options +func (o *ResumeOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error { + o.Resources = args + + o.Resumer = polymorphichelpers.ObjectResumerFn + + var err error + o.Namespace, o.EnforceNamespace, err = f.ToRawKubeConfigLoader().Namespace() + if err != nil { + return err + } + + o.ToPrinter = func(operation string) (printers.ResourcePrinter, error) { + o.PrintFlags.NamePrintFlags.Operation = operation + return o.PrintFlags.ToPrinter() + } + + o.Builder = f.NewBuilder + + return nil +} + +func (o *ResumeOptions) Validate() error { + if len(o.Resources) == 0 && cmdutil.IsFilenameSliceEmpty(o.Filenames, o.Kustomize) { + return fmt.Errorf("required resource not specified") + } + return nil +} + +// RunResume performs the execution of 'rollout resume' sub command +func (o ResumeOptions) RunResume() error { + r := o.Builder(). + WithScheme(scheme.Scheme, scheme.Scheme.PrioritizedVersionsAllGroups()...). + NamespaceParam(o.Namespace).DefaultNamespace(). + LabelSelectorParam(o.LabelSelector). + FilenameParam(o.EnforceNamespace, &o.FilenameOptions). + ResourceTypeOrNameArgs(true, o.Resources...). + ContinueOnError(). + Latest(). + Flatten(). + Do() + if err := r.Err(); err != nil { + return err + } + + allErrs := []error{} + infos, err := r.Infos() + if err != nil { + // restore previous command behavior where + // an error caused by retrieving infos due to + // at least a single broken object did not result + // in an immediate return, but rather an overall + // aggregation of errors. + allErrs = append(allErrs, err) + } + + patches := set.CalculatePatches(infos, scheme.DefaultJSONEncoder(), set.PatchFn(o.Resumer)) + + if len(patches) == 0 && len(allErrs) == 0 { + fmt.Fprintf(o.ErrOut, "No resources found in %s namespace.\n", o.Namespace) + return nil + } + + for _, patch := range patches { + info := patch.Info + + if patch.Err != nil { + resourceString := info.Mapping.Resource.Resource + if len(info.Mapping.Resource.Group) > 0 { + resourceString = resourceString + "." + info.Mapping.Resource.Group + } + allErrs = append(allErrs, fmt.Errorf("error: %s %q %v", resourceString, info.Name, patch.Err)) + continue + } + + if string(patch.Patch) == "{}" || len(patch.Patch) == 0 { + printer, err := o.ToPrinter("already resumed") + if err != nil { + allErrs = append(allErrs, err) + continue + } + if err = printer.PrintObj(info.Object, o.Out); err != nil { + allErrs = append(allErrs, err) + } + continue + } + + obj, err := resource.NewHelper(info.Client, info.Mapping). + WithFieldManager(o.fieldManager). + Patch(info.Namespace, info.Name, types.StrategicMergePatchType, patch.Patch, nil) + if err != nil { + allErrs = append(allErrs, fmt.Errorf("failed to patch: %v", err)) + continue + } + + info.Refresh(obj, true) + printer, err := o.ToPrinter("resumed") + if err != nil { + allErrs = append(allErrs, err) + continue + } + if err = printer.PrintObj(info.Object, o.Out); err != nil { + allErrs = append(allErrs, err) + } + } + + return utilerrors.NewAggregate(allErrs) +} diff --git a/vendor/k8s.io/kubectl/pkg/cmd/rollout/rollout_status.go b/vendor/k8s.io/kubectl/pkg/cmd/rollout/rollout_status.go new file mode 100644 index 00000000..8248c9f1 --- /dev/null +++ b/vendor/k8s.io/kubectl/pkg/cmd/rollout/rollout_status.go @@ -0,0 +1,243 @@ +/* +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 rollout + +import ( + "context" + "fmt" + "time" + + "github.com/spf13/cobra" + + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/fields" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/watch" + "k8s.io/cli-runtime/pkg/genericclioptions" + "k8s.io/cli-runtime/pkg/genericiooptions" + "k8s.io/cli-runtime/pkg/resource" + "k8s.io/client-go/dynamic" + "k8s.io/client-go/tools/cache" + watchtools "k8s.io/client-go/tools/watch" + cmdutil "k8s.io/kubectl/pkg/cmd/util" + "k8s.io/kubectl/pkg/polymorphichelpers" + "k8s.io/kubectl/pkg/scheme" + "k8s.io/kubectl/pkg/util/completion" + "k8s.io/kubectl/pkg/util/i18n" + "k8s.io/kubectl/pkg/util/interrupt" + "k8s.io/kubectl/pkg/util/templates" +) + +var ( + statusLong = templates.LongDesc(i18n.T(` + Show the status of the rollout. + + By default 'rollout status' will watch the status of the latest rollout + until it's done. If you don't want to wait for the rollout to finish then + you can use --watch=false. Note that if a new rollout starts in-between, then + 'rollout status' will continue watching the latest revision. If you want to + pin to a specific revision and abort if it is rolled over by another revision, + use --revision=N where N is the revision you need to watch for.`)) + + statusExample = templates.Examples(` + # Watch the rollout status of a deployment + kubectl rollout status deployment/nginx`) +) + +// RolloutStatusOptions holds the command-line options for 'rollout status' sub command +type RolloutStatusOptions struct { + PrintFlags *genericclioptions.PrintFlags + + Namespace string + EnforceNamespace bool + BuilderArgs []string + LabelSelector string + + Watch bool + Revision int64 + Timeout time.Duration + + StatusViewerFn func(*meta.RESTMapping) (polymorphichelpers.StatusViewer, error) + Builder func() *resource.Builder + DynamicClient dynamic.Interface + + FilenameOptions *resource.FilenameOptions + genericiooptions.IOStreams +} + +// NewRolloutStatusOptions returns an initialized RolloutStatusOptions instance +func NewRolloutStatusOptions(streams genericiooptions.IOStreams) *RolloutStatusOptions { + return &RolloutStatusOptions{ + PrintFlags: genericclioptions.NewPrintFlags("").WithTypeSetter(scheme.Scheme), + FilenameOptions: &resource.FilenameOptions{}, + IOStreams: streams, + Watch: true, + Timeout: 0, + } +} + +// NewCmdRolloutStatus returns a Command instance for the 'rollout status' sub command +func NewCmdRolloutStatus(f cmdutil.Factory, streams genericiooptions.IOStreams) *cobra.Command { + o := NewRolloutStatusOptions(streams) + + validArgs := []string{"deployment", "daemonset", "statefulset"} + + cmd := &cobra.Command{ + Use: "status (TYPE NAME | TYPE/NAME) [flags]", + DisableFlagsInUseLine: true, + Short: i18n.T("Show the status of the rollout"), + Long: statusLong, + Example: statusExample, + ValidArgsFunction: completion.SpecifiedResourceTypeAndNameNoRepeatCompletionFunc(f, validArgs), + Run: func(cmd *cobra.Command, args []string) { + cmdutil.CheckErr(o.Complete(f, args)) + cmdutil.CheckErr(o.Validate()) + cmdutil.CheckErr(o.Run()) + }, + } + + usage := "identifying the resource to get from a server." + cmdutil.AddFilenameOptionFlags(cmd, o.FilenameOptions, usage) + cmd.Flags().BoolVarP(&o.Watch, "watch", "w", o.Watch, "Watch the status of the rollout until it's done.") + cmd.Flags().Int64Var(&o.Revision, "revision", o.Revision, "Pin to a specific revision for showing its status. Defaults to 0 (last revision).") + cmd.Flags().DurationVar(&o.Timeout, "timeout", o.Timeout, "The length of time to wait before ending watch, zero means never. Any other values should contain a corresponding time unit (e.g. 1s, 2m, 3h).") + cmdutil.AddLabelSelectorFlagVar(cmd, &o.LabelSelector) + + return cmd +} + +// Complete completes all the required options +func (o *RolloutStatusOptions) Complete(f cmdutil.Factory, args []string) error { + o.Builder = f.NewBuilder + + var err error + o.Namespace, o.EnforceNamespace, err = f.ToRawKubeConfigLoader().Namespace() + if err != nil { + return err + } + + o.BuilderArgs = args + o.StatusViewerFn = polymorphichelpers.StatusViewerFn + + o.DynamicClient, err = f.DynamicClient() + if err != nil { + return err + } + + return nil +} + +// Validate makes sure all the provided values for command-line options are valid +func (o *RolloutStatusOptions) Validate() error { + if len(o.BuilderArgs) == 0 && cmdutil.IsFilenameSliceEmpty(o.FilenameOptions.Filenames, o.FilenameOptions.Kustomize) { + return fmt.Errorf("required resource not specified") + } + + if o.Revision < 0 { + return fmt.Errorf("revision must be a positive integer: %v", o.Revision) + } + + return nil +} + +// Run performs the execution of 'rollout status' sub command +func (o *RolloutStatusOptions) Run() error { + r := o.Builder(). + WithScheme(scheme.Scheme, scheme.Scheme.PrioritizedVersionsAllGroups()...). + NamespaceParam(o.Namespace).DefaultNamespace(). + LabelSelectorParam(o.LabelSelector). + FilenameParam(o.EnforceNamespace, o.FilenameOptions). + ResourceTypeOrNameArgs(true, o.BuilderArgs...). + ContinueOnError(). + Latest(). + Flatten(). + Do() + + err := r.Err() + if err != nil { + return err + } + + resourceFound := false + err = r.Visit(func(info *resource.Info, _ error) error { + resourceFound = true + mapping := info.ResourceMapping() + statusViewer, err := o.StatusViewerFn(mapping) + if err != nil { + return err + } + + fieldSelector := fields.OneTermEqualSelector("metadata.name", info.Name).String() + lw := &cache.ListWatch{ + ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { + options.FieldSelector = fieldSelector + return o.DynamicClient.Resource(info.Mapping.Resource).Namespace(info.Namespace).List(context.TODO(), options) + }, + WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { + options.FieldSelector = fieldSelector + return o.DynamicClient.Resource(info.Mapping.Resource).Namespace(info.Namespace).Watch(context.TODO(), options) + }, + } + + // if the rollout isn't done yet, keep watching deployment status + ctx, cancel := watchtools.ContextWithOptionalTimeout(context.Background(), o.Timeout) + intr := interrupt.New(nil, cancel) + return intr.Run(func() error { + _, err = watchtools.UntilWithSync(ctx, lw, &unstructured.Unstructured{}, nil, func(e watch.Event) (bool, error) { + switch t := e.Type; t { + case watch.Added, watch.Modified: + status, done, err := statusViewer.Status(e.Object.(runtime.Unstructured), o.Revision) + if err != nil { + return false, err + } + fmt.Fprintf(o.Out, "%s", status) + // Quit waiting if the rollout is done + if done { + return true, nil + } + + shouldWatch := o.Watch + if !shouldWatch { + return true, nil + } + + return false, nil + + case watch.Deleted: + // We need to abort to avoid cases of recreation and not to silently watch the wrong (new) object + return true, fmt.Errorf("object has been deleted") + + default: + return true, fmt.Errorf("internal error: unexpected event %#v", e) + } + }) + return err + }) + }) + + if err != nil { + return err + } + + if !resourceFound { + fmt.Fprintf(o.ErrOut, "No resources found in %s namespace.\n", o.Namespace) + } + + return nil +} diff --git a/vendor/k8s.io/kubectl/pkg/cmd/rollout/rollout_undo.go b/vendor/k8s.io/kubectl/pkg/cmd/rollout/rollout_undo.go new file mode 100644 index 00000000..30ebc99b --- /dev/null +++ b/vendor/k8s.io/kubectl/pkg/cmd/rollout/rollout_undo.go @@ -0,0 +1,179 @@ +/* +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 rollout + +import ( + "fmt" + + "github.com/spf13/cobra" + + "k8s.io/cli-runtime/pkg/genericclioptions" + "k8s.io/cli-runtime/pkg/genericiooptions" + "k8s.io/cli-runtime/pkg/printers" + "k8s.io/cli-runtime/pkg/resource" + cmdutil "k8s.io/kubectl/pkg/cmd/util" + "k8s.io/kubectl/pkg/polymorphichelpers" + "k8s.io/kubectl/pkg/scheme" + "k8s.io/kubectl/pkg/util/completion" + "k8s.io/kubectl/pkg/util/i18n" + "k8s.io/kubectl/pkg/util/templates" +) + +// UndoOptions is the start of the data required to perform the operation. As new fields are added, add them here instead of +// referencing the cmd.Flags() +type UndoOptions struct { + PrintFlags *genericclioptions.PrintFlags + ToPrinter func(string) (printers.ResourcePrinter, error) + + Builder func() *resource.Builder + ToRevision int64 + DryRunStrategy cmdutil.DryRunStrategy + Resources []string + Namespace string + LabelSelector string + EnforceNamespace bool + RESTClientGetter genericclioptions.RESTClientGetter + + resource.FilenameOptions + genericiooptions.IOStreams +} + +var ( + undoLong = templates.LongDesc(i18n.T(` + Roll back to a previous rollout.`)) + + undoExample = templates.Examples(` + # Roll back to the previous deployment + kubectl rollout undo deployment/abc + + # Roll back to daemonset revision 3 + kubectl rollout undo daemonset/abc --to-revision=3 + + # Roll back to the previous deployment with dry-run + kubectl rollout undo --dry-run=server deployment/abc`) +) + +// NewRolloutUndoOptions returns an initialized UndoOptions instance +func NewRolloutUndoOptions(streams genericiooptions.IOStreams) *UndoOptions { + return &UndoOptions{ + PrintFlags: genericclioptions.NewPrintFlags("rolled back").WithTypeSetter(scheme.Scheme), + IOStreams: streams, + ToRevision: int64(0), + } +} + +// NewCmdRolloutUndo returns a Command instance for the 'rollout undo' sub command +func NewCmdRolloutUndo(f cmdutil.Factory, streams genericiooptions.IOStreams) *cobra.Command { + o := NewRolloutUndoOptions(streams) + + validArgs := []string{"deployment", "daemonset", "statefulset"} + + cmd := &cobra.Command{ + Use: "undo (TYPE NAME | TYPE/NAME) [flags]", + DisableFlagsInUseLine: true, + Short: i18n.T("Undo a previous rollout"), + Long: undoLong, + Example: undoExample, + ValidArgsFunction: completion.SpecifiedResourceTypeAndNameCompletionFunc(f, validArgs), + Run: func(cmd *cobra.Command, args []string) { + cmdutil.CheckErr(o.Complete(f, cmd, args)) + cmdutil.CheckErr(o.Validate()) + cmdutil.CheckErr(o.RunUndo()) + }, + } + + cmd.Flags().Int64Var(&o.ToRevision, "to-revision", o.ToRevision, "The revision to rollback to. Default to 0 (last revision).") + usage := "identifying the resource to get from a server." + cmdutil.AddFilenameOptionFlags(cmd, &o.FilenameOptions, usage) + cmdutil.AddDryRunFlag(cmd) + cmdutil.AddLabelSelectorFlagVar(cmd, &o.LabelSelector) + o.PrintFlags.AddFlags(cmd) + return cmd +} + +// Complete completes all the required options +func (o *UndoOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error { + o.Resources = args + var err error + o.DryRunStrategy, err = cmdutil.GetDryRunStrategy(cmd) + if err != nil { + return err + } + + if o.Namespace, o.EnforceNamespace, err = f.ToRawKubeConfigLoader().Namespace(); err != nil { + return err + } + + o.ToPrinter = func(operation string) (printers.ResourcePrinter, error) { + o.PrintFlags.NamePrintFlags.Operation = operation + cmdutil.PrintFlagsWithDryRunStrategy(o.PrintFlags, o.DryRunStrategy) + return o.PrintFlags.ToPrinter() + } + + o.RESTClientGetter = f + o.Builder = f.NewBuilder + + return err +} + +func (o *UndoOptions) Validate() error { + if len(o.Resources) == 0 && cmdutil.IsFilenameSliceEmpty(o.Filenames, o.Kustomize) { + return fmt.Errorf("required resource not specified") + } + return nil +} + +// RunUndo performs the execution of 'rollout undo' sub command +func (o *UndoOptions) RunUndo() error { + r := o.Builder(). + WithScheme(scheme.Scheme, scheme.Scheme.PrioritizedVersionsAllGroups()...). + NamespaceParam(o.Namespace).DefaultNamespace(). + LabelSelectorParam(o.LabelSelector). + FilenameParam(o.EnforceNamespace, &o.FilenameOptions). + ResourceTypeOrNameArgs(true, o.Resources...). + ContinueOnError(). + Latest(). + Flatten(). + Do() + if err := r.Err(); err != nil { + return err + } + + err := r.Visit(func(info *resource.Info, err error) error { + if err != nil { + return err + } + rollbacker, err := polymorphichelpers.RollbackerFn(o.RESTClientGetter, info.ResourceMapping()) + if err != nil { + return err + } + + result, err := rollbacker.Rollback(info.Object, nil, o.ToRevision, o.DryRunStrategy) + if err != nil { + return err + } + + printer, err := o.ToPrinter(result) + if err != nil { + return err + } + + return printer.PrintObj(info.Object, o.Out) + }) + + return err +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 2754a9ab..19a5878c 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -631,6 +631,9 @@ github.com/libp2p/go-netroute # github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de ## explicit github.com/liggitt/tabwriter +# github.com/lithammer/dedent v1.1.0 +## explicit +github.com/lithammer/dedent # github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683 ## explicit; go 1.16 github.com/lufia/plan9stats @@ -1984,6 +1987,7 @@ k8s.io/kubectl/pkg/apps k8s.io/kubectl/pkg/cmd/apiresources k8s.io/kubectl/pkg/cmd/exec k8s.io/kubectl/pkg/cmd/get +k8s.io/kubectl/pkg/cmd/rollout k8s.io/kubectl/pkg/cmd/set k8s.io/kubectl/pkg/cmd/set/env k8s.io/kubectl/pkg/cmd/util