1
16
17 package rollout
18
19 import (
20 "context"
21 "fmt"
22 "time"
23
24 "github.com/spf13/cobra"
25
26 "k8s.io/apimachinery/pkg/api/meta"
27 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
28 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
29 "k8s.io/apimachinery/pkg/fields"
30 "k8s.io/apimachinery/pkg/runtime"
31 "k8s.io/apimachinery/pkg/watch"
32 "k8s.io/cli-runtime/pkg/genericclioptions"
33 "k8s.io/cli-runtime/pkg/genericiooptions"
34 "k8s.io/cli-runtime/pkg/resource"
35 "k8s.io/client-go/dynamic"
36 "k8s.io/client-go/tools/cache"
37 watchtools "k8s.io/client-go/tools/watch"
38 cmdutil "k8s.io/kubectl/pkg/cmd/util"
39 "k8s.io/kubectl/pkg/polymorphichelpers"
40 "k8s.io/kubectl/pkg/scheme"
41 "k8s.io/kubectl/pkg/util/completion"
42 "k8s.io/kubectl/pkg/util/i18n"
43 "k8s.io/kubectl/pkg/util/interrupt"
44 "k8s.io/kubectl/pkg/util/templates"
45 )
46
47 var (
48 statusLong = templates.LongDesc(i18n.T(`
49 Show the status of the rollout.
50
51 By default 'rollout status' will watch the status of the latest rollout
52 until it's done. If you don't want to wait for the rollout to finish then
53 you can use --watch=false. Note that if a new rollout starts in-between, then
54 'rollout status' will continue watching the latest revision. If you want to
55 pin to a specific revision and abort if it is rolled over by another revision,
56 use --revision=N where N is the revision you need to watch for.`))
57
58 statusExample = templates.Examples(`
59 # Watch the rollout status of a deployment
60 kubectl rollout status deployment/nginx`)
61 )
62
63
64 type RolloutStatusOptions struct {
65 PrintFlags *genericclioptions.PrintFlags
66
67 Namespace string
68 EnforceNamespace bool
69 BuilderArgs []string
70 LabelSelector string
71
72 Watch bool
73 Revision int64
74 Timeout time.Duration
75
76 StatusViewerFn func(*meta.RESTMapping) (polymorphichelpers.StatusViewer, error)
77 Builder func() *resource.Builder
78 DynamicClient dynamic.Interface
79
80 FilenameOptions *resource.FilenameOptions
81 genericiooptions.IOStreams
82 }
83
84
85 func NewRolloutStatusOptions(streams genericiooptions.IOStreams) *RolloutStatusOptions {
86 return &RolloutStatusOptions{
87 PrintFlags: genericclioptions.NewPrintFlags("").WithTypeSetter(scheme.Scheme),
88 FilenameOptions: &resource.FilenameOptions{},
89 IOStreams: streams,
90 Watch: true,
91 Timeout: 0,
92 }
93 }
94
95
96 func NewCmdRolloutStatus(f cmdutil.Factory, streams genericiooptions.IOStreams) *cobra.Command {
97 o := NewRolloutStatusOptions(streams)
98
99 validArgs := []string{"deployment", "daemonset", "statefulset"}
100
101 cmd := &cobra.Command{
102 Use: "status (TYPE NAME | TYPE/NAME) [flags]",
103 DisableFlagsInUseLine: true,
104 Short: i18n.T("Show the status of the rollout"),
105 Long: statusLong,
106 Example: statusExample,
107 ValidArgsFunction: completion.SpecifiedResourceTypeAndNameNoRepeatCompletionFunc(f, validArgs),
108 Run: func(cmd *cobra.Command, args []string) {
109 cmdutil.CheckErr(o.Complete(f, args))
110 cmdutil.CheckErr(o.Validate())
111 cmdutil.CheckErr(o.Run())
112 },
113 }
114
115 usage := "identifying the resource to get from a server."
116 cmdutil.AddFilenameOptionFlags(cmd, o.FilenameOptions, usage)
117 cmd.Flags().BoolVarP(&o.Watch, "watch", "w", o.Watch, "Watch the status of the rollout until it's done.")
118 cmd.Flags().Int64Var(&o.Revision, "revision", o.Revision, "Pin to a specific revision for showing its status. Defaults to 0 (last revision).")
119 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).")
120 cmdutil.AddLabelSelectorFlagVar(cmd, &o.LabelSelector)
121
122 return cmd
123 }
124
125
126 func (o *RolloutStatusOptions) Complete(f cmdutil.Factory, args []string) error {
127 o.Builder = f.NewBuilder
128
129 var err error
130 o.Namespace, o.EnforceNamespace, err = f.ToRawKubeConfigLoader().Namespace()
131 if err != nil {
132 return err
133 }
134
135 o.BuilderArgs = args
136 o.StatusViewerFn = polymorphichelpers.StatusViewerFn
137
138 o.DynamicClient, err = f.DynamicClient()
139 if err != nil {
140 return err
141 }
142
143 return nil
144 }
145
146
147 func (o *RolloutStatusOptions) Validate() error {
148 if len(o.BuilderArgs) == 0 && cmdutil.IsFilenameSliceEmpty(o.FilenameOptions.Filenames, o.FilenameOptions.Kustomize) {
149 return fmt.Errorf("required resource not specified")
150 }
151
152 if o.Revision < 0 {
153 return fmt.Errorf("revision must be a positive integer: %v", o.Revision)
154 }
155
156 return nil
157 }
158
159
160 func (o *RolloutStatusOptions) Run() error {
161 r := o.Builder().
162 WithScheme(scheme.Scheme, scheme.Scheme.PrioritizedVersionsAllGroups()...).
163 NamespaceParam(o.Namespace).DefaultNamespace().
164 LabelSelectorParam(o.LabelSelector).
165 FilenameParam(o.EnforceNamespace, o.FilenameOptions).
166 ResourceTypeOrNameArgs(true, o.BuilderArgs...).
167 ContinueOnError().
168 Latest().
169 Flatten().
170 Do()
171
172 err := r.Err()
173 if err != nil {
174 return err
175 }
176
177 resourceFound := false
178 err = r.Visit(func(info *resource.Info, _ error) error {
179 resourceFound = true
180 mapping := info.ResourceMapping()
181 statusViewer, err := o.StatusViewerFn(mapping)
182 if err != nil {
183 return err
184 }
185
186 fieldSelector := fields.OneTermEqualSelector("metadata.name", info.Name).String()
187 lw := &cache.ListWatch{
188 ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
189 options.FieldSelector = fieldSelector
190 return o.DynamicClient.Resource(info.Mapping.Resource).Namespace(info.Namespace).List(context.TODO(), options)
191 },
192 WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
193 options.FieldSelector = fieldSelector
194 return o.DynamicClient.Resource(info.Mapping.Resource).Namespace(info.Namespace).Watch(context.TODO(), options)
195 },
196 }
197
198
199 ctx, cancel := watchtools.ContextWithOptionalTimeout(context.Background(), o.Timeout)
200 intr := interrupt.New(nil, cancel)
201 return intr.Run(func() error {
202 _, err = watchtools.UntilWithSync(ctx, lw, &unstructured.Unstructured{}, nil, func(e watch.Event) (bool, error) {
203 switch t := e.Type; t {
204 case watch.Added, watch.Modified:
205 status, done, err := statusViewer.Status(e.Object.(runtime.Unstructured), o.Revision)
206 if err != nil {
207 return false, err
208 }
209 fmt.Fprintf(o.Out, "%s", status)
210
211 if done {
212 return true, nil
213 }
214
215 shouldWatch := o.Watch
216 if !shouldWatch {
217 return true, nil
218 }
219
220 return false, nil
221
222 case watch.Deleted:
223
224 return true, fmt.Errorf("object has been deleted")
225
226 default:
227 return true, fmt.Errorf("internal error: unexpected event %#v", e)
228 }
229 })
230 return err
231 })
232 })
233
234 if err != nil {
235 return err
236 }
237
238 if !resourceFound {
239 fmt.Fprintf(o.ErrOut, "No resources found in %s namespace.\n", o.Namespace)
240 }
241
242 return nil
243 }
244
View as plain text