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

Commit

Permalink
feat: Add region in Debuggee labels in GCF env (#951)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
Louis-Ye committed May 4, 2021
1 parent 4992871 commit a88e904
Show file tree
Hide file tree
Showing 2 changed files with 168 additions and 37 deletions.
48 changes: 44 additions & 4 deletions src/agent/debuglet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -472,6 +472,12 @@ export class Debuglet extends EventEmitter {
that.logger.warn(NODE_10_CIRC_REF_MESSAGE);
}

const platform = Debuglet.getPlatform();
let region: string | undefined;
if (platform === Platforms.CLOUD_FUNCTION) {
region = await Debuglet.getRegion();
}

// We can register as a debuggee now.
that.logger.debug('Starting debuggee, project', project);
that.running = true;
Expand All @@ -484,9 +490,12 @@ export class Debuglet extends EventEmitter {
sourceContext,
onGCP,
that.debug.packageInfo,
platform,
that.config.description,
undefined
/*errorMessage=*/ undefined,
region
);

that.scheduleRegistration_(0 /* immediately */);
that.emit('started');
}
Expand Down Expand Up @@ -534,8 +543,10 @@ export class Debuglet extends EventEmitter {
sourceContext: SourceContext | undefined,
onGCP: boolean,
packageInfo: PackageInfo,
platform: string,
description?: string,
errorMessage?: string
errorMessage?: string,
region?: string
): Debuggee {
const cwd = process.cwd();
const mainScript = path.relative(cwd, process.argv[1]);
Expand All @@ -555,9 +566,13 @@ export class Debuglet extends EventEmitter {
'agent.name': packageInfo.name,
'agent.version': packageInfo.version,
projectid: projectId,
platform: Debuglet.getPlatform(),
platform,
};

if (region) {
labels.region = region;
}

if (serviceContext) {
if (
typeof serviceContext.service === 'string' &&
Expand All @@ -580,6 +595,10 @@ export class Debuglet extends EventEmitter {
}
}

if (region) {
desc += ' region:' + region;
}

if (!description && process.env.FUNCTION_NAME) {
description = 'Function: ' + process.env.FUNCTION_NAME;
}
Expand Down Expand Up @@ -620,7 +639,7 @@ export class Debuglet extends EventEmitter {
* Use environment vars to infer the current platform.
* For now this is only Cloud Functions and other.
*/
private static getPlatform(): Platforms {
static getPlatform(): Platforms {
const {FUNCTION_NAME, FUNCTION_TARGET} = process.env;
// (In theory) only the Google Cloud Functions environment will have these env vars.
if (FUNCTION_NAME || FUNCTION_TARGET) {
Expand All @@ -637,6 +656,27 @@ export class Debuglet extends EventEmitter {
return (await metadata.instance('attributes/cluster-name')).data as string;
}

/**
* Returns the region from environment varaible if available.
* Otherwise, returns the region from the metadata service.
* If metadata is not available, returns undefined.
*/
static async getRegion(): Promise<string | undefined> {
if (process.env.FUNCTION_REGION) {
return process.env.FUNCTION_REGION;
}

try {
// Example returned region format: /process/1234567/us-central
const segments = ((await metadata.instance('region')) as string).split(
'/'
);
return segments[segments.length - 1];
} catch (err) {
return undefined;
}
}

static async getSourceContextFromFile(): Promise<SourceContext> {
// If read errors, the error gets thrown to the caller.
const contents = await readFilep('source-context.json', 'utf8');
Expand Down
157 changes: 124 additions & 33 deletions test/test-debuglet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -856,6 +856,80 @@ describe('Debuglet', () => {
debuglet.start();
});

it('should attempt to retreive region correctly if needed', done => {
const savedGetPlatform = Debuglet.getPlatform;
Debuglet.getPlatform = () => Platforms.CLOUD_FUNCTION;

const clusterScope = nock(gcpMetadata.HOST_ADDRESS)
.get('/computeMetadata/v1/instance/region')
.once()
.reply(200, '123/456/region_name', gcpMetadata.HEADERS);

const debug = new Debug(
{projectId: 'fake-project', credentials: fakeCredentials},
packageInfo
);

nocks.oauth2();

const config = debugletConfig();
const debuglet = new Debuglet(debug, config);
const scope = nock(config.apiUrl)
.post(REGISTER_PATH)
.reply(200, {
debuggee: {id: DEBUGGEE_ID},
});

debuglet.once('registered', () => {
Debuglet.getPlatform = savedGetPlatform;
assert.strictEqual(
(debuglet.debuggee as Debuggee).labels?.region,
'region_name'
);
debuglet.stop();
clusterScope.done();
scope.done();
done();
});

debuglet.start();
});

it('should continue to register when could not get region', done => {
const savedGetPlatform = Debuglet.getPlatform;
Debuglet.getPlatform = () => Platforms.CLOUD_FUNCTION;

const clusterScope = nock(gcpMetadata.HOST_ADDRESS)
.get('/computeMetadata/v1/instance/region')
.once()
.reply(400);

const debug = new Debug(
{projectId: 'fake-project', credentials: fakeCredentials},
packageInfo
);

nocks.oauth2();

const config = debugletConfig();
const debuglet = new Debuglet(debug, config);
const scope = nock(config.apiUrl)
.post(REGISTER_PATH)
.reply(200, {
debuggee: {id: DEBUGGEE_ID},
});

debuglet.once('registered', () => {
Debuglet.getPlatform = savedGetPlatform;
debuglet.stop();
clusterScope.done();
scope.done();
done();
});

debuglet.start();
});

it('should pass config source context to api', done => {
const REPO_URL =
'https://github.com/non-existent-users/non-existend-repo';
Expand Down Expand Up @@ -1462,7 +1536,8 @@ describe('Debuglet', () => {
{service: 'some-service', version: 'production'},
{},
false,
packageInfo
packageInfo,
Platforms.DEFAULT
);
assert.ok(debuggee);
assert.ok(debuggee.labels);
Expand All @@ -1477,7 +1552,8 @@ describe('Debuglet', () => {
{service: 'default', version: 'yellow.5'},
{},
false,
packageInfo
packageInfo,
Platforms.DEFAULT
);
assert.ok(debuggee);
assert.ok(debuggee.labels);
Expand All @@ -1493,6 +1569,7 @@ describe('Debuglet', () => {
{},
false,
packageInfo,
Platforms.DEFAULT,
undefined,
'Some Error Message'
);
Expand All @@ -1507,7 +1584,8 @@ describe('Debuglet', () => {
{enableCanary: true, allowCanaryOverride: true},
{},
false,
packageInfo
packageInfo,
Platforms.DEFAULT
);
assert.strictEqual(debuggee.canaryMode, 'CANARY_MODE_DEFAULT_ENABLED');
});
Expand All @@ -1519,7 +1597,8 @@ describe('Debuglet', () => {
{enableCanary: true, allowCanaryOverride: false},
{},
false,
packageInfo
packageInfo,
Platforms.DEFAULT
);
assert.strictEqual(debuggee.canaryMode, 'CANARY_MODE_ALWAYS_ENABLED');
});
Expand All @@ -1531,7 +1610,8 @@ describe('Debuglet', () => {
{enableCanary: false, allowCanaryOverride: true},
{},
false,
packageInfo
packageInfo,
Platforms.DEFAULT
);
assert.strictEqual(debuggee.canaryMode, 'CANARY_MODE_DEFAULT_DISABLED');
});
Expand All @@ -1543,54 +1623,65 @@ describe('Debuglet', () => {
{enableCanary: false, allowCanaryOverride: false},
{},
false,
packageInfo
packageInfo,
Platforms.DEFAULT
);
assert.strictEqual(debuggee.canaryMode, 'CANARY_MODE_ALWAYS_DISABLED');
});
});

describe('getPlatform', () => {
it('should correctly identify default platform.', () => {
const debuggee = Debuglet.createDebuggee(
'some project',
'id',
{service: 'some-service', version: 'production'},
{},
false,
packageInfo
);
assert.ok(debuggee.labels!.platform === Platforms.DEFAULT);
assert.ok(Debuglet.getPlatform() === Platforms.DEFAULT);
});

it('should correctly identify GCF (legacy) platform.', () => {
// GCF sets this env var on older runtimes.
process.env.FUNCTION_NAME = 'mock';
const debuggee = Debuglet.createDebuggee(
'some project',
'id',
{service: 'some-service', version: 'production'},
{},
false,
packageInfo
);
assert.ok(debuggee.labels!.platform === Platforms.CLOUD_FUNCTION);
assert.ok(Debuglet.getPlatform() === Platforms.CLOUD_FUNCTION);
delete process.env.FUNCTION_NAME; // Don't contaminate test environment.
});

it('should correctly identify GCF (modern) platform.', () => {
// GCF sets this env var on modern runtimes.
process.env.FUNCTION_TARGET = 'mock';
const debuggee = Debuglet.createDebuggee(
'some project',
'id',
{service: 'some-service', version: 'production'},
{},
false,
packageInfo
);
assert.ok(debuggee.labels!.platform === Platforms.CLOUD_FUNCTION);
assert.ok(Debuglet.getPlatform() === Platforms.CLOUD_FUNCTION);
delete process.env.FUNCTION_TARGET; // Don't contaminate test environment.
});
});

describe('getRegion', () => {
it('should return function region for older GCF runtime', async () => {
process.env.FUNCTION_REGION = 'mock';

assert.ok((await Debuglet.getRegion()) === 'mock');

delete process.env.FUNCTION_REGION;
});

it('should return region for newer GCF runtime', async () => {
const clusterScope = nock(gcpMetadata.HOST_ADDRESS)
.get('/computeMetadata/v1/instance/region')
.once()
.reply(200, '123/456/region_name', gcpMetadata.HEADERS);

assert.ok((await Debuglet.getRegion()) === 'region_name');

clusterScope.done();
});

it('should return undefined when cannot get region metadata', async () => {
const clusterScope = nock(gcpMetadata.HOST_ADDRESS)
.get('/computeMetadata/v1/instance/region')
.once()
.reply(400);

assert.ok((await Debuglet.getRegion()) === undefined);

clusterScope.done();
});
});

describe('_createUniquifier', () => {
it('should create a unique string', () => {
const fn = Debuglet._createUniquifier;
Expand Down

0 comments on commit a88e904

Please sign in to comment.