-
Notifications
You must be signed in to change notification settings - Fork 29
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Extend GCP Residency Detection Support (#528)
* feat: Extend GCP Residency Detection Support * test: update tests * test: init `gcp-residency` tests * chore: add temporary debug logs for `windows` * chore: Remove debug logs for windows * test: Add some tests for `gcp-residency` * refactor: use MAC Address to determine GCE residency * refactor: Re-Add Linux GCE detection * feat: Extend GCP Serverless Runtime Support - A refactor + Cloud Run Job support
- Loading branch information
1 parent
12e7bda
commit 2b35bb0
Showing
5 changed files
with
354 additions
and
25 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
/** | ||
* Copyright 2022 Google LLC | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
import {readFileSync, statSync} from 'fs'; | ||
import {networkInterfaces, platform} from 'os'; | ||
|
||
/** | ||
* Known paths unique to Google Compute Engine Linux instances | ||
*/ | ||
export const GCE_LINUX_BIOS_PATHS = { | ||
BIOS_DATE: '/sys/class/dmi/id/bios_date', | ||
BIOS_VENDOR: '/sys/class/dmi/id/bios_vendor', | ||
}; | ||
|
||
const GCE_MAC_ADDRESS_REGEX = /^42:01/; | ||
|
||
/** | ||
* Determines if the process is running on a Google Cloud Serverless environment (Cloud Run or Cloud Functions instance). | ||
* | ||
* Uses the: | ||
* - {@link https://cloud.google.com/run/docs/container-contract#env-vars Cloud Run environment variables}. | ||
* - {@link https://cloud.google.com/functions/docs/env-var Cloud Functions environment variables}. | ||
* | ||
* @returns {boolean} `true` if the process is running on GCP serverless, `false` otherwise. | ||
*/ | ||
export function isGoogleCloudServerless(): boolean { | ||
/** | ||
* `CLOUD_RUN_JOB` is used for Cloud Run Jobs | ||
* - See {@link https://cloud.google.com/run/docs/container-contract#env-vars Cloud Run environment variables}. | ||
* | ||
* `FUNCTION_NAME` is used in older Cloud Functions environments: | ||
* - See {@link https://cloud.google.com/functions/docs/env-var Python 3.7 and Go 1.11}. | ||
* | ||
* `K_SERVICE` is used in Cloud Run and newer Cloud Functions environments: | ||
* - See {@link https://cloud.google.com/run/docs/container-contract#env-vars Cloud Run environment variables}. | ||
* - See {@link https://cloud.google.com/functions/docs/env-var Cloud Functions newer runtimes}. | ||
*/ | ||
const isGFEnvironment = | ||
process.env.CLOUD_RUN_JOB || | ||
process.env.FUNCTION_NAME || | ||
process.env.K_SERVICE; | ||
|
||
return !!isGFEnvironment; | ||
} | ||
|
||
/** | ||
* Determines if the process is running on a Linux Google Compute Engine instance. | ||
* | ||
* @returns {boolean} `true` if the process is running on Linux GCE, `false` otherwise. | ||
*/ | ||
export function isGoogleComputeEngineLinux(): boolean { | ||
if (platform() !== 'linux') return false; | ||
|
||
try { | ||
// ensure this file exist | ||
statSync(GCE_LINUX_BIOS_PATHS.BIOS_DATE); | ||
|
||
// ensure this file exist and matches | ||
const biosVendor = readFileSync(GCE_LINUX_BIOS_PATHS.BIOS_VENDOR, 'utf8'); | ||
|
||
return /Google/.test(biosVendor); | ||
} catch { | ||
return false; | ||
} | ||
} | ||
|
||
/** | ||
* Determines if the process is running on a Google Compute Engine instance with a known | ||
* MAC address. | ||
* | ||
* @returns {boolean} `true` if the process is running on GCE (as determined by MAC address), `false` otherwise. | ||
*/ | ||
export function isGoogleComputeEngineMACAddress(): boolean { | ||
const interfaces = networkInterfaces(); | ||
|
||
for (const item of Object.values(interfaces)) { | ||
if (!item) continue; | ||
|
||
for (const {mac} of item) { | ||
if (GCE_MAC_ADDRESS_REGEX.test(mac)) { | ||
return true; | ||
} | ||
} | ||
} | ||
|
||
return false; | ||
} | ||
|
||
/** | ||
* Determines if the process is running on a Google Compute Engine instance. | ||
* | ||
* @returns {boolean} `true` if the process is running on GCE, `false` otherwise. | ||
*/ | ||
export function isGoogleComputeEngine(): boolean { | ||
return isGoogleComputeEngineLinux() || isGoogleComputeEngineMACAddress(); | ||
} | ||
|
||
/** | ||
* Determines if the process is running on Google Cloud Platform. | ||
* | ||
* @returns {boolean} `true` if the process is running on GCP, `false` otherwise. | ||
*/ | ||
export function detectGCPResidency(): boolean { | ||
return isGoogleCloudServerless() || isGoogleComputeEngine(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,195 @@ | ||
/** | ||
* Copyright 2022 Google LLC | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
import {strict as assert} from 'assert'; | ||
import * as fs from 'fs'; | ||
import * as os from 'os'; | ||
|
||
import {beforeEach, describe, it} from 'mocha'; | ||
import {SinonSandbox, createSandbox} from 'sinon'; | ||
|
||
import * as gcpResidency from '../src/gcp-residency'; | ||
|
||
const ENVIRONMENT_BACKUP = {...process.env}; | ||
|
||
describe('gcp-residency', () => { | ||
let sandbox: SinonSandbox; | ||
|
||
beforeEach(() => { | ||
process.env = {...ENVIRONMENT_BACKUP}; | ||
sandbox = createSandbox(); | ||
removeServerlessEnvironmentVariables(); | ||
}); | ||
|
||
afterEach(() => { | ||
sandbox.restore(); | ||
}); | ||
|
||
/** | ||
* A simple utility for stubbing the networkInterface for GCE emulation. | ||
* | ||
* @param isGCE determines if the address should begin with `42:01` or not | ||
*/ | ||
function setGCENetworkInterface(isGCE = true) { | ||
const mac = isGCE ? '42:01:00:00:00:00' : '00:00:00:00:00:00'; | ||
|
||
sandbox.stub(os, 'networkInterfaces').returns({ | ||
'test-interface': [{mac} as os.NetworkInterfaceInfo], | ||
}); | ||
} | ||
|
||
/** | ||
* A simple utility for stubbing the platform for GCE emulation. | ||
* | ||
* @param platform a Node.js platform | ||
*/ | ||
function setGCEPlatform(platform: NodeJS.Platform = 'linux') { | ||
sandbox.stub(os, 'platform').returns(platform); | ||
} | ||
|
||
/** | ||
* A simple utility for stubbing the Linux BIOS files for GCE emulation. | ||
* | ||
* @param isGCE options: | ||
* - set `true` to simulate the files exist and are GCE | ||
* - set `false` for exist, but are not GCE | ||
* - set `null` for simulate ENOENT | ||
*/ | ||
function setGCELinuxBios(isGCE: boolean | null) { | ||
sandbox.stub(fs, 'statSync').callsFake(path => { | ||
assert.equal(path, gcpResidency.GCE_LINUX_BIOS_PATHS.BIOS_DATE); | ||
|
||
return undefined; | ||
}); | ||
|
||
sandbox.stub(fs, 'readFileSync').callsFake((path, encoding) => { | ||
assert.equal(path, gcpResidency.GCE_LINUX_BIOS_PATHS.BIOS_VENDOR); | ||
assert.equal(encoding, 'utf8'); | ||
|
||
if (isGCE === true) { | ||
return 'x Google x'; | ||
} else if (isGCE === false) { | ||
return 'Sandwich Co.'; | ||
} else { | ||
throw new Error("File doesn't exist"); | ||
} | ||
}); | ||
} | ||
|
||
function removeServerlessEnvironmentVariables() { | ||
delete process.env.CLOUD_RUN_JOB; | ||
delete process.env.FUNCTION_NAME; | ||
delete process.env.K_SERVICE; | ||
} | ||
|
||
describe('isGoogleCloudServerless', () => { | ||
it('should return `true` if `CLOUD_RUN_JOB` env is set', () => { | ||
process.env.CLOUD_RUN_JOB = '1'; | ||
|
||
assert(gcpResidency.isGoogleCloudServerless()); | ||
}); | ||
|
||
it('should return `true` if `FUNCTION_NAME` env is set', () => { | ||
process.env.FUNCTION_NAME = '1'; | ||
|
||
assert(gcpResidency.isGoogleCloudServerless()); | ||
}); | ||
|
||
it('should return `true` if `K_SERVICE` env is set', () => { | ||
process.env.K_SERVICE = '1'; | ||
|
||
assert(gcpResidency.isGoogleCloudServerless()); | ||
}); | ||
|
||
it('should return `false` if none of the envs are set', () => { | ||
assert.equal(gcpResidency.isGoogleCloudServerless(), false); | ||
}); | ||
}); | ||
|
||
describe('isGoogleComputeEngine', () => { | ||
it('should return `true` if on Linux and has the expected BIOS files', () => { | ||
setGCENetworkInterface(false); | ||
setGCEPlatform('linux'); | ||
setGCELinuxBios(true); | ||
|
||
assert.equal(gcpResidency.isGoogleComputeEngine(), true); | ||
}); | ||
|
||
it('should return `false` if on Linux and the expected BIOS files are not GCE', () => { | ||
setGCENetworkInterface(false); | ||
setGCEPlatform('linux'); | ||
setGCELinuxBios(false); | ||
|
||
assert.equal(gcpResidency.isGoogleComputeEngine(), false); | ||
}); | ||
|
||
it('should return `false` if on Linux and the BIOS files do not exist', () => { | ||
setGCENetworkInterface(false); | ||
setGCEPlatform('linux'); | ||
setGCELinuxBios(null); | ||
|
||
assert.equal(gcpResidency.isGoogleComputeEngine(), false); | ||
}); | ||
|
||
it('should return `true` if the host MAC address begins with `42:01`', () => { | ||
setGCENetworkInterface(true); | ||
setGCEPlatform('win32'); | ||
setGCELinuxBios(null); | ||
|
||
assert.equal(gcpResidency.isGoogleComputeEngine(), true); | ||
}); | ||
|
||
it('should return `false` if the host MAC address does not begin with `42:01` & is not Linux', () => { | ||
setGCENetworkInterface(false); | ||
setGCEPlatform('win32'); | ||
setGCELinuxBios(null); | ||
|
||
assert.equal(gcpResidency.isGoogleComputeEngine(), false); | ||
}); | ||
}); | ||
|
||
describe('detectGCPResidency', () => { | ||
it('should return `true` if `isGoogleCloudServerless`', () => { | ||
// `isGoogleCloudServerless` = true | ||
process.env.K_SERVICE = '1'; | ||
|
||
// `isGoogleComputeEngine` = false | ||
setGCENetworkInterface(false); | ||
|
||
assert(gcpResidency.detectGCPResidency()); | ||
}); | ||
|
||
it('should return `true` if `isGoogleComputeEngine`', () => { | ||
// `isGoogleCloudServerless` = false | ||
removeServerlessEnvironmentVariables(); | ||
|
||
// `isGoogleComputeEngine` = true | ||
setGCENetworkInterface(true); | ||
|
||
assert(gcpResidency.detectGCPResidency()); | ||
}); | ||
|
||
it('should return `false` !`isGoogleCloudServerless` && !`isGoogleComputeEngine`', () => { | ||
// `isGoogleCloudServerless` = false | ||
removeServerlessEnvironmentVariables(); | ||
|
||
// `isGoogleComputeEngine` = false | ||
setGCENetworkInterface(false); | ||
|
||
assert.equal(gcpResidency.detectGCPResidency(), false); | ||
}); | ||
}); | ||
}); |
Oops, something went wrong.