Skip to content

Commit

Permalink
Merge pull request #33 from tetianakravchenko/k8s-namespaced-aware-re…
Browse files Browse the repository at this point in the history
…sources

Add namespace MetaGen to the generic Resource
  • Loading branch information
tetianakravchenko authored Nov 21, 2022
2 parents 2c727af + bec6581 commit 6ee6924
Show file tree
Hide file tree
Showing 4 changed files with 174 additions and 40 deletions.
21 changes: 7 additions & 14 deletions kubernetes/metadata/pod.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ type pod struct {
store cache.Store
client k8s.Interface
node MetaGen
namespace MetaGen
resource *Resource
addResourceMetadata *AddResourceMetadataConfig
}
Expand All @@ -48,21 +47,22 @@ func NewPodMetadataGenerator(
addResourceMetadata *AddResourceMetadataConfig) MetaGen {

return &pod{
resource: NewResourceMetadataGenerator(cfg, client),
resource: NewNamespaceAwareResourceMetadataGenerator(cfg, client, namespace),
store: pods,
node: node,
namespace: namespace,
client: client,
addResourceMetadata: addResourceMetadata,
}
}

// Generate generates pod metadata from a resource object
// Metadata map is in the following form:
// {
// "kubernetes": {},
// "some.ecs.field": "asdf"
// }
//
// {
// "kubernetes": {},
// "some.ecs.field": "asdf"
// }
//
// All Kubernetes fields that need to be stored under kubernetes. prefix are populated by
// GenerateK8s method while fields that are part of ECS are generated by GenerateECS method
func (p *pod) Generate(obj kubernetes.Resource, opts ...FieldOptions) mapstr.M {
Expand Down Expand Up @@ -122,13 +122,6 @@ func (p *pod) GenerateK8s(obj kubernetes.Resource, opts ...FieldOptions) mapstr.
_, _ = out.Put("node.name", po.Spec.NodeName)
}

if p.namespace != nil {
meta := p.namespace.GenerateFromName(po.GetNamespace())
if meta != nil {
out.DeepUpdate(meta)
}
}

if po.Status.PodIP != "" {
_, _ = out.Put("pod.ip", po.Status.PodIP)
}
Expand Down
32 changes: 25 additions & 7 deletions kubernetes/metadata/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ const deploymentType = "Deployment"
type Resource struct {
config *Config
clusterInfo ClusterInfo
namespace MetaGen
}

// NewResourceMetadataGenerator creates a metadata generator for a generic resource
Expand All @@ -56,14 +57,23 @@ func NewResourceMetadataGenerator(cfg *config.C, client k8s.Interface) *Resource
return r
}

// NewNamespaceAwareResourceMetadataGenerator creates a metadata generator with informatuon about namespace
func NewNamespaceAwareResourceMetadataGenerator(cfg *config.C, client k8s.Interface, namespace MetaGen) *Resource {
r := NewResourceMetadataGenerator(cfg, client)
r.namespace = namespace
return r
}

// Generate generates metadata from a resource object
// Generate method returns metadata in the following form:
// {
// "kubernetes": {},
// "ecs.a.field": 42,
// }
//
// {
// "kubernetes": {},
// "ecs.a.field": 42,
// }
//
// This method should be called in top level and not as part of other metadata generators.
// For retrieving metadata without kubernetes. prefix one should call GenerateK8s instead.
// For retrieving metadata without 'kubernetes.' prefix one should call GenerateK8s instead.
func (r *Resource) Generate(kind string, obj kubernetes.Resource, opts ...FieldOptions) mapstr.M {
ecsFields := r.GenerateECS(obj)
meta := mapstr.M{
Expand Down Expand Up @@ -113,8 +123,16 @@ func (r *Resource) GenerateK8s(kind string, obj kubernetes.Resource, options ...
},
}

if accessor.GetNamespace() != "" {
_ = safemapstr.Put(meta, "namespace", accessor.GetNamespace())
namespaceName := accessor.GetNamespace()
if namespaceName != "" {
_ = safemapstr.Put(meta, "namespace", namespaceName)

if r.namespace != nil {
nsMeta := r.namespace.GenerateFromName(namespaceName)
if nsMeta != nil {
meta.DeepUpdate(nsMeta)
}
}
}

// Add controller metadata if present
Expand Down
132 changes: 132 additions & 0 deletions kubernetes/metadata/resource_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,15 @@ import (

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
appsv1 "k8s.io/api/apps/v1"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
k8sfake "k8s.io/client-go/kubernetes/fake"
"k8s.io/client-go/tools/cache"

"github.com/elastic/elastic-agent-autodiscover/kubernetes"
"github.com/elastic/elastic-agent-libs/config"
"github.com/elastic/elastic-agent-libs/mapstr"
"github.com/elastic/go-ucfg"
)
Expand Down Expand Up @@ -130,3 +134,131 @@ func TestResource_Generate(t *testing.T) {
})
}
}

func TestNamespaceAwareResource_GenerateWithNamespace(t *testing.T) {
client := k8sfake.NewSimpleClientset()
tests := []struct {
resourceName string
input kubernetes.Resource
namespace kubernetes.Resource
output mapstr.M
name string
}{
{
name: "test not namespaced kubernetes resource - PersistentVolume",
resourceName: "persistentvolume",
input: &v1.PersistentVolume{
ObjectMeta: metav1.ObjectMeta{
Name: "pvc-18705cfb-9fb8-441f-9b32-0d67a21af839",
UID: "020fd954-3674-496a-9e77-c25f0f2257ea",
Labels: map[string]string{
"foo": "bar",
},
Annotations: map[string]string{},
},
TypeMeta: metav1.TypeMeta{
Kind: "PersistentVolume",
APIVersion: "v1",
},
},
namespace: &v1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: defaultNs,
UID: types.UID(uid),
Labels: map[string]string{
"nskey": "nsvalue",
},
Annotations: map[string]string{
"ns.annotation": "value",
},
},
TypeMeta: metav1.TypeMeta{
Kind: "Namespace",
APIVersion: "v1",
},
},
output: mapstr.M{
"kubernetes": mapstr.M{
"persistentvolume": mapstr.M{
"name": "pvc-18705cfb-9fb8-441f-9b32-0d67a21af839",
"uid": "020fd954-3674-496a-9e77-c25f0f2257ea",
},
"labels": mapstr.M{
"foo": "bar",
},
},
},
},
{
name: "test namespaced kubernetes resource",
resourceName: "deployment",
input: &appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: "default",
UID: "f33ca314-8cc5-48ea-90b7-3102c7430f75",
Labels: map[string]string{
"foo": "bar",
},
Annotations: map[string]string{},
},
TypeMeta: metav1.TypeMeta{
Kind: "Deployment",
APIVersion: "apps/v1",
},
},
namespace: &v1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: defaultNs,
UID: types.UID(uid),
Labels: map[string]string{
"nskey": "nsvalue",
},
Annotations: map[string]string{
"ns.annotation": "value",
},
},
TypeMeta: metav1.TypeMeta{
Kind: "Namespace",
APIVersion: "v1",
},
},
output: mapstr.M{
"kubernetes": mapstr.M{
"deployment": mapstr.M{
"name": name,
"uid": "f33ca314-8cc5-48ea-90b7-3102c7430f75",
},
"labels": mapstr.M{
"foo": "bar",
},
"namespace": "default",
"namespace_uid": uid,
"namespace_labels": mapstr.M{
"nskey": "nsvalue",
},
"namespace_annotations": mapstr.M{
"ns_annotation": "value",
},
},
},
},
}

for _, test := range tests {
nsConfig, err := config.NewConfigFrom(map[string]interface{}{
"include_annotations": []string{"ns.annotation"},
})
require.NoError(t, err)

namespaces := cache.NewStore(cache.MetaNamespaceKeyFunc)
err = namespaces.Add(test.namespace)
require.NoError(t, err)
nsMeta := NewNamespaceMetadataGenerator(nsConfig, namespaces, client)

metagen := NewNamespaceAwareResourceMetadataGenerator(nsConfig, client, nsMeta)
t.Run(test.name, func(t *testing.T) {
assert.Equal(t, test.output, metagen.Generate(test.resourceName, test.input))
})
}
}
29 changes: 10 additions & 19 deletions kubernetes/metadata/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,26 +28,26 @@ import (
)

type service struct {
store cache.Store
namespace MetaGen
resource *Resource
store cache.Store
resource *Resource
}

// NewServiceMetadataGenerator creates a metagen for service resources
func NewServiceMetadataGenerator(cfg *config.C, services cache.Store, namespace MetaGen, client k8s.Interface) MetaGen {
return &service{
resource: NewResourceMetadataGenerator(cfg, client),
store: services,
namespace: namespace,
resource: NewNamespaceAwareResourceMetadataGenerator(cfg, client, namespace),
store: services,
}
}

// Generate generates service metadata from a resource object
// Metadata map is in the following form:
// {
// "kubernetes": {},
// "some.ecs.field": "asdf"
// }
//
// {
// "kubernetes": {},
// "some.ecs.field": "asdf"
// }
//
// All Kubernetes fields that need to be stored under kuberentes. prefix are populetad by
// GenerateK8s method while fields that are part of ECS are generated by GenerateECS method
func (s *service) Generate(obj kubernetes.Resource, opts ...FieldOptions) mapstr.M {
Expand All @@ -73,15 +73,6 @@ func (s *service) GenerateK8s(obj kubernetes.Resource, opts ...FieldOptions) map

out := s.resource.GenerateK8s("service", obj, opts...)

if s.namespace != nil {
meta := s.namespace.GenerateFromName(svc.GetNamespace())
if meta != nil {
// Use this in 8.0
//out.Put("namespace", meta["namespace"])
out.DeepUpdate(meta)
}
}

selectors := svc.Spec.Selector
if len(selectors) == 0 {
return out
Expand Down

0 comments on commit 6ee6924

Please sign in to comment.