diff --git a/pkg/graph/core.go b/pkg/graph/core.go index 11ce2af..91e479d 100644 --- a/pkg/graph/core.go +++ b/pkg/graph/core.go @@ -16,6 +16,7 @@ package graph import ( "context" + "fmt" "strings" v1 "k8s.io/api/core/v1" @@ -62,6 +63,18 @@ func (g *CoreV1Graph) Unstructured(unstr *unstructured.Unstructured) (*Node, err return nil, err } return g.Endpoints(obj) + case "PersistentVolume": + obj := &v1.PersistentVolume{} + if err := FromUnstructured(unstr, obj); err != nil { + return nil, err + } + return g.PersistentVolume(obj) + case "PersistentVolumeClaim": + obj := &v1.PersistentVolumeClaim{} + if err := FromUnstructured(unstr, obj); err != nil { + return nil, err + } + return g.PersistentVolumeClaim(obj) case "Service": obj := &v1.Service{} if err := FromUnstructured(unstr, obj); err != nil { @@ -133,6 +146,110 @@ func (g *CoreV1Graph) Pod(pod *v1.Pod) (*Node, error) { g.graph.Relationship(n, "Container", c) } + if pod.Spec.ServiceAccountName != "" { + sa, err := g.ServiceAccount(pod.GetNamespace(), pod.Spec.ServiceAccountName) + if err != nil { + return nil, err + } + g.graph.Relationship(n, "ServiceAccount", sa) + } + + for _, imagePullSecret := range pod.Spec.ImagePullSecrets { + secret, err := g.Secret(pod.GetNamespace(), imagePullSecret.Name) + if err != nil { + return nil, err + } + g.graph.Relationship(n, "ImagePullSecret", secret) + } + + for _, volume := range pod.Spec.Volumes { + if volume.ConfigMap != nil { + cm, err := g.ConfigMap(pod.GetNamespace(), volume.ConfigMap.Name) + if err != nil { + return nil, err + } + g.graph.Relationship(n, "ConfigMap", cm) + } + + if volume.Secret != nil { + secret, err := g.Secret(pod.GetNamespace(), volume.Secret.SecretName) + if err != nil { + return nil, err + } + g.graph.Relationship(n, "Secret", secret) + } + + if volume.PersistentVolumeClaim != nil { + pvc, err := g.PersistentVolumeClaimRef(pod.GetNamespace(), volume.PersistentVolumeClaim.ClaimName) + if err != nil { + return nil, err + } + g.graph.Relationship(n, "PersistentVolumeClaim", pvc) + } + + if volume.Projected != nil { + for _, source := range volume.Projected.Sources { + if source.ConfigMap != nil { + cm, err := g.ConfigMap(pod.GetNamespace(), source.ConfigMap.Name) + if err != nil { + return nil, err + } + g.graph.Relationship(n, "ConfigMap", cm) + } + + if source.Secret != nil { + secret, err := g.Secret(pod.GetNamespace(), source.Secret.Name) + if err != nil { + return nil, err + } + g.graph.Relationship(n, "Secret", secret) + } + } + } + } + + for _, container := range append(pod.Spec.InitContainers, pod.Spec.Containers...) { + for _, envFrom := range container.EnvFrom { + if envFrom.ConfigMapRef != nil { + cm, err := g.ConfigMap(pod.GetNamespace(), envFrom.ConfigMapRef.Name) + if err != nil { + return nil, err + } + g.graph.Relationship(n, "ConfigMap", cm) + } + + if envFrom.SecretRef != nil { + secret, err := g.Secret(pod.GetNamespace(), envFrom.SecretRef.Name) + if err != nil { + return nil, err + } + g.graph.Relationship(n, "Secret", secret) + } + } + + for _, env := range container.Env { + if env.ValueFrom == nil { + continue + } + + if env.ValueFrom.ConfigMapKeyRef != nil { + cm, err := g.ConfigMap(pod.GetNamespace(), env.ValueFrom.ConfigMapKeyRef.Name) + if err != nil { + return nil, err + } + g.graph.Relationship(n, "ConfigMap", cm) + } + + if env.ValueFrom.SecretKeyRef != nil { + secret, err := g.Secret(pod.GetNamespace(), env.ValueFrom.SecretKeyRef.Name) + if err != nil { + return nil, err + } + g.graph.Relationship(n, "Secret", secret) + } + } + } + return n, nil } @@ -260,6 +377,72 @@ func (g *CoreV1Graph) Service(obj *v1.Service) (*Node, error) { return nil, nil } +// ConfigMap adds a v1.ConfigMap resource to the Graph or resolves an existing node for it. +func (g *CoreV1Graph) ConfigMap(namespace string, name string) (*Node, error) { + if name == "" { + return nil, fmt.Errorf("configmap reference is missing a name") + } + + if n := g.graph.FindNode(v1.SchemeGroupVersion.String(), "ConfigMap", namespace, name); n != nil { + return n, nil + } + + n := g.graph.Node( + schema.FromAPIVersionAndKind(v1.GroupName, "ConfigMap"), + &metav1.ObjectMeta{ + UID: ToUID("ConfigMap", namespace, name), + Namespace: namespace, + Name: name, + }, + ) + + return n, nil +} + +// Secret adds a v1.Secret resource to the Graph or resolves an existing node for it. +func (g *CoreV1Graph) Secret(namespace string, name string) (*Node, error) { + if name == "" { + return nil, fmt.Errorf("secret reference is missing a name") + } + + if n := g.graph.FindNode(v1.SchemeGroupVersion.String(), "Secret", namespace, name); n != nil { + return n, nil + } + + n := g.graph.Node( + schema.FromAPIVersionAndKind(v1.GroupName, "Secret"), + &metav1.ObjectMeta{ + UID: ToUID("Secret", namespace, name), + Namespace: namespace, + Name: name, + }, + ) + + return n, nil +} + +// ServiceAccount adds a v1.ServiceAccount resource to the Graph or resolves an existing node for it. +func (g *CoreV1Graph) ServiceAccount(namespace string, name string) (*Node, error) { + if name == "" { + return nil, fmt.Errorf("serviceaccount reference is missing a name") + } + + if n := g.graph.FindNode(v1.SchemeGroupVersion.String(), "ServiceAccount", namespace, name); n != nil { + return n, nil + } + + n := g.graph.Node( + schema.FromAPIVersionAndKind(v1.GroupName, "ServiceAccount"), + &metav1.ObjectMeta{ + UID: ToUID("ServiceAccount", namespace, name), + Namespace: namespace, + Name: name, + }, + ) + + return n, nil +} + // ServiceTypeClusterIP adds a v1.Service of type ClusterIP to the Graph. func (g *CoreV1Graph) ServiceTypeClusterIP(obj *v1.Service) (*Node, error) { n := g.graph.Node(schema.FromAPIVersionAndKind(v1.GroupName, "Service"), obj) @@ -337,3 +520,105 @@ func (g *CoreV1Graph) Node(obj *v1.Node) (*Node, error) { return n, nil } + +// PersistentVolume adds a v1.PersistentVolume resource to the Graph. +func (g *CoreV1Graph) PersistentVolume(obj *v1.PersistentVolume) (*Node, error) { + n := g.graph.Node(obj.GroupVersionKind(), obj) + + if obj.Spec.StorageClassName != "" { + sc, err := g.StorageClass(obj.Spec.StorageClassName) + if err != nil { + return nil, err + } + g.graph.Relationship(n, "StorageClass", sc) + } + + return n, nil +} + +// PersistentVolumeRef adds a v1.PersistentVolume resource to the Graph or resolves an existing node for it. +func (g *CoreV1Graph) PersistentVolumeRef(name string) (*Node, error) { + if name == "" { + return nil, fmt.Errorf("persistentvolume reference is missing a name") + } + + if n := g.graph.FindNode(v1.SchemeGroupVersion.String(), "PersistentVolume", "", name); n != nil { + return n, nil + } + + n := g.graph.Node( + schema.FromAPIVersionAndKind(v1.GroupName, "PersistentVolume"), + &metav1.ObjectMeta{ + UID: ToUID("PersistentVolume", name), + Name: name, + }, + ) + + return n, nil +} + +// PersistentVolumeClaim adds a v1.PersistentVolumeClaim resource to the Graph. +func (g *CoreV1Graph) PersistentVolumeClaim(obj *v1.PersistentVolumeClaim) (*Node, error) { + n := g.graph.Node(obj.GroupVersionKind(), obj) + + if obj.Spec.StorageClassName != nil && *obj.Spec.StorageClassName != "" { + sc, err := g.StorageClass(*obj.Spec.StorageClassName) + if err != nil { + return nil, err + } + g.graph.Relationship(n, "StorageClass", sc) + } + + if obj.Spec.VolumeName != "" { + pv, err := g.PersistentVolumeRef(obj.Spec.VolumeName) + if err != nil { + return nil, err + } + g.graph.Relationship(n, "PersistentVolume", pv) + } + + return n, nil +} + +// StorageClass adds a storage.k8s.io/v1 StorageClass resource to the Graph or resolves an existing node for it. +func (g *CoreV1Graph) StorageClass(name string) (*Node, error) { + if name == "" { + return nil, fmt.Errorf("storageclass reference is missing a name") + } + + if n := g.graph.FindNode("storage.k8s.io/v1", "StorageClass", "", name); n != nil { + return n, nil + } + + n := g.graph.Node( + schema.FromAPIVersionAndKind("storage.k8s.io/v1", "StorageClass"), + &metav1.ObjectMeta{ + UID: ToUID("StorageClass", name), + Name: name, + }, + ) + + return n, nil +} + +// PersistentVolumeClaimRef adds a v1.PersistentVolumeClaim resource to the Graph or resolves an existing node for it. +func (g *CoreV1Graph) PersistentVolumeClaimRef(namespace string, name string) (*Node, error) { + if name == "" { + return nil, fmt.Errorf("persistentvolumeclaim reference is missing a name") + } + + if n := g.graph.FindNode(v1.SchemeGroupVersion.String(), "PersistentVolumeClaim", namespace, name); n != nil { + return n, nil + } + + n := g.graph.Node( + schema.FromAPIVersionAndKind(v1.GroupName, "PersistentVolumeClaim"), + &metav1.ObjectMeta{ + UID: ToUID("PersistentVolumeClaim", namespace, name), + Namespace: namespace, + Name: name, + }, + ) + + return n, nil +} diff --git a/pkg/graph/graph.go b/pkg/graph/graph.go index e21a62f..2cd8da2 100644 --- a/pkg/graph/graph.go +++ b/pkg/graph/graph.go @@ -177,6 +177,10 @@ func NewGraph(clientset *kubernetes.Clientset, objs []*unstructured.Unstructured errs := []error{} + for _, obj := range objs { + g.Node(obj.GroupVersionKind(), obj) + } + for _, obj := range objs { _, err := g.Unstructured(obj) if err != nil { @@ -252,6 +256,17 @@ func (g *Graph) Node(gvk schema.GroupVersionKind, obj metav1.Object) *Node { return node } +// FindNode returns a node by identity when it already exists in the graph. +func (g *Graph) FindNode(apiVersion string, kind string, namespace string, name string) *Node { + for _, node := range g.Nodes { + if node.APIVersion == apiVersion && node.Kind == kind && node.Namespace == namespace && node.Name == name { + return node + } + } + + return nil +} + // Finalize adds missing relationships to the Graph. func (g *Graph) Finalize() error { for _, node := range g.Nodes {