Skip to content

Commit 1991aed

Browse files
abhee11blumamir
andauthored
feat: added prefix to look for containerid (#2341)
* feature: added prefix to look for containerid * fix:added seperate utils file and new test file and more tests * fix:test refactor --------- Co-authored-by: Amir Blum <[email protected]>
1 parent efe3c92 commit 1991aed

File tree

4 files changed

+192
-35
lines changed

4 files changed

+192
-35
lines changed

detectors/node/opentelemetry-resource-detector-container/src/detectors/ContainerDetector.ts

Lines changed: 33 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,19 @@ import { SEMRESATTRS_CONTAINER_ID } from '@opentelemetry/semantic-conventions';
2424
import * as fs from 'fs';
2525
import * as util from 'util';
2626
import { diag } from '@opentelemetry/api';
27+
import { extractContainerIdFromLine } from './utils';
2728

2829
export class ContainerDetector implements Detector {
2930
readonly CONTAINER_ID_LENGTH = 64;
3031
readonly DEFAULT_CGROUP_V1_PATH = '/proc/self/cgroup';
3132
readonly DEFAULT_CGROUP_V2_PATH = '/proc/self/mountinfo';
3233
readonly UTF8_UNICODE = 'utf8';
3334
readonly HOSTNAME = 'hostname';
35+
readonly MARKING_PREFIX = 'containers';
36+
readonly CRIO = 'crio-';
37+
readonly CRI_CONTAINERD = 'cri-containerd-';
38+
readonly DOCKER = 'docker-';
39+
readonly HEX_STRING_REGEX: RegExp = /^[a-f0-9]+$/i;
3440

3541
private static readFileAsync = util.promisify(fs.readFile);
3642

@@ -51,35 +57,17 @@ export class ContainerDetector implements Detector {
5157
}
5258
}
5359

54-
private async _getContainerIdV1() {
60+
private async _getContainerIdV1(): Promise<string | undefined> {
5561
const rawData = await ContainerDetector.readFileAsync(
5662
this.DEFAULT_CGROUP_V1_PATH,
5763
this.UTF8_UNICODE
5864
);
5965
const splitData = rawData.trim().split('\n');
60-
for (const line of splitData) {
61-
const lastSlashIdx = line.lastIndexOf('/');
62-
if (lastSlashIdx === -1) {
63-
continue;
64-
}
65-
const lastSection = line.substring(lastSlashIdx + 1);
66-
const colonIdx = lastSection.lastIndexOf(':');
67-
if (colonIdx !== -1) {
68-
// since containerd v1.5.0+, containerId is divided by the last colon when the cgroupDriver is systemd:
69-
// https://github.com/containerd/containerd/blob/release/1.5/pkg/cri/server/helpers_linux.go#L64
70-
return lastSection.substring(colonIdx + 1);
71-
} else {
72-
let startIdx = lastSection.lastIndexOf('-');
73-
let endIdx = lastSection.lastIndexOf('.');
7466

75-
startIdx = startIdx === -1 ? 0 : startIdx + 1;
76-
if (endIdx === -1) {
77-
endIdx = lastSection.length;
78-
}
79-
if (startIdx > endIdx) {
80-
continue;
81-
}
82-
return lastSection.substring(startIdx, endIdx);
67+
for (const line of splitData) {
68+
const containerID = extractContainerIdFromLine(line);
69+
if (containerID) {
70+
return containerID;
8371
}
8472
}
8573
return undefined;
@@ -94,10 +82,19 @@ export class ContainerDetector implements Detector {
9482
.trim()
9583
.split('\n')
9684
.find(s => s.includes(this.HOSTNAME));
97-
const containerIdStr = str
98-
?.split('/')
99-
.find(s => s.length === this.CONTAINER_ID_LENGTH);
100-
return containerIdStr || '';
85+
86+
if (!str) return '';
87+
88+
const strArray = str?.split('/') ?? [];
89+
for (let i = 0; i < strArray.length - 1; i++) {
90+
if (
91+
strArray[i] === this.MARKING_PREFIX &&
92+
strArray[i + 1]?.length === this.CONTAINER_ID_LENGTH
93+
) {
94+
return strArray[i + 1];
95+
}
96+
}
97+
return '';
10198
}
10299

103100
/*
@@ -107,9 +104,14 @@ export class ContainerDetector implements Detector {
107104
*/
108105
private async _getContainerId(): Promise<string | undefined> {
109106
try {
110-
return (
111-
(await this._getContainerIdV1()) || (await this._getContainerIdV2())
112-
);
107+
const containerIdV1 = await this._getContainerIdV1();
108+
if (containerIdV1) {
109+
return containerIdV1; // If containerIdV1 is a non-empty string, return it.
110+
}
111+
const containerIdV2 = await this._getContainerIdV2();
112+
if (containerIdV2) {
113+
return containerIdV2; // If containerIdV2 is a non-empty string, return it.
114+
}
113115
} catch (e) {
114116
if (e instanceof Error) {
115117
const errorMessage = e.message;
@@ -119,7 +121,7 @@ export class ContainerDetector implements Detector {
119121
);
120122
}
121123
}
122-
return undefined;
124+
return undefined; // Explicitly return undefined if neither ID is found.
123125
}
124126
}
125127

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
export const CONTAINER_ID_LENGTH = 64;
17+
export const DEFAULT_CGROUP_V1_PATH = '/proc/self/cgroup';
18+
export const DEFAULT_CGROUP_V2_PATH = '/proc/self/mountinfo';
19+
export const UTF8_UNICODE = 'utf8';
20+
export const HOSTNAME = 'hostname';
21+
export const MARKING_PREFIX = 'containers';
22+
export const CRIO = 'crio-';
23+
export const CRI_CONTAINERD = 'cri-containerd-';
24+
export const DOCKER = 'docker-';
25+
export const HEX_STRING_REGEX = /^[a-f0-9]+$/i;
26+
27+
export function truncatePrefix(lastSection: string, prefix: string): string {
28+
return lastSection.substring(prefix.length);
29+
}
30+
31+
export function extractContainerIdFromLine(line: string): string | undefined {
32+
if (!line) {
33+
return undefined;
34+
}
35+
const sections = line.split('/');
36+
if (sections.length <= 1) {
37+
return undefined;
38+
}
39+
let lastSection = sections[sections.length - 1];
40+
41+
// Handle containerd v1.5.0+ format with systemd cgroup driver
42+
const colonIndex = lastSection.lastIndexOf(':');
43+
if (colonIndex !== -1) {
44+
lastSection = lastSection.substring(colonIndex + 1);
45+
}
46+
47+
// Truncate known prefixes from the last section
48+
if (lastSection.startsWith(CRIO)) {
49+
lastSection = truncatePrefix(lastSection, CRIO);
50+
} else if (lastSection.startsWith(DOCKER)) {
51+
lastSection = truncatePrefix(lastSection, DOCKER);
52+
} else if (lastSection.startsWith(CRI_CONTAINERD)) {
53+
lastSection = truncatePrefix(lastSection, CRI_CONTAINERD);
54+
}
55+
// Remove anything after the first period
56+
if (lastSection.includes('.')) {
57+
lastSection = lastSection.split('.')[0];
58+
}
59+
// Check if the remaining string is a valid hex string
60+
if (HEX_STRING_REGEX.test(lastSection)) {
61+
return lastSection;
62+
}
63+
return undefined;
64+
}

detectors/node/opentelemetry-resource-detector-container/test/ContainerDetector.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,10 @@ import {
2626
import { ContainerDetector } from '../src';
2727

2828
describe('ContainerDetector', () => {
29-
let readStub;
29+
let readStub: sinon.SinonStub;
3030
const correctCgroupV1Data =
31-
'12:pids:/kubepods.slice/bcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklm';
32-
const correctCgroupV2Data = `tmhdefghijklmnopqrstuvwxyzafgrefghiugkmnopqrstuvwxyzabcdefghijkl/hostname
31+
'12:pids:/kubepods.slice/4e6f77206973207468652074696d6520666f7220616c6c20676f6f64206d656e20746f20636f6d6520746f2074686520616964';
32+
const correctCgroupV2Data = `containers/tmhdefghijklmnopqrstuvwxyzafgrefghiugkmnopqrstuvwxyzabcdefghijkl/hostname
3333
fhkjdshgfhsdfjhdsfkjhfkdshkjhfd/host
3434
sahfhfjkhjhfhjdhfjkdhfkjdhfjkhhdsjfhdfhjdhfkj/somethingelse`;
3535

@@ -63,7 +63,7 @@ describe('ContainerDetector', () => {
6363

6464
assert.ok(resource);
6565
assertContainerResource(resource, {
66-
id: 'bcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklm',
66+
id: '4e6f77206973207468652074696d6520666f7220616c6c20676f6f64206d656e20746f20636f6d6520746f2074686520616964',
6767
});
6868
});
6969

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import * as assert from 'assert';
18+
import { extractContainerIdFromLine } from '../src/detectors/utils';
19+
20+
describe(' extractContainerId from line tests', () => {
21+
it('should extract container ID from crio-prefixed line', () => {
22+
const line =
23+
'11:devices:/kubepods.slice/kubepods-besteffort.slice/kubepods-besteffort-pod5c5979ec_6b2b_11e9_a923_42010a800002.slice/crio-1234567890abcdef.scope';
24+
const expected = '1234567890abcdef';
25+
assert.strictEqual(extractContainerIdFromLine(line), expected);
26+
});
27+
28+
it('should extract container ID from docker-prefixed line', () => {
29+
const line =
30+
'11:devices:/docker/1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef';
31+
const expected =
32+
'1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef';
33+
assert.strictEqual(extractContainerIdFromLine(line), expected);
34+
});
35+
36+
it('should extract container ID from cri-containerd-prefixed line', () => {
37+
const line =
38+
'11:devices:/kubepods/burstable/pod2c4b2241-5c01-11e9-8e4e-42010a800002/cri-containerd-1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef';
39+
const expected =
40+
'1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef';
41+
assert.strictEqual(extractContainerIdFromLine(line), expected);
42+
});
43+
44+
it('should handle containerd v1.5.0+ format with systemd cgroup driver', () => {
45+
const line =
46+
'0::/system.slice/containerd.service/kubepods-burstable-pod2c4b2241-5c01-11e9-8e4e-42010a800002.slice:cri-containerd:1234567890abcdef';
47+
const expected = '1234567890abcdef';
48+
assert.strictEqual(extractContainerIdFromLine(line), expected);
49+
});
50+
51+
it('should return undefined for invalid container ID', () => {
52+
const line =
53+
'11:devices:/kubepods.slice/kubepods-besteffort.slice/kubepods-besteffort-pod5c5979ec_6b2b_11e9_a923_42010a800002.slice/invalid-id.scope';
54+
assert.strictEqual(extractContainerIdFromLine(line), undefined);
55+
});
56+
57+
it('should return undefined for empty line', () => {
58+
const line = '';
59+
assert.strictEqual(extractContainerIdFromLine(line), undefined);
60+
});
61+
62+
it('should return undefined for line without container ID', () => {
63+
const line = '11:devices:/';
64+
assert.strictEqual(extractContainerIdFromLine(line), undefined);
65+
});
66+
67+
// Additional test cases
68+
it('should handle line with multiple colons', () => {
69+
const line =
70+
'0::/system.slice/containerd.service/kubepods-burstable-pod2c4b2241-5c01-11e9-8e4e-42010a800002.slice:cri-containerd-1234567890abcdef.extra';
71+
const expected = '1234567890abcdef';
72+
assert.strictEqual(extractContainerIdFromLine(line), expected);
73+
});
74+
75+
it('should return containerid for valid hex string with any length', () => {
76+
const line =
77+
'11:devices:/docker/1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcde';
78+
assert.strictEqual(
79+
extractContainerIdFromLine(line),
80+
'1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcde'
81+
);
82+
});
83+
84+
it('should extract container ID with additional suffix', () => {
85+
const line =
86+
'11:devices:/docker/1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef.suffix';
87+
const expected =
88+
'1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef';
89+
assert.strictEqual(extractContainerIdFromLine(line), expected);
90+
});
91+
});

0 commit comments

Comments
 (0)