diff --git a/.gitmodules b/.gitmodules index 0d99efd5..7989090c 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,3 +7,9 @@ [submodule "packages/grpc-js/deps/udpa"] path = packages/grpc-js/deps/udpa url = https://github.com/cncf/udpa.git +[submodule "packages/grpc-js/deps/googleapis"] + path = packages/grpc-js/deps/googleapis + url = https://github.com/googleapis/googleapis.git +[submodule "packages/grpc-js/deps/protoc-gen-validate"] + path = packages/grpc-js/deps/protoc-gen-validate + url = https://github.com/envoyproxy/protoc-gen-validate.git diff --git a/packages/grpc-js/deps/googleapis b/packages/grpc-js/deps/googleapis new file mode 160000 index 00000000..8c53b2cb --- /dev/null +++ b/packages/grpc-js/deps/googleapis @@ -0,0 +1 @@ +Subproject commit 8c53b2cb792234354c13336ac7daee61333deade diff --git a/packages/grpc-js/deps/protoc-gen-validate b/packages/grpc-js/deps/protoc-gen-validate new file mode 160000 index 00000000..0af61d9d --- /dev/null +++ b/packages/grpc-js/deps/protoc-gen-validate @@ -0,0 +1 @@ +Subproject commit 0af61d9dc28712dc0e6f8e1a940855a2ee0cb9ed diff --git a/packages/grpc-js/generateTypes.sh b/packages/grpc-js/generateTypes.sh new file mode 100644 index 00000000..8eee726c --- /dev/null +++ b/packages/grpc-js/generateTypes.sh @@ -0,0 +1,20 @@ +#!/bin/sh +# 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. + +base=$(dirname $0) + +./node_modules/.bin/proto-loader-gen-types --keepCase --longs String --enums String --defaults --oneofs --json\ + --includeDirs deps/envoy-api/ deps/udpa/ node_modules/protobufjs/ deps/googleapis/ deps/protoc-gen-validate/ \ + -O src/generated/ --grpcLib ../index envoy/service/discovery/v2/ads.proto envoy/api/v2/listener.proto envoy/api/v2/route.proto envoy/api/v2/cluster.proto envoy/api/v2/endpoint.proto \ No newline at end of file diff --git a/packages/grpc-js/package.json b/packages/grpc-js/package.json index 86243b30..2e5836f5 100644 --- a/packages/grpc-js/package.json +++ b/packages/grpc-js/package.json @@ -15,7 +15,7 @@ "types": "build/src/index.d.ts", "license": "Apache-2.0", "devDependencies": { - "@grpc/proto-loader": "^0.5.4", + "@grpc/proto-loader": "^0.6.0-pre3", "@types/gulp": "^4.0.6", "@types/gulp-mocha": "0.0.32", "@types/lodash": "^4.14.108", diff --git a/packages/grpc-js/src/xds-bootstrap.ts b/packages/grpc-js/src/xds-bootstrap.ts new file mode 100644 index 00000000..95fd96e3 --- /dev/null +++ b/packages/grpc-js/src/xds-bootstrap.ts @@ -0,0 +1,113 @@ +/* + * 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 * as adsTypes from './generated/ads'; + +/* eslint-disable @typescript-eslint/no-explicit-any */ + +export interface ChannelCredsConfig { + type: string; + config?: object; +} + +export interface XdsServerConfig { + serverUri: string; + channelCreds: ChannelCredsConfig[]; +} + +export interface BootstrapInfo { + xdsServers: XdsServerConfig[]; + node: adsTypes.messages.envoy.api.v2.core.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'); + } + return { + serverUri: obj.server_uri, + channelCreds: obj.channel_creds.map(validateChannelCredsConfig) + }; +} + +function validateNode(obj: any): adsTypes.messages.envoy.api.v2.core.Node { + throw new Error('Not implemented'); +} + +function validateBootstrapFile(obj: any): BootstrapInfo { + return { + xdsServers: obj.xds_servers.map(validateXdsServerConfig), + node: validateNode(obj.node) + } +} + +let loadedBootstrapInfo: Promise | null = null; + +export async function loadBootstrapInfo(): Promise { + if (loadedBootstrapInfo !== null) { + return loadedBootstrapInfo; + } + const bootstrapPath = process.env.GRPC_XDS_BOOTSTRAP; + if (bootstrapPath === undefined) { + return Promise.reject(new Error('GRPC_XDS_BOOTSTRAP environment variable needs to be set to the path to the bootstrap file to use xDS')); + } + 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; +} \ No newline at end of file diff --git a/packages/grpc-js/src/xds-client.ts b/packages/grpc-js/src/xds-client.ts new file mode 100644 index 00000000..e8eea6f3 --- /dev/null +++ b/packages/grpc-js/src/xds-client.ts @@ -0,0 +1,49 @@ +/* + * 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 * as protoLoader from '@grpc/proto-loader'; +import { loadPackageDefinition } from './make-client'; +import * as adsTypes from './generated/ads'; +import { ChannelCredentials } from './channel-credentials'; + +const packageDefinition = protoLoader.loadSync([ + 'envoy/service/discovery/v2/ads.proto', + 'envoy/api/v2/listener.proto', + 'envoy/api/v2/route.proto', + 'envoy/api/v2/cluster.proto', + 'envoy/api/v2/endpoint.proto' + ], { + keepCase: true, + longs: String, + enums: String, + defaults: true, + oneofs: true, + includeDirs: [ + 'deps/envoy-api/', + 'deps/udpa/', + 'node_modules/protobufjs/', + 'deps/googleapis/', + 'deps/protoc-gen-validate/' + ] + }); + +const loadedDefinition = loadPackageDefinition(packageDefinition) as unknown as adsTypes.ProtoGrpcType; + +export class XdsClient { + +} \ No newline at end of file