Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
285 changes: 285 additions & 0 deletions pkg/graph/core.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ package graph

import (
"context"
"fmt"
"strings"

v1 "k8s.io/api/core/v1"
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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
}

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
}
15 changes: 15 additions & 0 deletions pkg/graph/graph.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down
Loading