Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
f3db796
fix(detector-aws): extract full container ID from ECS Fargate cgroup …
seongpil0948 Jul 12, 2025
f2be3bf
test(detector-aws): refactor ECS container ID extraction tests
seongpil0948 Jul 20, 2025
a406a64
fix(detector-aws): extract full container ID from ECS Fargate cgroup …
seongpil0948 Jul 25, 2025
01469cc
chore(comment): remove comment
seongpil0948 Jul 26, 2025
2ee2f08
Merge branch 'main' into fix/2455_ECS-consistent-container-id
seongpil0948 Aug 8, 2025
1f31841
feat: Apply suggestions from code review
seongpil0948 Aug 8, 2025
75e3471
feat(aws-ecs-detector): improve container ID validation and test cove…
seongpil0948 Aug 9, 2025
6eecd69
Merge branch 'open-telemetry:main' into fix/2455_ECS-consistent-conta…
seongpil0948 Aug 12, 2025
7c19509
feat(resource-detector-aws): prefer primary container ID across lines…
seongpil0948 Aug 15, 2025
7df9ad2
Merge branch 'open-telemetry:main' into fix/2455_ECS-consistent-conta…
seongpil0948 Aug 15, 2025
2f37644
Merge branch 'main' into fix/2455_ECS-consistent-container-id
seongpil0948 Aug 21, 2025
5505284
fix(resource-detector-aws): remove redundant variable in ECS containe…
seongpil0948 Aug 21, 2025
6337a9e
Merge branch 'main' into fix/2455_ECS-consistent-container-id
seongpil0948 Aug 24, 2025
c8add07
Merge branch 'main' into fix/2455_ECS-consistent-container-id
seongpil0948 Aug 29, 2025
098ef50
fix: lint
pichlermarc Aug 29, 2025
b509c16
Merge branch 'main' into fix/2455_ECS-consistent-container-id
pichlermarc Sep 3, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 49 additions & 4 deletions packages/resource-detector-aws/src/detectors/AwsEcsDetector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,10 +163,8 @@ export class AwsEcsDetector implements ResourceDetector {
);
const splitData = rawData.trim().split('\n');
for (const str of splitData) {
if (str.length > AwsEcsDetector.CONTAINER_ID_LENGTH) {
containerId = str.substring(
str.length - AwsEcsDetector.CONTAINER_ID_LENGTH
);
containerId = this._extractContainerIdFromLine(str);
if (containerId) {
break;
}
}
Expand All @@ -176,6 +174,53 @@ export class AwsEcsDetector implements ResourceDetector {
return containerId;
}

/**
* Extract container ID from a cgroup line using regex pattern matching.
* Handles the new AWS ECS Fargate format: /ecs/<taskId>/<taskId>-<containerId>
* Returns the last segment after the final '/' which should be the complete container ID.
*/
private _extractContainerIdFromLine(line: string): string | undefined {
if (!line) {
return undefined;
}

// Primary pattern: Match /ecs/taskId/taskId-containerId format (new ECS Fargate)
// This captures the full taskId-containerId part after the last slash
const ecsPattern = /\/ecs\/[a-zA-Z0-9-]+\/([a-zA-Z0-9-]+)$/;
const ecsMatch = line.match(ecsPattern);
if (
ecsMatch &&
ecsMatch[1] &&
ecsMatch[1].length >= 12 &&
ecsMatch[1].length <= 128
) {
return ecsMatch[1];
}

// Fallback: Extract last segment after slash for any path-like format
const segments = line.split('/');
if (segments.length > 1) {
const lastSegment = segments[segments.length - 1];
if (
lastSegment &&
lastSegment.length >= 12 &&
lastSegment.length <= 128
) {
return lastSegment;
}
}

// Legacy fallback: original logic for lines that are just the container ID (64 chars)
if (
line.length > AwsEcsDetector.CONTAINER_ID_LENGTH &&
!line.includes('/')
) {
return line.substring(line.length - AwsEcsDetector.CONTAINER_ID_LENGTH);
}

return undefined;
}

/**
* Add metadata-v4-related resource attributes to `data` (in-place)
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -452,4 +452,85 @@ describe('AwsEcsResourceDetector', () => {
});
});
});

describe('Container ID extraction', () => {
const testMetadataUri = 'http://169.254.170.2/v4/test';
const testHostname = 'test-hostname';
let readStub: sinon.SinonStub;

beforeEach(() => {
process.env.ECS_CONTAINER_METADATA_URI_V4 = testMetadataUri;
});

afterEach(() => {
sinon.restore();
});

function setupMocks(cgroupData: string) {
sinon.stub(os, 'hostname').returns(testHostname);
readStub = sinon
.stub(AwsEcsDetector, 'readFileAsync' as any)
.resolves(cgroupData);
}

function setupMetadataNock() {
return nock('http://169.254.170.2:80')
.persist(false)
.get('/v4/test')
.reply(200, {
ContainerARN: 'arn:aws:ecs:us-west-2:111122223333:container/test',
})
.get('/v4/test/task')
.reply(200, {
TaskARN: 'arn:aws:ecs:us-west-2:111122223333:task/default/test',
Family: 'test-family',
Revision: '1',
Cluster: 'test-cluster',
LaunchType: 'FARGATE',
});
}

it('should extract full container ID from new ECS Fargate format', async () => {
const taskId = 'c23e5f76c09d438aa1824ca4058bdcab';
const containerId = '1234567890abcdef';
const cgroupData = `/ecs/${taskId}/${taskId}-${containerId}`;

setupMocks(cgroupData);
const nockScope = setupMetadataNock();

const resource = detectResources({ detectors: [awsEcsDetector] });
await resource.waitForAsyncAttributes?.();

sinon.assert.calledOnce(readStub);
assert.ok(resource);
assertEcsResource(resource, {});
assertContainerResource(resource, {
name: testHostname,
id: `${taskId}-${containerId}`,
});

nockScope.done();
});

it('should handle backward compatibility with legacy format', async () => {
const legacyContainerId =
'abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklm';

setupMocks(legacyContainerId);
const nockScope = setupMetadataNock();

const resource = detectResources({ detectors: [awsEcsDetector] });
await resource.waitForAsyncAttributes?.();

sinon.assert.calledOnce(readStub);
assert.ok(resource);
assertEcsResource(resource, {});
assertContainerResource(resource, {
name: testHostname,
id: 'bcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklm',
});

nockScope.done();
});
});
});