diff --git a/internal/graph/graph.go b/internal/graph/graph.go index 6a23805..39f98fa 100644 --- a/internal/graph/graph.go +++ b/internal/graph/graph.go @@ -206,10 +206,18 @@ type Node struct { Namespace string Name string OwnerReferences []metav1.OwnerReference + Dependencies map[types.UID]RelationshipSet Dependents map[types.UID]RelationshipSet Depth uint } +func (n *Node) AddDependency(uid types.UID, r Relationship) { + if _, ok := n.Dependencies[uid]; !ok { + n.Dependencies[uid] = RelationshipSet{} + } + n.Dependencies[uid][r] = struct{}{} +} + func (n *Node) AddDependent(uid types.UID, r Relationship) { if _, ok := n.Dependents[uid]; !ok { n.Dependents[uid] = RelationshipSet{} @@ -217,6 +225,13 @@ func (n *Node) AddDependent(uid types.UID, r Relationship) { n.Dependents[uid][r] = struct{}{} } +func (n *Node) GetDeps(depsIsDependencies bool) map[types.UID]RelationshipSet { + if depsIsDependencies { + return n.Dependencies + } + return n.Dependents +} + func (n *Node) GetObjectReferenceKey() ObjectReferenceKey { ref := ObjectReference{ Group: n.Group, @@ -276,10 +291,22 @@ func (n NodeList) Swap(i, j int) { // NodeMap contains a relationship tree stored as a map of nodes. type NodeMap map[types.UID]*Node +// ResolveDependencies resolves all dependencies of the provided objects and +// returns a relationship tree. +func ResolveDependencies(m meta.RESTMapper, objects []unstructuredv1.Unstructured, uids []types.UID) (NodeMap, error) { + return resolveDeps(m, objects, uids, true) +} + // ResolveDependents resolves all dependents of the provided objects and returns // a relationship tree. -//nolint:funlen,gocognit,gocyclo func ResolveDependents(m meta.RESTMapper, objects []unstructuredv1.Unstructured, uids []types.UID) (NodeMap, error) { + return resolveDeps(m, objects, uids, false) +} + +// resolveDeps resolves all dependencies or dependents of the provided objects +// and returns a relationship tree. +//nolint:funlen,gocognit,gocyclo +func resolveDeps(m meta.RESTMapper, objects []unstructuredv1.Unstructured, uids []types.UID, depsIsDependencies bool) (NodeMap, error) { if len(uids) == 0 { return NodeMap{}, nil } @@ -307,6 +334,7 @@ func ResolveDependents(m meta.RESTMapper, objects []unstructuredv1.Unstructured, Kind: m.GroupVersionKind.Kind, Resource: m.Resource.Resource, OwnerReferences: o.GetOwnerReferences(), + Dependencies: map[types.UID]RelationshipSet{}, Dependents: map[types.UID]RelationshipSet{}, } uid, key := node.UID, node.GetObjectReferenceKey() @@ -356,6 +384,7 @@ func ResolveDependents(m meta.RESTMapper, objects []unstructuredv1.Unstructured, for k, rset := range rmap.DependenciesByRef { if n, ok := globalMapByKey[k]; ok { for r := range rset { + node.AddDependency(n.UID, r) n.AddDependent(node.UID, r) } } @@ -363,6 +392,7 @@ func ResolveDependents(m meta.RESTMapper, objects []unstructuredv1.Unstructured, for k, rset := range rmap.DependentsByRef { if n, ok := globalMapByKey[k]; ok { for r := range rset { + n.AddDependency(node.UID, r) node.AddDependent(n.UID, r) } } @@ -371,6 +401,7 @@ func ResolveDependents(m meta.RESTMapper, objects []unstructuredv1.Unstructured, if ols, ok := rmap.ObjectLabelSelectors[k]; ok { for _, n := range resolveLabelSelectorToNodes(ols) { for r := range rset { + node.AddDependency(n.UID, r) n.AddDependent(node.UID, r) } } @@ -380,6 +411,7 @@ func ResolveDependents(m meta.RESTMapper, objects []unstructuredv1.Unstructured, if ols, ok := rmap.ObjectLabelSelectors[k]; ok { for _, n := range resolveLabelSelectorToNodes(ols) { for r := range rset { + n.AddDependency(node.UID, r) node.AddDependent(n.UID, r) } } @@ -389,6 +421,7 @@ func ResolveDependents(m meta.RESTMapper, objects []unstructuredv1.Unstructured, if os, ok := rmap.ObjectSelectors[k]; ok { for _, n := range resolveSelectorToNodes(os) { for r := range rset { + node.AddDependency(n.UID, r) n.AddDependent(node.UID, r) } } @@ -398,6 +431,7 @@ func ResolveDependents(m meta.RESTMapper, objects []unstructuredv1.Unstructured, if os, ok := rmap.ObjectSelectors[k]; ok { for _, n := range resolveSelectorToNodes(os) { for r := range rset { + n.AddDependency(node.UID, r) node.AddDependent(n.UID, r) } } @@ -406,6 +440,7 @@ func ResolveDependents(m meta.RESTMapper, objects []unstructuredv1.Unstructured, for uid, rset := range rmap.DependenciesByUID { if n, ok := globalMapByUID[uid]; ok { for r := range rset { + node.AddDependency(n.UID, r) n.AddDependent(node.UID, r) } } @@ -413,19 +448,22 @@ func ResolveDependents(m meta.RESTMapper, objects []unstructuredv1.Unstructured, for uid, rset := range rmap.DependentsByUID { if n, ok := globalMapByUID[uid]; ok { for r := range rset { + n.AddDependency(node.UID, r) node.AddDependent(n.UID, r) } } } } - // Populate dependents based on Owner-Dependent relationships + // Populate dependencies & dependents based on Owner-Dependent relationships for _, node := range globalMapByUID { for _, ref := range node.OwnerReferences { if n, ok := globalMapByUID[ref.UID]; ok { if ref.Controller != nil && *ref.Controller { + node.AddDependency(n.UID, RelationshipControllerRef) n.AddDependent(node.UID, RelationshipControllerRef) } + node.AddDependency(n.UID, RelationshipOwnerRef) n.AddDependent(node.UID, RelationshipOwnerRef) } } @@ -435,161 +473,161 @@ func ResolveDependents(m meta.RESTMapper, objects []unstructuredv1.Unstructured, var err error for _, node := range globalMapByUID { switch { - // Populate dependents based on PersistentVolume relationships + // Populate dependencies & dependents based on PersistentVolume relationships case node.Group == corev1.GroupName && node.Kind == "PersistentVolume": rmap, err = getPersistentVolumeRelationships(node) if err != nil { klog.V(4).Infof("Failed to get relationships for persistentvolume named \"%s\": %s", node.Name, err) continue } - // Populate dependents based on PersistentVolumeClaim relationships + // Populate dependencies & dependents based on PersistentVolumeClaim relationships case node.Group == corev1.GroupName && node.Kind == "PersistentVolumeClaim": rmap, err = getPersistentVolumeClaimRelationships(node) if err != nil { klog.V(4).Infof("Failed to get relationships for persistentvolumeclaim named \"%s\" in namespace \"%s\": %s", node.Name, node.Namespace, err) continue } - // Populate dependents based on Pod relationships + // Populate dependencies & dependents based on Pod relationships case node.Group == corev1.GroupName && node.Kind == "Pod": rmap, err = getPodRelationships(node) if err != nil { klog.V(4).Infof("Failed to get relationships for pod named \"%s\" in namespace \"%s\": %s", node.Name, node.Namespace, err) continue } - // Populate dependents based on Service relationships + // Populate dependencies & dependents based on Service relationships case node.Group == corev1.GroupName && node.Kind == "Service": rmap, err = getServiceRelationships(node) if err != nil { klog.V(4).Infof("Failed to get relationships for service named \"%s\" in namespace \"%s\": %s", node.Name, node.Namespace, err) continue } - // Populate dependents based on ServiceAccount relationships + // Populate dependencies & dependents based on ServiceAccount relationships case node.Group == corev1.GroupName && node.Kind == "ServiceAccount": rmap, err = getServiceAccountRelationships(node) if err != nil { klog.V(4).Infof("Failed to get relationships for serviceaccount named \"%s\" in namespace \"%s\": %s", node.Name, node.Namespace, err) continue } - // Populate dependents based on PodSecurityPolicy relationships + // Populate dependencies & dependents based on PodSecurityPolicy relationships case node.Group == policyv1beta1.GroupName && node.Kind == "PodSecurityPolicy": rmap, err = getPodSecurityPolicyRelationships(node) if err != nil { klog.V(4).Infof("Failed to get relationships for podsecuritypolicy named \"%s\": %s", node.Name, err) continue } - // Populate dependents based on PodDisruptionBudget relationships + // Populate dependencies & dependents based on PodDisruptionBudget relationships case node.Group == policyv1.GroupName && node.Kind == "PodDisruptionBudget": rmap, err = getPodDisruptionBudgetRelationships(node) if err != nil { klog.V(4).Infof("Failed to get relationships for poddisruptionbudget named \"%s\": %s", node.Name, err) continue } - // Populate dependents based on MutatingWebhookConfiguration relationships + // Populate dependencies & dependents based on MutatingWebhookConfiguration relationships case node.Group == admissionregistrationv1.GroupName && node.Kind == "MutatingWebhookConfiguration": rmap, err = getMutatingWebhookConfigurationRelationships(node) if err != nil { klog.V(4).Infof("Failed to get relationships for mutatingwebhookconfiguration named \"%s\": %s", node.Name, err) continue } - // Populate dependents based on ValidatingWebhookConfiguration relationships + // Populate dependencies & dependents based on ValidatingWebhookConfiguration relationships case node.Group == admissionregistrationv1.GroupName && node.Kind == "ValidatingWebhookConfiguration": rmap, err = getValidatingWebhookConfigurationRelationships(node) if err != nil { klog.V(4).Infof("Failed to get relationships for validatingwebhookconfiguration named \"%s\": %s", node.Name, err) continue } - // Populate dependents based on APIService relationships + // Populate dependencies & dependents based on APIService relationships case node.Group == apiregistrationv1.GroupName && node.Kind == "APIService": rmap, err = getAPIServiceRelationships(node) if err != nil { klog.V(4).Infof("Failed to get relationships for apiservice named \"%s\": %s", node.Name, err) continue } - // Populate dependents based on Event relationships + // Populate dependencies & dependents based on Event relationships case (node.Group == eventsv1.GroupName || node.Group == corev1.GroupName) && node.Kind == "Event": rmap, err = getEventRelationships(node) if err != nil { klog.V(4).Infof("Failed to get relationships for event named \"%s\" in namespace \"%s\": %s", node.Name, node.Namespace, err) continue } - // Populate dependents based on Ingress relationships + // Populate dependencies & dependents based on Ingress relationships case (node.Group == networkingv1.GroupName || node.Group == extensionsv1beta1.GroupName) && node.Kind == "Ingress": rmap, err = getIngressRelationships(node) if err != nil { klog.V(4).Infof("Failed to get relationships for ingress named \"%s\" in namespace \"%s\": %s", node.Name, node.Namespace, err) continue } - // Populate dependents based on IngressClass relationships + // Populate dependencies & dependents based on IngressClass relationships case node.Group == networkingv1.GroupName && node.Kind == "IngressClass": rmap, err = getIngressClassRelationships(node) if err != nil { klog.V(4).Infof("Failed to get relationships for ingressclass named \"%s\": %s", node.Name, err) continue } - // Populate dependents based on NetworkPolicy relationships + // Populate dependencies & dependents based on NetworkPolicy relationships case node.Group == networkingv1.GroupName && node.Kind == "NetworkPolicy": rmap, err = getNetworkPolicyRelationships(node) if err != nil { klog.V(4).Infof("Failed to get relationships for networkpolicy named \"%s\": %s", node.Name, err) continue } - // Populate dependents based on RuntimeClass relationships + // Populate dependencies & dependents based on RuntimeClass relationships case node.Group == nodev1.GroupName && node.Kind == "RuntimeClass": rmap, err = getRuntimeClassRelationships(node) if err != nil { klog.V(4).Infof("Failed to get relationships for runtimeclass named \"%s\": %s", node.Name, err) continue } - // Populate dependents based on ClusterRole relationships + // Populate dependencies & dependents based on ClusterRole relationships case node.Group == rbacv1.GroupName && node.Kind == "ClusterRole": rmap, err = getClusterRoleRelationships(node) if err != nil { klog.V(4).Infof("Failed to get relationships for clusterrole named \"%s\": %s", node.Name, err) continue } - // Populate dependents based on ClusterRoleBinding relationships + // Populate dependencies & dependents based on ClusterRoleBinding relationships case node.Group == rbacv1.GroupName && node.Kind == "ClusterRoleBinding": rmap, err = getClusterRoleBindingRelationships(node) if err != nil { klog.V(4).Infof("Failed to get relationships for clusterrolebinding named \"%s\": %s", node.Name, err) continue } - // Populate dependents based on Role relationships + // Populate dependencies & dependents based on Role relationships case node.Group == rbacv1.GroupName && node.Kind == "Role": rmap, err = getRoleRelationships(node) if err != nil { klog.V(4).Infof("Failed to get relationships for role named \"%s\" in namespace \"%s\": %s: %s", node.Name, node.Namespace, err) continue } - // Populate dependents based on RoleBinding relationships + // Populate dependencies & dependents based on RoleBinding relationships case node.Group == rbacv1.GroupName && node.Kind == "RoleBinding": rmap, err = getRoleBindingRelationships(node) if err != nil { klog.V(4).Infof("Failed to get relationships for rolebinding named \"%s\" in namespace \"%s\": %s: %s", node.Name, node.Namespace, err) continue } - // Populate dependents based on CSIStorageCapacity relationships + // Populate dependencies & dependents based on CSIStorageCapacity relationships case node.Group == storagev1beta1.GroupName && node.Kind == "CSIStorageCapacity": rmap, err = getCSIStorageCapacityRelationships(node) if err != nil { klog.V(4).Infof("Failed to get relationships for csistoragecapacity named \"%s\": %s: %s", node.Name, err) continue } - // Populate dependents based on CSINode relationships + // Populate dependencies & dependents based on CSINode relationships case node.Group == storagev1.GroupName && node.Kind == "CSINode": rmap, err = getCSINodeRelationships(node) if err != nil { klog.V(4).Infof("Failed to get relationships for csinode named \"%s\": %s: %s", node.Name, err) continue } - // Populate dependents based on StorageClass relationships + // Populate dependencies & dependents based on StorageClass relationships case node.Group == storagev1.GroupName && node.Kind == "StorageClass": rmap, err = getStorageClassRelationships(node) if err != nil { klog.V(4).Infof("Failed to get relationships for storageclass named \"%s\": %s: %s", node.Name, err) continue } - // Populate dependents based on VolumeAttachment relationships + // Populate dependencies & dependents based on VolumeAttachment relationships case node.Group == storagev1.GroupName && node.Kind == "VolumeAttachment": rmap, err = getVolumeAttachmentRelationships(node) if err != nil { @@ -602,8 +640,8 @@ func ResolveDependents(m meta.RESTMapper, objects []unstructuredv1.Unstructured, updateRelationships(node, rmap) } - // Create submap containing the provided objects & their dependents from the - // global map + // Create submap containing the provided objects & either their dependencies + // or dependents from the global map var depth uint nodeMap, uidQueue, uidSet := NodeMap{}, []types.UID{}, map[types.UID]struct{}{} for _, uid := range uids { @@ -623,7 +661,7 @@ func ResolveDependents(m meta.RESTMapper, objects []unstructuredv1.Unstructured, continue } - // Guard against possible cyclic dependency + // Guard against possible cycles if _, ok := uidSet[uid]; ok { uidQueue = uidQueue[1:] continue @@ -638,16 +676,17 @@ func ResolveDependents(m meta.RESTMapper, objects []unstructuredv1.Unstructured, if node.Depth == 0 || depth < node.Depth { node.Depth = depth } - dependents, ix := make([]types.UID, len(node.Dependents)), 0 - for dUID := range node.Dependents { - nodeMap[dUID] = globalMapByUID[dUID] - dependents[ix] = dUID + deps := node.GetDeps(depsIsDependencies) + depUIDs, ix := make([]types.UID, len(deps)), 0 + for depUID := range deps { + nodeMap[depUID] = globalMapByUID[depUID] + depUIDs[ix] = depUID ix++ } - uidQueue = append(uidQueue[1:], dependents...) + uidQueue = append(uidQueue[1:], depUIDs...) } } - klog.V(4).Infof("Resolved %d dependents for %d objects", len(nodeMap)-1, len(uids)) + klog.V(4).Infof("Resolved %d deps for %d objects", len(nodeMap)-1, len(uids)) return nodeMap, nil } diff --git a/internal/printers/printers.go b/internal/printers/printers.go index d8d315b..dc680c0 100644 --- a/internal/printers/printers.go +++ b/internal/printers/printers.go @@ -27,7 +27,7 @@ func lessGroupKind(lhs, rhs schema.GroupKind) bool { } type Interface interface { - Print(w io.Writer, nodeMap graph.NodeMap, rootUID types.UID, maxDepth uint) error + Print(w io.Writer, nodeMap graph.NodeMap, rootUID types.UID, maxDepth uint, depsIsDependencies bool) error } type tablePrinter struct { @@ -39,7 +39,7 @@ type tablePrinter struct { client client.Interface } -func (p *tablePrinter) Print(w io.Writer, nodeMap graph.NodeMap, rootUID types.UID, maxDepth uint) error { +func (p *tablePrinter) Print(w io.Writer, nodeMap graph.NodeMap, rootUID types.UID, maxDepth uint, depsIsDependencies bool) error { root, ok := nodeMap[rootUID] if !ok { return fmt.Errorf("requested object (uid: %s) not found in list of fetched objects", rootUID) @@ -52,17 +52,17 @@ func (p *tablePrinter) Print(w io.Writer, nodeMap graph.NodeMap, rootUID types.U return p.printTablesByGK(w, nodeMap, maxDepth) } - return p.printTable(w, nodeMap, root, maxDepth) + return p.printTable(w, nodeMap, root, maxDepth, depsIsDependencies) } -func (p *tablePrinter) printTable(w io.Writer, nodeMap graph.NodeMap, root *graph.Node, maxDepth uint) error { +func (p *tablePrinter) printTable(w io.Writer, nodeMap graph.NodeMap, root *graph.Node, maxDepth uint, depsIsDependencies bool) error { // Generate Table to print showGroup := false if sg := p.configFlags.ShowGroup; sg != nil { showGroup = *sg } showGroupFn := createShowGroupFn(nodeMap, showGroup, maxDepth) - t, err := nodeMapToTable(nodeMap, root, maxDepth, showGroupFn) + t, err := nodeMapToTable(nodeMap, root, maxDepth, depsIsDependencies, showGroupFn) if err != nil { return err } diff --git a/internal/printers/printers_humanreadable.go b/internal/printers/printers_humanreadable.go index 3fd8e56..83eadd7 100644 --- a/internal/printers/printers_humanreadable.go +++ b/internal/printers/printers_humanreadable.go @@ -498,15 +498,17 @@ func nodeToTableRow(node *graph.Node, rset graph.RelationshipSet, namePrefix str } } -// nodeMapToTable converts the provided node & its dependents into table rows. +// nodeMapToTable converts the provided node & either its dependencies or +// dependents into table rows. func nodeMapToTable( nodeMap graph.NodeMap, root *graph.Node, maxDepth uint, + depsIsDependencies bool, showGroupFn func(kind string) bool) (*metav1.Table, error) { // Sorts the list of UIDs based on the underlying object in following order: // Namespace, Kind, Group, Name - sortDependentsFn := func(d map[types.UID]graph.RelationshipSet) []types.UID { + sortDepsFn := func(d map[types.UID]graph.RelationshipSet) []types.UID { nodes, ix := make(graph.NodeList, len(d)), 0 for uid := range d { nodes[ix] = nodeMap[uid] @@ -523,12 +525,12 @@ func nodeMapToTable( var rows []metav1.TableRow row := nodeToTableRow(root, nil, "", showGroupFn) uidSet := map[types.UID]struct{}{} - dependentRows, err := nodeDependentsToTableRows(nodeMap, uidSet, root, "", 1, maxDepth, sortDependentsFn, showGroupFn) + depRows, err := nodeDepsToTableRows(nodeMap, uidSet, root, "", 1, maxDepth, depsIsDependencies, sortDepsFn, showGroupFn) if err != nil { return nil, err } rows = append(rows, row) - rows = append(rows, dependentRows...) + rows = append(rows, depRows...) table := metav1.Table{ ColumnDefinitions: objectColumnDefinitions, Rows: rows, @@ -537,51 +539,53 @@ func nodeMapToTable( return &table, nil } -// nodeDependentsToTableRows converts the dependents of the provided node into -// table rows. -func nodeDependentsToTableRows( +// nodeDepsToTableRows converts either the dependencies or dependents of the +// provided node into table rows. +func nodeDepsToTableRows( nodeMap graph.NodeMap, uidSet map[types.UID]struct{}, node *graph.Node, prefix string, depth uint, maxDepth uint, - sortDependentsFn func(d map[types.UID]graph.RelationshipSet) []types.UID, + depsIsDependencies bool, + sortDepsFn func(d map[types.UID]graph.RelationshipSet) []types.UID, showGroupFn func(kind string) bool) ([]metav1.TableRow, error) { rows := make([]metav1.TableRow, 0, len(nodeMap)) - // Guard against possible cyclic dependency + // Guard against possible cycles if _, ok := uidSet[node.UID]; ok { return rows, nil } uidSet[node.UID] = struct{}{} - dependents := sortDependentsFn(node.Dependents) - lastIx := len(dependents) - 1 - for ix, childUID := range dependents { - var childPrefix, dependentPrefix string + deps := node.GetDeps(depsIsDependencies) + depUIDs := sortDepsFn(deps) + lastIx := len(depUIDs) - 1 + for ix, childUID := range depUIDs { + var childPrefix, depPrefix string if ix != lastIx { - childPrefix, dependentPrefix = prefix+"├── ", prefix+"│ " + childPrefix, depPrefix = prefix+"├── ", prefix+"│ " } else { - childPrefix, dependentPrefix = prefix+"└── ", prefix+" " + childPrefix, depPrefix = prefix+"└── ", prefix+" " } child, ok := nodeMap[childUID] if !ok { return nil, fmt.Errorf("dependent object (uid: %s) not found in list of fetched objects", childUID) } - rset, ok := node.Dependents[childUID] + rset, ok := deps[childUID] if !ok { return nil, fmt.Errorf("dependent object (uid: %s) not found", childUID) } row := nodeToTableRow(child, rset, childPrefix, showGroupFn) rows = append(rows, row) if maxDepth == 0 || depth < maxDepth { - dependentRows, err := nodeDependentsToTableRows(nodeMap, uidSet, child, dependentPrefix, depth+1, maxDepth, sortDependentsFn, showGroupFn) + depRows, err := nodeDepsToTableRows(nodeMap, uidSet, child, depPrefix, depth+1, maxDepth, depsIsDependencies, sortDepsFn, showGroupFn) if err != nil { return nil, err } - rows = append(rows, dependentRows...) + rows = append(rows, depRows...) } } diff --git a/pkg/cmd/helm/helm.go b/pkg/cmd/helm/helm.go index 9c5b93d..7c23158 100644 --- a/pkg/cmd/helm/helm.go +++ b/pkg/cmd/helm/helm.go @@ -267,7 +267,7 @@ func (o *CmdOptions) Run() error { nodeMap[rootUID] = rootNode // Print output - return o.Printer.Print(o.Out, nodeMap, rootUID, *o.Flags.Depth) + return o.Printer.Print(o.Out, nodeMap, rootUID, *o.Flags.Depth, false) } // getManifestObjects fetches all objects found in the manifest of the provided diff --git a/pkg/cmd/lineage/flags.go b/pkg/cmd/lineage/flags.go index 4e0bfd4..fa9bcd6 100644 --- a/pkg/cmd/lineage/flags.go +++ b/pkg/cmd/lineage/flags.go @@ -15,11 +15,14 @@ const ( flagDepthShorthand = "d" flagScopes = "scopes" flagScopesShorthand = "S" + flagDependencies = "dependencies" + flagDependenciesShorthand = "D" ) // Flags composes common configuration flag structs used in the command. type Flags struct { AllNamespaces *bool + Dependencies *bool Depth *uint Scopes *[]string } @@ -36,6 +39,9 @@ func (f *Flags) AddFlags(flags *pflag.FlagSet) { if f.AllNamespaces != nil { flags.BoolVarP(f.AllNamespaces, flagAllNamespaces, flagAllNamespacesShorthand, *f.AllNamespaces, "If present, list object relationships across all namespaces") } + if f.Dependencies != nil { + flags.BoolVarP(f.Dependencies, flagDependencies, flagDependenciesShorthand, *f.Dependencies, "If present, list object dependencies instead of dependents") + } if f.Depth != nil { flags.UintVarP(f.Depth, flagDepth, flagDepthShorthand, *f.Depth, "Maximum depth to find relationships") } @@ -58,11 +64,13 @@ func (*Flags) RegisterFlagCompletionFunc(cmd *cobra.Command, f cmdutil.Factory) // with default values set. func NewFlags() *Flags { allNamespaces := false + dependencies := false depth := uint(0) scopes := []string{} return &Flags{ AllNamespaces: &allNamespaces, + Dependencies: &dependencies, Depth: &depth, Scopes: &scopes, } diff --git a/pkg/cmd/lineage/lineage.go b/pkg/cmd/lineage/lineage.go index 838cfdc..0842308 100644 --- a/pkg/cmd/lineage/lineage.go +++ b/pkg/cmd/lineage/lineage.go @@ -33,9 +33,9 @@ var ( # List all dependents of the node named "k3d-dev-server" & the corresponding relationship type(s) %CMD_PATH% node/k3d-dev-server -o wide`) - cmdShort = "Display all dependents of a Kubernetes object" + cmdShort = "Display all dependencies or dependents of a Kubernetes object" cmdLong = templates.LongDesc(` - Display all dependents of a Kubernetes object. + Display all dependencies or dependents of a Kubernetes object. TYPE is a Kubernetes resource. Shortcuts and groups will be resolved. NAME is the name of a particular Kubernetes resource.`) @@ -164,6 +164,7 @@ func (o *CmdOptions) Validate() error { klog.V(4).Infof("RequestType: %v", o.RequestType) klog.V(4).Infof("RequestName: %v", o.RequestName) 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.Scopes: %v", *o.Flags.Scopes) klog.V(4).Infof("ClientFlags.Context: %s", *o.ClientFlags.Context) @@ -223,14 +224,18 @@ func (o *CmdOptions) Run() error { // to get the root object but unable to list its resource type objs.Items = append(objs.Items, *root) - // Find all dependents of the root object + // Find either all dependencies or dependents of the root object + depsIsDependencies, resolveDeps := false, graph.ResolveDependents + if o.Flags.Dependencies != nil && *o.Flags.Dependencies { + depsIsDependencies, resolveDeps = true, graph.ResolveDependencies + } mapper := o.Client.GetMapper() rootUID := root.GetUID() - nodeMap, err := graph.ResolveDependents(mapper, objs.Items, []types.UID{rootUID}) + nodeMap, err := resolveDeps(mapper, objs.Items, []types.UID{rootUID}) if err != nil { return err } // Print output - return o.Printer.Print(o.Out, nodeMap, rootUID, *o.Flags.Depth) + return o.Printer.Print(o.Out, nodeMap, rootUID, *o.Flags.Depth, depsIsDependencies) }