Skip to content

Commit

Permalink
add filtering and listing functionalities
Browse files Browse the repository at this point in the history
  • Loading branch information
chunglu-chou committed Aug 22, 2022
1 parent eb8ebd7 commit ad9f752
Show file tree
Hide file tree
Showing 9 changed files with 249 additions and 45 deletions.
2 changes: 1 addition & 1 deletion cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ func main() {
destroy.Command(f, invFactory, loader, ioStreams),
diff.NewCommand(f, ioStreams),
preview.Command(f, invFactory, loader, ioStreams),
status.Command(f, invFactory, loader),
status.Command(context.TODO(), f, invFactory, status.NewInventoryLoader(loader)),
}
for _, subCmd := range subCmds {
subCmd.PreRunE = preRunE
Expand Down
205 changes: 175 additions & 30 deletions cmd/status/cmdstatus.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@ package status
import (
"context"
"fmt"
"strings"
"time"

"github.com/spf13/cobra"
"k8s.io/cli-runtime/pkg/genericclioptions"
cmdutil "k8s.io/kubectl/pkg/cmd/util"
"k8s.io/kubectl/pkg/util/slice"
"sigs.k8s.io/cli-utils/cmd/flagutils"
"sigs.k8s.io/cli-utils/cmd/status/printers"
"sigs.k8s.io/cli-utils/cmd/status/printers/printer"
Expand All @@ -24,18 +26,39 @@ import (
"sigs.k8s.io/cli-utils/pkg/kstatus/status"
"sigs.k8s.io/cli-utils/pkg/manifestreader"
"sigs.k8s.io/cli-utils/pkg/object"
printcommon "sigs.k8s.io/cli-utils/pkg/print/common"
pkgprinters "sigs.k8s.io/cli-utils/pkg/printers"
)

func GetRunner(factory cmdutil.Factory, invFactory inventory.ClientFactory, loader manifestreader.ManifestLoader) *Runner {
const (
Known = "known"
Current = "current"
Deleted = "deleted"
Forever = "forever"
)

const (
Local = "local"
Remote = "remote"
)

var (
PollUntilOptions = []string{Known, Current, Deleted, Forever}
)

func GetRunner(ctx context.Context, factory cmdutil.Factory,
invFactory inventory.ClientFactory, loader Loader) *Runner {
r := &Runner{
ctx: ctx,
factory: factory,
invFactory: invFactory,
loader: NewInventoryLoader(loader),
pollerFactoryFunc: pollerFactoryFunc,
loader: loader,
PollerFactoryFunc: pollerFactoryFunc,
}
c := &cobra.Command{
Use: "status (DIRECTORY | STDIN)",
RunE: r.runE,
Use: "status (DIRECTORY | STDIN)",
PreRunE: r.preRunE,
RunE: r.runE,
}
c.Flags().DurationVar(&r.period, "poll-period", 2*time.Second,
"Polling period for resource statuses.")
Expand All @@ -44,18 +67,24 @@ func GetRunner(factory cmdutil.Factory, invFactory inventory.ClientFactory, load
c.Flags().StringVar(&r.output, "output", "events", "Output format.")
c.Flags().DurationVar(&r.timeout, "timeout", 0,
"How long to wait before exiting")
c.Flags().StringVar(&r.invType, "inv-type", Local, "Type of the inventory info, must be local or remote")
c.Flags().StringVar(&r.inventoryNames, "inv-names", "", "Names of targeted inventory: inv1,inv2,...")
c.Flags().StringVar(&r.namespaces, "namespaces", "", "Names of targeted namespaces: ns1,ns2,...")
c.Flags().StringVar(&r.statuses, "statuses", "", "Targeted status: st1,st2...")

r.Command = c
return r
}

func Command(f cmdutil.Factory, invFactory inventory.ClientFactory, loader manifestreader.ManifestLoader) *cobra.Command {
return GetRunner(f, invFactory, loader).Command
func Command(ctx context.Context, f cmdutil.Factory,
invFactory inventory.ClientFactory, loader Loader) *cobra.Command {
return GetRunner(ctx, f, invFactory, loader).Command
}

// Runner captures the parameters for the command and contains
// the run function.
type Runner struct {
ctx context.Context
Command *cobra.Command
factory cmdutil.Factory
invFactory inventory.ClientFactory
Expand All @@ -66,49 +95,165 @@ type Runner struct {
timeout time.Duration
output string

pollerFactoryFunc func(cmdutil.Factory) (poller.Poller, error)
invType string
inventoryNames string
inventoryNameSet map[string]bool
namespaces string
namespaceSet map[string]bool
statuses string
statusSet map[string]bool

PollerFactoryFunc func(cmdutil.Factory) (poller.Poller, error)
}

// runE implements the logic of the command and will delegate to the
// poller to compute status for each of the resources. One of the printer
// implementations takes care of printing the output.
func (r *Runner) runE(cmd *cobra.Command, args []string) error {
func (r *Runner) preRunE(*cobra.Command, []string) error {
if !slice.ContainsString(PollUntilOptions, r.pollUntil, nil) {
return fmt.Errorf("pollUntil must be one of %s", strings.Join(PollUntilOptions, ","))
}

if found := pkgprinters.ValidatePrinterType(r.output); !found {
return fmt.Errorf("unknown output type %q", r.output)
}

if r.invType != Local && r.invType != Remote {
return fmt.Errorf("inv-type flag should be either local or remote")
}

if r.invType == Local && r.inventoryNames != "" {
return fmt.Errorf("inv-names flag should only be used when inv-type is set to remote")
}

if r.inventoryNames != "" {
r.inventoryNameSet = make(map[string]bool)
for _, name := range strings.Split(r.inventoryNames, ",") {
r.inventoryNameSet[name] = true
}
}

if r.namespaces != "" {
r.namespaceSet = make(map[string]bool)
for _, ns := range strings.Split(r.namespaces, ",") {
r.namespaceSet[ns] = true
}
}

if r.statuses != "" {
r.statusSet = make(map[string]bool)
for _, st := range strings.Split(r.statuses, ",") {
parsedST := strings.ToLower(st)
r.statusSet[parsedST] = true
}
}

return nil
}

// Load inventory info from local storage
// and get info from the cluster based on the local info
// wrap it to be a map mapping from string to objectMetadataSet
func (r *Runner) loadInvFromDisk(cmd *cobra.Command, args []string) (*printer.PrintData, error) {
inv, err := r.loader.GetInvInfo(cmd, args)
if err != nil {
return err
return nil, err
}

invClient, err := r.invFactory.NewClient(r.factory)
if err != nil {
return err
return nil, err
}

// Based on the inventory template manifest we look up the inventory
// from the live state using the inventory client.
identifiers, err := invClient.GetClusterObjs(inv)
if err != nil {
return err
return nil, err
}

// Exit here if the inventory is empty.
if len(identifiers) == 0 {
_, _ = fmt.Fprint(cmd.OutOrStdout(), "no resources found in the inventory\n")
return nil
printData := printer.PrintData{
Identifiers: object.ObjMetadataSet{},
InvNameMap: make(map[object.ObjMetadata]string),
StatusSet: r.statusSet,
}

for _, obj := range identifiers {
// check if the object is under one of the targeted namespaces
if _, ok := r.namespaceSet[obj.Namespace]; ok || len(r.namespaceSet) == 0 {
// add to the map for future reference
printData.InvNameMap[obj] = inv.Name()
// append to identifiers
printData.Identifiers = append(printData.Identifiers, obj)
}
}
return &printData, nil
}

// Retrieve a list of inventory object from the cluster
func (r *Runner) listInvFromCluster() (*printer.PrintData, error) {
invClient, err := r.invFactory.NewClient(r.factory)
if err != nil {
return nil, err
}

// initialize maps in printData
printData := printer.PrintData{
InvNameMap: make(map[object.ObjMetadata]string),
StatusSet: make(map[string]bool),
Identifiers: object.ObjMetadataSet{},
InvNameMap: make(map[object.ObjMetadata]string),
StatusSet: r.statusSet,
}
for _, obj := range identifiers {
// add to the map for future reference
printData.InvNameMap[obj] = inv.Name()
// append to identifiers
printData.Identifiers = append(printData.Identifiers, obj)

identifiersMap, err := invClient.ListClusterInventoryObjs(r.ctx)
if err != nil {
return nil, err
}

for invName, identifiers := range identifiersMap {
// Check if there are targeted inventory names and include the current inventory name
if _, ok := r.inventoryNameSet[invName]; !ok && len(r.inventoryNameSet) != 0 {
continue
}
// Filter objects
for _, obj := range identifiers {
// check if the object is under one of the targeted namespaces
if _, ok := r.namespaceSet[obj.Namespace]; ok || len(r.namespaceSet) == 0 {
// add to the map for future reference
printData.InvNameMap[obj] = invName
// append to identifiers
printData.Identifiers = append(printData.Identifiers, obj)
}
}
}
return &printData, nil
}

// runE implements the logic of the command and will delegate to the
// poller to compute status for each of the resources. One of the printer
// implementations takes care of printing the output.
func (r *Runner) runE(cmd *cobra.Command, args []string) error {
var printData *printer.PrintData
var err error
switch r.invType {
case Local:
if len(args) != 0 {
printcommon.SprintfWithColor(printcommon.YELLOW,
"Warning: Path is assigned while list flag is enabled, ignore the path")
}
printData, err = r.loadInvFromDisk(cmd, args)
case Remote:
printData, err = r.listInvFromCluster()
default:
return fmt.Errorf("invType must be either local or remote")
}
if err != nil {
return err
}

// Exit here if the inventory is empty.
if len(printData.Identifiers) == 0 {
_, _ = fmt.Fprint(cmd.OutOrStdout(), "no resources found in the inventory\n")
return nil
}

statusPoller, err := r.pollerFactoryFunc(r.factory)
statusPoller, err := r.PollerFactoryFunc(r.factory)
if err != nil {
return err
}
Expand All @@ -119,7 +264,7 @@ func (r *Runner) runE(cmd *cobra.Command, args []string) error {
In: cmd.InOrStdin(),
Out: cmd.OutOrStdout(),
ErrOut: cmd.ErrOrStderr(),
}, &printData)
}, printData)
if err != nil {
return fmt.Errorf("error creating printer: %w", err)
}
Expand Down Expand Up @@ -151,11 +296,11 @@ func (r *Runner) runE(cmd *cobra.Command, args []string) error {
return fmt.Errorf("unknown value for pollUntil: %q", r.pollUntil)
}

eventChannel := statusPoller.Poll(ctx, identifiers, polling.PollOptions{
eventChannel := statusPoller.Poll(ctx, printData.Identifiers, polling.PollOptions{
PollInterval: r.period,
})

return printer.Print(eventChannel, identifiers, cancelFunc)
return printer.Print(eventChannel, printData.Identifiers, cancelFunc)
}

// desiredStatusNotifierFunc returns an Observer function for the
Expand Down
13 changes: 10 additions & 3 deletions cmd/status/cmdstatus_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,10 +83,14 @@ func TestCommand(t *testing.T) {
expectedOutput string
}{
"no inventory template": {
pollUntil: "known",
printer: "events",
input: "",
expectedErrMsg: "Package uninitialized. Please run \"init\" command.",
},
"no inventory in live state": {
pollUntil: "known",
printer: "events",
input: inventoryTemplate,
expectedOutput: "no resources found in the inventory\n",
},
Expand Down Expand Up @@ -500,17 +504,19 @@ foo/deployment.apps/default/foo is InProgress: inProgress
factory: tf,
invFactory: inventory.FakeClientFactory(tc.inventory),
loader: NewInventoryLoader(loader),
pollerFactoryFunc: func(c cmdutil.Factory) (poller.Poller, error) {
PollerFactoryFunc: func(c cmdutil.Factory) (poller.Poller, error) {
return &fakePoller{tc.events}, nil
},

pollUntil: tc.pollUntil,
output: tc.printer,
timeout: tc.timeout,
invType: Local,
}

cmd := &cobra.Command{
RunE: runner.runE,
PreRunE: runner.preRunE,
RunE: runner.runE,
}
cmd.SetIn(strings.NewReader(tc.input))
var buf bytes.Buffer
Expand Down Expand Up @@ -542,13 +548,14 @@ foo/deployment.apps/default/foo is InProgress: inProgress
factory: tf,
invFactory: inventory.FakeClientFactory(tc.inventory),
loader: NewInventoryLoader(loader),
pollerFactoryFunc: func(c cmdutil.Factory) (poller.Poller, error) {
PollerFactoryFunc: func(c cmdutil.Factory) (poller.Poller, error) {
return &fakePoller{tc.events}, nil
},

pollUntil: tc.pollUntil,
output: tc.printer,
timeout: tc.timeout,
invType: Local,
}

cmd := &cobra.Command{
Expand Down
6 changes: 6 additions & 0 deletions pkg/inventory/fake-inventory-client.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
package inventory

import (
"context"

"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
cmdutil "k8s.io/kubectl/pkg/cmd/util"
"sigs.k8s.io/cli-utils/pkg/apis/actuation"
Expand Down Expand Up @@ -101,3 +103,7 @@ func (fic *FakeClient) GetClusterInventoryInfo(Info) (*unstructured.Unstructured
func (fic *FakeClient) GetClusterInventoryObjs(_ Info) (object.UnstructuredSet, error) {
return object.UnstructuredSet{}, nil
}

func (fic *FakeClient) ListClusterInventoryObjs(_ context.Context) (map[string]object.ObjMetadataSet, error) {
return map[string]object.ObjMetadataSet{}, nil
}
6 changes: 4 additions & 2 deletions pkg/inventory/inventory-client-factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@

package inventory

import cmdutil "k8s.io/kubectl/pkg/cmd/util"
import (
cmdutil "k8s.io/kubectl/pkg/cmd/util"
)

var (
_ ClientFactory = ClusterClientFactory{}
Expand All @@ -20,5 +22,5 @@ type ClusterClientFactory struct {
}

func (ccf ClusterClientFactory) NewClient(factory cmdutil.Factory) (Client, error) {
return NewClient(factory, WrapInventoryObj, InvInfoToConfigMap, ccf.StatusPolicy)
return NewClient(factory, WrapInventoryObj, InvInfoToConfigMap, ccf.StatusPolicy, ConfigMapGVK)
}
Loading

0 comments on commit ad9f752

Please sign in to comment.