Skip to content

Commit

Permalink
feat: Support --exclude-types flag to exclude resource types from rel…
Browse files Browse the repository at this point in the history
…ationship discovery

Refs: #3
Signed-off-by: Justin Toh <[email protected]>
  • Loading branch information
tohjustin committed Mar 4, 2022
1 parent 23c9dbe commit 8619c0a
Show file tree
Hide file tree
Showing 7 changed files with 128 additions and 10 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ Flags for configuring relationship discovery parameters
| `--all-namespaces`, `-A` | If present, list object relationships across all namespaces |
| `--dependencies`, `-D` | If present, list object dependencies instead of dependents. <br/> Not supported in `helm` subcommand |
| `--depth`, `-d` | Maximum depth to find relationships |
| `--exclude-types` | Accepts a comma separated list of resource types to exclude from relationship discovery. <br/> You can also use multiple flag options like --exclude-types type1 --exclude-types type2... |
| `--scopes`, `-S` | Accepts a comma separated list of additional namespaces to find relationships. <br/> You can also use multiple flag options like -S namespace1 -S namespace2... |

Flags for configuring output format
Expand Down
19 changes: 16 additions & 3 deletions internal/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,9 @@ type GetTableOptions struct {
}

type ListOptions struct {
APIResources []APIResource
Namespaces []string
APIResourcesToExclude []APIResource
APIResourcesToInclude []APIResource
Namespaces []string
}

type Interface interface {
Expand Down Expand Up @@ -218,14 +219,26 @@ func decodeIntoTable(obj runtime.Object) (*metav1.Table, error) {
func (c *client) List(ctx context.Context, opts ListOptions) (*unstructuredv1.UnstructuredList, error) {
klog.V(4).Infof("List with options: %+v", opts)
var err error
apis := opts.APIResources
apis := opts.APIResourcesToInclude
if len(apis) == 0 {
apis, err = c.GetAPIResources(ctx)
if err != nil {
return nil, err
}
}

// Exclude resources
if len(opts.APIResourcesToExclude) > 0 {
excludeGKSet := ResourcesToGroupKindSet(opts.APIResourcesToExclude)
newAPIs := []APIResource{}
for _, api := range apis {
if _, ok := excludeGKSet[api.GroupKind()]; !ok {
newAPIs = append(newAPIs, api)
}
}
apis = newAPIs
}

// Deduplicate list of namespaces & determine the scope for listing objects
isClusterScopeRequest, nsSet := false, make(map[string]struct{})
if len(opts.Namespaces) == 0 {
Expand Down
28 changes: 28 additions & 0 deletions internal/client/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,13 @@ import (
// APIResource represents a Kubernetes API resource.
type APIResource metav1.APIResource

func (r APIResource) GroupKind() schema.GroupKind {
return schema.GroupKind{
Group: r.Group,
Kind: r.Kind,
}
}

func (r APIResource) GroupVersionKind() schema.GroupVersionKind {
return schema.GroupVersionKind{
Group: r.Group,
Expand Down Expand Up @@ -40,6 +47,27 @@ func (r APIResource) WithGroupString() string {
return r.Name + "." + r.Group
}

func ResourcesToGroupKindSet(apis []APIResource) map[schema.GroupKind]struct{} {
gkSet := map[schema.GroupKind]struct{}{}
for _, api := range apis {
gk := api.GroupKind()
// Account for resources that migrated API groups (for Kubernetes v1.18 & above)
switch {
// migrated from "events.v1" to "events.v1.events.k8s.io"
case gk.Kind == "Event" && (gk.Group == "" || gk.Group == "events.k8s.io"):
gkSet[schema.GroupKind{Kind: gk.Kind, Group: ""}] = struct{}{}
gkSet[schema.GroupKind{Kind: gk.Kind, Group: "events.k8s.io"}] = struct{}{}
// migrated from "ingresses.v1.extensions" to "ingresses.v1.networking.k8s.io"
case gk.Kind == "Ingress" && (gk.Group == "extensions" || gk.Group == "networking.k8s.io"):
gkSet[schema.GroupKind{Kind: gk.Kind, Group: "extensions"}] = struct{}{}
gkSet[schema.GroupKind{Kind: gk.Kind, Group: "networking.k8s.io"}] = struct{}{}
default:
gkSet[gk] = struct{}{}
}
}
return gkSet
}

// ObjectMeta contains the metadata for identifying a Kubernetes object.
type ObjectMeta struct {
APIResource
Expand Down
13 changes: 12 additions & 1 deletion pkg/cmd/helm/flags.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package helm

import (
"fmt"

"github.com/spf13/cobra"
"github.com/spf13/pflag"
cmdutil "k8s.io/kubectl/pkg/cmd/util"
Expand All @@ -11,6 +13,7 @@ import (
const (
flagAllNamespaces = "all-namespaces"
flagAllNamespacesShorthand = "A"
flagExcludeTypes = "exclude-types"
flagDepth = "depth"
flagDepthShorthand = "d"
flagScopes = "scopes"
Expand All @@ -21,6 +24,7 @@ const (
type Flags struct {
AllNamespaces *bool
Depth *uint
ExcludeTypes *[]string
Scopes *[]string
}

Expand All @@ -39,8 +43,13 @@ func (f *Flags) AddFlags(flags *pflag.FlagSet) {
if f.Depth != nil {
flags.UintVarP(f.Depth, flagDepth, flagDepthShorthand, *f.Depth, "Maximum depth to find relationships")
}
if f.ExcludeTypes != nil {
excludeTypesUsage := fmt.Sprintf("Accepts a comma separated list of resource types to exclude from relationship discovery. You can also use multiple flag options like --%s resource1 --%s resource2...", flagExcludeTypes, flagExcludeTypes)
flags.StringSliceVar(f.ExcludeTypes, flagExcludeTypes, *f.ExcludeTypes, excludeTypesUsage)
}
if f.Scopes != nil {
flags.StringSliceVarP(f.Scopes, flagScopes, flagScopesShorthand, *f.Scopes, "Accepts a comma separated list of additional namespaces to find relationships. You can also use multiple flag options like -S namespace1 -S namespace2...")
scopesUsage := fmt.Sprintf("Accepts a comma separated list of additional namespaces to find relationships. You can also use multiple flag options like -%s namespace1 -%s namespace2...", flagScopesShorthand, flagScopesShorthand)
flags.StringSliceVarP(f.Scopes, flagScopes, flagScopesShorthand, *f.Scopes, scopesUsage)
}
}

Expand All @@ -59,11 +68,13 @@ func (*Flags) RegisterFlagCompletionFunc(cmd *cobra.Command, f cmdutil.Factory)
func NewFlags() *Flags {
allNamespaces := false
depth := uint(0)
excludeTypes := []string{}
scopes := []string{}

return &Flags{
AllNamespaces: &allNamespaces,
Depth: &depth,
ExcludeTypes: &excludeTypes,
Scopes: &scopes,
}
}
40 changes: 37 additions & 3 deletions pkg/cmd/helm/helm.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ var (
# List all resources associated with release named "bar" & the corresponding relationship type(s)
%CMD_PATH% bar --output=wide
# List all resources associated with release named "bar", excluding event & secret resource types
%CMD_PATH% pv/disk --dependencies --exclude-types=ev,secret
# List only resources provisioned by the release named "bar"
%CMD_PATH% bar --depth=1`)
cmdShort = "Display resources associated with a Helm release & their dependents"
Expand Down Expand Up @@ -169,6 +172,7 @@ func (o *CmdOptions) Validate() error {
klog.V(4).Infof("RequestRelease: %v", o.RequestRelease)
klog.V(4).Infof("Flags.AllNamespaces: %t", *o.Flags.AllNamespaces)
klog.V(4).Infof("Flags.Depth: %v", *o.Flags.Depth)
klog.V(4).Infof("Flags.ExcludeTypes: %v", *o.Flags.ExcludeTypes)
klog.V(4).Infof("Flags.Scopes: %v", *o.Flags.Scopes)
klog.V(4).Infof("ClientFlags.Context: %s", *o.ClientFlags.Context)
klog.V(4).Infof("ClientFlags.Namespace: %s", *o.ClientFlags.Namespace)
Expand All @@ -182,7 +186,7 @@ func (o *CmdOptions) Validate() error {
}

// Run implements all the necessary functionality for the helm command.
//nolint:funlen
//nolint:funlen,gocognit
func (o *CmdOptions) Run() error {
ctx := context.Background()

Expand Down Expand Up @@ -213,6 +217,33 @@ func (o *CmdOptions) Run() error {
return err
}

// Determine resources to list
excludeAPIs := []client.APIResource{}
if o.Flags.ExcludeTypes != nil {
for _, kind := range *o.Flags.ExcludeTypes {
api, err := o.Client.ResolveAPIResource(kind)
if err != nil {
return err
}
excludeAPIs = append(excludeAPIs, *api)
}
}

// Filter out objects that matches any excluded resource
if len(excludeAPIs) > 0 {
excludeGKSet := client.ResourcesToGroupKindSet(excludeAPIs)
newRlsObjs := []unstructuredv1.Unstructured{}
for _, i := range rlsObjs {
if _, ok := excludeGKSet[i.GroupVersionKind().GroupKind()]; !ok {
newRlsObjs = append(newRlsObjs, i)
}
}
rlsObjs = newRlsObjs
if _, ok := excludeGKSet[stgObj.GroupVersionKind().GroupKind()]; ok {
stgObj = nil
}
}

// Determine the namespaces to list objects
var namespaces []string
nsSet := map[string]struct{}{o.Namespace: {}}
Expand All @@ -229,8 +260,11 @@ func (o *CmdOptions) Run() error {
namespaces = append(namespaces, *o.Flags.Scopes...)
}

// Fetch all resources in the cluster
objs, err := o.Client.List(ctx, client.ListOptions{Namespaces: namespaces})
// Fetch resources in the cluster
objs, err := o.Client.List(ctx, client.ListOptions{
APIResourcesToExclude: excludeAPIs,
Namespaces: namespaces,
})
if err != nil {
return err
}
Expand Down
13 changes: 12 additions & 1 deletion pkg/cmd/lineage/flags.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package lineage

import (
"fmt"

"github.com/spf13/cobra"
"github.com/spf13/pflag"
cmdutil "k8s.io/kubectl/pkg/cmd/util"
Expand All @@ -11,6 +13,7 @@ import (
const (
flagAllNamespaces = "all-namespaces"
flagAllNamespacesShorthand = "A"
flagExcludeTypes = "exclude-types"
flagDepth = "depth"
flagDepthShorthand = "d"
flagScopes = "scopes"
Expand All @@ -24,6 +27,7 @@ type Flags struct {
AllNamespaces *bool
Dependencies *bool
Depth *uint
ExcludeTypes *[]string
Scopes *[]string
}

Expand All @@ -45,8 +49,13 @@ func (f *Flags) AddFlags(flags *pflag.FlagSet) {
if f.Depth != nil {
flags.UintVarP(f.Depth, flagDepth, flagDepthShorthand, *f.Depth, "Maximum depth to find relationships")
}
if f.ExcludeTypes != nil {
excludeTypesUsage := fmt.Sprintf("Accepts a comma separated list of resource types to exclude from relationship discovery. You can also use multiple flag options like --%s kind1 --%s kind1...", flagExcludeTypes, flagExcludeTypes)
flags.StringSliceVar(f.ExcludeTypes, flagExcludeTypes, *f.ExcludeTypes, excludeTypesUsage)
}
if f.Scopes != nil {
flags.StringSliceVarP(f.Scopes, flagScopes, flagScopesShorthand, *f.Scopes, "Accepts a comma separated list of additional namespaces to find relationships. You can also use multiple flag options like -S namespace1 -S namespace2...")
scopesUsage := fmt.Sprintf("Accepts a comma separated list of additional namespaces to find relationships. You can also use multiple flag options like -%s namespace1 -%s namespace2...", flagScopesShorthand, flagScopesShorthand)
flags.StringSliceVarP(f.Scopes, flagScopes, flagScopesShorthand, *f.Scopes, scopesUsage)
}
}

Expand All @@ -66,12 +75,14 @@ func NewFlags() *Flags {
allNamespaces := false
dependencies := false
depth := uint(0)
excludeTypes := []string{}
scopes := []string{}

return &Flags{
AllNamespaces: &allNamespaces,
Dependencies: &dependencies,
Depth: &depth,
ExcludeTypes: &excludeTypes,
Scopes: &scopes,
}
}
24 changes: 22 additions & 2 deletions pkg/cmd/lineage/lineage.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ var (
# List all dependents of the node named "k3d-dev-server" & the corresponding relationship type(s)
%CMD_PATH% node/k3d-dev-server --output=wide
# List all dependents of the persistentvolume named "disk", excluding event & secret resource types
%CMD_PATH% pv/disk --dependencies --exclude-types=ev,secret
# List all dependencies of the pod named "bar-5cc79d4bf5-xgvkc"
%CMD_PATH% pod.v1. bar-5cc79d4bf5-xgvkc --dependencies
Expand Down Expand Up @@ -172,6 +175,7 @@ func (o *CmdOptions) Validate() error {
klog.V(4).Infof("Flags.AllNamespaces: %t", *o.Flags.AllNamespaces)
klog.V(4).Infof("Flags.Dependencies: %t", *o.Flags.Dependencies)
klog.V(4).Infof("Flags.Depth: %v", *o.Flags.Depth)
klog.V(4).Infof("Flags.ExcludeTypes: %v", *o.Flags.ExcludeTypes)
klog.V(4).Infof("Flags.Scopes: %v", *o.Flags.Scopes)
klog.V(4).Infof("ClientFlags.Context: %s", *o.ClientFlags.Context)
klog.V(4).Infof("ClientFlags.Namespace: %s", *o.ClientFlags.Namespace)
Expand All @@ -185,6 +189,7 @@ func (o *CmdOptions) Validate() error {
}

// Run implements all the necessary functionality for the lineage command.
//nolint:funlen
func (o *CmdOptions) Run() error {
ctx := context.Background()

Expand All @@ -211,6 +216,18 @@ func (o *CmdOptions) Run() error {
return err
}

// Determine resources to list
excludeAPIs := []client.APIResource{}
if o.Flags.ExcludeTypes != nil {
for _, kind := range *o.Flags.ExcludeTypes {
api, err := o.Client.ResolveAPIResource(kind)
if err != nil {
return err
}
excludeAPIs = append(excludeAPIs, *api)
}
}

// Determine the namespaces to list objects
namespaces := []string{o.Namespace}
if o.Flags.AllNamespaces != nil && *o.Flags.AllNamespaces {
Expand All @@ -220,8 +237,11 @@ func (o *CmdOptions) Run() error {
namespaces = append(namespaces, *o.Flags.Scopes...)
}

// Fetch all resources in the cluster
objs, err := o.Client.List(ctx, client.ListOptions{Namespaces: namespaces})
// Fetch resources in the cluster
objs, err := o.Client.List(ctx, client.ListOptions{
APIResourcesToExclude: excludeAPIs,
Namespaces: namespaces,
})
if err != nil {
return err
}
Expand Down

0 comments on commit 8619c0a

Please sign in to comment.