Skip to content
This repository was archived by the owner on Apr 3, 2024. It is now read-only.

Commit a88e904

Browse files
authored
feat: Add region in Debuggee labels in GCF env (#951)
* feat: Add region in Debuggee labels in GCF env * Read GCF region from either the environment or GCP metadata service * Add the region into Debuggee labels * Add the corresponding tests Fixes #950 * fix: add tests for more coverage * fix: change getRegion method to return undefined upon not available
1 parent 4992871 commit a88e904

File tree

2 files changed

+168
-37
lines changed

2 files changed

+168
-37
lines changed

src/agent/debuglet.ts

Lines changed: 44 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -472,6 +472,12 @@ export class Debuglet extends EventEmitter {
472472
that.logger.warn(NODE_10_CIRC_REF_MESSAGE);
473473
}
474474

475+
const platform = Debuglet.getPlatform();
476+
let region: string | undefined;
477+
if (platform === Platforms.CLOUD_FUNCTION) {
478+
region = await Debuglet.getRegion();
479+
}
480+
475481
// We can register as a debuggee now.
476482
that.logger.debug('Starting debuggee, project', project);
477483
that.running = true;
@@ -484,9 +490,12 @@ export class Debuglet extends EventEmitter {
484490
sourceContext,
485491
onGCP,
486492
that.debug.packageInfo,
493+
platform,
487494
that.config.description,
488-
undefined
495+
/*errorMessage=*/ undefined,
496+
region
489497
);
498+
490499
that.scheduleRegistration_(0 /* immediately */);
491500
that.emit('started');
492501
}
@@ -534,8 +543,10 @@ export class Debuglet extends EventEmitter {
534543
sourceContext: SourceContext | undefined,
535544
onGCP: boolean,
536545
packageInfo: PackageInfo,
546+
platform: string,
537547
description?: string,
538-
errorMessage?: string
548+
errorMessage?: string,
549+
region?: string
539550
): Debuggee {
540551
const cwd = process.cwd();
541552
const mainScript = path.relative(cwd, process.argv[1]);
@@ -555,9 +566,13 @@ export class Debuglet extends EventEmitter {
555566
'agent.name': packageInfo.name,
556567
'agent.version': packageInfo.version,
557568
projectid: projectId,
558-
platform: Debuglet.getPlatform(),
569+
platform,
559570
};
560571

572+
if (region) {
573+
labels.region = region;
574+
}
575+
561576
if (serviceContext) {
562577
if (
563578
typeof serviceContext.service === 'string' &&
@@ -580,6 +595,10 @@ export class Debuglet extends EventEmitter {
580595
}
581596
}
582597

598+
if (region) {
599+
desc += ' region:' + region;
600+
}
601+
583602
if (!description && process.env.FUNCTION_NAME) {
584603
description = 'Function: ' + process.env.FUNCTION_NAME;
585604
}
@@ -620,7 +639,7 @@ export class Debuglet extends EventEmitter {
620639
* Use environment vars to infer the current platform.
621640
* For now this is only Cloud Functions and other.
622641
*/
623-
private static getPlatform(): Platforms {
642+
static getPlatform(): Platforms {
624643
const {FUNCTION_NAME, FUNCTION_TARGET} = process.env;
625644
// (In theory) only the Google Cloud Functions environment will have these env vars.
626645
if (FUNCTION_NAME || FUNCTION_TARGET) {
@@ -637,6 +656,27 @@ export class Debuglet extends EventEmitter {
637656
return (await metadata.instance('attributes/cluster-name')).data as string;
638657
}
639658

659+
/**
660+
* Returns the region from environment varaible if available.
661+
* Otherwise, returns the region from the metadata service.
662+
* If metadata is not available, returns undefined.
663+
*/
664+
static async getRegion(): Promise<string | undefined> {
665+
if (process.env.FUNCTION_REGION) {
666+
return process.env.FUNCTION_REGION;
667+
}
668+
669+
try {
670+
// Example returned region format: /process/1234567/us-central
671+
const segments = ((await metadata.instance('region')) as string).split(
672+
'/'
673+
);
674+
return segments[segments.length - 1];
675+
} catch (err) {
676+
return undefined;
677+
}
678+
}
679+
640680
static async getSourceContextFromFile(): Promise<SourceContext> {
641681
// If read errors, the error gets thrown to the caller.
642682
const contents = await readFilep('source-context.json', 'utf8');

test/test-debuglet.ts

Lines changed: 124 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -856,6 +856,80 @@ describe('Debuglet', () => {
856856
debuglet.start();
857857
});
858858

859+
it('should attempt to retreive region correctly if needed', done => {
860+
const savedGetPlatform = Debuglet.getPlatform;
861+
Debuglet.getPlatform = () => Platforms.CLOUD_FUNCTION;
862+
863+
const clusterScope = nock(gcpMetadata.HOST_ADDRESS)
864+
.get('/computeMetadata/v1/instance/region')
865+
.once()
866+
.reply(200, '123/456/region_name', gcpMetadata.HEADERS);
867+
868+
const debug = new Debug(
869+
{projectId: 'fake-project', credentials: fakeCredentials},
870+
packageInfo
871+
);
872+
873+
nocks.oauth2();
874+
875+
const config = debugletConfig();
876+
const debuglet = new Debuglet(debug, config);
877+
const scope = nock(config.apiUrl)
878+
.post(REGISTER_PATH)
879+
.reply(200, {
880+
debuggee: {id: DEBUGGEE_ID},
881+
});
882+
883+
debuglet.once('registered', () => {
884+
Debuglet.getPlatform = savedGetPlatform;
885+
assert.strictEqual(
886+
(debuglet.debuggee as Debuggee).labels?.region,
887+
'region_name'
888+
);
889+
debuglet.stop();
890+
clusterScope.done();
891+
scope.done();
892+
done();
893+
});
894+
895+
debuglet.start();
896+
});
897+
898+
it('should continue to register when could not get region', done => {
899+
const savedGetPlatform = Debuglet.getPlatform;
900+
Debuglet.getPlatform = () => Platforms.CLOUD_FUNCTION;
901+
902+
const clusterScope = nock(gcpMetadata.HOST_ADDRESS)
903+
.get('/computeMetadata/v1/instance/region')
904+
.once()
905+
.reply(400);
906+
907+
const debug = new Debug(
908+
{projectId: 'fake-project', credentials: fakeCredentials},
909+
packageInfo
910+
);
911+
912+
nocks.oauth2();
913+
914+
const config = debugletConfig();
915+
const debuglet = new Debuglet(debug, config);
916+
const scope = nock(config.apiUrl)
917+
.post(REGISTER_PATH)
918+
.reply(200, {
919+
debuggee: {id: DEBUGGEE_ID},
920+
});
921+
922+
debuglet.once('registered', () => {
923+
Debuglet.getPlatform = savedGetPlatform;
924+
debuglet.stop();
925+
clusterScope.done();
926+
scope.done();
927+
done();
928+
});
929+
930+
debuglet.start();
931+
});
932+
859933
it('should pass config source context to api', done => {
860934
const REPO_URL =
861935
'https://github.com/non-existent-users/non-existend-repo';
@@ -1462,7 +1536,8 @@ describe('Debuglet', () => {
14621536
{service: 'some-service', version: 'production'},
14631537
{},
14641538
false,
1465-
packageInfo
1539+
packageInfo,
1540+
Platforms.DEFAULT
14661541
);
14671542
assert.ok(debuggee);
14681543
assert.ok(debuggee.labels);
@@ -1477,7 +1552,8 @@ describe('Debuglet', () => {
14771552
{service: 'default', version: 'yellow.5'},
14781553
{},
14791554
false,
1480-
packageInfo
1555+
packageInfo,
1556+
Platforms.DEFAULT
14811557
);
14821558
assert.ok(debuggee);
14831559
assert.ok(debuggee.labels);
@@ -1493,6 +1569,7 @@ describe('Debuglet', () => {
14931569
{},
14941570
false,
14951571
packageInfo,
1572+
Platforms.DEFAULT,
14961573
undefined,
14971574
'Some Error Message'
14981575
);
@@ -1507,7 +1584,8 @@ describe('Debuglet', () => {
15071584
{enableCanary: true, allowCanaryOverride: true},
15081585
{},
15091586
false,
1510-
packageInfo
1587+
packageInfo,
1588+
Platforms.DEFAULT
15111589
);
15121590
assert.strictEqual(debuggee.canaryMode, 'CANARY_MODE_DEFAULT_ENABLED');
15131591
});
@@ -1519,7 +1597,8 @@ describe('Debuglet', () => {
15191597
{enableCanary: true, allowCanaryOverride: false},
15201598
{},
15211599
false,
1522-
packageInfo
1600+
packageInfo,
1601+
Platforms.DEFAULT
15231602
);
15241603
assert.strictEqual(debuggee.canaryMode, 'CANARY_MODE_ALWAYS_ENABLED');
15251604
});
@@ -1531,7 +1610,8 @@ describe('Debuglet', () => {
15311610
{enableCanary: false, allowCanaryOverride: true},
15321611
{},
15331612
false,
1534-
packageInfo
1613+
packageInfo,
1614+
Platforms.DEFAULT
15351615
);
15361616
assert.strictEqual(debuggee.canaryMode, 'CANARY_MODE_DEFAULT_DISABLED');
15371617
});
@@ -1543,54 +1623,65 @@ describe('Debuglet', () => {
15431623
{enableCanary: false, allowCanaryOverride: false},
15441624
{},
15451625
false,
1546-
packageInfo
1626+
packageInfo,
1627+
Platforms.DEFAULT
15471628
);
15481629
assert.strictEqual(debuggee.canaryMode, 'CANARY_MODE_ALWAYS_DISABLED');
15491630
});
1631+
});
15501632

1633+
describe('getPlatform', () => {
15511634
it('should correctly identify default platform.', () => {
1552-
const debuggee = Debuglet.createDebuggee(
1553-
'some project',
1554-
'id',
1555-
{service: 'some-service', version: 'production'},
1556-
{},
1557-
false,
1558-
packageInfo
1559-
);
1560-
assert.ok(debuggee.labels!.platform === Platforms.DEFAULT);
1635+
assert.ok(Debuglet.getPlatform() === Platforms.DEFAULT);
15611636
});
15621637

15631638
it('should correctly identify GCF (legacy) platform.', () => {
15641639
// GCF sets this env var on older runtimes.
15651640
process.env.FUNCTION_NAME = 'mock';
1566-
const debuggee = Debuglet.createDebuggee(
1567-
'some project',
1568-
'id',
1569-
{service: 'some-service', version: 'production'},
1570-
{},
1571-
false,
1572-
packageInfo
1573-
);
1574-
assert.ok(debuggee.labels!.platform === Platforms.CLOUD_FUNCTION);
1641+
assert.ok(Debuglet.getPlatform() === Platforms.CLOUD_FUNCTION);
15751642
delete process.env.FUNCTION_NAME; // Don't contaminate test environment.
15761643
});
15771644

15781645
it('should correctly identify GCF (modern) platform.', () => {
15791646
// GCF sets this env var on modern runtimes.
15801647
process.env.FUNCTION_TARGET = 'mock';
1581-
const debuggee = Debuglet.createDebuggee(
1582-
'some project',
1583-
'id',
1584-
{service: 'some-service', version: 'production'},
1585-
{},
1586-
false,
1587-
packageInfo
1588-
);
1589-
assert.ok(debuggee.labels!.platform === Platforms.CLOUD_FUNCTION);
1648+
assert.ok(Debuglet.getPlatform() === Platforms.CLOUD_FUNCTION);
15901649
delete process.env.FUNCTION_TARGET; // Don't contaminate test environment.
15911650
});
15921651
});
15931652

1653+
describe('getRegion', () => {
1654+
it('should return function region for older GCF runtime', async () => {
1655+
process.env.FUNCTION_REGION = 'mock';
1656+
1657+
assert.ok((await Debuglet.getRegion()) === 'mock');
1658+
1659+
delete process.env.FUNCTION_REGION;
1660+
});
1661+
1662+
it('should return region for newer GCF runtime', async () => {
1663+
const clusterScope = nock(gcpMetadata.HOST_ADDRESS)
1664+
.get('/computeMetadata/v1/instance/region')
1665+
.once()
1666+
.reply(200, '123/456/region_name', gcpMetadata.HEADERS);
1667+
1668+
assert.ok((await Debuglet.getRegion()) === 'region_name');
1669+
1670+
clusterScope.done();
1671+
});
1672+
1673+
it('should return undefined when cannot get region metadata', async () => {
1674+
const clusterScope = nock(gcpMetadata.HOST_ADDRESS)
1675+
.get('/computeMetadata/v1/instance/region')
1676+
.once()
1677+
.reply(400);
1678+
1679+
assert.ok((await Debuglet.getRegion()) === undefined);
1680+
1681+
clusterScope.done();
1682+
});
1683+
});
1684+
15941685
describe('_createUniquifier', () => {
15951686
it('should create a unique string', () => {
15961687
const fn = Debuglet._createUniquifier;

0 commit comments

Comments
 (0)