Kaynağa Gözat

Imp ns labels annots (#1896)

* [CORE-140] Implementation of nsLabel/nsAnnotation

Signed-off-by: Alex Meijer <ameijer@kubecost.com>

* remove commented code

Signed-off-by: Alex Meijer <ameijer@kubecost.com>

* updates to have namespace labels appear when aggregated

Signed-off-by: Alex Meijer <ameijer@kubecost.com>

* Add Intersection() tests

Signed-off-by: Alex Meijer <ameijer@kubecost.com>

* run bingen to set minimum version for these labels

Signed-off-by: Alex Meijer <ameijer@kubecost.com>

* rm agg meta

Signed-off-by: Alex Meijer <ameijer@kubecost.com>

* fix tests

Signed-off-by: Alex Meijer <ameijer@kubecost.com>

* add back in agg meta capability

Signed-off-by: Alex Meijer <ameijer@kubecost.com>

* add missing lines, always default to false

Signed-off-by: Alex Meijer <ameijer@kubecost.com>

---------

Signed-off-by: Alex Meijer <ameijer@kubecost.com>
Signed-off-by: Alex Meijer <ameijer@users.noreply.github.com>
Alex Meijer 2 yıl önce
ebeveyn
işleme
1abc346db0

+ 1 - 1
pkg/costmodel/aggregation.go

@@ -2265,7 +2265,7 @@ func (a *Accesses) ComputeAllocationHandler(w http.ResponseWriter, r *http.Reque
 	includeProportionalAssetResourceCosts := qp.GetBool("includeProportionalAssetResourceCosts", false)
 
 	// include aggregated labels/annotations if true
-	includeAggregatedMetadata := qp.GetBool("includeAggregatedMetadata", true)
+	includeAggregatedMetadata := qp.GetBool("includeAggregatedMetadata", false)
 
 	asr, err := a.Model.QueryAllocation(window, resolution, step, aggregateBy, includeIdle, idleByNode, includeProportionalAssetResourceCosts, includeAggregatedMetadata)
 	if err != nil {

+ 16 - 0
pkg/costmodel/allocation_helpers.go

@@ -926,6 +926,11 @@ func applyLabels(podMap map[podKey]*pod, nodeLabels map[nodeKey]map[string]strin
 				allocLabels = make(map[string]string)
 			}
 
+			nsLabels := alloc.Properties.NamespaceLabels
+			if nsLabels == nil {
+				nsLabels = make(map[string]string)
+			}
+
 			// Apply node labels first, then namespace labels, then pod labels
 			// so that pod labels overwrite namespace labels, which overwrite
 			// node labels.
@@ -943,6 +948,7 @@ func applyLabels(podMap map[podKey]*pod, nodeLabels map[nodeKey]map[string]strin
 			if labels, ok := namespaceLabels[nsKey]; ok {
 				for k, v := range labels {
 					allocLabels[k] = v
+					nsLabels[k] = v
 				}
 			}
 
@@ -953,6 +959,8 @@ func applyLabels(podMap map[podKey]*pod, nodeLabels map[nodeKey]map[string]strin
 			}
 
 			alloc.Properties.Labels = allocLabels
+			alloc.Properties.NamespaceLabels = nsLabels
+			
 		}
 	}
 }
@@ -964,11 +972,18 @@ func applyAnnotations(podMap map[podKey]*pod, namespaceAnnotations map[string]ma
 			if allocAnnotations == nil {
 				allocAnnotations = make(map[string]string)
 			}
+
+			nsAnnotations := alloc.Properties.NamespaceAnnotations
+			if nsAnnotations == nil {
+				nsAnnotations = make(map[string]string)
+			}
+
 			// Apply namespace annotations first, then pod annotations so that
 			// pod labels overwrite namespace labels.
 			if labels, ok := namespaceAnnotations[key.Namespace]; ok {
 				for k, v := range labels {
 					allocAnnotations[k] = v
+					nsAnnotations[k] = v
 				}
 			}
 			if labels, ok := podAnnotations[key]; ok {
@@ -978,6 +993,7 @@ func applyAnnotations(podMap map[podKey]*pod, namespaceAnnotations map[string]ma
 			}
 
 			alloc.Properties.Annotations = allocAnnotations
+			alloc.Properties.NamespaceAnnotations = nsAnnotations
 		}
 	}
 }

+ 1 - 3
pkg/kubecost/allocation.go

@@ -1174,10 +1174,8 @@ func (as *AllocationSet) AggregateBy(aggregateBy []string, options *AllocationAg
 	// them to their respective sets, removing them from the set of allocations
 	// to aggregate.
 	for _, alloc := range as.Allocations {
-		// if the user does not want any aggregated labels/annotations returned
-		// set the properties accordingly
-		alloc.Properties.AggregatedMetadata = options.IncludeAggregatedMetadata
 
+		alloc.Properties.AggregatedMetadata = options.IncludeAggregatedMetadata
 		// External allocations get aggregated post-hoc (see step 6) and do
 		// not necessarily contain complete sets of properties, so they are
 		// moved to a separate AllocationSet.

+ 88 - 11
pkg/kubecost/allocationprops.go

@@ -92,17 +92,19 @@ func ParseProperty(text string) (string, error) {
 
 // AllocationProperties describes a set of Kubernetes objects.
 type AllocationProperties struct {
-	Cluster        string                `json:"cluster,omitempty"`
-	Node           string                `json:"node,omitempty"`
-	Container      string                `json:"container,omitempty"`
-	Controller     string                `json:"controller,omitempty"`
-	ControllerKind string                `json:"controllerKind,omitempty"`
-	Namespace      string                `json:"namespace,omitempty"`
-	Pod            string                `json:"pod,omitempty"`
-	Services       []string              `json:"services,omitempty"`
-	ProviderID     string                `json:"providerID,omitempty"`
-	Labels         AllocationLabels      `json:"labels,omitempty"`
-	Annotations    AllocationAnnotations `json:"annotations,omitempty"`
+	Cluster              string                `json:"cluster,omitempty"`
+	Node                 string                `json:"node,omitempty"`
+	Container            string                `json:"container,omitempty"`
+	Controller           string                `json:"controller,omitempty"`
+	ControllerKind       string                `json:"controllerKind,omitempty"`
+	Namespace            string                `json:"namespace,omitempty"`
+	Pod                  string                `json:"pod,omitempty"`
+	Services             []string              `json:"services,omitempty"`
+	ProviderID           string                `json:"providerID,omitempty"`
+	Labels               AllocationLabels      `json:"labels,omitempty"`
+	Annotations          AllocationAnnotations `json:"annotations,omitempty"`
+	NamespaceLabels      AllocationLabels      `json:"namespaceLabels,omitempty"`      // @bingen:field[version=17]
+	NamespaceAnnotations AllocationAnnotations `json:"namespaceAnnotations,omitempty"` // @bingen:field[version=17]
 	// When set to true, maintain the intersection of all labels + annotations
 	// in the aggregated AllocationProperties object
 	AggregatedMetadata bool `json:"-"` //@bingen:field[ignore]
@@ -141,12 +143,24 @@ func (p *AllocationProperties) Clone() *AllocationProperties {
 	}
 	clone.Labels = labels
 
+	nsLabels := make(map[string]string, len(p.NamespaceLabels))
+	for k, v := range p.NamespaceLabels {
+		nsLabels[k] = v
+	}
+	clone.NamespaceLabels = nsLabels
+
 	annotations := make(map[string]string, len(p.Annotations))
 	for k, v := range p.Annotations {
 		annotations[k] = v
 	}
 	clone.Annotations = annotations
 
+	nsAnnotations := make(map[string]string, len(p.NamespaceAnnotations))
+	for k, v := range p.NamespaceAnnotations {
+		nsAnnotations[k] = v
+	}
+	clone.NamespaceAnnotations = nsAnnotations
+
 	return clone
 }
 
@@ -200,6 +214,19 @@ func (p *AllocationProperties) Equal(that *AllocationProperties) bool {
 		return false
 	}
 
+	pNamespaceLabels := p.NamespaceLabels
+	thatNamespaceLabels := that.NamespaceLabels
+	if len(pNamespaceLabels) == len(thatNamespaceLabels) {
+		for k, pv := range pNamespaceLabels {
+			tv, ok := thatNamespaceLabels[k]
+			if !ok || tv != pv {
+				return false
+			}
+		}
+	} else {
+		return false
+	}
+
 	pAnnotations := p.Annotations
 	thatAnnotations := that.Annotations
 	if len(pAnnotations) == len(thatAnnotations) {
@@ -213,6 +240,19 @@ func (p *AllocationProperties) Equal(that *AllocationProperties) bool {
 		return false
 	}
 
+	pNamespaceAnnotations := p.NamespaceAnnotations
+	thatNamespaceAnnotations := that.NamespaceAnnotations
+	if len(pNamespaceAnnotations) == len(thatNamespaceAnnotations) {
+		for k, pv := range pNamespaceAnnotations {
+			tv, ok := thatNamespaceAnnotations[k]
+			if !ok || tv != pv {
+				return false
+			}
+		}
+	} else {
+		return false
+	}
+
 	pServices := p.Services
 	thatServices := that.Services
 	if len(pServices) == len(thatServices) {
@@ -452,6 +492,20 @@ func (p *AllocationProperties) Intersection(that *AllocationProperties) *Allocat
 	if p.Namespace == that.Namespace {
 
 		intersectionProps.Namespace = p.Namespace
+
+		// CORE-140: In the case that the namespace is the same, also copy over the namespaceLabels and annotations
+		// Note - assume that if the namespace is the same on both, then namespace label/annotation sets
+		// will be the same, so just carry one set over
+		if p.Container == UnmountedSuffix {
+			// This logic is designed to effectively ignore the unmounted/unallocated objects
+			// and just copy over the labels from the other, 'legitimate' allocation.
+			intersectionProps.NamespaceLabels = copyStringMap(that.NamespaceLabels)
+			intersectionProps.NamespaceAnnotations = copyStringMap(that.NamespaceAnnotations)
+		} else {
+			intersectionProps.NamespaceLabels = copyStringMap(p.NamespaceLabels)
+			intersectionProps.NamespaceAnnotations = copyStringMap(p.NamespaceAnnotations)
+		}
+
 		// ignore the incoming labels from unallocated or unmounted special case pods
 		if p.AggregatedMetadata || that.AggregatedMetadata {
 			intersectionProps.AggregatedMetadata = true
@@ -476,15 +530,26 @@ func (p *AllocationProperties) Intersection(that *AllocationProperties) *Allocat
 			}
 		}
 	}
+
 	if p.Pod == that.Pod {
 		intersectionProps.Pod = p.Pod
 	}
 	if p.ProviderID == that.ProviderID {
 		intersectionProps.ProviderID = p.ProviderID
 	}
+
 	return intersectionProps
 }
 
+func copyStringMap(original map[string]string) map[string]string {
+	copy := make(map[string]string)
+	for key, value := range original {
+		copy[key] = value
+	}
+
+	return copy
+}
+
 func mapIntersection(map1, map2 map[string]string) map[string]string {
 	result := make(map[string]string)
 	for key, value := range map1 {
@@ -548,11 +613,23 @@ func (p *AllocationProperties) String() string {
 	}
 	strs = append(strs, fmt.Sprintf("Labels:{%s}", strings.Join(labelStrs, ",")))
 
+	var nsLabelStrs []string
+	for k, prop := range p.NamespaceLabels {
+		nsLabelStrs = append(nsLabelStrs, fmt.Sprintf("%s:%s", k, prop))
+	}
+	strs = append(strs, fmt.Sprintf("NamespaceLabels:{%s}", strings.Join(nsLabelStrs, ",")))
+
 	var annotationStrs []string
 	for k, prop := range p.Annotations {
 		annotationStrs = append(annotationStrs, fmt.Sprintf("%s:%s", k, prop))
 	}
 	strs = append(strs, fmt.Sprintf("Annotations:{%s}", strings.Join(annotationStrs, ",")))
 
+	var nsAnnotationStrs []string
+	for k, prop := range p.NamespaceAnnotations {
+		nsAnnotationStrs = append(nsAnnotationStrs, fmt.Sprintf("%s:%s", k, prop))
+	}
+	strs = append(strs, fmt.Sprintf("NamespaceAnnotations:{%s}", strings.Join(nsAnnotationStrs, ",")))
+
 	return fmt.Sprintf("{%s}", strings.Join(strs, "; "))
 }

+ 151 - 23
pkg/kubecost/allocationprops_test.go

@@ -21,8 +21,10 @@ func TestAllocationPropsIntersection(t *testing.T) {
 				Annotations: map[string]string{},
 			},
 			expected: &AllocationProperties{
-				Labels:      nil,
-				Annotations: nil,
+				Labels:               nil,
+				Annotations:          nil,
+				NamespaceLabels:      map[string]string{},
+				NamespaceAnnotations: map[string]string{},
 			},
 		},
 		"nil intersection": {
@@ -30,7 +32,7 @@ func TestAllocationPropsIntersection(t *testing.T) {
 			allocationProps2: nil,
 			expected:         nil,
 		},
-		"intersection, with labels/annotations, no aggregated metdata": {
+		"intersection, with labels/annotations, no aggregated metadata": {
 			allocationProps1: &AllocationProperties{
 				AggregatedMetadata: false,
 				Node:               "node1",
@@ -44,13 +46,15 @@ func TestAllocationPropsIntersection(t *testing.T) {
 				Annotations:        map[string]string{"key4": "val4"},
 			},
 			expected: &AllocationProperties{
-				AggregatedMetadata: false,
-				Node:               "node1",
-				Labels:             nil,
-				Annotations:        nil,
+				AggregatedMetadata:   false,
+				Node:                 "node1",
+				Labels:               nil,
+				Annotations:          nil,
+				NamespaceLabels:      map[string]string{},
+				NamespaceAnnotations: map[string]string{},
 			},
 		},
-		"intersection, with labels/annotations, with aggregated metdata": {
+		"intersection, with labels/annotations, same values": {
 			allocationProps1: &AllocationProperties{
 				AggregatedMetadata: false,
 				ControllerKind:     "controller1",
@@ -66,11 +70,13 @@ func TestAllocationPropsIntersection(t *testing.T) {
 				Annotations:        map[string]string{"key2": "val2"},
 			},
 			expected: &AllocationProperties{
-				AggregatedMetadata: true,
-				Namespace:          "ns1",
-				ControllerKind:     "",
-				Labels:             map[string]string{"key1": "val1"},
-				Annotations:        map[string]string{"key2": "val2"},
+				AggregatedMetadata:   true,
+				Namespace:            "ns1",
+				ControllerKind:       "",
+				Labels:               map[string]string{"key1": "val1"},
+				Annotations:          map[string]string{"key2": "val2"},
+				NamespaceLabels:      map[string]string{},
+				NamespaceAnnotations: map[string]string{},
 			},
 		},
 		"intersection, with labels/annotations, special case container": {
@@ -89,11 +95,13 @@ func TestAllocationPropsIntersection(t *testing.T) {
 				Annotations:        map[string]string{"key2": "val2"},
 			},
 			expected: &AllocationProperties{
-				AggregatedMetadata: true,
-				Namespace:          "ns1",
-				ControllerKind:     "",
-				Labels:             map[string]string{"key1": "val1"},
-				Annotations:        map[string]string{"key2": "val2"},
+				AggregatedMetadata:   true,
+				Namespace:            "ns1",
+				ControllerKind:       "",
+				Labels:               map[string]string{"key1": "val1"},
+				Annotations:          map[string]string{"key2": "val2"},
+				NamespaceLabels:      map[string]string{},
+				NamespaceAnnotations: map[string]string{},
 			},
 		},
 		"test services are nulled when intersecting": {
@@ -115,11 +123,13 @@ func TestAllocationPropsIntersection(t *testing.T) {
 				Annotations:        map[string]string{"key2": "val2"},
 			},
 			expected: &AllocationProperties{
-				AggregatedMetadata: true,
-				Namespace:          "ns1",
-				ControllerKind:     "",
-				Labels:             map[string]string{"key1": "val1"},
-				Annotations:        map[string]string{"key2": "val2"},
+				AggregatedMetadata:   true,
+				Namespace:            "ns1",
+				ControllerKind:       "",
+				Labels:               map[string]string{"key1": "val1"},
+				Annotations:          map[string]string{"key2": "val2"},
+				NamespaceLabels:      map[string]string{},
+				NamespaceAnnotations: map[string]string{},
 			},
 		},
 	}
@@ -237,3 +247,121 @@ func TestGenerateKey(t *testing.T) {
 		})
 	}
 }
+
+func TestIntersection(t *testing.T) {
+
+	propsEmpty := AllocationProperties{}
+
+	propsMedium := AllocationProperties{
+		Cluster:        "cluster1",
+		Node:           "Node1",
+		Container:      "container1",
+		Controller:     "controller1",
+		ControllerKind: "controllerkind1",
+		Namespace:      "ns1",
+		Pod:            "pod1",
+		Services:       []string{"service1"},
+		ProviderID:     "provider1",
+	}
+
+	propsFull := AllocationProperties{
+		Cluster:              "cluster2",
+		Node:                 "Node2",
+		Container:            "container2",
+		Controller:           "controller2",
+		ControllerKind:       "controllerkind2",
+		Namespace:            "ns2",
+		Pod:                  "pod2",
+		Services:             []string{"service2"},
+		ProviderID:           "provider2",
+		NamespaceLabels:      AllocationLabels{"key1": "value1"},
+		NamespaceAnnotations: AllocationAnnotations{"key2": "value2", "key5": "value5"},
+		Labels:               AllocationLabels{"key3": "value3"},
+		Annotations:          AllocationAnnotations{"key4": "value4"},
+	}
+
+	// Case 1: no intersection
+	// expect empty result object
+	testObj1 := AllocationProperties{}
+
+	result := testObj1.Intersection(&propsEmpty)
+
+	if !result.Equal(&propsEmpty) {
+		t.Fatalf("Case 1: expected empty object, no intersection")
+	}
+
+	// Case 2: Only has labels/annotations
+	// expect empty result object
+	testObj2 := AllocationProperties{
+		Labels:      map[string]string{"app": "product-label-light"},
+		Annotations: map[string]string{"app": "product-annotation-light"},
+	}
+
+	result = testObj2.Intersection(&propsMedium)
+
+	if !result.Equal(&propsEmpty) {
+		t.Fatalf("Case 2: expected empty object, no intersection")
+	}
+
+	// Case 3: Has non-label/annotations set
+	// expect all non label/annotation/service string array fields to be unset
+	// different container names should be omitted
+	testObj3 := AllocationProperties{
+		Cluster:        "cluster1",
+		Node:           "Node1",
+		Container:      "container2",
+		Controller:     "controller1",
+		ControllerKind: "controllerkind1",
+		Namespace:      "ns1",
+		Pod:            "pod1",
+		Services:       []string{"service1"},
+		ProviderID:     "provider1",
+	}
+
+	expectedResult := AllocationProperties{
+		Cluster:        "cluster1",
+		Node:           "Node1",
+		Controller:     "controller1",
+		ControllerKind: "controllerkind1",
+		Namespace:      "ns1",
+		Pod:            "pod1",
+		ProviderID:     "provider1",
+	}
+
+	result = testObj3.Intersection(&propsMedium)
+
+	if !result.Equal(&expectedResult) {
+		t.Fatalf("Case 3: expected output %v does not match actual output %v", expectedResult, result)
+	}
+
+	// Case 4: Copy over NamespaceLabels/Annots when namespace is the same
+	testObj4 := AllocationProperties{
+		Cluster:              "cluster2",
+		Node:                 "NodeX",
+		Container:            "containerX",
+		Controller:           "controllerX",
+		ControllerKind:       "controllerkindX",
+		Namespace:            "ns2",
+		Pod:                  "podX",
+		Services:             []string{"serviceX"},
+		ProviderID:           "providerX",
+		NamespaceLabels:      AllocationLabels{"key1": "value1"},
+		NamespaceAnnotations: AllocationAnnotations{"key2": "value2", "key5": "value5"},
+		Labels:               AllocationLabels{"key3": "value3"},
+		Annotations:          AllocationAnnotations{"key4": "value4"},
+	}
+
+	expectedResult = AllocationProperties{
+		Cluster:              "cluster2",
+		Namespace:            "ns2",
+		NamespaceLabels:      AllocationLabels{"key1": "value1"},
+		NamespaceAnnotations: AllocationAnnotations{"key2": "value2", "key5": "value5"},
+	}
+
+	result = testObj4.Intersection(&propsFull)
+
+	if !result.Equal(&expectedResult) {
+		t.Fatalf("Case 4: expected output %v does not match actual output %v", expectedResult, result)
+	}
+
+}

+ 1 - 1
pkg/kubecost/bingen.go

@@ -46,7 +46,7 @@ package kubecost
 // @bingen:end
 
 // Allocation Version Set: Includes Allocation pipeline specific resources
-// @bingen:set[name=Allocation,version=16]
+// @bingen:set[name=Allocation,version=17]
 // @bingen:generate:Allocation
 // @bingen:generate[stringtable]:AllocationSet
 // @bingen:generate:AllocationSetRange

+ 147 - 2
pkg/kubecost/kubecost_codecs.go

@@ -13,11 +13,12 @@ package kubecost
 
 import (
 	"fmt"
-	util "github.com/opencost/opencost/pkg/util"
 	"reflect"
 	"strings"
 	"sync"
 	"time"
+
+	util "github.com/opencost/opencost/pkg/util"
 )
 
 const (
@@ -40,7 +41,7 @@ const (
 	AssetsCodecVersion uint8 = 19
 
 	// AllocationCodecVersion is used for any resources listed in the Allocation version set
-	AllocationCodecVersion uint8 = 16
+	AllocationCodecVersion uint8 = 17
 
 	// AuditCodecVersion is used for any resources listed in the Audit version set
 	AuditCodecVersion uint8 = 1
@@ -1186,6 +1187,60 @@ func (target *AllocationProperties) MarshalBinaryWithContext(ctx *EncodingContex
 	}
 	// --- [end][write][alias](AllocationAnnotations) ---
 
+	// --- [begin][write][alias](AllocationLabels) ---
+	if map[string]string(target.NamespaceLabels) == nil {
+		buff.WriteUInt8(uint8(0)) // write nil byte
+	} else {
+		buff.WriteUInt8(uint8(1)) // write non-nil byte
+
+		// --- [begin][write][map](map[string]string) ---
+		buff.WriteInt(len(map[string]string(target.NamespaceLabels))) // map length
+		for vvv, zzz := range map[string]string(target.NamespaceLabels) {
+			if ctx.IsStringTable() {
+				p := ctx.Table.AddOrGet(vvv)
+				buff.WriteInt(p) // write table index
+			} else {
+				buff.WriteString(vvv) // write string
+			}
+			if ctx.IsStringTable() {
+				q := ctx.Table.AddOrGet(zzz)
+				buff.WriteInt(q) // write table index
+			} else {
+				buff.WriteString(zzz) // write string
+			}
+		}
+		// --- [end][write][map](map[string]string) ---
+
+	}
+	// --- [end][write][alias](AllocationLabels) ---
+
+	// --- [begin][write][alias](AllocationAnnotations) ---
+	if map[string]string(target.NamespaceAnnotations) == nil {
+		buff.WriteUInt8(uint8(0)) // write nil byte
+	} else {
+		buff.WriteUInt8(uint8(1)) // write non-nil byte
+
+		// --- [begin][write][map](map[string]string) ---
+		buff.WriteInt(len(map[string]string(target.NamespaceAnnotations))) // map length
+		for vvvv, zzzz := range map[string]string(target.NamespaceAnnotations) {
+			if ctx.IsStringTable() {
+				r := ctx.Table.AddOrGet(vvvv)
+				buff.WriteInt(r) // write table index
+			} else {
+				buff.WriteString(vvvv) // write string
+			}
+			if ctx.IsStringTable() {
+				s := ctx.Table.AddOrGet(zzzz)
+				buff.WriteInt(s) // write table index
+			} else {
+				buff.WriteString(zzzz) // write string
+			}
+		}
+		// --- [end][write][map](map[string]string) ---
+
+	}
+	// --- [end][write][alias](AllocationAnnotations) ---
+
 	return nil
 }
 
@@ -1427,6 +1482,96 @@ func (target *AllocationProperties) UnmarshalBinaryWithContext(ctx *DecodingCont
 	target.Annotations = AllocationAnnotations(tt)
 	// --- [end][read][alias](AllocationAnnotations) ---
 
+	// field version check
+	if uint8(17) <= version {
+		// --- [begin][read][alias](AllocationLabels) ---
+		var eee map[string]string
+		if buff.ReadUInt8() == uint8(0) {
+			eee = nil
+		} else {
+			// --- [begin][read][map](map[string]string) ---
+			ggg := buff.ReadInt() // map len
+			fff := make(map[string]string, ggg)
+			for jj := 0; jj < ggg; jj++ {
+				var vvv string
+				var kkk string
+				if ctx.IsStringTable() {
+					lll := buff.ReadInt() // read string index
+					kkk = ctx.Table[lll]
+				} else {
+					kkk = buff.ReadString() // read string
+				}
+				hhh := kkk
+				vvv = hhh
+
+				var zzz string
+				var nnn string
+				if ctx.IsStringTable() {
+					ooo := buff.ReadInt() // read string index
+					nnn = ctx.Table[ooo]
+				} else {
+					nnn = buff.ReadString() // read string
+				}
+				mmm := nnn
+				zzz = mmm
+
+				fff[vvv] = zzz
+			}
+			eee = fff
+			// --- [end][read][map](map[string]string) ---
+
+		}
+		target.NamespaceLabels = AllocationLabels(eee)
+		// --- [end][read][alias](AllocationLabels) ---
+
+	} else {
+	}
+
+	// field version check
+	if uint8(17) <= version {
+		// --- [begin][read][alias](AllocationAnnotations) ---
+		var ppp map[string]string
+		if buff.ReadUInt8() == uint8(0) {
+			ppp = nil
+		} else {
+			// --- [begin][read][map](map[string]string) ---
+			rrr := buff.ReadInt() // map len
+			qqq := make(map[string]string, rrr)
+			for iii := 0; iii < rrr; iii++ {
+				var vvvv string
+				var ttt string
+				if ctx.IsStringTable() {
+					uuu := buff.ReadInt() // read string index
+					ttt = ctx.Table[uuu]
+				} else {
+					ttt = buff.ReadString() // read string
+				}
+				sss := ttt
+				vvvv = sss
+
+				var zzzz string
+				var xxx string
+				if ctx.IsStringTable() {
+					yyy := buff.ReadInt() // read string index
+					xxx = ctx.Table[yyy]
+				} else {
+					xxx = buff.ReadString() // read string
+				}
+				www := xxx
+				zzzz = www
+
+				qqq[vvvv] = zzzz
+			}
+			ppp = qqq
+			// --- [end][read][map](map[string]string) ---
+
+		}
+		target.NamespaceAnnotations = AllocationAnnotations(ppp)
+		// --- [end][read][alias](AllocationAnnotations) ---
+
+	} else {
+	}
+
 	return nil
 }
 

+ 12 - 0
pkg/kubecost/kubecost_codecs_test.go

@@ -480,6 +480,14 @@ func TestProperties_BinaryEncoding(t *testing.T) {
 	p0.Controller = "daemonset-abc"
 	p0.ControllerKind = "daemonset"
 	p0.Namespace = "namespace1"
+	p0.NamespaceLabels = map[string]string{
+		"app":                "cost-analyzer-namespace",
+		"kubernetes.io/name": "cost-analyzer",
+	}
+	p0.NamespaceAnnotations = map[string]string{
+		"com.kubernetes.io/managed-by":             "helm",
+		"kubernetes.io/last-applied-configuration": "cost-analyzer",
+	}
 	p0.Node = "node1"
 	p0.Pod = "daemonset-abc-123"
 	p0.Labels = map[string]string{
@@ -508,6 +516,10 @@ func TestProperties_BinaryEncoding(t *testing.T) {
 	p0.Controller = "daemonset-abc"
 	p0.ControllerKind = "daemonset"
 	p0.Namespace = "namespace1"
+	p0.NamespaceAnnotations = map[string]string{
+		"com.kubernetes.io/managed-by":             "helm",
+		"kubernetes.io/last-applied-configuration": "cost-analyzer",
+	}
 	p0.Services = []string{}
 	bs, err = p0.MarshalBinary()
 	if err != nil {