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

feat: Add region in Debuggee labels in GCF env #951

Merged
merged 3 commits into from
May 4, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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();
Louis-Ye marked this conversation as resolved.
Show resolved Hide resolved
}

// 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