From 30ded63a9dfbd7a8429e059d2024b4ecb4fb47ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20=C5=9Awi=C4=85tek?= Date: Fri, 27 Sep 2024 15:11:05 +0200 Subject: [PATCH] Allow ReplicaSet and Job metadata generators to use partial meta objects We only use metadata from Jobs and ReplicaSets, but require that full resources are supplied. This change relaxes this requirement, allowing PartialObjectMetadata resources to be used. This allows callers to use metadata informers and avoid having to receive and serialize non-metadata updates from the API Server. --- kubernetes/metadata/job.go | 7 +++-- kubernetes/metadata/job_test.go | 42 +++++++++++++++++++++++++- kubernetes/metadata/replicaset.go | 7 +++-- kubernetes/metadata/replicaset_test.go | 36 +++++++++++++++++++++- kubernetes/metadata/resource.go | 5 +-- 5 files changed, 87 insertions(+), 10 deletions(-) diff --git a/kubernetes/metadata/job.go b/kubernetes/metadata/job.go index ba13e826b2..f5006f61a9 100644 --- a/kubernetes/metadata/job.go +++ b/kubernetes/metadata/job.go @@ -18,6 +18,7 @@ package metadata import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" k8s "k8s.io/client-go/kubernetes" "k8s.io/client-go/tools/cache" @@ -65,7 +66,7 @@ func (jb *job) GenerateECS(obj kubernetes.Resource) mapstr.M { // GenerateK8s generates job metadata from a resource object func (jb *job) GenerateK8s(obj kubernetes.Resource, opts ...FieldOptions) mapstr.M { - _, ok := obj.(*kubernetes.Job) + _, ok := obj.(metav1.Object) if !ok { return nil } @@ -81,12 +82,12 @@ func (jb *job) GenerateFromName(name string, opts ...FieldOptions) mapstr.M { } if obj, ok, _ := jb.store.GetByKey(name); ok { - jobObj, ok := obj.(*kubernetes.Job) + res, ok := obj.(kubernetes.Resource) if !ok { return nil } - return jb.GenerateK8s(jobObj, opts...) + return jb.GenerateK8s(res, opts...) } return nil diff --git a/kubernetes/metadata/job_test.go b/kubernetes/metadata/job_test.go index 1f2e54bde6..d15278e508 100644 --- a/kubernetes/metadata/job_test.go +++ b/kubernetes/metadata/job_test.go @@ -39,7 +39,7 @@ func TestJob_Generate(t *testing.T) { client := k8sfake.NewSimpleClientset() boolean := true tests := []struct { - input kubernetes.Resource + input *batchv1.Job output mapstr.M name string }{ @@ -92,6 +92,10 @@ func TestJob_Generate(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { assert.Equal(t, test.output, metagen.Generate(test.input)) + objMeta := &metav1.PartialObjectMetadata{ + ObjectMeta: test.input.ObjectMeta, + } + assert.Equal(t, test.output, metagen.Generate(objMeta)) }) } } @@ -144,6 +148,42 @@ func TestJob_GenerateFromName(t *testing.T) { "namespace": defaultNs, }, }, + { + name: "test simple object with owner", + input: &metav1.PartialObjectMetadata{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + UID: types.UID(uid), + Namespace: defaultNs, + Labels: map[string]string{ + "foo": "bar", + }, + Annotations: map[string]string{}, + OwnerReferences: []metav1.OwnerReference{ + { + APIVersion: "apps", + Kind: "CronJob", + Name: "nginx-job", + UID: "005f3b90-4b9d-12f8-acf0-31020a840144", + Controller: &boolean, + }, + }, + }, + }, + output: mapstr.M{ + "job": mapstr.M{ + "name": name, + "uid": uid, + }, + "labels": mapstr.M{ + "foo": "bar", + }, + "cronjob": mapstr.M{ + "name": "nginx-job", + }, + "namespace": defaultNs, + }, + }, } for _, test := range tests { diff --git a/kubernetes/metadata/replicaset.go b/kubernetes/metadata/replicaset.go index 3fccc802df..794a2260da 100644 --- a/kubernetes/metadata/replicaset.go +++ b/kubernetes/metadata/replicaset.go @@ -18,6 +18,7 @@ package metadata import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" k8s "k8s.io/client-go/kubernetes" "k8s.io/client-go/tools/cache" @@ -67,7 +68,7 @@ func (rs *replicaset) GenerateECS(obj kubernetes.Resource) mapstr.M { // GenerateK8s generates replicaset metadata from a resource object func (rs *replicaset) GenerateK8s(obj kubernetes.Resource, opts ...FieldOptions) mapstr.M { - _, ok := obj.(*kubernetes.ReplicaSet) + _, ok := obj.(metav1.Object) if !ok { return nil } @@ -83,12 +84,12 @@ func (rs *replicaset) GenerateFromName(name string, opts ...FieldOptions) mapstr } if obj, ok, _ := rs.store.GetByKey(name); ok { - replicaSet, ok := obj.(*kubernetes.ReplicaSet) + res, ok := obj.(kubernetes.Resource) if !ok { return nil } - return rs.GenerateK8s(replicaSet, opts...) + return rs.GenerateK8s(res, opts...) } return nil diff --git a/kubernetes/metadata/replicaset_test.go b/kubernetes/metadata/replicaset_test.go index 1a19d1f8af..cb30fe8ad8 100644 --- a/kubernetes/metadata/replicaset_test.go +++ b/kubernetes/metadata/replicaset_test.go @@ -40,7 +40,7 @@ func TestReplicaset_Generate(t *testing.T) { client := k8sfake.NewSimpleClientset() boolean := true tests := []struct { - input kubernetes.Resource + input *appsv1.ReplicaSet output mapstr.M name string }{ @@ -111,6 +111,11 @@ func TestReplicaset_Generate(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { assert.Equal(t, test.output, metagen.Generate(test.input)) + // verify this works with just the metadata + objMeta := &metav1.PartialObjectMetadata{ + ObjectMeta: test.input.ObjectMeta, + } + assert.Equal(t, test.output, metagen.Generate(objMeta)) }) } } @@ -181,6 +186,35 @@ func TestReplicase_GenerateFromName(t *testing.T) { "namespace": defaultNs, }, }, + { + name: "test simple object with owner", + input: &metav1.PartialObjectMetadata{ + ObjectMeta: metav1.ObjectMeta{ + Name: "nginx-rs", + Namespace: defaultNs, + UID: uid, + OwnerReferences: []metav1.OwnerReference{ + { + APIVersion: "apps", + Kind: "Deployment", + Name: "nginx-deployment", + UID: "005f3b90-4b9d-12f8-acf0-31020a840144", + Controller: &boolean, + }, + }, + }, + }, + output: mapstr.M{ + "replicaset": mapstr.M{ + "name": "nginx-rs", + "uid": uid, + }, + "deployment": mapstr.M{ + "name": "nginx-deployment", + }, + "namespace": defaultNs, + }, + }, } for _, test := range tests { diff --git a/kubernetes/metadata/resource.go b/kubernetes/metadata/resource.go index 304605b45f..a0cbc88e59 100644 --- a/kubernetes/metadata/resource.go +++ b/kubernetes/metadata/resource.go @@ -21,10 +21,11 @@ import ( "regexp" "strings" + "github.com/elastic/elastic-agent-autodiscover/kubernetes" "k8s.io/apimachinery/pkg/api/meta" + k8s "k8s.io/client-go/kubernetes" - "github.com/elastic/elastic-agent-autodiscover/kubernetes" "github.com/elastic/elastic-agent-autodiscover/utils" "github.com/elastic/elastic-agent-libs/config" "github.com/elastic/elastic-agent-libs/mapstr" @@ -85,7 +86,7 @@ func (r *Resource) Generate(kind string, obj kubernetes.Resource, opts ...FieldO } // GenerateECS generates ECS metadata from a resource object -func (r *Resource) GenerateECS(obj kubernetes.Resource) mapstr.M { +func (r *Resource) GenerateECS(_ kubernetes.Resource) mapstr.M { ecsMeta := mapstr.M{} if r.clusterInfo.URL != "" { _, _ = ecsMeta.Put("orchestrator.cluster.url", r.clusterInfo.URL)