diff --git a/README.md b/README.md index 7223528..2f3ffed 100644 --- a/README.md +++ b/README.md @@ -97,6 +97,7 @@ List of supported relationships used for discovering dependent objects: - [Service References](https://kubernetes.io/docs/reference/kubernetes-api/service-resources/service-v1/) - [ServiceAccount References](https://kubernetes.io/docs/reference/kubernetes-api/authentication-resources/service-account-v1/) - [StorageClass References](https://kubernetes.io/docs/reference/kubernetes-api/config-and-storage-resources/storage-class-v1/) + - [VolumeAttachment References](https://kubernetes.io/docs/reference/kubernetes-api/config-and-storage-resources/volume-attachment-v1/) - Helm - [Release References](https://helm.sh/docs/intro/using_helm/#three-big-concepts) & [Storage References](https://helm.sh/docs/topics/advanced/#storage-backends) diff --git a/internal/graph/graph.go b/internal/graph/graph.go index afdb03a..fa552b2 100644 --- a/internal/graph/graph.go +++ b/internal/graph/graph.go @@ -457,6 +457,13 @@ func ResolveDependents(m meta.RESTMapper, objects []unstructuredv1.Unstructured, klog.V(4).Infof("Failed to get relationships for storageclass named \"%s\": %s: %s", node.Name, err) continue } + // Populate dependents based on VolumeAttachment relationships + case node.Group == "storage.k8s.io" && node.Kind == "VolumeAttachment": + rmap, err = getVolumeAttachmentRelationships(node) + if err != nil { + klog.V(4).Infof("Failed to get relationships for volumeattachment named \"%s\": %s: %s", node.Name, err) + continue + } default: continue } diff --git a/internal/graph/kubernetes.go b/internal/graph/kubernetes.go index 46ac465..87d4648 100644 --- a/internal/graph/kubernetes.go +++ b/internal/graph/kubernetes.go @@ -65,6 +65,15 @@ const ( // Kubernetes StorageClass relationships. RelationshipStorageClassProvisioner Relationship = "StorageClassProvisioner" + + // Kubernetes VolumeAttachment relationships. + RelationshipVolumeAttachmentAttacher Relationship = "VolumeAttachmentAttacher" + RelationshipVolumeAttachmentNode Relationship = "VolumeAttachmentNode" + RelationshipVolumeAttachmentSourceVolume Relationship = "VolumeAttachmentSourceVolume" + RelationshipVolumeAttachmentSourceVolumeClaim Relationship = "VolumeAttachmentSourceVolumeClaim" + RelationshipVolumeAttachmentSourceVolumeCSIDriver Relationship = "VolumeAttachmentSourceVolumeCSIDriver" + RelationshipVolumeAttachmentSourceVolumeCSIDriverSecret Relationship = "VolumeAttachmentSourceVolumeCSIDriverSecret" + RelationshipVolumeAttachmentSourceVolumeStorageClass Relationship = "VolumeAttachmentSourceVolumeStorageClass" ) // getClusterRoleRelationships returns a map of relationships that this @@ -599,3 +608,78 @@ func getValidatingWebhookConfigurationRelationships(n *Node) (*RelationshipMap, return &result, nil } + +// getVolumeAttachmentRelationships returns a map of relationships that this +// VolumeAttachment has with other objects, based on what was referenced in its +// manifest. +//nolint:funlen,nestif +func getVolumeAttachmentRelationships(n *Node) (*RelationshipMap, error) { + var va storagev1.VolumeAttachment + err := runtime.DefaultUnstructuredConverter.FromUnstructured(n.UnstructuredContent(), &va) + if err != nil { + return nil, err + } + + var ref ObjectReference + result := newRelationshipMap() + + // RelationshipVolumeAttachmentAttacher + if a := va.Spec.Attacher; len(a) > 0 { + ref = ObjectReference{Group: "storage.k8s.io", Kind: "CSIDriver", Name: a} + result.AddDependencyByKey(ref.Key(), RelationshipVolumeAttachmentAttacher) + } + + // RelationshipVolumeAttachmentNode + if n := va.Spec.NodeName; len(n) > 0 { + ref = ObjectReference{Kind: "Node", Name: n} + result.AddDependencyByKey(ref.Key(), RelationshipVolumeAttachmentNode) + } + + // RelationshipVolumeAttachmentSourceVolume + if pvName := va.Spec.Source.PersistentVolumeName; pvName != nil && len(*pvName) > 0 { + ref = ObjectReference{Kind: "PersistentVolume", Name: *pvName} + result.AddDependentByKey(ref.Key(), RelationshipVolumeAttachmentSourceVolume) + } + + if iv := va.Spec.Source.InlineVolumeSpec; iv != nil { + // RelationshipVolumeAttachmentSourceVolumeClaim + if iv.ClaimRef != nil { + ref = ObjectReference{Kind: "PersistentVolumeClaim", Name: iv.ClaimRef.Name, Namespace: iv.ClaimRef.Namespace} + result.AddDependentByKey(ref.Key(), RelationshipVolumeAttachmentSourceVolumeClaim) + } + + // RelationshipVolumeAttachmentSourceVolumeStorageClass + ref = ObjectReference{Group: "storage.k8s.io", Kind: "StorageClass", Name: iv.StorageClassName} + result.AddDependentByKey(ref.Key(), RelationshipVolumeAttachmentSourceVolumeStorageClass) + + // RelationshipVolumeAttachmentSourceVolumeCSIDriver + // RelationshipVolumeAttachmentSourceVolumeCSIDriverSecret + //nolint:gocritic + switch { + case iv.PersistentVolumeSource.CSI != nil: + csi := iv.PersistentVolumeSource.CSI + if d := csi.Driver; len(d) > 0 { + ref = ObjectReference{Group: "storage.k8s.io", Kind: "CSIDriver", Name: csi.Driver} + result.AddDependentByKey(ref.Key(), RelationshipVolumeAttachmentSourceVolumeCSIDriver) + } + if ces := csi.ControllerExpandSecretRef; ces != nil { + ref = ObjectReference{Kind: "Secret", Name: ces.Name, Namespace: ces.Namespace} + result.AddDependentByKey(ref.Key(), RelationshipVolumeAttachmentSourceVolumeCSIDriverSecret) + } + if cps := csi.ControllerPublishSecretRef; cps != nil { + ref = ObjectReference{Kind: "Secret", Name: cps.Name, Namespace: cps.Namespace} + result.AddDependentByKey(ref.Key(), RelationshipVolumeAttachmentSourceVolumeCSIDriverSecret) + } + if nps := csi.NodePublishSecretRef; nps != nil { + ref = ObjectReference{Kind: "Secret", Name: nps.Name, Namespace: nps.Namespace} + result.AddDependentByKey(ref.Key(), RelationshipVolumeAttachmentSourceVolumeCSIDriverSecret) + } + if nss := csi.NodeStageSecretRef; nss != nil { + ref = ObjectReference{Kind: "Secret", Name: nss.Name, Namespace: nss.Namespace} + result.AddDependentByKey(ref.Key(), RelationshipVolumeAttachmentSourceVolumeCSIDriverSecret) + } + } + } + + return &result, nil +} diff --git a/internal/printers/printers_humanreadable.go b/internal/printers/printers_humanreadable.go index 6ba4227..f2b7eb5 100644 --- a/internal/printers/printers_humanreadable.go +++ b/internal/printers/printers_humanreadable.go @@ -9,6 +9,7 @@ import ( appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" eventsv1 "k8s.io/api/events/v1" + storagev1 "k8s.io/api/storage/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" unstructuredv1 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" @@ -361,6 +362,34 @@ func getStatefulSetReadyStatus(u *unstructuredv1.Unstructured) (string, string, return ready, "", nil } +// getVolumeAttachmentReadyStatus returns the ready & status value of a +// VolumeAttachment. +func getVolumeAttachmentReadyStatus(u *unstructuredv1.Unstructured) (string, string, error) { + var va storagev1.VolumeAttachment + err := runtime.DefaultUnstructuredConverter.FromUnstructured(u.UnstructuredContent(), &va) + if err != nil { + return "", "", err + } + var ready, status string + if va.Status.Attached { + ready = "True" + } else { + ready = "False" + } + var errTime time.Time + if err := va.Status.AttachError; err != nil { + status = err.Message + errTime = err.Time.Time + } + if err := va.Status.DetachError; err != nil { + if err.Time.After(errTime) { + status = err.Message + } + } + + return ready, status, nil +} + // nodeToTableRow converts the provided node into a table row. //nolint:gocognit,goconst func nodeToTableRow(node *graph.Node, rset graph.RelationshipSet, namePrefix string, showGroupFn func(kind string) bool) metav1.TableRow { @@ -392,6 +421,8 @@ func nodeToTableRow(node *graph.Node, rset graph.RelationshipSet, namePrefix str ready, status, _ = getStatefulSetReadyStatus(node.Unstructured) case node.Group == "events.k8s.io" && node.Kind == "Event": ready, status, _ = getEventReadyStatus(node.Unstructured) + case node.Group == "storage.k8s.io" && node.Kind == "VolumeAttachment": + ready, status, _ = getVolumeAttachmentReadyStatus(node.Unstructured) case node.Unstructured != nil: ready, status, _ = getObjectReadyStatus(node.Unstructured) }