UNPKG

etquia

Version:

Dummy package for the grpc-node repository

310 lines (290 loc) 8.59 kB
/* * Copyright 2020 gRPC authors. * * 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 * as fs from 'fs'; import { Struct } from './generated/google/protobuf/Struct'; import { Value } from './generated/google/protobuf/Value'; /* eslint-disable @typescript-eslint/no-explicit-any */ export interface Locality { region?: string; zone?: string; sub_zone?: string; } export interface Node { id: string, locality: Locality; cluster?: string; metadata?: Struct; } export interface ChannelCredsConfig { type: string; config?: object; } export interface XdsServerConfig { serverUri: string; channelCreds: ChannelCredsConfig[]; serverFeatures: string[]; } export interface BootstrapInfo { xdsServers: XdsServerConfig[]; node: Node; } function validateChannelCredsConfig(obj: any): ChannelCredsConfig { if (!('type' in obj)) { throw new Error('type field missing in xds_servers.channel_creds element'); } if (typeof obj.type !== 'string') { throw new Error( `xds_servers.channel_creds.type field: expected string, got ${typeof obj.type}` ); } if ('config' in obj) { if (typeof obj.config !== 'object' || obj.config === null) { throw new Error( 'xds_servers.channel_creds config field must be an object if provided' ); } } return { type: obj.type, config: obj.config, }; } function validateXdsServerConfig(obj: any): XdsServerConfig { if (!('server_uri' in obj)) { throw new Error('server_uri field missing in xds_servers element'); } if (typeof obj.server_uri !== 'string') { throw new Error( `xds_servers.server_uri field: expected string, got ${typeof obj.server_uri}` ); } if (!('channel_creds' in obj)) { throw new Error('channel_creds missing in xds_servers element'); } if (!Array.isArray(obj.channel_creds)) { throw new Error( `xds_servers.channel_creds field: expected array, got ${typeof obj.channel_creds}` ); } if (obj.channel_creds.length === 0) { throw new Error( 'xds_servers.channel_creds field: at least one entry is required' ); } if ('server_features' in obj) { if (!Array.isArray(obj.server_features)) { throw new Error( `xds_servers.server_features field: expected array, got ${typeof obj.server_features}` ); } for (const feature of obj.server_features) { if (typeof feature !== 'string') { `xds_servers.server_features field element: expected string, got ${typeof feature}` } } } return { serverUri: obj.server_uri, channelCreds: obj.channel_creds.map(validateChannelCredsConfig), serverFeatures: obj.server_features ?? [] }; } function validateValue(obj: any): Value { if (Array.isArray(obj)) { return { kind: 'listValue', listValue: { values: obj.map((value) => validateValue(value)), }, }; } else { switch (typeof obj) { case 'boolean': return { kind: 'boolValue', boolValue: obj, }; case 'number': return { kind: 'numberValue', numberValue: obj, }; case 'string': return { kind: 'stringValue', stringValue: obj, }; case 'object': if (obj === null) { return { kind: 'nullValue', nullValue: 'NULL_VALUE', }; } else { return { kind: 'structValue', structValue: getStructFromJson(obj), }; } default: throw new Error(`Could not handle struct value of type ${typeof obj}`); } } } function getStructFromJson(obj: any): Struct { if (typeof obj !== 'object' || obj === null) { throw new Error('Invalid JSON object for Struct field'); } const fields: { [key: string]: Value } = {}; for (const [fieldName, value] of Object.entries(obj)) { fields[fieldName] = validateValue(value); } return { fields, }; } /** * Validate that the input obj is a valid Node proto message. Only checks the * fields we expect to see: id, cluster, locality, and metadata. * @param obj */ function validateNode(obj: any): Node { const result: Node = { id: '', locality: {} }; if (!('id' in obj)) { throw new Error('id field missing in node element'); } if (typeof obj.id !== 'string') { throw new Error(`node.id field: expected string, got ${typeof obj.id}`); } result.id = obj.id; if (!('locality' in obj)) { throw new Error('locality field missing in node element'); } result.locality = {}; if ('region' in obj.locality) { if (typeof obj.locality.region !== 'string') { throw new Error( `node.locality.region field: expected string, got ${typeof obj.locality .region}` ); } result.locality.region = obj.locality.region; } if ('zone' in obj.locality) { if (typeof obj.locality.zone !== 'string') { throw new Error( `node.locality.zone field: expected string, got ${typeof obj.locality .zone}` ); } result.locality.zone = obj.locality.zone; } if ('sub_zone' in obj.locality) { if (typeof obj.locality.sub_zone !== 'string') { throw new Error( `node.locality.sub_zone field: expected string, got ${typeof obj .locality.sub_zone}` ); } result.locality.sub_zone = obj.locality.sub_zone; } if ('cluster' in obj) { if (typeof obj.cluster !== 'string') { throw new Error( `node.cluster field: expected string, got ${typeof obj.cluster}` ); } result.cluster = obj.cluster; } if ('metadata' in obj) { result.metadata = getStructFromJson(obj.metadata); } return result; } function validateBootstrapFile(obj: any): BootstrapInfo { return { xdsServers: obj.xds_servers.map(validateXdsServerConfig), node: validateNode(obj.node), }; } let loadedBootstrapInfo: Promise<BootstrapInfo> | null = null; export async function loadBootstrapInfo(): Promise<BootstrapInfo> { if (loadedBootstrapInfo !== null) { return loadedBootstrapInfo; } /** * If GRPC_XDS_BOOTSTRAP exists * then use its value as the name of the bootstrap file. * * If the file is missing or the contents of the file are malformed, * return an error. */ const bootstrapPath = process.env.GRPC_XDS_BOOTSTRAP; if (bootstrapPath) { loadedBootstrapInfo = new Promise((resolve, reject) => { fs.readFile(bootstrapPath, { encoding: 'utf8' }, (err, data) => { if (err) { reject( new Error( `Failed to read xDS bootstrap file from path ${bootstrapPath} with error ${err.message}` ) ); } try { const parsedFile = JSON.parse(data); resolve(validateBootstrapFile(parsedFile)); } catch (e) { reject( new Error( `Failed to parse xDS bootstrap file at path ${bootstrapPath} with error ${e.message}` ) ); } }); }); return loadedBootstrapInfo; } /** * Else, if GRPC_XDS_BOOTSTRAP_CONFIG exists * then use its value as the bootstrap config. * * If the value is malformed, return an error. * * See: https://github.com/grpc/grpc-node/issues/1868 */ const bootstrapConfig = process.env.GRPC_XDS_BOOTSTRAP_CONFIG; if (bootstrapConfig) { try { const parsedConfig = JSON.parse(bootstrapConfig); const loadedBootstrapInfoValue = validateBootstrapFile(parsedConfig); loadedBootstrapInfo = Promise.resolve(loadedBootstrapInfoValue); } catch (e) { throw new Error( `Failed to parse xDS bootstrap config from environment variable GRPC_XDS_BOOTSTRAP_CONFIG with error ${e.message}` ); } return loadedBootstrapInfo; } return Promise.reject( new Error( 'The GRPC_XDS_BOOTSTRAP or GRPC_XDS_BOOTSTRAP_CONFIG environment variables need to be set to the path to the bootstrap file to use xDS' ) ); }