Skip to content

Commit

Permalink
FWI-4610 - /vv/add pod metadata support (#80)
Browse files Browse the repository at this point in the history
* expose pod-metadata info

* bump lib minor versions

* fix comments

* add test assertion

* make assertions explicit
  • Loading branch information
vitorvezani authored Jul 21, 2023
1 parent 984bb05 commit 3185c7f
Show file tree
Hide file tree
Showing 14 changed files with 418 additions and 98 deletions.
16 changes: 8 additions & 8 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ module github.com/fairwindsops/controller-utils
go 1.19

require (
github.com/go-logr/logr v1.2.3
github.com/go-logr/logr v1.2.4
github.com/go-logr/stdr v1.2.2
github.com/stretchr/testify v1.8.4
k8s.io/api v0.26.1
k8s.io/apimachinery v0.26.1
k8s.io/client-go v0.26.1
k8s.io/api v0.26.7
k8s.io/apimachinery v0.26.7
k8s.io/client-go v0.26.7
)

require (
Expand All @@ -23,11 +23,11 @@ require (
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
golang.org/x/net v0.7.0 // indirect
golang.org/x/net v0.8.0 // indirect
golang.org/x/oauth2 v0.5.0 // indirect
golang.org/x/sys v0.5.0 // indirect
golang.org/x/term v0.5.0 // indirect
golang.org/x/text v0.7.0 // indirect
golang.org/x/sys v0.6.0 // indirect
golang.org/x/term v0.6.0 // indirect
golang.org/x/text v0.8.0 // indirect
golang.org/x/time v0.3.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/protobuf v1.28.1 // indirect
Expand Down
32 changes: 16 additions & 16 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCv
github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0=
github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY=
Expand Down Expand Up @@ -98,8 +98,8 @@ golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.5.0 h1:HuArIo48skDwlrvM3sEdHXElYslAMsf3KwRkkW4MC4s=
golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I=
Expand All @@ -112,15 +112,15 @@ golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw=
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
Expand Down Expand Up @@ -172,12 +172,12 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
k8s.io/api v0.26.1 h1:f+SWYiPd/GsiWwVRz+NbFyCgvv75Pk9NK6dlkZgpCRQ=
k8s.io/api v0.26.1/go.mod h1:xd/GBNgR0f707+ATNyPmQ1oyKSgndzXij81FzWGsejg=
k8s.io/apimachinery v0.26.1 h1:8EZ/eGJL+hY/MYCNwhmDzVqq2lPl3N3Bo8rvweJwXUQ=
k8s.io/apimachinery v0.26.1/go.mod h1:tnPmbONNJ7ByJNz9+n9kMjNP8ON+1qoAIIC70lztu74=
k8s.io/client-go v0.26.1 h1:87CXzYJnAMGaa/IDDfRdhTzxk/wzGZ+/HUQpqgVSZXU=
k8s.io/client-go v0.26.1/go.mod h1:IWNSglg+rQ3OcvDkhY6+QLeasV4OYHDjdqeWkDQZwGE=
k8s.io/api v0.26.7 h1:Lf4iEBEJb5OFNmawtBfSZV/UNi9riSJ0t1qdhyZqI40=
k8s.io/api v0.26.7/go.mod h1:Vk9bMadzA49UHPmHB//lX7VRCQSXGoVwfLd3Sc1SSXI=
k8s.io/apimachinery v0.26.7 h1:590jSBwaSHCAFCqltaEogY/zybFlhGsnLteLpuF2wig=
k8s.io/apimachinery v0.26.7/go.mod h1:qYzLkrQ9lhrZRh0jNKo2cfvf/R1/kQONnSiyB7NUJU0=
k8s.io/client-go v0.26.7 h1:hyU9aKHlwVOykgyxzGYkrDSLCc4+mimZVyUJjPyUn1E=
k8s.io/client-go v0.26.7/go.mod h1:okYjy0jtq6sdeztALDvCh24tg4opOQS1XNvsJlERDAo=
k8s.io/klog/v2 v2.90.0 h1:VkTxIV/FjRXn1fgNNcKGM8cfmL1Z33ZjXRTVxKCoF5M=
k8s.io/klog/v2 v2.90.0/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=
k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 h1:+70TFaan3hfJzs+7VK2o+OGxg8HsuBr/5f6tVAjDu6E=
Expand Down
9 changes: 6 additions & 3 deletions pkg/controller/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ type Workload struct {
TopController unstructured.Unstructured
Pods []unstructured.Unstructured
PodSpec *corev1.PodSpec
PodMetadata *metav1.ObjectMeta
PodCount int
RunningPodCount int
}
Expand Down Expand Up @@ -141,13 +142,14 @@ func (client Client) getAllTopControllers(namespace string, includePods bool) ([
}
for _, controller := range objectCache {
key := getControllerKey(controller)
podSpec, err := GetPodSpec(controller.UnstructuredContent())
podMetadata, podSpec, err := GetPodMetadataAndSpec(controller.UnstructuredContent())
if err != nil {
return nil, err
}
workloadMap[key] = Workload{
TopController: controller,
PodSpec: podSpec,
PodMetadata: podMetadata,
}
}
pods, err := client.getAllPods(namespace)
Expand All @@ -165,17 +167,18 @@ func (client Client) getAllTopControllers(namespace string, includePods bool) ([
existingWorkload, ok := workloadMap[key]
if !ok {
existingWorkload.TopController = controller
podSpec, err := GetPodSpec(controller.UnstructuredContent())
podMetadata, podSpec, err := GetPodMetadataAndSpec(controller.UnstructuredContent())
if err != nil {
return nil, err
}
if podSpec == nil {
podSpec, err = GetPodSpec(pod.UnstructuredContent())
podMetadata, podSpec, err = GetPodMetadataAndSpec(pod.UnstructuredContent())
if err != nil {
return nil, err
}
}
existingWorkload.PodSpec = podSpec
existingWorkload.PodMetadata = podMetadata
}
existingWorkload.PodCount++
if getPodStatus(pod) == podStatusRunning {
Expand Down
44 changes: 37 additions & 7 deletions pkg/controller/pod.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,26 +18,56 @@ import (
"encoding/json"

corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

var podSpecFields = []string{"jobTemplate", "spec", "template"}

// GetPodSpec looks inside arbitrary YAML for a PodSpec
func GetPodSpec(obj map[string]interface{}) (*corev1.PodSpec, error) {
// GetPodMetadataAndSpec looks inside arbitrary YAML for a PodSpec and it's metadata
func GetPodMetadataAndSpec(obj map[string]any) (*metav1.ObjectMeta, *corev1.PodSpec, error) {
return getPodMetadataAndSpecRecursively(nil, obj)
}

func getPodMetadataAndSpecRecursively(parent map[string]any, obj map[string]any) (*metav1.ObjectMeta, *corev1.PodSpec, error) {
// TODO examine this for ways to make it more efficient.
for _, child := range podSpecFields {
if childYaml, ok := obj[child]; ok {
return GetPodSpec(childYaml.(map[string]interface{}))
return getPodMetadataAndSpecRecursively(obj, childYaml.(map[string]any))
}
}
if _, ok := obj["containers"]; !ok {
return nil, nil
return nil, nil, nil
}
b, err := json.Marshal(obj)
if err != nil {
return nil, err
return nil, nil, err
}
podSpec := corev1.PodSpec{}
// pod spec found,
var podSpec corev1.PodSpec
err = json.Unmarshal(b, &podSpec)
return &podSpec, err
if err != nil {
return nil, nil, err
}
// looks for its metadata
metadata, err := getMetadata(parent)
if err != nil {
return nil, nil, err
}
return metadata, &podSpec, nil
}

func getMetadata(parent map[string]any) (*metav1.ObjectMeta, error) {
if parent == nil {
return nil, nil
}
if _, ok := parent["metadata"]; !ok {
return nil, nil
}
b, err := json.Marshal(parent["metadata"])
if err != nil {
return nil, err
}
var metadata metav1.ObjectMeta
err = json.Unmarshal(b, &metadata)
return &metadata, err
}
54 changes: 45 additions & 9 deletions pkg/controller/pod_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,27 +19,63 @@ import (
"os"
"testing"

corev1 "k8s.io/api/core/v1"

"github.com/stretchr/testify/assert"
)

func TestGetPodSpec(t *testing.T) {
podSpec, err := readPodSpecFile(t, "./tests/deployment.json")
podMetadata, podSpec, err := GetPodMetadataAndSpec(readFile(t, "./testdata/secret.json"))
assert.NoError(t, err)
assert.Nil(t, podMetadata)
assert.Nil(t, podSpec)

podMetadata, podSpec, err = GetPodMetadataAndSpec(readFile(t, "./testdata/deployment.json"))
assert.NoError(t, err)
assert.NotNil(t, podSpec)
assert.Equal(t, 1, len(podSpec.Containers))
assert.Equal(t, map[string]string{"k8s-app": "deployment", "pod-label": "pod-label-value"}, podMetadata.Labels)
assert.Len(t, podSpec.Containers, 1)

podSpec, err = readPodSpecFile(t, "./tests/secret.json")
podMetadata, podSpec, err = GetPodMetadataAndSpec(readFile(t, "./testdata/cronjob.json"))
assert.NoError(t, err)
assert.Nil(t, podSpec)
assert.NotNil(t, podSpec)
assert.Nil(t, podMetadata)
assert.Len(t, podSpec.Containers, 1)

podMetadata, podSpec, err = GetPodMetadataAndSpec(readFile(t, "./testdata/daemon-set.json"))
assert.NoError(t, err)
assert.NotNil(t, podSpec)
assert.Equal(t, map[string]string{"name": "fluentd-elasticsearch"}, podMetadata.Labels)
assert.Len(t, podSpec.Containers, 1)

podMetadata, podSpec, err = GetPodMetadataAndSpec(readFile(t, "./testdata/job.json"))
assert.NoError(t, err)
assert.NotNil(t, podSpec)
assert.Nil(t, podMetadata)
assert.Len(t, podSpec.Containers, 1)

podMetadata, podSpec, err = GetPodMetadataAndSpec(readFile(t, "./testdata/replica-set.json"))
assert.NoError(t, err)
assert.NotNil(t, podSpec)
assert.Equal(t, map[string]string{"tier": "frontend"}, podMetadata.Labels)
assert.Len(t, podSpec.Containers, 1)

podMetadata, podSpec, err = GetPodMetadataAndSpec(readFile(t, "./testdata/replication-controller.json"))
assert.NoError(t, err)
assert.NotNil(t, podSpec)
assert.Equal(t, map[string]string{"app": "nginx"}, podMetadata.Labels)
assert.Len(t, podSpec.Containers, 1)

podMetadata, podSpec, err = GetPodMetadataAndSpec(readFile(t, "./testdata/stateful-set.json"))
assert.NoError(t, err)
assert.NotNil(t, podSpec)
assert.Equal(t, map[string]string{"app": "nginx"}, podMetadata.Labels)
assert.Len(t, podSpec.Containers, 1)
}

func readPodSpecFile(t *testing.T, file string) (*corev1.PodSpec, error) {
func readFile(t *testing.T, file string) map[string]any {
contents, err := os.ReadFile(file)
assert.NoError(t, err)
var object map[string]interface{}
var object map[string]any
err = json.Unmarshal(contents, &object)
assert.NoError(t, err)
return GetPodSpec(object)
return object
}
31 changes: 31 additions & 0 deletions pkg/controller/testdata/cronjob.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"apiVersion": "batch/v1",
"kind": "CronJob",
"metadata": {
"name": "hello"
},
"spec": {
"schedule": "* * * * *",
"jobTemplate": {
"spec": {
"template": {
"spec": {
"containers": [
{
"name": "hello",
"image": "busybox:1.28",
"imagePullPolicy": "IfNotPresent",
"command": [
"/bin/sh",
"-c",
"date; echo Hello from the Kubernetes cluster"
]
}
],
"restartPolicy": "OnFailure"
}
}
}
}
}
}
69 changes: 69 additions & 0 deletions pkg/controller/testdata/daemon-set.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
{
"apiVersion": "apps/v1",
"kind": "DaemonSet",
"metadata": {
"name": "fluentd-elasticsearch",
"namespace": "kube-system",
"labels": {
"k8s-app": "fluentd-logging"
}
},
"spec": {
"selector": {
"matchLabels": {
"name": "fluentd-elasticsearch"
}
},
"template": {
"metadata": {
"labels": {
"name": "fluentd-elasticsearch"
}
},
"spec": {
"tolerations": [
{
"key": "node-role.kubernetes.io/control-plane",
"operator": "Exists",
"effect": "NoSchedule"
},
{
"key": "node-role.kubernetes.io/master",
"operator": "Exists",
"effect": "NoSchedule"
}
],
"containers": [
{
"name": "fluentd-elasticsearch",
"image": "quay.io/fluentd_elasticsearch/fluentd:v2.5.2",
"resources": {
"limits": {
"memory": "200Mi"
},
"requests": {
"cpu": "100m",
"memory": "200Mi"
}
},
"volumeMounts": [
{
"name": "varlog",
"mountPath": "/var/log"
}
]
}
],
"terminationGracePeriodSeconds": 30,
"volumes": [
{
"name": "varlog",
"hostPath": {
"path": "/var/log"
}
}
]
}
}
}
}
Loading

0 comments on commit 3185c7f

Please sign in to comment.