Skip to content

Commit 09323ed

Browse files
committed
[processor/k8sattributes] Support name:tag@digest image name format
1 parent eac16d3 commit 09323ed

File tree

8 files changed

+129
-40
lines changed

8 files changed

+129
-40
lines changed

.chloggen/fix-k8s-image-parsing.yaml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Use this changelog template to create an entry for release notes.
2+
3+
# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix'
4+
change_type: bug_fix
5+
6+
# The name of the component, or a single word describing the area of concern, (e.g. filelogreceiver)
7+
component: processor/k8sattribute
8+
9+
# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
10+
note: fixes parsing of k8s image names to support images with tags and digests.
11+
12+
# Mandatory: One or more tracking issues related to the change. You can use the PR number here if no issue exists.
13+
issues: [36131]
14+
15+
# (Optional) One or more lines of additional information to render under the primary note.
16+
# These lines will be padded with 2 spaces and then inserted directly into the document.
17+
# Use pipe (|) for multiline entries.
18+
subtext:
19+
20+
# If your change doesn't affect end users or the exported elements of any package,
21+
# you should instead start your pull request title with [chore] or use the "Skip Changelog" label.
22+
# Optional: The change log or logs in which this entry should be included.
23+
# e.g. '[user]' or '[user, api]'
24+
# Include 'user' if the change is relevant to end users.
25+
# Include 'api' if there is a change to a library API.
26+
# Default: '[user]'
27+
change_logs: [user]

processor/k8sattributesprocessor/e2e_test.go

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ func newExpectedValue(mode int, value string) *expectedValue {
5252

5353
// TestE2E_ClusterRBAC tests the k8s attributes processor in a k8s cluster with the collector's service account having
5454
// cluster-wide permissions to list/watch namespaces, nodes, pods and replicasets. The config in the test does not
55-
// set filter::namespace.
55+
// set filter::namespace, and the telemetrygen image has a latest tag but no digest.
5656
// The test requires a prebuilt otelcontribcol image uploaded to a kind k8s cluster defined in
5757
// `/tmp/kube-config-otelcol-e2e-testing`. Run the following command prior to running the test locally:
5858
//
@@ -432,7 +432,8 @@ func TestE2E_ClusterRBAC(t *testing.T) {
432432
}
433433
}
434434

435-
// Test with `filter::namespace` set and only role binding to collector's SA. We can't get node and namespace labels/annotations.
435+
// Test with `filter::namespace` set and only role binding to collector's SA. We can't get node and namespace labels/annotations,
436+
// and the telemetrygen image has a digest but no tag.
436437
func TestE2E_NamespacedRBAC(t *testing.T) {
437438

438439
testDir := filepath.Join("testdata", "e2e", "namespacedrbac")
@@ -504,7 +505,7 @@ func TestE2E_NamespacedRBAC(t *testing.T) {
504505
"k8s.container.name": newExpectedValue(equal, "telemetrygen"),
505506
"container.image.name": newExpectedValue(equal, "ghcr.io/open-telemetry/opentelemetry-collector-contrib/telemetrygen"),
506507
"container.image.repo_digests": newExpectedValue(regex, "ghcr.io/open-telemetry/opentelemetry-collector-contrib/telemetrygen@sha256:[0-9a-fA-f]{64}"),
507-
"container.image.tag": newExpectedValue(equal, "latest"),
508+
"container.image.tag": newExpectedValue(exist, ""),
508509
"container.id": newExpectedValue(exist, ""),
509510
},
510511
},
@@ -528,7 +529,7 @@ func TestE2E_NamespacedRBAC(t *testing.T) {
528529
"k8s.container.name": newExpectedValue(equal, "telemetrygen"),
529530
"container.image.name": newExpectedValue(equal, "ghcr.io/open-telemetry/opentelemetry-collector-contrib/telemetrygen"),
530531
"container.image.repo_digests": newExpectedValue(regex, "ghcr.io/open-telemetry/opentelemetry-collector-contrib/telemetrygen@sha256:[0-9a-fA-f]{64}"),
531-
"container.image.tag": newExpectedValue(equal, "latest"),
532+
"container.image.tag": newExpectedValue(exist, ""),
532533
"container.id": newExpectedValue(exist, ""),
533534
},
534535
},
@@ -552,7 +553,7 @@ func TestE2E_NamespacedRBAC(t *testing.T) {
552553
"k8s.container.name": newExpectedValue(equal, "telemetrygen"),
553554
"container.image.name": newExpectedValue(equal, "ghcr.io/open-telemetry/opentelemetry-collector-contrib/telemetrygen"),
554555
"container.image.repo_digests": newExpectedValue(regex, "ghcr.io/open-telemetry/opentelemetry-collector-contrib/telemetrygen@sha256:[0-9a-fA-f]{64}"),
555-
"container.image.tag": newExpectedValue(equal, "latest"),
556+
"container.image.tag": newExpectedValue(exist, ""),
556557
"container.id": newExpectedValue(exist, ""),
557558
},
558559
},
@@ -575,7 +576,7 @@ func TestE2E_NamespacedRBAC(t *testing.T) {
575576
}
576577

577578
// Test with `filter::namespace` set, role binding for namespace-scoped objects (pod, replicaset) and clusterrole
578-
// binding for node and namespace objects.
579+
// binding for node and namespace objects, and the telemetrygen image has a tag and digest.
579580
func TestE2E_MixRBAC(t *testing.T) {
580581

581582
testDir := filepath.Join("testdata", "e2e", "mixrbac")
@@ -662,7 +663,7 @@ func TestE2E_MixRBAC(t *testing.T) {
662663
"k8s.container.name": newExpectedValue(equal, "telemetrygen"),
663664
"container.image.name": newExpectedValue(equal, "ghcr.io/open-telemetry/opentelemetry-collector-contrib/telemetrygen"),
664665
"container.image.repo_digests": newExpectedValue(regex, "ghcr.io/open-telemetry/opentelemetry-collector-contrib/telemetrygen@sha256:[0-9a-fA-f]{64}"),
665-
"container.image.tag": newExpectedValue(equal, "latest"),
666+
"container.image.tag": newExpectedValue(equal, "0.112.0"),
666667
"container.id": newExpectedValue(exist, ""),
667668
"k8s.namespace.labels.foons": newExpectedValue(equal, "barns"),
668669
"k8s.node.labels.foo": newExpectedValue(equal, "too"),
@@ -689,7 +690,7 @@ func TestE2E_MixRBAC(t *testing.T) {
689690
"k8s.container.name": newExpectedValue(equal, "telemetrygen"),
690691
"container.image.name": newExpectedValue(equal, "ghcr.io/open-telemetry/opentelemetry-collector-contrib/telemetrygen"),
691692
"container.image.repo_digests": newExpectedValue(regex, "ghcr.io/open-telemetry/opentelemetry-collector-contrib/telemetrygen@sha256:[0-9a-fA-f]{64}"),
692-
"container.image.tag": newExpectedValue(equal, "latest"),
693+
"container.image.tag": newExpectedValue(equal, "0.112.0"),
693694
"container.id": newExpectedValue(exist, ""),
694695
"k8s.namespace.labels.foons": newExpectedValue(equal, "barns"),
695696
"k8s.node.labels.foo": newExpectedValue(equal, "too"),
@@ -716,7 +717,7 @@ func TestE2E_MixRBAC(t *testing.T) {
716717
"k8s.container.name": newExpectedValue(equal, "telemetrygen"),
717718
"container.image.name": newExpectedValue(equal, "ghcr.io/open-telemetry/opentelemetry-collector-contrib/telemetrygen"),
718719
"container.image.repo_digests": newExpectedValue(regex, "ghcr.io/open-telemetry/opentelemetry-collector-contrib/telemetrygen@sha256:[0-9a-fA-f]{64}"),
719-
"container.image.tag": newExpectedValue(equal, "latest"),
720+
"container.image.tag": newExpectedValue(equal, "0.112.0"),
720721
"container.id": newExpectedValue(exist, ""),
721722
"k8s.namespace.labels.foons": newExpectedValue(equal, "barns"),
722723
"k8s.node.labels.foo": newExpectedValue(equal, "too"),
@@ -745,7 +746,8 @@ func TestE2E_MixRBAC(t *testing.T) {
745746
// While `k8s.pod.ip` is not set in `k8sattributes:extract:metadata` and the `pod_association` is not `connection`
746747
// we expect that the `k8s.pod.ip` metadata is not added.
747748
// While `container.image.repo_digests` is not set in `k8sattributes::extract::metadata`, we expect
748-
// that the `container.image.repo_digests` metadata is not added
749+
// that the `container.image.repo_digests` metadata is not added.
750+
// The telemetrygen image has neither a tag nor digest (implicitly latest version)
749751
func TestE2E_NamespacedRBACNoPodIP(t *testing.T) {
750752
testDir := filepath.Join("testdata", "e2e", "namespaced_rbac_no_pod_ip")
751753

@@ -816,7 +818,7 @@ func TestE2E_NamespacedRBACNoPodIP(t *testing.T) {
816818
"k8s.container.name": newExpectedValue(equal, "telemetrygen"),
817819
"container.image.name": newExpectedValue(equal, "ghcr.io/open-telemetry/opentelemetry-collector-contrib/telemetrygen"),
818820
"container.image.repo_digests": newExpectedValue(shouldnotexist, ""),
819-
"container.image.tag": newExpectedValue(equal, "latest"),
821+
"container.image.tag": newExpectedValue(exist, ""),
820822
"container.id": newExpectedValue(exist, ""),
821823
},
822824
},
@@ -840,7 +842,7 @@ func TestE2E_NamespacedRBACNoPodIP(t *testing.T) {
840842
"k8s.container.name": newExpectedValue(equal, "telemetrygen"),
841843
"container.image.name": newExpectedValue(equal, "ghcr.io/open-telemetry/opentelemetry-collector-contrib/telemetrygen"),
842844
"container.image.repo_digests": newExpectedValue(shouldnotexist, ""),
843-
"container.image.tag": newExpectedValue(equal, "latest"),
845+
"container.image.tag": newExpectedValue(exist, ""),
844846
"container.id": newExpectedValue(exist, ""),
845847
},
846848
},
@@ -864,7 +866,7 @@ func TestE2E_NamespacedRBACNoPodIP(t *testing.T) {
864866
"k8s.container.name": newExpectedValue(equal, "telemetrygen"),
865867
"container.image.name": newExpectedValue(equal, "ghcr.io/open-telemetry/opentelemetry-collector-contrib/telemetrygen"),
866868
"container.image.repo_digests": newExpectedValue(shouldnotexist, ""),
867-
"container.image.tag": newExpectedValue(equal, "latest"),
869+
"container.image.tag": newExpectedValue(exist, ""),
868870
"container.id": newExpectedValue(exist, ""),
869871
},
870872
},

processor/k8sattributesprocessor/internal/kube/client.go

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ package kube // import "github.com/open-telemetry/opentelemetry-collector-contri
55

66
import (
77
"context"
8+
"errors"
89
"fmt"
910
"regexp"
1011
"strings"
@@ -632,6 +633,23 @@ func removeUnnecessaryPodData(pod *api_v1.Pod, rules ExtractionRules) *api_v1.Po
632633
return &transformedPod
633634
}
634635

636+
// parseAttributesFromImage parses the image name and tag for differently-formatted image names.
637+
func parseNameAndTagFromImage(image string) (name, tag string, err error) {
638+
ref, err := reference.Parse(image)
639+
if err != nil {
640+
return
641+
}
642+
namedRef, ok := ref.(reference.Named)
643+
if !ok {
644+
return "", "", errors.New("cannot retrieve image name")
645+
}
646+
name = namedRef.Name()
647+
if taggedRef, ok := namedRef.(reference.Tagged); ok {
648+
tag = taggedRef.Tag()
649+
}
650+
return
651+
}
652+
635653
func (c *WatchClient) extractPodContainersAttributes(pod *api_v1.Pod) PodContainers {
636654
containers := PodContainers{
637655
ByID: map[string]*Container{},
@@ -643,16 +661,14 @@ func (c *WatchClient) extractPodContainersAttributes(pod *api_v1.Pod) PodContain
643661
if c.Rules.ContainerImageName || c.Rules.ContainerImageTag {
644662
for _, spec := range append(pod.Spec.Containers, pod.Spec.InitContainers...) {
645663
container := &Container{}
646-
nameTagSep := strings.LastIndex(spec.Image, ":")
647-
if c.Rules.ContainerImageName {
648-
if nameTagSep > 0 {
649-
container.ImageName = spec.Image[:nameTagSep]
650-
} else {
651-
container.ImageName = spec.Image
664+
name, tag, err := parseNameAndTagFromImage(spec.Image)
665+
if err == nil {
666+
if c.Rules.ContainerImageName {
667+
container.ImageName = name
668+
}
669+
if c.Rules.ContainerImageTag {
670+
container.ImageTag = tag
652671
}
653-
}
654-
if c.Rules.ContainerImageTag && nameTagSep > 0 {
655-
container.ImageTag = spec.Image[nameTagSep+1:]
656672
}
657673
containers.ByName[spec.Name] = container
658674
}

processor/k8sattributesprocessor/internal/kube/client_test.go

Lines changed: 57 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1469,11 +1469,15 @@ func Test_extractPodContainersAttributes(t *testing.T) {
14691469
Containers: []api_v1.Container{
14701470
{
14711471
Name: "container1",
1472-
Image: "test/image1:0.1.0",
1472+
Image: "example.com:5000/test/image1:0.1.0",
14731473
},
14741474
{
14751475
Name: "container2",
1476-
Image: "example.com:port1/image2:0.2.0",
1476+
Image: "example.com:81/image2@sha256:430ac608abaa332de4ce45d68534447c7a206edc5e98aaff9923ecc12f8a80d9",
1477+
},
1478+
{
1479+
Name: "container3",
1480+
Image: "example-website.com/image3:1.0@sha256:4b0b1b6f6cdd3e5b9e55f74a1e8d19ed93a3f5a04c6b6c3c57c4e6d19f6b7c4d",
14771481
},
14781482
},
14791483
InitContainers: []api_v1.Container{
@@ -1494,7 +1498,13 @@ func Test_extractPodContainersAttributes(t *testing.T) {
14941498
{
14951499
Name: "container2",
14961500
ContainerID: "docker://container2-id-456",
1497-
ImageID: "sha256:430ac608abaa332de4ce45d68534447c7a206edc5e98aaff9923ecc12f8a80d9",
1501+
ImageID: "sha256:4b0b1b6f6cdd3e5b9e55f74a1e8d19ed93a3f5a04c6b6c3c57c4e6d19f6b7c4d",
1502+
RestartCount: 2,
1503+
},
1504+
{
1505+
Name: "container3",
1506+
ContainerID: "docker://container3-id-abc",
1507+
ImageID: "docker.io/otel/collector:2.0.0@sha256:430ac608abaa332de4ce45d68534447c7a206edc5e98aaff9923ecc12f8a80d9",
14981508
RestartCount: 2,
14991509
},
15001510
},
@@ -1538,13 +1548,15 @@ func Test_extractPodContainersAttributes(t *testing.T) {
15381548
pod: &pod,
15391549
want: PodContainers{
15401550
ByID: map[string]*Container{
1541-
"container1-id-123": {ImageName: "test/image1"},
1542-
"container2-id-456": {ImageName: "example.com:port1/image2"},
1551+
"container1-id-123": {ImageName: "example.com:5000/test/image1"},
1552+
"container2-id-456": {ImageName: "example.com:81/image2"},
1553+
"container3-id-abc": {ImageName: "example-website.com/image3"},
15431554
"init-container-id-789": {ImageName: "test/init-image"},
15441555
},
15451556
ByName: map[string]*Container{
1546-
"container1": {ImageName: "test/image1"},
1547-
"container2": {ImageName: "example.com:port1/image2"},
1557+
"container1": {ImageName: "example.com:5000/test/image1"},
1558+
"container2": {ImageName: "example.com:81/image2"},
1559+
"container3": {ImageName: "example-website.com/image3"},
15481560
"init_container": {ImageName: "test/init-image"},
15491561
},
15501562
},
@@ -1589,6 +1601,11 @@ func Test_extractPodContainersAttributes(t *testing.T) {
15891601
2: {ContainerID: "container2-id-456"},
15901602
},
15911603
},
1604+
"container3-id-abc": {
1605+
Statuses: map[int]ContainerStatus{
1606+
2: {ContainerID: "container3-id-abc"},
1607+
},
1608+
},
15921609
"init-container-id-789": {
15931610
Statuses: map[int]ContainerStatus{
15941611
0: {ContainerID: "init-container-id-789"},
@@ -1606,6 +1623,11 @@ func Test_extractPodContainersAttributes(t *testing.T) {
16061623
2: {ContainerID: "container2-id-456"},
16071624
},
16081625
},
1626+
"container3": {
1627+
Statuses: map[int]ContainerStatus{
1628+
2: {ContainerID: "container3-id-abc"},
1629+
},
1630+
},
16091631
"init_container": {
16101632
Statuses: map[int]ContainerStatus{
16111633
0: {ContainerID: "init-container-id-789"},
@@ -1632,6 +1654,11 @@ func Test_extractPodContainersAttributes(t *testing.T) {
16321654
2: {},
16331655
},
16341656
},
1657+
"container3-id-abc": {
1658+
Statuses: map[int]ContainerStatus{
1659+
2: {ImageRepoDigest: "docker.io/otel/collector:2.0.0@sha256:430ac608abaa332de4ce45d68534447c7a206edc5e98aaff9923ecc12f8a80d9"},
1660+
},
1661+
},
16351662
"init-container-id-789": {
16361663
Statuses: map[int]ContainerStatus{
16371664
0: {ImageRepoDigest: "ghcr.io/initimage1@sha256:42e8ba40f9f70d604684c3a2a0ed321206b7e2e3509fdb2c8836d34f2edfb57b"},
@@ -1649,6 +1676,11 @@ func Test_extractPodContainersAttributes(t *testing.T) {
16491676
2: {},
16501677
},
16511678
},
1679+
"container3": {
1680+
Statuses: map[int]ContainerStatus{
1681+
2: {ImageRepoDigest: "docker.io/otel/collector:2.0.0@sha256:430ac608abaa332de4ce45d68534447c7a206edc5e98aaff9923ecc12f8a80d9"},
1682+
},
1683+
},
16521684
"init_container": {
16531685
Statuses: map[int]ContainerStatus{
16541686
0: {ImageRepoDigest: "ghcr.io/initimage1@sha256:42e8ba40f9f70d604684c3a2a0ed321206b7e2e3509fdb2c8836d34f2edfb57b"},
@@ -1669,19 +1701,25 @@ func Test_extractPodContainersAttributes(t *testing.T) {
16691701
want: PodContainers{
16701702
ByID: map[string]*Container{
16711703
"container1-id-123": {
1672-
ImageName: "test/image1",
1704+
ImageName: "example.com:5000/test/image1",
16731705
ImageTag: "0.1.0",
16741706
Statuses: map[int]ContainerStatus{
16751707
0: {ContainerID: "container1-id-123", ImageRepoDigest: "docker.io/otel/collector@sha256:55d008bc28344c3178645d40e7d07df30f9d90abe4b53c3fc4e5e9c0295533da"},
16761708
},
16771709
},
16781710
"container2-id-456": {
1679-
ImageName: "example.com:port1/image2",
1680-
ImageTag: "0.2.0",
1711+
ImageName: "example.com:81/image2",
16811712
Statuses: map[int]ContainerStatus{
16821713
2: {ContainerID: "container2-id-456"},
16831714
},
16841715
},
1716+
"container3-id-abc": {
1717+
ImageName: "example-website.com/image3",
1718+
ImageTag: "1.0",
1719+
Statuses: map[int]ContainerStatus{
1720+
2: {ContainerID: "container3-id-abc", ImageRepoDigest: "docker.io/otel/collector:2.0.0@sha256:430ac608abaa332de4ce45d68534447c7a206edc5e98aaff9923ecc12f8a80d9"},
1721+
},
1722+
},
16851723
"init-container-id-789": {
16861724
ImageName: "test/init-image",
16871725
ImageTag: "1.0.2",
@@ -1692,19 +1730,25 @@ func Test_extractPodContainersAttributes(t *testing.T) {
16921730
},
16931731
ByName: map[string]*Container{
16941732
"container1": {
1695-
ImageName: "test/image1",
1733+
ImageName: "example.com:5000/test/image1",
16961734
ImageTag: "0.1.0",
16971735
Statuses: map[int]ContainerStatus{
16981736
0: {ContainerID: "container1-id-123", ImageRepoDigest: "docker.io/otel/collector@sha256:55d008bc28344c3178645d40e7d07df30f9d90abe4b53c3fc4e5e9c0295533da"},
16991737
},
17001738
},
17011739
"container2": {
1702-
ImageName: "example.com:port1/image2",
1703-
ImageTag: "0.2.0",
1740+
ImageName: "example.com:81/image2",
17041741
Statuses: map[int]ContainerStatus{
17051742
2: {ContainerID: "container2-id-456"},
17061743
},
17071744
},
1745+
"container3": {
1746+
ImageName: "example-website.com/image3",
1747+
ImageTag: "1.0",
1748+
Statuses: map[int]ContainerStatus{
1749+
2: {ContainerID: "container3-id-abc", ImageRepoDigest: "docker.io/otel/collector:2.0.0@sha256:430ac608abaa332de4ce45d68534447c7a206edc5e98aaff9923ecc12f8a80d9"},
1750+
},
1751+
},
17081752
"init_container": {
17091753
ImageName: "test/init-image",
17101754
ImageTag: "1.0.2",

processor/k8sattributesprocessor/processor_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1131,7 +1131,7 @@ func TestProcessorAddContainerAttributes(t *testing.T) {
11311131
ByName: map[string]*kube.Container{
11321132
"app": {
11331133
Statuses: map[int]kube.ContainerStatus{
1134-
2: {ImageRepoDigest: "docker.io/otel/collector@sha256:deadbeef02"},
1134+
2: {ImageRepoDigest: "docker.io/otel/collector:1.2.3@sha256:deadbeef02"},
11351135
},
11361136
},
11371137
},
@@ -1145,7 +1145,7 @@ func TestProcessorAddContainerAttributes(t *testing.T) {
11451145
wantAttrs: map[string]any{
11461146
kube.K8sIPLabelName: "1.1.1.1",
11471147
conventions.AttributeK8SContainerName: "app",
1148-
containerImageRepoDigests: []string{"docker.io/otel/collector@sha256:deadbeef02"},
1148+
containerImageRepoDigests: []string{"docker.io/otel/collector:1.2.3@sha256:deadbeef02"},
11491149
},
11501150
},
11511151
{

processor/k8sattributesprocessor/testdata/e2e/mixrbac/telemetrygen/deployment.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ spec:
2828
{{- if eq .DataType "traces" }}
2929
- --status-code=
3030
{{- end }}
31-
image: ghcr.io/open-telemetry/opentelemetry-collector-contrib/telemetrygen:latest
31+
image: ghcr.io/open-telemetry/opentelemetry-collector-contrib/telemetrygen:0.112.0@sha256:b248ef911f93ae27cbbc85056d1ffacc87fd941bbdc2ffd951b6df8df72b8096
3232
imagePullPolicy: IfNotPresent
3333
name: telemetrygen
3434
restartPolicy: Always

processor/k8sattributesprocessor/testdata/e2e/namespaced_rbac_no_pod_ip/telemetrygen/deployment.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ spec:
3030
{{- if eq .DataType "traces" }}
3131
- --status-code=
3232
{{- end }}
33-
image: ghcr.io/open-telemetry/opentelemetry-collector-contrib/telemetrygen:latest
33+
image: ghcr.io/open-telemetry/opentelemetry-collector-contrib/telemetrygen
3434
imagePullPolicy: IfNotPresent
3535
name: telemetrygen
3636
restartPolicy: Always

0 commit comments

Comments
 (0)