1
2
3
4 package status
5
6 import (
7 "context"
8 "fmt"
9 "strings"
10 "time"
11
12 "github.com/spf13/cobra"
13 "k8s.io/cli-runtime/pkg/genericclioptions"
14 cmdutil "k8s.io/kubectl/pkg/cmd/util"
15 "k8s.io/kubectl/pkg/util/slice"
16 "sigs.k8s.io/cli-utils/cmd/flagutils"
17 "sigs.k8s.io/cli-utils/cmd/status/printers"
18 "sigs.k8s.io/cli-utils/cmd/status/printers/printer"
19 "sigs.k8s.io/cli-utils/pkg/apply/poller"
20 "sigs.k8s.io/cli-utils/pkg/common"
21 "sigs.k8s.io/cli-utils/pkg/inventory"
22 "sigs.k8s.io/cli-utils/pkg/kstatus/polling"
23 "sigs.k8s.io/cli-utils/pkg/kstatus/polling/aggregator"
24 "sigs.k8s.io/cli-utils/pkg/kstatus/polling/collector"
25 "sigs.k8s.io/cli-utils/pkg/kstatus/polling/event"
26 "sigs.k8s.io/cli-utils/pkg/kstatus/status"
27 "sigs.k8s.io/cli-utils/pkg/manifestreader"
28 "sigs.k8s.io/cli-utils/pkg/object"
29 printcommon "sigs.k8s.io/cli-utils/pkg/print/common"
30 pkgprinters "sigs.k8s.io/cli-utils/pkg/printers"
31 )
32
33 const (
34 Known = "known"
35 Current = "current"
36 Deleted = "deleted"
37 Forever = "forever"
38 )
39
40 const (
41 Local = "local"
42 Remote = "remote"
43 )
44
45 var (
46 PollUntilOptions = []string{Known, Current, Deleted, Forever}
47 )
48
49 func GetRunner(ctx context.Context, factory cmdutil.Factory,
50 invFactory inventory.ClientFactory, loader Loader) *Runner {
51 r := &Runner{
52 ctx: ctx,
53 factory: factory,
54 invFactory: invFactory,
55 loader: loader,
56 PollerFactoryFunc: pollerFactoryFunc,
57 }
58 c := &cobra.Command{
59 Use: "status (DIRECTORY | STDIN)",
60 PreRunE: r.preRunE,
61 RunE: r.runE,
62 }
63 c.Flags().DurationVar(&r.period, "poll-period", 2*time.Second,
64 "Polling period for resource statuses.")
65 c.Flags().StringVar(&r.pollUntil, "poll-until", "known",
66 "When to stop polling. Must be one of 'known', 'current', 'deleted', or 'forever'.")
67 c.Flags().StringVar(&r.output, "output", "events", "Output format.")
68 c.Flags().DurationVar(&r.timeout, "timeout", 0,
69 "How long to wait before exiting")
70 c.Flags().StringVar(&r.invType, "inv-type", Local, "Type of the inventory info, must be local or remote")
71 c.Flags().StringVar(&r.inventoryNames, "inv-names", "", "Names of targeted inventory: inv1,inv2,...")
72 c.Flags().StringVar(&r.namespaces, "namespaces", "", "Names of targeted namespaces: ns1,ns2,...")
73 c.Flags().StringVar(&r.statuses, "statuses", "", "Targeted status: st1,st2...")
74
75 r.Command = c
76 return r
77 }
78
79 func Command(ctx context.Context, f cmdutil.Factory,
80 invFactory inventory.ClientFactory, loader Loader) *cobra.Command {
81 return GetRunner(ctx, f, invFactory, loader).Command
82 }
83
84
85
86 type Runner struct {
87 ctx context.Context
88 Command *cobra.Command
89 factory cmdutil.Factory
90 invFactory inventory.ClientFactory
91 loader Loader
92
93 period time.Duration
94 pollUntil string
95 timeout time.Duration
96 output string
97
98 invType string
99 inventoryNames string
100 inventoryNameSet map[string]bool
101 namespaces string
102 namespaceSet map[string]bool
103 statuses string
104 statusSet map[string]bool
105
106 PollerFactoryFunc func(cmdutil.Factory) (poller.Poller, error)
107 }
108
109 func (r *Runner) preRunE(*cobra.Command, []string) error {
110 if !slice.ContainsString(PollUntilOptions, r.pollUntil, nil) {
111 return fmt.Errorf("pollUntil must be one of %s", strings.Join(PollUntilOptions, ","))
112 }
113
114 if found := pkgprinters.ValidatePrinterType(r.output); !found {
115 return fmt.Errorf("unknown output type %q", r.output)
116 }
117
118 if r.invType != Local && r.invType != Remote {
119 return fmt.Errorf("inv-type flag should be either local or remote")
120 }
121
122 if r.invType == Local && r.inventoryNames != "" {
123 return fmt.Errorf("inv-names flag should only be used when inv-type is set to remote")
124 }
125
126 if r.inventoryNames != "" {
127 r.inventoryNameSet = make(map[string]bool)
128 for _, name := range strings.Split(r.inventoryNames, ",") {
129 r.inventoryNameSet[name] = true
130 }
131 }
132
133 if r.namespaces != "" {
134 r.namespaceSet = make(map[string]bool)
135 for _, ns := range strings.Split(r.namespaces, ",") {
136 r.namespaceSet[ns] = true
137 }
138 }
139
140 if r.statuses != "" {
141 r.statusSet = make(map[string]bool)
142 for _, st := range strings.Split(r.statuses, ",") {
143 parsedST := strings.ToLower(st)
144 r.statusSet[parsedST] = true
145 }
146 }
147
148 return nil
149 }
150
151
152
153
154 func (r *Runner) loadInvFromDisk(cmd *cobra.Command, args []string) (*printer.PrintData, error) {
155 inv, err := r.loader.GetInvInfo(cmd, args)
156 if err != nil {
157 return nil, err
158 }
159
160 invClient, err := r.invFactory.NewClient(r.factory)
161 if err != nil {
162 return nil, err
163 }
164
165
166
167 identifiers, err := invClient.GetClusterObjs(inv)
168 if err != nil {
169 return nil, err
170 }
171
172 printData := printer.PrintData{
173 Identifiers: object.ObjMetadataSet{},
174 InvNameMap: make(map[object.ObjMetadata]string),
175 StatusSet: r.statusSet,
176 }
177
178 for _, obj := range identifiers {
179
180 if _, ok := r.namespaceSet[obj.Namespace]; ok || len(r.namespaceSet) == 0 {
181
182 printData.InvNameMap[obj] = inv.Name()
183
184 printData.Identifiers = append(printData.Identifiers, obj)
185 }
186 }
187 return &printData, nil
188 }
189
190
191 func (r *Runner) listInvFromCluster() (*printer.PrintData, error) {
192 invClient, err := r.invFactory.NewClient(r.factory)
193 if err != nil {
194 return nil, err
195 }
196
197
198 printData := printer.PrintData{
199 Identifiers: object.ObjMetadataSet{},
200 InvNameMap: make(map[object.ObjMetadata]string),
201 StatusSet: r.statusSet,
202 }
203
204 identifiersMap, err := invClient.ListClusterInventoryObjs(r.ctx)
205 if err != nil {
206 return nil, err
207 }
208
209 for invName, identifiers := range identifiersMap {
210
211 if _, ok := r.inventoryNameSet[invName]; !ok && len(r.inventoryNameSet) != 0 {
212 continue
213 }
214
215 for _, obj := range identifiers {
216
217 if _, ok := r.namespaceSet[obj.Namespace]; ok || len(r.namespaceSet) == 0 {
218
219 printData.InvNameMap[obj] = invName
220
221 printData.Identifiers = append(printData.Identifiers, obj)
222 }
223 }
224 }
225 return &printData, nil
226 }
227
228
229
230
231 func (r *Runner) runE(cmd *cobra.Command, args []string) error {
232 var printData *printer.PrintData
233 var err error
234 switch r.invType {
235 case Local:
236 if len(args) != 0 {
237 printcommon.SprintfWithColor(printcommon.YELLOW,
238 "Warning: Path is assigned while list flag is enabled, ignore the path")
239 }
240 printData, err = r.loadInvFromDisk(cmd, args)
241 case Remote:
242 printData, err = r.listInvFromCluster()
243 default:
244 return fmt.Errorf("invType must be either local or remote")
245 }
246 if err != nil {
247 return err
248 }
249
250
251 if len(printData.Identifiers) == 0 {
252 _, _ = fmt.Fprint(cmd.OutOrStdout(), "no resources found in the inventory\n")
253 return nil
254 }
255
256 statusPoller, err := r.PollerFactoryFunc(r.factory)
257 if err != nil {
258 return err
259 }
260
261
262
263 printer, err := printers.CreatePrinter(r.output, genericclioptions.IOStreams{
264 In: cmd.InOrStdin(),
265 Out: cmd.OutOrStdout(),
266 ErrOut: cmd.ErrOrStderr(),
267 }, printData)
268 if err != nil {
269 return fmt.Errorf("error creating printer: %w", err)
270 }
271
272
273
274 ctx := cmd.Context()
275 var cancel func()
276 if r.timeout != 0 {
277 ctx, cancel = context.WithTimeout(ctx, r.timeout)
278 } else {
279 ctx, cancel = context.WithCancel(ctx)
280 }
281 defer cancel()
282
283
284
285 var cancelFunc collector.ObserverFunc
286 switch r.pollUntil {
287 case "known":
288 cancelFunc = allKnownNotifierFunc(cancel)
289 case "current":
290 cancelFunc = desiredStatusNotifierFunc(cancel, status.CurrentStatus)
291 case "deleted":
292 cancelFunc = desiredStatusNotifierFunc(cancel, status.NotFoundStatus)
293 case "forever":
294 cancelFunc = func(*collector.ResourceStatusCollector, event.Event) {}
295 default:
296 return fmt.Errorf("unknown value for pollUntil: %q", r.pollUntil)
297 }
298
299 eventChannel := statusPoller.Poll(ctx, printData.Identifiers, polling.PollOptions{
300 PollInterval: r.period,
301 })
302
303 return printer.Print(eventChannel, printData.Identifiers, cancelFunc)
304 }
305
306
307
308
309 func desiredStatusNotifierFunc(cancelFunc context.CancelFunc,
310 desired status.Status) collector.ObserverFunc {
311 return func(rsc *collector.ResourceStatusCollector, _ event.Event) {
312 var rss []*event.ResourceStatus
313 for _, rs := range rsc.ResourceStatuses {
314 rss = append(rss, rs)
315 }
316 aggStatus := aggregator.AggregateStatus(rss, desired)
317 if aggStatus == desired {
318 cancelFunc()
319 }
320 }
321 }
322
323
324
325
326 func allKnownNotifierFunc(cancelFunc context.CancelFunc) collector.ObserverFunc {
327 return func(rsc *collector.ResourceStatusCollector, _ event.Event) {
328 for _, rs := range rsc.ResourceStatuses {
329 if rs.Status == status.UnknownStatus {
330 return
331 }
332 }
333 cancelFunc()
334 }
335 }
336
337 func pollerFactoryFunc(f cmdutil.Factory) (poller.Poller, error) {
338 return polling.NewStatusPollerFromFactory(f, polling.Options{})
339 }
340
341 type Loader interface {
342 GetInvInfo(cmd *cobra.Command, args []string) (inventory.Info, error)
343 }
344
345 type InventoryLoader struct {
346 Loader manifestreader.ManifestLoader
347 }
348
349 func NewInventoryLoader(loader manifestreader.ManifestLoader) *InventoryLoader {
350 return &InventoryLoader{
351 Loader: loader,
352 }
353 }
354
355 func (ir *InventoryLoader) GetInvInfo(cmd *cobra.Command, args []string) (inventory.Info, error) {
356 _, err := common.DemandOneDirectory(args)
357 if err != nil {
358 return nil, err
359 }
360
361 reader, err := ir.Loader.ManifestReader(cmd.InOrStdin(), flagutils.PathFromArgs(args))
362 if err != nil {
363 return nil, err
364 }
365 objs, err := reader.Read()
366 if err != nil {
367 return nil, err
368 }
369
370 invObj, _, err := inventory.SplitUnstructureds(objs)
371 if err != nil {
372 return nil, err
373 }
374 inv := inventory.WrapInventoryInfoObj(invObj)
375 return inv, nil
376 }
377
View as plain text