Skip to content

Commit 24f3b94

Browse files
committed
add filtering and listing functionalities
1 parent eb8ebd7 commit 24f3b94

File tree

9 files changed

+252
-45
lines changed

9 files changed

+252
-45
lines changed

cmd/main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ func main() {
7171
destroy.Command(f, invFactory, loader, ioStreams),
7272
diff.NewCommand(f, ioStreams),
7373
preview.Command(f, invFactory, loader, ioStreams),
74-
status.Command(f, invFactory, loader),
74+
status.Command(context.TODO(), f, invFactory, status.NewInventoryLoader(loader)),
7575
}
7676
for _, subCmd := range subCmds {
7777
subCmd.PreRunE = preRunE

cmd/status/cmdstatus.go

Lines changed: 176 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,13 @@ package status
66
import (
77
"context"
88
"fmt"
9+
ss "strings"
910
"time"
1011

1112
"github.com/spf13/cobra"
1213
"k8s.io/cli-runtime/pkg/genericclioptions"
1314
cmdutil "k8s.io/kubectl/pkg/cmd/util"
15+
"k8s.io/kubectl/pkg/util/slice"
1416
"sigs.k8s.io/cli-utils/cmd/flagutils"
1517
"sigs.k8s.io/cli-utils/cmd/status/printers"
1618
"sigs.k8s.io/cli-utils/cmd/status/printers/printer"
@@ -24,18 +26,39 @@ import (
2426
"sigs.k8s.io/cli-utils/pkg/kstatus/status"
2527
"sigs.k8s.io/cli-utils/pkg/manifestreader"
2628
"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"
2731
)
2832

29-
func GetRunner(factory cmdutil.Factory, invFactory inventory.ClientFactory, loader manifestreader.ManifestLoader) *Runner {
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 {
3051
r := &Runner{
52+
ctx: ctx,
3153
factory: factory,
3254
invFactory: invFactory,
33-
loader: NewInventoryLoader(loader),
34-
pollerFactoryFunc: pollerFactoryFunc,
55+
loader: loader,
56+
PollerFactoryFunc: pollerFactoryFunc,
3557
}
3658
c := &cobra.Command{
37-
Use: "status (DIRECTORY | STDIN)",
38-
RunE: r.runE,
59+
Use: "status (DIRECTORY | STDIN)",
60+
PreRunE: r.preRunE,
61+
RunE: r.runE,
3962
}
4063
c.Flags().DurationVar(&r.period, "poll-period", 2*time.Second,
4164
"Polling period for resource statuses.")
@@ -44,18 +67,24 @@ func GetRunner(factory cmdutil.Factory, invFactory inventory.ClientFactory, load
4467
c.Flags().StringVar(&r.output, "output", "events", "Output format.")
4568
c.Flags().DurationVar(&r.timeout, "timeout", 0,
4669
"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...")
4774

4875
r.Command = c
4976
return r
5077
}
5178

52-
func Command(f cmdutil.Factory, invFactory inventory.ClientFactory, loader manifestreader.ManifestLoader) *cobra.Command {
53-
return GetRunner(f, invFactory, loader).Command
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
5482
}
5583

5684
// Runner captures the parameters for the command and contains
5785
// the run function.
5886
type Runner struct {
87+
ctx context.Context
5988
Command *cobra.Command
6089
factory cmdutil.Factory
6190
invFactory inventory.ClientFactory
@@ -66,49 +95,166 @@ type Runner struct {
6695
timeout time.Duration
6796
output string
6897

69-
pollerFactoryFunc func(cmdutil.Factory) (poller.Poller, error)
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)
70107
}
71108

72-
// runE implements the logic of the command and will delegate to the
73-
// poller to compute status for each of the resources. One of the printer
74-
// implementations takes care of printing the output.
75-
func (r *Runner) runE(cmd *cobra.Command, args []string) error {
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, %s, %s, %s",
112+
Known, Current, Deleted, Forever)
113+
}
114+
115+
if found := pkgprinters.ValidatePrinterType(r.output); !found {
116+
return fmt.Errorf("unknown output type %q", r.output)
117+
}
118+
119+
if r.invType != Local && r.invType != Remote {
120+
return fmt.Errorf("inv-type flag should be either local or remote")
121+
}
122+
123+
if r.invType == Local && r.inventoryNames != "" {
124+
return fmt.Errorf("inv-names flag should only be used when inv-type is set to remote")
125+
}
126+
127+
if r.inventoryNames != "" {
128+
r.inventoryNameSet = make(map[string]bool)
129+
for _, name := range ss.Split(r.inventoryNames, ",") {
130+
r.inventoryNameSet[name] = true
131+
}
132+
}
133+
134+
if r.namespaces != "" {
135+
r.namespaceSet = make(map[string]bool)
136+
for _, ns := range ss.Split(r.namespaces, ",") {
137+
r.namespaceSet[ns] = true
138+
}
139+
}
140+
141+
if r.statuses != "" {
142+
r.statusSet = make(map[string]bool)
143+
for _, st := range ss.Split(r.statuses, ",") {
144+
parsedST := ss.ToLower(st)
145+
r.statusSet[parsedST] = true
146+
}
147+
}
148+
149+
return nil
150+
}
151+
152+
// Load inventory info from local storage
153+
// and get info from the cluster based on the local info
154+
// wrap it to be a map mapping from string to objectMetadataSet
155+
func (r *Runner) loadInvFromDisk(cmd *cobra.Command, args []string) (*printer.PrintData, error) {
76156
inv, err := r.loader.GetInvInfo(cmd, args)
77157
if err != nil {
78-
return err
158+
return nil, err
79159
}
80160

81161
invClient, err := r.invFactory.NewClient(r.factory)
82162
if err != nil {
83-
return err
163+
return nil, err
84164
}
85165

86166
// Based on the inventory template manifest we look up the inventory
87167
// from the live state using the inventory client.
88168
identifiers, err := invClient.GetClusterObjs(inv)
89169
if err != nil {
90-
return err
170+
return nil, err
91171
}
92172

93-
// Exit here if the inventory is empty.
94-
if len(identifiers) == 0 {
95-
_, _ = fmt.Fprint(cmd.OutOrStdout(), "no resources found in the inventory\n")
96-
return nil
173+
printData := printer.PrintData{
174+
Identifiers: object.ObjMetadataSet{},
175+
InvNameMap: make(map[object.ObjMetadata]string),
176+
StatusSet: r.statusSet,
177+
}
178+
179+
for _, obj := range identifiers {
180+
// check if the object is under one of the targeted namespaces
181+
if _, ok := r.namespaceSet[obj.Namespace]; ok || len(r.namespaceSet) == 0 {
182+
// add to the map for future reference
183+
printData.InvNameMap[obj] = inv.Name()
184+
// append to identifiers
185+
printData.Identifiers = append(printData.Identifiers, obj)
186+
}
187+
}
188+
return &printData, nil
189+
}
190+
191+
// Retrieve a list of inventory object from the cluster
192+
func (r *Runner) listInvFromCluster() (*printer.PrintData, error) {
193+
invClient, err := r.invFactory.NewClient(r.factory)
194+
if err != nil {
195+
return nil, err
97196
}
98197

99198
// initialize maps in printData
100199
printData := printer.PrintData{
101-
InvNameMap: make(map[object.ObjMetadata]string),
102-
StatusSet: make(map[string]bool),
200+
Identifiers: object.ObjMetadataSet{},
201+
InvNameMap: make(map[object.ObjMetadata]string),
202+
StatusSet: r.statusSet,
103203
}
104-
for _, obj := range identifiers {
105-
// add to the map for future reference
106-
printData.InvNameMap[obj] = inv.Name()
107-
// append to identifiers
108-
printData.Identifiers = append(printData.Identifiers, obj)
204+
205+
identifiersMap, err := invClient.ListClusterInventoryObjs(r.ctx)
206+
if err != nil {
207+
return nil, err
208+
}
209+
210+
for invName, identifiers := range identifiersMap {
211+
// Check if there are targeted inventory names and include the current inventory name
212+
if _, ok := r.inventoryNameSet[invName]; !ok && len(r.inventoryNameSet) != 0 {
213+
continue
214+
}
215+
// Filter objects
216+
for _, obj := range identifiers {
217+
// check if the object is under one of the targeted namespaces
218+
if _, ok := r.namespaceSet[obj.Namespace]; ok || len(r.namespaceSet) == 0 {
219+
// add to the map for future reference
220+
printData.InvNameMap[obj] = invName
221+
// append to identifiers
222+
printData.Identifiers = append(printData.Identifiers, obj)
223+
}
224+
}
225+
}
226+
return &printData, nil
227+
}
228+
229+
// runE implements the logic of the command and will delegate to the
230+
// poller to compute status for each of the resources. One of the printer
231+
// implementations takes care of printing the output.
232+
func (r *Runner) runE(cmd *cobra.Command, args []string) error {
233+
var printData *printer.PrintData
234+
var err error
235+
if r.invType == Local {
236+
if len(args) != 0 {
237+
fmt.Printf("%c[%dm", printcommon.ESC, printcommon.YELLOW)
238+
fmt.Println("Warning: Path is assigned while list flag is enabled, ignore the path")
239+
fmt.Printf("%c[%dm", printcommon.ESC, printcommon.RESET)
240+
}
241+
printData, err = r.loadInvFromDisk(cmd, args)
242+
} else if r.invType == Remote {
243+
printData, err = r.listInvFromCluster()
244+
} else {
245+
return fmt.Errorf("invType must be either local or remote")
246+
}
247+
if err != nil {
248+
return err
249+
}
250+
251+
// Exit here if the inventory is empty.
252+
if len(printData.Identifiers) == 0 {
253+
_, _ = fmt.Fprint(cmd.OutOrStdout(), "no resources found in the inventory\n")
254+
return nil
109255
}
110256

111-
statusPoller, err := r.pollerFactoryFunc(r.factory)
257+
statusPoller, err := r.PollerFactoryFunc(r.factory)
112258
if err != nil {
113259
return err
114260
}
@@ -119,7 +265,7 @@ func (r *Runner) runE(cmd *cobra.Command, args []string) error {
119265
In: cmd.InOrStdin(),
120266
Out: cmd.OutOrStdout(),
121267
ErrOut: cmd.ErrOrStderr(),
122-
}, &printData)
268+
}, printData)
123269
if err != nil {
124270
return fmt.Errorf("error creating printer: %w", err)
125271
}
@@ -151,11 +297,11 @@ func (r *Runner) runE(cmd *cobra.Command, args []string) error {
151297
return fmt.Errorf("unknown value for pollUntil: %q", r.pollUntil)
152298
}
153299

154-
eventChannel := statusPoller.Poll(ctx, identifiers, polling.PollOptions{
300+
eventChannel := statusPoller.Poll(ctx, printData.Identifiers, polling.PollOptions{
155301
PollInterval: r.period,
156302
})
157303

158-
return printer.Print(eventChannel, identifiers, cancelFunc)
304+
return printer.Print(eventChannel, printData.Identifiers, cancelFunc)
159305
}
160306

161307
// desiredStatusNotifierFunc returns an Observer function for the

cmd/status/cmdstatus_test.go

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -83,10 +83,14 @@ func TestCommand(t *testing.T) {
8383
expectedOutput string
8484
}{
8585
"no inventory template": {
86+
pollUntil: "known",
87+
printer: "events",
8688
input: "",
8789
expectedErrMsg: "Package uninitialized. Please run \"init\" command.",
8890
},
8991
"no inventory in live state": {
92+
pollUntil: "known",
93+
printer: "events",
9094
input: inventoryTemplate,
9195
expectedOutput: "no resources found in the inventory\n",
9296
},
@@ -500,17 +504,19 @@ foo/deployment.apps/default/foo is InProgress: inProgress
500504
factory: tf,
501505
invFactory: inventory.FakeClientFactory(tc.inventory),
502506
loader: NewInventoryLoader(loader),
503-
pollerFactoryFunc: func(c cmdutil.Factory) (poller.Poller, error) {
507+
PollerFactoryFunc: func(c cmdutil.Factory) (poller.Poller, error) {
504508
return &fakePoller{tc.events}, nil
505509
},
506510

507511
pollUntil: tc.pollUntil,
508512
output: tc.printer,
509513
timeout: tc.timeout,
514+
invType: Local,
510515
}
511516

512517
cmd := &cobra.Command{
513-
RunE: runner.runE,
518+
PreRunE: runner.preRunE,
519+
RunE: runner.runE,
514520
}
515521
cmd.SetIn(strings.NewReader(tc.input))
516522
var buf bytes.Buffer
@@ -542,13 +548,14 @@ foo/deployment.apps/default/foo is InProgress: inProgress
542548
factory: tf,
543549
invFactory: inventory.FakeClientFactory(tc.inventory),
544550
loader: NewInventoryLoader(loader),
545-
pollerFactoryFunc: func(c cmdutil.Factory) (poller.Poller, error) {
551+
PollerFactoryFunc: func(c cmdutil.Factory) (poller.Poller, error) {
546552
return &fakePoller{tc.events}, nil
547553
},
548554

549555
pollUntil: tc.pollUntil,
550556
output: tc.printer,
551557
timeout: tc.timeout,
558+
invType: Local,
552559
}
553560

554561
cmd := &cobra.Command{

pkg/inventory/fake-inventory-client.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
package inventory
55

66
import (
7+
"context"
8+
79
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
810
cmdutil "k8s.io/kubectl/pkg/cmd/util"
911
"sigs.k8s.io/cli-utils/pkg/apis/actuation"
@@ -101,3 +103,7 @@ func (fic *FakeClient) GetClusterInventoryInfo(Info) (*unstructured.Unstructured
101103
func (fic *FakeClient) GetClusterInventoryObjs(_ Info) (object.UnstructuredSet, error) {
102104
return object.UnstructuredSet{}, nil
103105
}
106+
107+
func (fic *FakeClient) ListClusterInventoryObjs(_ context.Context) (map[string]object.ObjMetadataSet, error) {
108+
return map[string]object.ObjMetadataSet{}, nil
109+
}

pkg/inventory/inventory-client-factory.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33

44
package inventory
55

6-
import cmdutil "k8s.io/kubectl/pkg/cmd/util"
6+
import (
7+
cmdutil "k8s.io/kubectl/pkg/cmd/util"
8+
)
79

810
var (
911
_ ClientFactory = ClusterClientFactory{}
@@ -20,5 +22,5 @@ type ClusterClientFactory struct {
2022
}
2123

2224
func (ccf ClusterClientFactory) NewClient(factory cmdutil.Factory) (Client, error) {
23-
return NewClient(factory, WrapInventoryObj, InvInfoToConfigMap, ccf.StatusPolicy)
25+
return NewClient(factory, WrapInventoryObj, InvInfoToConfigMap, ccf.StatusPolicy, ConfigMapGVK)
2426
}

0 commit comments

Comments
 (0)